831 lines
74 KiB
Plaintext
831 lines
74 KiB
Plaintext
' 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 |