Skip navigation
NintendoAge
Welcome, Guest! Please Login or Join
Loading...

Nerdy Nights Sound: Part 10 Simple Drums

Jan 18, 2010 at 2:32:11 AM
MetalSlime (0)
avatar
(Thomas Hjelm) < Crack Trooper >
Posts: 140 - Joined: 08/14/2008
Japan
Profile
Last Week: Finite Looping, Key Changes, Chord Progressions

This Week
: Simple Drums

Drums
This week we are going to take a look at drums.  I saved them until now because they use the Noise channel, which operates much differently from the other 3 channels.  With the Square and Triangle channels we manually set the waveform period to choose what note to play.  Our note lookup table was just a table of periods to plug into the channel ports.  The Noise channel on the other hand produces random noise.  We don't use a note table at all.

Noise Channel
The noise channel doesn't produce notes, it produces noise.  We communicate with the noise channel through 3 ports: $400C, $400E, $400F.  Note that port $400D is unused.  Let's take a closer look at the Noise ports.

Volume - $400C
$400C let's you control the volume of the Noise channel.  It works just like the Square channels, except there is no Duty Cycle:

NOI_ENV ($400C)

76543210
  ||||||
  ||++++- Volume
  |+----- Saw Envelope Disable (0: use internal counter for volume; 1: use Volume for volume)
  +------ Length Counter Disable (0: use Length Counter; 1: disable Length Counter)
 
For our purposes, we will set Saw Envelope Disable and Length Counter Disable and forget about them.  This will allow us to have full control of "note" length and volume via Volume Envelopes.  We did the same thing for the Square channels.

Random Generator - $400E
$400E let's us control the settings for the random generator.  It looks like this:

NOI_RAND ($400E)

76543210
|   ||||
|   ++++- Sound Type
+-------- Mode

Mode sets the mode.  There are two modes, Mode-0 and Mode-1.  Mode-0 sounds dull and breathy, like static sssh.  Mode-1 sounds more sharp, robotic and buzzy.  There's really no good way to describe in words, so the best way to know the difference in sound is to listen to both modes.  (see below)

Each mode has 16 possible sounds, selected by Sound Type.  This means that the Noise channel really only gives us 32 possible sounds total!  More complex sound effects on the Noise channel are created by playing combinations of these 32 noises one after another.  To hear what each of the 32 sounds sound like, listen to Song 8 in this week's sample program.  It plays the 16 Mode-0 sounds followed by the 16 Mode-1 sounds.

Note: In this tutorial I will assign each of the 32 Noise channel sounds a number 00-1F.  The left number (0 or 1) refers to the Mode.  The right number (0-F) refers to the Sound Type.  For example, sound "04" means "Mode 0, Sound Type 4".  Sound "1E" means "Mode 1, Sound Type E".

Length Counter - $400F
$400F is the Noise channel's length counter.  We disabled the length counter in $400C, so we can ignore this port completely!

Simple Noise Drums

The simplest way to make a drum sound on the Noise channel is to play a single Sound Type under a volume envelope that decays to 0 (silence).  Many games use this kind of drum exclusively.  The Guardian Legend for example only uses two drum sounds throughout the whole game: 04 and 06.  Battle Kid makes heavy use this simple drum style too.  Lots of games do.

So how will we represent simple drums in the sound data?  Recall that our sound engine distinguishes between Notes, Note Lengths and Opcodes using ranges:

.fetch:
    lda [sound_ptr], y
    bpl .note                ;if < #$80, it's a Note
    cmp #$A0
    bcc .note_length         ;else if < #$A0, it's a Note Length
.opcode:                     ;else it's an opcode
    ;do opcode stuff
    ;range A0-FF
.note_length:
    ;do note length stuff
    ;range 80-9F
.note:
    ;do note stuff
    ;range 00-7F
    
Although we won't use our note table for the Noise channel, we will treat drums like notes as far as ranges are concerned.  So we need our drum data values to be in the range of $00-$7F.  That's easy.  We'll assign the Mode-0 sounds to $00-$0F and Mode-1 sounds to $10-$1F.  Some drum data might look like this then:

example_drum_data:
    .byte eighth, $04
    .byte sixteenth, $1E, $1E, $1F
    .byte d_eighth, $04
    .byte sixteenth, $06, $06, $08, $08
    .byte eighth, $17, $07
    .byte loop
    .word example_drum_data


Since we are not using the note table for Noise, we will need to alter our Note code to check the channel and branch to different code if we are processing the Noise channel (new stuff in red):

.note:
    ;do Note stuff
    sta sound_temp2             ;save the note value
    lda stream_channel, x       ;what channel are we using?
    cmp #NOISE                  ;is it the Noise channel?
    bne .not_noise              
    jsr se_do_noise             ;if so, JSR to a subroutine to handle noise data
    jmp .reset_ve                   ;and skip the note table when we return
.not_noise:                     ;else grab a period from the note_table
    lda sound_temp2     ;restore note value
    sty sound_temp1     ;save our index into the data stream
    clc
    adc stream_note_offset, x   ;add note offset
    asl a
    tay
    lda note_table, y
    sta stream_note_LO, x
    lda note_table+1, y
    sta stream_note_HI, x
    ldy sound_temp1     ;restore data stream index
 
    ;check if it's a rest and modify the status flag appropriately
    jsr se_check_rest
.reset_ve:    
    lda #$00
    sta stream_ve_index, x  
.update_pointer:
    iny
    tya
    clc
    adc stream_ptr_LO, x
    sta stream_ptr_LO, x
    bcc .end
    inc stream_ptr_HI, x
.end:
    rts
    
This code checks to see if the channel is the Noise channel.  If so it JSRs to a special Noise subroutine.  Upon return, it jumps over the note_table code and updates the volume envelope and stream pointer like normal.

So what does our special Noise subroutine, se_do_noise, look like?  The job of se_do_noise will be to take the note value and convert it into something we can write to $400E (NOI_RAND).  Recall that $400E expects the Mode number in bit7 and the Sound Type in bits 0-3:

NOI_RAND ($400E)

76543210
|   ||||
|   ++++- Sound Type
+-------- Mode

Mode-0 sounds don't need to be converted at all.  We represent them with the values $00-$0F, which correspond exactly to the values we need to write to $400E.

Mode-1 sounds need to be tweaked a bit.  We represent Mode-1 sounds with the values $10-$1F, or in binary %00010000-%00011111.  Notice we identify the Mode number using bit4.  Port $400E expects the Mode number in bit7 though, not bit4, so we will need to set bit7 ourselves:

se_do_noise:
    lda sound_temp2     ;restore the note value
    and #%00010000      ;isolate bit4
    beq .mode0          ;if it's clear, Mode-0, so no conversion
.mode1:
    lda sound_temp2     ;else Mode-1, restore the note value
    ora #%10000000      ;set bit 7 to set Mode-1
    sta sound_temp2
.mode0:
    lda sound_temp2
    sta stream_note_LO, x   ;temporary port that gets copied to $400E
    rts
    
Now everything is set.  Note values of $00-$0F will get written to stream_note_LO directly.  Note values of $10-$1F will get converted to $90-$9F first and then get written to stream_note_LO.  Note that we do not bother clearing bit4 for Mode-1 sounds.  Bit4 has no effect on $400E so we can just leave it as it is.

Drum Decay
The only thing left to add is a volume envelope for the simple drums to use.  It should be short and decay to zero (silence):

volume_envelopes:
    .word se_ve_1
    .word se_ve_2
    .word se_ve_3
    .word se_ve_tgl_1
    .word se_ve_tgl_2
    .word se_battlekid_loud
    .word se_battlekid_loud_long
    .word se_battlekid_soft
    .word se_battlekid_soft_long
    .word se_drum_decay
    
se_drum_decay:
    .byte $0E, $09, $08, $06, $04, $03, $02, $01, $00  ;7 frames per drum.  Experiment to get the length and attack you want.
    .byte $FF
    
ve_drum_decay = $09

You can of course make multiple volume envelopes for the drums to choose from.

Conclusion

Now we can add drum data to our songs.  Here is the drum data for The Guardian Legend boss song we've been using:

;in the song header, after the header info for Squares and Triangle:
    .byte MUSIC_NOI     ;which stream
    .byte $01           ;status byte: enabled
    .byte NOISE         ;which channel
    .byte $30           ;initial volume_duty value (disable length counter and saw envelope)
    .byte ve_drum_decay ;volume envelope
    .word song1_noise   ;pointer to the sound data stream
    .byte $53           ;tempo
 
song1_noise:
    .byte eighth, $04                   ;this song only uses drum 04 (Mode-0, Sound Type 4) for a snare
    .byte sixteenth, $04, $04, $04
    .byte d_eighth, $04
    .byte sixteenth, $04, $04, $04, $04
    .byte eighth, $04, $04
    .byte loop
    .word song1_noise
    
Try adding some drums to your own songs!
    
Putting It All Together
Download and unzip the drums.zip sample files.  Make sure the following files are in the same folder as NESASM3:

    drums.asm
    sound_engine.asm
    sound_opcodes.asm
    drums.chr
    note_table.i
    note_length_table.i
    vol_envelopes.i
    song0.i
    song1.i
    song2.i
    song3.i
    song4.i
    song5.i
    song6.i
    song7.i
    song8.i
    drums.bat

Double click drums.bat. That will run NESASM3 and should produce the drums.nes file. Run that NES file in FCEUXD SP.

Use the controller to select songs and play them.  Controls are as follows:
    
Up: Play
Down: Stop
Right : Next Song/SFX
Left : Previous Song/SFX

Song0 is a silence song.  Not selectable.
Song1-Song7 are the same as last week, but a few of them (1, 4 and 6) have drums now.
Song8 is a new "song" that plays each of the Noise channel's 32 sounds, in order from 00-1F.  First it plays them with a sustained volume envelope so that you can hear how they sound drawn out.  Next they are played using the 7-frame ve_drum_decay volume envelope we made so you can hear how they sound as simple drum sounds.

Next Week: More Complex Drums, Noise SFX

-------------------------
MetalSlime runs away

My nesdev blog: http://tummaigames.com/blog...

Jan 18, 2010 at 8:56:31 AM
Mario's Right Nut (352)
avatar
(Cunt Punch) < Bowser >
Posts: 6634 - Joined: 11/21/2008
Texas
Profile
Awesome, nicely done! Keep them comming. I can't wait to start adding music to my game.

-------------------------

This is my shiny thing, and if you try to take it off me, I may have to eat you.

Check out my dev blog.


Jan 19, 2010 at 3:48:13 AM
udisi (88)
avatar
< King Solomon >
Posts: 3270 - Joined: 11/15/2006
United States
Profile
getting close now. woot

Jan 19, 2010 at 5:26:39 AM
MetalSlime (0)
avatar
(Thomas Hjelm) < Crack Trooper >
Posts: 140 - Joined: 08/14/2008
Japan
Profile
what part/feature are you waiting for the most?  I might be able to explain how to do it and then you could try writing it on your own instead of waiting for the tutorial to come around.

-------------------------
MetalSlime runs away

My nesdev blog: http://tummaigames.com/blog...

Jan 20, 2010 at 1:17:18 AM
udisi (88)
avatar
< King Solomon >
Posts: 3270 - Joined: 11/15/2006
United States
Profile
Oh, nothing in particular. I think you'll have it all covered. May even be good after this one. need to talk to zi.

Jan 20, 2010 at 1:40:50 AM
Zzap (47)
avatar
(James ) < King Solomon >
Posts: 3301 - Joined: 05/01/2007
Australia
Profile
Your blog looks great too Thomas, you've got some neat ideas documented there!

-------------------------

Chunkout for iPhone, iPad and iTouch out now!
Chunkout Games: FaceBook | Web

Feb 8, 2010 at 3:34:54 PM
removed-07-06-2016 (214)

< Bowser >
Posts: 5018 - Joined: 06/26/2008
Other
Profile
I just wanted to bump this thread with a hearty thank you. This stuff is really fantastic, and while I'm not programming at the moment, I still take the time to read through your tutorials every time one is posted. Keep up the awesome work, and thank you for contributing to the community like this.

Cheers!

May 17, 2010 at 4:37:20 PM
Mario's Right Nut (352)
avatar
(Cunt Punch) < Bowser >
Posts: 6634 - Joined: 11/21/2008
Texas
Profile
And that's ten. Whew. Some good stuff here. I can't wait to bust out some Pantera on this bad boy. Seriously, though, good job. I can't imagine the time investment that you put into these. This is a great asset to the programming community.

-------------------------

This is my shiny thing, and if you try to take it off me, I may have to eat you.

Check out my dev blog.


Jun 4, 2010 at 2:02:34 AM
rizz (9)
avatar
(Frank Westphal) < Eggplant Wizard >
Posts: 317 - Joined: 06/29/2007
Wisconsin
Profile
For some reason, I have to do a write to $400F (any value) prior to my write to $400E. Any clue as to why?

I believe I've said it before, but these tutorials are fantastic.

-------------------------

My Development Blog


Aug 29, 2011 at 1:05:13 AM
thefox (0)
avatar
(Kalle Immonen) < Meka Chicken >
Posts: 533 - Joined: 07/08/2008
Finland
Profile
Originally posted by: rizz

For some reason, I have to do a write to $400F (any value) prior to my write to $400E. Any clue as to why?

I believe I've said it before, but these tutorials are fantastic.

I've also been wondering why this is (even when length counter is disabled and using the constant volume flag for the envelope). Haven't been able to find an explanation for it from any of the available APU docs.

-------------------------
Download STREEMERZ for NES from fauxgame.com! — Some other stuff I've done: kkfos.aspekt.fi

Jan 23, 2016 at 5:34:38 PM
Mog (140)
avatar
(Mr Mog) < King Solomon >
Posts: 4728 - Joined: 05/02/2009
Federated States of Micronesia
Profile
Does anyone have the zip file from this?

Jan 23, 2016 at 5:54:42 PM
thefox (0)
avatar
(Kalle Immonen) < Meka Chicken >
Posts: 533 - Joined: 07/08/2008
Finland
Profile
Originally posted by: thefox
 
Originally posted by: rizz

For some reason, I have to do a write to $400F (any value) prior to my write to $400E. Any clue as to why?

I believe I've said it before, but these tutorials are fantastic.

I've also been wondering why this is (even when length counter is disabled and using the constant volume flag for the envelope). Haven't been able to find an explanation for it from any of the available APU docs.
Going to answer myself here. I'm guessing that the $400F write is needed because the length counter might be initially holding a 0 value -- the $400F write will reload a non-zero value into the length counter (and the length counter's halt flag will then keep it at a non-zero value).

You don't see this problem with the pulse channels because the pulse's "length counter load" is in the same register ($4003/$4007) as the high 3 bits of period, so the length counter reload will happen as a side-effect of writing those high 3 bits.

Also, since the length counter value is read from a lookup table which doesn't contain zeros, the written value is irrelevant (if the length counter halt flag is set).

EDIT: Just as a further note, even if length counter is not used, the zero value in it is a problem because it will unconditionally silence the channel.
EDIT #2: Accidentally had written "$400E write", not "$400F write". Fixed.

-------------------------
Download STREEMERZ for NES from fauxgame.com! — Some other stuff I've done: kkfos.aspekt.fi


Edited: 02/16/2016 at 01:20 PM by thefox