-
Notifications
You must be signed in to change notification settings - Fork 1
/
apu.s
509 lines (479 loc) · 9.75 KB
/
apu.s
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
;
; Updates the APU registers. x and y are free to use
;
; calculates FLOOR(((var_Temp + 1) * (var_Temp2 + 1) - 1) / 16)
; 4 effective bits for var_Temp2, each block adds one
.if .defined(USE_VRC6) || .defined(USE_FDS)
ft_multiply_volume: ;;; ;; ; 050B
lda var_Temp
lsr var_Temp2
bcs :+
lsr a
:
lsr var_Temp2
bcc :+
adc var_Temp
: lsr a
lsr var_Temp2
bcc :+
adc var_Temp
: lsr a
lsr var_Temp2
bcc :+
adc var_Temp
: lsr a
beq :+
rts
: lda var_Temp
ora var_ch_Volume, x
beq :+
lda #$01 ; Round up to 1
: rts
.endif
ft_update_2a03:
lda var_PlayerFlags
bne @Play
lda #$00 ; Kill all channels
sta $4015
rts
@KillSweepUnit: ; Reset sweep unit to avoid strange problems
lda #$C0
sta $4017
lda #$40
sta $4017
rts
@Play:
.if .defined(USE_LINEARPITCH) ;;; ;; ;
lda var_SongFlags
and #FLAG_LINEARPITCH
beq @End
.if .defined(PAL_PERIOD_TABLE)
lda var_SongFlags
and #FLAG_USEPAL
bne :+
jsr ft_load_ntsc_table
jmp @TableLoaded
: jsr ft_load_pal_table
.else
jsr ft_load_ntsc_table
.endif
@TableLoaded:
ldx #APU_OFFSET
jsr ft_linear_fetch_pitch
jsr ft_linear_fetch_pitch
jsr ft_linear_fetch_pitch
@End:
.endif ; ;; ;;;
ldx #$00
ldy #$00
; ==============================================================================
; Square 1 / 2
; ==============================================================================
@Square:
.if .defined(CHANNEL_CONTROL)
lda bit_mask, x
and var_Channels
bne :+
jmp @DoneSquare
:
.endif
lda var_ch_Note, x ; Kill channel if note = off
bne :+ ; branch
jmp @KillSquare
:
; Calculate volume
.if 0
ldx #$00
jsr ft_get_volume
beq @KillSquare
.endif
; Calculate volume
lda var_ch_LengthCounter + APU_OFFSET, x ;;; ;; ;
and #$01
beq :+
lda var_ch_VolColumn + APU_OFFSET, x ; do not automatically kill channel when hardware envelope is enabled
asl a
and #$F0
ora var_ch_Volume + APU_OFFSET, x
tay
lda ft_volume_table, y ; ignore tremolo
bpl @DoneVolumeSquare ; always ; ;; ;;;
: lda var_ch_VolColumn + APU_OFFSET, x ; Kill channel if volume column = 0
asl a
bne :+
jmp @KillSquare
: and #$F0
sta var_Temp
lda var_ch_Volume + APU_OFFSET, x
bne :+
jmp @KillSquare
: ora var_Temp
tay
lda ft_volume_table, y
sec
sbc var_ch_TremoloResult + APU_OFFSET, x
bpl :+
lda #$00
: bne :+
lda var_ch_VolColumn + APU_OFFSET, x
beq :+
lda #$01
:
@DoneVolumeSquare:
; Write to registers
pha
lda var_ch_DutyCurrent + APU_OFFSET, x
and #$03
tay
pla
ora ft_duty_table, y ; Add volume
sta var_Temp ;;; ;; ; allow length counter and envelope
txa
asl a
asl a
tay
lda var_ch_LengthCounter + APU_OFFSET, x
and #$03
eor #$03
asl a
asl a
asl a
asl a
ora var_Temp ; ;; ;;;
sta $4000, y
iny
; Period table isn't limited to $7FF anymore
lda var_ch_PeriodCalcHi + APU_OFFSET, x
and #$F8
beq :+
lda #$07
sta var_ch_PeriodCalcHi + APU_OFFSET, x
lda #$FF
sta var_ch_PeriodCalcLo + APU_OFFSET, x
:
lda var_ch_Sweep + APU_OFFSET, x ; Check if sweep is active
beq @NoSquareSweep
and #$80
beq @DoneSquare ; See if sweep is triggered, if then don't touch sound registers until next note
lda var_ch_Sweep + APU_OFFSET, x ; Trigger sweep
sta $4000, y
iny
and #$7F
sta var_ch_Sweep + APU_OFFSET, x
jsr @KillSweepUnit
lda var_ch_PeriodCalcLo + APU_OFFSET, x
sta $4000, y
iny
lda var_ch_PeriodCalcHi + APU_OFFSET, x
sta $4000, y
lda #$FF
sta var_ch_PrevFreqHigh + APU_OFFSET, x
jmp @DoneSquare
@NoSquareSweep: ; No Sweep
lda #$08
sta $4000, y
iny
;jsr @KillSweepUnit ; test
lda var_ch_PeriodCalcLo + APU_OFFSET, x
sta $4000, y
iny
lda var_ch_LengthCounter + APU_OFFSET, x ;;; ;; ;
and #$03
beq :+
lda var_ch_Trigger + APU_OFFSET, x
beq @DoneSquare
bne :++
: lda var_ch_PeriodCalcHi + APU_OFFSET, x
cmp var_ch_PrevFreqHigh + APU_OFFSET, x
beq @DoneSquare
sta var_ch_PrevFreqHigh + APU_OFFSET, x
: lda var_ch_LengthCounter + APU_OFFSET, x
and #$F8
ora var_ch_PeriodCalcHi + APU_OFFSET, x
sta $4000, y ; ;; ;;;
jmp @DoneSquare
@KillSquare:
lda #$30
sta $4000, y
@DoneSquare:
inx
cpx #$02
bcs :+
ldy #$04
;txa
;asl a
;asl a
;tay
jmp @Square
:
; ==============================================================================
; Triangle
; ==============================================================================
@Triangle:
.if .defined(CHANNEL_CONTROL)
lda var_Channels
and #$04
beq @Noise
.endif
lda var_ch_Volume + APU_TRI
beq @KillTriangle
lda var_ch_VolColumn + APU_TRI
beq @KillTriangle
lda var_ch_Note + APU_TRI
beq @KillTriangle
lda var_ch_LengthCounter + APU_TRI ;;; ;; ;
and #%00000011
beq :+ ; branch if no length counter and no linear counter
lda var_Linear_Counter
and #$7F
bpl :++ ; always
: lda var_Linear_Counter
ora #$80 ; ;; ;;;
: sta $4008
@EndTriangleVolume:
; Period table isn't limited to $7FF anymore
lda var_ch_PeriodCalcHi + APU_TRI
and #$F8
beq @TimerOverflow3
lda #$07
sta var_ch_PeriodCalcHi + APU_TRI
lda #$FF
sta var_ch_PeriodCalcLo + APU_TRI
@TimerOverflow3:
; lda #$08
; sta $4009
lda var_ch_PeriodCalcLo + APU_TRI
sta $400A
lda var_ch_Trigger + APU_TRI ;;; ;; ;
bne :+
lda var_ch_LengthCounter + APU_TRI
and #%00000011
bne @SkipTriangleKill
: lda var_ch_LengthCounter + APU_TRI
and #%11111000
ora var_ch_PeriodCalcHi + APU_TRI ; ;; ;;;
sta $400B
jmp @SkipTriangleKill
@KillTriangle:
lda #$00
sta $4008
@SkipTriangleKill:
; ==============================================================================
; Noise
; ==============================================================================
@Noise:
.if .defined(CHANNEL_CONTROL)
lda var_Channels
and #$08
bne :+ ; branch
jmp @DPCM
:
.endif
lda var_ch_Note + APU_NOI
bne :+ ; branch
jmp @KillNoise
:
; Calculate volume
lda var_ch_LengthCounter + APU_NOI ;;; ;; ;
and #$01
beq :+
lda var_ch_VolColumn + APU_NOI ; do not automatically kill channel when hardware envelope is enabled
asl a
and #$F0
ora var_ch_Volume + APU_NOI
tax
lda ft_volume_table, x ; ignore tremolo
bpl @DoneVolumeNoise ; always ; ;; ;;;
: lda var_ch_VolColumn + APU_NOI ; Kill channel if volume column = 0
asl a
beq @KillNoise
and #$F0
sta var_Temp
lda var_ch_Volume + APU_NOI
beq @KillNoise
ora var_Temp
tax
lda ft_volume_table, x
sec
sbc var_ch_TremoloResult + APU_NOI
bpl :+
lda #$00
: bne :+
lda var_ch_VolColumn + APU_NOI
beq :+
lda #$01
:
@DoneVolumeNoise:
; Write to registers
sta var_Temp ;;; ;; ;
lda var_ch_LengthCounter + APU_NOI
and #$03
eor #$03
asl a
asl a
asl a
asl a
ora var_Temp ; ;; ;;;
sta $400C
lda #$00
sta $400D
lda var_ch_DutyCurrent + APU_NOI
; and #$01
ror a
ror a
and #$80
sta var_Temp
.if 0
.if .defined(SCALE_NOISE)
; Divide noise period by 16
lda var_ch_PeriodCalcLo + APU_NOI
lsr a
lsr a
lsr a
lsr a
.else
; Limit noise period to range 0 - 15
lda var_ch_PeriodCalcHi + APU_NOI
bne :+
lda var_ch_PeriodCalcLo + APU_NOI
cmp #$10
bcc :++
: lda #$0F
: eor #$0F
.endif
.else
; No limit
lda var_ch_PeriodCalcLo + APU_NOI
and #$0F
eor #$0F
.endif
ora var_Temp
sta $400E
lda var_ch_LengthCounter + APU_NOI ;;; ;; ;
and #$03
beq :+
lda var_ch_Trigger + APU_NOI
beq @DPCM
: lda var_ch_LengthCounter + APU_NOI
sta $400F ; ;; ;;;
jmp @DPCM
@KillNoise:
lda #$30
sta $400C
@DPCM:
; ==============================================================================
; DPCM
; ==============================================================================
.if .defined(USE_DPCM)
.if .defined(CHANNEL_CONTROL)
lda var_Channels
and #$10
bne :+
rts ; Skip DPCM
;beq @Return
:
.endif
.if .defined(USE_ALL) ;;; ;; ;
ldx #EFF_CHANS
.elseif .defined(USE_N163)
ldx var_EffChannels
.else
ldx #EFF_CHANS
.endif
lda var_ch_DPCM_Retrig ; Retrigger
beq @SkipRetrigger
dec var_ch_DPCM_RetrigCntr
bne @SkipRetrigger
sta var_ch_DPCM_RetrigCntr
lda #$01
sta var_ch_Note, x
@SkipRetrigger:
lda var_ch_DPCMDAC ; See if delta counter should be updated
bmi @SkipDAC
sta $4011
@SkipDAC:
lda #$80 ; store a negative value to mark that it's already updated
sta var_ch_DPCMDAC
lda var_ch_Note, x
beq @KillDPCM
bmi @SkipDPCM
lda var_ch_SamplePitch
and #$40
sta var_Temp
lda var_ch_DPCM_EffPitch
bpl :+
lda var_ch_SamplePitch
: ora var_Temp
sta $4010
lda #$80
sta var_ch_DPCM_EffPitch
; Setup sample bank (if used)
.if .defined(USE_BANKSWITCH)
lda var_ch_SampleBank
beq :+
jsr ft_bankswitch2
:
.endif
; Sample position (add sample offset)
clc
lda var_ch_SamplePtr
adc var_ch_DPCM_Offset
sta $4012
; Sample length (remove sample offset)
lda var_ch_DPCM_Offset
asl a
asl a
sta var_Temp
sec
lda var_ch_SampleLen
sbc var_Temp
sta $4013
lda #$80
sta var_ch_Note, x
lda #$0F
sta $4015
lda #$1F
sta $4015
rts
@SkipDPCM:
cmp #$FF
beq @ReleaseDPCM
rts
@ReleaseDPCM:
; todo
lda #$0F
sta $4015
lda #$80
sta var_ch_Note, x
rts
@KillDPCM:
lda #$0F
sta $4015
lda #$80
sta $4011
sta var_ch_Note, x
.endif
@Return:
rts
; Lookup tables
ft_duty_table:
.repeat 4, i
.byte $40 * i
.endrep
ft_duty_2a03_to_vrc6:
.byte $01, $03, $07, $03
ft_duty_vrc6_to_2a03:
.byte $00, $00, $01, $01, $01, $01, $02, $02
; Volume table: (column volume) * (instrument volume)
ft_volume_table:
.repeat 16, xx
.repeat 16, yy
.if xx = 0 || yy = 0
.byte 0
.elseif xx * yy < 15
.byte 1
.else
.byte xx * yy / 15
.endif
.endrep
.endrep