TriOS-alt/zubehör/game boulderdash/sourcen/Bellatrix-Code/SIDemu.spin

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