-
Notifications
You must be signed in to change notification settings - Fork 17
/
Copy pathelite-source-bank-6.asm
15370 lines (11682 loc) · 583 KB
/
elite-source-bank-6.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
; ******************************************************************************
;
; NES ELITE GAME SOURCE (BANK 6)
;
; NES Elite was written by Ian Bell and David Braben and is copyright D. Braben
; and I. Bell 1991/1992
;
; The sound player in this bank was written by David Whittaker
;
; The code in this file has been reconstructed from a disassembly of the version
; released on Ian Bell's personal website at http://www.elitehomepage.org/
;
; The commentary is copyright Mark Moxon, and any misunderstandings or mistakes
; in the documentation are entirely my fault
;
; The terminology and notations used in this commentary are explained at
; https://elite.bbcelite.com/terminology
;
; The deep dive articles referred to in this commentary can be found at
; https://elite.bbcelite.com/deep_dives
;
; ------------------------------------------------------------------------------
;
; This source file contains the game code for ROM bank 6 of NES Elite.
;
; ------------------------------------------------------------------------------
;
; This source file produces the following binary file:
;
; * bank6.bin
;
; ******************************************************************************
; ******************************************************************************
;
; ELITE BANK 6
;
; Produces the binary file bank1.bin.
;
; ******************************************************************************
ORG CODE%
; ******************************************************************************
;
; Name: ResetMMC1_b6
; Type: Subroutine
; Category: Start and end
; Summary: The MMC1 mapper reset routine at the start of the ROM bank
; Deep dive: Splitting NES Elite across multiple ROM banks
;
; ------------------------------------------------------------------------------
;
; When the NES is switched on, it is hardwired to perform a JMP ($FFFC). At this
; point, there is no guarantee as to which ROM banks are mapped to $8000 and
; $C000, so to ensure that the game starts up correctly, we put the same code
; in each ROM at the following locations:
;
; * We put $C000 in address $FFFC in every ROM bank, so the NES always jumps
; to $C000 when it starts up via the JMP ($FFFC), irrespective of which
; ROM bank is mapped to $C000.
;
; * We put the same reset routine (this routine, ResetMMC1) at the start of
; every ROM bank, so the same routine gets run, whichever ROM bank is mapped
; to $C000.
;
; This ResetMMC1 routine is therefore called when the NES starts up, whatever
; the bank configuration ends up being. It then switches ROM bank 7 to $C000 and
; jumps into bank 7 at the game's entry point BEGIN, which starts the game.
;
; ******************************************************************************
.ResetMMC1_b6
SEI ; Disable interrupts
INC $C006 ; Reset the MMC1 mapper, which we can do by writing a
; value with bit 7 set into any address in ROM space
; (i.e. any address from $8000 to $FFFF)
;
; The INC instruction does this in a more efficient
; manner than an LDA/STA pair, as it:
;
; * Fetches the contents of address $C006, which
; contains the high byte of the JMP destination
; below, i.e. the high byte of BEGIN, which is $C0
;
; * Adds 1, to give $C1
;
; * Writes the value $C1 back to address $C006
;
; $C006 is in the ROM space and $C1 has bit 7 set, so
; the INC does all that is required to reset the mapper,
; in fewer cycles and bytes than an LDA/STA pair
;
; Resetting MMC1 maps bank 7 to $C000 and enables the
; bank at $8000 to be switched, so this instruction
; ensures that bank 7 is present
JMP BEGIN ; Jump to BEGIN in bank 7 to start the game
; ******************************************************************************
;
; Name: Interrupts_b6
; Type: Subroutine
; Category: Start and end
; Summary: The IRQ and NMI handler while the MMC1 mapper reset routine is
; still running
;
; ******************************************************************************
.Interrupts_b6
IF _NTSC
RTI ; Return from the IRQ interrupt without doing anything
;
; This ensures that while the system is starting up and
; the ROM banks are in an unknown configuration, any IRQ
; interrupts that go via the vector at $FFFE and any NMI
; interrupts that go via the vector at $FFFA will end up
; here and be dealt with
;
; Once bank 7 is switched into $C000 by the ResetMMC1
; routine, the vector is overwritten with the last two
; bytes of bank 7, which point to the IRQ routine
ENDIF
; ******************************************************************************
;
; Name: versionNumber_b6
; Type: Variable
; Category: Text
; Summary: The game's version number in bank 6
;
; ******************************************************************************
IF _NTSC
EQUS " 5.0"
ELIF _PAL
EQUS "<2.8>"
ENDIF
; ******************************************************************************
;
; Name: ChooseMusicS
; Type: Subroutine
; Category: Sound
; Summary: A jump table entry at the start of bank 6 for the ChooseMusic
; routine
;
; ******************************************************************************
.ChooseMusicS
JMP ChooseMusic ; Jump to the ChooseMusic routine, returning from the
; subroutine using a tail call
; ******************************************************************************
;
; Name: MakeSoundsS
; Type: Subroutine
; Category: Sound
; Summary: A jump table entry at the start of bank 6 for the MakeSounds
; routine
;
; ******************************************************************************
.MakeSoundsS
JMP MakeSounds ; Jump to the MakeSounds routine, returning from the
; subroutine using a tail call
; ******************************************************************************
;
; Name: StopSoundsS
; Type: Subroutine
; Category: Sound
; Summary: A jump table entry at the start of bank 6 for the StopSounds
; routine
;
; ******************************************************************************
.StopSoundsS
JMP StopSounds ; Jump to the StopSounds routine, returning from the
; subroutine using a tail call
; ******************************************************************************
;
; Name: EnableSoundS
; Type: Subroutine
; Category: Sound
; Summary: A jump table entry at the start of bank 6 for the EnableSound
; routine
;
; ******************************************************************************
.EnableSoundS
JMP EnableSound ; Jump to the EnableSound routine, returning from the
; subroutine using a tail call
; ******************************************************************************
;
; Name: StartEffectOnSQ1S
; Type: Subroutine
; Category: Sound
; Summary: A jump table entry at the start of bank 6 for the StartEffectOnSQ1
; routine
;
; ******************************************************************************
.StartEffectOnSQ1S
JMP StartEffectOnSQ1 ; Jump to the StartEffectOnSQ1 routine, returning from
; the subroutine using a tail call
; ******************************************************************************
;
; Name: StartEffectOnSQ2S
; Type: Subroutine
; Category: Sound
; Summary: A jump table entry at the start of bank 6 for the StartEffectOnSQ2
; routine
;
; ******************************************************************************
.StartEffectOnSQ2S
JMP StartEffectOnSQ2 ; Jump to the StartEffectOnSQ2 routine, returning from
; the subroutine using a tail call
; ******************************************************************************
;
; Name: StartEffectOnNOISES
; Type: Subroutine
; Category: Sound
; Summary: A jump table entry at the start of bank 6 for the
; StartEffectOnNOISE routine
;
; ******************************************************************************
.StartEffectOnNOISES
JMP StartEffectOnNOISE ; Jump to the StartEffectOnNOISE routine, returning from
; the subroutine using a tail call
; ******************************************************************************
;
; Name: ChooseMusic
; Type: Subroutine
; Category: Sound
; Summary: Set the tune for the background music
;
; ------------------------------------------------------------------------------
;
; The tune numbers are as follows:
;
; * 0 for the title music ("Elite Theme"), which is set in the TITLE routine
; and as the default tune in the ResetMusic routine
;
; * 1 for docking ("The Blue Danube"), which is set in the TT102 routine
;
; * 2 for the combat demo music ("Game Theme"), though this is never set
; directly, only via tune 4
;
; * 3 for the scroll text music ("Assassin's Touch"), though this is never set
; directly, only via tune 4
;
; * 4 for the full combat demo suite ("Assassin's Touch" followed by "Game
; Theme"), which is set in the DEATH2 routine
;
; ------------------------------------------------------------------------------
;
; Arguments:
;
; A The number of the tune to choose
;
; ******************************************************************************
.ChooseMusic
TAY ; Set Y to the tune number
JSR StopSoundsS ; Call StopSounds via StopSoundsS to stop all sounds
; (both music and sound effects)
;
; This also sets enableSound to 0
; We now calculate the offset into the tuneData table
; for this tune\s data, which will be Y * 9 as there are
; nine bytes for each tune at the start of the table
LDA #0 ; Set A = 0 so we can build the results of the
; calculation by adding 9 to A, Y times
CLC ; Clear the C flag for the addition below
.cmus1
DEY ; Decrement the tune number in Y
BMI cmus2 ; If the result is negative then A contains the result
; of Y * 9, so jump to cmus2
ADC #9 ; Set A = A * 9
BNE cmus1 ; Loop back to cmus1 to add another 9 to A (this BNE is
; effectively a JMP as A is never zero)
.cmus2
TAX ; Copy the result into X, so X = tune number * 9, which
; we can use as the offset into the tuneData table
; below
; We now reset the four 19-byte blocks of memory that
; are used to store the channel-specific variables, as
; follows:
;
; sectionDataSQ1 to applyVolumeSQ1
; sectionDataSQ2 to applyVolumeSQ2
; sectionDataTRI to volumeEnvelopeTRI+1
; sectionDataNOISE to applyVolumeNOISE
;
; There is no volumeEnvelopeTRI variable but the space
; is still reserved, which is why the TRI channel clears
; to volumeEnvelopeTRI+1
LDA #0 ; Set A = 0 to use when zeroing these locations
LDY #18 ; Set a counter in Y for the 19 bytes in each block
.cmus3
STA sectionDataSQ1,Y ; Zero the Y-th byte of sectionDataSQ1
STA sectionDataSQ2,Y ; Zero the Y-th byte of sectionDataSQ2
STA sectionDataTRI,Y ; Zero the Y-th byte of sectionDataTRI
STA sectionDataNOISE,Y ; Zero the Y-th byte of sectionDataNOISE
DEY ; Decrement the loop counter in Y
BPL cmus3 ; Loop back until we have zeroed bytes 0 to 18 in all
; four blocks
TAY ; Set Y = 0, to use as an index when fetching addresses
; from the tuneData table
LDA tuneData,X ; Fetch the first byte from the tune's block at
STA tuneSpeed ; tuneData, which contains the tune's speed, and store
STA tuneSpeedCopy ; it in tuneSpeed and tuneSpeedCopy
;
; For tune 0, this would be 47
LDA tuneData+1,X ; Set soundAddr(1 0) and sectionListSQ1(1 0) to the
STA sectionListSQ1 ; first address from the tune's block at tuneData
STA soundAddr ;
LDA tuneData+2,X ; For tune 0, this would set both variables to point to
STA sectionListSQ1+1 ; the list of tune sections at tune0Data_SQ1
STA soundAddr+1
LDA (soundAddr),Y ; Fetch the address that the first address points to
STA sectionDataSQ1 ; and put it in sectionDataSQ1(1 0), incrementing the
INY ; index in Y in the process
LDA (soundAddr),Y ;
STA sectionDataSQ1+1 ; For tune 0, this would set sectionDataSQ1(1 0) to the
; address of tune0Data_SQ1_0
LDA tuneData+3,X ; Set soundAddr(1 0) and sectionListSQ2(1 0) to the
STA sectionListSQ2 ; second address from the tune's block at tuneData
STA soundAddr ;
LDA tuneData+4,X ; For tune 0, this would set both variables to point to
STA sectionListSQ2+1 ; the list of tune sections at tune0Data_SQ2
STA soundAddr+1
DEY ; Decrement the index in Y, so it is zero once again
LDA (soundAddr),Y ; Fetch the address that the second address points to
STA sectionDataSQ2 ; and put it in sectionDataSQ2(1 0), incrementing the
INY ; index in Y in the process
LDA (soundAddr),Y ;
STA sectionDataSQ2+1 ; For tune 0, this would set sectionDataSQ2(1 0) to the
; address of tune0Data_SQ2_0
LDA tuneData+5,X ; Set soundAddr(1 0) and sectionListTRI(1 0) to the
STA sectionListTRI ; third address from the tune's block at tuneData
STA soundAddr ;
LDA tuneData+6,X ; For tune 0, this would set both variables to point to
STA sectionListTRI+1 ; the list of tune sections at tune0Data_TRI
STA soundAddr+1
DEY ; Decrement the index in Y, so it is zero once again
LDA (soundAddr),Y ; Fetch the address that the third address points to
STA sectionDataTRI ; and put it in sectionDataTRI(1 0), incrementing the
INY ; index in Y in the process
LDA (soundAddr),Y ;
STA sectionDataTRI+1 ; For tune 0, this would set sectionDataTRI(1 0) to the
; address of tune0Data_TRI_0
LDA tuneData+7,X ; Set soundAddr(1 0) and sectionListNOISE(1 0) to the
STA sectionListNOISE ; fourth address from the tune's block at tuneData
STA soundAddr ;
LDA tuneData+8,X ; For tune 0, this would set both variables to point to
STA sectionListNOISE+1 ; the list of tune sections at tune0Data_NOISE
STA soundAddr+1
DEY ; Decrement the index in Y, so it is zero once again
LDA (soundAddr),Y ; Fetch the address that the fourth address points to
STA sectionDataNOISE ; and put it in sectionDataNOISE(1 0), incrementing the
INY ; index in Y in the process
LDA (soundAddr),Y ;
STA sectionDataNOISE+1 ; For tune 0, this would set sectionDataNOISE(1 0) to
; the address of tune0Data_NOISE_0
STY pauseCountSQ1 ; Set pauseCountSQ1 = 1 so we start sending music to the
; SQ1 channel straight away, without a pause
STY pauseCountSQ2 ; Set pauseCountSQ2 = 1 so we start sending music to the
; SQ2 channel straight away, without a pause
STY pauseCountTRI ; Set pauseCountTRI = 1 so we start sending music to the
; TRI channel straight away, without a pause
STY pauseCountNOISE ; Set pauseCountNOISE = 1 so we start sending music to
; the NOISE channel straight away, without a pause
INY ; Increment Y to 2
STY nextSectionSQ1 ; Set nextSectionSQ1(1 0) = 2 (the high byte was already
; zeroed above), so the next section after the first on
; the SQ1 channel is the second section
STY nextSectionSQ2 ; Set nextSectionSQ2(1 0) = 2 (the high byte was already
; zeroed above), so the next section after the first on
; the SQ2 channel is the second section
STY nextSectionTRI ; Set nextSectionTRI(1 0) = 2 (the high byte was already
; zeroed above), so the next section after the first on
; the TRI channel is the second section
STY nextSectionNOISE ; Set nextSectionNOISE = 2 (the high byte was already
; zeroed above), so the next section after the first on
; the NOISE channel is the second section
LDX #0 ; Set tuningAll = 0 to set all channels to the default
STX tuningAll ; tuning
DEX ; Decrement X to $FF
STX tuneProgress ; Set tuneProgress = $FF, so adding any non-zero speed
; at the start of MakeMusic will overflow the progress
; counter and start playing the music straight away
STX playMusic ; Set playMusic = $FF to enable the new tune to be
; played
INC enableSound ; Increment enableSound to 1 to enable sound, now that
; we have set up the music to play
RTS ; Return from the subroutine
; ******************************************************************************
;
; Name: EnableSound
; Type: Subroutine
; Category: Sound
; Summary: Enable sounds (music and sound effects)
;
; ******************************************************************************
.EnableSound
LDA playMusic ; If playMusic = 0 then the music has been disabled by
BEQ enas1 ; note command $FE, so jump to enas1 to leave the value
; of enableSound alone and return from the subroutine
; as only a new call to ChooseMusic can enable the music
; again
LDA enableSound ; If enableSound is already non-zero, jump to enas1 to
BNE enas1 ; leave it and return from the subroutine
INC enableSound ; Otherwise increment enableSound from 0 to 1
.enas1
RTS ; Return from the subroutine
; ******************************************************************************
;
; Name: StopSounds
; Type: Subroutine
; Category: Sound
; Summary: Stop all sounds (music and sound effects)
;
; ******************************************************************************
.StopSounds
LDA #0 ; Set enableSound = 0 to disable all sounds (music and
STA enableSound ; sound effects)
STA effectOnSQ1 ; Set effectOnSQ1 = 0 to indicate the SQ1 channel is
; clear of sound effects
STA effectOnSQ2 ; Set effectOnSQ2 = 0 to indicate the SQ2 channel is
; clear of sound effects
STA effectOnNOISE ; Set effectOnNOISE = 0 to indicate the NOISE channel is
; clear of sound effects
TAX ; We now clear the 16 bytes at sq1Volume, so set X = 0
; to act as an index in the following loop
.stop1
STA sq1Volume,X ; Zero the X-th byte of sq1Volume
INX ; Increment the index counter
CPX #16 ; Loop back until we have cleared all 16 bytes
BNE stop1
STA TRI_LINEAR ; Zero the linear counter for the TRI channel, which
; configures it as follows:
;
; * Bit 7 clear = do not reload the linear counter
;
; * Bits 0-6 = counter reload value of 0
;
; So this silences the TRI channel
LDA #%00110000 ; Set the volume of the SQ1, SQ2 and NOISE channels to
STA SQ1_VOL ; zero as follows:
STA SQ2_VOL ;
STA NOISE_VOL ; * Bits 6-7 = duty pulse length is 3
; * Bit 5 set = infinite play
; * Bit 4 set = constant volume
; * Bits 0-3 = volume is 0
LDA #%00001111 ; Enable the sound channels by writing to the sound
STA SND_CHN ; status register in SND_CHN as follows:
;
; Bit 4 clear = disable the DMC channel
; Bit 3 set = enable the NOISE channel
; Bit 2 set = enable the TRI channel
; Bit 1 set = enable the SQ2 channel
; Bit 0 set = enable the SQ1 channel
RTS ; Return from the subroutine
; ******************************************************************************
;
; Name: MakeSounds
; Type: Subroutine
; Category: Sound
; Summary: Make the current sounds (music and sound effects)
; Deep dive: Sound effects in NES Elite
; Music in NES Elite
;
; ******************************************************************************
.MakeSounds
JSR MakeMusic ; Calculate the current music on the SQ1, SQ2, TRI and
; NOISE channels
JSR MakeSound ; Calculate the current sound effects on the SQ1, SQ2
; and NOISE channels
LDA enableSound ; If enableSound = 0 then sound is disabled, so jump to
BEQ maks3 ; maks3 to return from the subroutine
LDA effectOnSQ1 ; If effectOnSQ1 is non-zero then a sound effect is
BNE maks1 ; being made on channel SQ1, so jump to maks1 to skip
; writing the music data to the APU (so sound effects
; take precedence over music)
LDA sq1Volume ; Send sq1Volume to the APU via SQ1_VOL
STA SQ1_VOL
LDA sq1Sweep ; If sq1Sweep is non-zero then there is a sweep unit in
BNE maks1 ; play on channel SQ1, so jump to maks1 to skip the
; following as the sweep will take care of the pitch
LDA sq1Lo ; Otherwise send sq1Lo to the APU via SQ1_LO to set the
STA SQ1_LO ; pitch on channel SQ1
.maks1
LDA effectOnSQ2 ; If effectOnSQ2 is non-zero then a sound effect is
BNE maks2 ; being made on channel SQ2, so jump to maks2 to skip
; writing the music data to the APU (so sound effects
; take precedence over music)
LDA sq2Volume ; Send sq2Volume to the APU via SQ2_VOL
STA SQ2_VOL
LDA sq2Sweep ; If sq2Sweep is non-zero then there is a sweep unit in
BNE maks2 ; play on channel SQ2, so jump to maks2 to skip the
; following as the sweep will take care of the pitch
LDA sq2Lo ; Otherwise send sq2Lo to the APU via SQ2_LO to set the
STA SQ2_LO ; pitch on channel SQ2
.maks2
LDA triLo ; Send triLo to the APU via TRI_LO
STA TRI_LO
LDA effectOnNOISE ; If effectOnNOISE is non-zero then a sound effect is
BNE maks3 ; being made on channel NOISE, so jump to maks3 to skip
; writing the music data to the APU (so sound effects
; take precedence over music)
LDA noiseVolume ; Send noiseVolume to the APU via NOISE_VOL
STA NOISE_VOL
LDA noiseLo ; Send noiseLo to the APU via NOISE_LO
STA NOISE_LO
.maks3
RTS ; Return from the subroutine
; ******************************************************************************
;
; Name: MakeMusic
; Type: Subroutine
; Category: Sound
; Summary: Play the current music on the SQ1, SQ2, TRI and NOISE channels
; Deep dive: Music in NES Elite
;
; ******************************************************************************
.MakeMusic
LDA enableSound ; If enableSound is non-zero then sound is enabled, so
BNE makm1 ; jump to makm1 to play the current music
RTS ; Otherwise sound is disabled, so return from the
; subroutine
.makm1
LDA tuneSpeed ; Set tuneProgress = tuneProgress + tuneSpeed
CLC ;
ADC tuneProgress ; This moves the tune along by the current speed,
STA tuneProgress ; setting the C flag only when the addition of this
; iteration's speed overflows the addition
;
; This ensures that we only send music to the APU once
; every 256 / tuneSpeed iterations, which keeps the
; music in sync and sends the music more regularly with
; higher values of tuneSpeed
BCC makm2 ; If the addition didn't overflow, jump to makm2 to skip
; playing music in this VBlank
JSR MakeMusicOnSQ1 ; Play the current music on the SQ1 channel
JSR MakeMusicOnSQ2 ; Play the current music on the SQ2 channel
JSR MakeMusicOnTRI ; Play the current music on the TRI channel
JSR MakeMusicOnNOISE ; Play the current music on the NOISE channel
.makm2
JSR ApplyEnvelopeSQ1 ; Apply volume and pitch changes to the SQ1 channel
JSR ApplyEnvelopeSQ2 ; Apply volume and pitch changes to the SQ2 channel
JSR ApplyEnvelopeTRI ; Apply volume and pitch changes to the TRI channel
JMP ApplyEnvelopeNOISE ; Apply volume and pitch changes to the NOISE channel,
; returning from the subroutine using a tail call
; ******************************************************************************
;
; Name: MakeMusicOnSQ1
; Type: Subroutine
; Category: Sound
; Summary: Play the current music on the SQ1 channel
; Deep dive: Music in NES Elite
;
; ******************************************************************************
.MakeMusicOnSQ1
DEC pauseCountSQ1 ; Decrement the sound counter for SQ1
BEQ muso1 ; If the counter has reached zero, jump to muso1 to make
; music on the SQ1 channel
RTS ; Otherwise return from the subroutine
.muso1
LDA sectionDataSQ1 ; Set soundAddr(1 0) = sectionDataSQ1(1 0)
STA soundAddr ;
LDA sectionDataSQ1+1 ; So soundAddr(1 0) points to the note data for this
STA soundAddr+1 ; part of the tune
LDA #0 ; Set sq1Sweep = 0
STA sq1Sweep
STA applyVolumeSQ1 ; Set applyVolumeSQ1 = 0 so we don't apply the volume
; envelope by default (this gets changed if we process
; note data below, as opposed to a command)
.muso2
LDY #0 ; Set Y to the next entry from the note data
LDA (soundAddr),Y
TAY
INC soundAddr ; Increment soundAddr(1 0) to point to the next entry
BNE muso3 ; in the note data
INC soundAddr+1
.muso3
TYA ; Set A to the next entry that we just fetched from the
; note data
BMI muso8 ; If bit 7 of A is set then this is a command byte, so
; jump to muso8 to process it
CMP #$60 ; If the note data in A is less than $60, jump to muso4
BCC muso4
ADC #$A0 ; The note data in A is between $60 and $7F, so set the
STA startPauseSQ1 ; following:
;
; startPauseSQ1 = A - $5F
;
; We know the C flag is set as we just passed through a
; BCC, so the ADC actually adds $A1, which is the same
; as subtracting $5F
;
; So this sets startPauseSQ1 to a value between 1 and
; 32, corresponding to note data values between $60 and
; $7F
JMP muso2 ; Jump back to muso2 to move on to the next entry from
; the note data
.muso4
; If we get here then the note data in A is less than
; $60, which denotes a sound to send to the APU, so we
; now convert the data to a frequency and send it to the
; APU to make a sound on channel SQ1
CLC ; Set Y = (A + tuningAll + tuningSQ1) * 2
ADC tuningAll
CLC
ADC tuningSQ1
ASL A
TAY
LDA noteFrequency,Y ; Set (sq1Hi sq1Lo) the frequency for note Y
STA sq1LoCopy ;
STA sq1Lo ; Also save a copy of the low byte in sq1LoCopy
LDA noteFrequency+1,Y
STA sq1Hi
LDX effectOnSQ1 ; If effectOnSQ1 is non-zero then a sound effect is
BNE muso5 ; being made on channel SQ1, so jump to muso5 to skip
; writing the music data to the APU (so sound effects
; take precedence over music)
LDX sq1Sweep ; Send sq1Sweep to the APU via SQ1_SWEEP
STX SQ1_SWEEP
LDX sq1Lo ; Send (sq1Hi sq1Lo) to the APU via SQ1_HI and SQ1_LO
STX SQ1_LO
STA SQ1_HI
.muso5
LDA #1 ; Set volumeIndexSQ1 = 1
STA volumeIndexSQ1
LDA volumeRepeatSQ1 ; Set volumeCounterSQ1 = volumeRepeatSQ1
STA volumeCounterSQ1
.muso6
LDA #$FF ; Set applyVolumeSQ1 = $FF so we apply the volume
STA applyVolumeSQ1 ; envelope in the next iteration
.muso7
LDA soundAddr ; Set sectionDataSQ1(1 0) = soundAddr(1 0)
STA sectionDataSQ1 ;
LDA soundAddr+1 ; This updates the pointer to the note data for the
STA sectionDataSQ1+1 ; channel, so the next time we can pick up where we left
; off
LDA startPauseSQ1 ; Set pauseCountSQ1 = startPauseSQ1
STA pauseCountSQ1 ;
; So if startPauseSQ1 is non-zero (as set by note data
; the range $60 to $7F), the next startPauseSQ1
; iterations of MakeMusicOnSQ1 will do nothing
RTS ; Return from the subroutine
.muso8
; If we get here then bit 7 of the note data in A is
; set, so this is a command byte
LDY #0 ; Set Y = 0, so we can use it in various commands below
CMP #$FF ; If A is not $FF, jump to muso10 to check for the next
BNE muso10 ; command
; If we get here then the command in A is $FF
;
; <$FF> moves to the next section in the current tune
LDA nextSectionSQ1 ; Set soundAddr(1 0) to the following:
CLC ;
ADC sectionListSQ1 ; sectionListSQ1(1 0) + nextSectionSQ1(1 0)
STA soundAddr ;
LDA nextSectionSQ1+1 ; So soundAddr(1 0) points to the address of the next
ADC sectionListSQ1+1 ; section in the current tune
STA soundAddr+1 ;
; So if we are playing tune 2 and nextSectionSQ1(1 0)
; points to the second section, then soundAddr(1 0)
; will now point to the second address in tune2Data_SQ1,
; which itself points to the note data for the second
; section at tune2Data_SQ1_1
LDA nextSectionSQ1 ; Set nextSectionSQ1(1 0) = nextSectionSQ1(1 0) + 2
ADC #2 ;
STA nextSectionSQ1 ; So nextSectionSQ1(1 0) now points to the next section,
TYA ; as each section consists of two bytes in the table at
ADC nextSectionSQ1+1 ; sectionListSQ1(1 0)
STA nextSectionSQ1+1
LDA (soundAddr),Y ; If the address at soundAddr(1 0) is non-zero then it
INY ; contains a valid address to the section's note data,
ORA (soundAddr),Y ; so jump to muso9 to skip the following
BNE muso9 ;
; This also increments the index in Y to 1
; If we get here then the command is trying to move to
; the next section, but that section contains value of
; $0000 in the tuneData table, so there is no next
; section and we have reached the end of the tune, so
; instead we jump back to the start of the tune
LDA sectionListSQ1 ; Set soundAddr(1 0) = sectionListSQ1(1 0)
STA soundAddr ;
LDA sectionListSQ1+1 ; So we start again by pointing soundAddr(1 0) to the
STA soundAddr+1 ; first entry in the section list for channel SQ1, which
; contains the address of the first section's note data
LDA #2 ; Set nextSectionSQ1(1 0) = 2
STA nextSectionSQ1 ;
LDA #0 ; So the next section after we play the first section
STA nextSectionSQ1+1 ; will be the second section
.muso9
; By this point, Y has been incremented to 1
LDA (soundAddr),Y ; Set soundAddr(1 0) to the address at soundAddr(1 0)
TAX ;
DEY ; As we pointed soundAddr(1 0) to the address of the
LDA (soundAddr),Y ; new section above, this fetches the first address from
STA soundAddr ; the new section's address list, which points to the
STX soundAddr+1 ; new section's note data
;
; So soundAddr(1 0) now points to the note data for the
; new section, so we're ready to start processing notes
; and commands when we rejoin the muso2 loop
JMP muso2 ; Jump back to muso2 to start processing data from the
; new section
.muso10
CMP #$F6 ; If A is not $F6, jump to muso12 to check for the next
BNE muso12 ; command
; If we get here then the command in A is $F6
;
; <$F6 $xx> sets the volume envelope number to $xx
LDA (soundAddr),Y ; Fetch the next entry in the note data into A
INC soundAddr ; Increment soundAddr(1 0) to point to the next entry
BNE muso11 ; in the note data
INC soundAddr+1
.muso11
STA volumeEnvelopeSQ1 ; Set volumeEnvelopeSQ1 to the volume envelope number
; that we just fetched
JMP muso2 ; Jump back to muso2 to move on to the next entry from
; the note data
.muso12
CMP #$F7 ; If A is not $F7, jump to muso14 to check for the next
BNE muso14 ; command
; If we get here then the command in A is $F7
;
; <$F7 $xx> sets the pitch envelope number to $xx
LDA (soundAddr),Y ; Fetch the next entry in the note data into A
INC soundAddr ; Increment soundAddr(1 0) to point to the next entry
BNE muso13 ; in the note data
INC soundAddr+1
.muso13
STA pitchEnvelopeSQ1 ; Set pitchEnvelopeSQ1 to the pitch envelope number that
; we just fetched
STY pitchIndexSQ1 ; Set pitchIndexSQ1 = 0 to point to the start of the
; data for pitch envelope A
JMP muso2 ; Jump back to muso2 to move on to the next entry from
; the note data
.muso14
CMP #$FA ; If A is not $FA, jump to muso16 to check for the next
BNE muso16 ; command
; If we get here then the command in A is $FA
;
; <$FA %ddlc0000> configures the SQ1 channel as follows:
;
; * %dd = duty pulse length
;
; * %l set = infinite play
; * %l clear = one-shot play
;
; * %c set = constant volume
; * %c clear = envelope volume
LDA (soundAddr),Y ; Fetch the next entry in the note data into A
STA dutyLoopEnvSQ1 ; Store the entry we just fetched in dutyLoopEnvSQ1, to
; configure SQ1 as follows:
;
; * Bits 6-7 = duty pulse length
;
; * Bit 5 set = infinite play
; * Bit 5 clear = one-shot play
;
; * Bit 4 set = constant volume
; * Bit 4 clear = envelope volume
INC soundAddr ; Increment soundAddr(1 0) to point to the next entry
BNE muso15 ; in the note data
INC soundAddr+1
.muso15
JMP muso2 ; Jump back to muso2 to move on to the next entry from
; the note data
.muso16
CMP #$F8 ; If A is not $F8, jump to muso17 to check for the next
BNE muso17 ; command
; If we get here then the command in A is $F8
;
; <$F8> sets the volume of the SQ1 channel to zero
LDA #%00110000 ; Set the volume of the SQ1 channel to zero as follows:
STA sq1Volume ;
; * Bits 6-7 = duty pulse length is 3
; * Bit 5 set = infinite play
; * Bit 4 set = constant volume
; * Bits 0-3 = volume is 0
JMP muso7 ; Jump to muso7 to return from the subroutine, so we
; continue on from the next entry from the note data in