forked from bruddog/Tecmo_Super_Bowl_NES_Disassembly
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Bank28_sound_engine.asm
6696 lines (5930 loc) · 387 KB
/
Bank28_sound_engine.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
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
_F{_SOUND_ENGINE_CONSTANTS
.ENUM $00
SQ_0_EFFECT_CH_ID .dsb 1
SQ_1_EFFECT_CH_ID .dsb 1
TRI_EFFECT_CH_ID .dsb 1
NOISE_EFFECT_CH_ID .dsb 1
SQ_0_MUSIC_CH_ID .dsb 1
SQ_1_MUSIC_CH_ID .dsb 1
TRI_MUSIC_CH_ID .dsb 1
NOISE_MUSIC_CH_ID .dsb 1
.ENDE
NOTE_TIMER_ACTIVE_BITMASK = $80
NOTE_TIMER_INACTIVE_BITMASK = $7F
CLEAR_ALL_NOTE_STATUS_BITMASK = $4F
REST_NOTE_STATUS_BITMASK = $20
SQUARE_CH_SWEEP_ENABLED_BITMASK = $10
BLEND_NOTES_ON_STATUS_BITMASK = $02
BLEND_NOTES_OFF_STATUS_BITMASK = $FD
_F}_SOUND_ENGINE_CONSTANTS
_F{_SOUND_ENGINE_MACROS
.MACRO PLAY_VOICE_DMC_SAMPLE[addr_len] addr, len
sample_addr = ((addr - $C000)>>6)
sample_length = (len/16)
LDA #$0F ; SET APU STATUS = DMC BYTES RE-MAINING =0, SILENCE DMC WHEN DONE
STA APU_STATUS ;
STA APU_DMC_IRQ_FREQ ; CLEAR DMC IRQ AND DMC LOOP FLAG, SET DMC ENCODING RATE = 33.1KhZ
LDA #sample_addr ; SET SAMPLE ADDRESS = $C000+ (0X40 * #$95) = 0X3E540
STA DMC_START ;
LDA #sample_length ; SET DMC SAMPLE LENGTH = 0X10 * 0X3F = 0X3F0 BYTES
STA DMC_LEN ;
LDA #$1F ; SET APU STATUS = START DMC SAMPLE
STA APU_STATUS ;
.ENDM
.MACRO PLAY_DRUM_DMC_SAMPLE[addr_len] addr, len
sample_addr = ((addr - $C000)>>6)
sample_length = (len/16)
LDA #$0F ; SET APU STATUS = DMC BYTES RE-MAINING =0, SILENCE DMC WHEN DONE
STA APU_STATUS ;
STA APU_DMC_IRQ_FREQ ; CLEAR DMC IRQ AND DMC LOOP FLAG, SET DMC ENCODING RATE = 33.1KhZ
LDA #$F0 ; SET DMC OUTPUT LEVEL
STA APU_DMC_OUPUT_LEV ;
LDA #sample_addr ; SET SAMPLE ADDRESS = $C000+ (0X40 * A) = 0X3E0C0
STA DMC_START ;
LDA #sample_length ; SET DMC SAMPLE LENGTH = 0X10 * 0X20 = 0X200 BYTES
STA DMC_LEN ;
LDA #$1F ; SET APU STATUS = START DMC SAMPLE
STA APU_STATUS ;
.ENDM
.MACRO PLAY_DRUM_DMC_SAMPLE_VARIABLE_ENCODE[addr_len_encode] addr, len, encode
sample_addr = ((addr - $C000)>>6)
sample_length = (len/16)
LDA #$0F ; SET APU STATUS = DMC BYTES RE-MAINING =0, SILENCE DMC WHEN DONE
STA APU_STATUS ;
LDA #encode ; CLEAR DMC IRQ AND DMC LOOP FLAG, SET DMC ENCODING RATE = 33.1KhZ
STA APU_DMC_IRQ_FREQ ;
LDA #$F0 ; SET DMC OUTPUT LEVEL
STA APU_DMC_OUPUT_LEV ;
LDA #sample_addr ; SET SAMPLE ADDRESS = $C000+ (0X40 * A) = 0X3E2C0
STA DMC_START ;
LDA #sample_length ; SET DMC SAMPLE LENGTH = 0X10 * 0X1F = 0X1F0 BYTES
STA DMC_LEN ;
LDA #$1F ; SET APU STATUS = START DMC SAMPLE
STA APU_STATUS ;
.ENDM
_F}_SOUND_ENGINE_MACROS
.BASE $8000
.ORG $8000
_F{_SOUND_ENGINE ; DONE
SOUND_ENGINE_START: ; SOUND ENGINE START
LDA SOFT_APU_STATUS ; DOES SOFT APU STATUS = CHANNEL(S) ENABLED
BNE @check_sound_id ; YES->CHECK FOR SOUND TO PLAY
@stop_dmc_enable_all_channels:
LDA #$0F ; SET APU STATUS = NOISE,TRIANGLE AND PULSE CHANNELS = ENABLED , DMC DISABLED
STA APU_STATUS ;
STA SOFT_APU_STATUS ; SET SOFT APU STATUS = NOISE,TRIANGLE AND PULSE CHANNELS = ENABLED , DMC DISABLED
@check_sound_id: ; CHECK FOR SOUND TO PLAY
LDA SOUND_ID_TO_PLAY ; DOES SOUND TO PLAY = STOP SONG
CMP #STOP_SONG_SOUND_ID ;
BNE @check_for_song_after_int ; NO->CHECK FOR SONG/EFFECT ADDRESS TO PROCESS
@stop_song_id: ;
LDA #$00 ;
STA INT_SONG_PLAYING_BOOLEAN ; SET INTERCEPTION MUSIC PLAYING = FALSE
STA ON_FIELD_SONG_TO_PLAY_AFTER_INT
JMP @set_song_effect_data_bank ; JUMP->SET SONG/EFFECT BANK
@check_for_song_after_int: ; CHECK FOR SONG/EFFECT ADDRESS TO PROCESS
LDA ON_FIELD_SONG_TO_PLAY_AFTER_INT ; IS SONG TO PLAY AFTER INTERCEPTION = NONE
BEQ @set_song_effect_data_bank ; YES->SET SONG/EFFECT BANK
LDY INT_SONG_PLAYING_BOOLEAN ; IS INTERCEPTION MUSIC PLAYING = TRUE
BNE @clear_sound_to_play ; YES->SET SOUND TO PLAY TO NONE
@set_sound_id_to_int_song_to_play:
STA SOUND_ID_TO_PLAY ; SET SOUND TO PLAY = NONE
LDA #$00 ; SET SONG TO PLAY AFTER INTERCEPTION = NONE
STA ON_FIELD_SONG_TO_PLAY_AFTER_INT ;
JMP @set_song_effect_data_bank ; JUMP->SET SONG/EFFECT BANK
@clear_sound_to_play: ; SET SOUND TO PLAY TO NONE
LDA #$00 ; SET SOUND TO PLAY = NO NEW SOUND TO PLAY
STA SOUND_ID_TO_PLAY ;
@set_song_effect_data_bank: ; SET SONG/EFFECT BANK
LDA #$07 ;
ORA SOFT_MODE_8000 ;
STA SOFT_BANK_SELECT ; SET SOFT BANK SELECT = $A000 BANK
STA BANK_SELECT_MMC3 ; SET MMC3 BANK SELECT = $A000 BANK
LDY SOUND_ID_TO_PLAY ; IS SOUND TO PLAY >= SONG
CPY #START_OF_SONG_IDS ;
BCS @check_for_next_bank_of_music ; YES->CHECK FOR NEW SONG BANK
@set_song_data_bank_to_current_bank:
LDY CURRENT_SOUND_DATA_BANK ;
STY BANK_DATA_MMC3 ; SET $A000 BANK = CURRENT_SOUND_A000_BANK
JMP @check_silence_all_channels ;
@check_for_next_bank_of_music: ; SET BANK FOR SOUNDS (0X20-0X3D)
CPY #P2_PROBOWL_SONG ; IS SOUND TO PLAY >= SOUND 61?
BCS @set_A000_bank_to_song_bank_1 ;
@set_A000_bank_to_song_bank_2:
LDY #SONG_EFFECTS_DATA_BANK_2 ; SET $A000 BANK = BANK 30
STY BANK_DATA_MMC3 ;
STY CURRENT_SOUND_DATA_BANK ; SET CURRENT_SOUND_A000_BANK = BANK 30
JMP @check_silence_all_channels ;
@set_A000_bank_to_song_bank_1: ; SET BANK FOR SOUNDS (>=0X3D)
LDY #SONG_EFFECTS_DATA_BANK_1 ; SET $A000 BANK = BANK 29
STY BANK_DATA_MMC3 ;
STY CURRENT_SOUND_DATA_BANK ; SET CURRENT_SOUND_A000_BANK = BANK 29
@check_silence_all_channels: ;
LDA SILENCE_ALL_CHANNELS_BOOLEAN ; DOES SILENCE ALL CHANNELS = TRUE
BEQ @check_for_fade_out_no_new_song ; NO-> CHECK FOR SOUND TO PLAY
LDA SOUND_ID_TO_PLAY ; DOES SOUND TO PLAY = “READY” SOUND
CMP #READY_DMC ;
BEQ @check_for_fade_out_no_new_song ; YES-> CHECK FOR SOUND TO PLAY
RTS ; RETURN
@check_for_fade_out_no_new_song: ; CHECK FOR SOUND TO PLAY
LDA SOUND_ID_TO_PLAY ; DOES SOUND TO PLAY = NO NEW SOUND TO PLAY
BEQ PROCESS_CURRENT_MUSIC_PLAYING ; YES->PROCESS CURRENT SOUND
CMP #FADE_OUT ; DOES SOUND TO PLAY = FADE OUT SONG
BNE @set_song_effect_address ; NO-> LOAD EFFECT/SONG ADDRESS
@else_do_fadeout:
JSR SET_FADE_OUT_TIMERS ; FADEOUT SOUND()
@set_song_effect_address: ; LOAD EFFECT/SONG ADDRESS
ASL ; SHIFT SOUND ID SINCE EACH ADDR IS TWO BYTES
TAY ;
LDA SOUND_EFFECT_SONG_DATA_ADDR_TABLE,Y
STA CURR_SONG_ADDR ;
LDA SOUND_EFFECT_SONG_DATA_ADDR_TABLE+1,Y
STA CURR_SONG_ADDR+1 ;
@clear_sound_to_play_and_volume_offset:
LDY #$00 ; SET SOUND TO PLAY = NO NEW SOUND TO PLAY
STY SOUND_ID_TO_PLAY ;
STY TEMP_VOLUME_OFFSET ;
STY SILENCE_ALL_CHANNELS_BOOLEAN ;
_WHILE PLUS ; CHECK FOR LOAD CHANNEL INFORMATION AND ADDR
LDA (CURR_SONG_ADDR),Y ; DOES DATA = END OF CHANNELS?
BMI PROCESS_CURRENT_MUSIC_PLAYING ; YES->PROCESS CHANNEL ADDRESSES
ASL ; LOAD CHANNEL ID
TAX ;
LDA MUSIC_CHANNEL_ID_BITMASK_TABLE,X ; SET CHANNEL ID FROM CHANNEL ID TABLE
ORA CHANNEL_ACTIVE ;
STA CHANNEL_ACTIVE ; SET CHANNEL AS ACTIVE IN ACTIVE CHANNELS
@clear_various_arrays: ; CLEAR CHANNEL ARRAYS
LDA #$00 ;
STA CURRENT_CHANNEL_PITCH_ADJUST[],X ; SET CURRENT CHANNEL PITCH = ZERO
STA CURRENT_CHANNEL_NOTE_STATUS[],X ; SET CURRENT CHANNEL STATUS = ZERO
STA CURRENT_CHANNEL_FADE_OUT_TIMER[],X ;
STA CURRENT_CHANNEL_FADE_OUT_RELOAD[],X ;
STA CURRENT_CHANNEL_NOTE_TIMER[],X ; SET CURRENT CHANNEL TIMER VALUE = ZERO
STA CURRENT_CH_VOLUME[],X ; SET CURRENT CHANNEL VOLUME = ZERO
@set_note_duration_to_one_tick:
LDA #$01 ;
STA CURRENT_NOTE_DURATION[],X ;
@save_current_song_addr:
INY ; CURRENT SOUND DATA ADDR ++
LDA (CURR_SONG_ADDR),Y ; SET CURRENT CHANNEL SOUND DATA ADDR = DATA ADDR
STA CURRENT_CH_SONG_ADDR[],X ;
INY ;
LDA (CURR_SONG_ADDR),Y ;
STA CURRENT_CH_SONG_ADDR[]+1,X ;
LDA #$05 ; SET CURRENT CHANNEL RETURN ADDR OFFSET = 5
STA CURRENT_SECTION_OFFSET_FOR_RETURN[],X ;
INY ;
_END_WHILE
PROCESS_CURRENT_MUSIC_PLAYING: ; PROCESS CHANNEL ADDRESSES
LDA #<MUSIC_DATA_ADDRESSES_RAM_START ; SET CURRENT CHANNEL DATA ADDR = $0741
STA CURR_SONG_ADDR ;
LDA #>MUSIC_DATA_ADDRESSES_RAM_START ;
STA CURR_SONG_ADDR+1 ;
LDA #$00 ; SET CHANNEL INDEX = FIRST CHANNEL = SQUARE
STA CURR_SOUND_CHANNEL_INDEX ;
_WHILE NOT_EQUAL ; CHECK CHANNELS LOOP
LDA CHANNEL_ACTIVE ; LOAD CHANNEL ID
LSR ;
BCC @save_channel_status ; IS CHANNEL ACTIVE? NO
ORA #$80 ; SET CHANNEL STATUS = ACTIVE
@save_channel_status: ;
STA CHANNEL_ACTIVE ; SAVE CHANNEL STATUS
@check_channel_status:
BCC @set_to_next_channel ; CHANNEL ACTIVE ? NO->SET TO NEXT CHANNEL
@time_remaining_for_current_note:
LDX CURR_SOUND_CHANNEL_INDEX ; IS THERE TIME REMANING FOR CURRENT NOTE?
DEC CURRENT_NOTE_DURATION[],X ;
BNE @do_volume_envelope ; YES->PROCESS VOLUME ENVELOPE
@check_if_note_blending_on:
LDA CURRENT_CHANNEL_NOTE_STATUS[],X ; DOES CHANNEL STATUS = BLEND NOTES
AND #$02 ;
BNE @reset_check_vibrato_do_next_data ; YES->PROCESS CURRENT NOTE OR COMMAND
@reset_volume_envelope_if_off: ; RESET VOLUME ENVELOPE TO START OF ENVELOPE
LDA #$00 ; CLEAR CUR CH VOLUME ENVELOPE DATA OFFSET = START OF ENVELOPE
STA CURRENT_VOLUME_ENVELOPE_INDEX[],X ;
LDA #$01 ; SET CUR CH VOLUME ENVELOPE TIME/LENGTH = 1
STA CURRENT_VOLUME_ENVELOPE_DURATION[],X ;
LDA #$00 ; CLEAR VIBRATO OFFSET = 0
STA CURRENT_CHANNEL_VIBRATO_OFFSET[],X ;
@reset_check_vibrato_do_next_data : ; PROCESS CURRENT NOTE OR COMMAND
LDA #$01 ; SET VIBRATO TIMER = 1 = CHECK FOR VIBRATO
STA CURRENT_CHANNEL_VIBRATO_TIMER[],X ;
JSR PROCESS_NOTE_OR_COMMAND_START ; PROCESS NOTE OR COMMAND()
LDX CURR_SOUND_CHANNEL_INDEX ; LOAD CURRENT CHANNEL INDEX
@do_volume_envelope: ; PROCESS VOLUME ENVELOPE
DEC CURRENT_VOLUME_ENVELOPE_DURATION[],X ; CUR CH VOLUME ENVELOPE LENGTH--
BNE @update_channel_volume ; AT END OF ENVELOPE? NO->UPDATE CHANNEL VOLUME
@copy_volume_envelope_rom_addr_to_mem:
LDA CURRENT_VOLUME_ENVELOPE_ADDR[],X ; LOAD VOLUME ENVELOPE ADDR
STA VOLUME_ENEVELOPE_ADDR ;
LDA CURRENT_VOLUME_ENVELOPE_ADDR[] + 1,X ;
STA VOLUME_ENEVELOPE_ADDR+1 ;
@set_volume_envelope_duration:
LDA CURRENT_VOLUME_ENVELOPE_INDEX[],X ; LOAD CUR CH VOLUME ENVELOPE DATA OFFSET
TAY ;
LDA (VOLUME_ENEVELOPE_ADDR ),Y ; SET CUR CH VOLUME ENVELOPE LENGTH = DATA VALUE
STA CURRENT_VOLUME_ENVELOPE_DURATION[],X ;
@set_volume_envelope_volume_value:
INY ; DATA ADDR++
LDA (VOLUME_ENEVELOPE_ADDR ),Y ; SET CUR CH VOLUME ENVELOPE VOLUME LEVEL = DATA VALUE
STA CURRENT_VOLUME_ENVELOPE_VALUE[],X ;
INY ; DATA ADDR++
TYA ; SAVE CUR CH VOLUME ENVELOPE DATA OFFSET
STA CURRENT_VOLUME_ENVELOPE_INDEX[],X ;
@update_channel_volume: ; UPDATE CHANNEL VOLUME
JSR UPDATE_CHANNEL_VOLUME_AND_VIBRATO_EFFECT
@set_to_next_channel: ; SET TO NEXT CHANNEL
CLC ; CURRENT CHANNEL SOUND DATA ADDR + OFFSET TO NEXT CHANNEL DATA
LDA #$06 ;
ADC CURR_SONG_ADDR ;
STA CURR_SONG_ADDR ;
@update_sound_channel_index:
LDA #$02 ; CHANNEL INDEX = CHANNEL INDEX + OFFSET TO NEXT CHANNEL
ADC CURR_SOUND_CHANNEL_INDEX ;
STA CURR_SOUND_CHANNEL_INDEX ;
CMP #$10 ; HAVE WE CHECKED ALL CHANNELS?
_END_WHILE
active_channel_mask = SOUND_ENGINE_LOCAL_1
LDA #$00 ; CLEAR CURRENT SOUND/SONG ADDR
STA CURR_SONG_ADDR ;
STA CURR_SONG_ADDR+1 ;
STA APU_CHANNEL_OFFSET ; CLEAR APU CHANNEL OFFSET
LDA #$03 ; SET CHANNELS TO CHECK = 4 (SQUARE 0, SQUARE 1, TRIANGLE, NOISE)
STA SOUND_CHANNELS_LEFT ;
LDA #$11 ; SET ACTIVE CHANNEL MASK (0X10= MUSIC, 0X01 = EFFECT)
STA active_channel_mask ;
_WHILE PLUS ; CHECK CHANNEL LOOP
LDA CHANNEL_ACTIVE ; IS CURRENT SOUND CHANNEL ACTIVE FOR MUSIC OR EFFECT?
AND active_channel_mask ;
BEQ @set_to_next_channel ; NO->GO TO NEXT SOUND CHANNEL
@check_if_effect_channel:
AND #$0F ; IS CURRENT SOUND CHANNEL = EFFECT?
BNE @set_duty_value ; YES->SET CHANNEL APU FREQ AND CHECK FOR SWEEP
@update_song_addr
CLC ; SET APU DATA OFFSET = MUSIC CHANNEL OFFSET
LDA #$08 ;
ADC CURR_SONG_ADDR ;
STA CURR_SONG_ADDR ;
@set_duty_value: ; SET CHANNEL APU FREQ AND CHECK FOR SWEEP
LDY CURR_SONG_ADDR ; LOAD CURRENT APU CHANNEL DUTY DATA
LDA CURRENT_CHANNEL_DUTY_VALUE[],Y ;
LDX APU_CHANNEL_OFFSET ; LOAD CURRENT APU CHANNEL OFFSET
STA APU_CUR_CH_VOL[],X ; SET APU DUTY/LENGTH REGISTER = CHANNEL DUTY VALUE
@check_for_sweep_enabled:
LDA CURRENT_CHANNEL_NOTE_STATUS[],Y ; DOES CURRENT APU CHANNEL FLAGS = SWEEP UP
AND #SQUARE_CH_SWEEP_ENABLED_BITMASK ;
BNE @check_for_note_active ; NO-> SET APU TIMER
@set_nes_apu_sweep_flag:
LDA #$08 ; SET APU SWEEP REGISTER NEGATE FLAG = SUBTRACT FROM PERIOD, SWEEPING TOWARD HIGHER FREQUENCIES
STA APU_CUR_SQ_CH_SWEEP[],X ;
@check_for_note_active: ; CHECK FOR SET APU TIMER/FREQ
LDA CURRENT_CHANNEL_NOTE_STATUS[],Y ; IS CURRENT APU CHANNEL FLAG DATA = TIMER ACTIVE
AND #NOTE_TIMER_ACTIVE_BITMASK ;
BNE @set_to_next_channel ; NO->GO TO NEXT SOUND CHANNEL
@save_note_period_low_value:
LDA CURRENT_CH_NOTE_FREQ_VAL[],Y ; SET APU TIMER LOW VALUE = CURRENT APU CHANNEL TIMER LOW VALUE
STA APU_CUR_CH_PERIOD_VALUE[],X ;
LDA CURRENT_CH_NOTE_FREQ_VAL[] + 1,Y ; LOAD CURRENT APU CHANNEL TIMER HIGH VALUE
ORA #$18 ; SET BASE LENGTH OF LENGTH COUNTER = WHOLE NOTE
CPX #$07 ; IS CHANNEL == SQUARE WAVE CHANNEL?
BCS @save_high_period_value ; NO-> SET LENGTH COUNTER AND TIMER HIGH VALUE (A= VALUE)
@set_note_timer
PHA ; SAVE LENGTH AND TIMER HIGH VALUE
LDA CURRENT_CHANNEL_NOTE_STATUS[],Y ; DOES CURRENT APU CHANNEL FLAG DATA = SWEEP ENABLED?
AND #SQUARE_CH_SWEEP_ENABLED_BITMASK ;
BEQ @check_for_sq_wave_timer_done: ; NO->CHECK FOR SQUARE WAVE TIMER DONE
@set_timer_to_done
LDA #$FF ; YES SET TIMER DONE = 0XFF
STA CURRENT_CHANNEL_NOTE_TIMER[],X ;
PLA ; RESTORE SQUARE WAVE LENGTH AND TIMER HIGH VALUE
JMP @save_high_period_value ; JUMP->SET LENGTH COUNTER AND TIMER HIGH VALUE (A= VALUE)
@check_for_sq_wave_timer_done: ; CHECK FOR SQUARE WAVE TIMER DONE
PLA ; SQUARE WAVE LENGTH AND TIMER HIGH VALUE > END VALUE?
CMP CURRENT_CHANNEL_NOTE_TIMER[],X ;
BEQ @set_to_next_channel ; YES->GO TO NEXT SOUND CHANNEL
STA CURRENT_CHANNEL_NOTE_TIMER[],X ; SAVE NEW TIMER VALUE
@save_high_period_value: ; SET LENGTH COUNTER AND TIMER HIGH VALUE (A= VALUE)
STA APU_CUR_CH_PERIOD_VALUE[]+1,X ; SET APU LENGTH COUNTER AND TIMER HIGH VALUE = LENGTH AND TIMER HIGH VALUE
LDA CURRENT_CHANNEL_NOTE_STATUS[],Y ; SET CURRENT APU CHANNEL FLAG DATA = TIMER ACTIVE
ORA #NOTE_TIMER_ACTIVE_BITMASK ;
STA CURRENT_CHANNEL_NOTE_STATUS[],Y ;
@set_to_next_channel: ; GO TO NEXT SOUND CHANNEL
CLC ; = CURRENT ADDR + LENGTH OF SONG ADDR PTR
LDA #$02 ;
ADC CURR_SONG_ADDR+1 ;
STA CURR_SONG_ADDR ;
STA CURR_SONG_ADDR+1 ;
LDA #$04 ; =SOFT APU CHANNEL OFFSET += OFFSET TO NEXT CHANNEL *** odd adding order
ADC APU_CHANNEL_OFFSET ;
STA APU_CHANNEL_OFFSET ;
ASL active_channel_mask ; SHIFT CHANNEL MASK TO NEXT CHANNEL
DEC SOUND_CHANNELS_LEFT ; CHANNELS TO CHECK --
_END_WHILE ; DONE? NO->CHECK CHANNEL LOOP
LDA SILENCE_ALL_CHANNELS_BOOLEAN ; DOES SILENCE ALL CHANNELS = TRUE
BEQ @exit_sound_engine ; YES->RETURN FROM MAIN SOUND LOOP
@silence_all_channels: ; SILENCE ALL CHANELS
LDA #$00 ; SET APU STATUS = ALL CHANNELS OFF, LENGTH LEFT =0
STA APU_STATUS ;
STA SOFT_APU_STATUS ; SET SOFT APU STATUS = ALL CHANNELS OFF, LENGTH LEFT =0
@exit_sound_engine: ; RETURN FROM MAIN SOUND LOOP
RTS ; RETURN
PROCESS_NOTE_OR_COMMAND_START: ; PROCESS NOTE OR NOTE COMMAND
LDA CURRENT_CHANNEL_NOTE_STATUS[],X ; SET CHANNEL STATUS = TIMER INACTIVE, SWEEP DISABLED, NO REST NOTE
AND #CLEAR_ALL_NOTE_STATUS_BITMASK ;
STA CURRENT_CHANNEL_NOTE_STATUS[],X ;
@copy_song_addr_to_zp_temp:
LDA CURRENT_CH_SONG_ADDR[],X ; SAVE CURRENT CHANNEL DATA ADDR IN TEMP ADDR
STA CURR_SONG_CHANNEL_ADDR ;
LDA CURRENT_CH_SONG_ADDR[]+1,X ;
STA CURR_SONG_CHANNEL_ADDR+1 ;
LDY #$00 ;
PROCESS_NOTE_OR_COMMAND_LOOP: ; CHECK FOR PITCH/LENGTH/COMMAND DATA LOOP
save_note_length = SOUND_ENGINE_LOCAL_1 ; 8 bit
note_frequency_value = SOUND_ENGINE_LOCAL_2 ; 16 bit
START_OF_MUSIC_COMMANDS_BYTECODES = $E0
REST_NOTE_VALUE = $0C
LDA (CURR_SONG_CHANNEL_ADDR),Y ; IS DATA = COMMAND/NOT LENGTH?
BPL @save_note_pitch ; NO->GET NOTE PITCH
@check_for_command:
INY ; DATA ADDR++
CMP #START_OF_MUSIC_COMMANDS_BYTECODES ; IS COMMAND = EFFECT?
BCS DO_SONG_DATA_COMMAND ; YES->PROCESS EFFECT
@set_note_length: ; DATA IS LENGTH OF NOTE
AND #$3F ; CONVERT DATA VALUE TO TABLE INDEX
TAX ;
LDA NOTE_LENGTH_TABLE,X ; GET NOTE LENGTH VALUE FROM NOTE LENGTH TABLE
LDX CURR_SOUND_CHANNEL_INDEX ;
STA CURRENT_NOTE_DURATION[],X ; SAVE NOTE LENGTH IN CURRENT CHANNEL CURRENT NOTE LENGTH
STA CURRENT_NOTE_DURATION[]+1,X ; SAVE NOTE LENGTH IN CURRENT CHANNEL PREVIOUS NOTE LENGTH
JMP PROCESS_NOTE_OR_COMMAND_LOOP ; JUMP->CHECK FOR PITCH/LENGTH/COMMAND DATA LOOP
@save_note_pitch: ; GET NOTE PITCH
INY ; DATA ADDR++
STA save_note_length ; SAVE NOTE LENGTH
TYA ;
CLC ;
ADC CURR_SONG_CHANNEL_ADDR ; UPDATE CURRENT CHANNEL ADDR AND TEMP ADDR
STA CURRENT_CH_SONG_ADDR[],X ;
LDA #$00 ;
ADC CURR_SONG_CHANNEL_ADDR+1 ;
STA CURRENT_CH_SONG_ADDR[]+1,X ;
@check_if_note_is_rest:
LDA save_note_length ; DOES NOTE = NO NOTE/REST?
CMP #REST_NOTE_VALUE ;
BNE @get_note_period: ; NO-> NOTE IS NOTE A REST
@set_channel_status_rest_note:
LDA CURRENT_CHANNEL_NOTE_STATUS[],X ; SET CHANNEL STATUS = NOTE IS REST = SILENCE
ORA #REST_NOTE_STATUS_BITMASK ;
STA CURRENT_CHANNEL_NOTE_STATUS[],X ;
BNE @save_note_length ; BRANCH->SET NOTE LENGTH
@get_note_period: ; NOTE IS NOT A REST
ASL ; CONVERT TEMP PITCH DATA TO TABLE INDEX
TAX ;
@save_frequency_value:
LDA NOTE_FREQUENCY_LOOKUP_TABLE,X ; LOAD NOTE PERIOD VALUE FROM PITCH/FREQUENCY TABLE
STA note_frequency_value ;
LDA NOTE_FREQUENCY_LOOKUP_TABLE+1,X ;
STA note_frequency_value+1 ;
@check_for_pitch_adjust:
LDX CURR_SOUND_CHANNEL_INDEX ;
LDA CURRENT_CHANNEL_PITCH_ADJUST[],X ; LOAD PITCH ADJUST VALUE
BPL @save_pitch_value_high_byte ; IS POSITIVE? YES->ADD PITCH SHIFT VALUE (A= VALUE)
@do_pitch_adjust:
AND #$7F ; = ABS(PITCH ADJUST VALUE)
STA PITCH_ADJUST_VALUE ;
LDA note_frequency_value ; = NOTE PERIOD VALUE
SEC ;
SBC PITCH_ADJUST_VALUE ; - PITCH ADJUST
STA CURRENT_CH_NOTE_FREQ_VAL[],X ;
LDA note_frequency_value+1 ;
BCS @save_pitch_value_low_byte ;
SBC #$00 ;
BPL @save_pitch_value_low_byte ;
@save_pitch_value_high_byte: ; ADD AND SAVE PITCH SHIFT VALUE (A= VALUE)
CLC ;
ADC note_frequency_value ;
STA CURRENT_CH_NOTE_FREQ_VAL[],X ;
LDA note_frequency_value+1 ;
ADC #$00 ;
@save_pitch_value_low_byte: ; SAVE LOW BYTE OF PITCH VALUE
STA CURRENT_CH_NOTE_FREQ_VAL[] + 1,X ;
@save_note_length: ; SET NOTE LENGTH
LDA CURRENT_NOTE_DURATION[]+1,X ; CURRENT NOTE LENGTH = PREVIOUS NOTE LENGTH
STA CURRENT_NOTE_DURATION[],X ;
RTS ; RETURN TO PROCESS CHANNEL COMMAND
DO_SONG_DATA_COMMAND: ; PROCESS MUSIC EFFECT COMMAND
AND #$1F ; CONVERT COMMAND TO TABLE INDEX
ASL ;
TAX ;
LDA SOUND_EFFECTS_TABLE,X ; LOAD TEMP EFFECT ADDR FROM EFFECT ADDR TABLE
STA CURR_SOUND_COMMAND_ADDR ; AND SAVE IN $F6/$F7
LDA SOUND_EFFECTS_TABLE+1,X ;
STA CURR_SOUND_COMMAND_ADDR +1 ;
LDX CURR_SOUND_CHANNEL_INDEX ; LOAD CHANNEL INDEX
JMP (CURR_SOUND_COMMAND_ADDR) ; JUMP-> EFFECT OR DMC ADDR
_F}_SOUND_ENGINE
_F{_SOUND_ENGINE_COMMAND_FUNCTION_TABLE ; DONE
SOUND_EFFECTS_TABLE:
.WORD SET_VOLUME_ENVELOPE_COMMAND ;0XE0 SET VOLUME ENVELOPE
.WORD SET_INT_MUSIC_PLAYING_COMMAND ;0xE1 SET INTERCEPTION MUSIC PLAYING
.WORD SET_DUTY_VOLUME_LENGTH_COMMAND ;0xE2 SET DUTY CYCLE, LENGTH COUNTER, VOLUME TYPE
.WORD SET_VOLUME_COMMAND ;0xE3 SET VOLUME
.WORD ENABLE_SWEEP_EFFECT_COMMAND ;0xE4 ENABLE SWEEP AND SET SWEEP TYPE
.WORD ADJUST_PITCH_COMMAND ;0xE5 ADJUST PITCH
.WORD DO_NOTHING_MUSIC_CHANNEL_COMMAND ;0xE6 SKIP TO NEXT BYTE
.WORD SET_INT_MUSIC_NOT_PLAYING_COMMAND ;0xE7 SET INTERCEPTION MUSIC NOT PLAYING
.WORD JUMP_TO_NEW_SONG_ADDR_COMMAND ;0xE8 JUMP TO (ADDR)
.WORD DO_MUSIC_SECTION_AND_RETURN_COMMAND ;0xE9 DO MUSIC SECTION (ADDR)
.WORD RETURN_FROM_SECTION_COMMAND ;0XEA RETURN FROM MUSCICTION SECTION
.WORD LOOP_SECTION_COMMAND ;0XEB LOOP (# OF TIMES TO LOOP)
.WORD CHECK_FOR_END_OF_LOOP_COMMAND ;0XEC CHECK FOR END OF LOOP
.WORD TURN_ON_VIBRATO_COMMAND ;0XED TURN ON VIBRATO
.WORD ENABLE_ALL_CHANNELS_SILENCE_DMC_COMMAND;0XEE ENABLE ALL SOUND CHANNELS BUT DMC
.WORD TURN_OFF_VIBRATO_COMMAND ;0xEF TURN OFF VIBRATO
.WORD PLAY_KICK_DRUM_HIGH_DMC_SAMPLE_COMMAND ;0xF0 DMC “KICK DRUM” HIGH FREQ
.WORD PLAY_KICK_DRUM_MED_DMC_SAMPLE_COMMAND ;0xF1 DMC “KICK DRUM” MED FREQ
.WORD PLAY_KICK_DRUM_LOW_DMC_SAMPLE_COMMAND ;0xF2 DMC “KICK DRUM” LOW FREQ
.WORD TURN_ON_NOTE_SLUR_COMMAND ;0xF3 SLUR NOTES
.WORD TURN_OFF_NOTE_SLUR_COMMAND ;0xF4 END SLUR NOTES
.WORD DISABLE_ALL_SOUND_CHANNELS_COMMAND ;0xF5 DISABLE ALL CHANNELS (*** unused )
.WORD ENABLE_ALL_SOUND_CHANNELS_COMMAND ;0xF6 ENABLE ALL CHANNELS (*** unused )
.WORD PLAY_SAFE_DMC_SAMPLE_COMMAND ;0xF7 DMC “SAFE”
.WORD PLAY_DOWN_DMC_SAMPLE_COMMAND ;0xF8 DMC “DOWN”
.WORD PLAY_HUT_DMC_SAMPLE_COMMAND ;0xF9 DMC “HUT”
.WORD PLAY_TOUCHDOWN_DMC_SAMPLE_COMMAND ;0xFA DMC “TOUCHDOWN”
.WORD PLAY_BASS_DMC_SAMPLE_COMMAND ;0xFB DMC “BASS DRUM”
.WORD PLAY_SNARE_DMC_SAMPLE_COMMAND ;0xFC DMC “SNARE DRUM”
.WORD PLAY_SNARE_HIGH_DMC_SAMPLE_COMMAND ;0xFD DMC “SNARE DRUM HIGH”
.WORD SET_FADE_OUT_SONG_TIMERS_COMMAND ;0XFE UNUSED
.WORD END_OF_MUSIC_CHANNEL_COMMAND ;0xFF END OF CHANNEL
_F}_SOUND_ENGINE_COMMAND_FUNCTION_TABLE
_F{_SOUND_ENGINE_FUNCTIONS ; DONE
TURN_ON_NOTE_SLUR_COMMAND: ; 0XF3 TURN ON SLUR NOTES
LDA CURRENT_CHANNEL_NOTE_STATUS[],X ;
ORA #BLEND_NOTES_ON_STATUS_BITMASK ;
STA CURRENT_CHANNEL_NOTE_STATUS[],X ;
JMP PROCESS_NOTE_OR_COMMAND_LOOP ; JUMP->CHECK FOR PITCH/LENGTH/COMMAND DATA LOOP
TURN_OFF_NOTE_SLUR_COMMAND: ; 0XF4 TURN OFF SLUR NOTES
LDA CURRENT_CHANNEL_NOTE_STATUS[],X ;
AND #BLEND_NOTES_OFF_STATUS_BITMASK ;
STA CURRENT_CHANNEL_NOTE_STATUS[],X ;
JMP PROCESS_NOTE_OR_COMMAND_LOOP ; JUMP->CHECK FOR PITCH/LENGTH/COMMAND DATA LOOP
SET_INT_MUSIC_PLAYING_COMMAND: ; 0XE1 INTCERCEPTION MUSIC IN PROGRESS
LDA #$FF ; SET INTERCEPTION MUSIC PLAYING = TRUE
STA INT_SONG_PLAYING_BOOLEAN ;
JMP PROCESS_NOTE_OR_COMMAND_LOOP ; JUMP->CHECK FOR PITCH/LENGTH/COMMAND DATA LOOP
SET_INT_MUSIC_NOT_PLAYING_COMMAND: ; 0XE7 INTCERCEPTION SONG DONE
LDA #$00 ; SET INTERCEPTION MUSIC PLAYING = FALSE
STA INT_SONG_PLAYING_BOOLEAN ;
JMP PROCESS_NOTE_OR_COMMAND_LOOP ; JUMP->CHECK FOR PITCH/LENGTH/COMMAND DATA LOOP
SET_VOLUME_ENVELOPE_COMMAND: ; 0xE0 SET VOLUME ENVELOPE ADDR
save_song_data_index = SOUND_ENGINE_LOCAL_4
LDA (CURR_SONG_CHANNEL_ADDR),Y
INY ; DATA ADDR++
STY save_song_data_index ; SAVE DATA ADDR
ASL ; SHIFT VALUE FOR TABLE ENTRY
TAY ;
@copy_value_from_rom_to_ram:
LDA VOLUME_ENVELOPE_ADDR_TABLE,Y
STA CURRENT_VOLUME_ENVELOPE_ADDR[],X
LDA VOLUME_ENVELOPE_ADDR_TABLE+1,Y
STA CURRENT_VOLUME_ENVELOPE_ADDR[] + 1,X ;
@restore_song_index:
LDY save_song_data_index ; LOAD DATA OFFSET
JMP PROCESS_NOTE_OR_COMMAND_LOOP ; JUMP->CHECK FOR PITCH/LENGTH/COMMAND DATA LOOP
SET_VOLUME_COMMAND: ; 0xE3 SET VOLUME RANGE = 0X00 TO 0X0F
LDA TEMP_VOLUME_OFFSET ; CHECK if saved volume offset (used during fade out)
BNE @exit ;
@set_volume:
LDA (CURR_SONG_CHANNEL_ADDR),Y ; SET CHANNEL VOLUME VALUE = DATA VALUE
STA CURRENT_CH_VOLUME[],X ;
@exit: ;
INY ; DATA ADDR++
JMP PROCESS_NOTE_OR_COMMAND_LOOP ; JUMP->CHECK FOR PITCH/LENGTH/COMMAND DATA LOOP
JUMP_TO_NEW_SONG_ADDR_COMMAND: ; 0XE8 JUMP TO ADDR
LDA (CURR_SONG_CHANNEL_ADDR),Y ; SET CURRENT ADDR = ADDR AT DATA LOC
INY ;
TAX ;
LDA (CURR_SONG_CHANNEL_ADDR),Y ;
@save_addr_to_jump_to:
STX CURR_SONG_CHANNEL_ADDR ;
STA CURR_SONG_CHANNEL_ADDR+1 ;
LDY #$00 ; SET ADDRESS OFFSET = START
LDX CURR_SOUND_CHANNEL_INDEX ; LOAD CURRENT CHANNEL INDEX
JMP PROCESS_NOTE_OR_COMMAND_LOOP ; JUMP->CHECK FOR PITCH/LENGTH/COMMAND DATA LOOP
DO_MUSIC_SECTION_AND_RETURN_COMMAND: ; 0xE9 DO MUSIC SECTION (ADDR)
LDA (CURR_SONG_CHANNEL_ADDR),Y ; SAVE ADDR TO JUMP TO
INY ;
PHA ;
LDA (CURR_SONG_CHANNEL_ADDR),Y ;
INY ;
PHA ;
TYA ;
LDY CURRENT_SECTION_OFFSET_FOR_RETURN[],X ; LOAD CHNL RETURN ADDRESS OFFSET
CLC ; SAVE CURRENT CHANNEL RETURN ADDR
ADC CURR_SONG_CHANNEL_ADDR ;
STA (CURR_SONG_ADDR),Y ;
DEY ;
LDA #$00 ;
ADC CURR_SONG_CHANNEL_ADDR +1 ;
STA (CURR_SONG_ADDR),Y ;
DEY ;
TYA ;
STA CURRENT_SECTION_OFFSET_FOR_RETURN[],X ; SAVE CHNL RETURN ADDR OFFSET
PLA ; SAVE CURRENT CHANNEL ADDR = ADDR
STA CURR_SONG_CHANNEL_ADDR +1 ;
PLA ;
STA CURR_SONG_CHANNEL_ADDR ;
LDY #$00 ; SET ADDRESS OFFSET = START
JMP PROCESS_NOTE_OR_COMMAND_LOOP ; JUMP->CHECK FOR PITCH/LENGTH/COMMAND DATA LOOP
RETURN_FROM_SECTION_COMMAND: ; 0XEA RETURN FROM MUSIC SECTION
LDY CURRENT_SECTION_OFFSET_FOR_RETURN[],X ; LOAD CHNL RETURN ADDR OFFSET
INY ;
LDA (CURR_SONG_ADDR),Y ; TRANSFER RETURN CH ADDR TO CURR CH ADDR
INY ;
STA CURR_SONG_CHANNEL_ADDR +1 ;
LDA (CURR_SONG_ADDR),Y ;
STA CURR_SONG_CHANNEL_ADDR ;
TYA ;
STA CURRENT_SECTION_OFFSET_FOR_RETURN[],X ; SAVE CHNL RETURN ADDR OFFSET
LDY #$00 ; SET ADDRESS OFFSET = START
JMP PROCESS_NOTE_OR_COMMAND_LOOP ; JUMP->CHECK FOR PITCH/LENGTH/COMMAND DATA LOOP
LOOP_SECTION_COMMAND: ; 0XEB LOOP MUSIC SECTION
num_times_to_loop = SOUND_ENGINE_LOCAL_1
LDA (CURR_SONG_CHANNEL_ADDR),Y ; LOAD # TIMES TO LOOP
INY ; CHANNEL ADDR OFFSET++
STY APU_CHANNEL_OFFSET ; SAVE CHANNEL ADDR OFFSET
STA num_times_to_loop ; SAVE # TIMES TO LOOP
LDY CURRENT_SECTION_OFFSET_FOR_RETURN[],X ; LOAD CHNL RETURN ADDR OFFSET
LDA APU_CHANNEL_OFFSET ; SAVE CURRENT CHANNEL RETURN ADDR
CLC ;
ADC CURR_SONG_CHANNEL_ADDR ;
STA CURR_SONG_CHANNEL_ADDR ;
STA (CURR_SONG_ADDR),Y ;
DEY ;
LDA #$00 ;
ADC CURR_SONG_CHANNEL_ADDR +1 ;
STA CURR_SONG_CHANNEL_ADDR +1 ;
STA (CURR_SONG_ADDR),Y ;
DEY ;
@save_times__to_loop:
LDA num_times_to_loop ; SAVE TIMES TO REPEAT
STA (CURR_SONG_ADDR),Y ;
DEY ;
TYA ;
STA CURRENT_SECTION_OFFSET_FOR_RETURN[],X ; SAVE CHNL RETURN ADDR OFFSET
LDY #$00 ; SET ADDRESS OFFSET = START
JMP PROCESS_NOTE_OR_COMMAND_LOOP ; JUMP->CHECK FOR PITCH/LENGTH/COMMAND DATA LOOP
CHECK_FOR_END_OF_LOOP_COMMAND: ; 0XEC CHECK FOR END OF LOOP
STY APU_CHANNEL_OFFSET ;
LDY CURRENT_SECTION_OFFSET_FOR_RETURN[],X ; LOAD CH OFFSET TO LOOP DATA
INY ;
LDA (CURR_SONG_ADDR),Y ; = NUMBER OF TIMES TO LOOP--
SEC ;
SBC #$01 ;
STA (CURR_SONG_ADDR),Y ;
BEQ @done_with_loop ; DONE LOOPING? YES->DONE WITH MUSIC LOOP
@loop_back_to_start_of_loop_section:
INY ; SET CH CURRENT ADDR = START OF LOOP ADDR
LDA (CURR_SONG_ADDR),Y ;
STA CURR_SONG_CHANNEL_ADDR +1 ;
INY ;
LDA (CURR_SONG_ADDR),Y ;
STA CURR_SONG_CHANNEL_ADDR ;
LDY #$00 ; SET ADDRESS OFFSET = START
JMP PROCESS_NOTE_OR_COMMAND_LOOP ; JUMP->CHECK FOR PITCH/LENGTH/COMMAND DATA LOOP
@done_with_loop: ; DONE WITH MUSIC LOOP
INY ;
INY ;
TYA ;
STA CURRENT_SECTION_OFFSET_FOR_RETURN[],X ; SAVE CHNL RETURN ADDR OFFSET
LDY APU_CHANNEL_OFFSET ; SET ADDRESS OFFSET = CURRENT OFFSET
JMP PROCESS_NOTE_OR_COMMAND_LOOP ; JUMP->CHECK FOR PITCH/LENGTH/COMMAND DATA LOOP
ENABLE_SWEEP_EFFECT_COMMAND: ; 0XE4 SET SWEEP ENABLED(VALUES = 0X80 TO 0X87) SQUARE CHANNEL ONLY
LDA CURRENT_CHANNEL_NOTE_STATUS[],X ; SET CURRENT CHANNEL STATUS = SWEEP ACTIVE
ORA #SQUARE_CH_SWEEP_ENABLED_BITMASK ;
STA CURRENT_CHANNEL_NOTE_STATUS[],X ;
LDA APU_VOLUME_VALUE_CH_INDEX_TABLE+1,X ; LOAD APU CHANNEL INDEX OFFSET FROM CHANNEL TABLE
TAX ;
LDA (CURR_SONG_CHANNEL_ADDR),Y ; SET APU REGISTER = SWEEP ENABLED + SWEEP SHIFT AMOUNT
STA APU_CUR_SQ_CH_SWEEP[],X ;
INY ; DATA ADDR++
LDX CURR_SOUND_CHANNEL_INDEX ; LOAD CHANNEL INDEX
JMP PROCESS_NOTE_OR_COMMAND_LOOP ; JUMP->CHECK FOR PITCH/LENGTH/COMMAND DATA LOOP
SET_DUTY_VOLUME_LENGTH_COMMAND: ; 0XE2 SET DUTY CYCLE, LENGTH COUNTER, VOLUME TYPE
LDA (CURR_SONG_CHANNEL_ADDR),Y ; GET DUTY CYCLE VALUE
INY ; DATA ADDR++
STA CURRENT_CH_DUTY_LENGTH_VOL_TYPE[],X ;
JMP PROCESS_NOTE_OR_COMMAND_LOOP ; JUMP->CHECK FOR PITCH/LENGTH/COMMAND DATA LOOP
END_OF_MUSIC_CHANNEL_COMMAND: ; 0XFF END/ TURN OFF CHANNEL
LDA CHANNEL_ACTIVE ; SET CURRENT CHANNEL = NOT ACTIVE
AND #NOTE_TIMER_INACTIVE_BITMASK ;
STA CHANNEL_ACTIVE ;
CPX #$07 ; IS CHANNEL INDEX = MUSIC CHANNEL?
BCS @set_to_forever_rest_note ;
@turn_off_effect_channel:
STX EFFECT_CH_INDEX ; *** do we need to use RAM variable?
TXA ;
ORA #$08 ;
TAX ;
LDA #$FF ;
STA CURRENT_CHANNEL_NOTE_TIMER[],X ;
LDX EFFECT_CH_INDEX ;
@set_to_forever_rest_note: ; TURN OFF MUSIC CHANNEL
LDA #$FF ;
STA CURRENT_CHANNEL_NOTE_TIMER[],X ;
LDA CURRENT_CHANNEL_NOTE_STATUS[],X ; CLEAR ALL CURRENT CHANNEL STATUS EXCEPT CHANNEL OFF/REST NOTE STAT
AND #REST_NOTE_STATUS_BITMASK ;
STA CURRENT_CHANNEL_NOTE_STATUS[],X ;
LDA APU_VOLUME_VALUE_CH_INDEX_TABLE,X ; LOAD CHANNEL ID INDEX FROM CHANNEL TABLE
PHA ;
@set_apu_register_index:
LDA APU_VOLUME_VALUE_CH_INDEX_TABLE+1,X ;
TAX ;
set_apu_volume_value:
PLA ;
STA APU_CUR_CH_VOL[],X ; SET LENGTH COUNT HALT = TRUE. SET CONSTANT VOLUME = TRUE, SET VOL = 0
LDA #$04 ;
STA APU_CUR_SQ_CH_SWEEP[],X ; SET CHANNEL SWEEP UNIT = DISABLED, SET SHIFT TO 4
RTS ; RETURN
APU_VOLUME_VALUE_CH_INDEX_TABLE: ; CHANNEL TABLE
; HALT MASK, APU_REGISTER_OFFSET
.DB $30, $00 ; SQUARE CHAN 0 EFFECTS
.DB $30, $04 ; SQUARE CHAN 1 EFFECTS
.DB $00, $08 ; TRIANGLE CHAN EFFFECTS *** writes 00 to triangle to silence
.DB $30, $0C ; NOISE CHAN EFFECTS
.DB $30, $00 ; SQUARE CHAN 0 MUSIC
.DB $30, $04 ; SQUARE CHAN 1 MUSIC
.DB $00, $08 ; TRIANGLE CHAN MUSIC *** writes 00 to triangle to silence
.DB $30, $0C ; NOISE CHAN MUSIC
ADJUST_PITCH_COMMAND: ; 0XE5 ADJUST PITCH
LDA (CURR_SONG_CHANNEL_ADDR),Y ;
INY ; DATA ADRR++
STA CURRENT_CHANNEL_PITCH_ADJUST[],X ;
JMP PROCESS_NOTE_OR_COMMAND_LOOP ; JUMP->CHECK FOR PITCH/LENGTH/COMMAND DATA LOOP
DO_NOTHING_MUSIC_CHANNEL_COMMAND: ; 0XE6 DO NOTHING
JMP PROCESS_NOTE_OR_COMMAND_LOOP ; JUMP->CHECK FOR PITCH/LENGTH/COMMAND DATA LOOP
TURN_ON_VIBRATO_COMMAND: ; 0XED TURN ON VIBRATO(DATA= VIBRATO TYPE)
LDA (CURR_SONG_CHANNEL_ADDR),Y ; LOAD WAVER EFFECT INDEX
INY ; DATA ADRR++
STY APU_CHANNEL_OFFSET ; SAVE OFFSET
@set_vibrato_addr:
ASL ;
TAY ;
LDA VIBRATO_ADDR_TABLE,Y ; SET CUR CH VIBRATO ADDR FROM VIBRATO POINTER TABLE
STA CURRENT_CHANNEL_VIBRATO_ADDR[],X ;
LDA VIBRATO_ADDR_TABLE+1,Y ;
STA CURRENT_CHANNEL_VIBRATO_ADDR[]+1,X ;
@set_status_vibrato_on:
LDY APU_CHANNEL_OFFSET ; LOAD SOUND DATA OFFSET
LDA CURRENT_CHANNEL_NOTE_STATUS[],X ; SET CUR CH VIBRATO FLAG = TRUE
ORA #$01 ;
STA CURRENT_CHANNEL_NOTE_STATUS[],X ;
@set_vibrato_time:
LDA #$01 ; SET VIBRATO TIME = CHECK FOR VIBRATO
STA CURRENT_CHANNEL_VIBRATO_TIMER[],X ;
@set_vibrato_offset_to_beginning:
LDA #$00 ; SET VIBRATO DATA OFFSET = START
STA CURRENT_CHANNEL_VIBRATO_OFFSET[],X ;
JMP PROCESS_NOTE_OR_COMMAND_LOOP ; JUMP->CHECK FOR PITCH/LENGTH/COMMAND DATA LOOP
TURN_OFF_VIBRATO_COMMAND: ; 0XEF TURN OFF VIBRATO
LDA CURRENT_CHANNEL_NOTE_STATUS[],X ; SET CUR CH VIBRATO FLAG = FALSE
AND #$FE ;
STA CURRENT_CHANNEL_NOTE_STATUS[],X ;
JMP PROCESS_NOTE_OR_COMMAND_LOOP ; JUMP->PROCESS SOUND DATA LOOP
DISABLE_ALL_SOUND_CHANNELS_COMMAND: ; 0XF5 SILENCE ALL CHANNELS *** UNUSED
LDA #$01 ; SET SILENCE ALL APU CHANNELS = TRUE
STA SILENCE_ALL_CHANNELS_BOOLEAN ;
LDA CURRENT_CHANNEL_NOTE_TIMER[] ; *** pointless
LDA CURRENT_CHANNEL_NOTE_TIMER[]+2 ; *** pointless
LDA CURRENT_CHANNEL_NOTE_TIMER[]+4 ; *** pointless
LDA CURRENT_CHANNEL_NOTE_TIMER[]+6 ; *** pointless
LDA CURRENT_CHANNEL_NOTE_TIMER[]+8 ; *** pointless
LDA CURRENT_CHANNEL_NOTE_TIMER[]+10 ; *** pointless
LDA CURRENT_CHANNEL_NOTE_TIMER[]+12 ; *** pointless
LDA CURRENT_CHANNEL_NOTE_TIMER[]+14 ; *** pointless
JMP PROCESS_NOTE_OR_COMMAND_LOOP ; JUMP->CHECK FOR PITCH/LENGTH/COMMAND DATA LOOP
ENABLE_ALL_SOUND_CHANNELS_COMMAND: ; 0XF6 ENABLE ALL CHANNELS *** UNUSED
LDA #$00 ; SET SILENCE ALL APU CHANNELS = FALSE
STA SILENCE_ALL_CHANNELS_BOOLEAN ;
JMP PROCESS_NOTE_OR_COMMAND_LOOP ; JUMP->CHECK FOR PITCH/LENGTH/COMMAND DATA LOOP
SET_FADE_OUT_SONG_TIMERS_COMMAND: ; 0XFE UNUSED SET FADE OUT COMMAND
LDA (CURR_SONG_CHANNEL_ADDR),Y ;
INY ;
STA CURRENT_CHANNEL_FADE_OUT_TIMER[],X ;
STA CURRENT_CHANNEL_FADE_OUT_RELOAD[],X ;
JMP PROCESS_NOTE_OR_COMMAND_LOOP ; JUMP->CHECK FOR PITCH/LENGTH/COMMAND DATA LOOP
ENABLE_ALL_CHANNELS_SILENCE_DMC_COMMAND: ; 0xEE ENABLE ALL CHANNELS BUT DMC
LDA #$0F ; SET APU STATUS = DMC DISABLED, ALL OTHER CHANNELS = ENABLED
STA APU_STATUS ;
JMP PROCESS_NOTE_OR_COMMAND_LOOP ; JUMP->CHECK FOR PITCH/LENGTH/COMMAND DATA LOOP
PLAY_SAFE_DMC_SAMPLE_COMMAND: ; 0XF7 PLAY “SAFE” DMC SAMPLE
; *** uses incorrect length
PLAY_VOICE_DMC_SAMPLE[addr_len] DMC_SAFE_VOICE_DATA, DMC_SAFE_LENGTH
JMP PROCESS_NOTE_OR_COMMAND_LOOP ;
PLAY_DOWN_DMC_SAMPLE_COMMAND: ; 0XF8 PLAY “DOWN” DMC SAMPLE
PLAY_VOICE_DMC_SAMPLE[addr_len] DMC_DOWN_VOICE_DATA, DMC_DOWN_LENGTH
JMP PROCESS_NOTE_OR_COMMAND_LOOP ;
PLAY_HUT_DMC_SAMPLE_COMMAND: ; 0XF9 PLAY “HUT” DMC SAMPLE
PLAY_VOICE_DMC_SAMPLE[addr_len] DMC_HUT_VOICE_DATA, DMC_HUT_LENGTH
JMP PROCESS_NOTE_OR_COMMAND_LOOP ;
PLAY_TOUCHDOWN_DMC_SAMPLE_COMMAND: ; 0XFA PLAY “TOUCHDOWN” DMC SAMPLE *** start of data sample uses last $30 bytes end of safe kind of weird.
PLAY_VOICE_DMC_SAMPLE[addr_len] (DMC_TOUCHDOWN_VOICE_DATA -$30) , (DMC_TOUCHDOWN_LENGTH + $30)
JMP PROCESS_NOTE_OR_COMMAND_LOOP ;
PLAY_BASS_DMC_SAMPLE_COMMAND: ; 0XFB DMC BASS DRUM
PLAY_DRUM_DMC_SAMPLE[addr_len] DMC_BASS_DRUM_DATA, DMC_BASS_LENGTH
JMP PROCESS_NOTE_OR_COMMAND_LOOP ;
PLAY_SNARE_DMC_SAMPLE_COMMAND: ; 0XFC DMC SNARE DRUM
PLAY_DRUM_DMC_SAMPLE[addr_len] DMC_SNARE_DRUM_DATA, DMC_SNARE_LENGTH
JMP PROCESS_NOTE_OR_COMMAND_LOOP ; JUMP->CHECK FOR PITCH/LENGTH/COMMAND DATA LOOP
PLAY_KICK_DRUM_HIGH_DMC_SAMPLE_COMMAND: ; 0XF0 DMC DRUM SAMPLE 1 (KICK DRUM HIGH FREQ)
PLAY_DRUM_DMC_SAMPLE_VARIABLE_ENCODE[addr_len_encode] DMC_KICK_DRUM_DATA, DMC_KICK_LENGTH, $0F
JMP PROCESS_NOTE_OR_COMMAND_LOOP ;
PLAY_KICK_DRUM_MED_DMC_SAMPLE_COMMAND: ; 0XF1 DMC DRUM SAMPLE 2(KICK DRUM MED FREQ)
PLAY_DRUM_DMC_SAMPLE_VARIABLE_ENCODE[addr_len_encode] DMC_KICK_DRUM_DATA, DMC_KICK_LENGTH, $0E
JMP PROCESS_NOTE_OR_COMMAND_LOOP ;
PLAY_KICK_DRUM_LOW_DMC_SAMPLE_COMMAND: ; 0XF2 DMC DRUM SAMPLE 3((KICK DRUM LOW FREQ))
PLAY_DRUM_DMC_SAMPLE_VARIABLE_ENCODE[addr_len_encode] DMC_KICK_DRUM_DATA, DMC_KICK_LENGTH, $0C
JMP PROCESS_NOTE_OR_COMMAND_LOOP ;
PLAY_SNARE_HIGH_DMC_SAMPLE_COMMAND: ; 0XFD DMC DRUM SAMPLE 4 (SNARE HIGH) *** start of data sample uses last $80 bytes of hut kind of weird.
PLAY_DRUM_DMC_SAMPLE_VARIABLE_ENCODE[addr_len_encode] (DMC_HIGH_SNARE_DRUM_DATA -$80), (DMC_HIGH_SNARE_LENGTH + $80), $0F
JMP PROCESS_NOTE_OR_COMMAND_LOOP ; JUMP->CHECK FOR PITCH/LENGTH/COMMAND DATA LOOP
INY ; *** UNEEDED/CANT REACH
JMP PROCESS_NOTE_OR_COMMAND_LOOP ; *** JUMP->CHECK FOR PITCH/LENGTH/COMMAND DATA LOOP
UPDATE_CHANNEL_VOLUME_AND_VIBRATO_EFFECT: ; UPDATE VOLUME(X= CHANNEL INDEX)
LDA CURRENT_CHANNEL_NOTE_STATUS[],X ; DOES NOTE = REST NOTE?
AND #REST_NOTE_STATUS_BITMASK ;
BNE @set_volume_to_zero ; YES->SET VOLUME = 0
@check_for_fade_timer:
LDA CURRENT_CH_VOLUME[],X ; LOAD CURRENT CH VOLUME OFFSET
LDY CURRENT_CHANNEL_FADE_OUT_TIMER[],X ;
BEQ @update_channel_volume ; IS FADE OUT = 0? YES->SUBTRACT VOLUME OFFSET FROM ENVELOPE VOLUME
STA TEMP_VOLUME_OFFSET ; SAVE CURRENT CH VOLUME OFFSET
DEC CURRENT_CHANNEL_FADE_OUT_TIMER[],X ;
BNE @update_channel_volume ;
@check_if_vol_is_min:
CMP #$0F ;
BNE @decrease_volume_by_one ;
LDA END_OF_CHANNEL_DATA_ADDR ; set channel address to end of channel
STA CURRENT_CH_SONG_ADDR[],X ;
LDA END_OF_CHANNEL_DATA_ADDR+1 ;
STA CURRENT_CH_SONG_ADDR[]+1,X ;
BNE @update_channel_volume ;
@decrease_volume_by_one: ; DECREASE VOLUME
CLC ; CURRENT CH VOLUME++
ADC #$01 ;
CMP #$0F ; *** pointless
STA CURRENT_CH_VOLUME[],X ;
@reload_fad_out_timer:
LDA CURRENT_CHANNEL_FADE_OUT_RELOAD[],X ;
STA CURRENT_CHANNEL_FADE_OUT_TIMER[],X ;
@update_channel_volume: ; SUBTRACT VOLUME OFFSET FROM ENVELOPE VOLUME
LDA CURRENT_VOLUME_ENVELOPE_VALUE[],X ; = ENVELOPE VOLUME VALUE – CHANNEL VOLUME SETTING
SEC ;
SBC CURRENT_CH_VOLUME[],X ;
BPL @save_volume ; VOLUME >0? YES->SAVE VOLUME ADJUSTMENT (A=VOLUME VALUE)
@set_volume_to_zero: ; SET VOLUME = 0
LDA #$00 ;
@save_volume: ; SAVE VOLUME ADJUSTMENT (A=VOLUME VALUE)
ORA CURRENT_CH_DUTY_LENGTH_VOL_TYPE[],X ; VOLUME + DUTY/LENGH HALT/CONSTANT VOLUME
STA CURRENT_CHANNEL_DUTY_VALUE[],X ;
@check_if_vibrato_on:
LDA CURRENT_CHANNEL_NOTE_STATUS[],X ; DOES CURRENT CHANNEL VIBRATO ON = TRUE?
AND #$01 ;
BNE @do_vibrato ; YES-> DO VIBRATO
@exit: ; EXIT
RTS ;
@do_vibrato: ; DO VIBRATO
DEC CURRENT_CHANNEL_VIBRATO_TIMER[],X ; VIBRATO TIMER--