NESDev and Strangulation Records messageboards
Forum Index | FAQ | New User | Login | Search

Previous ThreadView All ThreadsNext Thread*Show in Threaded Mode


SubjectTriangle's linear counter new  
Posted byHugin
Posted on7/24/03 7:02 PM



I've read Brad's excellent NESSOUND.txt doc, I've experimented back and forth with my emu and I've gone through other's source, but I just can't understand that how that damn linear counter works. Hopefully, some of you can help me out - I'm completely lost:

1. What's the purpose of the linear counter? Is it only there for specifying a more precise length of each note? Why would you want it to be a feature of the triangle channel only?
2. Are both the linear counter and the length counter used at the same time? I mean, does the linear counter count down at 240 Hz _parallel_ to the length counter at 60 Hz, as opposed to one finishing its counting before the other starts?
3. If (2), what's the use of the length counter anyway?
4. If (2), does the channel go silent when they both reach zero or when either one does?
5. If NOT(2), how do I know which one is the 'active' counter? Brad's doc says "length counter clock disable / linear counter start" about $4008.7.

As of now, my emu works well except for some long notes which are shortened. This is probably due to me not clocking the length counter correctly. I'm not sure when to enable/disable the length counter or the linear counter.

Any more info on the linear counter that you can think of would be appreciated.

/Confused




SubjectRe: Triangle's linear counter new  
Posted byMemblers
Posted on7/25/03 02:16 AM
From IP68.58.99.218  



I cheated when I implemented the linear counter in my NSF player (on SNES), but I haven't noticed any problems with it. I triggered the linear counter when $400B was written to, if $4008.7 was set.

And I completely ignored the length counter for the triangle channel. I don't recall seeing any game try to use it. (has anyone?)

1. Yeah, it's very high precision and only on the triangle channel. Games by Konami rely upon it extensively.
2. Sounds to me like the linear counter disables the length counter.
5. Sounds like if $4008.7 is clear, length counter is in effect, and if it's set, then the linear counter could be in effect.

All the counters in my emu seem to work fine when I use a write to the high-byte of frequency as the trigger.




SubjectRe: Triangle's linear counter new  
Posted byDisch
Posted on7/26/03 8:28 PM
From IP66.127.105.177  



Ugh... the painful linear counter...

I followed the doc to the letter and it still didn't work completely. After checking other nsf players I found 3 things that needed to be changed:

- When a write to $400B takes place... the counter goes into load mode (and anything in the load register gets loaded into the counter). If $4008.7 was 0, it signals the counter to enter count mode on the next linear counter clock. This is what the NESSOUND doc specifies... however... in that next linear clock, you only go to count mode if $4008.7 is still 0.

- Changing the mode upon writes to $4008 like the doc specifies seems to cause more problems than it solves. When a write to $4008 takes place, don't touch the mode of the linear counter.

- if the channel is disabled via $4015, the linear counter shouldn't be loaded with the load value even if in load mode. This doesn't seem right, but it seems to work o_O


I don't know if those problems are related... but I've had to do them to get the triangle channel working correctly.


For the details on how my counter works... here's snippits from my source:


// --- variables used ---

BYTE nLinearTimer; //'Load' would be a more accurate name. This is the load register
BYTE nLinearCount; //the counter
BYTE bLinearMode; //the current mode (0 = load, 1 = count)
BYTE bLinearStart; //$4008.7 bit
BYTE bLinearSignalCount; //signal to enter count on next linear clock




// --- linear clock ---

if( bLinearSignalCount && !bLinearStart )
{
bLinearSignalCount = 0;
bLinearMode = 1; //enter count mode
}

if(bLinearMode && nLinearCount)
nLinearCount--;



// --- writes to 4008 --- (val is the written value)

bLengthDisable = bLinearStart = val & 0x80;
nLinearTimer = val & 0x7F;
if(bChannelEnabled && !bLinearMode)
nLinearCount = nLinearTimer;


// --- writes to 400B ---

bLinearMode = 0;
if(!bLinearStart)
bLinearSignalCount = 1;
if(bChannelEnabled)
{
// [load the length counter here]
nLinearCount = nLinearTimer;
}
// [load MSB of wavelength here]





SubjectRe: Triangle's linear counter new  
Posted byTimW
Posted on7/31/03 9:54 PM
From IP209.179.53.245  



just posting to bring this back to the top of the board, lol. I'm having trouble understanding it too. from the docs it looks as though the linear and length counter must always be in use/nonuse similtaniously. It's weird, I mean the length counter does seem useless. Does anyone know for sure?? Also, Disch what game were you using to test your triangle wave code. you said it did some weird stuff and the notes were not playing at the proper length. I Would like to test that with my code.

Thanks,
Tim

Also Memblers, I got the square waves working well, not outstanding, but cool didn't sample though, just used counters and whatnot. For some reason I had trouble getting sampling/syntheses to work. Just FYI and thanks for all your help




SubjectRe: Triangle's linear counter  
Posted byDisch
Posted on7/31/03 10:22 PM
From IP66.127.105.177  



The length counter does seem kind of strange. If they're both running at the same time... in order for the length counter to EVER be used, it would have to be 1/4 of the linear counter (since it's clocked at 1/4th the speed). Why you would want to do that is beyond me.

>>
>> Also, Disch what game were you using to test your triangle wave code.
>>

All sorts... I'm working with an NSF player though... not an emulator.

But I've tested with the Megaman series, CV series, SMB series, the FamiCompo submissions (AI Bomb owns me), Maniac Mansion, and a bunch of other games.

The only one I can recall having any problems with is Megaman 1... which sounds strange at the intro to Fireman's stage, Iceman's stage, and during that little breakdown a few seconds into Bombman's stage. Though it's been so long since I played that game on the original console I forgot what they're supposed to sound like. And the emulators I've tried experience the same weirdness that I am.




SubjectRe: Triangle's linear counter new  
Posted byHugin
Posted on8/1/03 7:01 PM



Now, I've followed Brad's doc to the letter. The 'enable' condition for the triangle channel is:
enabled = (LinearCounter || (LengthCounter && $4008.7)) && ($4015.2)
This works well in all games I have tested. Disch, you said you had found some problems when following the doc to the letter. I was wondering, which game should I try out for this _not_ to work? Always bughunting... :)
Could someone direct me to a problematic linear counter game? I can only support the simplest mappers (iNES 0,2,3) so far.
/Hugin




SubjectRe: Triangle's linear counter new  
Posted byMemblers
Posted on8/1/03 7:34 PM
From IP68.58.99.218  



Most games don't really use the counter. Without a register viewer of some kind, it can be hard to tell the difference.

Some ones that come to mind that do use the counter are Life Force, Kid Icarus, Rush 'n Attack, and SMB2. Also, IIRC Megaman 1 was the only one in the series that used the linear counter.




SubjectRe: Triangle's linear counter new  
Posted byMemblers
Posted on8/1/03 7:40 PM
From IP68.58.99.218  



Oh, I was wrong, Megaman 2 uses it quite a bit also.

Megaman 1 sounds all screwed up on my NSF player too. Maybe because it zeros the frequency AND uses the linear counter? way to go, capcom :P




SubjectRe: Triangle's linear counter new  
Posted byDisch
Posted on8/1/03 8:31 PM
From IP66.127.105.177  



>> enabled = (LinearCounter || (LengthCounter && $4008.7))
>>

Hmm... From what I read:

Regardling Linear Counter:

"The counter is always being clocked, except when 0 appears on the output of the counter. At this point, the linear counter & triangle step counter clocks signals are disabled, which results in both counters latching their current state (the linear counter will stay at 0, and the triangle step counter will stop, and the channel will be silenced due to this)."



Regarding Length Counter:

"When the length counter arrives at a count of 0, the counter will be stopped (stay on 0), and the appropriate channel will be silenced."



That would lead me to believe that in order for the channel to be active, both the linear counter AND the length counter would have to be non-zero (since a count of zero in either counter would force silence). Your code snippit seems like it would have the channel active if EITHER is non-zero. Unless there's a special case scenario for the Length Counter for the Triangle channel... I don't see how that would be accurate.

I'm not doubting that this method works... in fact I'd like to test it myself in a bit ^_^. I'm just wondering how you came to that conclusion. Was there something in the doc that I missed? Or did you base yours off a previous version of the doc?

As for a game to test it on... it seems like Megaman 1 is causing the most problems. I don't know what mapper that game uses though >_<

I'll definatly have to try this method in a bit. If I can/can't get it working I'll post back here.




SubjectRe: Triangle's linear counter new  
Posted byHugin
Posted on8/1/03 9:21 PM



I first got the idea from Brad's doc:

________________________________________
$4008(tri) bits
---------------
0-6 linear counter load register
7 length counter clock disable / linear counter start
________________________________________

$4007.7 = "Length counter clock disable". You had the 'bLengthDisable' identifier in your own code snippet. If I just make the condition (Length || Linear), the channel never goes silent. This may be due to some other bug where I ought to set LengthCounter = 0, but it seems right to me. And most of all, it works. I haven't checked out Mega Man yet, I just responded immediatly to this post.


For what it's worth, I'm posting a part of my source. It's written with the _excellent_ assembler SpAsm. I know that people generally aren't very fond of assembly, but its ease of use and clarity is perfect for almost every software project. Personally I used C++ for one or two years before I came across SpAsm, which revolutionized my programming. Anyway, here's my triangle code:



; Regs
Write4008: ; 0 - 6: Linear counter, 7: Linear counter start
mov B$Triangle@Reg0 al

; Linear counter mode?
and B$Triangle@Reg0 al
ifNotFlag B$Triangle@Reg0 080, mov B$Triangle@LinearCounterMode LCM_COUNT

; Load linear counter latch
clearFlag al 080
mov B$Triangle@LinearCounterLatch al
if D$Triangle@LinearCounterMode = LCM_LOAD, mov B$Triangle@LinearCounter al

; Enabled?
call TriangleEnabled
ret

Write4009: ret
Write400A: ; 0 - 7: Low bits of frequency

; Update frequency
mov B$Triangle@TimerLatch al
call TriangleUpdateTimer

; Enabled?
call TriangleEnabled
ret

Write400B: ; 0 - 2: High bits of frequency, 3 - 7: length counter

; Update length counter
mov edx eax
shr dl 3 | and eax 01F
mov dl B$edx+LengthCounterTable
mov B$Triangle@LengthCounter dl

; Update frequency
and al 7
mov B$Triangle@TimerLatch+1 al
call TriangleUpdateTimer

; Update linear counter
mov D$Triangle@LinearCounterMode LCM_LOAD
copy D$Triangle@LinearCounterLatch D$Triangle@LinearCounter
test D$Triangle@Reg0 080 | setz B$Triangle@ChangeMode

; Enabled?
call TriangleEnabled
ret
____________________________________________________________________________________________
[LCM_LOAD 0
LCM_COUNT 1]
; Counters
TriangleClockLinearCounter:
; Mode = count
longIf D$Triangle@LinearCounterMode = LCM_COUNT
if D$Triangle@LinearCounter = 0, ret
dec D$Triangle@LinearCounter
jz TriangleEnabled
ret
endif

; Mode = load
copy D$Triangle@LinearCounterLatch D$Triangle@LinearCounter

; Change mode
if D$Triangle@ChangeMode = &FALSE, ret
mov D$Triangle@ChangeMode &FALSE
mov D$Triangle@LinearCounterMode LCM_COUNT
ret

TriangleClockLengthCounter:
; Active?
if D$Triangle@LengthCounter = 0, ret
if D$Triangle@Silenced = &TRUE, ret

; Count down
dec D$Triangle@LengthCounter
jz TriangleEnabled
ret

TriangleEnabled:
mov eax 0
or eax D$Triangle@LinearCounter
ifFlag B$Triangle@Reg0 080, or eax D$Triangle@LengthCounter
if D$Triangle@Silenced = &TRUE, mov eax 0 ; Silenced through $4015

test eax eax | setnz B$Triangle@Enabled
ret




SubjectRe: Triangle's linear counter new  
Posted byDisch
Posted on8/2/03 04:16 AM
From IP66.127.105.177  



If I knew assembly I could dissect that =P

haha...~feels ignorant~

if you could throw me some C++ equivilent code it'd be appreciated ^_^. In the meantime I'm struggling with the DMC =P ~goes to post other thread~




SubjectRe: Triangle's linear counter new  
Posted byHugin
Posted on8/5/03 6:15 PM



Disch, after a lot of trouble I finally found a few bugs in my triangle code and after having corrected them, I realized that you were right

all the time - don't change the linear counter mode on $4008 writes. Thanx.
I've used SMB 1 for testing, since I'm very familiar with its music (aren't we all?). After a few changes, it now sounds great. I didn't find

any weird sounds in Mega Man, though.

>- When a write to $400B takes place... the counter goes into load mode (and anything in the load register
>gets loaded into the counter). If $4008.7 was 0, it signals the counter to >enter count mode on the next
>linear counter clock. This is what the NESSOUND doc specifies... >however... in that next linear clock,
>you only go to count mode if $4008.7 is still 0.

This fixed a bug on the lava levels (SMB 1) where some notes were shortened.

>- Changing the mode upon writes to $4008 like the doc specifies seems to cause more problems than it solves.
>When a write to $4008 takes place, don't touch the mode of the linear counter.

This fixed a bug in the flag sequence (SMB 1) where some notes were shortened.

>- if the channel is disabled via $4015, the linear counter shouldn't be loaded with the load
>value even if in load mode. This doesn't seem right, but it seems to work o_O
The channel is always silent when $4015.2 = 1 (Since length counter = 0). The value of the linear counter shouldn't matter. In which game did

you find problems with this?


Below is a brief explanation of my triangle code, with more C-like syntax :)
Perhaps it will be useful to anyone who has followed this thread, or perhaps someone will find a bug and tell me about it.

$4008 write:
LinearCounterLoad = data & 0x7F
if (LinearCounterMode == LOAD)
LinearCounter = LinearCounterLoad

$400A write:
TimerLoad.low = data
Timer = TimerLoad

$400B write:
LengthCounter = LengthCounterTable[data >> 3]
TimerLoad.high = data & 7
Timer = TimerLoad
LinearCounterMode = LOAD
LinearCounter = LinearCounterLoad
ChangeMode = ($4008.7 == 0)

LinearClock:
if (LinearCounterMode == COUNT && LinearCounter)
LinearCounter--

if (ChangeMode)
ChangeMode = 0
if ($4008.7 = 0)
LinearCounterMode = COUNT

LengthClock:
if (LengthCounter && ($4008.7 = 0) && ($4015.2 = 0))
LengthCounter--

TriangleStepGenerator:
Timer = TimerLoad
if (($4015.2 = 0) && LinearCounter && LengthCounter) /* channel enabled? */
if (Output == 0xF) step--
if (Output == 0x0) step++
output += step

Thanx for your help.
/Hugin




SubjectRe: Triangle's linear counter new  
Posted byHugin
Posted on8/5/03 6:21 PM



Oops, my idents disappeared. I'll insert some brackets:

$4008 write:
LinearCounterLoad = data & 0x7F
if (LinearCounterMode == LOAD)
{
LinearCounter = LinearCounterLoad
}

$400A write:
TimerLoad.low = data
Timer = TimerLoad

$400B write:
LengthCounter = LengthCounterTable[data >> 3]
TimerLoad.high = data & 7
Timer = TimerLoad
LinearCounterMode = LOAD
LinearCounter = LinearCounterLoad
ChangeMode = ($4008.7 == 0)

LinearClock:
if (LinearCounterMode == COUNT && LinearCounter)
{
LinearCounter--
}

if (ChangeMode)
ChangeMode = 0
if ($4008.7 = 0)
{
LinearCounterMode = COUNT
}

LengthClock:
if (LengthCounter && ($4008.7 = 0) && ($4015.2 = 0))
{
LengthCounter--
}

TriangleStepGenerator:
Timer = TimerLoad
if (($4015.2 = 0) && LinearCounter && LengthCounter) /* channel enabled? */
{
if (Output == 0xF) step--
if (Output == 0x0) step++
output += step
}




SubjectRe: Triangle's linear counter new  
Posted byAnonymous
Posted on8/5/03 8:30 PM
From IP67.73.4.152  



Disch,

The condition for the channel to be disabled is, bDisabled = (!Length || !Linear) if either the linear counter or the length counter reaches zero channel goes. I took your advice btw, everything you said in the previous posts seems to work well about not touching the mode in 4008 writes. the sound seems to work well for megaman just to let you know you're on the right track. also, it doesn't matter if the channel is looping, as this only halts the count, if the count is zero the channel is off period. doesn't matter if its looping.






SubjectRe: Triangle's linear counter new  
Posted byDisch
Posted on8/5/03 8:45 PM
From IP66.127.105.177  



>The channel is always silent when $4015.2 = 1 (Since length counter = 0). The
>value of the linear counter shouldn't matter. In which game did
>you find problems with this?

I vaguely remember a game having problems with this... but after going back and taking out the 'fix'... I didn't notice any problems. Maybe one of the other tweaks fixed the same problem this did... or maybe my mind was elsewhere when thought I needed to impliment that =P

You're right though... it doesn't seem to do anything. Keep it in the back of your mind though... in case problems come up again later. For some reason I just get the feeling that this really did fix something.


Anyway... glad I could be of help ^_^. I'm going to try and mess with this some more... gonna try and figure out the proper way to do all of this... Brad's doc specifically says that writes to 4008 change the mode... so it seems odd that we have to completely disregard that to get it to work. I get the feeling we're doing things wrong but for some reason it works out anyway.


After re-reading the doc a few times, I noticed that I may have been reading something wrong. From the doc:

Writes to $400B
---------------
cur mode
--- ----
1 load
0 load (on next linear counter clock), count



"load (on next linear counter clock), count" -- I originally took that to mean it goes into Load mode immediatly, then enters count mode on the next linear clock (seems logical). But looking at it again... it looks like it might mean the opposite. Maybe it means enter Count mode immediately and enter load mode on the next linear clock.

I've tried this method (along with undoing all the little tweaks and re-implimenting 4008 so that it changes the mode)... and there are still problems.. but I might be on to something with this.

I started making a log of all the games' writes to the triangle's registers and looking at the order of which it writes to them to try and figure out how the counter is supposed to work in all cases. I only recorded like 2 games, then I got bored =P... so I've been sitting on it for a while. I'll pick it up again later.

Thanks for all of your help as well =) . Maybe if I ever get around to fully understanding this counter, I can write up a new doc to give to Memblers ^_^. I'm sure there are lots of coders who get confused by the Linear Counter.

-Disch-




SubjectRe: Triangle's linear counter new  
Posted byTimW
Posted on8/6/03 05:26 AM
From IP209.179.52.215  



disch, I tested megaman, and I had a lot of clicks in the sound. I found a way around it though, (use NNNester which enables channel toggeling to test it) I figure that the clicks were from the step generator. I added some code that seemed to clean the sound up a lot. I get lots better sound when I only clock the step generator if the wavelength, ie the value in 400a, 400b is not zero, it seems to clean things up quite a bit. it seems like the step generator should halt when the wavelength is zero. try it.

so the wave would look like this

012345(wavelength gets set to zero)55555555(wavelength non zero)6789....98765 I'll post my code if you want, I use your method when it comes to ignoring the mode in 4008 writes so our code should be similiar.






SubjectRe: Triangle's linear counter new  
Posted byTimW
Posted on8/6/03 05:38 AM
From IP209.179.52.215  



thinking it over it seems extremely logical that the generator would halt if the wavelength was zero, this could easly stop the output spikes from the programmible timer which control the step generator. this probably halts the duty cycle generator also. I'm going to impliment this in the duty cycle generator to see if I notice cleaner sound anywhere.




SubjectRe: Triangle's linear counter new  
Posted byDisch
Posted on8/6/03 06:02 AM
From IP66.127.105.177  



Yeah...I've been doing that too. I actually have my tri-step counter stop if the wavelength is less than 8 (I don't remember why I chose that number... I think I saw it in some other player's source or something). Before I put that in I noticed the same problems you were. It's not like a clicking though... it's just a really really high-pitched note.

At this point I'm willing to bet that the note shouldn't be shut out because of it's frequency.. but rather the linear timer isn't exactly accurate and the sound should be cutting out before the wavelength gets reset to zero. This is probably just another tweak to compensate for the inaccurate linear counter.

I gotta figure out the proper way to do all this stuff.




SubjectRe: Triangle's linear counter new  
Posted byDisch
Posted on8/6/03 06:06 AM
From IP66.127.105.177  



It should already kind of interpreted in your duty cycle generator.

The Sweep Unit silences the channel under certain conditions... one of them being a wavelength less than 8 (Brad's doc specifies this in the portion describing the Sweep Unit).

I don't know if it should be done on the Noise channel though.




SubjectAnother Tweak new  
Posted byDisch
Posted on9/6/03 8:31 PM
From IP66.127.105.177  



Yeah... this topic is old... but I found another tweak:

- Enter load mode on $4008 writes if the status $4008.7 has changed from its previous value.


This fixes track 10 on Maniac Mansion.. which a lot of NSF players (including my own) seem to have problems with. I haven't noticed any negative side effects in any other NSFs yet... but I can only try so many.




SubjectRe: Triangle's linear counter new  
Posted byTimW
Posted on9/8/03 1:36 PM
From IP216.244.20.32  



that actually hurt some of the games I have more then it helped, I haven't found problems with my triangle code, does the game you're testing use an obsure mapper? I'd like to try it. what I do have problems with is my square wave code, the only game I've tested where it doesn't work right is zelda, and only in the inro screen, I think this may be related to the problem you were talking about a while back. basically the two channles are playing the same sound but it's out of phase, it doesn't cancel it out like I you're problem, but it sounds jacked up, but I think it's an emualtion problem, did you ever fix the problems you where having with duck tales?




SubjectRe: Triangle's linear counter new  
Posted byDisch
Posted on9/8/03 3:50 PM
From IP66.127.105.177  



Which games are being messed up?

I have no idea about the mapper XD. NNNesterJ says " Mapper [ 1 -> 1 ] ".

Does your emu play the song correctly? (In Maniac Mansion, it's the song that plays whenever you run into one of the aliens. But the song stops after a few seconds so it's hard to hear the whole song from an emulator).

I don't think it's a bad NSF rip... but maybe it is. That would suck.

The problem with the song is the register writes to the tri channel work like so:

$4008 - $00 <-- writes 00 to 4008 first... presumably to stop the previous note
$4009 - $7F <-- writes 7F to 4009... don't know why, 4009 isn't used
$400A - ?? <-- sets low byte of freq
$400B - ?? <-- sets high byte of freq and length counter (don't think length counter is used)
$4008 - $8? <-- writes to 4008 again with the high bit set and something non-zero in the linear counter load reg.


Now... this works *sometimes*. If the linear counter gets clocked after the second 4008 write (most of the time), the counter gets loaded with the correct value and the song plays normally.

If the linear counter gets clocked between the 400B write and second 4008 write, then 0 gets put in the linear counter and the second 4008 write is ignored (this causes the note to be missed completely... sounds really out of place). This happens in my player as well as Festalon, and maybe some others, but I haven't really checked.

Just thought I'd explain my logic for this =P. What games are causing trouble... I can look to see how they're writing to those regs and why things are messing up.



And about the square prob... still haven't fixed it =(




Previous ThreadView All ThreadsNext Thread*Show in Threaded Mode
Jump to

Memblers' homepage             Contact Me

Forums powered by WWWThreads Demo