-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathvgmplayer.asm
410 lines (316 loc) · 7.3 KB
/
vgmplayer.asm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
\ ******************************************************************
\\ VGM Player
\\ Code module
\ ******************************************************************
_ENABLE_AUDIO = TRUE ; enables output to sound chip (disable for silent testing/demo loop)
_ENABLE_VOLUME = TRUE
.vgm_player_start
\ ******************************************************************
\ * Optimized VGM music player routines
\ * Plays a RAW format VGM audio stream from an Exomiser compressed or uncompressed data stream
\ ******************************************************************
\ * EXO VGM data file
\ * This must be compressed using the following flags:
\ * exomizer.exe raw -c -m 256 <file.raw> -o <file.exo>
;----------------------------------------------------------------------------------
; Music playback routines
;----------------------------------------------------------------------------------
\\ Initialise the VGM player with an Exomizer compressed data stream
\\ X - lo byte of data stream to be played
\\ Y - hi byte of data stream to be played
.vgm_init_stream
{
\\ Initialise music player
stz vgm_player_ended
\\ Initialise exomizer - must have some data ready to decrunch
jmp exo_init_decruncher
}
.vgm_deinit_player
{
\\ Deinitialise music player
LDA #&FF
STA vgm_player_ended
\\ FALLS THROUGH TO vgm_silence_psg
}
.vgm_silence_psg
{
\\ Zero volume on all channels
LDA #&9F: JSR psg_strobe
LDA #&BF: JSR psg_strobe
LDA #&DF: JSR psg_strobe
LDA #&FF: JSR psg_strobe
.return
RTS
}
\\ "RAW" VGM data is just packets of chip register data, terminated with 255
\\ <packets section>
\\ [byte] - indicating number of data writes within the current packet (max 11)
\\ [dd] ... - data
\\ [byte] - number of data writes within the next packet
\\ [dd] ... - data
\\ ...`
\\ <eof section>
\\ [0xff] - eof
; VGM "Music" player - fetches data from a compressed EXO stream
; We use EXO since music tracks benefit around 50% less RAM by using compression
.vgm_poll_player
{
ldx musicon
jsr vgm_set_volume_mask
\\ Assume this is called every 20ms..
LDA vgm_player_ended
BNE _sample_end
\\ Get next byte from the stream
jsr exo_get_decrunched_byte
bcs _sample_end
cmp #&ff
beq _player_end
\\ Byte is #data bytes to send to sound chip:
TAY
.sound_data_loop
BEQ wait_20_ms
phy
jsr exo_get_decrunched_byte
bcc not_sample_end
ply
JMP _sample_end
.not_sample_end
JSR psg_strobe
ply
dey
; JMP sound_data_loop
bpl sound_data_loop ; equivalent to JMP as will always be positive
.wait_20_ms
CLC
RTS
._player_end
; Happens in deinit fn
;STA vgm_player_ended
\\ Silence sound chip
JSR vgm_deinit_player
._sample_end
SEC
RTS
}
;----------------------------------------------------------------------------------
; Sound effect playback routines
;----------------------------------------------------------------------------------
; Stop any currently playing SFX
.vgm_sfx_stop
{
ldy #0
; FALLS THROUGH to vgm_sfx_play with Y=0 means address is invalid.
}
; Play a sound effect from memory
; SFX are stored as uncompressed RAW VGM
; Currently only one SFX can be played at once
; X/Y contain address of SFX to be played
.vgm_sfx_play
{
stx vgm_sfx_addr+0
sty vgm_sfx_addr+1
rts
}
; fetch a byte from the currently selected SFX RAW data buffer
; returns byte in A, Y is trashed, X is preserved
; data buffer address is auto-incremented.
.vgm_sfx_get_byte
{
ldy #0
lda (vgm_sfx_addr),y
tay
; advance ptr
inc vgm_sfx_addr+0
bne no_skip
inc vgm_sfx_addr+1
.no_skip
tya ; so that flags are set
rts
}
; SFX update routine - checks if a SFX is queued and feeds one update's worth of data to the sound chip.
; Call this routine every 50Hz
; Carry set if SFX finished
.vgm_sfx_update
{
ldx soundon
jsr vgm_set_volume_mask
; only play something if sfx addr hi byte is valid
lda vgm_sfx_addr+1
beq finished
; Ignore music player status for sfx
lda vgm_player_ended
beq finished
\\ Get packet size
\\ Byte is #data bytes to send to sound chip:
jsr vgm_sfx_get_byte
beq packet_end
; end of stream?
cmp #&ff
bne update
; invalidate address
lda #0
sta vgm_sfx_addr+1
\\ Silence sound chip
JSR vgm_deinit_player
.finished
sec
rts
.update
tax
.sound_data_loop
; fetch packet byte
jsr vgm_sfx_get_byte
; send to sound chip
jsr psg_strobe
; for all bytes in packet
dex
bne sound_data_loop
.packet_end
clc
rts
}
; volume ramp table - set by audio_set_volume - note that on SN chip 15=off, 0=full
.volume_table EQUB 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 ;
; local ZP vars
volume_interp = locals + 0
volume_increment = locals + 2
volume_store = locals + 3
; 0 = 16* 0 = 0 &00
; 1 = 16* 1 = 16 &10
; 2 = 16* 2 = 32 &20
; 8 = 16* 8 = &80
;...
;15 = 16*15 = 240 &F0
; at full volume, it's 0 to 15 (0-15)
; at half volume it's 8 to 15 (0-8)
; at silence it's 15 to 15 (0-0)
;
.vgm_volume EQUB 15 ; volume is 0-15 where 0 is silence, 15 is full
.vgm_volume_mask EQUB 0 ; set to 0 for no volume adjust, 15 for full audio mask
.vgm_volume_mask_t EQUB 15,0
; set volume mask
; X contains volume flag (1=on,0=off)
;
.vgm_set_volume_mask
{
lda vgm_volume_mask_t,x
sta vgm_volume_mask
rts
}
.vgm_volume_up
{
ldx vgm_volume
cpx #15
beq local_rts
inx
stx vgm_volume
jmp vgm_set_volume
}
.local_rts RTS
.vgm_volume_down
{
ldx vgm_volume
beq local_rts
dex
stx vgm_volume
jmp vgm_set_volume
}
; set volume by setting the 16-byte volume_table ramp using a hacky linear interpolation
; TODO: needs a bug fix as the full volume ramp is out by 1 level
; Note that volumes below 7 will degrade music quality due to lack of precision
; on entry X is volume (0=silence, 15=full)
.vgm_set_volume
{
IF _ENABLE_VOLUME
; stx volume_store
lda #15
sta volume_store
; sec
; sbc volume_store
; sta volume_store
; set volume table
lda #0
stz volume_interp+0
stz volume_interp+1
cpx #0
beq done_loopx
inc volume_store
.loopx
dec volume_store
clc
adc #17
dex
bne loopx
.done_loopx
sta volume_increment
; x=0 on entry
.loopx2
clc
lda volume_interp+1
adc volume_store
sta volume_table,x
lda volume_interp+0
clc
adc volume_increment
sta volume_interp+0
lda volume_interp+1
adc #0
sta volume_interp+1
; offset volume
inx
cpx #16
bne loopx2
ENDIF
rts
}
; SN76489 register update
; A contains register data to write
; Trashes Y
.psg_strobe
.psg_strobe_sei
; sei ; **SELF-MODIFIED CODE**
IF _ENABLE_VOLUME
; Check if volume control needs applying
; First check if bit 7 is set, 0=DATA 1=LATCH
bit psg_latch_bit ;
beq no_volume ; [3]
; ; this is a latch register write
; and check bit 4 to see if it is a volume register write, 1=VOLUME, 0=PITCH
bit psg_volume_bit ; [4]
beq no_volume ; not a volume register write
tay ; [2]
and #&f0 ; [2]
sta psg_register
tya ; [2]
and #&0f ; [2]
tay ; [2]
lda volume_table,y
and #&0f
ora vgm_volume_mask ; all bits set to mask audio, or clear to leave as is
ora psg_register
.no_volume
ENDIF ; _ENABLE_VOLUME
IF _ENABLE_AUDIO
ldy #255
sty $fe43
sta $fe41
lda #0
sta $fe40
nop
nop
nop
nop
nop
nop
lda #$08
sta $fe40
ENDIF ; _ENABLE_AUDIO
.psg_strobe_cli
; cli ; **SELF-MODIFIED CODE**
RTS
.psg_register EQUB 0 ; cant be in ZP as used in IRQ
.psg_volume_bit EQUB 16 ; bit 4
.psg_latch_bit EQUB 128 ; bit 7
;PSG_STROBE_SEI_INSN = psg_strobe_sei
;PSG_STROBE_CLI_INSN = psg_strobe_cli
.vgm_player_end