2022-07-02 15:08:19 +00:00
|
|
|
%import syslib
|
|
|
|
|
|
|
|
psg {
|
|
|
|
; $1F9C0 - $1F9FF 16 blocks of 4 PSG registers (16 voices)
|
|
|
|
; 00 frequency word LSB
|
|
|
|
; 01 frequency word MSB. freqword = HERZ / 0.3725290298461914
|
|
|
|
; 02 bit 7 =right, bit 6 = left, bits 5-0 = volume 0-63 levels
|
|
|
|
; 03 bit 7,6 = waveform, bits 5-0 = Pulse width 0-63
|
|
|
|
; waveform: 0=pulse, 1=sawtooth, 2=triangle, 3=noise
|
|
|
|
const ubyte PULSE = %00000000
|
|
|
|
const ubyte SAWTOOTH = %01000000
|
|
|
|
const ubyte TRIANGLE = %10000000
|
|
|
|
const ubyte NOISE = %11000000
|
|
|
|
const ubyte LEFT = %01000000
|
|
|
|
const ubyte RIGHT = %10000000
|
|
|
|
|
|
|
|
sub voice(ubyte voice_num, ubyte channel, ubyte volume, ubyte waveform, ubyte pulsewidth) {
|
2022-08-19 20:17:23 +00:00
|
|
|
; -- Enables a 'voice' on the PSG.
|
|
|
|
; voice_num = 0-15, the voice number.
|
|
|
|
; channel = either LEFT or RIGHT or (LEFT|RIGHT). Specifies the stereo channel(s) to use.
|
|
|
|
; volume = 0-63, the starting volume for the voice
|
|
|
|
; waveform = one of PULSE,SAWTOOTH,TRIANGLE,NOISE.
|
|
|
|
; pulsewidth = 0-63. Specifies the pulse width for waveform=PULSE.
|
2022-07-02 23:40:29 +00:00
|
|
|
envelope_states[voice_num] = 255
|
2022-07-03 09:55:13 +00:00
|
|
|
cx16.r0 = $f9c2 + voice_num * 4
|
|
|
|
cx16.VERA_CTRL = 0
|
|
|
|
cx16.VERA_ADDR_L = lsb(cx16.r0)
|
|
|
|
cx16.VERA_ADDR_M = msb(cx16.r0)
|
|
|
|
cx16.VERA_ADDR_H = 1
|
|
|
|
cx16.VERA_DATA0 = channel | volume
|
|
|
|
cx16.VERA_ADDR_L++
|
|
|
|
cx16.VERA_DATA0 = waveform | pulsewidth
|
2022-07-02 23:40:29 +00:00
|
|
|
envelope_volumes[voice_num] = mkword(volume, 0)
|
2022-07-03 08:58:57 +00:00
|
|
|
envelope_maxvolumes[voice_num] = volume
|
2022-07-02 15:08:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
; sub freq_hz(ubyte voice_num, float hertz) {
|
|
|
|
; ; this would rely on floating point math to convert hertz to vera frequency
|
2022-08-19 20:17:23 +00:00
|
|
|
; ; could be replaced by integer math maybe with a lookup table?
|
2022-07-02 15:08:19 +00:00
|
|
|
; uword vera_freq = (hertz / 0.3725290298461914) as uword
|
2022-07-02 17:28:24 +00:00
|
|
|
; freq(voice_num, vera_freq)
|
2022-07-02 15:08:19 +00:00
|
|
|
; }
|
|
|
|
|
2022-07-02 17:28:24 +00:00
|
|
|
sub freq(ubyte voice_num, uword vera_freq) {
|
2022-08-19 20:17:23 +00:00
|
|
|
; -- Changes the frequency of the voice's sound.
|
|
|
|
; voice_num = 0-15, vera_freq = 0-65535 calculate this via the formula given in the Vera's PSG documentation.
|
|
|
|
; (https://github.com/commanderx16/x16-docs/blob/master/VERA%20Programmer's%20Reference.md)
|
2022-07-03 09:55:13 +00:00
|
|
|
cx16.r0 = $f9c0 + voice_num * 4
|
|
|
|
cx16.VERA_CTRL = 0
|
|
|
|
cx16.VERA_ADDR_L = lsb(cx16.r0)
|
|
|
|
cx16.VERA_ADDR_M = msb(cx16.r0)
|
|
|
|
cx16.VERA_ADDR_H = 1
|
|
|
|
cx16.VERA_DATA0 = lsb(vera_freq)
|
|
|
|
cx16.VERA_ADDR_L++
|
|
|
|
cx16.VERA_DATA0 = msb(vera_freq)
|
2022-07-02 15:08:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
sub volume(ubyte voice_num, ubyte vol) {
|
2022-08-19 20:17:23 +00:00
|
|
|
; -- Modifies the volume of this voice.
|
|
|
|
; voice_num = 0-15, vol = 0-63 where 0=silent, 63=loudest.
|
2022-07-03 09:55:13 +00:00
|
|
|
cx16.r0 = $f9c2 + voice_num * 4
|
|
|
|
cx16.vpoke(1, cx16.r0, cx16.vpeek(1, cx16.r0) & %11000000 | vol)
|
2022-07-02 23:40:29 +00:00
|
|
|
envelope_volumes[voice_num] = mkword(vol, 0)
|
2022-07-03 08:58:57 +00:00
|
|
|
envelope_maxvolumes[voice_num] = vol
|
2022-07-02 15:08:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
sub pulse_width(ubyte voice_num, ubyte pw) {
|
2022-08-19 20:17:23 +00:00
|
|
|
; -- Modifies the pulse width of this voice (when waveform=PULSE)
|
|
|
|
; voice_num = 0-15, pw = 0-63 where 0=narrow, 63=50%cycle so square wave.
|
2022-07-03 09:55:13 +00:00
|
|
|
cx16.r0 = $f9c3 + voice_num * 4
|
|
|
|
cx16.vpoke(1, cx16.r0, cx16.vpeek(1, cx16.r0) & %11000000 | pw)
|
2022-07-02 15:08:19 +00:00
|
|
|
}
|
|
|
|
|
2022-07-03 08:58:57 +00:00
|
|
|
sub envelope(ubyte voice_num, ubyte maxvolume, ubyte attack, ubyte sustain, ubyte release) {
|
2022-08-19 20:17:23 +00:00
|
|
|
; -- Enables AttackSustainRelease volume envelope for a voice.
|
|
|
|
; Note: this requires setting up envelopes_irq() as well, read its description.
|
|
|
|
; voice_num = 0-15 maxvolume = 0-63
|
|
|
|
; attack, sustain, release = 0-255 that determine the speed of the A/D/R.
|
|
|
|
; TODO describe how the speeds are calculated. For now, experiment. Higher values means *slower* enveloping.
|
2022-07-02 21:10:15 +00:00
|
|
|
envelope_states[voice_num] = 255
|
2022-07-03 08:58:57 +00:00
|
|
|
envelope_attacks[voice_num] = attack
|
2022-07-02 22:55:25 +00:00
|
|
|
envelope_sustains[voice_num] = sustain
|
2022-07-03 08:58:57 +00:00
|
|
|
envelope_releases[voice_num] = release
|
2022-07-02 15:08:19 +00:00
|
|
|
if attack
|
|
|
|
attack = 0
|
|
|
|
else
|
2022-07-03 08:58:57 +00:00
|
|
|
attack = maxvolume ; max volume when no attack is set
|
2022-07-02 15:08:19 +00:00
|
|
|
envelope_volumes[voice_num] = mkword(attack, 0)
|
2022-07-03 08:58:57 +00:00
|
|
|
envelope_maxvolumes[voice_num] = maxvolume
|
2022-07-02 15:08:19 +00:00
|
|
|
envelope_states[voice_num] = 0
|
|
|
|
}
|
|
|
|
|
|
|
|
sub silent() {
|
2022-08-19 20:17:23 +00:00
|
|
|
; -- Shut down all PSG voices.
|
2022-07-03 08:58:57 +00:00
|
|
|
for cx16.r1L in 0 to 15 {
|
|
|
|
envelope_states[cx16.r1L] = 255
|
|
|
|
envelope_volumes[cx16.r1L] = 0
|
|
|
|
volume(cx16.r1L, 0)
|
2022-07-02 15:08:19 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
sub envelopes_irq() {
|
2022-07-03 08:58:57 +00:00
|
|
|
; If you want to use real-time volume envelopes (Attack-Sustain-Release),
|
|
|
|
; you have to call this routine every 1/60th second, for example from your vsync irq handler,
|
|
|
|
; or just install this routine as the only irq handler if you don't have to do other things there.
|
|
|
|
; Example: cx16.set_irq(&psg.envelopes_irq, true)
|
|
|
|
|
2022-07-02 17:28:24 +00:00
|
|
|
; cx16.r0 = the volume word (volume scaled by 256)
|
2022-07-03 08:58:57 +00:00
|
|
|
; cx16.r1L = the voice number
|
|
|
|
; cx16.r2L = attack value
|
2022-07-02 17:28:24 +00:00
|
|
|
|
2022-07-13 15:20:33 +00:00
|
|
|
pushw(cx16.r0)
|
|
|
|
push(cx16.r1L)
|
|
|
|
push(cx16.r2L)
|
|
|
|
pushw(cx16.r9)
|
2022-07-02 21:10:15 +00:00
|
|
|
; calculate new volumes
|
2022-07-03 08:58:57 +00:00
|
|
|
for cx16.r1L in 0 to 15 {
|
|
|
|
when envelope_states[cx16.r1L] {
|
2022-07-02 18:07:05 +00:00
|
|
|
0 -> {
|
|
|
|
; attack
|
2022-07-03 08:58:57 +00:00
|
|
|
cx16.r2L = envelope_maxvolumes[cx16.r1L]
|
|
|
|
cx16.r0 = envelope_volumes[cx16.r1L] + envelope_attacks[cx16.r1L] * $0040
|
|
|
|
if msb(cx16.r0) > cx16.r2L or envelope_attacks[cx16.r1L]==0 {
|
|
|
|
cx16.r0 = mkword(cx16.r2L, 0)
|
|
|
|
envelope_attacks[cx16.r1L] = 0
|
|
|
|
envelope_states[cx16.r1L] = 1 ; start sustain
|
2022-07-02 18:07:05 +00:00
|
|
|
}
|
2022-07-03 08:58:57 +00:00
|
|
|
envelope_volumes[cx16.r1L] = cx16.r0
|
2022-07-02 15:08:19 +00:00
|
|
|
}
|
2022-07-02 18:07:05 +00:00
|
|
|
1 -> {
|
2022-07-02 22:55:25 +00:00
|
|
|
; sustain
|
2022-07-03 08:58:57 +00:00
|
|
|
if envelope_sustains[cx16.r1L] {
|
|
|
|
envelope_sustains[cx16.r1L]--
|
2022-07-02 22:55:25 +00:00
|
|
|
} else {
|
2022-07-03 08:58:57 +00:00
|
|
|
envelope_states[cx16.r1L] = 2 ; start release
|
2022-07-02 22:55:25 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
2 -> {
|
2022-07-02 18:07:05 +00:00
|
|
|
; release
|
2022-07-03 08:58:57 +00:00
|
|
|
cx16.r0 = envelope_volumes[cx16.r1L] - envelope_releases[cx16.r1L] * $0040
|
2022-07-02 18:07:05 +00:00
|
|
|
if msb(cx16.r0) & %11000000 {
|
|
|
|
cx16.r0 = 0
|
2022-07-03 08:58:57 +00:00
|
|
|
envelope_releases[cx16.r1L] = 0
|
2022-07-02 18:07:05 +00:00
|
|
|
}
|
2022-07-03 08:58:57 +00:00
|
|
|
envelope_volumes[cx16.r1L] = cx16.r0
|
2022-07-02 15:08:19 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-07-02 21:10:15 +00:00
|
|
|
|
2022-07-02 23:40:29 +00:00
|
|
|
; set new volumes of all 16 voices, using vera stride of 4
|
2022-07-02 21:10:15 +00:00
|
|
|
cx16.push_vera_context()
|
|
|
|
cx16.VERA_CTRL = 0
|
|
|
|
cx16.VERA_ADDR_L = $c2
|
|
|
|
cx16.VERA_ADDR_M = $f9
|
|
|
|
cx16.VERA_ADDR_H = 1 | %00110000
|
|
|
|
cx16.VERA_CTRL = 1
|
|
|
|
cx16.VERA_ADDR_L = $c2
|
|
|
|
cx16.VERA_ADDR_M = $f9
|
|
|
|
cx16.VERA_ADDR_H = 1 | %00110000
|
2022-07-03 08:58:57 +00:00
|
|
|
for cx16.r1L in 0 to 15 {
|
|
|
|
cx16.VERA_DATA0 = cx16.VERA_DATA1 & %11000000 | msb(envelope_volumes[cx16.r1L])
|
2022-07-02 21:10:15 +00:00
|
|
|
}
|
|
|
|
cx16.pop_vera_context()
|
2022-07-13 15:20:33 +00:00
|
|
|
popw(cx16.r9)
|
|
|
|
pop(cx16.r2L)
|
|
|
|
pop(cx16.r1L)
|
|
|
|
popw(cx16.r0)
|
2022-07-02 15:08:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
ubyte[16] envelope_states
|
2022-07-03 08:58:57 +00:00
|
|
|
uword[16] envelope_volumes ; scaled by 256
|
|
|
|
ubyte[16] envelope_attacks
|
2022-07-02 22:55:25 +00:00
|
|
|
ubyte[16] envelope_sustains
|
2022-07-03 08:58:57 +00:00
|
|
|
ubyte[16] envelope_releases
|
|
|
|
ubyte[16] envelope_maxvolumes
|
2022-07-02 15:08:19 +00:00
|
|
|
}
|