2022-07-02 15:08:19 +00:00
|
|
|
%import syslib
|
|
|
|
|
|
|
|
psg {
|
2023-12-29 21:21:44 +00:00
|
|
|
%option ignore_unused
|
2023-06-29 22:29:50 +00:00
|
|
|
|
2022-07-02 15:08:19 +00:00
|
|
|
; $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
|
|
|
|
|
2023-08-28 15:35:53 +00:00
|
|
|
sub voice(ubyte voice_num, ubyte channel, ubyte vol, 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.
|
2023-08-28 15:35:53 +00:00
|
|
|
; vol = 0-63, the starting volume for the voice
|
2022-08-19 20:17:23 +00:00
|
|
|
; 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
|
2023-05-22 19:13:20 +00:00
|
|
|
sys.irqsafe_set_irqd()
|
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
|
2023-08-28 15:35:53 +00:00
|
|
|
cx16.VERA_DATA0 = channel | vol
|
2022-07-03 09:55:13 +00:00
|
|
|
cx16.VERA_ADDR_L++
|
|
|
|
cx16.VERA_DATA0 = waveform | pulsewidth
|
2023-08-28 15:35:53 +00:00
|
|
|
envelope_volumes[voice_num] = mkword(vol, 0)
|
|
|
|
envelope_maxvolumes[voice_num] = vol
|
2023-05-22 19:13:20 +00:00
|
|
|
sys.irqsafe_clear_irqd()
|
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.
|
2024-04-08 19:30:05 +00:00
|
|
|
; (https://github.com/X16Community/x16-docs/blob/101759f3bfa5e6cce4e8c5a0b67cb0f2f1c6341e/X16%20Reference%20-%2009%20-%20VERA%20Programmer's%20Reference.md)
|
2023-04-26 15:22:27 +00:00
|
|
|
; Write freq MSB first and then LSB to reduce the chance on clicks
|
2023-05-22 19:13:20 +00:00
|
|
|
sys.irqsafe_set_irqd()
|
2023-04-26 15:22:27 +00:00
|
|
|
cx16.r0 = $f9c1 + voice_num * 4
|
2022-07-03 09:55:13 +00:00
|
|
|
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 = msb(vera_freq)
|
2023-04-26 15:22:27 +00:00
|
|
|
cx16.VERA_ADDR_L--
|
|
|
|
cx16.VERA_DATA0 = lsb(vera_freq)
|
2023-05-22 19:13:20 +00:00
|
|
|
sys.irqsafe_clear_irqd()
|
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-02 23:40:29 +00:00
|
|
|
envelope_volumes[voice_num] = mkword(vol, 0)
|
2023-04-26 15:22:27 +00:00
|
|
|
cx16.vpoke_mask(1, $f9c2 + voice_num * 4, %11000000, vol)
|
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.
|
2023-04-26 15:22:27 +00:00
|
|
|
cx16.vpoke_mask(1, $f9c3 + voice_num * 4, %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
|
2023-04-23 23:23:03 +00:00
|
|
|
; attack, sustain, release = 0-255 that determine the speed of the A/D/R:
|
|
|
|
; attack time: MAXVOL/15/attack seconds. higher value = faster attack.
|
|
|
|
; sustain time: sustain/60 seconds higher sustain value = longer sustain (!).
|
|
|
|
; release time: MAXVOL/15/release seconds. higher vaule = faster release.
|
|
|
|
|
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
|
2023-05-28 20:30:34 +00:00
|
|
|
cx16.r0 = mkword(maxvolume, 0)
|
|
|
|
if cx16.r0<envelope_volumes[voice_num]
|
|
|
|
envelope_volumes[voice_num] = cx16.r0
|
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
|
|
|
|
volume(cx16.r1L, 0)
|
2022-07-02 15:08:19 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-11-21 21:33:37 +00:00
|
|
|
sub envelopes_irq() -> bool {
|
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.
|
2023-11-22 22:23:10 +00:00
|
|
|
; Example: cx16.set_vsync_irq_handler(&psg.envelopes_irq)
|
2023-05-03 22:16:24 +00:00
|
|
|
; NOTE: this routine calls save/restore_vera_context() for you, don't nest this or call it yourself!
|
2022-07-03 08:58:57 +00:00
|
|
|
|
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
|
2023-12-26 13:47:31 +00:00
|
|
|
sys.pushw(cx16.r0)
|
|
|
|
sys.push(cx16.r1L)
|
|
|
|
sys.push(cx16.r2L)
|
|
|
|
sys.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
|
2024-02-04 22:22:43 +00:00
|
|
|
if envelope_sustains[cx16.r1L]!=0 {
|
2022-07-03 08:58:57 +00:00
|
|
|
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
|
2024-02-04 22:22:43 +00:00
|
|
|
if msb(cx16.r0) & %11000000 !=0 {
|
2022-07-02 18:07:05 +00:00
|
|
|
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
|
2023-04-17 21:37:15 +00:00
|
|
|
cx16.save_vera_context()
|
2022-07-02 21:10:15 +00:00
|
|
|
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
|
|
|
}
|
2023-04-17 21:37:15 +00:00
|
|
|
cx16.restore_vera_context()
|
2023-12-26 13:47:31 +00:00
|
|
|
cx16.r9 = sys.popw()
|
|
|
|
cx16.r2L = sys.pop()
|
|
|
|
cx16.r1L = sys.pop()
|
|
|
|
cx16.r0 = sys.popw()
|
2023-11-21 21:33:37 +00:00
|
|
|
return true ; run the system IRQ handler afterwards
|
2022-07-02 15:08:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
ubyte[16] envelope_states
|
2023-05-28 20:49:33 +00:00
|
|
|
uword[16] @split envelope_volumes ; scaled by 256
|
2022-07-03 08:58:57 +00:00
|
|
|
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
|
|
|
}
|