[stella] music compression

Subject: [stella] music compression
From: Schwerin <schwerin@xxxxxxxx>
Date: Mon, 5 Apr 1999 00:28:36 -0400
>Have you considered creating your own waveforms?

Yes.  Sorry I left out the juicy bits about the design of the sound engine.
I figured I would tinker with it first, but I haven't updated it since my
original post so I might as well describe what it is as of now.

The sound code is a loop consisting of two parts.  The top of the loop
computes the sound output, and updates AUDV0 and AUDV1 (AUDCx and AUDFx
are not changed, they are set to output a constant high line level).
This results in two 4 bit channels.  There are two "voices" per channel, and
the voices are mixed together in software.

The bottom of the loop updates the four pitch registers for the voices.
This part of the loop is "application specific".  In a music program, this is
where you would do the overhead involved in reading from the song data
and writing to the voice registers.  The pitch registers are "latched" in
a sense; you can leave them alone and they retain the previous value.

In review:

Sound Loop:

Assuming that Compute_Volumes() and Set_Pitches() are each written so that
they always take the same amount of time to run, the highest pitch available
to us is some function of the total time of the loop.  The current version
requires between 1 and 2 scanlines.  In this version, Set_Pitches()
leaves the latch values unaffected, and just polls the joystick to see if
the user wants to exit out of the sound playback mode.  I believe it would
be reasonable to update the sound every other scanline, leaving one line
for sound computation and one line for computing desired pitch.

The sound engine uses a waveform lookup table to generate sound.
The table is 256 bytes in length, and page aligned.  The table is one
cycle of a sine wave.  The sine wave is 3-bit sampled, one sample
per byte (so the values range from 0 - 6).  I used 0 - 6 because I think it
samples well.

You can use a different wave table if you wish, such as sawtooth or
squarewave, or random, or whatever.  All you need to do is use different
data.  The current sound engine uses that one table to generate all four
voices, but this could be modified to have different instruments for each
My sine data is listed at the end of this post.

The sound generation is a pretty standard kind of table scanning.  Each voice
gets its value from a lookup pointer to the wavetable.  The pointer
has it's own position and speed (frequency) in the table.  Each "sound
update" (SUP
from now on), the position is incremented by the freq value.  The wavetable is
read at the new address.  If the address goes beyond the table it wraps around
to the front.  If you decrease the frequency, the position moves in smaller
increments and takes more time to reach the end of the table.

Each SUP, voice0 and voice1 pointers are updated.  The 3-bit lookup for
voice0 and voice1 are added to produce a 4-bit value.  This is stored in AUDV0.
Voice2 and voice3 go into AUDV1.

The "fancy" part of this table lookup is that the frequency is a 16 bit
fixed point
value, with the lower 8 bits as the fractional part.
The position is also a 16 bit fixed value.  The top half is used to index the
wavetable.  The bottom half is used to keep track of a "fractional" position.

A simple frequency is 256.  The pointer dutifully walks through the table, one
address every SUP.

The lowest frequency is 1.  The pointer walks one address every 256 SUPs.

At frequency 0, the pointer doesn't move and the voice is stuck at some
constant volume value.

The highest frequency theoretically is $7F00 which walks half the
table every SUP, although it's safer to stick lower values which don't have
such large skips.

Frequencies above $8000 produce aliasing effects which end up sounding just
lower tones than what you want.  The table is read "backwards".

The fractional math has the benefit of a greater choice of pitches, but the
drawback is that extra noise is introduced when you have a fractional
The reason for this is that wave "samples" are stretched exactly the same
of times for frequencies like 256,512,768 etc and have a nice symmetry.
For fractional frequencies the stretching alternates from long to short, more
noticable in high tones.

Obviously the best thing would be to have a fast processor (hey, don't boo me,
I like 1 Mhz, I'm just being academic here).  The next best thing is to have
a fast sound loop called %100 of the time.  This increases the "sample rate"
and the quality of higher tones.  Even without either of these, lower tones
can come out clear.

I have played chords on this engine.  My current demo puts the 4 frequency
values on the screen for joystick interface.  You tweak them and listen to
the results.  It works.  It's not the most beautiful sounding; especially if
you have to spend half a minute setting up a chord, wishing to get to the
next chord already.  I still want to hear what it will sound like with music.

I think a more rewarding exercise would be some kind of background game
with one channel for drums or effects and one for a melody.  For the melody
channel, get into stuff like note attack and decay and use samples or synthesis
for an instrument specific sound beyond "Beep".  For the drum channel use 4-bit
samples and/or bursts of white noise.

Any volunteers out there to collect some sound samples or write
a program to automate this?  I'm not sure what sampling rate, but probably
1 sample for every 2 scanlines is in the ballpark.  (One line for sound,
one for
game logic and graphics).  What would be great would be something that could
take standard sound files like .wav for instance, and convert it into a hex
listing of values from $00 to $0F.  Of course this kind of thing requires
a lot of hand tweaking.  If anyone out there can convert or knows of
programs to convert sound files into raw data, I can write a convert to source

Also, if the music is always the same, the music can be presampled.  Instead of
reading a wavetable for each voice, read a wavetable for each channel.  The
chords get mashed together at assembly time and not in real-time.  Or do
sampling of bits and pieces of a song and trigger parts when needed.

The limitation here is memory storage, and lack of appropriate tools to
design music & soundtracks.

Anyway, I've rambled long enough......

>  This allows you to use any
>frequency you want.  If you only update the sound registers once per line,
>you get 0 to 15.7kHz, or 8 (resonable) octaves.  If you update every other
>scanline, you get a top end of 7.85kHz...still not bad.

A synthesis model like mine requires 1 to 2 lines of compute time.
A strictly 2 chan. wavetable sample model can be pulled off in 1 line (I

>Also, let's assume that no voice ever jumps more than an octave.  If you
>use relative motion, with chromatic scales, you need 3.5 bits of storage
>for each voice, and you give each voice a total range of 4 octaves

Can you explain this?  Assume you stay the same, that's "0"
Assume you go up 1 half step, thats "1"
Assume you go up 11 half steps, thats "11"
Assume you go down 1 half step, thats "12"
Assume you go down 11 half steps, thats "22"
So you need a value from 0 to 22, if 0 - 31 is 5 bits,
then I'll buy that 0-22 is 4.5 bits but not 3.5
4.5 x 4:That's 18 bits per beat, which more than my "16 bits uncompressed"
and way more than my 2.5 bits per beat goal.

>(conservative 2-scanline approach).  Also, since (I assume) you're doing
>2 voices per channel, if you're just re-writing the volume data for each
>channel, you can store the information in terms of channels instead of
>voices.  Doing this gives double the storage resolution, or 1.75 bits
>per voice, per beat.

Are you confusing the wavetable sample with note information?
I don't see how I can combine the musical information for two voices
into one channel.  (Other than wavetable precomputation, which is a
different matter than musical note data for a song).

>This all assumes infinite clock cycles with which to work.  :)  Right now,
>I'm way too tired to do the math and get the *real* numbers.  But it might
>give you a different way of thinking about the problem.

Or maybe it's time to think about a different problem, like a more samples
oriented engine as described above.  But thank you for posting it and please
help me understand some of your ideas on this.

>"So why didn't he try to give feedback before my engine was locked
>in place!???"  - Andrew

"Where were you when the page was blank??" is my favorite variety of this

For the intrepid, here is the loop.  I took out my interface code because
I'm shy on releasing it before it's improved.


*** "Quad" 4 voice wavetable based synthesiser ***

*** Set the position variables to 0 on startup
*** Set and change the freq variables for different pitches
*** pos and freq are 16 bit

                lda     Pos0
                adc     Freq0
                sta     Pos0
                lda     Pos0+1
                adc     Freq0+1
                sta     Pos0+1

                lda     Pos1
                adc     Freq1
                sta     Pos1
                lda     Pos1+1
                adc     Freq1+1
                sta     Pos1+1

                lda     Pos2
                adc     Freq2
                sta     Pos2
                lda     Pos2+1
                adc     Freq2+1
                sta     Pos2+1

                lda     Pos3
                adc     Freq3
                sta     Pos3
                lda     Pos3+1
                adc     Freq3+1
                sta     Pos3+1

                ldx     Pos0+1
                lda     Wave,X
                sta     temp
                ldx     Pos1+1
                lda     Wave,X
                adc     temp
                sta     AUDV0

                ldx     Pos2+1
                lda     Wave,X
                sta     temp
                ldx     Pos3+1
                lda     Wave,X
                adc     temp
                sta     AUDV1

*** 256 bytes of 3 bit sampled sine wave ***
                .byte $03,$03,$03,$03,$03,$03,$03,$04
                .byte $04,$04,$04,$04,$04,$04,$04,$04
                .byte $04,$04,$04,$05,$05,$05,$05,$05
                .byte $05,$05,$05,$05,$05,$05,$05,$05
                .byte $05,$05,$06,$06,$06,$06,$06,$06
                .byte $06,$06,$06,$06,$06,$06,$06,$06
                .byte $06,$06,$06,$06,$06,$06,$06,$06
                .byte $06,$06,$06,$06,$06,$06,$06,$06
                .byte $06,$06,$06,$06,$06,$06,$06,$06
                .byte $06,$06,$06,$06,$06,$06,$06,$06
                .byte $06,$06,$06,$06,$06,$06,$06,$06
                .byte $06,$06,$06,$06,$06,$06,$06,$05
                .byte $05,$05,$05,$05,$05,$05,$05,$05
                .byte $05,$05,$05,$05,$05,$05,$04,$04
                .byte $04,$04,$04,$04,$04,$04,$04,$04
                .byte $04,$04,$03,$03,$03,$03,$03,$03
                .byte $03,$03,$03,$03,$03,$03,$03,$02
                .byte $02,$02,$02,$02,$02,$02,$02,$02
                .byte $02,$02,$02,$01,$01,$01,$01,$01
                .byte $01,$01,$01,$01,$01,$01,$01,$01
                .byte $01,$01,$00,$00,$00,$00,$00,$00
                .byte $00,$00,$00,$00,$00,$00,$00,$00
                .byte $00,$00,$00,$00,$00,$00,$00,$00
                .byte $00,$00,$00,$00,$00,$00,$00,$00
                .byte $00,$00,$00,$00,$00,$00,$00,$00
                .byte $00,$00,$00,$00,$00,$00,$00,$00
                .byte $00,$00,$00,$00,$00,$00,$00,$00
                .byte $00,$00,$00,$00,$00,$00,$00,$01
                .byte $01,$01,$01,$01,$01,$01,$01,$01
                .byte $01,$01,$01,$01,$01,$01,$02,$02
                .byte $02,$02,$02,$02,$02,$02,$02,$02
                .byte $02,$02,$02,$02,$03,$03,$03,$03

Archives (includes files) at http://www.biglist.com/lists/stella/archives/
Unsub & more at http://www.biglist.com/lists/stella/

Current Thread