328 lines
21 KiB
Plaintext
328 lines
21 KiB
Plaintext
|
{{
|
||
|
┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||
|
│ AYcog - AY-3-891X / YM2149 emulator V0.22 (C) 2010-05 Johannes Ahlebrand │
|
||
|
├──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
|
||
|
│ TERMS OF USE: Parallax Object Exchange License │
|
||
|
├──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
|
||
|
│Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation │
|
||
|
│files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, │
|
||
|
│modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software│
|
||
|
│is furnished to do so, subject to the following conditions: │
|
||
|
│ │
|
||
|
│The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.│
|
||
|
│ │
|
||
|
│THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE │
|
||
|
│WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR │
|
||
|
│COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, │
|
||
|
│ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. │
|
||
|
└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||
|
}}
|
||
|
CON
|
||
|
|
||
|
PSG_FREQ = 2_000_000.0 ' Clock frequency input to the chip (Colour Genie EG2000 computer runs at 2.2Mhz)
|
||
|
|
||
|
' WARNING !!
|
||
|
' Don't alter the constants below unless you know what you are doing
|
||
|
'-------------------------------------------------------------------
|
||
|
SAMPLE_RATE = 125_000 ' Sample rate of AYcog
|
||
|
OSC_CORR = trunc(1.05 * PSG_FREQ) ' Calibrates the relative oscillator frequency
|
||
|
NOISE_CORR = OSC_CORR>>1 ' Calibrates the relative noise frequency
|
||
|
ENV_CORR = OSC_CORR>>6 ' Calibrates the relative envelope timing
|
||
|
'
|
||
|
' Reg bits function
|
||
|
' -----------------------------------
|
||
|
' 00 7..0 channel A fine tune
|
||
|
' 01 3..0 channel A coarse tune
|
||
|
' 02 7..0 channel B fine tune
|
||
|
' 03 3..0 channel B coarse tune
|
||
|
' 04 7..0 channel C fine tune
|
||
|
' 05 3..0 channel C coarse tune
|
||
|
' 06 4..0 noise period
|
||
|
' 07 7..0 enable register
|
||
|
' 08 4..0 channel A volume
|
||
|
' 09 4..0 channel B volume
|
||
|
' 10 4..0 channel C volume
|
||
|
' 11 7..0 envelope fine tune
|
||
|
' 12 7..0 envelope coarse tune
|
||
|
' 13 3..0 envelope shape
|
||
|
' 14 7..0 I/O port A value
|
||
|
' 15 7..0 I/O port B value
|
||
|
|
||
|
VAR
|
||
|
long cog
|
||
|
|
||
|
PUB start(right,left,AYregisters)
|
||
|
if (AYregisters & 1) <> 0 ' we need word aligned registers
|
||
|
abort(-1)
|
||
|
arg1 := $18000000 | left
|
||
|
arg2 := $18000000 | right
|
||
|
r1 := ((1<<right) | (1<<left))&!1
|
||
|
sampleRate := clkfreq/SAMPLE_RATE
|
||
|
cog := cognew(@AYEMU,AYregisters) + 1
|
||
|
return cog
|
||
|
|
||
|
PUB stop
|
||
|
if cog
|
||
|
cogstop(cog~ -1)
|
||
|
|
||
|
dat org 0
|
||
|
'
|
||
|
' Assembly AY emulator
|
||
|
'
|
||
|
AYEMU mov AY_Address, par ' Setup everyting
|
||
|
mov dira, r1
|
||
|
mov ctra, arg1
|
||
|
mov ctrb, arg2
|
||
|
mov waitCounter, cnt
|
||
|
add waitCounter, sampleRate
|
||
|
'-----------------------------------------------------------
|
||
|
mainLoop call #getRegisters
|
||
|
call #AY ' Main loop
|
||
|
call #mixer
|
||
|
jmp #mainLoop
|
||
|
|
||
|
'
|
||
|
' Read all AY registers from hub memory and convert
|
||
|
' them to more convenient representations.
|
||
|
'
|
||
|
getRegisters mov tempValue, AY_Address
|
||
|
rdword frequency1, tempValue ' reg 0+1 Read in all 4
|
||
|
shl frequency1, #20 ' frequency registers
|
||
|
add tempValue, #2 ' and make them "32 bits"
|
||
|
rdword frequency2, tempValue ' reg 2+3
|
||
|
shl frequency2, #20
|
||
|
add tempValue, #2
|
||
|
rdword frequency3, tempValue ' reg 4+5
|
||
|
shl frequency3, #20
|
||
|
add tempValue, #2
|
||
|
rdbyte noisePeriod, tempValue ' reg 6
|
||
|
and noisePeriod, #$1f
|
||
|
add tempValue, #1
|
||
|
rdbyte enableRegister, tempValue ' reg 7
|
||
|
min noisePeriod, #1
|
||
|
add tempValue, #1
|
||
|
rdbyte amplitude1, tempValue ' reg 8
|
||
|
and amplitude1, #31
|
||
|
add tempValue, #1
|
||
|
rdbyte amplitude2, tempValue ' reg 9
|
||
|
and amplitude2, #31
|
||
|
add tempValue, #1
|
||
|
rdbyte amplitude3, tempValue ' reg 10
|
||
|
and amplitude3, #31
|
||
|
add tempValue, #1
|
||
|
rdbyte envelopePeriod, tempValue ' reg 11
|
||
|
add tempValue, #1
|
||
|
shl noisePeriod, #20
|
||
|
rdbyte temp1, tempValue ' reg 12
|
||
|
shl temp1, #8
|
||
|
or envelopePeriod, temp1 wz
|
||
|
if_z mov envelopePeriod, half_period ' 0 == half the period of 1
|
||
|
if_nz shl envelopePeriod, #16
|
||
|
add tempValue, #1
|
||
|
rdbyte envelopeShape, tempValue ' reg 13
|
||
|
movd oscValues, enableRegister
|
||
|
getRegisters_ret ret
|
||
|
|
||
|
'
|
||
|
' Calculate AY samples channel 1-3 and store in out1-out3
|
||
|
'
|
||
|
AY
|
||
|
'───────────────────────────────────────────────────────────
|
||
|
'───────────────────────────────────────────────────────────
|
||
|
' Envelope shaping -> envelopeAmplitude
|
||
|
'───────────────────────────────────────────────────────────
|
||
|
Envelope sub envCounter, envSubValue wc ' Handles envelope incrementing
|
||
|
if_c add envCounter, envelopePeriod
|
||
|
if_c add envelopeValue, envelopeInc
|
||
|
'───────────────────────────────────────────────────────────
|
||
|
test envelopeShape, #16 wz ' Handle envelope reset bit ( Extra bit added by Ahle2 )
|
||
|
if_z neg envelopeValue, #0
|
||
|
if_z mov envelopeInc, #1
|
||
|
if_z mov envCounter, envelopePeriod
|
||
|
if_z or envelopeShape, #16
|
||
|
if_z wrbyte envelopeShape, tempValue '<-IMPORTANT, sets bit 5 in hub ram
|
||
|
'───────────────────────────────────────────────────────────
|
||
|
test envelopeShape, #8 wc ' Handle continue = 0
|
||
|
test envelopeShape, #4 wz
|
||
|
if_nc_and_z mov envelopeShape, #9
|
||
|
if_nc_and_nz mov envelopeShape, #15
|
||
|
'───────────────────────────────────────────────────────────
|
||
|
test envelopeShape, #2 wz ' Sets the envelope hold level
|
||
|
muxz envHoldLevel, #15 '
|
||
|
'───────────────────────────────────────────────────────────
|
||
|
test envelopeValue, #16 wz ' Check if > 15
|
||
|
test envelopeShape, #1 wc ' Check hold bit
|
||
|
if_nz_and_c mov envelopeInc, #0 ' Hold envelope
|
||
|
if_nz_and_c mov envelopeValue, envHoldLevel '
|
||
|
'───────────────────────────────────────────────────────────
|
||
|
if_nz test envelopeShape, #2 wc ' Check and handle alternation
|
||
|
if_nz_and_c neg envelopeInc, envelopeInc
|
||
|
if_nz_and_c add envelopeValue, envelopeInc
|
||
|
'───────────────────────────────────────────────────────────
|
||
|
mov envelopeAmplitude, envelopeValue
|
||
|
test envelopeShape, #4 wc ' Check and handle invertion (attack)
|
||
|
if_nc xor envelopeAmplitude, #15 '(Move Value or ~Value to envelopeAmplitude)
|
||
|
|
||
|
'───────────────────────────────────────────────────────────
|
||
|
' Waveform shaping noise -> bit 3 of oscValues
|
||
|
'───────────────────────────────────────────────────────────
|
||
|
Noise1 sub phaseAccumulatorN, noiseSubValue wc ' Noise generator
|
||
|
if_c add phaseAccumulatorN, noisePeriod
|
||
|
if_c add noiseValue, noiseAdd
|
||
|
if_c ror noiseValue, #15 wc
|
||
|
if_c xor oscValues, #8
|
||
|
|
||
|
'───────────────────────────────────────────────────────────
|
||
|
' Waveform shaping channel 1 -> out1
|
||
|
'───────────────────────────────────────────────────────────
|
||
|
Env1 test amplitude1, #16 wz ' Selects envelope or fixed amplitude
|
||
|
if_nz mov amplitude1, envelopeAmplitude ' depending on bit 5 of amplitude register 1
|
||
|
mov arg1, amplitude1
|
||
|
call #getAmplitude
|
||
|
'───────────────────────────────────────────────────────────
|
||
|
Square1 cmp frequency1, freqRef wc
|
||
|
if_nc sub phaseAccumulator1, oscSubValue wc ' Square wave generator
|
||
|
if_c add phaseAccumulator1, frequency1 ' channel 1
|
||
|
if_c xor oscValues, #1
|
||
|
'───────────────────────────────────────────────────────────
|
||
|
test oscValues, mask513 wz ' Handles mixing of channel 1
|
||
|
negnz out1, r1 ' Tone on/off, Noice on/off
|
||
|
test oscValues, mask4104 wz
|
||
|
if_z mov out1, r1 ' arg2 = (ToneOn | ToneDisable) & (NoiseOn | NoiseDisable)
|
||
|
|
||
|
'───────────────────────────────────────────────────────────
|
||
|
' Waveform shaping channel 2 -> out2
|
||
|
'───────────────────────────────────────────────────────────
|
||
|
Env2 test amplitude2, #16 wz ' Selects envelope or fixed amplitude
|
||
|
if_nz mov amplitude2, envelopeAmplitude ' depending on bit 5 of amplitude register 2
|
||
|
mov arg1, amplitude2
|
||
|
call #getAmplitude
|
||
|
'───────────────────────────────────────────────────────────
|
||
|
Square2 cmp frequency2, freqRef wc
|
||
|
if_nc sub phaseAccumulator2, oscSubValue wc ' Square wave generator
|
||
|
if_c add phaseAccumulator2, frequency2 ' channel 2
|
||
|
if_c xor oscValues, #2
|
||
|
'───────────────────────────────────────────────────────────
|
||
|
test oscValues, mask1026 wz ' Handles mixing of channel 2
|
||
|
negz out2, r1 ' Tone on/off, Noice on/off
|
||
|
test oscValues, mask8200 wz
|
||
|
if_z mov out2, r1 ' arg2 = (ToneOn | ToneDisable) & (NoiseOn | NoiseDisable)
|
||
|
|
||
|
'───────────────────────────────────────────────────────────
|
||
|
' Waveform shaping channel 3 -> out3
|
||
|
'───────────────────────────────────────────────────────────
|
||
|
Env3 test amplitude3, #16 wz ' Selects envelope or fixed amplitude
|
||
|
if_nz mov amplitude3, envelopeAmplitude ' depending on bit 5 of amplitude register 3
|
||
|
mov arg1, amplitude3
|
||
|
call #getAmplitude
|
||
|
'───────────────────────────────────────────────────────────
|
||
|
Square3 cmp frequency3, freqRef wc
|
||
|
if_nc sub phaseAccumulator3, oscSubValue wc ' Square wave generator
|
||
|
if_c add phaseAccumulator3, frequency3 ' channel 3
|
||
|
if_c xor oscValues, #4
|
||
|
'───────────────────────────────────────────────────────────
|
||
|
test oscValues, mask2052 wz ' Handles mixing of channel 2
|
||
|
negz out3, r1 ' Tone on/off, Noice on/off
|
||
|
test oscValues, mask16392 wz
|
||
|
if_z mov out3, r1 ' arg2 = (ToneOn | ToneDisable) & (NoiseOn | NoiseDisable)
|
||
|
AY_ret ret
|
||
|
|
||
|
'
|
||
|
' Mix channels and update FRQA/FRQB PWM-values
|
||
|
'
|
||
|
mixer mov r1, val31bit ' DC offset
|
||
|
add r1, out1
|
||
|
add r1, out2
|
||
|
add r1, out3
|
||
|
waitcnt waitCounter, sampleRate ' Wait until the right time to update
|
||
|
mov FRQA, r1 '| Update PWM values in FRQA/FRQB
|
||
|
mov FRQB, r1 '|
|
||
|
mixer_ret ret
|
||
|
|
||
|
'
|
||
|
' Get amplitude table r1 = amplitudTable[arg1]
|
||
|
'
|
||
|
getAmplitude and arg1, #15
|
||
|
add arg1, #amplitudeTable ' Lookup the amplitude according
|
||
|
movs :indexed1, arg1 ' to the current state of the envelope
|
||
|
nop
|
||
|
:indexed1 mov r1, 0
|
||
|
getAmplitude_ret ret
|
||
|
|
||
|
|
||
|
'
|
||
|
' Variables, tables, masks and reference values
|
||
|
'
|
||
|
amplitudeTable long 1634706
|
||
|
long 2452059
|
||
|
long 3678089
|
||
|
long 5517133
|
||
|
long 8275700
|
||
|
long 12413550
|
||
|
long 18620325
|
||
|
long 27930488
|
||
|
long 41895733
|
||
|
long 62843600
|
||
|
long 94265400
|
||
|
long 141398100
|
||
|
long 212097150
|
||
|
long 318145725
|
||
|
long 477218588
|
||
|
long 715827882
|
||
|
|
||
|
'Masks and reference values
|
||
|
mask513 long 513
|
||
|
mask1026 long 1026
|
||
|
mask2052 long 2052
|
||
|
mask4104 long 4104
|
||
|
mask8200 long 8200
|
||
|
mask16392 long 16392
|
||
|
|
||
|
mask32bit long $ffffffff
|
||
|
mask16bit long $ffff
|
||
|
half_period long $00008000
|
||
|
val31bit long $80000000
|
||
|
noiseAdd long $88008800 'Value to add to the noise generator every noise update
|
||
|
sampleRate long 0
|
||
|
freqRef long 10<<20
|
||
|
|
||
|
'Setup and subroutine parameters
|
||
|
arg1 long 0
|
||
|
arg2 long 0
|
||
|
r1 long 0
|
||
|
AY_Address long 0
|
||
|
|
||
|
'AY variables
|
||
|
envCounter long 1
|
||
|
envSubValue long ENV_CORR
|
||
|
oscSubValue long OSC_CORR
|
||
|
noiseSubValue long NOISE_CORR
|
||
|
envelopeValue long 0
|
||
|
envelopeInc long 1
|
||
|
envHoldLevel res 1
|
||
|
oscValues res 1
|
||
|
amplitude1 res 1
|
||
|
amplitude2 res 1
|
||
|
amplitude3 res 1
|
||
|
envelopeAmplitude res 1
|
||
|
enableRegister res 1
|
||
|
envelopeShape res 1
|
||
|
frequency1 res 1
|
||
|
frequency2 res 1
|
||
|
frequency3 res 1
|
||
|
envelopePeriod res 1
|
||
|
noisePeriod res 1
|
||
|
phaseAccumulatorN res 1
|
||
|
phaseAccumulator1 res 1
|
||
|
phaseAccumulator2 res 1
|
||
|
phaseAccumulator3 res 1
|
||
|
noiseValue res 1
|
||
|
noiseOut res 1
|
||
|
out1 res 1
|
||
|
out2 res 1
|
||
|
out3 res 1
|
||
|
waitCounter res 1
|
||
|
tempValue res 1
|
||
|
temp1 res 1
|
||
|
fit
|