-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathPSGPLAYER.asm
841 lines (790 loc) · 42.4 KB
/
PSGPLAYER.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
**************************************
* This is a music player for Ed Snider's CoCo PSG Cartridge for the Tandy Color Computer
* The YM2149 sound chip is a clone of the AY-3-8910 Sound chip with a few differences
*
**************************************
* CoCoPSG background Player Version 1.00 - By Glen Hewlett
* Do whatever you want with this code...
* ************************************
* How to use this with your own assembly program:
* This player code assumes the music data you want to playback has already been copied to the CoCo PSG's own 512k RAM
* To make it easy to get your music in the correct format and stored in the CoCo PSG's RAM use the program mid2coco and use option 5
* It will create the PSGLOADR.BIN file that will need to be loaded from BASIC with the following commands:
* 1 POKE 3584,PEEK(375)
* 2 POKE 3585,PEEK(376)
* 3 LOADM"PSGLOADR.BIN"
* 4 LOADM"PSGPLAYER.BIN":' This is the name of this program with your code added...
*
* -Add your own program code starting at the bottom of this .asm file
* -If you need to use the VSync IRQ for your program you can add your code to that section of this .asm program since this program uses the VSync IRQ
* That section is identified around the middle of this program code where it says "IRQEntry:"
*
* Have fun :)
* Glen
*
* ************************************
* Playback of notes and velocity info on the CoCoPSG is done by sending the note and velocity data to the CoCo PSG.
* The data file includes a counter that is the number of every two vsync's which is 30 times a second in NTSC
* The player then counts down until the number reaches zero then the new notes or velocity info is sent to the CoCo PSG, etc.
* This means the player uses very little CPU cycles as a background process since it only reads the data and sends it to the
* CoCo PSG when the note or velocity change is needed and this countdown is done every 2nd vsync.
*
* 5 Byte header:
* Byte Value
* 00-02 'PSG' - Header Identifier
* 03 $xx - File version # currently only supprts a value of $01 for version 1.00
* 04 $xx - Number of times to loop this song where xx is:
* $00 - Don't change the number of loops value
* $01 to $FE - loop this number of times
* $FF - loop forever
* All bytes at this point are data bytes that are encoded as per the info below.
*
* First the descriptor byte
* Left nibble = # of vsyncs/2 to count until next change in tone or velocity is done
* $0x to $Fx = 0 to 15 delay count before next note or velocity change, add one to get real value of 1 to 16
*
* Right nibble = # of bytes to copy (1-11) count of bytes to copy max needed in current format is 3x3 = 9 bytes = (2 tone values, 1 velocity value for each of the 3 voices)
* or if the value of the right nibble is higher then 11 or $0B then do the following
* $x0 = 00 unused
* $xC = 12 signals the repeat count is larger then 16 so use the next byte as the repeat count (left nibble = bytes to copy)
* $xD = 13 signals the repeat count is larger then 256 so use the next 2 bytes as the repeat count (left nibble = bytes to copy)
* $xE = 14 Flip Channel C from/to noise or drum track
* $xF = 15 signals End of Music data
*
* Next process the data bytes
*
* My PSG playback format uses the following format for the data bytes
*
* ,------------- 0=Note, 1=Volume or channel C Noise/Drum frequency
* | ,,----- Channel 00=chan A, 01=chan B, 10=chan C, 11=chan C - Drum/Noise info
* | ||
* |Xxx xx|| x=volume or msb of 12 bit tone info or Xxxxx = 5 bit noise/drum frequency for channel C
* 1000 0001
* If it is a Note then it has the 4 msb of the 12 bit tone and the next byte has the 8 lsb's of the 12 bit tone value (so two bytes are always needed for tone data)
* Done, the next byte is the next encoded byte...
*
**************************************
* Info about the YM2149 used inside Ed Snider's CoCo PSD Cartridge for the Tandy Color Computer
* The YM2149 sound chip is a clone of the AY-3-8910 Sound chip with a few differences
*
* The YM2149 has three sound channels, A, B & C any of these three channels can output either Tone or Noise which is selectable by the user
*
* Specs for the YM2149 is here:
* http://www.ym2149.com/ym2149.pdf
*
* Info on playing back ym format songs is here:
* http://leonard.oxg.free.fr/ymformat.html
*
**************************************
* The YM2419 is used inside Ed Snider's CoCo PSG Cartridge for the Tandy Color Computer
*
* Registers
* ---------
*
* The AY-3-8910/8912 contains 16 internal registers as follows:
*
* Register Function Range
* 0 Channel A fine pitch 8-bit (0-255)
* 1 Channel A course pitch 4-bit (0-15)
* 2 Channel B fine pitch 8-bit (0-255)
* 3 Channel B course pitch 4-bit (0-15)
* 4 Channel C fine pitch 8-bit (0-255)
* 5 Channel C course pitch 4-bit (0-15)
*
* 6 Noise pitch 5-bit (0-31)
* 7 Mixer 8-bit (see below)
*
* 8 Channel A volume Bits 0 to 3 = 4-bit (0-15, see below) if bit 4 is set it signals this channel is using the Envelope info for the volume
* 9 Channel B volume Bits 0 to 3 = 4-bit (0-15, see below) if bit 4 is set it signals this channel is using the Envelope info for the volume
* 10 Channel C volume Bits 0 to 3 = 4-bit (0-15, see below) if bit 4 is set it signals this channel is using the Envelope info for the volume
*
* 11 Envelope fine duration 8-bit (0-255)
* 12 Envelope course duration 8-bit (0-255)
* 13 Envelope shape 4-bit (0-15)
*
* 14 I/O port A 8-bit (0-255)
* 15 I/O port B 8-bit (0-255)
*
* Note Frequency calculation
*
* The output frequency is equal to the IC's incoming clock frequency divided by 16 and then further divided by the number written to the course and fine pitch registers.
* The higher the number written to these, the lower the pitch.
*
* For example 1Khz is required and the CoCo PSG is using 2Mhz timing (Default) switchable to 1Mhz
* 2Mhz = 2000000 / 16 * Tone (Hz)
*
* Example if you want a tone of 440 Hz then take 440 * 16 = 7040
* Next do 2000000/7040 = 284.09
* Make the result an integer 284.09 is closer to 284 then 285 so use 284. Transpose the value 284 into a 12 bit number so it would be:
*
* Course values Fine Value
* Decimal 1x256 + 28
* Hexidecimal $01 + $1C
* Binary 0001 + 00011100
*
* Now that we know our Course (Most Significant) and the Fine (Least Significant) value we select which Channel we want to play back the 440 Hz tone
* we could use A, B or C. If you select channel B then we will need to send the numbers to Registers 2 & 3 as per the chart above.
* You could change the values of the course value and then the fine value, but I think it's best to change the fine value first, since the change isn't as drastic as changing the
* course value, even though it's done in the microseconds the change might be audible.
*
* This code sets the tone Frequency for audio channel B:
* LDA #2
* STA $FF5E * Select register 2 = B Fine value
* LDA #28
* STA $FF5F * Store value 28 into B Fine register
* LDA #3
* STA $FF5E * Select register 3 = B Course value
* LDA #1
* STA $FF5F * Store value 1 into the B Course register
*
* We should also set the volume level desired for the B Channel by doing the following:
* LDA #9
* STA $FF5E * Select register 9 = B Volume register
* LDA #15
* STA $FF5F * Store value 15 into B volume register, 15 is the loudest level setting, 0=lowest volume level
*
* Lastly we enable or disable the type of output we want from the B Channel using register 7 using the info from the chart below
* - The mixer (register 7) is made up of the following bits (low=enabled) the AY-3-8912 ignores bit 7 of this register.
* _ _
* I/O I/O Noise Noise Noise Tone Tone Tone
* B A C B A C B A
* Bit: 7 6 5 4 3 2 1 0
*
* To enable Channel B as Tone output we do the following:
* LDA #7
* STA $FF5E * Select register 7 = Mixer register
* LDA #$FD * $FD = 11111101 - Setting a value low (0) enables that option. We could enable C,B & A with a value of 11111000 or $F8
* STA $FF5F * Store value $FD to register 7
*
* At this point the tone will play continuously until you change the value of the tone or set the volume to 0 or change the Mixer register to turn off Channel B output
*
* Notes:
* - The AY-3-8912 does not contain register 15.
* - The volume registers (8, 9 and 10) contain a 4-bit setting but if bit 4 is set then that channel uses the envelope defined by register 13 and ignores its volume setting.
*
* Envelopes
* ---------
* The AY-3-8910/8912 contains the following preset envelopes or waveforms (set using control register 13). Note that these affect volume only and not the pitch:
*
* 0 \__________ single decay then off
*
* 4 /|_________ single attack then off
*
* 8 \|\|\|\|\|\ repeated decay
*
* 9 \__________ single decay then off
*
* 10 \/\/\/\/\/\ repeated decay-attack
* _________
* 11 \| single decay then hold
*
* 12 /|/|/|/|/|/ repeated attack
* __________
* 13 / single attack then hold
*
* 14 /\/\/\/\/\/ repeated attack-decay
*
* 15 /|_________ single attack then off
*
***************************************************
BANK0REG EQU $FF5A * Register for Memory Bank A Range $C000 to $DFFF
BANK1REG EQU $FF5B * Register for memory Bank B range $E000 to $FEFF (not used in this program)
CONTROL_REG EQU $FF5D * Setup feature of the CoCo PSG
* BIT FUNCTION
* 7 not used
* 6 not used
* 5 FLASH programming enable; 0=disabled, 1=enabled
* 4 Autostart enable; 0=enabled, 1=disabled
* 3 Write Enable (for FLASH/SRAM); 0=disabled, 1=enabled
* 2 Gameport B SEL signal (pin 7 of controller port B)
* 1 Gameport A SEL signal (pin 7 of controller port A)
* 0 YM2149 MASTER CLOCK; 0=2MHz, 1=1MHz
YM_RegSel EQU $FF5E * Address for the YM2149 Register Select Port
YM_Data EQU $FF5F * Address for the YM Data Port
MPI_Reg EQU $FF7F * Address for Multi Pak Interface Slot selector Register
opt cd
ORG $0E00 * 16k CoCo change ORG Value to $3B7A
* 32k CoCo, 64k CoCo or a CoCo 3 change ORG Value to $7B7A
* Variable storage
LoopSong FCB $FF * How many times do you want to loop the song (1 to 254), $FF=255 = continuous loop
RepeatCount FDB $0001 * Pointer to RAM location for keeping track of music playback
Every2nd FCB $00 * byte to keep track of every 2nd vsync
PointerM FDB Buffer * Pointer to Music Data
PSGRAMPtr FCB 128 * PSG RAM block pointer 128 = first block of 8k bytes
RAMPointer FDB $C000 * Pointer to CoCo PSG RAM Bank A
Buffer RMB $110 * Temporary Buffer needed since we are using the PSG and reading bytes might go beyond $DFFF
NoiseFlag FCB $00 * Value to keep track if Channel C is currenlty playing Noise or Not
MPI FCB $00 * Original MPI Program Register value
MPIPSG FCB $00 * CoCo PSG MPI Program Register value
CoCoPSGSlot RMB 1 * Memory location where BASIC will store the SLOT location of the CoCoPSG in the MPI
;* Timer related pointers, Can be removed as you probably don't require a timer to be shown on screen in your game
;Timer FCB 30 * used for the Clock counter counts down from 30 every time play loop is entered. When it hit's zero a second has passed
;Minutes1 EQU $420-5 * Location to display timer on screen (minutes and seconds)
;Minutes2 EQU Minutes1+1
;Seconds1 EQU Minutes1+3
;Seconds2 EQU Minutes1+4
**************************************************************************************************************
* Detect what slot the CoCoPSG is in the Multi-Pak Interface
* Using Ed Snider's idea to detect which slot the CoCoPSG is plugged into in the Multi-Pak Interface
*
* All registers are preserved except B
* Result is in the B accumulator
* If B=$FF then no CoCoPSG is in any slot of the MPI
* otherwise B = the slot where the CoCoPSG is in -1 (0 to 3) where 0 = Slot 1,1 = Slot 2,2 = Slot 3,3 = Slot 4
**************************************************************************************************************
FindCoCoPSG:
PSHS A,X,CC
ORCC #$50
LDA $FF7F * Get the original MPI_Register value (MPI Register)
PSHS A * Save it for restore later
LDB #%11111100 * We will AND this value to select through the different MPI slots SCS selector
CheckSlot:
STB $FF7F * Select Slot (MPI Register)
PSHS B * Save B
LDD #$06FF * Store $FF in registers 6 to 0 of the YM2149
CS1:
STD $FF5E * To see if the sum is 841 since Registers 0,2,4 store 8 bit values, 1,3,5 store 4 bit values (YM_Register Select)(YM_DATA)
DECA * and register 6 stores a 5 bit value
BPL CS1 * Keep adding 255 to each byte
LDA #06
LDX #$0000
CS2:
STA $FF5E * Select the different registers (6 ot 0) (YM_Register Select)
LDB $FF5F * Get the stored value of this register (YM_DATA)
ABX * Add B to X to keep a running total
DECA * Total stored on the CoCoPSG will be 255+15+255+15+255+15+31 = 841
BPL CS2 * Count down A from 6 to 0
PULS B * Restore B
CMPX #841 * If the sum is 841 then this is the CoCo PSG slot
BEQ FoundCoCoPSG * If X=841 then we found the slot with CoCo PSG :)
CMPB #$FF * Check if we just checked slot 4
BEQ ExitPSGCheck * If so then no CoCo PSG is detected exit with B=$FF :(
INCB * Else, load next slot value
BRA CheckSlot * Go check next slot
FoundCoCoPSG:
ANDB #%00000011 * B now has the slot # (0 to 3) value
ExitPSGCheck:
PULS A * Get the original MPI_Register value
STA $FF7F * Restore the original value (MPI Register)
PULS A,X,CC,PC * B = slot # -1 or $FF if not found
**************************************************************************************************************
* Main IRQ playback loop starts here
* If you want to use the VSync Interrupt request you can put your VSYNC IRQ code here...
IRQEntry:
PlayMusic:
COM Every2nd * We only process sound events every 2nd vsync (save more CPU time)
BEQ Back2 * Loop back every 2nd vsync, no need to set the MPI slot, we haven't touched it at this point
;* Show the running time on the screen (This section can be removed, it's only here for testing the player)
; DEC Timer * triggered 30 times every second
; BNE CheckCount * If we haven't reached zero yet then don't update the time
; LDB #30 * reset the timer back to 30
; STB Timer * save the value
; LDA Seconds2 * check if the ones value of the # of seconds
; CMPA #$39 * if it is a nine then make it a zero
; BEQ > * make it a zero and add to the tens value of the # of seconds
; INCA * Otherwise update
; STA Seconds2 * Save
; BRA CheckCount * Go process music data
;! LDA #$30 * set the ones value to zero
; STA Seconds2 * Save it
; LDA Seconds1 * Get the tens value of the seconds
; CMPA #$35 * check if it is a 5
; BEQ > * If so then add one to minute value
; INCA * otherwise add 1 to the tens value of the seconds
; STA Seconds1 * save it
; BRA CheckCount * Go process music data
;! LDA #$30 * Set the tens value of the seconds
; STA Seconds1 * to a zero
; LDA Minutes2 * Get the ones value of the minutes
; CMPA #$39 * check if it is a nine
; BEQ > * if so then go add one to the tens of the minute value
; INCA * otherwise increment the ones value of the seconds
; STA Minutes2 * update the value
; BRA CheckCount * Go process music data
;! LDA #$30 * Set the ones value of the minutes
; STA Minutes2 * to zero
; INC Minutes1 * add one the tens value of the minutes
;* End of time on screen display code. The above can be removed
CheckCount:
DEC RepeatCount+1 * decrement the LSB counter
BNE Back2 * if not zero yet then go back
LDA RepeatCount * Get the value of the MSB number of repeat counter
BEQ Playnotes * If we are now at zero then go get new notes to play
DEC RepeatCount * otherwise decrement the MSB of the counter
* IRQ Exits here
Back2:
LDA $FF02 * Acknowledge the IRQ
RTI * Return from Interrupt
**************************
Back:
LDA MPI * Change MPI Back to previous (original) slot value
STA MPI_Reg * Update MPI with new value
BRA Back2
* New Encoding:
* Encoding byte
* left nibble = # of times to repeat 1 to 15 repeat count
*
* right nibble = # of bytes to copy (1-11) count of bytes to copy
* 12 signals the repeat count is larger then 16 so use the next byte as the repeat count (left nibble = bytes to copy)
* 13 signals the repeat count is larger then 255 so use the next 2 bytes as the repeat count (left nibble = bytes to copy)
* 14 Flip Channel C from/to noise or drum track
* 15 signals End of File
* Play music
Playnotes:
* We are now loading another set of commands for the YM chip
* Check and Copy data to the buffer so we don't have to keep checking if X>$DFFF to load a new bank of RAM in down below
! LDB MPIPSG * Change MPI to CoCo PSG Slot
STB MPI_Reg * Update MPI with new value
LDX PointerM * Load X with the current music data pointer in the Buffer space
CMPX #Buffer+$100 * See if the pointer is less then $100
LBLO GetNext * skip if less
ORCC #$50 * Stop all Interrupts
CLR $FFDE * Go to ROM mode
LEAX -$100,X
STX RestoreX+1 * Store value down below (little self modifying code)
LDA RAMPointer * Check if our RAM pointer has reached the end of the PSG 8k block at $E000
INCA
CMPA #$DF *
BLO >
CMPA #$E0
BEQ DoneBlock
* We are going to need to load from $DF00 to $DFFF and switch to the next BANK to fill up the extra bit of the Buffer, if we get to this point
STA RAMPointer *
LDU RAMPointer * Source
LDY #Buffer * Destination
STS RestoreS1+2
Loop:
PULU D,X,S
STD ,Y
STX 2,Y
STS 4,Y
PULU D,X,S
STD 6,Y
STX 8,Y
STS 10,Y
PULU D,X
STD 12,Y
STX 14,Y
LEAY 16,Y
CMPY #Buffer+$100
BNE Loop
RestoreS1:
LDS #$0000
INC BANK0REG * Point to PSG new memory Bank
LDX #$C000
Loop1:
LDD ,X++
STD ,Y++
LDD ,X++
STD ,Y++
CMPY #Buffer+$110
BNE Loop1
DEC BANK0REG * Put it back to the last block in PSG memory Bank
BRA RestoreX
* Change to next Memory Block
DoneBlock:
INC BANK0REG * Point to PSG new memory Bank
LDA #$C0
! STA RAMPointer *
Done2nd:
LDU RAMPointer * Source
LDY #Buffer * Destination
STS RestoreS2+2
! PULU D,X,S
STD ,Y
STX 2,Y
STS 4,Y
PULU D,X,S
STD 6,Y
STX 8,Y
STS 10,Y
PULU D,X
STD 12,Y
STX 14,Y
LEAY 16,Y
CMPY #Buffer+$110
BNE <
RestoreS2:
LDS #$0000
RestoreX:
LDX #$0000 * Value gets changed by self modifying code above
RomRam2:
CLR $FFDE * Go to ROM mode (self modified to CLR $FFDF) if we are using a CoCo 3
GetNext:
LDB ,X * Get the first byte which has the repeat and data byte count info
ANDB #$0F * Clear off the left nibble info
CMPB #12 * check if the right nibble is
BLO Normal * lower then 12 then the byte is a normal byte where the left nibble is 0 to 15 and the right nibble is the # of music data bytes to copy
LBEQ SmallCount * if the right nibble is equal to 12 then handle an 8 bit value for the count
CMPB #13 * Check the right nibble is
LBEQ BigCount * equal to 13 if so go deal with big count (16 bit) value (next byte holds the repeat count value)
CMPB #14 * value of 14 flips Channel C Noise/Tone Flag and sets the mixer type accordingly
LBNE Exit1 * Must be 15 so EOF, Reached End of file we are done
* If we get here then the value is 14 or $0E
COM NoiseFlag * Value to keep track if Channel C is currenlty playing Noise or Not, 00 = playing tones, $FF = playing Noise
BEQ NowTone * If it's now zero then it was $FF so switch from Noise to normal Tone mode
NowNoise:
* Mixer Channel select
* ,,---------I/O
* ||,,,------Noise
* |||||,,,---Tone
* BACBACBA
LDB #%00011100 * = $DC Enable Channel C as noise, B and A as Tone data, 0 = selected
BRA >
NowTone:
* Mixer Channel select
* ,,---------I/O
* ||,,,------Noise
* |||||,,,---Tone
* BACBACBA
LDB #%00111000 * = $F8 Enable Channel C as normal Tone data, B and A as Tone data, 0 = selected
!
LDA #$07 * A = Channel 7 (Mixer Register), B = Tone/Noise for the Channel C
STD YM_RegSel * Update YM Chip with this info
LEAX 1,X
BRA GetNext
Normal:
LDA ,X+ * get the # of times to repeat these values, position X to point at the values to be copied
LSRA * Shift left nibble to the right nibble
LSRA * ""
LSRA * ""
LSRA * A now has the correct # of times to be repeated
STA RepeatCount+1 * Save the repeat count
SetByteCount:
CLRA
TFR D,Y * Y now has the number of bytes to be sent to the YM2149 sound chip
CopyBytes:
CLRA * Clear A, we will use A to identify the channel
LDB ,X+ * Get byte value
BMI VolumeChange * If bit 7 is high then this is a volume change
RORB
ROLA
RORB * B now has the most significant value of the tone
ROLA * A now has the Channel 0,1,2
LSLA * A = A * 2 which is 0, 2 or 4 (ready for the lsb of the tone)
INCA * A is channel 1, 3, or 5 = MSB channel register
STD YM_RegSel * Select the A=1,B=3 or C=5 MSB tone register, send msb tone value to the YM2149 sound chip
DECA * A is channel 0, 2, or 4 = LSB chennel register
LDB ,X+ * Get the LSB tone value
STD YM_RegSel * Select the A=0,B=2 or C=4 LSB tone register, send lsb tone value to the YM2149 sound chip
LEAY -2,Y * Decrement the counter for bytes sent (we always have two when we change the tone)
BNE CopyBytes * has reached 0, if not then go copy more bytes to the YM2149 sound chip
DoneAll:
STX PointerM * Save the pointer to the music data
GoBack1:
LBRA Back * Done go back to wait for the vsync trigger (actually every second vsync)
VolumeChange:
RORB
ROLA
RORB * B now has the volume info, with bit 5 set (which identified the byte was a volume change)
ROLA * A now has the Channel 0,1,2, or 3
CMPA #3 * if A is 3 then it's noise/drums for channel C
BEQ MakeNoise * Go play the noise frequency which is in accumulator B bits 4 to 0
ADDA #8 * Make A channel point to A=8,B=9,C=10
ANDB #%00001111 * Strip off the other bits, keep only the 0-15 value
STD YM_RegSel * Select the A=8,B=9,C=10 volume register, send volume level value to the YM2149 sound chip
LEAY -1,Y * Check if the counter for the number of bytes to send to the sound chip
BNE CopyBytes * has reached 0, if not then go copy more bytes to the YM2149 sound chip
BRA DoneAll * All done copying data to the YM2149
SmallCount:
LDB ,X+ * Get # of bytes to copy from the left nibble
LSRB * Shift left nibble to the right nibble
LSRB * ""
LSRB * ""
LSRB * B now has the # of bytes to copy
LDA ,X+ * A now has the # of times to repeat the copied values
STA RepeatCount+1 * Save the repeat count, position X to point at the values to be copied
BRA SetByteCount
BigCount:
LDB ,X+ * Get # of bytes to copy from the left nibble
LSRB * Shift left nibble to the right nibble
LSRB * ""
LSRB * ""
LSRB * B now has the # of bytes to copy
LDU ,X++ * Load U with # of repeats count, position X to point at the values to be copied
STU RepeatCount * Save the repeat count
BRA SetByteCount
MakeNoise:
LDA #6 * YM register 6
ANDB #%00011111 * strip off bit 5 which signified it was a Volume/Noise value
STD YM_RegSel * is noise frequency register, save the frequency
* Required each time we make a noise
LDD #$0A0B * A = 10 = Channel C, B = 11 = C volume level
STD YM_RegSel
LEAY -1,Y * Check if the counter for the number of bytes to send to the sound chip
BNE CopyBytes * has reached 0, if not then go copy more bytes to the YM2149 sound chip
BRA DoneAll * All done copying data to the YM2149
* Setup pointer to music data to the start of the song
Exit1:
CLR $FFDE * Go to ROM mode
LDA #128 * Set PSG Bank0 to the first 8k Block of it's RAM
STA BANK0REG
LDX #$C000 * Skip the 5 byte header info
STX RAMPointer
LDU #Buffer+5 * Set Music data to point to the start of the music data, skip 5 byte header
STU PointerM * Save it
LDU #Buffer
! LDD ,X++
STD ,U++
CMPX #$C000+$110
BNE <
RomRam3:
CLR $FFDE * Go to ROM mode (self modified to CLR $FFDF) if we are using a CoCo 3
CLR NoiseFlag * Set Channel C tone/noise flag to tone
LDA LoopSong
CMPA #$FF * $FF = loop forever
BEQ >
DEC LoopSong
BNE >
* If we reached 0 in our loop counter then stop playback
BSR PlaySetup
* Add your own code here to handle the end of the song... Maybe self mod the VSync IRQ entry code above to use your vsync IRQ code but skip the playback stuff...
* As this is right now the music wont stop playing unless you put some code here to handle it, right here.
GoBack2:
LBRA Back * Done go back to wait for the vsync trigger (actually every second vsync)
! BSR PlaySetup * Reset everything to start the song again
BRA GoBack2
PlaySetup:
* Clear channel repeat note counters
LDD #$0001 * Clear counter
STD RepeatCount * Clear the counter for how many times the current notes will play until they need to be changed
**************
* Set defaults for the YM2149
* ,,---------I/O
* ||,,,------Noise
* |||||,,,---Tone
* BACBACBA
LDD #$0738 * Register 7 = mixer Register, #%00111000 * $38 = Enable Channel A,B and C as Tone data, 0 = selected
STD YM_RegSel
ZeroVolume:
LDD #$0800 * Register 8 = Volume Register for Channel A, Volume Level 0=off to 15=full, logarithmic curve
STD YM_RegSel
INCA * Register 9 = Volume Register for Channel B
STD YM_RegSel
INCA * Register 10 = Volume Register for Channel C
STD YM_RegSel
;* This is more Timer display related code that can be removed along with the other Timer related section of code, above
; LDD #$3030 * ascii '00'
; STD Minutes1 * Start showing 00 minutes
; STD Seconds1 * Start showing 00 seconds
; CLR Every2nd * Process sound every 2nd Vsync IRQ hit
; LDB #30 * Vsync is triggered 60 times a second, since we only process every second vsync we count down 1/30th of a second
; STB Timer * store it
; LDB #': * Draw the colon on screen between the minutes and the seconds
; STB Minutes2+1 * store it
;* End of Time display related code that can be removed if you don't want to see a timer on the screen.
RTS
**************************************************************************************************************
* Setup the playback of the CoCo PSG playback routine used every second VSync IRQ
* The music data must already be stored in the CoCo PSG's 512k RAM before this code is executed
*
* An easy way to get the music into the CoCo PSG's RAM is to use the MIDI2CC converter and convert a MIDI file
* to Ed Snider's CoCo PSG format. This will output a PSGLOADR.BIN file that can be loaded and auto executed from
* BASIC, then you can load this code (with your own additional code at the bottom) and you will then have
* background music playing. Here is the BASIC code to start your program if you save this program as PSGPLAYR.BIN
*
* 1 POKE 3584,PEEK(375) :'Save original address for the
* 2 POKE 3585,PEEK(376) :'Close file routine which is different for different CoCo's
* 3 LOADM"PSGLOADR" :'Auto EXECs and loads the song data onto the CoCoPSG's RAM
* 4 LOADM"PSGPLAYR" :'This program with your code added
* 5 EXEC
**************************************************************************************************************
START
ORCC #$50 * Stop the FIRQ & the IRQ
* Test if we are running on a CoCo 3, if so we need to go back to RAM mode when exiting our code
LDD $FFFE * Get the Reset Interrupt address
CMPD #$8C1B * If it's $8C1B then it is a CoCo 3
BNE >
LDA #$DF * Since it's a CoCo 3 then we need to self modify the code below, so that we jump back to RAM mode when we exit our code
* We use $DF to change the CLR $FFDE (ROM mode) to CLR $FFDF (RAM mode) in the 3 locations
STA RomRam1+2 * Initial Setup of IRQ and BASIC hook is setup
STA RomRam2+2 * Done playing the notes and setting up the repeat value
STA RomRam3+2 * Done playing the song, reset buffer pointer and check repeat value
LDU #$400 * Top of screen
* Setup the CoCoPSG so it doesn't autostart and get the MPI Slot of the CoCoPSG so we can switch back and forth using it and the Disk drive controller
! LBSR FindCoCoPSG
CMPB #$FF * IF B=$FF then no CoCo PSG wasn't found :(
BNE >
LBSR PrintERROR * no CoCo PSG was found
LBSR PrintString * Go print the text in the next line on screen
FCC 'NO'
FCB $60
FCC 'COCO'
FCB $60
FCC 'PSG'
FCB $60
FCN 'FOUND '
JMP Exit0 * File is a PSG file but can't play this version # don't try to play this song, Exit before we setup IRQ
! STB CoCoPSGSlot * B now has the slot # (0 to 3) value
LDA MPI_Reg * GET Original MPI REG
STA MPI * Save the Original MPI value so we can swap back to the Disk controller
ANDA #%11111100 * SCS TO CoCo PSG SLOT (user selected) - LEAVE *CART/CTS ON ORIGINAL SLOT
ORA CoCoPSGSlot * Add the user slot value (0 to 3 at this point)
STA MPI_Reg * SWITCH SCS Only
LDA #%00010000 * DISABLE AUTOSTART
STA CONTROL_REG * WRITE PSG CONTROL REG
LDA MPI_Reg * GET MPI REG
LSLB * Setup B with the CART/CTS and SCS selection based on the CoCoPSG found value
LSLB
LSLB
LSLB * Shift value (0 to 3) left 4 so that it is for the CART/CTS value
ORB CoCoPSGSlot * B now has the proper values for the users CoCo PSG slot for the Cart/CTS and SCS
STB CoCoPSGSlot * Save the new value so we can OR the value into it
ANDA #%11001100 * CTS and SCS TO SLOT User selected slot
ORA CoCoPSGSlot * Add the user slot value (0 to 3 at this point)
STA MPIPSG * Save the CoCo PSG MPI selection
STA MPI_Reg * Update MPI with new value
CLR $FFDE * Go to ROM mode - Must be in ROM mode to read/write the CoCo PSG's 512k RAM
* Setup pointer to music data to the start of the song
LDA #128 * Set PSG Bank0 to the first 8k Block of it's RAM
STA BANK0REG
LDX #$C000
STX RAMPointer
LDU #Buffer * Set Music data to point to the start of the music data
STU PointerM * Save it
! LDD ,X++ * Fill the playback buffer
STD ,U++
CMPX #$C000+$110
BNE <
RomRam1:
CLR $FFDE * Go to ROM mode (self modified to CLR $FFDF) if we are using a CoCo 3
LDA MPI * Change MPI Back to previous (original) slot value
STA MPI_Reg * Update MPI with new value
************
* 5 Byte header:
* Byte Value
* 00-02 'PSG' - Header Identifier
* 03 $xx - File version # currently only supprts a value of 01 for version 1.00
* 04 $xx - Number of times to loop this song where xx is:
* $00 - Don't change the number of loops value
* $01 to $FE - loop this number of times
* $FF - loop forever
************
LDX PointerM * Load X with the current music data pointer in the Buffer space
LDD ,X++ *
CMPD #$5053 * Start of song data must be "PS"
BNE NotPSGFile * Print not a PSG file and Exit if not
LDD ,X++ * A='G', B=File version #
CMPA #$47 * Start of song file must be "G"
BEQ > * If it's equal then it is a PSG file, go check the version #
NotPSGFile:
BSR PrintERROR * File is not a PSG file, Print ERROR: to the screen
BSR PrintString * Go print the text in the next line on screen
FCC 'NOT'
FCB $60 * Space character code
FCC 'A'
FCB $60
FCC 'PSG'
FCB $60
FCN 'FILE '
JMP Exit0 * Exit before we setup IRQ
!
CMPB #1 * See if the version # is a 1
BEQ > * This player can only handle version 1 files. If it's a 1 then go play the song :)
UnknownVersion:
BSR PrintERROR * File is not a version 1 PSG format file, Print ERROR: to the screen
BSR PrintString * Go print the text in the next line on screen
FCC 'CAN'
FCB $60
FCC 'ONLY'
FCB $60
FCC 'PLAY'
FCB $60
FCC 'VERSION'
FCB $60
FCB $71 * '1' - Black foreground and green background to match the CoCo screen #1
FCB $60
FCC 'PSG'
FCB $60
FCN 'FILES '
JMP Exit0 * File is a PSG file but can't play this version # don't try to play this song, Exit before we setup IRQ
PrintERROR:
JSR PrintString * Go print the text in the next line on screen
FCC 'ERROR'
FDB $7A00 * Colon and zero (null) terminater
RTS * Return
PrintString:
LDX ,S * Get address of the Text to print off the stack
BRA PS_Skip1
PS_Print1:
STA ,U+
PS_Skip1:
LDA ,X+
BNE PS_Print1 * Did we find the end terminator (which is zero)? If not go print byte and get another...
STX ,S * Put the return address which is just after the text that was printed, back on the stack
RTS
!
LDA ,X+ * Get the value for Loop counter
BEQ > * Skip changing the loop counter if this value is zero
STA LoopSong * Otherwise update the LoopSong value
!
STX PointerM * Update the file pointer to the start of the music data
CLR NoiseFlag * Set the Tone/Noise value for channel C to start setting of 00 = Tone data
LDA MPIPSG * Change MPI to CoCo PSG Slot
STA MPI_Reg * Update MPI with new value
LBSR PlaySetup * Initialize the playback settings
* $FF01 (65281) PIA 0 side A control reg - PIA0AC CoCo 1/2/3
* Bit 7 HSYNC Flag 0=not Sync, 1=Hsync is triggered
* Bit 6 Unused
* Bit 5 1
* Bit 4 1
* Bit 3 Select Line LSB of MUX
* Bit 2 DATA DIRECTION TOGGLE 0 = $FF00 sets data direction 1 = normal
* Bit 1 IRQ POLARITY 0 = flag set on falling edge 1=set on rising edge
* Bit 0 HSYNC IRQ 0 = disabled 1 = enabled
* $FF03 (65283) PIA 0 side B control reg - PIA0BC CoCo 1/2/3
* Bit 7 VSYNC FLAG 0=not Sync, 1=Vsync is triggered
* Bit 6 N/A
* Bit 5 1
* Bit 4 1
* Bit 3 SELECT LINE MSB of MUX
* Bit 2 DATA DIRECTION TOGGLE 0 = $FF02 sets data direction 1=normal
* Bit 1 IRQ POLARITY 0=flag set on falling edge 1=set on rising edge
* Bit 0 VSYNC IRQ 0=disabled 1=enabled
* Setup Cartridge as the sound source
* $FF03 bit 3 $FF01 Bit 3 Sound Source
* 0 0 DAC
* 0 1 Cassette
* 1 0 Cartridge ***
* 1 1 No Sound
* 1 bit audio output is not included here and can be used independantly of the above sources
LDA $FF03 * PIA 0 side B control reg - PIA0BC
ORA #%00001000 * Bit 3 is the Select Line MSB of MUX set it to 1 for Cartridge source select
STA $FF03 * Save settings
LDA $FF01 * PIA 0 side A control reg - PIA0AC
ANDA #%11110111 * Bit 3 is the Select Line LSB of MUX set it to 0 for Cartridge source select
STA $FF01 * Save settings
* Also must enable sound output
LDA $FF23 * PIA 1 side B control reg - PIA1BC
ORA #%00001000 * Set bit 3 to enable sound output
STA $FF23 * Save settings
CarryOn:
* Back to normal MPI Slot
LDA MPI * Change MPI Back to previous (original) slot value
STA MPI_Reg * Update MPI with new value
* Setup IRQ jump address to jump to our routine
LDX #IRQEntry * Get our IRQ Start address
STX $10D * Store it on the IRQ jump address, so it will now jump to our routine everytime an IRQ is triggered
Exit0:
ANDCC #%10101111 * Enable the FIRQ & IRQ which will start the music playback
**************************************************************************************************************
* All setup and playing music in the background now...
* Remove the code below and add your program code here
* As a test with the music playing in the background this routine
* Reads the Atari Joysticks plugged into the CoCo PSG
!
LDA MPIPSG * Change MPI to CoCo PSG Slot
STA MPI_Reg * Update MPI with new value
LDA #14 * Select Joystick Port A
STA YM_RegSel
LDA YM_Data * Read the current value
STA $400
LDA #15 * Select Joystick Port B
STA YM_RegSel
LDA YM_Data * Read the current value
STA $401
* Back to normal MPI Slot
LDA MPI * Change MPI Back to previous (original) slot value
STA MPI_Reg * Update MPI with new value
BRA <
END START