home Polyphonics
download full demonstration
source (zip archive)
download translation/encoding spreadsheet
A discussion of the tables used to encode the music
A discussion of the "nyquist" sine wave synthesiser
A discussion of the example note sequencer
overview
The synthesister works by exploiting the nyquist-shannon
law which allows regularly spaced samples of a wavefoprm to be used
to reproduce that waveforem in trhe right circumstances.
This code is intended more as a proof-of-concept than as a general base
for developing polyphonic playback The sequencer in particular is rather
feature poor and inefficient,
the constants
to do this it uses a sine wave table, but with a twist,
the table has been twice folded to reduce the space it uses this
can means that instead of 256 bytes it needs only 64 bytes and a few
words of code to unfold it as needed
.costable:
;
table of 64 cosine values. 0=cos (2pi/512)*31,
;
63=cos(127*2pi/512)*31
.db 31,31,31,31 , 31,31,31,30
;....
.db 30,30,30,30 , 30,29,29,29
; ``.
.db 28,28,28,28 , 27,27,26,26
; `.
.db 26,25,25,24 , 24,23,23,22
; `
;
`
.db 22,21,21,20 , 19,19,18,18
; `
.db 17,16,16,15 , 14,14,13,12
;
.
.db 12,11,10,09 , 09,08,07,06
;
.
.db 06,05,04,03 , 03,02,01,00
;_______________'
it also uses a table of frequency constants.
these constants K are computed by
taking into account
the desired frequency F
the number of steps per cycles in the sine table N
and the number of samples per second R
some notes were not used in the piece I chose to encode
so I didn't store their frequencies in the table.
frequency_constants: ; for 10Mhz clock/8
bit pwm
.dw 0000,491,551,618,655,735,825,926
.dw 982,982,1102,1471,1853,1963,2203,2473
.dw 2620,2941,3301,3706,3926
Note that the first constant holds the value 0, when "played" by the synthesiser
this value results in silence, it's played during rests in the music
and briefly between notes.
Finally the notes themselved were encoded in one byte each, with three
bits for the duration: and 5 bits for the pitch.
All of these constants were computed using the gnumeric spreadsheet software
(I initially tried to use Kspread but found that I couldn;t
paste from it into a text editor)
the tool texmf from the abcmidi package was also
handy for converting a midi file into text which I could then
process into the spreadsheet data.
download an example for gnumeric
(15K) download the same file saved
in excel format (150K)
the gnumeric spreadsheed has a strange extension ".gnumeric.gz" but gnumeric
can still open it directly (it's like that because geocities doesn't allow
the extension .gnumeric)
the synthesiser
A discussion of the nyquist synthesiser
_playmusic_:
.org OVF1addr
rjmp phonics
.org _playmusic_
the synthesiser "phonics" is driven by the Overflow 1 interrupt,
timer 1 also drives the PWM so phonics gets called once per PWM cycle
.dseg
note_time_a: .byte 1 ; a countdown for the duration of the note
note_ptr: .byte 2 ; a pointer into rom for the sequencer
; to fetch the next note from
freqa: .byte 2 ; the frequency for the synthesiser to synthesise
phasea: .byte 2 ; the current phase of the channel
as this example code uses 4 channels this structure is repeated three more times
.byte 1
.byte 2
freqb: .byte 2
phaseb: .byte 2
.byte 1
.byte 2
freqc: .byte 2
phasec: .byte 2
.byte 1
note_ptr_D:
.byte 2
freqd: .byte 2
phased: .byte 2
.byte 1
.byte 1
Now the synthesiser itself:
;**************************************************************************
;**************************************************************************
;*** ***
;*** Polyphonic tone generator ***
;*** ***
;**************************************************************************
;**************************************************************************
; perfromance is critical so use "exclusive" registers 11-15 / 24-25
.def fl=r11 ; freq low byte
.def fh=r12
.def pl=r13 ; phase
.def ph=r14
.def ST=r15
.def op=r25
.def cntr=r24
owning these registers exclusively saves a few cycles in the initialisation
as they don't need to be saved.
phonics:
in ST,SREG ; 1
push ZL ; 2
push ZH ; 2
push r0 ; 2
ldi ZH,high(costable<<1) ; 1
note: for efficiency costable must not cross any page boundaries
ldi cntr,4 ; 1
"4": above is the number of channels to synthesize, if sythesizing
more than 4 channels the costable must be re-generated with a lower maximum
ldi op,128 ; 1
push yl ; 2
push yh ; 2
ldi YL,freqa ; 1 14 freq
;
pholoop: ;
ld fl,Y+ ; load freq/phase 2 +1
ld fh,Y+ ; 2 +2 ph
ld pl,Y ; 2
add pl,fl ; advance phase ; 1
st y+,pl ; 2 +3
ld ph,Y ; 2
adc ph,fh ; 1
st y+,ph ; store phase ; 2 +4
Phase is a 16 bit value, but only the most significant byte is
used when looking into the costable. thus 0..255 represents the 360 degrees
(or 2¶ radians) possible range of phase
; now a lookup to the 1/4 size cosine table ;
;
brpl pho_nofold ; fold 128-255 onto 127-0
com ph ; 2
pho_nofold:
sbrc ph,6 ; 2
;; the table only holds positive values so negatives need special treatment
rjmp pho_negval ; where cosine is negative (16)
ldi ZL,low(costable<<1) ; 1
add zl,ph ; 1
lpm ; 3
add op,r0 ; 1
rjmp pho_next ; 2 (8)
;; now it is neccessary to fold 127-64 onto 0-63 and treat the result as negative
pho_negval: ; 0..63 = - (127 .. 64); (1)
ldi ZL,(low(costable<<1)+127)&255 ; 1
sub zl,ph ; 1
lpm ; 3
sub op,r0 ; 1 (7)
pho_next:
subi yl,-3 ; 1 skip music data
dec cntr ; 1
brne pholoop ; 2 LOOP 28 CYC
once out of the loop all that remains is to output the value to
the dac, here the PWM is used
out OCR1AL,op ; 1
pop yh ; 2
pop yl ; 2
pop r0 ; 2
pop ZH ; 2
pop ZL ; 2
out SREG,ST ; 1
reti ; 5 (17)
; OVERHEAD 32 CYCLES
and that's it.
The Sequencer
music:
lds a,music_state ; odd/even are all that matters here
inc a ;
sts music_state,a ;
ldi c,note_duration ; and how long until we come again
sbrs a,0 ;
ldi c,pause_duration ;
sts musiccountdown,c ;
musicccountdown is decremented in the timer 0 ISR and when
it reaches 0 the main loop runs this music subroutine. loading it with
note_duration or pause_duration above effects the amount of sleep after
this run of music.
note_duration here is the shortest note to play - in the example code
that's a 1/16 beat or semiquaver
mov h,a
ldi c,255 ; magic number "255" signifies end-of-song
mov j,c
ldi YL,note_time_a
ldi d,4 ; 4 tracks/channels
; handle the state of the note...
the main loop of the sequencer, read the countdown for each note and
proceed
apropriately
music_loop:
ld a,Y ; a= time left for this note
subi a,1
sbrc h,0 ; only write the countdown back at the beginning
; of a note period
st Y,a
brne music_nextchan
sbrc h,0
rjmp music_play_event
; now it's an inter-note silence...
music_silence: ; silence the channel
ldi b,0
std y+3,b ; zero the freq
std y+4,b
music_nextchan:
subi yl,-7 ; skip the freq,phase
dec d
brne music_loop
ret ; all done!
here's the interesting bit where the bytes are read from the ROM and
processed into frequency and duration numbers
music_play_event:
ldd zl,Y+1 ; read the pointer
ldd zh,Y+2 ; read the pointer
lpm ; use the pointer to read from the rom.
cp r0,j ; end of music?
breq music_silence ; no need to reset the count field it's maxed already.
adiw Zl,1
std Y+1,zl ; store note pointer
std Y+2,zh ; store note pointer
mov c,r0 ;
; we have a "note" in b: calculate duration.
;in out
ldi b,6 ;765
sbrc c,5 ;000 => 1
ldi b,8 ;001 => 2
sbrc c,6 ;010 => 3
lsl b ;011 => 4
sbrs c,7 ;100 => 6
lsr b ;101 => 8
sbrs c,7 ;110 => 12
lsr b ;111 => 16
The above arithmetic contortions are an ad-hoc way of translating the
3-bit lengthj field into the apropriate length count, I was lucky that
the piece I chose used only 8 different lengths of note. a more general
solution could be done using a lookup table...
st Y,b ; store duration
Now lookup the apropriate frequency constant for the note to be played
ldi zh,high(frequency_constants <<1 )
ldi zl,low( frequency_constants <<1 )
andi c,0b00011111
lsl c
add zl,c
lpm
and store it in the structure
std y+3,r0 store ls freq
adiw zl,1
lpm
std y+4,r0 store ms freq
this channel is done, prepare to do the next
rjmp music_nextchan