-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathstcplay.asm
1068 lines (872 loc) · 32.1 KB
/
stcplay.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
;originally written for tasm assembler
; altered for fasm september 2017
; by andy rea
; re-altered for BRASS a day later :D
; by sirmorris
; ZX81 AY PLAYER
;
; MODIFIED FROM THE SOUNDTRACKER PLAYBACK MODULE
;
; 13/8/2011
;
; ANDY REA
;
; L8003: ;C30980
; JP INITALISE
; L8006: ;C34481
; JP PLAY_SOUND ;THIS IS THE CALL POINT
MUTE_STC:
LD HL,MUTE_LIST
JP MUTE_AY
.byte 0,0,0,0,0,0,0,0,0,0,0,0,0
MUTE_LIST:
.byte 0
;FOR INTTERUPT DRIVEN
;SOUND EVERY 1/50TH SECOND
INIT_STC:
;STC_STUFF
SONG_ADDRESS = $+1
LD HL,D_MIRROR ;START OF SONG DATA
LD A,(HL) ;GET DELAY VALUE
LD (PL_DELAY_VALUE),A ;STORE IT
LD (SONG_START_BASE_ADDRESS),HL ;STORE START OF SONG DATA
INC HL ;POINT TO NEXT BYTE IN SONG DATA
;THIS SHOULD POINT TO POSITIONS MAP
CALL GET_NEXT_WORD_AND_ADD_BASE ;GET A WORD + OFFSET INTO DE, RETURNS POINTER TO
;ACTUAL BYTE. I.E. START OF SONG (BASE) + OFFSET
;ON ENTRY HL = ADRRESS OF LOW BYTE
;ON EXIT HL = (ENTRY HL+2)
;DE = WORD fetched
LD A,(DE) ;GET NUMBER OF PATTERNS IN SONG
;THIS IS COUNT FROM ZERO, so A VALUE OF 1 MEANS 2 PATTERNS LONG
;A VALUE OF 2 MEANS 3 PATTERNS LONG AND SO ON...
INC DE ;POINT TO NEXT BYTE IN POSITIONS MAP
INC A ;SEE ABOVE, A NOW HOLDS TRUE NUMBER OF PATTERNS
LD (NUMBER_OF_PATTERNS_IN_SONG),A ;SAVE NUMBER OF PATTERNS
LD (PTR_POSITIONS_DATA),DE ;SAVE POINTER TO POSITIONS DATA
CALL GET_NEXT_WORD_AND_ADD_BASE ;GET A WORD + OFFSET INTO DE
;THIS SHOULD POINT TO THE ORNAMENTS
LD (PTR_COMPILED_ORNAMENTS),DE ;STORE POINTER TO COMPILED ORNAMENTS
PUSH DE ;TEMP SAVE THAT POINTER VALUE
CALL GET_NEXT_WORD_AND_ADD_BASE ;GET A WORD OFFSET
;THIS SHOULD POINT TO THE PATTERN DATA
LD (PTR_PATTERNS_DATA),DE ;SAVE POINTER TO PATTERNS DATA
LD HL,$001B ;OFFSET TO COMPILED SAMPLES
;THEY ALWAYS START AT OFFSET $001B
;THERE IS SOME TEXT IN THE STC FILE
;THIS SKIPS OVER THE TEXT FIELD
CALL ADD_BASE_ADDRESS ;ADDS HL TO THE BASE ADDRESS OF
;THE SONG DATA
;RETURND WITH ADDRESS TO SAMPLE IN DE
;OLD DE IN HL
EX DE,HL ;SWAP DE AND HL BACK
LD (PTR_SAMPLES_DATA),HL ;SAVE POINTER TO SAMPLES
;=====================
;THE FOLLOWING CODE CLEARS (SET TO 0) THE CHANNEL PROGRAM DATA STORES
;AND ALSO CLEARS (SET TO 0) THE AY_DATA
;TOODO WORK OUT WHAT EXTRA BYTE IS FOR
; I THINK IT MAY BE JUST TO CAUSE THE CHANNEL PHARSER TO THINK THAT THE
; END OF THE PATTERN DATA HAS BEEN REACHED, AND TO GO TO THE NEXT PATTERN.
; THE BYTE IT POINTS TO IS $FF
; PATTERN DATA IS TERMINATED WITH $FF
LD HL,EXTRA_UNKNOWN_BYTE ;??? NOT KNOWN YET
LD (PTR_CHANNEL_A_PATTERN_DATA),HL ;POINT CHANNEL A TO BYTE $FF
LD HL,CHANNEL_1_PROG_STORE_MINUS_2
LD DE,CHANNEL_1_PROG_STORE_MINUS_1
LD BC,$002C ;44 BYTES
LD (HL),B ;LOAD $00 INTO FIRST BYTE
LDIR ;FILL THE REST OF THE CHANNEL CONTROL VARS AND DATA TO SEDN TO AY WITH ZERO
POP HL ;RETRIEVE POINTER TO COMPILED ORNAMENTS...
LD BC,$0021 ;EACH COMPILED ORNAMENT IS 33 BYTES LONG
;BYTE 1 = ORNAMENT NUMBER
;NEXT 32 = ORNAMENT DATA
;POSITIVE NUMBERS = NORMAL
;NEGATIVE NUMBERS = 2'S COMPLIMENT !
XOR A ;ZERO A REGISTER
CALL mphlwastpbc ;LOOKS FOR A ZERO AT ORNAMENT NUMBER
;AND RETURNS WITH HL POINTING THERE
;NOT SURE WHY BECAUSE IF I HAVE READ
;THE STRUCTURE OF THE STC FILE CORRECTLY
;THE FIRST ORNAMENT IS ALWAYS ZERO
; BUT IT SEEMS THAT DIFFERENT COMPILERS CAN PUT THEM IN ANY ORDER....
DEC A ; MAKE A = 255
LD (CHANNEL_1_PROG_STORE_PLUS_7),A ;ENTRY IN CHANNEL 1 PROG DATA
LD (CHANNEL_2_PROG_STORE_PLUS_7),A ;ENTRY IN CHANNEL 2 PROG DATA
LD (CHANNEL_3_PROG_STORE_PLUS_7),A ;ENTRY IN CHANNEL 3 PROG DATA
;DEFAULT START VALUES
LD A,$01 ;INITIAL VALUE FOR DELAY COUNT
LD (DELAY_COUNT),A ;WHEN PLAY_SONG IS FIRST CALLED
;WILL MAKE IT START AT A NEW LINE.
INC HL ;SHOULD BE POINTING TO ORNAMENT 0, SO MAKE IT PONT TO ORNAMENT 0 DATA
LD (CHANNEL_1_PROG_STORE_PLUS_5),HL ;ENTRY IN CHANNEL 1 PROG DATA
LD (CHANNEL_2_PROG_STORE_PLUS_5),HL ;ENTRY IN CHANNEL 2 PROG DATA
LD (CHANNEL_3_PROG_STORE_PLUS_5),HL ;ENTRY IN CHANNEL 3 PROG DATA
;ALL CHANNELS POINT TO ORNAMENT ZERO (EMPTY ORNAMENT, ALL ZEROS)
CALL SEND_DATA_TO_AY ;PROG REGISTERS
;ALL AY DATA SET TO ZERO
;SHOULD SILENCE IT.
; EI ;INITIALIZZE COMPLETE
RET ;YEAH
;=============
; PLAY CONTROL VARS
;=============
PTR_POSITIONS_DATA:
.word $0000
PTR_COMPILED_ORNAMENTS:
.word $0000
PTR_PATTERNS_DATA:
.word $0000
PTR_SAMPLES_DATA:
.word $0000
PL_DELAY_VALUE:
.byte $00
DELAY_COUNT:
.byte $01
NUMBER_OF_PATTERNS_IN_SONG:
.byte $01
PTR_CHANNEL_A_PATTERN_DATA:
.word $E745
PTR_CHANNEL_B_PATTERN_DATA:
.word $678C
PTR_CHANNEL_C_PATTERN_DATA:
.word $67D8
EXTRA_UNKNOWN_BYTE:
.byte $FF
CHANNEL_1_PROG_STORE_MINUS_2:
.byte 0 ;SOME KIND OF FLAG, $00 = ORNANMENT , $01 = AY_ENVELOPE , $02 = USE NOISE
;THINK IT IS TO SAY WHAT LAST BYTE OF PATTERN DATA IS USED FOR.
CHANNEL_1_PROG_STORE_MINUS_1:
.byte 0 ;could be dual purpose...
;but one use is to hold the NUMBER OF BLANK LINES, USED TO RESET THE BLANK LINES COUNTER
; I THINK IT ALSO HOLDS THE REPEAT COUNTER FOR THE SAMPLES
CHANNEL_1_PROG_STORE_BASE:
;TOODO WORK OUT WHAT +$00 IS ?
.byte 0 ;??? CURRENT SAMPLE/ORNAMENT STEP ???
.byte 0 ;??? NOTE VALUE ???
.byte 0 ;BLANK LINES COUNTER
CHANNEL_1_PROG_STORE_PLUS_3:
.byte 0 ;POINTS TO SAMPLE DATA FOR CHANNEL
.byte 0
CHANNEL_1_PROG_STORE_PLUS_5:
.byte 0 ;POINTS TO ORNAMEWNT DATA FOR CHANNEL
.byte 0
CHANNEL_1_PROG_STORE_PLUS_7:
.byte 0 ;REPEAT LENGTH
;COUNTS DOWN
;RESET TO 32 WHEN A NEW NOTE STARTS
;CHANNEL_2_PROG_STORE_MINUS_2
c2psm2:
.byte 0
;CHANNEL_2_PROG_STORE_MINUS_1
c2psm1:
.byte 0
CHANNEL_2_PROG_STORE_BASE:
.byte 0
.byte 0
.byte 0
CHANNEL_2_PROG_STORE_PLUS_3:
.byte 0 ;POINTS TO SAMPLE DATA FOR CHANNEL
.byte 0
CHANNEL_2_PROG_STORE_PLUS_5:
.byte 0
.byte 0
CHANNEL_2_PROG_STORE_PLUS_7:
.byte 0
;CHANNEL_3_PROG_STORE_MINUS_2
L8096:
.byte 0
;CHANNEL_3_PROG_STORE_MINUS_1
L8097:
.byte 0
CHANNEL_3_PROG_STORE_BASE:
.byte 0
.byte 0
.byte 0
CHANNEL_3_PROG_STORE_PLUS_3:
.byte 0 ;POINTS TO SAMPLE DATA FOR CHANNEL
.byte 0
CHANNEL_3_PROG_STORE_PLUS_5:
.byte 0
.byte 0
CHANNEL_3_PROG_STORE_PLUS_7:
.byte 0
PL_CURRENT_POSITION:
.byte 0
DATA_TO_SEND_TO_AY:
AY_DATA_TONE_CHAN_A:
AY_REG0: ;TONE CHANNEL A, FINE
.byte 0
AY_REG1: ;TONE CHANNEL A, COARSE (LOWER 4 BITS, HIGH BYTE)
.byte 0
AY_DATA_TONE_CHAN_B:
AY_REG2: ;TONE CHANNEL B, FINE
.byte 0
AY_REG3: ;TONE CHANNEL B, COARSE (LOWER 4 BITS, HIGH BYTE)
.byte 0
AY_DATA_TONE_CHAN_C:
AY_REG4: ;TONE CHANNEL C, FINE
.byte 0
AY_REG5: ;TONE CHANNEL C, COARSE (LOWER 4 BITS, HIGH BYTE)
.byte 0
AY_DATA_NOISE_FREQ:
AY_REG6: ;NOISE GEN CONTROL (LOWER 5 BITS ONLY)
.byte 0
AY_DATA_MIXER:
AY_REG7: ;MIXER CONTOLR, b6,b7 io control, b5,b4,b3 = NOISE C,B,A, b2,b1,b0 = TONE C,B,A - A ZERO = ON.
.byte 0
AY_DATA_AMP_A:
AY_REG8: ;APPLITUDE CONTROL CHANNEL A, b7,b6,b5 NOT USED, b4 = AMPLITUDE 'MODE', b3,b2,b1,b0 = AMPLITUDE
.byte 0
AY_DATA_AMP_B:
AY_REG9: ;APPLITUDE CONTROL CHANNEL B, SEE ABOVE
.byte 0
AY_DATA_AMP_C:
AY_REG10: ;APPLITUDE CONTROL CHANNEL C, SEE ABOVE
.byte 0
AY_DATA_ENV_FREQ: ;THE CODE ONLY SEEMS TO EVER CHANGE THE FINE, COARSE REMAINS AT 0
AY_REG11: ;ENVELOPE FREQUENCY FINE
.byte 0
AY_REG12: ;ENVELOPE FREQUECY COARSE
.byte 0
END_DATA_TO_SEND_TO_AY:
AY_DATA_ENV_SHAPE:
AY_REG13:
.byte 0 ;ENVELOPE SHAPE (EFFECT)
;==============
;= tests for a match
;= between contents of memory pointed to by HL
;= with contents of A. if a match is found returns imediatly
;=
;= else hl incremented by BC and the new location is retested
;=
;= preserved DE, BC, A
;= altered HL, points to address of match
;==============
mphlwastpbc:
CP (HL) ;TEST BYTE POINTED TO BY HL WITH A
RET Z ;RETURN IF EQUAL
ADD HL,BC ;ELSE ADD BC
JP mphlwastpbc ;AND LOOP ROUND
;==============
;= =
;= SUBROUTINE =
;= =
;= gets the word pointed to by HL
;= adds contents of (SONG_START_BASE_ADDRESS) tot hat word
;= and returns result in DE
;=
;= preserved a
;= borked BC, HOLDS BASE ADDRESS
;= altered DE = new address
;= altered HL = HL + 2
;==============
GET_NEXT_WORD_AND_ADD_BASE:
LD E,(HL)
INC HL
LD D,(HL)
INC HL
EX DE,HL
SONG_START_BASE_ADDRESS = $+1
ADD_BASE_ADDRESS:
LD BC,$0000 ;SELF MODIFING CODE !
ADD HL,BC
EX DE,HL
RET
;==============
;= =
;= SUBROUTINE =
;= =
;==============
; ON ENTRY IY POINTS TO SAMPLE DATA FOR THE CHANNEL
; IB ENTRY A = CURENT STEP
;
; ON EXIT
;
; H = NOISE VALUE FOR CURRENT STEP
; L = ENV VALUE
; DE = EFFECT VALUE (BIT 4 OF D IS SIGN, VALUE IS 3 NIBBLES)
;
; B = 2 FOR TONE, 0 FOR NO TONE
; C = 16 FOR NOISE, 0 FOR NO NOISE
PROCESS_SAMPLE_DATA:
LD D,$00
LD E,A ; DE = A
ADD A,A ; A = A *2
ADD A,E ; A = A * 3
LD E,A ; DE = A * 3
ADD IY,DE ; IY NOW POINTS TO CURRENT SAMPLE DATA, FOR THIS STEP
LD A,(IY+$01) ; b7 NOISE MASK, b6 ENV MASK, b5 SIGN FOR EFFECT, b4-0 NOISE VAL
BIT 7,A ; TEST BIT 7, NOISE MASK
LD C,$10 ; PREPARE C = 16
JP NZ,KEEP_C ;
LD C,D ; ELSE C = ZERO
KEEP_C:
BIT 6,A ; TEST BIT 6, TONE MASK
LD B,$02 ; PREPARE B = 2
JP NZ,KEEP_B
LD B,D ; ELSE B =0
KEEP_B:
AND $1F ; NOISE VAL ONLY
LD H,A ; KEEP IT IN H
LD E,(IY+$02) ; LOW BYET EFFECT
LD A,(IY+$00) ; b7-4 HIGH PART OF EFFECT. b3-0 ENV VOL
PUSH AF ; TEMP SAVE
AND $F0 ; HIGH PART OF EFFECT
RRCA
RRCA
RRCA
RRCA
LD D,A ; HIGH PART OF EFFECT, DE NOW HOLDS EFFECT
POP AF ; RETRIEVE PREVIOUS
AND $0F ; ENV VOL ONLY
LD L,A ; ENV VOLUME
; SO H HOLDS NOISE VAL, L HOLDS ENV VAL
BIT 5,(IY+$01) ; TEST SIGN OF EFFECT
RET Z ; RETURN IF ZERO. FOR ADDITION
SET 4,D ; ELSE SET BIT 4 IN D, FOR SUBTRACTION
RET
;==============
;= =
;= SUBROUTINE =
;= =
;=====================================
;= =
;= SETS THE NEXT PATTERN TO PLAY =
;= =
;=====================================
NEXT_PATTERN:
LD A,(PL_CURRENT_POSITION) ;CURRENT POSITION
;FIRST RUN POSITION = 0
LD C,A
LD HL,NUMBER_OF_PATTERNS_IN_SONG ;POINT TO NUMBER OF PATTERNS
CP (HL) ;A - (HL) CARRY SET IF (HL) > A
JP C,NEXT_POSITION ;JP IF (HL) > A, MORE POSITIONS TO GO
;ELSE BACK TO POSITION 1.
XOR A ;RESET CURRENT POSITION
LD C,A
NEXT_POSITION:
INC A ; A = 1 IF RESET CURREN POSITION
; OR A = A+1 (NEXT POSITION)
LD (PL_CURRENT_POSITION),A ; STORE NEW CURRENT POSITION.
LD L,C ; CURRENT POSITION NUMBER LESS ONE !
LD H,$00 ; INTO HL
ADD HL,HL ; DOUBLE IT (INDEX TO ,2 BYTES PER VALUE, TABLE
LD DE,(PTR_POSITIONS_DATA) ; GET THE START OF THE POSITIONS TABLE )
ADD HL,DE ; ADD THE INDEX INTO TABLE
LD C,(HL) ; GET VALUES FROM THE POSITIONS MAP
INC HL ;
LD A,(HL) ; C (LOW BYTE [PATTERN TO USE]) AND A (HEIGTH OF PATTERN)
LD (PL_CURRENT_HEIGHT),A ; SAVE HEIGHT BYTE
LD A,C ; PATTERN NUMBER INTO A
LD HL,(PTR_PATTERNS_DATA) ; GET POINTER TO PATTERNS DATA TABLE
;
; THIS IS A TABLE WITH 7 BYTES PER ENTRY
; BYTE 1 = PATTERN NUMBER
; BYTES 2/3 = OFFSET TO CHANNEL A PATTERN DATA
; BYTES 4/5 = OFFSET TO CHANNEL B PATTERN DATA
; BYTES 6/7 = OFFSET TO CHANNEL C PATTERN DATA
LD BC,$0007 ; USE AS INCREMENT WHEN STEPPING THROUGH PATTERN DATA TABLE
CALL mphlwastpbc ; STEP THROUGH PATTERNS DATA UNTIL A MATCH IS FOUND
INC HL ; ROUTINE ABOVE LEAVES HL POINTING AT THE PATTERN NUMBER THAT MATCHED
; INC HL SO IT POINTS TO NEXT BYTE IN PATTERN DATA TABLE
CALL GET_NEXT_WORD_AND_ADD_BASE ; GET THE POINTER TO CHANNEL A PATTERN
LD (PTR_CHANNEL_A_PATTERN_DATA),DE ; STORE IT
CALL GET_NEXT_WORD_AND_ADD_BASE ; CHANNEL B
LD (PTR_CHANNEL_B_PATTERN_DATA),DE ; STORE IT
CALL GET_NEXT_WORD_AND_ADD_BASE ; CHANNEL C
LD (PTR_CHANNEL_C_PATTERN_DATA),DE ; AND STORE IT
RET
;==============
;= =
;= SUBROUTINE =
;= =
;==============
DECREMENT_COUNTER:
DEC (IY+$02) ;DECREMENT THE CURRENT CHANNELS BLANK LINE COUNTER.
RET P ;RETURN IF BIT 7 RESET SET ABOVE
;AS THAT MEMORY LOCATION HOLDS ZERO ON FIRST RUN, THIS OPERATION WILL ALWAYS CONTINUE HERE
; BUT THE VALUES ARE STILL MEANINGLESS (ON FIRST RUN)
LD A,(IY-$01) ;ELSE GET RESET VALUE
LD (IY+$02),A ;AND RESET IT
RET
;==================
;= =
;= MAIN PLAY LOOP =
;= =
;=============================
;= =
;= CALL EVERY 1/50 TH SECOND =
;= =
;=============================
PLAY_STC:
LD A,(DELAY_COUNT) ;GET CURRENT DELAY COUNT
DEC A ;DECREMENT IT
LD (DELAY_COUNT),A ;AND SAVE NEW VALUE
JP NZ,PROCESS_CHANNELS ;JP IF DELAY COUNT HAS NOT REACHED ZERO
;IN OTHER WORDS
;CONRINUE TO POST PROCESS THE CHANNELS
;STILL DOING SAME LINE IN PATTERN
;ON FIRST CALL TO PLAY_SOUND (AFTER INITALISE) THE DELAY COUNT WAS SET TO 1
;SO WE WILL ALWAYS ARRIVE HERE...
DELAY_COUNTDOWN_REACHED_ZERO:
LD A,(PL_DELAY_VALUE) ;GET THE DELAY VALUE FOR THIS SONG.
LD (DELAY_COUNT),A ;RESET DELAY COUNTER
LD IY,CHANNEL_1_PROG_STORE_BASE ;SET UP INDEX REGISTER FOR CHANNEL 1
CALL DECREMENT_COUNTER ;DECREMENT BLANK LINE COUNTER
;AND RESETS IT WHEN IT REACHES 0
;READY FOR NEXT DATA ???
;IF BLANK LINE COUNTER WAS RESET THEN WE
;WILL SKIP THE FOLLOWING JUMP AND
;PROCESS THE NEXT PATTERN DATA BYTE FOR
;CHANNEL 1
JP P,NEW_DATA_CHANNEL_2 ;IF STILL POSITIVE (SUB CALLED ABOVE)
;THEN JUMP (SKIP CHANNEL)
;ON FIRST RUN WE WILL END UP HERE...
NEW_DATA_CHANNEL_1:
;===============
;IF WE ARE HERE THEN THERE SHOULD BE NEW DATA IN THE PATTERN DATA FOR CHANNEL 1
LD HL,(PTR_CHANNEL_A_PATTERN_DATA) ;POINTS TO CHANNEL DATA IN CURRENT PATTERN ?
;
;ON FIRST RUN THIS IS POINTING TO $FF (FROM THE INITALISE ROUTINE)
;PATTERN DATA TERMINATED WITH $FF
LD A,(HL) ;GET THE BYTE POINTED TO
INC A ;IS IT THE TERMINATOR ?
CALL Z,NEXT_PATTERN ;THEN GET THE NEXT PATTERN FOR ALL CHANNELS
;NOTICE THIS IS A "CALL..."
;SO WHEN WE RETURN HERE, THE PATTERN DATA POINTER FOR EACH OF THE CHANNELS ARE SET
;AND POINT TO THE MATCHING PATTERNS FOR POSITION 1.
LD HL,(PTR_CHANNEL_A_PATTERN_DATA) ;POINTS TO CHANNEL DATA
;IY STILL POINTING TO CHANNEL_1_PROG_STORE_BASE
CALL PROCESS_CHANNEL_DATA ;PROCESS CHANNEL DATA
;RETURNS WITH HL POINTING TO THE
;NEXT UN-PROCESSED BYTE IN THAT CHANNEL
LD (PTR_CHANNEL_A_PATTERN_DATA),HL
NEW_DATA_CHANNEL_2:
LD IY,CHANNEL_2_PROG_STORE_BASE ;INDEX FOR CHANNEL 2
CALL DECREMENT_COUNTER
JP P,NEW_DATA_CHANNEL_3
LD HL,(PTR_CHANNEL_B_PATTERN_DATA)
CALL PROCESS_CHANNEL_DATA
LD (PTR_CHANNEL_B_PATTERN_DATA),HL
NEW_DATA_CHANNEL_3:
LD IY,CHANNEL_3_PROG_STORE_BASE ;INDEX FOR CHANNEL 3
CALL DECREMENT_COUNTER
JP P,PROCESS_CHANNELS
LD HL,(PTR_CHANNEL_C_PATTERN_DATA)
CALL PROCESS_CHANNEL_DATA
LD (PTR_CHANNEL_C_PATTERN_DATA),HL
JP PROCESS_CHANNELS ;CHANNEL PROCESSING DONE
;NOW LETS TURN THAT INTO AY DATA
;==============
;= =
;= SUBROUTINE =
;= =
;==============
;
; THIS ROUTINE DOES NOT RETURN TO CALLING CODE
; DIRECTLY, BUT RETURNS FROM ONE OF THE
; SECTIONS OF CODE JUMPED TO
; ACCORDING TO THE DATA BYTE VALUE
PROCESS_CHANNEL_DATA:
LD A,(HL) ;GET NEXT BYTE OF PATTERN DATA FOR CHANNEL
CP $60 ;A < $60 THEN IS NOTE DATA.
JP C,NOTE_DATA ;A = $0 THRU $5F ~ BITS 0-4 = NOTE IN SEMITONES $00 = C-1
CP $70 ;A => $60 < $70 ?
JP C,SAMPLE_NUMBER ;A = $60 THRU $6F ~ BITS 0-4 = SAMPLE NUMBER
CP $80 ;A => $70 < $80 ?
JP C,ORNAMENT_NUMBER ;A = $70 THRU $7F ~ BITS 0-4 = ORNAMENT NUMBER
JP Z,PLREST ;A = $80 ~ REST (STOP CHANNEL)
CP $81 ;A = $81 ?
JP Z,EMPTY_LOCATION ;A = $81 ~ EMPTY LOCATION ???
CP $82 ;A = $82
JP Z,ORNAMENT_ZERO ;A = $82 ~ SELECTS ORNAMENT 0
CP $8F ;A => $83 < $8F
JP C,EFFECT_NUMBER ; A = $83 < $8E ~ SELECTS EFFECT
SUB $A1 ; ELSE SUBRACT 161 FROM A
LD (IY+$02),A ; COUNTER FOR NUMBER OF BLANK LINES
LD (IY-$01),A ; AND ERM ???
INC HL ; POINT TO NEXT BYTE
JP PROCESS_CHANNEL_DATA ; AND LOOP ROUND TO PROCESS NEXT BYTE
;===============
;= =
;= ROUTINE =
;= =
;=================================
;= =
;= DEALS WITH SEMITONE NOTE DATA =
;= =
;=================================
NOTE_DATA:
LD (IY+$01),A ; STORE THE NOTE DATA BYTE (IN SEMITONES)
LD (IY+$00),$00 ; NEW NOTE STARTS AT STEP 0
LD (IY+$07),$20 ; AND 32 STEPS
;= THIS POINT IS JUMPED OT FOR AN EMPTY LOCATION
EMPTY_LOCATION:
INC HL ; POINT OT NEXT BYTE
RET ; DONE
;===============
;= =
;= ROUTINE =
;= =
;=================================
;= =
;= DEALS WITH SAMPLE NUMBER DATA =
;= =
;=================================
SAMPLE_NUMBER:
SUB $60 ; SUBTRACT $60 TO GET A 0 TO 15 NUMBER
PUSH HL ; TEMP SAVE CHANNEL DATA POINTER
LD BC,$0063 ; SAMPLE ARE 99 BYTES LONG
LD HL,(PTR_SAMPLES_DATA) ; BASE ADDRESS OF SAMPLES DATA
CALL mphlwastpbc ; STEP THROUGH SAMPLES DATA
; UNTIL SAMPLE NUMBER IS FOUND
INC HL ; POINT TO FIRST BYTE OF SAMPLE
LD (IY+$03),L
LD (IY+$04),H ; SAVE PTR TO SAMPLE IN CHANNEL PROG DATA
POP HL ;RETRIEVE CHANNEL DATA POINTER
INC HL ;POINT TO NEXT BYTE
JP PROCESS_CHANNEL_DATA ;LOOP BACK AND DEAL WITH NEXT BYTE
;===============
;= =
;= ROUTINE =
;= =
;=================================
;= =
;= TURNS OFF CHANNEL =
;= =
;=================================
PLREST:
INC HL ;POINT TO NEXT BYTE IN CHANNEL DATA
PLREST_2:
LD (IY+$07),$FF ; CODE FOR OFF (NO SOUND FROM CHANNEL)
; THAT LOCATION IS THE SAMPLE/ORN STEP
RET ;DONE
;===========
;= =
;= ROUTINE =
;= =
;=====================
;= =
;= SELECT ORNAMENT 0 =
;= =
;=====================
ORNAMENT_ZERO:
XOR A
JR ORNAMENT_ZERO_2 ;JUMP TO ORNAMENT SELECT ROUTINE, BUT JUMP OVER NORMALIZE SUB
;===========
;= =
;= ROUTINE =
;= =
;=====================
;= =
;= SELECT ORNAMENT =
;= =
;=====================
ORNAMENT_NUMBER:
SUB $70 ; SUBTRACT $70 TO GIVE A 0 TO 15 VALUE
ORNAMENT_ZERO_2:
PUSH HL ; SAVE CHANNEL DATA POINTER
LD BC,$0021 ; ORNAMENTS ARE 33 BYTES LONG (1 BYTE, NUMBER + 32 BYTES, DATA)
LD HL,(PTR_COMPILED_ORNAMENTS) ; ORNAMENTS BASE ADDRESS
CALL mphlwastpbc ; STEP OVER ORNAMENST UNTIL A MATCH IS FOUND
INC HL ; POINT TO FIRST BYTE OF ORNAMENT DATA
LD (IY+$05),L ;
LD (IY+$06),H ; SAVE ADDRESS OF ORNAMENT IN CHANNEL PROG DATA
LD (IY-$02),00 ; AND SET FLAG TO SAY ORNAMENT IN USE
POP HL ; RETRIEVE CHANNEL DATA POINTER
INC HL ; POINT TO NEXT BYTE
JP PROCESS_CHANNEL_DATA ; LOOP BACK AND PROCESS NEXT BYTE
;===============
;= =
;= ROUTINE =
;= =
;=================================
;= =
;= SELECT AN EFFECT =
;= =
;=================================
EFFECT_NUMBER:
SUB $80 ; SUBTRACT $80
LD (AY_DATA_ENV_SHAPE),A ; STORE IN the DATA TO SEND TO AY TABLE
INC HL ; POINT TO NEXT BYTE IN CHANNEL DATA
LD A,(HL) ; NEXT BYTE IS ENV FREQ VALUE.
INC HL ; POINT TO NEXT BYTE IN THE CHANNEL DATA
LD (AY_DATA_ENV_FREQ),A ; STORE IN the DATA TO SEND TO AY TABLE
LD (IY-$02),$01 ; SET THE FLAG TO AY_ENV_SHAPE IN USE
PUSH HL ; TEMP SAVE CHANNEL DATA POINTER
;WHEN USING AN EFFECT ORNAMENT IS SET TO ZERO
XOR A
LD BC,$0021 ; 33 BYTES IN EACH ORNAMENT
LD HL,(PTR_COMPILED_ORNAMENTS) ; COMPILED ORNAMENTS BASE ADDRESS
CALL mphlwastpbc ; STEP OVER EACH ORNAMENT TILL
; A MATCH IS FOUND
INC HL ;POINT TO FIRST BYTE OF ORNAMENT DATA
LD (IY+$05),L
LD (IY+$06),H ; STORE ORNAMENT PTR IN CHANNEL_PROG STORE
POP HL ; RETRIEVE CHANNEL DATA POINTER
JP PROCESS_CHANNEL_DATA ; LOOP BACK AND PROCESS NEXT BYTE
;==============
;= =
;= SUBROUTINE =
;= =
;==============
DO_REPEAT:
LD A,(IY+$07) ; GET CURRENT REPEAT COUNTDOWN ?
INC A
RET Z ; RETURN IF A WAS $FF (NOW $00), CHANNEL STAY MUTED TILL NEW NOTE
DEC A ; RESTORE A TO PREVIOUS VALUE
DEC A ; AND DECREMENT COUNTER
; !!! ZERO FLAG USED BELOW !!!
LD (IY+$07),A ; STORE NEW COUNT DOWN VALUE
PUSH AF ; TEMP SAVE NEW COUNT VALUE AND FLAGS !!!
LD A,(IY+$00) ; GET SAMPLE/ORN STEP
LD C,A ; PUT IT IN C
INC A ; INCREMENT IT
AND $1F ; KEEP ONLY LOWEST 5 BITS
LD (IY+$00),A ; STORE IT
POP AF ; RETRIEVE PREVIOUS A AND FLAGS !!!
RET NZ ; RETURN IF DOUBLE DEC A ABOVE RESULT WAS <> 0
LD E,(IY+$03) ; ELSE
LD D,(IY+$04) ; RETRIEVE THE SAMPLE PTR FOR THIS CHANNEL
LD HL,$0060
ADD HL,DE ; ADD $0060 TO THE SAMPLE PTR (POINTS TO REPEAT VALUE)
LD A,(HL) ;
DEC A ; IF REPEAT VALUE IS 0 THEN PLAY ONLY ONCE.
JP M,PLREST_2 ; JUMP IF REAPEAT COUNT ROLLS FROM 255 TO 0
; TO SILENCE CHANNEL, STORES $FF IN REPEAT COUNTER AND RET'S TO CALLING CODE
LD C,A ; REPEAT - 1 INTO C
INC A ; RESTORE PREVIOUS VALUE
AND $1F ; KEEP LOWEST 5 BITS ( 0 TO 31 )
LD (IY+$00),A ; STORE START STEP FOR REPEAT
INC HL ; POINT TO REAPEAT LENGTH
LD A,(HL) ; GET REPEAT LENGTH
INC A ; +1 BEFORE STORING
LD (IY+$07),A ; STORE IT
RET
;==============
;= =
;= SUBROUTINE =
;= =
;==============
SET_NOISE_FREQ:
LD A,C
OR A ; IS A = 0
RET NZ ; NOPE LEAVE NOISE FREQ AS IS
LD A,H ; ELSE H HOLDS NOISE FREQ TO USE
LD (AY_DATA_NOISE_FREQ),A
RET
;==============
;= =
;= SUBROUTINE =
;= =
;==============
CLEAR_AY_ENV_SHAPE:
LD A,(IY+$07)
INC A ; MUTE CHANNEL ?
RET Z ; YES LEAVE AY ENEVLOPE ALONE
LD A,(IY-$02)
OR A ; ELSE TEST FLAG BYTE
RET Z ; ORNAMENT IN USE
CP $02 ; TEST FOR NOISE IN USE
JP Z,AY_ENV_ZERO ; IF NOISE IN USE THEN SET AY_ENV_SHAPE TO ZERO
LD (IY-$02),$02 ; ?? SET NOISE IN USE.
JP AY_ENV_SKIP
AY_ENV_ZERO:
XOR A
LD (AY_DATA_ENV_SHAPE),A
AY_ENV_SKIP:
SET 4,(HL) ; SET THE AY_AMPLITUDE_MODE BIT FOR THIS CHANNEL
; IE. USE SAMPLE ENVELOPE VALUE FOR VOLUME
; INSTEAD OF AY_ENVELOPE
RET
;==============
;=
;= ROUTINE CONTINUES FROM CHANNEL PROCESSOR
;=
;==============
;PROCESS CHANNEL 1
PROCESS_CHANNELS:
LD IY,CHANNEL_1_PROG_STORE_BASE ; PROCCESS CHANNEL 1
CALL DO_REPEAT ; DO REPEATS ECT ?
; RETURNS CURRENT STEP VALUE IN C ???
LD A,C ;
LD (ORNAMENT_STEP_NUMBER),A ; STORE IT (MAYBE SELF MODIFING CODE AGAIN)
LD IY,(CHANNEL_1_PROG_STORE_PLUS_3) ;
CALL PROCESS_SAMPLE_DATA ; ON EXIT
;
; H = NOISE VALUE FOR CURRENT STEP
; L = ENV VALUE
; DE = EFFECT VALUE (BIT 4 OF D IS SIGN, VALUE IS 3 NIBBLES)
;
; B = 0 FOR TONE, 2 FOR NO TONE
; C = 0 FOR NOISE, 16 FOR NO NOISE
LD A,C
OR B ; MIX THE NOISE AND TONE MASKS
RRCA ; MOVE INTO CORRECT POSITION
LD (AY_DATA_MIXER),A ; AND STORE IT
LD IY,CHANNEL_1_PROG_STORE_BASE ; INDEX REGISTER BACK TO CHANNEL PROG STORE
LD A,(IY+$07) ; TEST FOR TUNE OFF CHANNEL $FF MEANS TURN OFF CHANNEL
INC A
JP Z,CHANNEL_1_VOL_ZERO ;
;
CALL SET_NOISE_FREQ ; ELSE DO NOISE FREQ
CALL GET_NOTE_FREQUENCY
LD (AY_DATA_TONE_CHAN_A),HL ; STORE FREQUENCY FOR THIS CHANNEL
CHANNEL_1_VOL_ZERO:
LD HL,AY_DATA_AMP_A
LD (HL),A ; IS EITHER SAMPLE ENVELOPE VALUE OR ZERO
CALL CLEAR_AY_ENV_SHAPE
;PROCESS CHANNEL 2
LD IY,CHANNEL_2_PROG_STORE_BASE
CALL DO_REPEAT
LD A,(IY+$07)
INC A
JP Z,CHANNEL_2_VOL_ZERO
LD A,C ; SAMPLW/ORN STEP
LD (ORNAMENT_STEP_NUMBER),A
LD IY,(CHANNEL_2_PROG_STORE_PLUS_3)
CALL PROCESS_SAMPLE_DATA
LD A,(AY_DATA_MIXER) ; GET CHANNEL 1 AY_MIXER VALUE
OR C
OR B ; MIX IN CHANNEL 2 MASKS
LD (AY_DATA_MIXER),A ; STORE NEW MIXER VALUE
CALL SET_NOISE_FREQ
LD IY,CHANNEL_2_PROG_STORE_BASE
CALL GET_NOTE_FREQUENCY
LD (AY_DATA_TONE_CHAN_B),HL
CHANNEL_2_VOL_ZERO:
LD HL,AY_DATA_AMP_B
LD (HL),A
CALL CLEAR_AY_ENV_SHAPE
;PROCESS CHANNEL 3
LD IY,CHANNEL_3_PROG_STORE_BASE
CALL DO_REPEAT
LD A,(IY+$07)
INC A
JP Z,CHANNEL_3_VOL_ZERO
LD A,C
LD (ORNAMENT_STEP_NUMBER),A
LD IY,(CHANNEL_3_PROG_STORE_PLUS_3)
CALL PROCESS_SAMPLE_DATA
LD A,(AY_DATA_MIXER)
RLC C
RLC B
OR B
OR C ;MIX CHANNEL 3 MASKS
LD (AY_DATA_MIXER),A
CALL SET_NOISE_FREQ
LD IY,CHANNEL_3_PROG_STORE_BASE
CALL GET_NOTE_FREQUENCY
LD (AY_DATA_TONE_CHAN_C),HL
CHANNEL_3_VOL_ZERO:
LD HL,AY_DATA_AMP_C
LD (HL),A
CALL CLEAR_AY_ENV_SHAPE
JP SEND_DATA_TO_AY ;PROCESSIGN DONE SET THE REGISTERS
;==============
;= =
;= SUBROUTINE =
;= =
;==============
GET_NOTE_FREQUENCY:
LD A,L
PUSH AF ; SAVE SAMPLE ENV VALUE
PUSH DE ; SAVE EFFECT VALUE
LD L,(IY+$05)
LD H,(IY+$06) ; HL POINTS TO ORNAMENT DATA
ORNAMENT_STEP_NUMBER = $+1 ; LOW BYTE OF ld de,$nnnn ONLY
LD DE,$0005 ; SELF MODIFYING CODE, ADDED TO BASE ADDRESS IN HL
ADD HL,DE ; HL NOW POINTS TO THE CORRECT STEP IN ORNAMENT
LD A,(IY+$01) ; GET THE CURRENT NOTE
ADD A,(HL) ; ADD THE ORNAMENT VALUE (SINGLE BYTE SIGNED, SO CAN SUBTRACT TOO)
PL_CURRENT_HEIGHT = $+1
ADD A,$00 ; ADD THE PATTERN HEIGHT
ADD A,A ; DOUBLE A (TONE TABLE IS WORDS)
LD E,A
LD D,$00 ; DE IS OFFSET INTO TONE TABLE
; NO BOUNDS CHECKING ???
LD HL,TONE_TABLE ; BASE ADDRESS OF TONE TABLE
ADD HL,DE ; HL NOW POINTS TO WORD FOR TONE DATA
LD E,(HL)
INC HL
LD D,(HL) ; PICK UP TONE DATA
EX DE,HL ; HL HOLDS TONE DATA
POP DE ; RETRIEVE ADDITIONAL EFFECT (FROM SAMPLE DATA)
POP AF ; RETRIEVE ENV VALUE
BIT 4,D ; TEST SIGN BIT IN EFFECT VAL (IT'S NOT REALLY A SIGNED VALUE)
; BUIT HAS A BIT TO SAY WETHER WE ADD OR SUB THE VALUE
; THIS LOOKS BACK TO FRONT, BUT...
; A HIHGER VALUE FOR TONE RESULTS IN A LOWER FREQUENCY
; IN SOUND OUTPUT
;
; SO AN ADDITIONAL EFFECT (HIGHER IN FREQUENCY OUTPUT)
; MUST BE SUBTRACTED FROM TONE DATA.
;
; AND THE OPOSITE IS TRUE FOR LOWER FREQUENCY
JR Z,SUB_EFFECT
RES 4,D ; CLEAR BIT BEFORE ADDING EFFECT VALUE
ADD HL,DE ; ELSE ADD EFFECT (LOWER FREQUENCY) |
RET
SUB_EFFECT:
AND A ; CLEAR CARRY IN PREP FOR SUBRACTION
; DOES NOT AFFECT SAMPLE ENV VALUE
SBC HL,DE ; SUBTRACT EFFECT (HIGHER FREQUENCY)
RET
;==============
;=
;= TONE TABLE
;=
;============