Jump to content
IGNORED

Assembly language sound effects


Asmusr

Recommended Posts

I would like to add some sound effects to my game that are a little more interesting than the usual square wave crash and chime examples from the A/E manual. Do you have any suggestions for tricks or techniques to use to produce other effects or wave forms? I have read about the sample trick used by the Sound FX program, but I have no room for samples, so it has to be programmed and relatively short, but it doesn't have to be limited to a 60 FPS data feed.

 

One idea I have is to rapidly changing the frequency on two generators slightly out of sync to obtain an illusion of phase shifting. I have no idea if that's possible or what that would sound like.

 

Before I start experimenting, are there any limitations to the sound emulation in Classic99 and MESS that I should be aware of?

Link to comment
Share on other sites

One idea I have is to rapidly changing the frequency on two generators slightly out of sync to obtain an illusion of phase shifting. I have no idea if that's possible or what that would sound like.

 

That's a trick i've often used and it works well. By varying the pitch of one channel slightly you can get some nice phase effects.

Link to comment
Share on other sites

I think 60 FPS will be efficient for any sound effect. Also real time manipulation.

 

The ISR will play all channels but only one track. For example, if you tryout BurgerTime, the music will stop every other second for sound effects to play. Like if you want to play music and have lasers or explosions at the same time, you have to plan which channels to use for what (or get more advanced than that), and also write your own multi track player, like ISR but capable of playing more than one track at the same time.

 

Having one frequency slight off from another can generate rich sound dynamics. This demonstrates the difference between one frequency and two close to each other.

 

100 CALL SOUND(1000,110,0)

100 CALL SOUND(1000,110,0,111,0)

 

I haven’t replicated my problems with Classic99 on other computers (yes, will do), but often have to go and check things like quality of smoothness, sound and speech on MESS. Classic99 appears rather jerky and I loose joystick control within 2 to about 25 minutes, MESS and Win994a works fine for trying to beat a score with MunchMan etc.

 

I once used a sound spectrum analyzer (PC) to sample frequencies in chunks of 1/60 of a sec. Works fine with “clean” sound effects from any source, but then even simple music and speech becomes pretty unrecognizable.

 

:)

Link to comment
Share on other sites

There are various simple yet interesting sounds that can be done using a single channel. In my games I distinguish a start effect and doing effect routines.

 

I use to "dump" sound at start of interruption so it doesn't "change" place in time, and most of times I've a counter/frequency value for each sound.

 

Like these (of course, add a volume setting)

 

1. Bump: start: count = 0x07. var = 0x0300 doing: if (!count) return; count--; channel3 = var; var += 0x20;

2. Laser: start count = 0x03. var = 0x0100 doing: if (!count) return; count--; channel3 = var; var >>= 1;

3. New life: start: count = 0x20. var = 0x0200 doing: if (!count) return; if (count & 1) return; count--; channel3 = var; var = var - (var >> 4)

 

Sometimes I also play with volume setting.

 

You can also put two tones in same channel, playing in alternate frames.

Link to comment
Share on other sites

You might want to check out the sound player by Tursi and the sound player by Marc, both are assembly language routines and a lot more advanced compared to the ISR da-dee-do player :-)

 

On a sidenote, I seem to recall that the Colecovision had an advanced sound player embedded in the OS7 bios.

Edited by retroclouds
  • Like 1
Link to comment
Share on other sites

Before I start experimenting, are there any limitations to the sound emulation in Classic99 and MESS that I should be aware of?

 

Classic99 currently can't cope with sound changes faster than 60hz, it'll drop them. That's the only limitation I'm aware of with it.

 

Oh, and it won't attempt to generate extremely high (inaudible) frequencies. (I can't remember the cutoff). But this does prevent it from working with samples.

Edited by Tursi
Link to comment
Share on other sites

  • 4 years later...

 

 

You might want to check out the sound player by Tursi and the sound player by Marc, both are assembly language routines and a lot more advanced compared to the ISR da-dee-do player :-)

On a sidenote, I seem to recall that the Colecovision had an advanced sound player embedded in the OS7 bios.

I'll be searching for information on these players....any advances in the past five years?

Link to comment
Share on other sites

  • 2 weeks later...
  • 3 weeks later...

9919 nibbles

 

I have a question for the gang who code assembly language.

 

I want a fast way to convert the frequency divider value for the 9919 into the 2 bytes I need to send to the chip.

I have chosen to keep the bytes together before I send them so that they I can handle them as 1 integer. Maybe that's not the best decision either.

Anyway I have it down to 4 instructions, but I am curious if there is a better way?

Currently in Forth Assembler I have this to convert >0ABC to >0CAB.

( TOS is alias for R4,  W is alias for R8)

 CODE >FCODE           \ TOS = 0ABC
          TOS W MOV,   \ DUP -> W
          TOS 4 SRL,   \ TOS =00AB 
          W 8 SLA,     \ W = BC00
          W 0F00 ANDI, \ W = 0C00
          W TOS A,     \ TOS = 0CAB 
Link to comment
Share on other sites

I have a similar problem (math on the interval) so I looked at this in detail.

If TOS is just a register as you say,
TOS 4 SRC,
TOS W MOV,
W 4 SRL,
W TOS MOVB,
Classic99 reports 114 cycles for your version, 84 for this.
Tips:
W SWPB, is faster than W 8 SLA,
When you need to operate on the LSB, and you know the WP, skip the SWPB for a tiny improvement
8317 @() TOS AB,
Shifts won't work if TOS needs to be *SP. FORTH assembler will happily take SP *? 4 SRC, but it isn't detected as an out of bounds condition and produces SP 5 SRC,
Best,
-Erik
Link to comment
Share on other sites

 

I have a similar problem (math on the interval) so I looked at this in detail.

If TOS is just a register as you say,
TOS 4 SRC,
TOS W MOV,
W 4 SRL,
W TOS MOVB,
Classic99 reports 114 cycles for your version, 84 for this.
Tips:
W SWPB, is faster than W 8 SLA,
When you need to operate on the LSB, and you know the WP, skip the SWPB for a tiny improvement
8317 @() TOS AB,
Shifts won't work if TOS needs to be *SP. FORTH assembler will happily take SP *? 4 SRC, but it isn't detected as an out of bounds condition and produces SP 5 SRC,
Best,
-Erik

 

 

 

Thanks for the assistance Erik. This is a great help.

114 clocks was far better than my Forth version but with your help it comes down even more.

Funny thing, i used the swap byte instruction from Forth and missed it here.

 

Thanks again.

 

Brian

Link to comment
Share on other sites

 

I have a similar problem (math on the interval) so I looked at this in detail.

If TOS is just a register as you say,

 

From the above comments:

 

Please explain "math on the interval"

 

 

For clarification, Camel99 Forth caches the top of the data stack in R4. This makes some primitives very simple.

It came into common practice with commercial Forth systems in the late 1980s as I recall but was never part of FIG Forth to my knowledge.

 

Example:

 

CODE @ *TOS TOS MOV, NEXT, ENDCODE

 

It means you have to manage that register a little differently but there is a net 8..10% speedup on most CPUs.

 

 

B

Link to comment
Share on other sites

For clarification, Camel99 Forth caches the top of the data stack in R4. This makes some primitives very simple.

It came into common practice with commercial Forth systems in the late 1980s as I recall but was never part of FIG Forth to my knowledge.

 

Right—fig-Forth, as implemented for the TMS9900, used the registers in the Forth context as

R0 \
R1  |
R2  |
R3   \ These registers are available. They are used 
R4   / only within fbForth words written in ALC.
R5  |
R6  |
R7 /
UP    Points to base of User Variable area
SP    Parameter Stack Pointer
W     Inner Interpreter current Word pointer
R11   Linkage for subroutines in ALC routines
R12   Used for CRU instructions
IP    Interpretive Pointer
RP    Return Stack Pointer
NEXT  Points to the next instruction fetch routine 

...lee

Link to comment
Share on other sites

 

From the above comments:

 

Please explain "math on the interval"

"Math on the interval"
Problem statement: Given the cmd bytes for a tone, generate a table that varies the frequency between plus/minus one half step.
I'll post my solution later. In brief, I interpolate between two interval (period register) values. A list of sound chip command bytes is created when a note begins and sent to the chip on 1/60th sec ticks.
Link to comment
Share on other sites

Ah yes. I am very interested in that.

 

My solution is pretty simple, I take the frequency of the oscillator as a 32bit integer and divide it by the audio frequency and now I use your improved code to roll the nibbles into the command bytes.

Looks like this at the moment. My oscillator is off by .8 Hz at the moment, but it still makes music that sounds in tune over a couple of octaves or so.

 

With your help it now looks like this:

DECIMAL
 : f(clk) ( -- d)  46324 1  ;   \ this is 111,860 as 32 bit int.

CODE >FCODE ( 0abc -- 0cab) \ version by Farmer Potato Atariage
            0B44 ,          \ TOS 4 SRC,   \ C0AB
            0948 ,          \ TOS W MOV,   \ DUP
            0948 ,          \ W 4 SRL,     \ 0C0A
            D108 ,          \ W TOS MOVB,  \ 0CAB
            NEXT,           \ 8 BYTES, 28 uS :-)
            ENDCODE

\ convert freq. to 9919 chip code
: HZ>CODE  ( freq -- fcode ) f(clk) ROT UM/MOD NIP >FCODE ;

I send the 2 command bytes to the chip in sequence, and typically then send the attenuator value and then I delay.

I am curious what about the advantages of sending the bytes on interrupt timing.

 

For reference the old Forth >FCODE was this: (SPLIT and FUSE are 2 CODE words I created to deal with CELL->BYTE->CELL conversion)

: >FCODE ( 0abc -- 0cab)
          4 LSHIFT  ( -- abc0 )
          ><        ( -- c0ab )
          SPLIT     ( -- ab c0)
          4 RSHIFT  ( -- ab  c)
          FUSE  ;   ( -- 0cab)  \ 18 BYTES 400 uS
Link to comment
Share on other sites

I would like to add some sound effects to my game that are a little more interesting than the usual square wave crash and chime examples from the A/E manual. Do you have any suggestions for tricks or techniques to use to produce other effects or wave forms? I have read about the sample trick used by the Sound FX program, but I have no room for samples, so it has to be programmed and relatively short, but it doesn't have to be limited to a 60 FPS data feed.

 

One idea I have is to rapidly changing the frequency on two generators slightly out of sync to obtain an illusion of phase shifting. I have no idea if that's possible or what that would sound like.

 

Before I start experimenting, are there any limitations to the sound emulation in Classic99 and MESS that I should be aware of?

 

 

Maaany years ago I did a music program in electronic music. One the things that I notice people do not use too much in the earlier games was envelope control.

The Parsec explosion is a notable exception in the way it fades out.

 

A large part of the uniqueness of a sound is how it starts, sustains and decays. This is commonly called sound envelope.

 

I have been playing with this in Forth but it can of course be done in assembler. The difference between a "PING" sound and a "PLINK" sound is simple how fast you make it decay after it comes on. If you make a loop that drops the volume form 0 to >0F at variable speeds you can hear it. PING takes 250..500 mS to decay and PLINK decays at in 40 or 50 mS as I recall.

 

I will post my examples for you when I get back to my desktop machine.

Link to comment
Share on other sites

I made a very simple sound player using 'envelopes' for my 4K compo entry. It couldn't actually do the whole attack/decay/sustain/release thing but it could play pings and zaps and explosions by specifying only the start value, the count, and the change in volume, frequency or both. This can save a lot of bytes compared to storing raw sound lists.

  • Like 2
Link to comment
Share on other sites

 

Ah yes. I am very interested in that.

 

My solution is pretty simple, I take the frequency of the oscillator as a 32bit integer and divide it by the audio frequency and now I use your improved code to roll the nibbles into the command bytes.

Looks like this at the moment. My oscillator is off by .8 Hz at the moment, but it still makes music that sounds in tune over a couple of octaves or so.

 

With your help it now looks like this:

DECIMAL
 : f(clk) ( -- d)  46324 1  ;   \ this is 111,860 as 32 bit int.

CODE >FCODE ( 0abc -- 0cab) \ version by Farmer Potato Atariage
            0B44 ,          \ TOS 4 SRC,   \ C0AB
            0948 ,          \ TOS W MOV,   \ DUP
            0948 ,          \ W 4 SRL,     \ 0C0A
            D108 ,          \ W TOS MOVB,  \ 0CAB
            NEXT,           \ 8 BYTES, 28 uS :-)
            ENDCODE

\ convert freq. to 9919 chip code
: HZ>CODE  ( freq -- fcode ) f(clk) ROT UM/MOD NIP >FCODE ;
The advantage of doing everything in the interrupt service routine is precise timing. The console sound list format uses a number of ticks (1/60th seconds) to idle before loading the next group of bytes. Alternately, in a game you might have the ISR just decrement some timers. Then the main game loop checks the timers, pushes sound bytes, moves sprites, etc. In FORTI the MUSIC ISR plays in the background so that, naturally, you can keep programming (or fiddling with the music by changing the tempo variable.)
You might want to add nearest rounding to HZ>CODE. The sound cmd byte table in the E/A manual incorporates rounding. For instance, 110 Hz is N=1016.916 or 1017 and the code is 093F. Your code rounds toward 0 and gives 093E. That makes a bias toward sharp. The error is 1% by the time you get to D#6 at 1244.51 Hz, where rounding down gives 1256.86 Hz.
But that's inconsequential. Interestingly, a real piano is tuned sharp in that octave, but for reasons that don't apply here. (In physical strings, harmonics of A3 tend sharp, so A6 is tuned sharp to match.)
For comparison, here is the table from FORTI. It uses the rounded values.
( A+) 93F , 03C , A38 , 735 , 732 , A2F , ( 110)
( D#) F2C , 72A , 128 , D25 , B23 , B21 ,
( A ) C1F , 01E , 51C , C1A , 419 , D17 , ( 220)
( D#) 816 , 315 , 014 , E12 , D11 , D10 ,
( A ) E0F , 00F , 20E , 60D , A0C , E0B , ( 440)
( D#) 40B , A0A , 00A , 709 , F08 , 708 ,
( A ) F07 , 807 , 107 , B06 , 506 , F05 , ( 880)
( D#) A05 , 505 , 005 , C04 , 704 , 304 ,
( A ) 004 , C03 , 903 , 503 , 203 , 003 , ( 1760) 
( D#) D02 , A02 , 802 , 602 , 402 , 202 ,

Main thread for that is

May the FORTH Musical offering - FORTI Decompiled

 

Link to comment
Share on other sites

I made a very simple sound player using 'envelopes' for my 4K compo entry. It couldn't actually do the whole attack/decay/sustain/release thing but it could play pings and zaps and explosions by specifying only the start value, the count, and the change in volume, frequency or both. This can save a lot of bytes compared to storing raw sound lists.

 

LOL. I did not read the date of your post. Glad to hear it all worked out.

Link to comment
Share on other sites

 

The advantage of doing everything in the interrupt service routine is precise timing. The console sound list format uses a number of ticks (1/60th seconds) to idle before loading the next group of bytes. Alternately, in a game you might have the ISR just decrement some timers. Then the main game loop checks the timers, pushes sound bytes, moves sprites, etc. In FORTI the MUSIC ISR plays in the background so that, naturally, you can keep programming (or fiddling with the music by changing the tempo variable.)
You might want to add nearest rounding to HZ>CODE. The sound cmd byte table in the E/A manual incorporates rounding. For instance, 110 Hz is N=1016.916 or 1017 and the code is 093F. Your code rounds toward 0 and gives 093E. That makes a bias toward sharp. The error is 1% by the time you get to D#6 at 1244.51 Hz, where rounding down gives 1256.86 Hz.
But that's inconsequential. Interestingly, a real piano is tuned sharp in that octave, but for reasons that don't apply here. (In physical strings, harmonics of A3 tend sharp, so A6 is tuned sharp to match.)
For comparison, here is the table from FORTI. It uses the rounded values.
( A+) 93F , 03C , A38 , 735 , 732 , A2F , ( 110)
( D#) F2C , 72A , 128 , D25 , B23 , B21 ,
( A ) C1F , 01E , 51C , C1A , 419 , D17 , ( 220)
( D#) 816 , 315 , 014 , E12 , D11 , D10 ,
( A ) E0F , 00F , 20E , 60D , A0C , E0B , ( 440)
( D#) 40B , A0A , 00A , 709 , F08 , 708 ,
( A ) F07 , 807 , 107 , B06 , 506 , F05 , ( 880)
( D#) A05 , 505 , 005 , C04 , 704 , 304 ,
( A ) 004 , C03 , 903 , 503 , 203 , 003 , ( 1760) 
( D#) D02 , A02 , 802 , 602 , 402 , 202 ,

Main thread for that is

May the FORTH Musical offering - FORTI Decompiled

 

 

 

This is great information. Thank you again.

 

For my demo music system I made a note creator rather than a table. It works like this:

\ note object creator
: NOTE:   ( freq -- )
           CREATE           \ compile time: create a name in the dictionary
                  HZ>CODE , \ convert freq to 9919 code. Compile into note

           DOES> @ PLAY ; \ run time:  fetch the number, play the note

​Then all I had to do was this:

\ FREQ  NATURAL    FREQ  ACCIDENTAL    EN-HARMONIC
\ -------------    ----------------   ----------------
  110 NOTE: A2
  131 NOTE: C3     139 NOTE: C#3       : DB3 C#3 ;
  147 NOTE: D3     156 NOTE: D#3       : Eb3 D#3 ;
  165 NOTE: E3
  175 NOTE: F3     185 NOTE: F#3       : Gb3 F#3 ;
  196 NOTE: G3     208 NOTE: G#3       : Ab3 G#3 ;
  220 NOTE: A3     233 NOTE: A#3       : Bb3 A#3 ;
  247 NOTE: B3
  262 NOTE: C4     277 NOTE: C#4       : Db4 C#4 ;
  294 NOTE: D4     311 NOTE: D#4       : Eb4 D#4 ;
  329 NOTE: E4
  349 NOTE: F4     370 NOTE: F#4       : Gb4 F#4 ;
  392 NOTE: G4     415 NOTE: G#4       : Ab4 G#4 ;
  440 NOTE: A4     466 NOTE: A#4       : Bb4 A#4 ;
  494 NOTE: B4
  523 NOTE: C5     554 NOTE: C#5       : Db5 C#5 ;
  587 NOTE: D5     622 NOTE: D#5       : Eb5 D#5 ;
  659 NOTE: E5
  698 NOTE: F5     740 NOTE: F#5       : Gb5 F#5 ;
  784 NOTE: G5     831 NOTE: G#5       : Ab5 G#5 ;
  880 NOTE: A5     932 NOTE: A#5       : Bb5 A#5 ;
  988 NOTE: B5
 1047 NOTE: C6

Now I have a choice. Just re-write my notes using your table values or figure out how to calculate better.

I did consider raising my 32bit clock frequency to 111861 Hz. That is closer to reality than 111860. I will check the results against a tuner.

However your table is a lot simpler to use.!

 

My music player is not multi-tasking nor is it multi-voiced yet but here is script in Forth that plays a single voice quite nicely.

For timing I am using the 9901 chip timer, calibrated to provided 1/60 of a second and also switch tasks while it waits.

It seems pretty solid, but you could break it if you are not careful.

: TWINKLE
     NORMAL  72 BPM
     GEN1   1/8  A3 A3 E4 E4 F#4 F#4   1/4 E4
            1/8  D4 D4 C#4 C#4  B3 B3  1/4 A3
            STACCATTO
            1/8  E4 E4 D4 D4 C#4 C#4   1/4 B3
            NORMAL
            1/8  E4 E4 D4 D4 C#4 C#4   1/4 B3
            DETACHE
            1/8  A3 A3 E4 E4 F#4 F#4   1/4 E4
            1/8  RIT. D4 D4  RIT. C#4  C#4
                 RIT. B3  RIT. B3   RIT. 1/2. A3 ;
Link to comment
Share on other sites

 

Rounding can be something like this:
( d1 n1 -- d1+n1/2 n1)
( add 1/2 n1 to d1. kludge, ignores overflow)
: +F2/ ROT OVER 2 / + ROT ROT ;

 

 

 

I will give this a try. Thanks

 

I can reduce this a bit with a couple of CODE words in my system.

: +F2/   ROT OVER 2/ + -ROT  ;
Link to comment
Share on other sites

So after looking at your table I realized I could solve this the other way around.

 

Given f=440 and the correct command byte=>FE, what should f(clk) be?

 

The answer is 111,760 Hz. So I changed that value and my 440 is a real A. And octaves sound more in tune now albeit not musically perfect.

 

So by fudging the clock frequency I get better command byte values.

 

If I am correct your values will generate the tempered scale and mine are the mathematical values, correct at 440Hz and drifting off of musical values at the low and high end.

 

For my technical lexicon consisting of Hz and dB commands this is fine.

For my music player I am going to switch everything to your values.

 

For comparison:

( A+) 93F , ( 110)
( A ) C1F , ( 220)
( A ) E0F , ( 440)
( A ) F07 , ( 880)
( A ) 004 , ( 1760)

 

B

 

 

post-50750-0-95240800-1528252001.jpg

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...