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
   
K= FN
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


comments? suggestions? email me!
This page passed the W3C test:Valid HTML 4.01!

Hosted by www.Geocities.ws

1