8bitworkshop/test/ecs/music.ecs

190 lines
4.4 KiB
Plaintext

//#resource "vcs-ca65.h"
import "vcslib.ecs"
component Song
songdata: array of 0..255
end
// TODO: merge with SoundChannel
component MusicChannel
duration: 0..255
note: 0..255
duty: 0..255
end
component MusicPlayer
timer: 0..255 default 255
channel: [MusicChannel]
songptr: 0..65535
volume: 0..15 default 15
tempo: 0..255 default 7
end
// Table of AUDF base values for each note
resource FREQZ ---
.byte 30, 30, 30, 30, 30, 28, 26, 25, 23, 22, 21, 19, 18, 17, 16, 15, 14, 13, 12, 12, 11, 10, 10, 9, 8, 8, 7, 7, 6, 6, 5, 5, 30, 29, 27, 25, 24, 22, 21, 20, 19, 18, 16, 15, 15, 14, 13, 12, 11, 11, 10, 31, 29, 27, 25, 24, 23, 21, 20, 19, 18, 16, 15, 15
---
// Table of duty-cycle bits for each note
resource DUTYZ ---
.byte 247, 247, 247, 247, 1, 73, 219, 1, 219, 73, 0, 219, 181, 85, 85, 85, 181, 219, 247, 1, 73, 181, 0, 73, 219, 17, 219, 17, 219, 73, 247, 85, 247, 1, 85, 247, 73, 247, 181, 17, 1, 0, 247, 247, 0, 1, 17, 73, 181, 0, 17, 0, 1, 85, 247, 73, 0, 181, 73, 1, 0, 247, 247, 0
---
// Table of AUDC values for each note
resource TONEZ ---
.byte 0, 0, 0, 0, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4
---
system MusicPlayer
on preframe do once
---
{{!musicpulse}} ; update song
{{!musicframe}} ; update registers
---
on prekernel do once
---
{{!musicframe}} ; update registers
---
on postkernel do once
---
{{!musicframe}} ; update registers
---
on postframe do once
---
{{!musicframe}} ; update registers
---
// TODO: unroll?
on musicframe do foreach [MusicChannel]
---
; Update channel pitch in AUDF0
; 8-bit rotation of duty cycle bits
lda {{get duration}}
beq :++
lda {{set duty}}
asl
bcc :+
ora #1
: sta {{set duty}}
lda {{get note}}
beq :+
; If next bit is set, add 1 to AUDF0
adc #0
sta AUDF0,x
:
---
on musicpulse do foreach [MusicChannel]
---
; Decrement the volumes for each channel
; Also decrement next-note timer, fetch next note
lda {{get duration}}
beq :+
lsr
sta AUDV0,x
dec {{set duration}}
:
---
on musicpulse do with [MusicPlayer]
---
lda {{get timer}}
bmi @Done
beq @NextData
dec {{set timer}}
jmp @Done
; Timer ran out, so fetch next note
@NextData:
ldx #0
lda ({{get songptr}},x)
bmi @LoadDuration
; < $80, play next note
ldx {{get channel}} ; next channel
tay
{{!musicnote}}
inx
txa
and #1
sta {{set channel}} ; inc next channel
jmp @IncDataPtr
; >= $80, load next duration
@LoadDuration:
cmp #$ff ; $ff = end of song
bne @NoResetTrack
sta {{set timer}}
{{!musicdone}}
jmp @Done
@NoResetTrack:
and #$7f
; asl
sta {{set timer}} ; store duration * 2
@IncDataPtr:
; increment song pointer
inc {{set songptr 0}}
bne @Done
inc {{set songptr 8}}
@Done:
---
// TODO: should use "with"?
on musicnote do select all [MusicChannel]
---
; Play a note
; X = channel (0,1)
; Y = note index (0-63)
lda {{^FREQZ}},y
sta {{base note}},x
lda {{^DUTYZ}},y
sta {{base duty}},x
lda {{^TONEZ}},y
sta AUDC0,x
; TODO: consts?
lda {{get MusicPlayer:tempo}}
sta {{base duration}},x
lda {{get MusicPlayer:volume}}
sta AUDV0,x
---
on playmusic do foreach [MusicPlayer] limit 1
---
lda #<{{arg 0}}
sta {{set songptr 0}}
lda #>{{arg 0}}
sta {{set songptr 8}}
lda #0
sta {{set timer}}
---
on stopmusic do foreach [MusicPlayer] limit 1
---
lda #$ff
sta {{set timer}}
---
end
///
demo music
using FrameLoop
using MusicPlayer
entity [MusicChannel] end
entity [MusicChannel] end
entity MusicPlayer [MusicPlayer]
const volume = 10
const tempo = 31
end
system music
on musicdone do with [MusicPlayer]
---
; TODO: nested exprs
; {{!playmusic ^SampleMusic}}
;{{^SampleMusic}}
{{!playmusic SampleMusic}}
---
on preframeloop do once
---
{{!musicdone}}
---
end
end demo
resource SampleMusic ---
.byte $35,$41,$8a,$37,$43,$8a,$33,$3f,$8a,$30,$3c,$94,$3e,$32,$8a,$3a,$2e,$94,$35,$29,$8a,$37,$2b,$8a,$33,$27,$8a,$30,$24,$94,$32,$26,$8a,$2e,$22,$94,$29,$1d,$8a,$2b,$1f,$8a,$27,$1b,$8a,$24,$18,$94,$1a,$26,$8a,$18,$24,$8a,$17,$23,$8a,$16,$22,$a8,$3a,$35,$ff
---