-
Notifications
You must be signed in to change notification settings - Fork 0
/
soundtest.asm
executable file
·536 lines (391 loc) · 11.4 KB
/
soundtest.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
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
\ *******************************************************************
\ * Zero-page sound related locations
\ *******************************************************************
ORG &70
.workspaceStart:
.soundtemp EQUB 0
.notereq EQUW 0, 0, 0, 0 ; addresses of note request sound blocks for each channel
.pitch EQUB 0 ; pitch channel 0
.volume EQUB 0 ; volume channel 0
EQUB 0 ; pitch channel 1
EQUB 0 ; volume channel 1
EQUB 0 ; pitch channel 2
EQUB 0 ; volume channel 2
EQUB 0 ; pitch channel 3
EQUB 0 ; volume channel 3
.pitchenv EQUB 0 ; pitch envelope channel 0
.volenv EQUB 0 ; volume envelope channel 0
EQUB 0 ; pitch envelope channel 1
EQUB 0 ; volume envelope channel 1
EQUB 0 ; pitch envelope channel 2
EQUB 0 ; volume envelope channel 2
EQUB 0 ; pitch envelope channel 3
EQUB 0 ; volume envelope channel 3
.pitchenvindex EQUB 0 ; pitch envelope index channel 0
.volenvstage EQUB 0 ; volume envelope stage channel 0
EQUB 0 ; pitch envelope index channel 1
EQUB 0 ; volume envelope stage channel 1
EQUB 0 ; pitch envelope index channel 2
EQUB 0 ; volume envelope stage channel 2
EQUB 0 ; pitch envelope index channel 3
EQUB 0 ; volume envelope stage channel 3
.workspaceEnd:
\\--------------------------------------------------------------------------
ORG &1900 ; or wherever
.codeStart:
JMP realStart
.tableStart:
\ *******************************************************************
\ * Sound frequency table
\ *******************************************************************
.freqlo
FOR n, 0, 47
EQUB LO(INT(1016 / 2^(n/48) + 0.5))
NEXT
IF HI(freqlo)<>HI(freqlo+47)
PRINT "Warning: freqlo table crosses page boundary"
ENDIF
.tableEnd:
; indexes 0-19: freqhi=3
; indexes 20-47: freqhi=2
freqhitransition = 20
\ *******************************************************************
\ * Two interleaved tables mapping sound channel numbers to
\ * sound chip commands
\ *******************************************************************
.channelfreq
EQUB &80 ; channel 0 pitch (+bass implementation)
.channelvolume
EQUB &9F ; channel 0 volume
EQUB &A0 ; channel 1 pitch
EQUB &BF ; channel 1 volume
EQUB &C0 ; channel 2 pitch (which can be used with noise channel)
EQUB &DF ; channel 2 volume
EQUB &E0 ; noise pitch
EQUB &FF ; noise volume
\\--------------------------------------------------------------------------
\ *******************************************************************
\ * Interrupt handler
\ *******************************************************************
.irq
lda $fe4d
and #2
BEQ irqOut
sta $fe4d
\\ ...in vsync handler...
txa:pha:tya:pha
JSR updatesound
\\ ...etc...
pla:tay:pla:tax
.irqOut:
LDA &FC
RTI
.dataStart:
\\ ****************************************************************************
\\ *
\\ * All the sound related code is here
\\ *
\\ ****************************************************************************
\\ Volume envelope definitions
\\ 1 2 3 4 5 6 7
.ve_attackrate EQUB 52, 24, 52, 52, 52, 52, 4
.ve_peak EQUB 52, 48, 52, 52, 52, 52, 44
.ve_sustaintime EQUB 4, 4, 48, 1, 0, 2, 4
.ve_releaserate EQUB 1, 4, 1, 4, 52, 20, 4
\\ Pitch envelope definitions
\\ 1 2 3 4 5 6 7 8 9
.pe_numsteps EQUB 6, 3, 3, 3, 3, 3, 3, 3, 3
.pe_deflo EQUB LO(pe1), LO(pe2), LO(pe3), LO(pe4), LO(pe5), LO(pe6), LO(pe7), LO(pe8), LO(pe9)
.pe_defhi EQUB HI(pe1), HI(pe2), HI(pe3), HI(pe4), HI(pe5), HI(pe6), HI(pe7), HI(pe8), HI(pe9)
.pe1 EQUB 1, 0, 0, 255, 0, 0
.pe2 EQUB 256-28, 16, 12 ; CEG triad
.pe3 EQUB 256-36, 20, 16 ; CFA triad
.pe4 EQUB 256-36, 12, 24 ; DFB triad
.pe5 EQUB 256-36, 16, 20 ; EbGC triad
.pe6 EQUB 256-28, 12, 16 ; CEbG triad
.pe7 EQUB 256-36, 24, 12 ; EbAC triad
.pe8 EQUB 256-24, 12, 12 ; GbAC triad
.pe9 EQUB 256-32, 20, 12 ; GCEb triad
.testsoundblock:
EQUB 90 ; pitch
EQUB 0 ; pitch envelope
EQUB 7 ; volume envelope
.dataEnd:
\\ Sound code entry point
.updatesound
LDX #6
.updatesoundloop
; see if a note has been requested on this channel
LDA notereq+1,X ; notereq MSB<>0 indicated a note request
BEQ nonewnote
; a note was requested - fill in details
; at (notereq,X) are:
; +0 pitch
; +1 pitch envelope number
; +2 volume envelope number
LDA (notereq,X)
STA pitch,X
JSR writepitch
INC notereq,X:BNE P%+4:INC notereq+1,X
LDA (notereq,X)
STA pitchenv,X
LDA #8:STA &FE40 ; easily used 16 cycles since the JSR writepitch
INC notereq,X:BNE P%+4:INC notereq+1,X
LDA (notereq,X)
STA volenv,X
TAY
LDA #0
STA notereq+1,X ; clear note request
STA pitchenvindex,X ; initialise pitch envelope pointer
STA volenvstage,X ; initialise volume envelope stage
; send vol/pitch to sound chip
LDA ve_attackrate-1,Y ; get initial volume from attack phase (-1 because valid indices start at 1)
STA volume,X
JSR writevolume
LDA #8 ; waste 16 cycles (6 from the RTS + 2
DEX:DEX ; + 2 + 2
STA &FE40 ; + 4)
BPL updatesoundloop
RTS
; No new note requested, so update what is already playing
.nonewnote
; Update volume if necessary
LDY volenv,X
BEQ novolumeenv
LDA volenvstage,X ; 0, 1-127 or &80
BMI release
BNE sustain
.attack
LDA volume,X
CMP ve_peak-1,Y
BCS sustain ; if already at peak, move to sustain phase
ADC ve_attackrate-1,Y
CMP ve_peak-1,Y
BCC updatevolume
LDA ve_peak-1,Y ; clamp at peak
STA volume,X
BNE updatevolume ; always taken
.sustain
LDA volenvstage,X
CMP ve_sustaintime-1,Y
INC volenvstage,X
BCC novolumeenv
.startrelease
LDA #128
STA volenvstage,X
.release
LDA volume,X
SEC
SBC ve_releaserate-1,Y
BPL P%+4
LDA #0
BNE updatevolume
STA volenv,X ; disable note, but still fall through so we write zero volume
STA pitchenv,X ; also stop pitch envelope update
.updatevolume
; Update volume here and send to sound chip
; Check against old value to see if anything has actually changed
TAY
EOR volume,X
AND #252
STY volume,X
BEQ volhasntchanged
TYA
JSR writevolume
LDA (0,X) ; waste 16 cycles (6 from the RTS + 6
LDA #8:STA &FE40 ; + 2 + most of the STA)
.volhasntchanged
.novolumeenv
; Update pitch if necessary
LDY pitchenv,X
BEQ nopitchenv
LDA pe_deflo-1,Y:STA pitchdata+1
LDA pe_defhi-1,Y:STA pitchdata+2
LDA pe_numsteps-1,Y:STA pitchsteps+1
LDY pitchenvindex,X
.pitchdata LDA &B9B9,Y ; self-modified
BEQ nochangepitch
CLC
ADC pitch,X
STA pitch,X
JSR writepitch
.nochangepitch
LDY pitchenvindex,X
INY
.pitchsteps CPY #0
BNE P%+4
LDY #0
STY pitchenvindex,X
LDA #8:STA &FE40
; iterate to next channel
.nopitchenv
DEX:DEX
BMI P%+5
JMP updatesoundloop
.exitsoundupdate
RTS
;-------------------------------------------------
.writevolume
; A = volume
; X = channel
; After calling this, wait at least 5 cycles, and then execute LDA #8:STA &FE40 or similar)
LSR A:LSR A
EOR channelvolume,X
STA &FE4F
LDY #0:STY &FE40
; Still pending: to pull sound chip enable high again
; Do it after the RTS to avoid idling unnecessarily
; (wait at least 5 cycles, and then execute LDA #8:STA &FE40 or similar)
RTS
.writepitchnoise
; A = note to be written to noise channel
; Still pending: to pull sound chip enable high again.
; Do it after the RTS to avoid idling unnecessarily
; (wait at least 5 cycles, and then execute LDA #8:STA &FE40 or similar)
ORA #&E0:STA &FE4F
LDY #0:STY &FE40
RTS
.writepitch
; A = note
; X = channel
; After calling this, wait at least 5 cycles, and then execute LDA #8:STA &FE40 or similar)
; Special handling for noise control
CPX #6
BEQ writepitchnoise
STX pitchchannel+1
; Find the octave and the note within the octave
LDX #0
CMP #48*4
BCC div48a
SBC #48*4
LDX #4
.div48a
CMP #48*2
BCC div48b
SBC #48*2
INX
INX
.div48b
CMP #48
BCC div48c
SBC #48
INX
.div48c
; X = octave number
; A = note within the octave
; Get 10-bit frequency for this note
; the top 2 bits are either 2 or 3, so we don't use a table for this
TAY
LDA #2
CPY #freqhitransition
ADC #0
EOR #1
STA soundtemp
LDA freqlo,Y
; Shift down according to octave
CPX #0
BEQ nooctaveshift
.octaveshift
LSR soundtemp
ROR A
DEX
BNE octaveshift
.nooctaveshift
; Write pitch to sound chip
; First, the low 4 bits
TAY ; preserve A
AND #15
.pitchchannel LDX #0 ; self-modified
ORA channelfreq,X
STA &FE4F
TYA ; get back A
; Pull sound chip enable low
LDY #0:STY &FE40
; Do some stuff while we have to wait 8us
ASL A:ROL soundtemp
ASL A:ROL soundtemp
ASL A:ROL soundtemp
ASL A:ROL soundtemp
; Pull sound chip enable high
LDA #8:STA &FE40
; Next, the top 6 bits
LDA soundtemp
STA &FE4F
; Pull sound chip enable low
STY &FE40 ; Y=0
; Still pending: to pull sound chip enable high again
; Do it after the RTS to avoid idling unnecessarily
; (wait at least 5 cycles, and then execute LDA #8:STA &FE40 or similar)
RTS
\\--------------------------------------------------------------------------
\\ At the beginning of the game, at some point, please configure the VIA as follows
\\ so that it's ready for the sound code.
\\ Configure sound chip ready for writing
\\ (Note, to access keyboard, write &03 to &FE40, but afterwards disable it
\\ again by writing &0B.)
LDA #&FF:STA &FE43
LDA #&0F:STA &FE42
LDA #&08:STA &FE40 ; sound chip enable pulled high (disabled for now)
LDA #&0B:STA &FE40 ; keyboard enable pulled high (disabled)
\\--------------------------------------------------------------------------
\\ To play a sound, put the address of a sound block in:
\\ notereq+0/notereq+1 - channel 0 (regular tone channel)
\\ notereq+2/notereq+3 - channel 1 (regular tone channel)
\\ notereq+4/notereq+5 - channel 2 (regular tone channel, also can be used as pitch for the noise channel)
\\ notereq+6/notereq+7 - channel 3 (the noise channel)
\\ Sound block contains:
\\ +0 - note pitch (in quarter semitones 0-255)
\\ +1 - pitch envelope index (0 = no pitch envelope)
\\ +2 - volume envelope index (must be >0, 0 is not valid)
.setupIRQ:
LDA #22:JSR &FFEE:LDA #7:JSR &FFEE
; *TAPE
LDA #140
JSR &FFF4
SEI
; Disable all interrupts
LDA #&7F
STA &FE4E
LDA #&FF:STA &FE43
LDA #&0F:STA &FE42
LDA #&08:STA &FE40 ; sound chip enable pulled high (disabled for now)
LDA #&0B:STA &FE40 ; keyboard enable pulled high (disabled)
; Set our handler
LDA #LO(irq)
STA &204
LDA #HI(irq)
STA &205
; Enable vsync timer
LDA #$82
STA &FE4E
CLI
RTS
.realStart:
LDA #0
TAY
.zeroLoop:
STA workspaceStart,Y
INY
CPY #workspaceEnd-workspaceStart
BNE zeroLoop
JSR setupIRQ
.mainLoop:
LDA #LO(testsoundblock):STA notereq+0
LDA #HI(testsoundblock):STA notereq+1 ; always store MSB last
.innerLoop:
LDA #65
STA $7c00
INC innerLoop+1
LDA innerLoop+1
CMP #(65+26)
BNE innerLoop
LDA #65
STA innerLoop+1
JMP innerLoop
RTS
.codeEnd:
SAVE "Code",codeStart,codeEnd
PRINT "zp workspace: ", (workspaceEnd-workspaceStart)
PRINT "table data: ", tableEnd-tableStart
PRINT "Data: ",dataEnd-dataStart
PRINT "code bytes: ", (codeEnd-codeStart)