CON frame_buffers = 8 'frame buffers (2ÊÝn) frame_bytes = 3 {for stepsize} + 13 {for aa..ff} '16 bytes per frame frame_longs = frame_bytes / 4 '4 longs per frame frame_buffer_bytes = frame_bytes * frame_buffers frame_buffer_longs = frame_longs * frame_buffers VAR long cog, tract, pace long index, attenuation, sample '3 longs ...must long dira_, dirb_, ctra_, ctrb_, zzzzz, cnt_ '6 longs ...be long frames[frame_buffer_longs] 'many longs ...contiguous PUB start(parameters, pos_pin, neg_pin) dira_ |= (|< pos_pin + |< neg_pin) ctrb_ := $18000000 + pos_pin & $3F ctrb_ += $04000000 + (neg_pin & $3F) << 9 tract := parameters cnt_ := clkfreq / 20_000 return cog := cognew(@entry, @attenuation) PUB set_attenuation(level) attenuation := level PUB set_pace(percentage) pace := percentage PUB go(time) repeat while frames[index] bytemove(@frames[index] + 3, tract, 13) frames[index] |= $01000000 / (time * 100 / pace #> 2) index := (index + frame_longs) & constant(frame_buffer_longs - 1) DAT entry org :zero mov reserves,#0 'zero all reserved data add :zero,d0 djnz clear_cnt,#:zero mov t1,#2*15 'assemble 15 multiply steps into reserves :minst mov mult_steps,mult_step '(saves hub memory) add :minst,d0s0 test t1,#1 wc if_c sub :minst,#2 djnz t1,#:minst mov mult_ret,antilog_ret 'write 'ret' after last instruction mov t1,#13 'assemble 13 cordic steps into reserves :cstep mov t2,#8 '(saves hub memory) :cinst mov cordic_steps,cordic_step add :cinst,d0s0 djnz t2,#:cinst sub :cinst,#8 add cordic_dx,#1 add cordic_dy,#1 add cordic_a,#1 djnz t1,#:cstep mov cordic_ret,antilog_ret 'write 'ret' over last instruction mov t1,par 'get dira/dirb/ctra/ctrb add t1,#2*4 mov t2,#4 :regs rdlong dira,t1 add t1,#4 add :regs,d0 djnz t2,#:regs add t1,#4 'get cnt ticks rdlong cnt_ticks,t1 mov cnt_value,cnt 'prepare for initial waitcnt add cnt_value,cnt_ticks ' %%%%%%%%%%%%%%%%%%%%%% ' % Vocal Tract Loop % ' %%%%%%%%%%%%%%%%%%%%%% ' Wait for next sample period, then output sample loop waitcnt cnt_value,cnt_ticks 'wait for sample period rdlong t1,par 'perform master attenuation sar x,t1 mov t1,x 'update duty cycle output for pin driving add t1,h80000000 mov frqb,t1 mov t1,par 'update sample receiver in main memory add t1,#1*4 wrlong x,t1 :White_noise_source test lfsr,lfsr_taps wc 'iterate lfsr three times rcl lfsr,#1 test lfsr,lfsr_taps wc rcl lfsr,#1 test lfsr,lfsr_taps wc rcl lfsr,#1 :Aspiration mov t1,aa 'aspiration amplitude mov t2,lfsr call #mult sar t1,#8 'set x mov x,t1 :Vibrato mov t1,vr 'vibrato rate shr t1,#10 add vphase,t1 mov t1,vp 'vibrato pitch mov t2,vphase call #sine add t1,gp 'sum glottal pitch (+) into vibrato pitch (+/-) :Glottal_pulse shr t1,#2 'divide final pitch by 3 to mesh mov t2,t1 '...12 notes/octave musical scale shr t2,#2 '(multiply by %0.0101010101010101) add t1,t2 mov t2,t1 shr t2,#4 add t1,t2 mov t2,t1 shr t2,#8 add t1,t2 add t1,tune 'tune scale so that gp=100 produces 110.00Hz (A2) call #antilog 'convert pitch (log frequency) to phase delta add gphase,t2 mov t1,gphase 'convert phase to glottal pulse sample call #antilog sub t2,h40000000 mov t1,ga call #sine sar t1,#6 'add to x add x,t1 :Vocal_tract_formants mov y,#0 'reset y mov a,f1 'formant1, sum and rotate (x,y) add x,f1x add y,f1y call #cordic mov f1x,x mov f1y,y mov a,f2 'formant2, sum and rotate (x,y) add x,f2x add y,f2y call #cordic mov f2x,x mov f2y,y mov a,f3 'formant3, sum and rotate (x,y) add x,f3x add y,f3y call #cordic mov f3x,x mov f3y,y mov a,f4 'formant4, sum and rotate (x,y) add x,f4x add y,f4y call #cordic mov f4x,x mov f4y,y :Nasal_anti_formant add nx,x 'subtract from x (nx negated) mov a,nf 'nasal frequency call #cordic mov t1,na 'nasal amplitude mov t2,x call #mult mov x,nx 'restore x neg nx,t1 'negate nx :Frication mov t1,lfsr 'phase noise sar t1,#3 add fphase,t1 sar t1,#1 add fphase,t1 mov t1,ff 'frication frequency shr t1,#1 add fphase,t1 mov t1,fa 'frication amplitude mov t2,fphase call #sine add x,t1 'add to x jmp :ret 'run segment of frame handler, return to loop :ret long :wait 'pointer to next frame handler routine :wait jmpret :ret,#loop '(6 or 17.5 cycles) mov frame_ptr,par 'check for next frame add frame_ptr,#8*4 'point past miscellaneous data add frame_ptr,frame_index 'point to start of frame rdlong step_size,frame_ptr 'get stepsize and step_size,h00FFFFFF wz 'isolate stepsize and check if not 0 if_nz jmp #:next 'if not 0, next frame ready mov :final1,:finali 'no frame ready, ready to finalize parameters mov frame_cnt,#13 'iterate aa..ff :final jmpret :ret,#loop '(13.5 or 4 cycles) :final1 mov par_curr,par_next 'current parameter = next parameter add :final1,d0s0 'update pointers djnz frame_cnt,#:final 'another parameter? jmp #:wait 'check for next frame :next add step_size,#1 'next frame ready, insure accurate accumulation mov step_acc,step_size 'initialize step accumulator movs :set1,#par_next 'ready to get parameters and steps for aa..ff movd :set2,#par_curr movd :set3,#par_next movd :set4,#par_step add frame_ptr,#3 'point to first parameter mov frame_cnt,#13 'iterate aa..ff :set jmpret :ret,#loop '(19.5 or 46.5 cycles) rdbyte t1,frame_ptr 'get new parameter shl t1,#24 'msb justify :set1 mov t2,par_next 'get next parameter :set2 mov par_curr,t2 'current parameter = next parameter :set3 mov par_next,t1 'next parameter = new parameter sub t1,t2 wc 'get next-current delta with sign in c negc t1,t1 'make delta absolute (by c, not msb) rcl vscl,#1 wz, nr 'save sign into nz (vscl unaffected) mov t2,#8 'multiply delta by step size :mult shl t1,#1 wc if_c add t1,step_size djnz t2,#:mult :set4 negnz par_step,t1 'set signed step add :set1,#1 'update pointers for next parameter+step add :set2,d0 add :set3,d0 add :set4,d0 add frame_ptr,#1 djnz frame_cnt,#:set 'another parameter? :stepframe jmpret :ret,#loop '(47.5 or 8 cycles) mov :step1,:stepi 'ready to step parameters mov frame_cnt,#13 'iterate aa..ff :step jmpret :ret,#loop '(3 or 4 cycles) :step1 add par_curr,par_step 'step parameter add :step1,d0s0 'update pointers for next parameter+step djnz frame_cnt,#:step 'another parameter? add step_acc,step_size 'accumulate frame steps test step_acc,h01000000 wc 'check for frame steps done if_nc jmp #:stepframe 'another frame step? sub frame_ptr,#frame_bytes 'zero stepsize in frame to signal frame done wrlong vscl,frame_ptr add frame_index,#frame_bytes'point to next frame and frame_index,#frame_buffer_bytes - 1 jmp #:wait 'check for next frame :finali mov par_curr,par_next 'instruction used to finalize parameters :stepi add par_curr,par_step 'instruction used to step parameters ' %%%%%%%%%%%%%%%%%%%%%% ' % Math Subroutines % ' %%%%%%%%%%%%%%%%%%%%%% ' Antilog ' in: t1 = log (top 4 bits = whole number, next 11 bits = fraction) ' out: t2 = antilog ($00010000..$FFEA0000) antilog mov t2,t1 shr t2,#16 'position 11-bit fraction shr t1,#16+12 'position 4-bit whole number and t2,h00000FFE 'get table offset or t2,h0000D000 'get table base rdword t2,t2 'lookup fractional antilog or t2,h00010000 'insert leading bit shl t2,t1 'shift up by whole number antilog_ret ret ' Scaled sine ' in: t1 = unsigned scale (15 top bits used) ' t2 = angle (13 top bits used) ' out: t1 = 17-bit * 15-bit scaled sine ($80014000..$7FFEC000) sine shr t2,#32-13 'get 13-bit angle test t2,h00001000 wz 'get sine quadrant 3|4 into nz test t2,h00000800 wc 'get sine quadrant 2|4 into c negc t2,t2 'if sine quadrant 2|4, negate table offset or t2,h00007000 'insert sine table base address >> 1 shl t2,#1 'shift left to get final word address rdword t2,t2 'read sine word from table negnz t2,t2 'if quadrant 3|4, negate word shl t2,#15 'msb-justify result 'multiply follows... ' Multiply ' in: t1 = unsigned multiplier (15 top bits used) ' t2 = signed multiplicand (17 top bits used) ' out: t1 = 32-bit signed product mult shr t1,#32-15 'position unsigned multiplier sar t2,#15 'position signed multiplicand shl t2,#15-1 jmp #mult_steps 'do multiply steps mult_step sar t1,#1 wc 'multiply step that gets assembled into reserves (x15) if_c add t1,t2 ' Cordic rotation ' in: a = 0 to <90 degree angle (~13 top bits used) ' x,y = signed coordinates ' out: x,y = scaled and rotated signed coordinates cordic sar x,#1 'multiply (x,y) by %0.10011001 (0.60725 * 0.984) mov t1,x '...for cordic pre-scaling and slight damping sar t1,#3 add x,t1 mov t1,x sar t1,#4 add x,t1 sar y,#1 mov t1,y sar t1,#3 add y,t1 mov t1,y sar t1,#4 add y,t1 mov t1,x 'do first cordic step sub x,y add y,t1 sub a,h80000000 wc jmp #cordic_steps+1 'do subsequent cordic steps (skip first instruction) cordic_step mov a,a wc 'cordic step that gets assembled into reserves (x13) mov t1,y cordic_dx sar t1,#1 '(source incremented for each step) mov t2,x cordic_dy sar t2,#1 '(source incremented for each step) sumnc x,t1 sumc y,t2 cordic_a sumnc a,cordic_delta '(source incremented for each step) ' %%%%%%%%%%%%%%%%%% ' % Defined Data % ' %%%%%%%%%%%%%%%%%% tune long $66920000 'scale tuned to 110.00Hz at gp=100 (manually calibrated) lfsr long 1 'linear feedback shift register for noise generation lfsr_taps long $80061000 cordic_delta long $4B901476 'cordic angle deltas (first is h80000000) long $27ECE16D long $14444750 long $0A2C350C long $05175F85 long $028BD879 long $0145F154 long $00A2F94D long $00517CBB long $0028BE60 long $00145F30 long $000A2F98 h80000000 long $80000000 'miscellaneous constants greater than 9 bits h40000000 long $40000000 h01000000 long $01000000 h00FFFFFF long $00FFFFFF h00010000 long $00010000 h0000D000 long $0000D000 h00007000 long $00007000 h00001000 long $00001000 h00000FFE long $00000FFE h00000800 long $00000800 d0 long $00000200 'destination/source field increments d0s0 long $00000201 clear_cnt long $1F0 - reserves 'number of reserved registers to clear on startup ' %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ' % Undefined Data (zeroed by initialization code) % ' %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% reserves 'reserved registers that get cleared on startup frqa_center res 1 cnt_ticks res 1 cnt_value res 1 frame_index res 1 frame_ptr res 1 frame_cnt res 1 step_size res 1 step_acc res 1 vphase res 1 gphase res 1 fphase res 1 f1x res 1 f1y res 1 f2x res 1 f2y res 1 f3x res 1 f3y res 1 f4x res 1 f4y res 1 nx res 1 a res 1 x res 1 y res 1 t1 res 1 t2 res 1 par_curr '*** current parameters aa res 1 'aspiration amplitude ga res 1 'glottal amplitude gp res 1 'glottal pitch vp res 1 'vibrato pitch vr res 1 'vibrato rate f1 res 1 'formant1 frequency f2 res 1 'formant2 frequency f3 res 1 'formant3 frequency f4 res 1 'formant4 frequency na res 1 'nasal amplitude nf res 1 'nasal frequency fa res 1 'frication amplitude ff res 1 'frication frequency par_next res 13 '*** next parameters par_step res 13 '*** parameter steps mult_steps res 2 * 15 'assembly area for multiply steps w/ret mult_ret sine_ret res 1 cordic_steps res 8 * 13 - 1 'assembly area for cordic steps w/ret cordic_ret res 1