' Propeller SID (MOS 6581) Emulator. ' Code released to public domain ' H. Peraza. ' ' Changes: ' ' 2009-12-07 - Started with the simple PWM example code from Propeller App Notes. ' - Simple frequency control added. 16-bit phase register used. ' - Added code to generate 3 of the SID waveforms: sawtooth, triangle ' and rectangle (square) wave. Sample uses upper 8 bits of the phase register. ' 2009-12-08 - Added a simple shift-register noise generator. ' - Changed sampling frequency to SID clk/32 = 31.25 KHz and phase register ' size to 19 bits so we can directly use SID frequency values. ' - Two more voices added, using identical code. The voices are simply added ' together to produce the output. ' - Scale waveforms according to an specified amplitude value. ' - "AND" waveforms together when more than one is selected in the control register. ' - Fixed noise generation. Now noise pitch also varies with frequency changes. ' 2009-12-09 - Added variables for all SID register. ' - Added envelope generation in a separate cog. Sounding already familiar! ' - Added a function to set a given SID register, to be used by external programs. ' - Got the BoulderDash main tune working! The transitions of one voice sounds ' somewhat "melted", though. ' - Implemented pulse-width control of the rectangle wave. ' - Added ring modulation option. ' 2009-12-10 - Changed the ring modulation method, hopefully is now closer to what SID does. ' Now harmonics and heterodynes can be heard when sweeping the frequency of ' the modulating generator. ' - Envelope generation is now done in the same main cog. The loop was split in two ' in order to have enough computing time. Waveform is now updated (sampled) at a ' half of the PWM frequency. The phase register is now 18 bits, but sample resolution ' is still the same upper 8 bits. As a side effect, digital artifacts seem less ' noticeable! Envelope is updated every 1.024 msec. ' 2009-12-11 - Track sustain changes while in sustain cycle, but only in the "down" direction. ' - Added main volume control. ' - Scale envelopes down a bit in order to avoid distortion when the three voices ' play simultaneously and the full volume is selected. ' - Had to rewrite the whole envelope generator again, in order to make it track ' the signal levels at any time and at any gate triggering moment. What a pain. ' Still not working correctly in all cases. var ' The 29 SID registers word voice1_Freq ' frequency word voice1_PW ' 12-bit pulse width for the RECTANGLE waveform byte voice1_ControlReg ' waveform type and control byte voice1_AttackDecay ' bits 0-3 decay, 4-7 attack byte voice1_SustainRelease ' bits 0-3 release, 4-7 sustain word voice2_Freq ' frequency word voice2_PW ' 12-bit pulse width for the RECTANGLE waveform byte voice2_ControlReg ' waveform type and control byte voice2_AttackDecay ' bits 0-3 decay, 4-7 attack byte voice2_SustainRelease ' bits 0-3 release, 4-7 sustain word voice3_Freq ' frequency word voice3_PW ' 12-bit pulse width for the RECTANGLE waveform byte voice3_ControlReg ' waveform type and control byte voice3_AttackDecay ' bits 0-3 decay, 4-7 attack byte voice3_SustainRelease ' bits 0-3 release, 4-7 sustain word FC ' filter cutoff/center frequency, not implemented byte Res_Filt ' bits 0-3 filter on/off selection, bits 4-7 filter resonance, not implemented byte Mode_Vol ' bits 0-3 volume level, bits 4-6 filter mode, bit 7 voice 3 on/off, filter bits not implemented byte PotX ' potentiometer X ADC value, not implemented byte PotY ' potentiometer Y ADC value, not implemented byte Osc3_Random ' upper 8-bits of voice 3 waveform, not implemented byte Env3 ' upper 8-bits of voice 3 envelope generator, not implemented con ' SID voice control register bits CREG_GATE = %0000_0001 CREG_SYNC = %0000_0010 CREG_RINGMOD = %0000_0100 CREG_TEST = %0000_1000 CREG_TRIANGLE = %0001_0000 CREG_SAWTOOTH = %0010_0000 CREG_RECTANGLE = %0100_0000 CREG_NOISE = %1000_0000 ' SID RES/FILT (reg 23) FREG_FILT1 = %0000_0001 FREG_FILT2 = %0000_0010 FREG_FILT3 = %0000_0100 ' SID MODE/VOL (reg 24) MREG_VOICE3OFF = %1000_0000 dat ' attack, decay and release envelope timings (msecs) AttackTimes word 2, 8, 16, 24 word 38, 56, 68, 80 word 100, 250, 500, 800 word 1000, 3000, 5000, 8000 DecayReleaseTimes word 6, 24, 48, 72 word 114, 168, 204, 240 word 300, 750, 1500, 2400 word 3000, 9000, 15000, 24000 var long param[26] word attack_time_1 word decay_time_1 word sustain_level_1 word release_time_1 word attack_time_2 word decay_time_2 word sustain_level_2 word release_time_2 word attack_time_3 word decay_time_3 word sustain_level_3 word release_time_3 pub start(pin) param[0] := clkfreq / F_PWM param[1] := |< pin param[2] := (%00100 << 26) + pin 'NCO/PWM + pin param[3] := @voice1_ControlReg param[4] := @voice1_Freq param[5] := @voice1_PW param[6] := @attack_time_1 param[7] := @decay_time_1 param[8] := @sustain_level_1 param[9] := @release_time_1 param[10] := @voice2_ControlReg param[11] := @voice2_Freq param[12] := @voice2_PW param[13] := @attack_time_2 param[14] := @decay_time_2 param[15] := @sustain_level_2 param[16] := @release_time_2 param[17] := @voice3_ControlReg param[18] := @voice3_Freq param[19] := @voice3_PW param[20] := @attack_time_3 param[21] := @decay_time_3 param[22] := @sustain_level_3 param[23] := @release_time_3 param[24] := @RES_Filt param[25] := @Mode_Vol 'initialize SID registers voice1_SustainRelease := $F0 voice2_SustainRelease := $F0 voice3_SustainRelease := $F0 Mode_Vol := $00 'volume off init_envelope1 init_envelope2 init_envelope3 cognew(@entry, @param) pub set_register(regnum, val) val &= $FF case regnum 'voice1 0: voice1_Freq &= $FF00 voice1_Freq |= val 1: voice1_Freq &= $00FF voice1_Freq |= val << 8 'shall we latch value and update freq only when writing hi-byte? 2: voice1_PW &= $FF00 voice1_PW |= val 3: voice1_PW &= $00FF voice1_PW |= val << 8 4: voice1_ControlReg := val 5: voice1_AttackDecay := val init_envelope1 6: voice1_SustainRelease := val init_envelope1 'voice2 7: voice2_Freq &= $FF00 voice2_Freq |= val 8: voice2_Freq &= $00FF voice2_Freq |= val << 8 9: voice2_PW &= $FF00 voice2_PW |= val 10: voice2_PW &= $00FF voice2_PW |= val << 8 11: voice2_ControlReg := val 12: voice2_AttackDecay := val init_envelope2 13: voice2_SustainRelease := val init_envelope2 'voice3 14: voice3_Freq &= $FF00 voice3_Freq |= val 15: voice3_Freq &= $00FF voice3_Freq |= val << 8 16: voice3_PW &= $FF00 voice3_PW |= val 17: voice3_PW &= $00FF voice3_PW |= val << 8 18: voice3_ControlReg := val 19: voice3_AttackDecay := val init_envelope3 20: voice3_SustainRelease := val init_envelope3 'misc 21: FC := val 22: RES_Filt := val 23: Mode_Vol := val con F_PWM = 31250 ' PWM osc period is 32µS period, waveform samples are output at half of that rate MAXLEVEL = 1000 * 16 ' = 16000 max level alloved per voice, must be divisible by 16 pri init_envelope1 sustain_level_1 := (voice1_SustainRelease >> 4) * constant(MAXLEVEL / 16) attack_time_1 := word[@AttackTimes + (voice1_AttackDecay >> 4) * 2] decay_time_1 := word[@DecayReleaseTimes + (voice1_AttackDecay & $0F) * 2] release_time_1 := word[@DecayReleaseTimes + (voice1_SustainRelease & $0F) * 2] pri init_envelope2 sustain_level_2 := (voice2_SustainRelease >> 4) * constant(MAXLEVEL / 16) attack_time_2 := word[@AttackTimes + (voice2_AttackDecay >> 4) * 2] decay_time_2 := word[@DecayReleaseTimes + (voice2_AttackDecay & $0F) * 2] release_time_2 := word[@DecayReleaseTimes + (voice2_SustainRelease & $0F) * 2] pri init_envelope3 sustain_level_3 := (voice3_SustainRelease >> 4) * constant(MAXLEVEL / 16) attack_time_3 := word[@AttackTimes + (voice3_AttackDecay >> 4) * 2] decay_time_3 := word[@DecayReleaseTimes + (voice3_AttackDecay & $0F) * 2] release_time_3 := word[@DecayReleaseTimes + (voice3_SustainRelease & $0F) * 2] dat org entry mov R0, par movd :load, #period mov R1, #26 :load rdlong 0, R0 'copy parameter block add R0, #4 add :load, d0 djnz R1, #:load mov dira, diraval 'set APIN to output mov ctra, ctraval 'establish counter A mode and APIN mov frqa, #1 'set counter to increment 1 each cycle mov switch, #0 mov phase1, #0 'init phases mov phase2, #0 mov phase3, #0 mov level1, #0 'reset envelope levels mov level2, #0 mov level3, #0 mov level1init, #0 mov level2init, #0 mov level3init, #0 mov level1acc, #0 mov level2acc, #0 mov level3acc, #0 mov level1acc, max_level mov level2acc, max_level mov level3acc, max_level mov t1cnt, #0 mov t2cnt, #0 mov t3cnt, #0 mov env_cycle, #%111 'all three voices start with attack on next cycle mov cnt_msec, #1 mov time, cnt 'record current time add time, period 'establish next period :loop xor switch, #1 wz if_z jmp #:L1 ' Voice1 rdword next_phase, voice1freq add next_phase, phase1 'advance phase rdbyte R2, voice1creg 'get control register bits into R2 mov sig1, #$FF 'set initial sample result to FF, so we could "and" different waveforms together test R2, #CREG_NOISE wz 'NOISE waveform? if_z jmp #:L12 'jump if not mov R0, next_phase xor R0, phase1 test R0, Hex10000 wz mov R0, noise1 if_z jmp #:L11 mov R0, noise and R0, #$FF mov noise1, R0 :L11 and sig1, R0 :L12 mov R0, phase1 shr R0, #10 'we use the upper 8 bits of the 18-bit phase value to generate the waveform and R0, #$FF 'get the waveform sample value into R0 mov R3, R0 ' and into R3 (this is already a sawtooth) test R2, #CREG_TRIANGLE wz 'TRIANGLE waveform? if_z jmp #:L14 test R0, #$80 wz 'sample MSB set? if_nz xor R0, #$FF 'invert sample if yes shl R0, #1 'shift left and we have a triangle test R2, #CREG_RINGMOD wz 'check for ring modulation (allowed only with triangle waveform) if_z jmp #:L13 'skip if no modulation mov R4, phase1 'this is as xor R4, phase3 ' close as I can get test R4, Hex8000 wz ' to 6581's idea if_nz xor R0, #$FF ' of ring modulation :L13 and sig1, R0 '"and" the waveforms :L14 test R2, #CREG_SAWTOOTH wz 'SAWTOOTH? if_nz and sig1, R3 'this is easy test R2, #CREG_RECTANGLE wz 'RECTANGLE? if_z jmp #:L15 'skip if not rdword R0, voice1pw 'get the pulse width into R0 shr R0, #4 'use only the high 8 bits of the 12-bit value cmp R0, R3 wc, wz 'compare with sawtooth if_c_and_z and sig1, #0 'clear output if pw < sawtooth :L15 mov R0, sig1 'get resulting sample into R0 mov R1, level1 'get current envelope amplitude into R1 call #Mult 'multiply with the current envelope amplitude shr R1, #12 'shift result to get useful bits mov sig1, R1 'store result for voice 1 mov phase1, next_phase 'store new waveform phase ' Voice2 rdword next_phase, voice2freq add next_phase, phase2 rdbyte R2, voice2creg 'get control register bits into R2 mov sig2, #$FF 'set initial sample value test R2, #CREG_NOISE wz 'NOISE waveform? if_z jmp #:L22 'skip if not mov R0, next_phase xor R0, phase2 test R0, Hex10000 wz mov R0, noise2 if_z jmp #:L21 mov R0, noise and R0, #$FF mov noise2, R0 :L21 and sig2, R0 :L22 mov R0, phase2 shr R0, #10 'get the upper 8 bits of the 18-bit phase value and R0, #$FF ' into R0 mov R3, R0 ' and into R3 test R2, #CREG_TRIANGLE wz 'TRIANGLE waveform? if_z jmp #:L24 test R0, #$80 wz 'MSB set? if_nz xor R0, #$FF 'invert sample if yes shl R0, #1 'shift left and we have a triangle test R2, #CREG_RINGMOD wz 'check for ring modulation if_z jmp #:L23 'skip if no modulation mov R4, phase2 xor R4, phase1 test R4, Hex8000 wz 'emulate 6581's ring modulation if_nz xor R0, #$FF :L23 and sig2, R0 '"and" the waveforms together :L24 test R2, #CREG_SAWTOOTH wz 'SAWTOOTH? if_nz and sig2, R3 'already have it test R2, #CREG_RECTANGLE wz 'RECTANGLE? if_z jmp #:L25 'skip if not rdword R0, voice2pw 'get pulse width into R0 shr R0, #4 'use upper 8 bits cmp R0, R3 wc, wz 'compare with sawtooth if_c_or_z and sig2, #0 'clear output if pw < sawtooth :L25 mov R0, sig2 'get resulting sample into R0 mov R1, level2 'get voice 2 amplitude into R1 call #Mult 'multiply by envelope amplitude shr R1, #12 'get useful bits mov sig2, R1 'store result for voice 2 mov phase2, next_phase 'store new waveform phase ' Voice3 rdword next_phase, voice3freq add next_phase, phase3 rdbyte R2, voice3creg 'get control bits into R2 mov sig3, #$FF 'set initial sample value test R2, #CREG_NOISE wz 'NOISE waveform? if_z jmp #:L32 'skip if not mov R0, next_phase xor R0, phase3 test R0, Hex10000 wz mov R0, noise3 if_z jmp #:L31 mov R0, noise and R0, #$FF mov noise3, R0 :L31 and sig3, R0 :L32 mov R0, phase3 shr R0, #10 'get the upper 8 bits of the 16-bit phase value and R0, #$FF ' into R0 mov R3, R0 ' and R3 test R2, #CREG_TRIANGLE wz 'TRIANGLE waveform? if_z jmp #:L34 test R0, #$80 wz 'MSB set? if_nz xor R0, #$FF 'invert sample if yes shl R0, #1 'shift left and we have a triangle test R2, #CREG_RINGMOD wz 'check for ring modulation if_z jmp #:L33 'skip if no modulation wanted mov R4, phase3 xor R4, phase2 test R4, Hex8000 wz 'emulate SID's ring modulation if_nz xor R0, #$FF :L33 and sig3, R0 'merge the waveforms together :L34 test R2, #CREG_SAWTOOTH wz 'SAWTOOTH? if_nz and sig3, R3 'already was in R3 test R2, #CREG_RECTANGLE wz 'RECTANGLE? if_z jmp #:L35 'jump if not rdword R0, voice3pw 'else get pulse width into R0 shr R0, #4 'leave only the upper 8 bits cmp R0, R3 wc, wz 'compare with sawtooth if_c_and_z and sig2, #0 'result is zero if pw < sawtooth :L35 mov R0, sig3 'get resulting sample into R0 mov R1, level3 'get current level of voice 3 into R1 call #Mult 'multiply by envelope amplitude shr R1, #12 'get useful bits mov sig3, R1 'and store result for voice3 mov phase3, next_phase ' Noise generator mov R0, noise shl noise, #1 xor R0, noise test R0, Hex4000 wz if_nz or noise, #1 jmp #:L2 :L1 'Update envelopes djnz cnt_msec, #:L41 'jump if not yet time to update envelopes mov cnt_msec, #16 '1.024 msec 'Voice 1 rdbyte R0, voice1creg 'load values rdword R1, attack1time rdword R2, decay1time rdword R3, sustain1level rdword R4, release1time test R0, #CREG_GATE wz 'if gate bit is set then process attack, decay and sustain if_z jmp #:L42 ' else release test gate_edge, #%001 wz 'gate just applied? or gate_edge, #%001 if_nz jmp #:L40 'jump if yes or env_cycle, #%001 mov t1cnt, R1 'else init attack phase, set counter to attack time mov level1init, level1 'set initial level mov level1inc, max_level ' and compute increment sub level1inc, level1init mov level1acc, #0 'reset accumulator :L40 test env_cycle, #%001 wz 'attack? if_z jmp #:L43 'jump to decay/sustain if not add level1acc, level1inc 'add increment to accum mov R0, level1acc 'divisor (attack time) already in R1 call #Divide 'obtain scaled level and R0, HexFFFF 'get rid of remainder add R0, level1init ' add to starting level mov level1, R0 ' and we have the current envelope level sub t1cnt, #1 'decrement attack time counter cmp t1cnt, #0 wz 'end of attack phase? if_nz jmp #:L51 'jump if yes and env_cycle, #%110 'switch to decay/sustain if yes mov t1cnt, R2 'set envelope time counter to decay counter mov level1init, level1 'set initial decay level mov level1inc, level1init ' and compute increment accordingly sub level1inc, R3 mov level1acc, #0 'reset accumulator jmp #:L51 :L43 cmp t1cnt, #0 wz 'end of decay phase? if_nz jmp #:L45 'jump if not, else sustain cmp R3, level1 wc 'current level > sustain? if_nc jmp #:L51 'jump if not add level1acc, level1 'else track level jmp #:L47 :L45 sub t1cnt, #1 'else decrement decay counter add level1acc, level1inc 'add increment to accumulator :L47 mov R0, level1acc mov R1, R2 'get divisor (decay time) into R1 call #Divide and R0, HexFFFF 'get rid of remainder mov level1, level1init sub level1, R0 'subtract from initial level mins level1, #0 jmp #:L51 :L42 test gate_edge, #%001 wz 'gate just released? and gate_edge, #%110 if_z jmp #:L46 mov t1cnt, R4 'prepare for release cycle mov level1init, level1 mov level1inc, level1 mov level1acc, #0 :L46 cmp t1cnt, #0 wz 'end of release cycle? if_z mov level1, #0 'silence voice if yes if_z jmp #:L51 ' and skip processing add level1acc, level1inc mov R0, level1acc mov R1, R4 call #Divide and R0, HexFFFF mov level1, level1init sub level1, R0 mins level1, #0 'don't go below zero sub t1cnt, #1 'decrement release counter :L51 'Voice 2 rdbyte R0, voice2creg 'load values rdword R1, attack2time rdword R2, decay2time rdword R3, sustain2level rdword R4, release2time test R0, #CREG_GATE wz 'if gate bit is set then process attack, decay and sustain if_z jmp #:L52 ' else release test gate_edge, #%010 wz 'gate just applied? or gate_edge, #%010 if_nz jmp #:L50 'jump if yes or env_cycle, #%010 mov t2cnt, R1 'else init attack phase, set counter to attack time mov level2init, level2 'set initial level mov level2inc, max_level ' and compute increment accordingly sub level2inc, level2init mov level2acc, #0 'reset accumulator :L50 test env_cycle, #%010 wz 'attack? if_z jmp #:L53 'jump to decay/sustain if not mov R6, R1 'save decay end time in R6 add level2acc, level2inc 'add increment to accum mov R0, level2acc 'divisor (attack time) already in R1 call #Divide 'obtain scaled level and R0, HexFFFF 'get rid of remainder add R0, level2init ' add to starting level mov level2, R0 ' and we have the current envelope level sub t2cnt, #1 'decrement attack time counter cmp t2cnt, #0 wz 'end of attack phase? if_nz jmp #:L61 and env_cycle, #%101 'switch to decay/sustain if yes mov t2cnt, R2 'set envelope time counter to decay mov level2init, level2 'set initial decay level mov level2inc, level2init ' and compute increment accordingly sub level2inc, R3 mov level2acc, #0 'reset accumulator jmp #:L61 :L53 cmp t2cnt, #0 wz 'end of decay phase? if_nz jmp #:L55 'jump if not, else sustain cmp R3, level2 wc 'current level > sustain? if_nc jmp #:L61 'jump if not add level2acc, level2 'else track level jmp #:L57 :L55 sub t2cnt, #1 'else decrement decay counter add level2acc, level2inc 'add increment to accumulator :L57 mov R0, level2acc mov R1, R2 'get divisor (decay time) into R1 call #Divide and R0, HexFFFF 'get rid of remainder mov level2, level2init sub level2, R0 'subtract from initial level mins level2, #0 jmp #:L61 :L52 test gate_edge, #%010 wz 'gate just released? and gate_edge, #%101 if_z jmp #:L56 mov t2cnt, R4 'prepare for release cycle mov level2init, level2 mov level2inc, level2 mov level2acc, #0 :L56 cmp t2cnt, #0 wz 'end of release cycle? if_z mov level2, #0 'silence voice if yes if_z jmp #:L61 ' and skip processing add level2acc, level2inc mov R0, level2acc mov R1, R4 call #Divide and R0, HexFFFF mov level2, level2init sub level2, R0 mins level2, #0 'don't go below zero sub t2cnt, #1 'decrease release counter :L61 'Voice 3 rdbyte R0, voice3creg 'load values rdword R1, attack3time rdword R2, decay3time rdword R3, sustain3level rdword R4, release3time test R0, #CREG_GATE wz 'if gate bit is set then process attack, decay and sustain if_z jmp #:L62 ' else release test gate_edge, #%100 wz 'gate just applied? or gate_edge, #%100 if_nz jmp #:L60 'jump if yes or env_cycle, #%100 mov t3cnt, R1 'else init attack phase, set envelope time counter to attack mov level3init, level3 'set initial level mov level3inc, max_level ' and compute increment sub level3inc, level3init ' accordingly mov level3acc, #0 'reset accumulator :L60 test env_cycle, #%100 wz 'attack? if_z jmp #:L63 'jump to decay/sustain if not mov R6, R1 'save decay end time in R6 add level3acc, level3inc 'add increment to accum mov R0, level3acc 'divisor (attack time) already in R1 call #Divide 'obtain scaled level and R0, HexFFFF 'get rid of remainder add R0, level3init ' add to starting level mov level3, R0 ' and we have the current envelope level sub t3cnt, #1 'decrement attack time counter cmp t3cnt, #0 wz 'end of attack phase? if_nz jmp #:L41 and env_cycle, #%011 'switch to decay/sustain if yes mov t3cnt, R2 'reset envelope time counter, now decay counter mov level3init, level3 'set initial decay level mov level3inc, level3init ' and compute increment accordingly sub level3inc, R3 mov level3acc, #0 'reset accumulator jmp #:L41 :L63 cmp t3cnt, #0 wz 'end of decay phase? if_nz jmp #:L65 'jump if not, else sustain cmp R3, level3 wc 'current level > sustain? if_nc jmp #:L41 'jump if not add level3acc, level3 'else track level jmp #:L67 :L65 sub t3cnt, #1 'else decrement decay counter add level3acc, level3inc 'add increment to accumulator :L67 mov R0, level3acc mov R1, R2 'get divisor (decay time) into R1 call #Divide and R0, HexFFFF 'get rid of remainder mov level3, level3init sub level3, R0 'subtract from initial level mins level3, #0 jmp #:L41 :L62 test gate_edge, #%100 wz 'gate just released? and gate_edge, #%011 if_z jmp #:L66 mov t3cnt, R4 'prepare for release cycle mov level3init, level3 mov level3inc, level3 mov level3acc, #0 :L66 cmp t3cnt, #0 wz 'end of release cycle? if_z mov level3, #0 'silence voice if yes if_z jmp #:L41 ' and skip processing add level3acc, level3inc mov R0, level3acc mov R1, R4 call #Divide and R0, HexFFFF mov level3, level3init sub level3, R0 mins level3, #0 'don't go below zero sub t3cnt, #1 'decrement release counter :L41 ' Mixer (simple adder) rdbyte R1, modevol 'get mode bits into R3 mov R0, sig1 add R0, sig2 test R1, #MREG_VOICE3OFF wz 'check if voice 3 is disabled if_z add R0, sig3 and R1, #$0F 'get main volume bits call #Mult 'multiply by signal level shr R1, #4 'scale result mov value, R1 :L2 waitcnt time, period 'wait until next period neg phsa, value jmp #:loop 'loop for next cycle '──────────────────────────────────────────────────────────────────────────────────────── ' Multiply R0[15..0] by R1[15..0] (R1[31..16] must be 0) ' On exit, product is in R1[31..0] Mult shl R0, #16 'get multiplicand into R0[31..16] mov R5, #16 'ready for 16 multiplier bits shr R1, #1 wc 'get initial multiplier bit into c :m1 if_c add R1, R0 wc 'if c set, add multiplicand to product rcr R1, #1 wc 'put next multiplier in c, shift prod. djnz R5, #:m1 'loop until done Mult_ret ret ' Divide R0[31..0] by R1[15..0] (R1[16] must be 0) ' On exit, quotient is in R0[15..0] and remainder is in R0[31..16] Divide shl R1, #15 'get divisor into R1[30..15] mov R5, #16 'ready for 16 quotient bits :d1 cmpsub R0, R1 wc 'R1 =< R0? Subtract it, quotient bit in c rcl R0, #1 'rotate c into quotient, shift dividend djnz R5, #:d1 'loop until done divide_ret ret 'quotient in R0[15..0], remainder in R0[31..16] '──────────────────────────────────────────────────────────────────────────────────────── d0 long 1 << 9 << 0 Hex4000 long $4000 Hex8000 long $8000 HexFFFF long $FFFF Hex10000 long $10000 max_level long MAXLEVEL noise long $6DE7 cnt_msec res 1 period res 1 diraval res 1 '|< pin ctraval res 1 '%00100 << 26 + pin (NCO/PWM + pin) voice1creg res 1 voice1freq res 1 voice1pw res 1 attack1time res 1 decay1time res 1 sustain1level res 1 release1time res 1 voice2creg res 1 voice2freq res 1 voice2pw res 1 attack2time res 1 decay2time res 1 sustain2level res 1 release2time res 1 voice3creg res 1 voice3freq res 1 voice3pw res 1 attack3time res 1 decay3time res 1 sustain3level res 1 release3time res 1 resfilt res 1 modevol res 1 time res 1 value res 1 switch res 1 next_phase res 1 phase1 res 1 phase2 res 1 phase3 res 1 level1 res 1 level2 res 1 level3 res 1 level1acc res 1 level2acc res 1 level3acc res 1 level1inc res 1 level2inc res 1 level3inc res 1 level1init res 1 level2init res 1 level3init res 1 sig1 res 1 sig2 res 1 sig3 res 1 noise1 res 1 noise2 res 1 noise3 res 1 t1cnt res 1 t2cnt res 1 t3cnt res 1 env_cycle res 1 'envelope cycle: bits 0..2 set if voice 1..3 starts attack on next gate gate_edge res 1 'gate edge detection: bits 0..2 if up transition detected for voice 1..3 R0 res 1 R1 res 1 R2 res 1 R3 res 1 R4 res 1 R5 res 1 R6 res 1 fit