Odd Triangle Click

This is an archive of a topic from NESdev BBS, taken in mid-October 2019 before a server upgrade.
View original topic
Odd Triangle Click
by on (#67904)
I can only test this in Nestopia (Mac) at the moment because of the fact that the ROM needs 32K SRAM support but I thought I'd see if anyone would care to speculate.

I get this strange random clicking/popping coming from the triangle channel but only on a certain note and only when I'm doing vibrato. Well, I've not tried every single pitch (yet) but I've tried several logical pitches (each semitone in the same octave, changing octaves).

See the audio file (output from Nestopia);

http://dl.dropbox.com/u/5493868/traingle_click.wav

In a sample editor the click look like this;

Image

by on (#67905)
Are you writing to high period register to reset the triangle wave phase? That's all I can think of, either that or a bug in Nestopia.

by on (#67907)
You're probably writing a zero period to the triangle momentarily, so that it very quickly advances to a different phase, then writing some other period. That's the only way to change its phase like this, aside from an emulator bug.

by on (#67912)
Dwedit wrote:
Are you writing to high period register to reset the triangle wave phase? That's all I can think of, either that or a bug in Nestopia.

That only resets the phase in pulse channels.

by on (#67917)
blargg wrote:
You're probably writing a zero period to the triangle momentarily, so that it very quickly advances to a different phase, then writing some other period. That's the only way to change its phase like this, aside from an emulator bug.


I first made sure that the zero period thing wasn't happening by doing;

lda periodLo
ora periodHi
bne @a
inc debugFlag
@a:rts

at the time of writing the values to the APU registers and it never changed debugFlag at all.

So after checking a ton of other stuff I bit the bullet and fixed up the 8K SRAM version (I have a flag in the code that says build a ROM using 8K or 32K version of the SRAM memory map but I'd not maintained the 8K conditional stuff for a while)

Ran the ROM in FCEUX (Windows) and the click isn't there. Seems it's a Nestopia bug.

That's probably the worst case scenario - I don't think Nestopia has been updated for years and so a fix is unlikely I guess :(

The pitch in question is where the hi/lo values are centered around 01/00, so the period goes something like (hi-lo);

01-00
01-02
01-04
01-02
01-00
00-FE
00-FC
00-FE
01-00

and so on.

From slowing the emulator down the click does seem to happen at the transition from 01-00 to 00-FE but it's hard to tell and it doesn't happen in a regular cycle (like the NES's square-wave phase vibrato click)

I'll test it on my PowerPak later to make sure that FCEUX is correct (or at least the same as real hardware).

by on (#67918)
Hmmmm. Scratch that : it does happen in FCEUX just infrequently.

by on (#67920)
What happens on a NES?

by on (#67922)
Anders_A wrote:
What happens on a NES?


Not had chance to try it yet.

However, I started to suspect it was my Vibrato routine as it is a bit strange (table-based using a fractional pitch table) so I simulated Vibrato with an FX Table using my Sxx command (just adds a signed 8-bit value to the pitch every frame) like this;

S01
--
SFF
--

Which makes the pitch rise (S01) and fall (SFF) like vibrato with no tricky code to go wrong. I was surprised to find that the click still appears.

It then occurred to me that NTRQ (my other NES tracker) uses a different method of vibrato to Pulsar so as I test I booted up NTRQ, placed a "A 3" note in a Pattern on the triangle voice, applied a bit of vibrato and there's the click again!

This is really strange.

by on (#67926)
...it happens on the NES too.

I'm now officially freaking out. :S

by on (#67927)
And it gets even stranger.

I thought the simplest way to test this would be to knock up a quick ROM that just does nothing but modulate the period of the triangle voice around period $0100. So I'm doing this in the NMI;

Code:

triVib:
   lda #$81
   sta $4008
   lda #$00
   sta $4009

        ;vibrato speed = advance through table every 4 frames
   lda toneCounter
   clc
   adc #$01
   and #$03
   sta toneCounter
   bne @a
   
        ;get period from table
   ldx toneIndex
   lda toneLo,x
   sta $400a
   lda toneHi,x
   sta $400b
   inx
   cpx #$0C
   bcc @b
   ldx #$00
@b:   stx toneIndex

@a:   rts
   
toneLo:   HEX 00 02 04 06 04 02 00 FE FC FA FC FE
toneHi:   HEX 01 01 01 01 01 01 01 00 00 00 00 00


And the triangle output clicks!

(comment out the 'bne !a' to speed up the vibrato and hear the click more frequently)

by on (#67930)
Full code, ASM6 format;

Code:

;--------------------------------------------------------------------------------
; iNES header
;--------------------------------------------------------------------------------

; iNES identifier
.BYTE "NES",$1a
.BYTE $01
.BYTE $00
.BYTE %00000000, %00000000
.BYTE $00,$00,$00,$00,$00,$00,$00,$00      

;--------------------------------------------------------------------------------

   ENUM $00
toneIndex   db $00
   ENDE

      
PPU0   EQU $2000
PPU1   EQU $2001

   ORG $C000
RESET   sei
   jsr VBLANKWAIT
      
   ;clear RAM
   lda #$00
   ldx #$00
@a:   sta $0000,x
   sta $0100,x
   sta $0200,x
   sta $0300,x
   sta $0400,x
   sta $0500,x
   sta $0600,x
   sta $0700,x
   inx
   bne @a

   ldx #$FF         ;reset stack pointer
   txs

   lda #$00
   sta PPU0
   sta PPU1
   
   sta toneIndex

   jsr INIT_GRAPHICS
   jsr init_apu

   lda #%10001000
   sta PPU0
   lda #%00011110
   sta PPU1

MAIN_LOOP:
   jmp MAIN_LOOP
   
VBLANKWAIT:
   lda $2002
   bpl VBLANKWAIT
   rts

VBLANK:   pha
   txa
   pha
   tya
   pha
   bit $2002

   ldy #$06      ;delay to push CPU display down
   ldx #$00      ; onto visible area of screen
@a:   dex
   bne @a
   dey
   bne @a
   
   lda #%11111111      ;make bg white
   sta PPU1
   
   jsr triVib

   lda #%00011110      ;set bg back to black
   sta PPU1

   pla
   tay
   pla
   tax
   pla
IRQ   rti


triVib:
   lda #$81
   sta $4008
   lda #$00
   sta $4009
   
   ldx toneIndex
   lda toneLo,x
   sta $400a
   lda toneHi,x
   sta $400b
   inx
   cpx #$0C
   bcc @b
   ldx #$00
@b:   stx toneIndex

@a:   rts
   
toneLo:   .byte $00,$02,$04,$06,$04,$02,$00,$FE,$FC,$FA,$FC,$FE
toneHi:   .byte $01,$01,$01,$01,$01,$01,$01,$00,$00,$00,$00,$00

init_apu:
   lda #$0F
   sta $4015
           ldy #0
@a:   lda @regs,y
   sta $4000,y
   iny
   cpy #$18
   bne @a
   rts
@regs:
   .byte $30,$08,$00,$00
   .byte $30,$08,$00,$00
   .byte $80,$00,$00,$00
   .byte $30,$00,$00,$00
   .byte $00,$00,$00,$00
   .byte $00,$0F,$00,$40

INIT_GRAPHICS:
   lda #$3f
   ldx #$00
   sta $2006
   stx $2006
@a:   lda PALETTE,x
   sta $2007
   inx
   cpx #$20
   bne @a
   rts

PALETTE   .byte $3F,$3F,$3F,$10,$3F,$3F,$3F,$3F
   .byte $3F,$3F,$3F,$3F,$3F,$3F,$3F,$3F
   .byte $3F,$3F,$3F,$3F,$3F,$3F,$3F,$3F
   .byte $3F,$3F,$3F,$3F,$3F,$3F,$3F,$3F
   
   ORG $FFFA
   DW VBLANK, RESET, IRQ
         

by on (#67934)
At least on Windows, Nestopia appears to reset the phase or the DC-killer or both when emulation pauses and restarts. Sometimes a momentary pause occurs a few seconds after a ROM starts.

by on (#67937)
tepples wrote:
At least on Windows, Nestopia appears to reset the phase or the DC-killer or both when emulation pauses and restarts. Sometimes a momentary pause occurs a few seconds after a ROM starts.


Yep, I'm aware of that one (I think I posted a great big thread about it last year before someone mentioned the DC-killer thing).

Compile the code and leave it running - you'll be able to hear the random clicking as opposed to one click like the DC-killer.

Oh, and it happens on a real NES with a PowerPak.

by on (#67940)
neilbaldwin wrote:
Oh, and it happens on a real NES with a PowerPak.

blargg's going to have a field day with this one.

by on (#67941)
Unfortunately I don't think this is a new hardware discovery (which would be cool), since the problem seemed pretty obvious at this point:
Quote:
The pitch in question is where the hi/lo values are centered around 01/00, so the period goes something like (hi-lo);

01-00
01-02
01-04
01-02
01-00
00-FE
00-FC
00-FE
01-00

Since you can't change both bytes of the raw period simultaneously, what happens when you go from 01-00 to 00-FE and back to 01-00? What was that, a short time where both registers were zero?

Also, note that it's not merely zero, but any really small value that matters. Just if you set it to a really high frequency, it's going to quickly change phase.

Now, we look at the code:
Code:
   sta $400a
   lda toneHi,x
   sta $400b

I'll be generous and assume that toneHi is aligned. This puts eight cycles between the low and high byte updates. If you're going from 00-FE to 01-00, that's eight cycles of the period being zero. Since a zero period causes the timer to tick every cycle, that's up to eight steps of the triangle phase, if the timer happens to reload just after you write the low byte.

by on (#67948)
Damn! I think he's got it. :)

There's still an elephant in the room though.

by on (#67950)
You mean why the waveform in your screenshot appears to jump back a few phase steps, rather than forward by a few as one would expect? No idea on that. Or perhaps why it occurs at different rates on different emulators? Granularity of them running their emulated APU, or possibly different initial timing or timing accuracy of the emulator in general. Occurrence would be a complex combination of things so that slight changes in accuracy would have a big effect on the pattern of occurrence (e.g. it's chaotic).

by on (#67951)
blargg wrote:
You mean why the waveform in your screenshot appears to jump back a few phase steps, rather than forward by a few as one would expect? No idea on that. Or perhaps why it occurs at different rates on different emulators? Granularity of them running their emulated APU, or possibly different initial timing or timing accuracy of the emulator in general. Occurrence would be a complex combination of things so that slight changes in accuracy would have a big effect on the pattern of occurrence (e.g. it's chaotic).


Well yeah, that and how is it possible to write two registers simultaneously?

:)

by on (#67954)
It's impossible but I see two potential fixes :
- Write a dummy nonzero value to $400b (such as #$07), the real $400a value and the real $400b value
- Write in the order $400a, $400b when the period goes upward, and the order $400b, $400a when it goes downward. Sounds a bit more difficult to implement, as you'd have to compare new and old values or something in the like.

by on (#67956)
Nice. It might be better to write $80 to $400A, to avoid any length counter/linear counter issues (better to avoid messing with it if you can). So something like this as the simplest-but-effective approach:

lda #$FF
sta $400A
lda high
sta $400B
lda low
sta $400A

by on (#67958)
Writing dummy values doesn't seem to improve it at all, if anything it's worse.

However I tried the switching-the-order-depending-on-direction idea like this;

Code:
triVib:   ldx toneIndex
   lda toneHi,x
   sec
   sbc oldHi
   beq @c
   bmi @down
   lda toneHi,x
   sta $400B
   sta oldHi
@c:   lda toneLo,x
   cmp oldLo
   beq @next
   sta $400a
   sta oldLo
   jmp @next
@down   lda toneLo,x
   cmp oldLo
   beq @d
   sta $400a
   sta oldLo
@d:   lda toneHi,x
   sta $400b
   sta oldHi
@next   inx
   cpx #$0C
   bcc @b
   ldx #$00
@b:   stx toneIndex

@a:   rts


and it's much better though you do still get the occasional click.

by on (#67966)
Would it be worth caching the two values to be written into A and Y, and then writing them back-to-back. You'll still have a race condition, but it will be down to 3 clock cycles instead of however many you currently have (8?).

ie..

lda something, X
tay
lda other, X
sta APU_a
sty APU_b

by on (#67972)
Well I guess the test program I did isn't the best implementation of how to write the two period registers.

In my actual audio engine it's just a pair of lda/sta instructions;

lda periodLo
sta $400a
lda periodHi
sta $400b

which could easily be changed to;

lda periodLo
ldx periodHi
sta $400a
stx $400b

but given blargg's (highly plausible) theory on why the click happens, it could still happen in between the two writes i.e. 3 cycles is not 0 cycles :)

The other thing I tried (which failed) was to always save a copy of what is written to the hi period and then when writing the lo period, ORA that with the saved hi period and if it comes out zero, do a ORA #$01 before writing the lo period register. I was trying to engineer it so that $00/$00 would never exist in the registers but it didn't seem to work. Possibly flawed logic and also not a great solution from a musical point of view since it's slightly detuning the period.

by on (#67975)
Write the max possible value to period high, then write period low, then write period high?
If you use X Y and A, you can do all writes within 4 clock cycles of each other.

by on (#67978)
OK it might be stupid for me to ask it this way but did you try both techniques I mentioned ? At least of them should somewhat work I guess :roll:

At worse, I think the frequency for a A note on a particular octave is $fd. Maybe you could fine-tune everything a bit up, so that it is something around $fb or $fa. Then you can get vibratoes of a great depth without ever touching the infamous $ff/$100 barrier. Then it would fix all triangle and square channel problems on vibratoes (except those of a really big depth) - but it won't fix pitch slides (which is less noticeable as the click happens only once). Lazy, but might work surprisingly well. :wink:

by on (#67979)
Bregalad wrote:
OK it might be stupid for me to ask it this way but did you try both techniques I mentioned ? At least of them should somewhat work I guess :roll:

At worse, I think the frequency for a A note on a particular octave is $fd. Maybe you could fine-tune everything a bit up, so that it is something around $fb or $fa. Then you can get vibratoes of a great depth without ever touching the infamous $ff/$100 barrier. Then it would fix all triangle and square channel problems on vibratoes (except those of a really big depth) - but it won't fix pitch slides (which is less noticeable as the click happens only once). Lazy, but might work surprisingly well. :wink:


Yep, both methods. Don't take my word for it though - the code I'm using is there in the post and is compilable ;)

Just to be clear - it actually only happens on the triangle voice. I think it's because only the triangle voice has it's output silenced when the period is 00/00?

Out of everything I've tried the changing-write-order-depending-on-direction method of yours seems to yield the best (but not 100%) results so I think I'm going to go with that.

At least until blargg comes up with a solution ;)

by on (#67980)
Quote:
The other thing I tried (which failed) was to always save a copy of what is written to the hi period and then when writing the lo period, ORA that with the saved hi period and if it comes out zero, do a ORA #$01 before writing the lo period register.

Remember, even a period of $0001 is too low. That causes it to clock the phase every other clock, so you can still get a slight click.

Dwedit wrote:
Write the max possible value to period high, then write period low, then write period high?

Then you might get a little rest in the output. As long as the period never goes below 128 or so, you won't get quick clocking. And again, I recommend against writing to $400B any more than absolutely necessary.

clueless wrote:
lda something, X
tay
lda other, X
sta APU_a
sty APU_b

BTW, you can read directly into Y. Here are some addressing modes often overlooked:

ldy abs,x
ldx abs,y
sty zp,x
stx zp,y

by on (#67981)
OK maybe my second method didn't work because I explained it backwards (I can't say how much hate when you have 1/2 chances of saying something wrong - I always end up saying the wrong thing no matter how much I think about it).
When the pitch is decreasing, and the period increasing, you're doing $ff -> $100 you want to write first to $400b ($00 -> $01) and then to $400a ($ff -> $00) so the period is never $0000.

When the pitch is increasing, and the period increasing, you're doing $100 -> $ff, you want to write first to $400a ($00 -> $ff) and then to $400b ($01 -> $00), so again the period is never $0000.

This should always work.
Also, I don't see the problem of the dummy value write. Sure the period will be wrong for a few cycles, but not long enough to cause (especially if the high dummy value is $07, which makes the counter less likely to be clocked- and if clocked it's only once so not an issue).

Quote:
Just to be clear - it actually only happens on the triangle voice.

I know but other problem arises when changing the high period of square voices, the phase is reset. I like to bring up lazy solutions to problems, because those solutions would have been more likely seen implemented in games in the 80's than crazy solutions which relies on obscure tricks on the hardware and that few if any emulator supports (blarrg's sweep counter abuse method).

by on (#67998)
Bregalad wrote:
OK maybe my second method didn't work because I explained it backwards (I can't say how much hate when you have 1/2 chances of saying something wrong - I always end up saying the wrong thing no matter how much I think about it).
When the pitch is decreasing, and the period increasing, you're doing $ff -> $100 you want to write first to $400b ($00 -> $01) and then to $400a ($ff -> $00) so the period is never $0000.

When the pitch is increasing, and the period increasing, you're doing $100 -> $ff, you want to write first to $400a ($00 -> $ff) and then to $400b ($01 -> $00), so again the period is never $0000.


Yep, this is exactly what I did, see the last bit of code I posted. It does work but not 100%, for some reason.

Bregalad wrote:
This should always work.
Also, I don't see the problem of the dummy value write. Sure the period will be wrong for a few cycles, but not long enough to cause (especially if the high dummy value is $07, which makes the counter less likely to be clocked- and if clocked it's only once so not an issue).


It just didn't have any positive effect. The clicking was still there, perhaps even more prominent (but that's debatable).

Bregalad wrote:
I know but other problem arises when changing the high period of square voices, the phase is reset. I like to bring up lazy solutions to problems, because those solutions would have been more likely seen implemented in games in the 80's than crazy solutions which relies on obscure tricks on the hardware and that few if any emulator supports (blarrg's sweep counter abuse method).


The square channel click is a different issue and is overcome in both NTRQ and Pulsar using blargg's clever trick with the sweep registers.

by on (#69536)
Call me nuts, but can you replicate the "click" in succession to create a low-volume saw wave?

by on (#69537)
Yes, I believe so. The code would have to be cycle-timed whenever the triangle was running. Probably not an improvement over using $4011.