NintendoAge http://nintendoage.com/forum/ -Sqooner Nerdy Nights Sound: Part 6 http://nintendoage.com/forum/messageview.cfm?catid=22&threadid=24885 2009-12-14T06:21:58 -05.00 MetalSlime 17 Originally posted by: ddribin

Hey MetalSlime,

Thanks a bunch for these tutorials.  They've been very interesting, and I've been having fun with the sound engine. However, I think I found a bug in sound_load in regards to stream_ticker_total.  In this article, you mention:


Originally posted by: MetalSlime
Then we will need to edit sound_load to read this new byte for each stream and store it in RAM.  We'll also want to initialize stream_ticker_total to some fixed starting value, preferably a high one so that the first tick will happen without a delay.  Finally, we will have to update all of our songs to include tempos in their headers.

Then, in the code, it gets initialized to $A0:

    lda #$A0
    sta stream_ticker_total, x

The problem is if a song has a tempo of less than $60, then the ticker won't roll over on the first frame. To hear this, change the tempo of song 2 to $10 and then play it twice in a row. The second time it gets played, the last note is briefly played again.

If I initialize stream_ticker_total to $FF in sound_load, then it seems to fix it. Is this the best fix? What was the reasoning behind using $A0?

Thanks,

-Dave



Thanks for the comments.  Glad you are finding the tutorials useful! 

You are right, at low tempo values the rollover is late.  $FF sounds like a good fix.  As for what I was thinking using $A0 I'm not sure.  I originally coded in a 16-bit counter for even more tempo precision, but later cut it because the tutorial was getting too long.  With a 16-bit counter, the tempo values tend to be higher so maybe $A0 seemed like a high enough initial value to me.  $FF seems like a better choice right now though.  Nice catch!

I had planned to make the 16-bit counter a homework idea, but looks like I forgot to add it at the end.

Thanks a lot!

]]>
Nerdy Nights Sound: Part 6 http://nintendoage.com/forum/messageview.cfm?catid=22&threadid=24885 2009-12-13T18:41:34 -05.00 MetalSlime 17 Hey MetalSlime,

Thanks a bunch for these tutorials.  They've been very interesting, and I've been having fun with the sound engine. However, I think I found a bug in sound_load in regards to stream_ticker_total.  In this article, you mention:


Originally posted by: MetalSlime
Then we will need to edit sound_load to read this new byte for each stream and store it in RAM.  We'll also want to initialize stream_ticker_total to some fixed starting value, preferably a high one so that the first tick will happen without a delay.  Finally, we will have to update all of our songs to include tempos in their headers.

Then, in the code, it gets initialized to $A0:

    lda #$A0
    sta stream_ticker_total, x

The problem is if a song has a tempo of less than $60, then the ticker won't roll over on the first frame. To hear this, change the tempo of song 2 to $10 and then play it twice in a row. The second time it gets played, the last note is briefly played again.

If I initialize stream_ticker_total to $FF in sound_load, then it seems to fix it. Is this the best fix? What was the reasoning behind using $A0?

Thanks,

-Dave


]]>
Nerdy Nights Sound: Part 6 http://nintendoage.com/forum/messageview.cfm?catid=22&threadid=24885 2009-10-27T11:30:17 -05.00 MetalSlime 17
Looping probably more logical step, but some of the bells and whistles are needed for me personally to try out some of the music that is planned for my game. It uses a lot of duty cycle sweeps, and volume envelopes. If I had those, I should be able to at least put in some partial tunes and see how they play through the engine.

So far though, must say, I'm liking the versatility of the engine, and the simplicity of the song data. ]]>
Nerdy Nights Sound: Part 6 http://nintendoage.com/forum/messageview.cfm?catid=22&threadid=24885 2009-10-27T01:53:13 -05.00 MetalSlime 17 Typo
Glad you got it fixed.  Little value errors like that are an annoying problem.  I get them all the time too .  One way to avoid this kind of error is to use constants/aliases, which we have been doing for the notes, channels, streams and note lengths.  We could make some constants for the different duty cycles:

duty_0 =        %00110000 ;bottom 4 bits blank, because we will OR with volume
duty_25 =       %01110000
duty_50 =       %10110000
duty_25_neg = %11110000

and then in our song files, instead of declaring the duty_vol byte like this:

.byte %10111111 ;duty 50%, volume F, but I might make a typo!

we could declare it like this:

.byte duty_50 | $0F ;the assembler will translate this into %10111111

At least I think that will work.  I think nesasm reads the "|" as a logical OR.

Sound
The difference in sound (smooth vs. choppy) is probably the result of turning off the saw envelopes and length counter. The saw envelope is the one built-in volume envelope you get with the hardware.  It's a simple volume-decreasing envelope (down to 0), so when you had it enabled it you probably got some hush on the notes.  Enabling the length counter would take control of the note lengths, and the length value that we've been feeding to $4003/$4007 would make those notes quite short.  That's my guess as to why you had a choppy sound before.  

When you turned saw envelopes and the length counter off, you basically told the square to give you a constant volume (which you set to F: %xxxx1111) for full-length notes, so the notes will have the same volume for the whole duration of the note.  Across several notes this gives a fluid sound.

If I'm right about the cause of the sound change (hard to know for sure without actually hearing what you mean by "choppy"), we should be able to fix it when we get to volume envelopes, since that is how we will control note volume and length.  You will be able to create your own volume envelope that gives you short, quieting notes.

Next "week's" lesson is supposed to be opcodes and looping.  I was going to try to fit volume envelopes in too if it didn't get too long, but I was also thinking about splitting it into two lessons.  I wasn't satisfied with the length of the last two lessons (too long).  If I do split them, do you have a preference for which topic I cover first (looping vs. volume envelopes)?  Maybe it will be easier if I cover volume envelopes first.  The tutorials might come faster if I do it that way too.  I'll think about it. ]]>
Nerdy Nights Sound: Part 6 http://nintendoage.com/forum/messageview.cfm?catid=22&threadid=24885 2009-10-25T21:00:48 -05.00 MetalSlime 17
Only thing weird now, is the tune is more fluid. before it seemed more choppy, and I haven't figured out how to get that choppy sound back.
]]>
Nerdy Nights Sound: Part 6 http://nintendoage.com/forum/messageview.cfm?catid=22&threadid=24885 2009-10-25T18:25:20 -05.00 MetalSlime 17 Nerdy Nights Sound: Part 6 http://nintendoage.com/forum/messageview.cfm?catid=22&threadid=24885 2009-10-25T17:57:57 -05.00 MetalSlime 17 Originally posted by: udisi

I've tried that. I didn't do that initially cause I didn't want the music to start upon startup. you can load the song and use the initialization function to play when you want it to start.

I actually use the initialization and disable functions throughout. If I have a screen without music, I want to disable, otherwise the song from the previous page will continue to play on the new page.

Music won't play on startup if you don't load a song, so you don't have to worry about that anymore.  The intended usage of the engine is this:

1) call sound_init on startup (in reset)
2) when you want to play a song or sound effect, call sound_load
3) when you want the engine to be quiet (like when changing states/screens), play the silence song (see song0.i included in tempo.zip)

You shouldn't be using sound_disable/sound_init to silence and restart the engine like that.  Everything should be done through sound_load.  You'll save on ROM space this way (fewer JSRs) and you'll have perfect control of when a song/sfx starts and finishes.
]]>
Nerdy Nights Sound: Part 6 http://nintendoage.com/forum/messageview.cfm?catid=22&threadid=24885 2009-10-24T13:55:20 -05.00 MetalSlime 17
I actually use the initialization and disable functions throughout. If I have a screen without music, I want to disable, otherwise the song from the previous page will continue to play on the new page. ]]>
Nerdy Nights Sound: Part 6 http://nintendoage.com/forum/messageview.cfm?catid=22&threadid=24885 2009-10-24T06:05:57 -05.00 MetalSlime 17
Once your sound engine is initialized, you should never have to touch sound_init again. Just call it once on startup and you're done. ]]>
Nerdy Nights Sound: Part 6 http://nintendoage.com/forum/messageview.cfm?catid=22&threadid=24885 2009-10-24T00:54:59 -05.00 MetalSlime 17
It's the only way I could get it to start when I wanted it to before, without a constant tone playing before the sound before.

Here's the engine, it's pretty much just checking for the start of the "homebrew animation" which is the cue to load the song. after that it's just a delay after games pops up before going to the title screen.

EngineSplash:
LDA rowcount
CMP #$01
BCS godelay
LDA #$02
STA current_song
JSR sound_load
godelay:
LDA game2
CMP #$9F
BEQ INCdelay
JMP GameEngineDone
INCdelay:
INC delaycount
LDA delaycount
CMP #$40
BEQ loadgame
JMP GameEngineDone
loadgame:
JSR ClrSprites
JSR sound_disable
LDA #BGTITLE
STA BG_ptr
STA ATT_ptr
JSR loadbackground
JSR loadattribute
JSR LoadTitle
JMP GameEngineDone

here's the actual hackish animation for the splash page. no laughing at my ugly code note the JSR sound_init is right when the first sprites of the "H" are suppose to move into place.

splashanim:

LDA beerpnt
CMP #$04
BNE INCbeer
JMP Homebrew

INCbeer:
INC beercount
LDA beercount
CMP #$20
BEQ beer1
JMP beerover
beer1:
LDA beerpnt
CMP #$00
BNE beer2

LoadPalettesbeer:
LDA $2002 ; read PPU status to reset the high/low latch
LDA #$3F
STA $2006 ; write the high byte of $3F00 address
LDA #$00
STA $2006 ; write the low byte of $3F00 address
LDX #$00 ; start out at 0
LoadPalettesbeerLoop:
LDA palettebeer, x ; load data from address (palette + the value in x)
; 1st time through loop it will load palette+0
; 2nd time through loop it will load palette+1
; 3rd time through loop it will load palette+2
; etc
STA $2007 ; write to PPU
INX ; X = X + 1
CPX #$20 ; Compare X to hex $20, decimal 32 - copying 32 bytes = 8 palettes
BNE LoadPalettesbeerLoop ; Branch to LoadPalettesLoop if compare was Not Equal to zero
; if compare was equal to 32, keep going down

LDA beerpnt
CLC
ADC #$01
STA beerpnt
LDA #$00
STA beercount
JMP beerover
beer2:
LDA beerpnt
CMP #$01
BNE beer3

LoadPalettesbeer2:
LDA $2002 ; read PPU status to reset the high/low latch
LDA #$3F
STA $2006 ; write the high byte of $3F00 address
LDA #$00
STA $2006 ; write the low byte of $3F00 address
LDX #$00 ; start out at 0
LoadPalettesbeer2Loop:
LDA palettebeer2, x ; load data from address (palette + the value in x)
; 1st time through loop it will load palette+0
; 2nd time through loop it will load palette+1
; 3rd time through loop it will load palette+2
; etc
STA $2007 ; write to PPU
INX ; X = X + 1
CPX #$20 ; Compare X to hex $20, decimal 32 - copying 32 bytes = 8 palettes
BNE LoadPalettesbeer2Loop ; Branch to LoadPalettesLoop if compare was Not Equal to zero
; if compare was equal to 32, keep going down

LDA beerpnt
CLC
ADC #$01
STA beerpnt
LDA #$00
STA beercount
JMP beerover
beer3:
LDA beerpnt
CMP #$02
BNE beer4

LoadPalettesbeer3:
LDA $2002 ; read PPU status to reset the high/low latch
LDA #$3F
STA $2006 ; write the high byte of $3F00 address
LDA #$00
STA $2006 ; write the low byte of $3F00 address
LDX #$00 ; start out at 0
LoadPalettesbeer3Loop:
LDA palettebeer3, x ; load data from address (palette + the value in x)
; 1st time through loop it will load palette+0
; 2nd time through loop it will load palette+1
; 3rd time through loop it will load palette+2
; etc
STA $2007 ; write to PPU
INX ; X = X + 1
CPX #$20 ; Compare X to hex $20, decimal 32 - copying 32 bytes = 8 palettes
BNE LoadPalettesbeer3Loop ; Branch to LoadPalettesLoop if compare was Not Equal to zero
; if compare was equal to 32, keep going down

LDA beerpnt
CLC
ADC #$01
STA beerpnt
LDA #$00
STA beercount
JMP beerover
beer4:

LDA beerpnt
CMP #$03
BNE beerover

LoadPalettesbeer4:
LDA $2002 ; read PPU status to reset the high/low latch
LDA #$3F
STA $2006 ; write the high byte of $3F00 address
LDA #$00
STA $2006 ; write the low byte of $3F00 address
LDX #$00 ; start out at 0
LoadPalettesbeer4Loop:
LDA palettebeer4, x ; load data from address (palette + the value in x)
; 1st time through loop it will load palette+0
; 2nd time through loop it will load palette+1
; 3rd time through loop it will load palette+2
; etc
STA $2007 ; write to PPU
INX ; X = X + 1
CPX #$20 ; Compare X to hex $20, decimal 32 - copying 32 bytes = 8 palettes
BNE LoadPalettesbeer4Loop ; Branch to LoadPalettesLoop if compare was Not Equal to zero
; if compare was equal to 32, keep going down

LDA #$04
STA beerpnt
LDA #$00
STA beercount

beerover:
RTS

Homebrew:

LDA rowcount
CMP #$0C
BNE INChomebrew
JMP Games

INChomebrew:

INC homebrewcount
LDA homebrewcount
CMP #$08
BEQ loadrow1
JMP hbover
loadrow1:
LDA rowcount
CMP #$00
BNE loadrow2

JSR sound_init
LDA #$5F
STA hone1
LDA #$67
STA hone2

LDA rowcount
CLC
ADC #$01
STA rowcount
LDA #$00
STA homebrewcount
JMP hbover
loadrow2:
LDA rowcount
CMP #$01
BNE loadrow3

LDA #$57
STA htwo1
LDA #$5F
STA htwo2
LDA #$67
STA htwo3

LDA rowcount
CLC
ADC #$01
STA rowcount
LDA #$00
STA homebrewcount
JMP hbover
loadrow3:
LDA rowcount
CMP #$02
BNE loadrow4

LDA #$4F
STA hthree1
LDA #$57
STA hthree2
LDA #$5F
STA hthree3

LDA rowcount
CLC
ADC #$01
STA rowcount
LDA #$00
STA homebrewcount
JMP hbover
loadrow4:
LDA rowcount
CMP #$03
BNE loadrow5

LDA #$4F
STA hfour1
LDA #$57
STA hfour2
LDA #$5F
STA hfour3

LDA rowcount
CLC
ADC #$01
STA rowcount
LDA #$00
STA homebrewcount
JMP hbover
loadrow5:
LDA rowcount
CMP #$04
BNE loadrow6

LDA #$4F
STA hfive1
LDA #$57
STA hfive2

LDA rowcount
CLC
ADC #$01
STA rowcount
LDA #$00
STA homebrewcount
JMP hbover
loadrow6:
LDA rowcount
CMP #$05
BNE loadrow7

LDA #$4F
STA hsix1
LDA #$57
STA hsix2

LDA rowcount
CLC
ADC #$01
STA rowcount
LDA #$00
STA homebrewcount
JMP hbover
loadrow7:
LDA rowcount
CMP #$06
BNE loadrow8

LDA #$4F
STA hseven1
LDA #$57
STA hseven2

LDA rowcount
CLC
ADC #$01
STA rowcount
LDA #$00
STA homebrewcount
JMP hbover
loadrow8:
LDA rowcount
CMP #$07
BNE loadrow9

LDA #$4F
STA height1
LDA #$57
STA height2

LDA rowcount
CLC
ADC #$01
STA rowcount
LDA #$00
STA homebrewcount
JMP hbover
loadrow9:
LDA rowcount
CMP #$08
BNE loadrow10

LDA #$4F
STA hnine1
LDA #$57
STA hnine2
LDA #$5F
STA hnine3

LDA rowcount
CLC
ADC #$01
STA rowcount
LDA #$00
STA homebrewcount
JMP hbover
loadrow10:
LDA rowcount
CMP #$09
BNE loadrow11

LoadPalettes5S:
LDA $2002 ; read PPU status to reset the high/low latch
LDA #$3F
STA $2006 ; write the high byte of $3F00 address
LDA #$00
STA $2006 ; write the low byte of $3F00 address
LDX #$00 ; start out at 0
LoadPalettes5SLoop:
LDA palette5S, x ; load data from address (palette + the value in x)
; 1st time through loop it will load palette+0
; 2nd time through loop it will load palette+1
; 3rd time through loop it will load palette+2
; etc
STA $2007 ; write to PPU
INX ; X = X + 1
CPX #$20 ; Compare X to hex $20, decimal 32 - copying 32 bytes = 8 palettes
BNE LoadPalettes5SLoop ; Branch to LoadPalettesLoop if compare was Not Equal to zero
; if compare was equal to 32, keep going down

LDA #$5F
STA hten2

LDA rowcount
CLC
ADC #$01
STA rowcount
LDA #$00
STA homebrewcount
JMP hbover
loadrow11:
LDA rowcount
CMP #$0A
BNE loadrow12

LoadPalettes6S:
LDA $2002 ; read PPU status to reset the high/low latch
LDA #$3F
STA $2006 ; write the high byte of $3F00 address
LDA #$00
STA $2006 ; write the low byte of $3F00 address
LDX #$00 ; start out at 0
LoadPalettes6SLoop:
LDA palette6S, x ; load data from address (palette + the value in x)
; 1st time through loop it will load palette+0
; 2nd time through loop it will load palette+1
; 3rd time through loop it will load palette+2
; etc
STA $2007 ; write to PPU
INX ; X = X + 1
CPX #$20 ; Compare X to hex $20, decimal 32 - copying 32 bytes = 8 palettes
BNE LoadPalettes6SLoop ; Branch to LoadPalettesLoop if compare was Not Equal to zero
; if compare was equal to 32, keep going down

LDA #$5F
STA heleven2
LDA #$67
STA heleven3

LDA rowcount
CLC
ADC #$01
STA rowcount
LDA #$00
STA homebrewcount
JMP hbover
loadrow12:
LDA rowcount
CMP #$0B
BNE hbover

LDA #$5F
STA htweleve1
LDA #$67
STA htweleve2


LDA rowcount
CLC
ADC #$01
STA rowcount
LDA #$00
STA homebrewcount
JMP hbover
hbover:

RTS

Games:

INC gamecount
LDA gamecount
CMP #$20
BNE gamesdone
LDA #$97
STA game1
LDA #$9F
STA game2
gamesdone:

RTS




]]>