Applecorn/mainmem.ensq.s
2022-11-25 23:09:40 -05:00

318 lines
12 KiB
ArmAsm

* 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 $E1002D
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