Last active
February 20, 2017 21:54
-
-
Save badvision/837c8e2449b449069c3b to your computer and use it in GitHub Desktop.
Mockingboard sound driver
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
*= $4000 | |
!cpu 65c02 | |
!sl "globals.txt" | |
;----------- | |
reg_ORB = 0 ;Output Register B | |
reg_ORA = 1 ;Output Register A | |
reg_DDRB = 2 ;Data direction reg B | |
reg_DDRA = 3 ;Data direction reg A | |
reg_T1CL = 4 ;T1 low-order latches (low-order counter for read operations) | |
reg_T1CH = 5 ;T1 high-order counter | |
reg_T1LL = 6 ;T1 low-order latches | |
reg_T1LH = 7 ;T1 high-order latches | |
reg_T2CL = 8 ;T2 low-order latches (low-order counter for read operations) | |
reg_T2CH = 9 ;T2 high-order counter | |
reg_SR = 10 ;Shift register | |
reg_ACR = 11 ;Aux control register | |
reg_PCR = 12 ;Perripheral control register | |
reg_IFR = 13 ;Interrupt flag register | |
reg_IER = 14 ;Interrupt enable register | |
reg_ORAH = 15 ;Output Register A (no handshake) | |
bus_OFF = 4 | |
bus_READ = 5 | |
bus_WRITE = 6 | |
bus_LATCH = 7 | |
int_ENABLE = $80 | |
int_DISABLE = $00 | |
int_TIMER1 = $40 | |
int_TIMER2 = $20 | |
anyInterruptEnabled = %11100000 ; AND with IER and BNE to test if interrupts are enabled | |
IRQ_VECTOR = $3fe | |
dataPtr = $FA ;Must not be affected by anything else | |
sequenceTablePtr = $FC ;Must not be affected by anything else | |
stackSize = 16 ; Size of stack for active commands per channel | |
;----------- | |
!set TRUE=100 | |
!set FALSE=1 | |
;TODO: MOVE TO NEW FILE | |
!set debug = TRUE | |
!set trace = FALSE | |
!set traceRestore = FALSE | |
!macro special p1, p2 { | |
!if debug = TRUE { | |
!byte $fc, p1, p2 | |
} | |
} | |
!macro disableTrace { | |
+traceOff | |
!set traceRestore = FALSE | |
} | |
!macro pauseTrace { | |
!if trace = TRUE { | |
+traceOff | |
!set traceRestore = TRUE | |
} | |
} | |
!macro restoreTrace { | |
!if traceRestore = TRUE { | |
+traceOn | |
} | |
!set traceRestore = FALSE | |
} | |
!macro traceOn { | |
!set trace = TRUE | |
+special $65, 1 | |
} | |
!macro traceOff { | |
!set trace = FALSE | |
+special $65, 0 | |
} | |
!macro dumpRegs { | |
+traceOn | |
NOP | |
+traceOff | |
} | |
!macro showMemoryMap { | |
+pauseTrace | |
+special $64, $da | |
} | |
!macro printNum n { | |
+pauseTrace | |
+special $50, n | |
} | |
!macro printRegA { | |
+pauseTrace | |
+special $51, 0 | |
} | |
!macro printRegX { | |
+pauseTrace | |
+special $51, 1 | |
} | |
!macro printRegY { | |
+pauseTrace | |
+special $51, 2 | |
} | |
!macro printRegSP { | |
+pauseTrace | |
+special $51, 3 | |
} | |
!macro printProgramCounter { | |
+pauseTrace | |
+special $51, 4 | |
} | |
!macro printlnNum n { | |
+pauseTrace | |
+special $5B, n | |
} | |
!macro printChr n { | |
+pauseTrace | |
+special $5c, n | |
} | |
!macro println { | |
+printChr 13 | |
} | |
!macro printChr n1, n2 { | |
+printChr n1 | |
+printChr n2 | |
} | |
!macro printChr n1, n2, n3 { | |
+printChr n1 | |
+printChr n2, n3 | |
} | |
!macro printChr n1, n2, n3, n4 { | |
+printChr n1 | |
+printChr n2, n3, n4 | |
} | |
!macro printChr n1, n2, n3, n4, n5 { | |
+printChr n1 | |
+printChr n2, n3, n4, n5 | |
} | |
!macro printChr n1, n2, n3, n4, n5, n6 { | |
+printChr n1 | |
+printChr n2, n3, n4, n5, n6 | |
} | |
!macro printChr n1, n2, n3, n4, n5, n6, n7 { | |
+printChr n1 | |
+printChr n2, n3, n4, n5, n6, n7 | |
} | |
!macro printChr n1, n2, n3, n4, n5, n6, n7, n8 { | |
+printChr n1 | |
+printChr n2, n3, n4, n5, n6, n7, n8 | |
} | |
!macro printChr n1, n2, n3, n4, n5, n6, n7, n8, n9 { | |
+printChr n1 | |
+printChr n2, n3, n4, n5, n6, n7, n8, n9 | |
} | |
START | |
;----------- | |
sei | |
lda #<data | |
sta dataPtr | |
lda #>data | |
sta dataPtr+1 | |
setup_playback | |
php | |
pha | |
phx | |
phy | |
verify_header | |
; TODO: Verify header is 3C 51 C0 | |
; If not, exit and print error message | |
update_pointers | |
; All of the song pointers are relative, before playback they need to be converted to absolute | |
;Get # of patterns | |
ldy #6 | |
lda (dataPtr),y | |
+printRegA | |
+printChr ' ','p','a','t','t','e','r','n','s' | |
+println | |
tax | |
lda #$01 | |
and status | |
bne detect_card | |
;For each pattern, add the base dataPtr address and overwrite | |
;Because this modifies the data area, it's important to keep track if this has been done already | |
;That information is tracked on bit 0 of the status register | |
+printChr 'A','d','j',' ','t','b','l' | |
+println | |
- iny | |
+printRegX | |
+println | |
clc | |
lda (dataPtr),y | |
adc #<data | |
sta (dataPtr),y | |
iny | |
lda (dataPtr),y | |
adc #>data | |
sta (dataPtr),y | |
dex | |
bne - | |
init_vars | |
+printChr 'I','n','i','t',' ','S','e','q' | |
+println | |
; (dataPtr),y is now at the end of the pattern offset table | |
; The sequence table is next, we should keep track of it | |
iny | |
tya | |
clc | |
adc dataPtr | |
sta sequenceTablePtr | |
lda #0 | |
adc dataPtr+1 | |
sta sequenceTablePtr+1 | |
detect_card | |
; TODO: Figure out what slot the mockingboard is in and store in X | |
ldx #$04 | |
install_handler | |
; Update internal routines to point to card location | |
clc | |
txa | |
adc #$c0 | |
sta regWrite1 + 2 | |
sta regWrite2 + 2 | |
sta regRead1 + 2 | |
sta regRead2 + 2 | |
;Setup all playback variables | |
ldy #3 | |
lda (dataPtr),y | |
sta speed | |
ldy #reg_T1LL | |
jsr regWrite1 | |
ldy #4 | |
lda (dataPtr),y | |
sta speed+1 | |
ldy #reg_T1LH | |
jsr regWrite1 | |
ldy #5 | |
lda (dataPtr),y | |
sta ticksPerBeat | |
+printRegA | |
+printChr ' ','t','p','b' | |
+println | |
; Set to first sequence | |
lda #0 | |
jsr change_sequence | |
; Register IRQ handler | |
; TODO: Support prodos IRQ handler or at least preserve it | |
lda #<playback_main | |
sta IRQ_VECTOR | |
lda #>playback_main | |
sta IRQ_VECTOR+1 | |
; Init both 6522 chips | |
; Set port A for output | |
lda #$ff | |
ldy #reg_DDRA | |
jsr regWriteBoth | |
; Set port B for output | |
lda #$0F | |
ldy #reg_DDRB | |
jsr regWriteBoth | |
; Set timer 1 to free-running mode | |
lda #$40 | |
ldy #reg_ACR | |
jsr regWrite1 | |
; Enable interrupts | |
lda #(int_ENABLE | int_TIMER1) | |
ldy #reg_IER | |
jsr regWrite1 | |
; Note that playback has started | |
lda #$81 | |
sta status | |
+printChr 'P','l','a','y' | |
+println | |
; Complete setup routine, leave interrupts enabled | |
ply | |
plx | |
pla | |
plp | |
cli | |
rts | |
;..... Playback support routines | |
; Call order: | |
; Get AY ready with JSR reset1/2 | |
; Load AY register # and JSR writeOra and then JSR latch | |
; Load register value and JSR writeOra and the JSR write | |
!zone Playback_Routines | |
writeOra1 | |
ldy #reg_ORA | |
regWrite1 | |
sta $c000,y | |
rts | |
reset1 | |
lda #bus_OFF | |
bra command1 | |
write1 | |
jsr writeOra1 | |
lda #bus_WRITE | |
bra command1 | |
latch1 | |
jsr writeOra1 | |
lda #bus_LATCH | |
command1 | |
ldy #reg_ORB | |
bra regWrite1 | |
; lda #bus_OFF | |
; bra regWrite1 | |
regWriteBoth | |
jsr regWrite1 | |
jmp regWrite2 | |
writeOra2 | |
ldy #reg_ORA | |
regWrite2 | |
sta $c080,y | |
rts | |
reset2 | |
lda #bus_OFF | |
bra command2 | |
write2 | |
jsr regWrite2 | |
lda #bus_WRITE | |
bra command2 | |
latch2 | |
jsr regWrite2 | |
lda #bus_LATCH | |
command2 | |
ldy #reg_ORB | |
bra regWrite2 | |
; lda #bus_OFF | |
; bra regWrite2 | |
regRead1 | |
lda $c000,y | |
rts | |
regRead2 | |
lda $c080,y | |
rts | |
;---------- | |
!zone Playback_Main | |
playback_main | |
sei | |
php | |
pha | |
phx | |
phy | |
; Check to see if the first 6522 via triggered the interrupt | |
ldy #reg_IFR | |
jsr regRead1 | |
ora #int_TIMER1 | |
; No interrupt? Exit. TODO: Support prodos IRQ handling | |
bne playback_tick | |
+printChr 'o','d','d',' ','i','r','q' | |
+println | |
finish_irq_service | |
ply | |
plx | |
pla | |
plp | |
cli | |
+printChr '-','T','i','c','k','-' | |
+println | |
+traceOff | |
rti | |
playback_tick | |
; Clear interrupt | |
lda #$FF | |
ldy #reg_IFR | |
jsr regWrite1 | |
; advance tick counter | |
dec currentTick | |
; New row? | |
; No: Still on same note, continue effects | |
bne do_tick | |
; Yes: advance row counter | |
lda ticksPerBeat | |
sta currentTick | |
lda currentRow | |
inc | |
cmp patternLength | |
; End of pattern? | |
bcs .advance_sequence | |
; No: Still on same pattern, play next row | |
sta currentRow | |
jmp play_row | |
; Yes: Advance sequence counter | |
.advance_sequence | |
lda (sequenceTablePtr) | |
cmp currentSequence | |
; End of song? | |
beq stop_playback | |
; No: play first row of next pattern | |
lda currentSequence | |
inc | |
jsr change_sequence | |
jmp play_row | |
; Yes: Song over, stop playback | |
stop_playback | |
;Note that playback has stopped | |
lda status | |
and #$7f | |
sta status | |
+printChr 'S','t','o','p' | |
+println | |
; Clear interrupts | |
lda #(int_DISABLE | int_TIMER1) | |
ldy #reg_IER | |
jsr regWrite1 | |
jmp finish_irq_service | |
do_tick | |
; Look at active effects and handle them accordingly | |
jmp finish_irq_service | |
!macro processChannel channelNumber, ayNumber { | |
!zone processChannel { | |
!set fineTune = (channelNumber - 1) * 2 | |
!set coarseTune = fineTune + 1 | |
!set vol = channelNumber + 7 | |
!set trackingData = ay1_params + (3+stackSize) * (channelNumber - 1 + (3*(ayNumber-1))) | |
; Get pitch/macro to play | |
jsr read_data | |
bpl + | |
; This is some non-pitch command | |
;11xxxxxx = Macro X playback | |
;101----- = Note off | |
jmp ++ | |
+ | |
+printChr 'A','y',' ' | |
+printNum ayNumber | |
+printChr ',','C','h',' ' | |
+printNum channelNumber | |
+printChr ',','P','i','t','c','h',' ' | |
+printRegA | |
+println | |
asl | |
tax | |
; Process fine tune parameter | |
lda #fineTune | |
!if ayNumber=1 { | |
jsr latch1 | |
} else { | |
jsr latch2 | |
} | |
lda pitch_table,x | |
sta trackingData + 1 | |
!if ayNumber=1 { | |
jsr write1 | |
} else { | |
jsr write2 | |
} | |
; Process coarse tune parameter | |
lda #coarseTune | |
!if ayNumber=1 { | |
jsr latch1 | |
} else { | |
jsr latch2 | |
} | |
lda pitch_table+1,x | |
sta trackingData + 2 | |
!if ayNumber=1 { | |
jsr write1 | |
} else { | |
jsr write2 | |
} | |
++ | |
jsr read_data | |
; Process volume | |
; $20 = Envelope generator | |
; $FF = No change | |
; $80-$FE = Commands | |
cmp #32 | |
bcs + | |
+printChr 'v','o','l',' ' | |
+printRegA | |
+println | |
pha | |
lda #vol | |
!if ayNumber=1 { | |
jsr latch1 | |
} else { | |
jsr latch2 | |
} | |
pla | |
!if ayNumber=1 { | |
jsr write1 | |
} else { | |
jsr write2 | |
} | |
+ | |
- | |
jsr read_data | |
+printChr 'S','k','i','p',' ' | |
+printRegA | |
+println | |
; TODO: Handle non-zero values as extra commands, not implemented at the moment | |
bne - | |
.end | |
} | |
} | |
!macro processRow ayNumber { | |
!zone processRow { | |
sta channelFlags | |
+printChr 'C','h',' ','f','l','g',':' | |
+printRegA | |
+println | |
.checkExtendedCommands | |
lda #1 | |
bit channelFlags | |
bne .hasExtendedCommands | |
jmp .checkEnvelopeCommands | |
.hasExtendedCommands | |
; TODO: Process extended commands | |
.checkEnvelopeCommands | |
lda #2 | |
bit channelFlags | |
bne .hasEnvelopeCommands | |
jmp .checkNoiseCommands | |
.hasEnvelopeCommands | |
; TODO: Process envelope commands | |
.checkNoiseCommands | |
lda #4 | |
bit channelFlags | |
bne .hasNoiseCommands | |
jmp .checkACommands | |
.hasNoiseCommands | |
; TODO: Process noise commands | |
.checkACommands | |
lda #8 | |
bit channelFlags | |
bne .hasACommands | |
jmp .checkBCommands | |
.hasACommands | |
+processChannel 1, ayNumber | |
.checkBCommands | |
lda #16 | |
bit channelFlags | |
bne .hasBCommands | |
jmp .checkCCommands | |
.hasBCommands | |
+processChannel 2, ayNumber | |
.checkCCommands | |
lda #32 | |
bit channelFlags | |
bne .hasCCommands | |
jmp .done | |
.hasCCommands | |
+processChannel 3, ayNumber | |
.done | |
} ; -- End Zone processRow | |
} ; -- End Macro processRow | |
play_row | |
lda currentRow | |
+printChr 'r','o','w',' ' | |
+printRegA | |
+printChr ' ','o','f',' ' | |
lda patternLength | |
+printRegA | |
+println | |
; Read next row of data and start playing it | |
jsr read_data | |
bpl .hasSomeData | |
; If the high-bit is set then there is nothing on this row, move on. | |
jmp finish_irq_service | |
.hasSomeData | |
+processRow 1 | |
jsr read_data | |
+processRow 2 | |
jmp finish_irq_service | |
;------------------- | |
; Set sequence (A has sequence #) | |
change_sequence | |
sta currentSequence | |
+printChr 'S','e','q',' ' | |
+printRegA | |
+println | |
tay | |
iny | |
lda (sequenceTablePtr),y | |
sta currentPattern | |
+printChr 'p','a','t',' ' | |
+printRegA | |
+println | |
asl | |
adc #7 | |
tay | |
lda (dataPtr),y | |
sta read_data+1 | |
iny | |
lda (dataPtr),y | |
sta read_data+2 | |
stz currentRow | |
lda ticksPerBeat | |
sta currentTick | |
jsr read_data | |
sta patternLength | |
+printChr 'p','a','t',' ','l','e','n',' ' | |
+printRegA | |
+println | |
rts | |
read_data | |
lda read_data | |
inc read_data+1 | |
bne + | |
inc read_data+2 | |
+ ora #$00 ; Reset flags based on value of A | |
rts | |
; End program section | |
;-------------------------------------------------------------------------------------------- | |
; Variables | |
; If hi-bit set then song is playing, otherwise not playing | |
; Bit 0 = Location still relative (1 = location offsets already processed) | |
status !byte 0 | |
ticksPerBeat !byte 0 ; Number of ticks per beat (affects tempo and effects) | |
speed !word 0 ; Duration of each tick | |
currentSequence !byte 0 | |
currentPattern !byte 0 | |
patternLength !byte 0 | |
currentRow !byte 0 | |
currentTick !byte 0 | |
channelFlags !byte 0 | |
ay1_params | |
ay1_aAmp !byte 0 | |
ay1_aFreq !word 0 | |
ay1_aStack !fill stackSize | |
ay1_bAmp !byte 0 | |
ay1_bFreq !word 0 | |
ay1_bStack !fill stackSize | |
ay1_cAmp !byte 0 | |
ay1_cFreq !word 0 | |
ay1_cStack !fill stackSize | |
ay1_envPeriod !word 0 | |
ay1_envStack !fill stackSize | |
ay2_params | |
ay2_aAmp !byte 0 | |
ay2_aFreq !word 0 | |
ay2_aStack !fill stackSize | |
ay2_bAmp !byte 0 | |
ay2_bFreq !word 0 | |
ay2_bStack !fill stackSize | |
ay2_cAmp !byte 0 | |
ay2_cFreq !word 0 | |
ay2_cStack !fill stackSize | |
ay2_envPeriod !word 0 | |
ay2_envStack !fill stackSize | |
;----------------------- | |
; Note values: Generate lookup tables for frequencies | |
clockRate = 1020484 | |
!macro note baseFreq, octave { | |
!set freq = (2^octave) * baseFreq | |
!set period = clockRate / freq | |
!word int(period + 0.5) | |
} | |
pitch_table | |
!for octave,0,5 { | |
+note 261.625625, octave ; 0: C | |
+note 277.1825, octave ; 1: C# | |
+note 293.664375, octave ; 2: D | |
+note 311.126875, octave ; 3: D# | |
+note 329.6275, octave ; 4: E | |
+note 349.228125, octave ; 5: F | |
+note 369.994375, octave ; 6: F# | |
+note 391.995625, octave ; 7: G | |
+note 415.305, octave ; 8: G# | |
+note 440, octave ; 9: A | |
+note 466.16375, octave ; 10:A# | |
+note 493.883125, octave ; 11:B | |
} | |
;------------------------------------------------------------------------------------------------------------------- | |
;Sample 1-pattern song, plays 4 repeats of the same test pattern | |
data | |
!byte $3C, $51, $C0 ;Header bytes: MU SI C0 | |
!byte 0, $F0 ;Global speed (LSB, MSB) | |
!byte 16 ;16 ticks per row | |
!byte 1 ;Number of patterns | |
;Offset of pattern 0 (LSB, MSB) | |
!word (pattern0-data) | |
!byte 1 ;Sequence length: 4 | |
!byte 0 ;Sequence is pattern 0 repeated 4 times | |
pattern0 | |
; !byte 16 ;Pattern length is 16 notes | |
!byte 2 ;Pattern length is 16 notes | |
;Row 1 | |
!byte %00001000 ;Bit pattern indicating Channel A has data (AY 1) | |
; %--CBANEX ; X = Extended commands | |
; E = Envelope generator commands | |
; N = Noise generator commands | |
; A/B/C = Channels A/B/C | |
!byte 12 ;C5 | |
; If this byte has the high-bit clear then it is a note pitch | |
; If first byte has high-bit set then it is either a macro, a note off, or indicating no command changes | |
; 10000000 No pitch change | |
; 1M###### Macro 0-31, next byte specifies pitch offset in semitones (+/- 72) | |
; 101----- Note off ($c0) | |
!byte 10 ;Amplitude is 10 | |
; If high-bit is set then it is a command of some sort: | |
; 32: Set the amplitude to the enveloper generator | |
; 11111111 No change to volume | |
; 101SS### Raise (D=1) or Lower (D=1) the amplitude by ### every tick for this row | |
; 1001#### Tremolo (rapid raise and lowering of amplitude, alternating every SS ticks by ### amount each tick) | |
!byte 0 ; No more data for channel A | |
!byte %00001000 ;Bit pattern indicating Channel A has data (AY 2) | |
!byte 12 ;C5 | |
!byte 10 ;Amplitude is 10 | |
!byte 0 ;No more data | |
;Row 2 | |
!byte %10000000 ;Bit pattern indicating no data for row (AY 1/AY 2) | |
;Row 3 | |
!byte %10000000 ;Bit pattern indicating no data for row (AY 1/AY 2) | |
;Row 4 | |
!byte %10000000 ;Bit pattern indicating no data for row (AY 1/AY 2) | |
;Row 5 | |
!byte %00011000 ;Bit pattern indicating Channels A and B have data (AY 1) | |
!byte %10000000 ;No pitch change | |
!byte 7 ;Set amplitude to half | |
!byte 0 ;No more data | |
!byte 7 ;Channel B pitch to G4 | |
!byte 15 ;Amplitude is 15 | |
!byte 0 ;No more data | |
!byte %00011000 ;Bit pattern indicating Channels A and B have data (AY 2) | |
!byte %10000000 ;No pitch change | |
!byte 7 ;Set amplitude to half | |
!byte 0 ;No more data | |
!byte 7 ;Channel B pitch to G4 | |
!byte 15 ;Amplitude is 15 | |
!byte 0 ;No more data | |
;Row 6 | |
!byte %10000000 ;Bit pattern indicating no data for row (AY 1/AY 2) | |
;Row 7 | |
!byte %10000000 ;Bit pattern indicating no data for row (AY 1/AY 2) | |
;Row 8 | |
!byte %10000000 ;Bit pattern indicating no data for row (AY 1/AY 2) | |
;Row 9 | |
!byte %00011000 ;Bit pattern indicating Channels A and B have data (AY 1) | |
!byte %10100000 ;Channel A off | |
!byte 0 ;No more data (note off takes no amplitude byte) | |
!byte %10100000 ;Channel B off | |
!byte 0 ;No more data (note off takes no amplitude byte) | |
!byte %00011000 ;Bit pattern indicating Channels A and B have data (AY 2) | |
!byte %10100000 ;Channel A off | |
!byte 0 ;No more data (note off takes no amplitude byte) | |
!byte %10100000 ;Channel B off | |
!byte 0 ;No more data (note off takes no amplitude byte) | |
;Row 10 | |
!byte %10000000 ;Bit pattern indicating no data for row (AY 1/AY 2) | |
;Row 11 | |
!byte %10000000 ;Bit pattern indicating no data for row (AY 1/AY 2) | |
;Row 12 | |
!byte %10000000 ;Bit pattern indicating no data for row (AY 1/AY 2) | |
;Row 13 | |
!byte %10000000 ;Bit pattern indicating no data for row (AY 1/AY 2) | |
;Row 14 | |
!byte %10000000 ;Bit pattern indicating no data for row (AY 1/AY 2) | |
;Row 15 | |
!byte %10000000 ;Bit pattern indicating no data for row (AY 1/AY 2) | |
;Row 16 | |
!byte %10000000 ;Bit pattern indicating no data for row (AY 1/AY 2) |
Almost have basic playback working but there's something breaking it horribly at the moment.
Changed debugging statements to use new fake NOP commands so output is STDOUT in JACE and doesn't rely on in-emulator display.
Some of the more sillier bugs have been found but there is still no sound playing yet. :(
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Verified basic loop is working now, and IRQ handling is verified. Also added debug statements.