;; xint code ;; defines chan0 equ 40h ; output to CTC chan1 equ 41h chan2 equ 42h inttab equ 1ah ; interrupt vector portA equ 0c0h ; port A address portB equ 0c1h ; port B address vin equ 0ffh ; volume in port pin equ 0feh ; pitch in port vout equ 0fch ; volume out pout equ 0fdh ; pitch out delay equ 0ffdh ; subroutine ;; initialize things ;; need to setup interrupt vector table, ;; give proper time constant to CTC ;; and set default values for variables setup: ld a, 82h ; port a = output, port b = input out (0c3h),a ;; not so sure about time constant stuff for CTC ld a, inttab ; get address of interrupt table ld i, a ; point to table ld a, 00h ; make table offset out (chan0), a ; store address of interrupt table ld a, 00110111b ; di,time,pre=256,ris,asap,tc,reset,i out (chan0),a ; setup channel 0 ld a, 11h ; set time constant to 9.1 ms out (chan0),a ; send time constant ld a, 11111111b ; di,time,pre=256,ris,asap,tc,reset,i out (chan1),a ; setup channel 1 ld a, 27h ; set time constant to 1 hz out (chan1),a ; send time constant ld a, 00h ; make table offset out (chan1), a ; send lsb of interrupt table ;; initialize variables ld a, 0 ld (cntr), a ; clear the counter ld (pitch),a ; clear pitch ld (vol),a ; clear volume ld (pptch),a ; clear previous pitch ld (pvol),a ; clear previous volume ld (mode),a ; set mode to pass im 2 ei dummy: ld a, 01h out (portA), a jp dummy ;; this routine is called every 10th of a sec ;; by CTC interrupt main: di ; disable interrupts push af ; push A and F registers onto the stack push bc ld a, 0 ; ld hl, 02h ; 2 ms delay out (pin),a ; trigger ADC call delay in a,(pin) ; get pitch ld (pitch),a ; store it out (vin),a ; trigger ADC call delay ; 2ms delay in a,(vin) ; get volume ld (vol),a ; store it in a, (portB) ; get switches (record, playback, fret) ld (switches), a ; save status of switches in memory bit 0, a ; check fret bit, if 1 call fret jp z, compress ; if fret is off, skip call call fret jp ldswitches compress: ld a, (pitch) call invert ld d, a ; multiplicand in d ld e, 188d ; multiplier in e call multiply ; result in hl ld a, h ; ld (pitch), a ; store back the compressed and inverted pitch ld a, (vol) call invert ld d, a ; multiplicand in d ld e, 188d ; multiplier in e call multiply ; result in hl ld a, h ld (vol), a ; store result back to vol ldswitches: ld a, (switches) ; get switches (record, playback, fret) is_rec: ;; if record button is pressed 0 otherwise bit 1, a ; check rec bit, if 1 call rec jp z, not_rec ; if 0, skip to not_rec ;; if here, we're in record mode ld b,1 ; MODE = record ld a, b ld (mode),a ; call record ; record the note jp end ; then we're done not_rec: ;; if playback button is pressed 0 otherwise bit 2, a ; check playback bit, if 1 call playback jp z, pass ; if 0, skip to pass ld b,2 ; MODE = playback ld a, b ld (mode),a call playback ; playback a note jp end ; the we're done pass: ;; if we're here, it's because we're ;; in passthrough mode. ie, no buttons were pressed ld a,0 ; MODE = pass ld (mode),a ;; if the previous mode was record, we have to ;; make sure to clean up ld a, (pmode) ; put previous mode in acc cp 1 ; check if it was record jp nz, end ;; pmode == record && mode != record ;; stoprec writes out previous note, stop byte ;; and resets counter, mem_ptr call stoprec end: ld a,(pitch) ld (pptch),a ; previous pitch = pitch ld a,(vol) ld (pvol),a ; previous volume = volume ld a,(mode) ld (pmode),a ; previous mode = mode ;; restrict output range from 0-1.1Volts and invert for VCO ld a, (pptch) ld b, a ; save pitch in b ld a, (pvol) ld c, a ; store volume in c ;; output note ld a, b out (pout), a ld a, c out (vout), a pop bc pop af ; pop A and F registers off the stack ei ; re-enable interrupts reti multiply: ;; multiplier in d, multiplicand in e ;; result in hl push af push bc ld a, d ; transfer mulitiplier to a ld d, 0 ld hl,0 ld b, 08h ; setup b to count eight rotations nxtbit: rra ; check if multiplier bit is 1 jr nc, noadd ; if not skip adding multiplicand add hl, de ; if multiplier is 1, add multiplicand to HL ; and place partial result in hl noadd: ex de, hl ; place multplicand in hl add hl, hl ; and shift left ex de, hl ; retrieve shifted multiplicand dec b ; 1 operation is complete, decrement counter jr nz, nxtbit ; go back to next bit if not done pop bc pop af ret invert: ;; inverts the bits in the range 00-bbh for the VCO input 1.1-0V ;; input in a, output in a xor 0ffh ret ;; plays back a note from memory playback: push bc push de push ix ;; check if this is the first cycle of playback ld a, (pmode) ; put previous mode into acc cp 2 ; check if it's playback jp z, contpb ; if yes, just continue playback call rst_ptr ; reset memory pointer to base ld a, 0 ld (cntr), a ; reset the counter contpb: call is_valid ; make sure mem_ptr is in the valid range ;; get the note from the current position in memory ld ix,(mem_ptr) ; ld b,(ix) ; load pitch in b ;; check if we have reached end of recording, ld a, (stopbyte) cp b ; compare byte in 'b' with stopbyte call z, rst_ptr ; if it is stopbyte then reset ptr to beginning jp z, endpb ; end playback if stopbyte reached ld c,(ix + 1) ; get volume byte ld a, b ld (pitch), a ; store pitch ld a, c ld (vol), a ; store vol ld d, (ix+2) ; get duration ld a, d ld (dur), a ; store duration out: ;; output note to DAC as long as duration is not 0 ld a, b out (pout), a ; output the pitch ld a, c out (vout), a ; oputput the volume ld ix, cntr inc (ix) ; increment the counter ld a, d cp (ix) ; compare counter with duration jp nc, endpb ; when counter <= duration, don't advance pointer ;; we need to increment mem_ptr. We need to know if incrementing ;; the high-order byte is necessary (we always increment the low-order byte ;; 3 times. call ptr_advance ld a, 0 ld (cntr), a ; reset the counter for the new note endpb: pop ix pop de pop bc ret ;; records a note to memory record: push bc push hl ld a, (pmode) ; check if previous mode was record cp 1 ; jp z, contrcd ; previous mode was record, so we just continue call rst_ptr ; otherwise, we know that we're starting to ; record, so we have to reset the pointer ; and counter ld a, 1 ld (cntr), a ; reset counter contrcd: ;; compare this note to previous note ld a, (pitch) ld b, a ld a, (pptch) cp b jp nz, newnote ;; pitch is the same. check if volume is too ld a, (pvol) ld b, a ld a, (vol) cp b jp nz, newnote samenote: ld a, (cntr) inc a ld (cntr), a ; increment counter jp endrec ; then we're done newnote: ;; write previous note to memory ;; first, make sure we have a valid place to put the note ld hl, (mem_ptr) ld bc, (mem_max) ld a, b ; load the HOB of mem_max cp h ; compare with HOB of mem_ptr jp z, checkLOB ; don't record if HOB of ptr = HOB of mem_max jp writenote checkLOB: ld a, c ; load the LOB of mem_max cp l jp z, endrec ; don't record if LOB of ptr > HOB of mem_max writenote: ;; here, we know the location is ok. ld a, (pptch) ld ix, (mem_ptr) ; ix = hl = mem_ptr ld (ix), a ; write pitch ld a, (pvol) ld (ix+1), a ; write volume ld a, (cntr) ld (ix+2), a ; write duration ;; reset stuff inc hl inc hl inc hl ld (mem_ptr), hl ; save the incremented pointer ld a, 1 ld (cntr), a ; reset counter endrec: pop hl pop bc ret org 1a05h ;; "frets" a note ;; uses a lookup table to translate input pitch to ;; output pitch. ;; for volume, it just kills the 4 least significant bits ;; this leaves us with 128 discrete volume levels which is ;; probably plenty fret: push af push hl push bc push de ld hl, ranges ; put the base address of the table into hl ld b,0 ; clear b ld a, (pitch) ; put the pitch in c xor 0ffh ld e, 00h ; index into ranges table ld d, 00h ld c, (hl) cp c jp c, getnote nxtrang: cp 0ffh jp nz, notff ld e, 27d jp getnote notff: inc e ld hl, ranges add hl, de ld c, (hl) ; load byte from ranges table cp c jp nc, nxtrang dec e getnote: ld hl, notes add hl, de ld a, (hl) ; fretted pitch in a ld (pitch), a ; store pitch ld a, (vol) ; load volume xor 0ffh and 0f0h; ld (vol),a ; store back to volume pop de pop bc pop hl pop af ret stoprec: push af push hl ld a, (stopbyte) ld hl, (mem_ptr) ld (hl), a ; load stopbyte at the end of the recording call rst_ptr ; reset memory pointer to base ld a, 0 ld (cntr), a ; reset counter to 0 pop hl pop af ret ptr_advance: push hl ld hl, (mem_ptr) ;; increment the pointer 3 times (since 1 note = 3 bytes) inc hl inc hl inc hl ld (mem_ptr), hl ; store the incremented mem_ptr pop hl ret is_valid: ;; check if mem_ptr is less than end of memory push af push hl push bc ld hl, (mem_max) ld a, h ; load a with HOB of maximum address ld bc, (mem_ptr) cp b ; compare with HOB of memory pointer jp nz, end_isvalid ; if HOB of mem_ptr > HOB of mem_max, reset pointer ld a, l ; load a with LOB of mem_max cp c ; compare with LOB of memory pointer call z, rst_ptr ; if LOB of mem_ptr > LOB of mem_max, reset pointer end_isvalid: pop bc pop hl pop af ret rst_ptr: push af ; preserve flags -- necessary for is_vaild to function properly push hl ld hl, (mem_base) ; load base address ld (mem_ptr), hl ; mem_ptr = base address pop hl pop af ret stopbyte: defb 0ffh mode: defb 00h pmode: defb 00h pitch: defb 00h pptch: defb 00h vol: defb 00h pvol: defb 00h dur: defb 00h cntr: defb 00h mem_base: defw 1ae0h mem_max: defw 1e60h mem_ptr: defw 1ae0h switches: defb 00h ;; ----------------------------------------------------- ;; note table used by fret ;; these need to be filled in once we figure out how ;; what voltages give what frequency with the VCO ranges: defb 08fh ; defb 092h ; defb 095h ; defb 098h ; defb 09ah ; defb 09dh ; defb 0a0h ; defb 0a3h ; defb 0a6h ; defb 0a9h ; defb 0abh ; defb 0afh ; defb 0b2h ; defb 0b5h ; defb 0b8h ; defb 0bah ; defb 0bdh ; defb 0c0h ; defb 0c3h ; defb 0c6h ; defb 0c9h ; defb 0cch ; defb 0cfh ; defb 0d2h ; defb 0d5h ; defb 0d8h ; defb 0dbh ; defb 0ffh ;131 notes: defb 018h ; defb 018h ; F defb 02ah ; E defb 02ah ; defb 04ah ; D defb 04ah ; defb 065h ; C defb 06fh ; B defb 06fh ; defb 07fh ; A defb 07fh ; defb 094h ; G defb 094h ; defb 0a1h ; F defb 0a4h ; E defb 0a4h ; defb 0ach ; D defb 0ach ; defb 0afh ; C defb 0b0h ; B defb 0b0h ; defb 0b2h ; A defb 0b2h ; defb 0b6h ; F defb 0b8h ; E defb 0b8h ; defb 0bbh ; D ;; ----------------------------------------------------- ;; create the interrupt vector org 1a00h intvect: defw 00h ; should never be called defw main ; CTC triggered routine