mirror of
https://github.com/irmen/prog8.git
synced 2024-11-22 15:33:02 +00:00
psg abstraction and attack/release envelope
This commit is contained in:
parent
25aad8d7be
commit
223b725a10
103
compiler/res/prog8lib/cx16/psg.p8
Normal file
103
compiler/res/prog8lib/cx16/psg.p8
Normal file
@ -0,0 +1,103 @@
|
||||
%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) {
|
||||
cx16.vpoke(1, $f9c2 + voice_num * 4, channel | volume)
|
||||
cx16.vpoke(1, $f9c3 + voice_num * 4, waveform | pulsewidth)
|
||||
}
|
||||
|
||||
; sub freq_hz(ubyte voice_num, float hertz) {
|
||||
; ; this would rely on floating point math to convert hertz to vera frequency
|
||||
; ; TODO should be replaced by integer math maybe with a lookup table?
|
||||
; uword vera_freq = (hertz / 0.3725290298461914) as uword
|
||||
; freq_vera(voice_num, vera_freq)
|
||||
; }
|
||||
|
||||
sub freq_vera(ubyte voice_num, uword vera_freq) {
|
||||
cx16.vpoke(1, $f9c1 + voice_num*4, msb(vera_freq))
|
||||
cx16.vpoke(1, $f9c0 + voice_num*4, lsb(vera_freq))
|
||||
}
|
||||
|
||||
sub volume(ubyte voice_num, ubyte vol) {
|
||||
uword reg = $f9c2 + voice_num * 4
|
||||
cx16.vpoke(1, reg, cx16.vpeek(1, reg) & %11000000 | vol)
|
||||
}
|
||||
|
||||
sub pulse_width(ubyte voice_num, ubyte pw) {
|
||||
uword reg = $f9c3 + voice_num * 4
|
||||
cx16.vpoke(1, reg, cx16.vpeek(1, reg) & %11000000 | pw)
|
||||
}
|
||||
|
||||
sub envelope(ubyte voice_num, ubyte attack, ubyte release) {
|
||||
envelope_attacks[voice_num] = attack * $0040
|
||||
envelope_releases[voice_num] = release * $0040
|
||||
if attack
|
||||
attack = 0
|
||||
else
|
||||
attack = 63 ; max volume when no attack is set
|
||||
envelope_volumes[voice_num] = mkword(attack, 0)
|
||||
envelope_states[voice_num] = 0
|
||||
}
|
||||
|
||||
sub silent() {
|
||||
ubyte voice
|
||||
for voice in 0 to 15 {
|
||||
volume(voice, 0)
|
||||
envelope_volumes[voice] = 0
|
||||
envelope_states[voice] = 1
|
||||
}
|
||||
}
|
||||
|
||||
sub envelopes_irq() {
|
||||
ubyte vera_ctrl = cx16.VERA_CTRL
|
||||
ubyte vera_addr_h = cx16.VERA_ADDR_H
|
||||
ubyte vera_addr_m = cx16.VERA_ADDR_M
|
||||
ubyte vera_addr_l = cx16.VERA_ADDR_L
|
||||
uword vol_word
|
||||
ubyte voice
|
||||
for voice in 0 to 15 {
|
||||
if envelope_states[voice] {
|
||||
; release
|
||||
vol_word = envelope_volumes[voice] - envelope_releases[voice]
|
||||
if msb(vol_word) & %11000000 {
|
||||
vol_word = 0
|
||||
envelope_releases[voice] = 0
|
||||
}
|
||||
envelope_volumes[voice] = vol_word
|
||||
volume(voice, msb(vol_word))
|
||||
} else {
|
||||
; attack
|
||||
vol_word = envelope_volumes[voice] + envelope_attacks[voice]
|
||||
if msb(vol_word) & %11000000 or envelope_attacks[voice]==0 {
|
||||
vol_word = mkword(63, 0)
|
||||
envelope_attacks[voice] = 0
|
||||
envelope_states[voice] = 1 ; start release
|
||||
}
|
||||
envelope_volumes[voice] = vol_word
|
||||
volume(voice, msb(vol_word))
|
||||
}
|
||||
}
|
||||
cx16.VERA_CTRL = vera_ctrl
|
||||
cx16.VERA_ADDR_L = vera_addr_l
|
||||
cx16.VERA_ADDR_M = vera_addr_m
|
||||
cx16.VERA_ADDR_H = vera_addr_h
|
||||
}
|
||||
|
||||
ubyte[16] envelope_states
|
||||
uword[16] envelope_volumes
|
||||
uword[16] envelope_attacks
|
||||
uword[16] envelope_releases
|
||||
}
|
@ -364,3 +364,9 @@ Also contains a helper function to calculate the file size of a loaded file (alt
|
||||
to 16 bits, 64Kb)
|
||||
|
||||
|
||||
psg (cx16 only)
|
||||
----------------
|
||||
Available for the Cx16 target.
|
||||
Contains a simple abstraction for the Vera's PSG (programmable sound generator) to play simple waveforms.
|
||||
It includes an interrupt routine to handle simple Attack/Release envelopes as well.
|
||||
See the examples/cx16/bdmusic.p8 program for ideas how to use it.
|
||||
|
@ -1,53 +1,48 @@
|
||||
%import textio
|
||||
%import syslib
|
||||
%import floats
|
||||
%import psg
|
||||
|
||||
main {
|
||||
|
||||
sub start() {
|
||||
sub start() {
|
||||
|
||||
txt.print("will play the music from boulderdash,\nmade in 1984 by peter liepa.\npress enter to start: ")
|
||||
void c64.CHRIN()
|
||||
txt.clear_screen()
|
||||
txt.print("will play the music from boulderdash,\nmade in 1984 by peter liepa.\npress enter to start: ")
|
||||
void c64.CHRIN()
|
||||
txt.clear_screen()
|
||||
|
||||
repeat {
|
||||
uword note
|
||||
for note in notes {
|
||||
ubyte note1 = lsb(note)
|
||||
ubyte note2 = msb(note)
|
||||
uword freqR = freq(note1)
|
||||
uword freqL = freq(note2)
|
||||
cx16.vpoke(1, $F9C0, lsb(freqR))
|
||||
cx16.vpoke(1, $F9C1, msb(freqR))
|
||||
cx16.vpoke(1, $F9C2, %10111111) ; left, max volume
|
||||
cx16.vpoke(1, $F9C3, %10000000) ; triangle
|
||||
cx16.vpoke(1, $F9C4, lsb(freqL))
|
||||
cx16.vpoke(1, $F9C5, msb(freqL))
|
||||
cx16.vpoke(1, $F9C6, %01111111) ; right, max volume
|
||||
cx16.vpoke(1, $F9C7, %10000000) ; triangle
|
||||
psg.voice(0, psg.LEFT, 63, psg.TRIANGLE, 0)
|
||||
psg.voice(1, psg.RIGHT, 63, psg.TRIANGLE, 0)
|
||||
cx16.set_irq(&psg.envelopes_irq, false)
|
||||
|
||||
; TODO ADSR of some kind?
|
||||
|
||||
print_notes(note1, note2)
|
||||
sys.wait(10)
|
||||
repeat {
|
||||
uword note
|
||||
for note in notes {
|
||||
ubyte note0 = lsb(note)
|
||||
ubyte note1 = msb(note)
|
||||
psg.freq_vera(0, vera_freq(note0))
|
||||
psg.freq_vera(1, vera_freq(note1))
|
||||
psg.envelope(0, 255, 6)
|
||||
psg.envelope(1, 255, 6)
|
||||
print_notes(note0, note1)
|
||||
sys.wait(10)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub freq(ubyte note) -> uword {
|
||||
float fword = freqs_hz[note-10] / (48828.125 / 131072.0) ; formula from the Vera PSG docs
|
||||
return fword as uword
|
||||
}
|
||||
sub vera_freq(ubyte note) -> uword {
|
||||
return (freqs_hz[note-10] / 0.3725290298461914) as uword
|
||||
}
|
||||
|
||||
sub print_notes(ubyte n1, ubyte n2) {
|
||||
txt.nl()
|
||||
txt.plot(n1, txt.DEFAULT_HEIGHT-1)
|
||||
txt.color(7)
|
||||
txt.chrout('Q')
|
||||
txt.plot(n2, txt.DEFAULT_HEIGHT-1)
|
||||
txt.color(4)
|
||||
txt.chrout('Q')
|
||||
}
|
||||
sub print_notes(ubyte n1, ubyte n2) {
|
||||
txt.nl()
|
||||
txt.plot(n1, txt.DEFAULT_HEIGHT-1)
|
||||
txt.color(7)
|
||||
txt.chrout('Q')
|
||||
txt.plot(n2, txt.DEFAULT_HEIGHT-1)
|
||||
txt.color(4)
|
||||
txt.chrout('Q')
|
||||
}
|
||||
|
||||
|
||||
; details about the boulderdash music can be found here:
|
||||
|
Loading…
Reference in New Issue
Block a user