Jump to content
IGNORED

Sound Effect driver


SpiceWare

Recommended Posts

Here's a preliminary sound effect driver for use with bB. I've not added anything to bB before, so I'm open to suggestions on how to make this better.

sfx.zip

 

Here's the test BIN. Joystick up/down to select a sound effect, hit FIRE to trigger it. Sound Effect 0 is a special case, it silences all output.

sfx_demo.bas.bin

 

To use in your program you need to do the following.

1. Allocate 2 of the bB variables for use by the driver:

 dim SFX_LEFT = y
dim SFX_RIGHT = z

 

2. Include the driver in your bB program:

 inline sfx.asm

 

3. Trigger a sound effect whenever appropriate. (is there a way to eliminate the temp1=?)

 temp1=sfxtrigger(#sfxJUMP)

 

4. Add a call to update the sound effects. For best results, put it after every drawscreen (this is to make the updates happen at the exact time every screen):

 drawscreen 
temp1=sfxupdate() : rem for best results, update sound effect after drawscreen

 

The sound effects are defined in 2 tables at the top of sfx.asm. The first table defines the Frequencies:

sfxF:
.byte 0, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26 ; Jump
.byte 0, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26 ; jump 2
.byte 0, 24, 25, 26, 27, 28, 29, 30, 31 ; throw
.byte 0,  0,  0,  0,  1,  1,  1,  2,  2,  2,  3,  3,  3 ; collect
.byte 0,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8 ; ping
.byte 0,  3,  3,  3,  2,  2,  2,  1,  1,  1,  0,  0,  0 ; stole   
.byte 0,  1,  2,  3,  4,  5,  6,  1,  2,  3,  4,  5,  6 ; extra
.byte 0,$10,$10,$10,$10,$10,$10 ; Bonus 50
.byte 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10 ; Health

 

The second table defines the Control (tone) and Volume:

sfxCV:
.byte 0,$c3,$c4,$c5,$c6,$c7,$c8,$c9,$ca,$cb,$cc,$cd,$ce,$cf ; Jump
sfxJUMP = *-sfxCV-1
.byte 0,$43,$44,$45,$46,$47,$48,$49,$4a,$4b,$4c,$4d,$4e,$4f ; Jump 2
sfxJUMP2 = *-sfxCV-1
.byte 0,$88,$89,$8a,$8b,$8c,$8d,$8e,$8f; throw
sfxTHROW = *-sfxCV-1
.byte 0,$6f,$6f,$6f,$6f,$6f,$6f,$6f,$6f,$6f,$6f,$6f,$6f ; collect
sfxCOLLECT = *-sfxCV-1
.byte 0,$41,$42,$43,$44,$45,$46,$47,$48,$49,$4a,$4b,$4c,$4d,$4e,$4f ; ping
sfxPING = *-sfxCV-1
.byte 0,$6f,$6f,$6f,$6f,$6f,$6f,$6f,$6f,$6f,$6f,$6f,$6f ; stole
sfxSTOLE = *-sfxCV-1
.byte 0,$1f,$1f,$1f,$1f,$1f,$1f,$1f,$1f,$1f,$1f,$1f,$1f ; extra
sfxEXTRA = *-sfxCV-1
.byte 0,$40,$40,$4f,$4f,$4f,$4f ; bonus
sfxBONUS50 = *-sfxCV-1
.byte $0,$7f,$7f,$7f,$7f,$7f,$7f,$7f,$7f,$7f,$7f ; Full Health
sfxHEALTH = *-sfxCV-1

 

Each .byte line contains the data for a single sound effect. The two tables are used together, so data in the first .byte line in sfxF goes along with the data in the first .byte line in sfxCV. The number of values must be the same in each table and each .byte line. The first value in each .byte line should be 0, it denotes end-of-sfx.

 

Table sfxCV looks a little complicated because of the extra lines such as sfxJUMP = *-sfxCV-1. All those are doing is calculating the value to be used when you trigger a sound effect. You can name your sound effects whatever you want, just make sure it's followed by = *-sfxCV-1 (also make sure you have a space before and after the equal sign).

 

Lets look at a single sound effect to explain how the data is used:

sfxF:
.byte 0, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26 ; Jump


sfxCV:
.byte 0,$c3,$c4,$c5,$c6,$c7,$c8,$c9,$ca,$cb,$cc,$cd,$ce,$cf ; Jump
sfxJUMP = *-sfxCV-1

 

When you trigger a JUMP sound effect by the function temp1=sfxtrigger(#sfxJUMP), a sound effect pointer will be initialized so that it points to the 26 in the sfxF table and the $cf in the sfxCV table. When you update the TIA sound registers by calling temp1=sfxupdate(), the 26 goes into AUDFx while $cf is split into two parts with the c going into AUDCx and the f going into AUDVx. Lastly the pointer gets updated so it now points to 25 and $ce for the next call to sfxupdate.

  • Like 6
Link to comment
Share on other sites

So far this morning I've added some validation checks for the sound effect tables. These check that the tables are not too large (max size is 256 bytes), plus they check that the tables are the same size.

 

~/Projects/Atari/bB11d/sfx> 2600basic.sh sfx_demo.bas
Starting build of sfx_demo.bas
batari Basic v1.01 (C)2005-2007
(40) Warning: function call with no arguments
(54) Warning: function call with no arguments
2600 Basic compilation complete.
SFX Warning: table sfxF is too large
SFX Warning: table sfxCV is too large
SFX Warning: table sfxF is not the same size as table sfxCV
     bytes of ROM space left
SFX Warning: table sfxF is too large
SFX Warning: table sfxCV is too large
SFX Warning: table sfxF is not the same size as table sfxCV
     1880 bytes of ROM space left
Build complete.

 

The two "no arguments" warnings are for these lines:

 if sound_effect = 0 then temp1=sfxoff()
...
temp1=sfxupdate()

 

Is there way to eliminate that warning, or a different way to set up sfxoff and sfxupdate so the bB compiler knows that "no arguments" is OK for those two functions?

 

It'd be nice to eliminate the assignment requirement too (the temp1= part of the command) as that'll save 2 bytes of ROM for each function call.

Edited by SpiceWare
Link to comment
Share on other sites

Looks like I neglected to include a comment about sound effect priority. Since there's only 2 sound channels, the Atari can only play 2 concurrent sound effects. My routine is set up with a priority system so that if 2 sound effects are currently playing then a new call to sfxtrigger will override one of the currently playing sound effects only if the new sound effect has a higher priority.

 

The priority is controlled by the order the sound effects are defined. In the sfxCV table, listed in the first post, you can see that the sound effects are defined in this order:

  1. sfxJUMP
  2. sfxJUMP2
  3. sfxTHROW
  4. sfxCOLLECT
  5. sfxPING
  6. sfxSTOLE
  7. sfxEXTRA
  8. sfxBONUS50
  9. sfxHEALTH

The first sound effect has the lowest priority, the last sound effect has the highest. If 2 sound effects are currently playing then the command temp1=sfxtrigger(#sfxJUMP) will be ignored while temp1=sfxtrigger(#sfxHEALTH) will always occur.

 

For a real game example, in Space Rocks my sound effects are defined in this order:

  1. sfxHeartbeat1
  2. sfxHeartbeat2
  3. sfxThrust
  4. sfxFlip180
  5. sfxPlayerShoot
  6. sfxSaucerShoot
  7. sfxSmallAsteroidHit
  8. sfxMediumAsteroidHit
  9. sfxLargeAsteroidHit
  10. sfxShields
  11. sfxSmallUFO
  12. sfxLargeUFO
  13. sfxSmallSaucerHit
  14. sfxLargeSaucerHit
  15. sfxPlayerHit
  16. sfxWarpIn
  17. sfxMagnaMine
  18. sfxMenuBlip
  19. sfxExtraShip

 

The order of sound effects was carefully selected. The Heartbeat and Thrust sound effects are background noise. It's no big deal if you don't hear them, so they're first in the list to denote that they have the lowest priority.

 

The asteroid explosion sound effects are ordered Small, Medium, then Large as a large explosion would "drown out" a medium or small explosion.

 

The MagnaMine and ExtraShip are highest priority as when those occur you always want the player to know. The MenuBlip will never occur during game play, so it's priority position doesn't really matter.

Edited by SpiceWare
Link to comment
Share on other sites

Looks like sfxr creates WAV files, which won't work with this driver.

 

PacManPlus has a tool which he used to create the sound effects for Space Rocks. I'm not familiar with his tool, nor do I know if he's made it (or plans to make it) available for others. I do know his tool is used with a Korg X5D though, so if you don't have one the tool wouldn't be useful.

 

Space Rocks also uses a more advanced driver, which packs a duration count in with the frequency data. The currently bB driver uses the data at a rate of 1 entry per screen, the advanced one lets you hold a value for up to 8 frames. However, to keep track of duration, the advanced driver needs you to give up 2 more bB variables. RAM is tight in bB, which is why I ported the simpler driver first. I should be able to rewrite the driver to only need 1 extra bB variable instead of 2, it'll just take more ROM to do so.

 

I plan to port the advanced driver once this one is finalized. This will give people a choice on which driver to use based on how many bB variables they're willing to give up.

 

 

What I envision is people sharing sound effects for the driver. Something along the lines of a topic with posts like this:

 

Space Rocks Shot sound effect

sfxF:
   .byte 0, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14 ; Player Shoots

sfxCV:
   .byte 0,$40,$41,$42,$43,$44,$45,$46,$47,$48,$49,$4a,$4b,$4c ; Player Shoots
sfxSHOOT = *-sfxCV-1

 

Then others would just copy the data into their tables.

Edited by SpiceWare
Link to comment
Share on other sites

I really have no experience with this (Atari sounds)

and what someone might want.

 

After looking at mostly theloon's sound effects

I was thinking something like this

 

Choose voice from a subset of the possible values

so that it could be packed in a byte with frequency.

 

I wonder if volume could be compressed in a similar way

3 bits of roughly log scale for volume 5 bits for duration

a 0 byte delimits sounds

bytes are in pairs the byte paired with zero selects

which voice subset to use.

 

a variable for each channel for voice subset table pointer

and duration counter.

 

edit ack 3 bits of roughly log scale for VOLUME 5 bits for duration

edit2 double ack

Edited by bogax
Link to comment
Share on other sites

There's many ways to write a sound driver, I'm only planning to port the two I currently use. I won't feel slighted in the least if you write another driver if mine doesn't do what you need.

 

 

As far as compression, the data for my advanced driver would shrink this 20 bytes of sound effect data:

sfxF:
.byte 0,$13,$13,$13,$13 ; heartbeat1
.byte 0,$16,$16,$16,$16 ; heartbeat2

sfxCV:
.byte 0, $6f,$6f,$6f,$6f
sfxHEART1 = *-sfxCV-1
.byte 0, $6f,$6f,$6f,$6f
sfxHEART2 = *-sfxCV-1

 

down to 8 bytes of data:

sfxF:
.byte 0, D4 + $13 ; heartbeat1
.byte 0, D4 + $16 ; heartbeat2

sfxCV:
.byte 0, $6f
sfxHEART1 = *-sfxCV-1
.byte 0, $6f
sfxHEART2 = *-sfxCV-1

 

The D4 is one of the eight duration constants:

D1 = 0
D2 = 1*32
D3 = 2*32
D4 = 3*32
D5 = 4*32
D6 = 5*32
D7 = 6*32
D8 = 7*32

 

The compression can potentially lot of ROM, at the expense of using more RAM. There isn't always a ROM savings though - if you look at the data in the first post you see this:

  • sfxJUMP - cannot be compressed, sfxF value changes on every frame
  • sfxJUMP2 - cannot be compressed, sfxF value changes on every frame
  • sfxTHROW - cannot be compressed, sfxF value changes on every frame
  • sfxCOLLECT - can be compressed
    sfxF:
    .byte 0, D3+1, D3+2, D3+3
    sfCV:
    .byte 0, $6f, $6f, $6f
  • sfxPING - cannot be compressed, sfxCV value changes on every frame
  • sfxSTOLE - can be compressed
    sfxF:
    .byte 0, D3+3, D3+2, D3+1, D3+0
    sfxCV:
    .byte 0, $6f, $6f, $6f, $6f
  • sfxEXTRA - cannot be compressed, sfxF value changes on every frame
  • sfxBONUS50 - can be compressed
    sfxF:
    .byte 0, D2+$10, D4+$10
    sfxCV:
    .byte 0, $40, $4f
  • sfxHEALTH - cannot be compressed, sfxF value changes on every frame

 

 

One benefit of my driver, provided you're not writing a game that utilizes stereo sound, is you no longer have to worry about assigning channels to sound effects as the driver automatically selects a channel for you. The driver first looks for an idle channel, if it can't find an idle one it then uses priority to determine if the requested sound effect should be played or ignored.

Edited by SpiceWare
Link to comment
Share on other sites

LIke I said I realy know nothing about it

I was only thinking about it because theloon

mentioned it once.

 

Mostly I wonder if using a subset of voices is

practical.

 

I suspect a log scale for volume would work out

just fine.

 

5 bits for duration seems like more than enough,

maybe too much. but I hadn't thought of anything

to use an extra bit for

of course it would be simpler to just make it

4 bits volume and 4 bits duration

 

Now you've got me thinking about prioritizing sound

effects

edit

and automatically assigning channels

 

As to compression

another possibility I was thinking of exploring is doing

something more stream like and using difference compression

(which looks like it would do just fine for your jumps)

I expect that's overkill though, and you'd probably have

to have a custom editor to make it usable

 

 

I was planning on doing it all in bB

I was thinking about rewriting yours in bB

just for the hell of it.

Edited by bogax
Link to comment
Share on other sites

Mostly I wonder if using a subset of voices is

practical.

There's only 2 voices (the channels), so using a subset would mean only 1 sound effect at a time.

 

I was planning on doing it all in bB

I was thinking about rewriting yours in bB

just for the hell of it.

 

Go for it.

Link to comment
Share on other sites

There's only 2 voices (the channels), so using a subset would mean only 1 sound effect at a time.

.

 

I meant the control

each channel has frequency, volume and control (voice)

and restricting it to a subset of 8 of the possible 15

(of which 5 are redundant anyway according to RT's page)

Link to comment
Share on other sites

Ah - that's a good idea.

 

You can disregard Control = 0 as well; that's only useful for playing back digital samples, which requires advanced coding that's not possible with bB. For digital samples you must update AUDVx on every-single-scanline, even during vertical blank and overscan.

Link to comment
Share on other sites

  • 2 weeks later...

This will help me in making sound effects.

I had simple music figured out, but sound effects was stopping me.

Not only did Rev Eng help me with his priority sound effect routine, but now there is Spiceware's also!

I should stop taking such long vacations, so much happens...

Link to comment
Share on other sites

You can disregard Control = 0 as well; that's only useful for playing back digital samples[...]

 

Actually there's one more use. If you change the volume level once (instead of toggling it at audible frequencies) you get a short snap of higher frequencies that makes a nice understated percussive sound. I used this as a lead-in beat to the 21 blue ditty.

  • Like 1
Link to comment
Share on other sites

  • 2 years later...

I used that to write the Gizzle Wap song, but not sound effects.

 

It's great for making sound effects. You can have fun trying to weave Atari 2600 sounds that you haven't heard a million times before. Every sound effect in Seaweed Assault and ChipOff were made using the Music and Sound Editor.

Link to comment
Share on other sites

  • 1 year later...

This driver is quite awesome! It makes sound so much easier to deal with. Thank you so much for providing this. The current version is perfect for my game, but for one that had music of any non-trivial length, I would think that the 4-byte version would be worth the ROM savings.

 

Here are two sound effects I have made. I'm happy with my ship shooting noise:

sfxF:
.byte 0, 8, 7, 7, 6, 5, 4, 3, 2, 2, 3 ; Player fires

sfxCV:
.byte $0,$16,$18,$18,$1A,$1B,$1C,$1D,$1F,$1D ; Player fires
sfxFIRE = *-sfxCV-1

I think my explosion sound could be improved, although I'm not sure how to do so:

sfxF:
 .byte 0, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31 ; Explosion

sfxCV:
    .byte 0,$82,$84,$86,$88,$86,$88,$86,$88,$86,$88 ; Explosion
sfxEXPLODE = *-sfxCV-1

I could really use an alien-sounding shooting noise, but I haven't yet had luck making one that sounds at all good.

  • Like 2
Link to comment
Share on other sites

  • 2 years later...

Am I right in thinking that I can only use the driver in the same back that I call the inline ASM?

 

I've tried an out of bank call to the sfx update with no joy.

 

I use this on Dare Devil in the main loop for sfx and it works great, what I need to do is generate a sound effect from a different part of the code in a different bank and that's where I'm struggling.

 

I also find when using the driver, other sounds get distorted. I thought the driver checked for an available channel and then used it if it was free, so for example, if i'm using channel 0 for music/whatever, the driver asm checks and sees that is the case and sends the sfx to channel 1?

Edited by Muddyfunster
Link to comment
Share on other sites

Yes, it needs to be called from the same bank where it is included. I was thinking you already ran into this with Tyre Trax? What I did is make a wrapper function in the correct bank that in turn calls the SFX driver, and then I did my bankswitch gosub to that wrapper function from where I needed it. Let me know if any examples would be helpful.

 

Also, the driver has no way of knowing if a channel is in use; it just knows if it is using a channel itself to play a sound. I don't know if there is even a way programmatically to determine if sound registers are being used, and even if there was, that would fail in certain cases if the music was at a "rest" at the time the sound register is checked.

 

The way I handled this myself was to set a bit if music was playing, and check for this bit before calling the SFX driver to avoid conflicts. E.g.:

 

   if !MusicPlayingBit6{6} then temp1=sfxupdate()

 

  • Like 1
Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...