-
Notifications
You must be signed in to change notification settings - Fork 23
/
famitone.s
1031 lines (884 loc) · 21.1 KB
/
famitone.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
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
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
;FamiTone audio library v1.24
;by Shiru ([email protected]) 06'11
;The name is suggested by Memblers from NesDev
;Feel free to do anything you want with this code, consider it Public Domain
;aliases for APU registers
APU_PL1_VOL = $4000
APU_PL1_SWEEP = $4001
APU_PL1_LO = $4002
APU_PL1_HI = $4003
APU_PL2_VOL = $4004
APU_PL2_SWEEP = $4005
APU_PL2_LO = $4006
APU_PL2_HI = $4007
APU_TRI_LINEAR = $4008
APU_TRI_LO = $400a
APU_TRI_HI = $400b
APU_NOISE_VOL = $400c
APU_NOISE_LO = $400e
APU_NOISE_HI = $400f
APU_DMC_FREQ = $4010
APU_DMC_RAW = $4011
APU_DMC_START = $4012
APU_DMC_LEN = $4013
APU_SND_CHN = $4015
;all the FamiTone variables take 112+15*FT_SFX_STREAMS bytes in a RAM page
FT_FRAME_CNT = FT_BASE_ADR
FT_SONG_SPEED = FT_BASE_ADR+1
FT_INSTRUMENT_L = FT_BASE_ADR+2
FT_INSTRUMENT_H = FT_BASE_ADR+3
FT_PULSE1_PREV = FT_BASE_ADR+4
FT_PULSE2_PREV = FT_BASE_ADR+5
FT_CHANNELS = FT_BASE_ADR+6
FT_CH1_VARS = FT_CHANNELS
FT_CH2_VARS = FT_CHANNELS+9
FT_CH3_VARS = FT_CHANNELS+18
FT_CH4_VARS = FT_CHANNELS+27
FT_CH5_VARS = FT_CHANNELS+36
FT_ENVELOPES = FT_BASE_ADR+51
FT_CH1_ENVS = FT_ENVELOPES ;three envelopes (5*3 bytes) for pulse and triangle
FT_CH2_ENVS = FT_ENVELOPES+15
FT_CH3_ENVS = FT_ENVELOPES+30
FT_CH4_ENVS = FT_ENVELOPES+45 ;only two envelopes (5*2 bytes) for noise
FT_DPCM_TABLE_L = FT_BASE_ADR+106
FT_DPCM_TABLE_H = FT_BASE_ADR+107
FT_DPCM_EFFECT = FT_BASE_ADR+108
FT_SFX_ADR_L = FT_BASE_ADR+109
FT_SFX_ADR_H = FT_BASE_ADR+110
FT_PAL_ADJUST = FT_BASE_ADR+111
;envelope variables offsets, every envelope uses 5 bytes
FT_ENV_STRUCT_SIZE = 5
FT_ENV_VALUE = FT_BASE_ADR+0
FT_ENV_REPEAT = FT_BASE_ADR+1
FT_ENV_ADR_L = FT_BASE_ADR+2
FT_ENV_ADR_H = FT_BASE_ADR+3
FT_ENV_PTR = FT_BASE_ADR+4
;channels variables offsets, every channel uses 9 bytes
FT_CHN_STRUCT_SIZE = 9
FT_CHN_REPEAT = FT_BASE_ADR+0
FT_CHN_NOTE = FT_BASE_ADR+1
FT_CHN_INSTRUMENT = FT_BASE_ADR+2
FT_CHN_DUTY = FT_BASE_ADR+3
FT_CHN_PTR_L = FT_BASE_ADR+4
FT_CHN_PTR_H = FT_BASE_ADR+5
FT_CHN_RETURN_L = FT_BASE_ADR+6
FT_CHN_RETURN_H = FT_BASE_ADR+7
FT_CHN_REF_LEN = FT_BASE_ADR+8
;aliases for outputs
FT_CH1_NOTE = FT_CH1_VARS+.lobyte(FT_CHN_NOTE)
FT_CH2_NOTE = FT_CH2_VARS+.lobyte(FT_CHN_NOTE)
FT_CH3_NOTE = FT_CH3_VARS+.lobyte(FT_CHN_NOTE)
FT_CH4_NOTE = FT_CH4_VARS+.lobyte(FT_CHN_NOTE)
FT_CH5_NOTE = FT_CH5_VARS+.lobyte(FT_CHN_NOTE)
FT_CH1_VOLUME = FT_CH1_ENVS+.lobyte(FT_ENV_VALUE)
FT_CH2_VOLUME = FT_CH2_ENVS+.lobyte(FT_ENV_VALUE)
FT_CH3_VOLUME = FT_CH3_ENVS+.lobyte(FT_ENV_VALUE)
FT_CH4_VOLUME = FT_CH4_ENVS+.lobyte(FT_ENV_VALUE)
FT_CH1_NOTE_OFF = FT_CH1_ENVS+.lobyte(FT_ENV_VALUE)+FT_ENV_STRUCT_SIZE
FT_CH2_NOTE_OFF = FT_CH2_ENVS+.lobyte(FT_ENV_VALUE)+FT_ENV_STRUCT_SIZE
FT_CH3_NOTE_OFF = FT_CH3_ENVS+.lobyte(FT_ENV_VALUE)+FT_ENV_STRUCT_SIZE
FT_CH4_NOTE_OFF = FT_CH4_ENVS+.lobyte(FT_ENV_VALUE)+FT_ENV_STRUCT_SIZE
FT_CH1_PITCH_OFF = FT_CH1_ENVS+.lobyte(FT_ENV_VALUE)+FT_ENV_STRUCT_SIZE*2
FT_CH2_PITCH_OFF = FT_CH2_ENVS+.lobyte(FT_ENV_VALUE)+FT_ENV_STRUCT_SIZE*2
FT_CH3_PITCH_OFF = FT_CH3_ENVS+.lobyte(FT_ENV_VALUE)+FT_ENV_STRUCT_SIZE*2
FT_CH1_DUTY = FT_CH1_VARS+.lobyte(FT_CHN_DUTY)
FT_CH2_DUTY = FT_CH2_VARS+.lobyte(FT_CHN_DUTY)
FT_CH3_DUTY = FT_CH3_VARS+.lobyte(FT_CHN_DUTY)
FT_CH4_DUTY = FT_CH4_VARS+.lobyte(FT_CHN_DUTY)
;output buffer, used then sound effects are enabled
FT_OUT_BUF = FT_BASE_ADR+112 ;11 bytes
;aliases for APU registers for music FamiTone
.if(!FT_SFX_ENABLE) ;if sound effects are disabled, just write to APU
FT_MR_PULSE1_V = APU_PL1_VOL
FT_MR_PULSE1_L = APU_PL1_LO
FT_MR_PULSE1_H = APU_PL1_HI
FT_MR_PULSE2_V = APU_PL2_VOL
FT_MR_PULSE2_L = APU_PL2_LO
FT_MR_PULSE2_H = APU_PL2_HI
FT_MR_TRI_V = APU_TRI_LINEAR
FT_MR_TRI_L = APU_TRI_LO
FT_MR_TRI_H = APU_TRI_HI
FT_MR_NOISE_V = APU_NOISE_VOL
FT_MR_NOISE_F = APU_NOISE_LO
.else ;otherwise write to output buffer
FT_MR_PULSE1_V = FT_OUT_BUF
FT_MR_PULSE1_L = FT_OUT_BUF+1
FT_MR_PULSE1_H = FT_OUT_BUF+2
FT_MR_PULSE2_V = FT_OUT_BUF+3
FT_MR_PULSE2_L = FT_OUT_BUF+4
FT_MR_PULSE2_H = FT_OUT_BUF+5
FT_MR_TRI_V = FT_OUT_BUF+6
FT_MR_TRI_L = FT_OUT_BUF+7
FT_MR_TRI_H = FT_OUT_BUF+8
FT_MR_NOISE_V = FT_OUT_BUF+9
FT_MR_NOISE_F = FT_OUT_BUF+10
.endif
;sound effect stream variables, 15 bytes per stream
FT_SFX_BASE_ADR = FT_BASE_ADR+123
FT_SFX_STRUCT_SIZE = 15
FT_SFX_REPEAT = FT_SFX_BASE_ADR
FT_SFX_PTR_L = FT_SFX_BASE_ADR+1
FT_SFX_PTR_H = FT_SFX_BASE_ADR+2
FT_SFX_OFF = FT_SFX_BASE_ADR+3
FT_SFX_BUF = FT_SFX_BASE_ADR+4 ;11 bytes
;aliases for channels to use in user calls
FT_SFX_CH0 = 0
FT_SFX_CH1 = FT_SFX_STRUCT_SIZE
FT_SFX_CH2 = FT_SFX_STRUCT_SIZE*2
FT_SFX_CH3 = FT_SFX_STRUCT_SIZE*3
;reset APU, initialize FamiTone
;in: A 0 for PAL, not 0 for NTSC
FamiToneInit:
cmp #0
beq @pal
lda #$ff
@pal:
sta FT_PAL_ADJUST
lda #$0f ;enable channels, stop DMC
sta APU_SND_CHN
lda #$81 ;disable triangle length counter
sta APU_TRI_LINEAR
lda #$01 ;load noise length
sta APU_NOISE_HI
lda #$30 ;volumes to 0
sta APU_PL1_VOL
sta APU_PL2_VOL
sta APU_NOISE_VOL
lda #$08 ;no sweep
sta APU_PL1_SWEEP
sta APU_PL2_SWEEP
lda #$ff
sta FT_PULSE1_PREV
sta FT_PULSE2_PREV
;stop music that currently plays (it is a part of FamiToneInit as well)
FamiToneMusicStop:
ldx #.lobyte(FT_CHANNELS) ;reset all the channels variables
ldy #5
@setChannels:
lda #0
sta FT_CHN_PTR_L,x
sta FT_CHN_PTR_H,x
sta FT_CHN_REPEAT,x
sta FT_CHN_REF_LEN,x
sta FT_CHN_INSTRUMENT,x
lda #63
sta FT_CHN_NOTE,x
lda #$30
sta FT_CHN_DUTY,x
txa
clc
adc #FT_CHN_STRUCT_SIZE
tax
dey
bne @setChannels
sty FT_CH1_VOLUME ;reset volumes
sty FT_CH2_VOLUME
sty FT_CH3_VOLUME
sty FT_CH4_VOLUME
sty FT_DPCM_EFFECT ;no DPCM effect playing
sty FT_SONG_SPEED ;no music playing
rts
;start playing a music
;in: X,Y address of the module (LSB,MSB)
FamiToneMusicStart:
lda #0
sta FT_SONG_SPEED ;stop music update
stx <FT_TEMP
sty <FT_TEMP+1
tay ;A=0
ldx FT_PAL_ADJUST
bmi @noSetAdjust
sta FT_PAL_ADJUST
@noSetAdjust:
ldx #.lobyte(FT_CHANNELS)
lda #5
sta FT_TEMP+2
@setChannels:
lda (FT_TEMP),y
sta FT_CHN_PTR_L,x
iny
lda (FT_TEMP),y
sta FT_CHN_PTR_H,x
iny
lda #0
sta FT_CHN_REPEAT,x
sta FT_CHN_REF_LEN,x
sta FT_CHN_INSTRUMENT,x
lda #63
sta FT_CHN_NOTE,x
lda #$30
sta FT_CHN_DUTY,x
txa
clc
adc #FT_CHN_STRUCT_SIZE
tax
dec <FT_TEMP+2
bne @setChannels
lda (FT_TEMP),y
iny
sta FT_INSTRUMENT_L
lda (FT_TEMP),y
iny
sta FT_INSTRUMENT_H
lda (FT_TEMP),y
pha
lda #4
sta <FT_TEMP+6
lda #.lobyte(FT_ENVELOPES)
@setEnvelopes:
pha
tax
lda #0
jsr setInstrument
pla
adc #FT_ENV_STRUCT_SIZE*3
dec <FT_TEMP+6
bne @setEnvelopes
pla
sta FT_SONG_SPEED ;this enables music update
sta FT_FRAME_CNT
rts
;pause and unpause current music
;in: A 0 or not 0 to play or pause
FamiToneMusicPause:
tax
lda FT_SONG_SPEED
cpx #0
beq @play
ora #$80
bne @set
@play:
and #$7f
@set:
sta FT_SONG_SPEED
rts
;update FamiTone state, should be called every TV frame
FamiToneUpdate:
.if(FT_THREAD)
lda FT_TEMP
pha
lda FT_TEMP+1
pha
.endif
ldx FT_PAL_ADJUST ;for PAL mode count 0..4
bmi @noAdjust
inx
cpx #5
bne @noSkip
ldx #0
@noSkip:
stx FT_PAL_ADJUST
@noAdjust:
lda FT_SONG_SPEED
beq @noMusic
bmi @noMusic ;music paused
lda FT_FRAME_CNT ;check TV frame counter
beq @row ;if it is 0 or negative, it is a new row
bmi @row
bne @noRow
@row:
lda #.lobyte(FT_CHANNELS) ;start of a row, updating all the channels
sta <FT_TEMP+4
lda #.lobyte(FT_ENVELOPES)
sta <FT_TEMP+5
lda #4 ;process pulse, triangle, and noise channels
sta <FT_TEMP+6
ldy #0
@processChns:
ldx <FT_TEMP+4
jsr channelStep
bcs @noNewNote ;check if there was a new note
ldx <FT_TEMP+4 ;setting up a new note with current instrument
lda FT_CHN_INSTRUMENT,x
ldx <FT_TEMP+5
jsr setInstrument
ldx <FT_TEMP+4
sta FT_CHN_DUTY,x
@noNewNote:
lda <FT_TEMP+4 ;next channel
clc
adc #FT_CHN_STRUCT_SIZE
sta <FT_TEMP+4
lda <FT_TEMP+5 ;next envelopes block
clc
adc #FT_ENV_STRUCT_SIZE*3
sta <FT_TEMP+5
dec <FT_TEMP+6
bne @processChns
.if(FT_DPCM_ENABLE)
ldx #.lobyte(FT_CH5_VARS)
jsr channelStep
bcs @ch5done
cmp #63
bne @ch5note
jsr FamiToneSampleStop
jmp @ch5done
@ch5note:
lda FT_CH5_NOTE
jsr FamiToneSampleStartM
@ch5done:
.endif
lda FT_FRAME_CNT ;set TV frame counter
clc
adc FT_SONG_SPEED
sta FT_FRAME_CNT
@noRow:
lda #11 ;now process all the envelopes (11 because noise has no pitch)
sta <FT_TEMP+2
ldx #.lobyte(FT_ENVELOPES)
@processEnvs:
txa
pha
jsr envelopeStep
pla
clc
adc #FT_ENV_STRUCT_SIZE
tax
dec <FT_TEMP+2
bne @processEnvs
@noMusic:
lda FT_SONG_SPEED
bpl @noPause
lda #$30
sta FT_MR_PULSE1_V
sta FT_MR_PULSE2_V
sta FT_MR_NOISE_V
lda #$80
sta FT_MR_TRI_V
jmp @noSkipM
@noPause:
;converting output values and sending them to the APU or into output buffer
lda FT_CH1_NOTE
cmp #63
bne @ch1note
lda #0
jmp @ch1cut
@ch1note:
clc
adc FT_CH1_NOTE_OFF
asl a
tax
lda FT_CH1_PITCH_OFF
pha
adc noteTable,x
sta FT_MR_PULSE1_L
pla
ora #$7f
bmi @ch1sign
lda #0
@ch1sign:
adc noteTable+1,x
.if(!FT_SFX_ENABLE)
cmp FT_PULSE1_PREV
beq @ch1prev
sta FT_PULSE1_PREV
.endif
sta FT_MR_PULSE1_H
@ch1prev:
lda FT_CH1_VOLUME
@ch1cut:
ora FT_CH1_DUTY
sta FT_MR_PULSE1_V
lda FT_CH2_NOTE
cmp #63
bne @ch2note
lda #0
jmp @ch2cut
@ch2note:
clc
adc FT_CH2_NOTE_OFF
asl a
tax
lda FT_CH2_PITCH_OFF
pha
adc noteTable,x
sta FT_MR_PULSE2_L
pla
ora #$7f
bmi @ch2sign
lda #0
@ch2sign:
adc noteTable+1,x
.if(!FT_SFX_ENABLE)
cmp FT_PULSE2_PREV
beq @ch2prev
sta FT_PULSE2_PREV
.endif
sta FT_MR_PULSE2_H
@ch2prev:
lda FT_CH2_VOLUME
@ch2cut:
ora FT_CH2_DUTY
sta FT_MR_PULSE2_V
lda FT_CH3_NOTE
cmp #63
bne @ch3note
lda #0
jmp @ch3cut
@ch3note:
clc
adc FT_CH3_NOTE_OFF
asl a
tax
lda FT_CH3_PITCH_OFF
pha
adc noteTable,x
sta FT_MR_TRI_L
pla
ora #$7f
bmi @ch3sign
lda #0
@ch3sign:
adc noteTable+1,x
sta FT_MR_TRI_H
lda FT_CH3_VOLUME
@ch3cut:
ora #$80
sta FT_MR_TRI_V
lda FT_CH4_NOTE
cmp #63
bne @ch4note
lda #0
jmp @ch4cut
@ch4note:
clc
adc FT_CH4_NOTE_OFF
and #$0f
eor #$0f
sta <FT_TEMP
lda FT_CH4_DUTY
asl a
and #$80
ora <FT_TEMP
sta FT_MR_NOISE_F
lda FT_CH4_VOLUME
@ch4cut:
ora #$f0
sta FT_MR_NOISE_V
dec FT_FRAME_CNT
lda FT_PAL_ADJUST ;for PAL mode decrease row length every fifth frame
bne @noSkipM
dec FT_FRAME_CNT
@noSkipM:
.if(FT_SFX_ENABLE)
ldx #FT_SFX_CH0 ;process all the sound effects streams
lda #FT_SFX_STREAMS
sta <FT_TEMP+3
@updateSfxChannels:
jsr FamiToneSfxUpdate
txa
clc
adc #FT_SFX_STRUCT_SIZE
tax
dec <FT_TEMP+3
bne @updateSfxChannels
lda FT_OUT_BUF ;now send data from output buffer to the APU
sta APU_PL1_VOL
lda FT_OUT_BUF+1
sta APU_PL1_LO
lda FT_OUT_BUF+2
cmp FT_PULSE1_PREV
beq @noUpdatePulse1
sta FT_PULSE1_PREV
sta APU_PL1_HI
@noUpdatePulse1:
lda FT_OUT_BUF+3
sta APU_PL2_VOL
lda FT_OUT_BUF+4
sta APU_PL2_LO
lda FT_OUT_BUF+5
cmp FT_PULSE2_PREV
beq @noUpdatePulse2
sta FT_PULSE2_PREV
sta APU_PL2_HI
@noUpdatePulse2:
lda FT_OUT_BUF+6
sta APU_TRI_LINEAR
lda FT_OUT_BUF+7
sta APU_TRI_LO
lda FT_OUT_BUF+8
sta APU_TRI_HI
lda FT_OUT_BUF+9
sta APU_NOISE_VOL
lda FT_OUT_BUF+10
sta APU_NOISE_LO
.endif
.if(FT_THREAD)
pla
sta FT_TEMP+1
pla
sta FT_TEMP
.endif
rts
;set envelopes of an instrument
;in: A instrument number 0..31
; X is offset of block of envelopes in the FamiTone's RAM page
;out: Y 0
setInstrument:
asl a ;A*8, every instrument takes 8 bytes
asl a
asl a
clc
adc FT_INSTRUMENT_L ;get instrument address into FT_TEMP
sta <FT_TEMP
lda #0
adc FT_INSTRUMENT_H
sta <FT_TEMP+1
lda #3 ;three envelopes for pulse and triangle channels
cpx #.lobyte(FT_CH4_ENVS)
bne @noNoise
lda #2 ;only two envelopes for noise, no pitch
@noNoise:
sta <FT_TEMP+2
ldy #0
clc
@loop:
lda (FT_TEMP),y ;get LSB of an envelope
sta FT_ENV_ADR_L,x
iny
lda (FT_TEMP),y ;get MSB
sta FT_ENV_ADR_H,x
iny
lda #0
sta FT_ENV_REPEAT,x ;reset repeat counter
sta FT_ENV_PTR,x ;reset envelope pointer
txa
adc #FT_ENV_STRUCT_SIZE
tax
dec <FT_TEMP+2
bne @loop
ldy #6
lda (FT_TEMP),y ;duty cycle
ldy #0
rts
;process channel
;in: X is offset of channel variables in the FamiTone's RAM page
; Y 0
;out: Carry is reset if the was new note
channelStep:
lda FT_CHN_REF_LEN,x ;check reference rows counter
beq @noRef ;if it is zero, there is no reference
dec FT_CHN_REF_LEN,x ;decrease rows counter
bne @noRef
lda FT_CHN_RETURN_L,x ;end of reference, return to previous pointer
sta <FT_TEMP
lda FT_CHN_RETURN_H,x
sta <FT_TEMP+1
jmp @noRepeatR
@noRef:
lda FT_CHN_REPEAT,x ;check pause counter
beq @noRepeat
dec FT_CHN_REPEAT,x ;decrease pause counter
sec ;no new note
rts
@noRepeat:
lda FT_CHN_PTR_L,x ;load channel pointer into temp
sta <FT_TEMP
lda FT_CHN_PTR_H,x
sta <FT_TEMP+1
@noRepeatR:
@readByte:
lda (FT_TEMP),y ;read byte of the channel
inc <FT_TEMP ;increase pointer
bne @1
inc <FT_TEMP+1
@1:
cmp #%01000000 ;if bits 7 or 6 are set, it is instrument, tag, or effect
bcs @special
sta FT_CHN_NOTE,x ;remember note code
@return:
lda <FT_TEMP ;store pointer from temp to the variable
sta FT_CHN_PTR_L,x
lda <FT_TEMP+1
sta FT_CHN_PTR_H,x
rts
@special:
cmp #%11000000 ;check if it is special tag
bcs @noInstr
cmp #%10000000 ;check if it is instrument
bcc @noPause
and #$3f
sta FT_CHN_REPEAT,x ;set up pause counter
jmp @return ;no new note, because Carry is set
@noPause:
and #$3f
sta FT_CHN_INSTRUMENT,x ;remember instrument number
jmp @readByte ;and read next byte
@noInstr:
cmp #%11111111 ;check if it is reference
bne @noEof
clc ;remember return address+3
lda <FT_TEMP
adc #3
sta FT_CHN_RETURN_L,x
lda <FT_TEMP+1
adc #0
sta FT_CHN_RETURN_H,x
lda (FT_TEMP),y ;read length of the reference (how many rows)
sta FT_CHN_REF_LEN,x
iny
lda (FT_TEMP),y ;read 16-bit absolute address of the reference
sta <FT_TEMP+2 ;remember in temp
iny
lda (FT_TEMP),y
sta <FT_TEMP+1
lda <FT_TEMP+2
sta <FT_TEMP
ldy #0
jmp @readByte ;and read next byte
@noEof:
cmp #%11111110 ;check if it is end of the channel
bne @effect
lda (FT_TEMP),y ;read two next bytes
sta <FT_TEMP+2
iny
lda (FT_TEMP),y
dey
sta <FT_TEMP+1 ;and set current pointer
lda <FT_TEMP+2
sta <FT_TEMP
jmp @readByte ;and read next byte
@effect:
and #$3f ;speed change
sta FT_SONG_SPEED
jmp @readByte
;process an envelope
;it does not matter which type an envelope is
;in: X offset of the envelope variables in the FamiTone's RAM page
envelopeStep:
lda FT_ENV_REPEAT,x ;check envelope repeat counter
beq @noRepeat ;if it is zero, process the envelope
dec FT_ENV_REPEAT,x ;otherwise decrement the counter
rts
@noRepeat:
lda FT_ENV_ADR_L,x ;load envelope address into temp
sta <FT_TEMP
lda FT_ENV_ADR_H,x
sta <FT_TEMP+1
ldy FT_ENV_PTR,x ;load envelope pointer
@readByte:
lda (FT_TEMP),y ;read byte of the envelope
iny ;increase pointer
ora #0
bpl @special ;if it is below 127, it is special code
clc ;otherwise it is an output value+192
adc #256-192
sta FT_ENV_VALUE,x
tya
sta FT_ENV_PTR,x ;remember the pointer and return
rts
@special:
cmp #127 ;if it is 127, it is end of an envelope
beq @loop
sta FT_ENV_REPEAT,x ;otherwise it is a value for repeat counter
tya
sta FT_ENV_PTR,x ;remember the value and return
rts
@loop:
lda (FT_TEMP),y ;load loop pointer
tay
jmp @readByte ;and read next byte
.if(FT_DPCM_ENABLE)
;set sample table pointer, only needed if DMC is used
;in: X,Y is address of a table that holds parameters for 12 samples
FamiToneSampleInit:
stx FT_DPCM_TABLE_L
sty FT_DPCM_TABLE_H
rts
;stop DMC sample if it plays
FamiToneSampleStop:
lda #%00001111
sta APU_SND_CHN
rts
;play DMC sample from table, used by music player and could be used externally
;in: A is number of sample, 0..11
FamiToneSampleStartM: ;for music (low priority)
ldx FT_DPCM_EFFECT
beq FamiToneSampleStartS
tax
lda APU_SND_CHN
and #16
beq @noEffect
rts
@noEffect:
sta FT_DPCM_EFFECT
txa
jmp FamiToneSampleStartS
FamiToneSampleStart: ;for sound effects (high priority)
ldx #1
stx FT_DPCM_EFFECT
FamiToneSampleStartS:
asl a ;get address in sample table
asl a
adc FT_DPCM_TABLE_L
sta <FT_TEMP
lda #0
adc FT_DPCM_TABLE_H
sta <FT_TEMP+1
lda #%00001111 ;stop DMC
sta APU_SND_CHN
ldy #0
lda (FT_TEMP),y ;sample offset
sta APU_DMC_START
iny
lda (FT_TEMP),y ;sample length
sta APU_DMC_LEN
iny
lda (FT_TEMP),y ;pitch and loop
sta APU_DMC_FREQ
lda #32 ;reset DAC counter
sta APU_DMC_RAW
lda #%00011111 ;start DMC
sta APU_SND_CHN
rts
.endif
.if(FT_SFX_ENABLE)
;init sound effects player, set pointer to data
;in: X,Y is address of sound effects data
FamiToneSfxInit:
stx FT_SFX_ADR_L ;remember pointer to the data
sty FT_SFX_ADR_H
ldx #FT_SFX_CH0 ;init all the streams
ldy #FT_SFX_STREAMS
@1:
lda #0
sta FT_SFX_REPEAT,x
sta FT_SFX_PTR_H,x
jsr FamiToneSfxClearBuf
txa
clc
adc #FT_SFX_STRUCT_SIZE
tax
dey
bne @1
rts
;play a sound effect
;in: A is a number of the sound effect
; X is offset of sound effect stream, should be FT_SFX_CH0..FT_SFX_CH3
FamiToneSfxStart:
asl a ;get address in effects list
tay
lda #0
sta FT_SFX_PTR_H,x ;this stops the effect
sta FT_SFX_REPEAT,x ;reset repeat counter and pointer offset
sta FT_SFX_OFF,x
jsr FamiToneSfxClearBuf
lda FT_SFX_ADR_L
sta <FT_TEMP
lda FT_SFX_ADR_H
sta <FT_TEMP+1
lda (FT_TEMP),y ;read effect pointer from the table
sta FT_SFX_PTR_L,x ;and remember it
iny
lda (FT_TEMP),y
sta FT_SFX_PTR_H,x ;this enables the effect
rts
;clear output buffer of a sound effect
;in: A is 0
; X is offset of sound effect stream
FamiToneSfxClearBuf:
sta FT_SFX_BUF+6,x
lda #$30
sta FT_SFX_BUF,x
sta FT_SFX_BUF+3,x
sta FT_SFX_BUF+9,x
rts
;update one sound effect stream
;in: X is offset of sound effect stream
FamiToneSfxUpdate:
lda FT_SFX_REPEAT,x ;check if repeat counter is not zero
beq @noRepeat
dec FT_SFX_REPEAT,x ;decrement and return
jmp @updateBuf ;just mix with output buffer
@noRepeat:
lda FT_SFX_PTR_H,x ;check if MSB of pointer is not zero
bne @sfxActive
rts ;return otherwise, no active effect
@sfxActive:
sta <FT_TEMP+1 ;load effect pointer into temp
lda FT_SFX_PTR_L,x
sta <FT_TEMP
ldy FT_SFX_OFF,x
@readByte:
lda (FT_TEMP),y ;read byte of effect
iny
cmp #$10 ;if it is less than $10, it is register write
bcc @getData
cmp #$ff ;if it is $ff, it is end of the effect
bcs @eof
adc #256-$10 ;otherwise it is number of repeats +$10
sta FT_SFX_REPEAT,x
tya
sta FT_SFX_OFF,x
jmp @updateBuf
@eof:
lda #0
sta FT_SFX_PTR_H,x
jmp @updateBuf
@getData:
stx <FT_TEMP+2 ;it is register write
adc <FT_TEMP+2 ;get offset in the effect output buffer
tax
lda (FT_TEMP),y ;read value
iny
sta FT_SFX_BUF,x ;store into output buffer
ldx <FT_TEMP+2
jmp @readByte ;and read next byte
@updateBuf:
lda FT_OUT_BUF ;now compare effect output buffer with main output buffer
and #$0f ;if volume of pulse 1 of effect is higher than of main buffer
sta <FT_TEMP ;overwrite pulse 1 of main buffer with one from effect buffer
lda FT_SFX_BUF,x
and #$0f
cmp <FT_TEMP
bcc @noPulse1
lda FT_SFX_BUF,x
sta FT_OUT_BUF
lda FT_SFX_BUF+1,x
sta FT_OUT_BUF+1
lda FT_SFX_BUF+2,x
sta FT_OUT_BUF+2
@noPulse1:
lda FT_OUT_BUF+3 ;same for pulse 2
and #$0f
sta <FT_TEMP
lda FT_SFX_BUF+3,x
and #$0f
cmp <FT_TEMP
bcc @noPulse2
lda FT_SFX_BUF+3,x
sta FT_OUT_BUF+3
lda FT_SFX_BUF+4,x
sta FT_OUT_BUF+4
lda FT_SFX_BUF+5,x
sta FT_OUT_BUF+5
@noPulse2:
lda FT_SFX_BUF+6,x ;overwrite triangle of main output buffer if it is active
beq @noTriangle