* MAINMEM.ENSQ.S * (c) Bobbi 2022 GPLv3 * * Ensoniq DOC Driver for Apple IIGS. * Simulates Hitachi SN76489 sound generator chip found in BBC Micro. * * Ensoniq control registers ENSQSNDCTL EQU $C03C ENSQSNDDAT EQU $C03D ENSQADDRL EQU $C03E ENSQADDRH EQU $C03F * Initialize Ensoniq * Setup wavetable - one period of a square wave * Start timer on oscillator #4, silence oscillators 0 to 3 ENSQINIT LDX #3 LDA #$80 ; Initialize sound queues :L0 STZ SND0STARTIDX,X STZ SND0ENDIDX,X DEX BNE :L0 * One cycle of square wave for 256 samples * Starts at address $0000 in DOC RAM LDA ENSQSNDCTL ; Get settings ORA #$60 ; DOC RAM, autoincrement on STA ENSQSNDCTL ; Set it LDA #$00 STA ENSQADDRL ; DOC RAM addr $0000 STA ENSQADDRH ; DOC RAM addr $0000 LDA #210 ; High value of square wave LDX #$00 :L1 STA ENSQSNDDAT ; 128 cycles of high value INX CPX #128 BNE :L1 LDA #40 ; Low value of square wave :L2 STA ENSQSNDDAT ; 128 cycles of low value INX CPX #0 BNE :L2 * One cycle of pulse wave for 256 samples * Starts at $0100 in DOC RAM LDA #210 ; High value of square wave LDX #$00 :L3 STA ENSQSNDDAT ; 8 cycles of high value INX CPX #8 BNE :L3 LDA #40 ; Low value of square wave :L4 STA ENSQSNDDAT ; 255-8 cycles of low value INX CPX #0 BNE :L4 * Random waveform for 256 samples * Starts at $0200 in DOC RAM LDY #$00 :L5 LDA $E000,Y ; Read ROM code as 'random' data BNE :S1 ; Filter out zeros LDA #$01 ; Change 0 -> 1 :S1 STA ENSQSNDDAT INY CPY #0 BNE :L5 * One cycle of pulse wave for 4096 samples ** Starts at address $1000 in DOC RAM * LDA ENSQSNDCTL ; Get settings * ORA #$60 ; DOC RAM, autoincrement on * STA ENSQSNDCTL ; Set it * LDA #$00 * STA ENSQADDRL ; DOC RAM addr $1000 * LDA #$10 * STA ENSQADDRH ; DOC RAM addr $1000 * LDA #210 ; High value of pulse wave * LDX #$00 *:L3 STA ENSQSNDDAT ; 128 cycles of high value * INX * CPX #128 * BNE :L3 * LDA #40 ; Low value of pulse wave *:L4 STA ENSQSNDDAT ; 128 cycles of low value * INX * CPX #0 * BNE :L4 * LDY #$00 *:L6 LDX #$00 *:L5 STA ENSQSNDDAT ; 256 cycles of low value * INX * CPX #0 * BNE :L5 * INY * CPY #16 ; Do it 15 times * BNE :L6 LDA #$5C ; GS IRQ.SOUND initialization STAL $E1002C LDA #ENSQISR STAL $E1002E LDA #$00 ; Bank $00 STAL $E1002F LDX #$E1 ; DOC Osc Enable register $E1 LDY #10 ; Five oscillators enabled JSR ENSQWRTDOC LDY #$00 ; Amplitude for osc #4 (timer) LDA #33+1 ; Freq G2+1/8 tone = 99.46Hz LDX #$04 JSR ENSQNOTE ; Start oscillator 4 LDX #$A4 ; Control register for osc #4 LDY #$08 ; Free run, with IRQ, start JSR ENSQWRTDOC ; Fall through * Silence all channels ENSQSILENT LDY #$00 ; Amplitude LDA #$80 ; Frequency LDX #$03 :L1 JSR ENSQNOTE ; Initialize channel Y STZ CHANTIMES,X ; No note playing DEX BPL :L1 RTS * Stop Ensoniq interrupt ENSQSTOP JSR ENSQSILENT LDX #$A4 ; Control register for osc #4 LDY #$00 ; Free run, no IRQ, start JSR ENSQWRTDOC RTS * Configure an Ensoniq oscillator to play a note * On entry: X - oscillator number 0-3 , A - frequency, Y - amplitude * Preserves all registers ENSQNOTE PHA PHX PHY STX OSCNUM ; Stash oscillator number 0-3 CPX #$00 BEQ :NOISE ; Oscillator 0 is noise channel CPX #$01 ; Oscillator 1 controls noise freq BNE :S0 ; Not 1? Skip CMP #$00 ; If frequency is zero .. BEQ :S0 ; .. skip STA :CH1NOTE ; Store frequency for noise gen :S0 PHA ; Stash orig freq TAY LDA EFREQLOW,Y TAY ; Frequency value LS byte LDA #$00 ; DOC register base $00 (Freq Lo) JSR ADDOSC ; Actual register in X JSR ENSQWRTDOC PLA ; Get orig freq back TAY LDA EFREQHIGH,Y TAY ; Frequency value MS byte LDA #$20 ; DOC register base $20 (Freq Hi) JSR ADDOSC ; Actual register in X JSR ENSQWRTDOC PLY ; Amplitude value PHY LDA #$40 ; DOC register base $40 (Volume) JSR ADDOSC ; Actual register in X JSR ENSQWRTDOC LDY #$00 ; Wavetable pointer $00 LDA #$80 ; DOC register base $80 (Wavetable) JSR ADDOSC ; Actual register in X JSR ENSQWRTDOC LDY #$00 ; For 256 byte wavetable, res 0 LDA #$C0 ; DOC register base $C0 (WT size) JSR ADDOSC ; Actual register in X JSR ENSQWRTDOC LDY #$00 ; Free run, no IRQ, start LDA #$A0 ; DOC register base $A0 (Control) JSR ADDOSC ; Actual register in X JSR ENSQWRTDOC BRA :DONE :NOISE PHA ; Preserve value of parameter P AND #$04 ; 'Periodic noise' or 'white noise'? BEQ :S1 LDY #$02 ; DOC RAM $0200 - white noise BRA :S2 :S1 LDY #$01 ; DOC RAM $0100 - periodic noise :S2 LDX #$80 ; Wavetable pointer Register JSR ENSQWRTDOC PLA ; Restore P AND #$03 ; Keep least significant 2 bits TAY LDA :NOISENOTE,Y ; BBC Micro note value :S3 PHA ; Gonna need it again TAY ; Computed note LDA EFREQHIGH,Y TAY ; Frequency value LS byte LDX #$20 ; Freq Hi Register JSR ENSQWRTDOC PLY ; Get computed note back LDA EFREQLOW,Y TAY ; Frequency value LS byte LDX #$00 ; Freq Lo Register JSR ENSQWRTDOC LDX #$40 ; Amplitude Register PLY PHY JSR ENSQWRTDOC LDX #$C0 ; Wavetable size Register LDY #$04 ; Size 256, resolution 4 * LDY #$00 ; Size 256, resolution 1 JSR ENSQWRTDOC LDX #$A0 ; Wavetable size Register LDY #$00 ; Free run, no IRQ, start JSR ENSQWRTDOC :DONE PLY PLX PLA RTS :NOISENOTE DB 149 ; BBC Micro note P=0 or 4 DB 101 ; BBC Micro note P=1 or 5 DB 53 ; BBC Micro note P=2 or 6 :CH1NOTE DB 00 ; Note on channel 1 * Adjust frequency of note already playing * On entry: X - oscillator number 0-3, Y - frequency to set * Preserves X & Y ENSQFREQ PHX PHY ; Gonna need it again LDA EFREQLOW,Y TAY ; Frequency value LS byte LDA #$00 ; DOC register base $00 (Freq Lo) JSR ADDOSC ; Actual register in X JSR ENSQWRTDOC PLY ; Get freq back PHY LDA EFREQHIGH,Y TAY ; Frequency value MS byte LDA #$20 ; DOC register base $20 (Freq Hi) JSR ADDOSC ; Actual register in X JSR ENSQWRTDOC PLY PLX RTS * Adjust amplitude of note already playing * On entry: X - oscillator number 0-3, Y - amplitude to set * Preserves X & Y ENSQAMP PHX PHY ; Gonna need it again LDA #$40 ; DOC register base $00 (Freq Lo) JSR ADDOSC ; Actual register in X JSR ENSQWRTDOC PLY PLX RTS * Ensoniq interrupt service routine - just calls generic audio ISR ENSQISR CLD JSR AUDIOISR RTL ** ** Private functions follow (ie: not part of driver API) ** * Add oscillator number to value in A, return sum in X * Used by ENSQNOTE & ENSQFREQ ADDOSC CLC ADC OSCNUM TAX RTS OSCNUM DB $00 * Wait for Ensoniq to be ready ENSQWAIT LDA ENSQSNDCTL BMI ENSQWAIT RTS * Write to a DOC register * On entry: Value in Y, register in X * Preserves all registers ENSQWRTDOC PHA JSR ENSQWAIT ; Wait for DOC to be ready LDA ENSQSNDCTL AND #$90 ; DOC register, no autoincr ORA #$0F ; Master volume maximum STA ENSQSNDCTL STX ENSQADDRL ; Select DOC register STZ ENSQADDRH STY ENSQSNDDAT ; Write data PLA RTS