Applecorn/mainmem.audio.s

816 lines
33 KiB
ArmAsm
Raw Normal View History

* MAINMEM.AUDIO.S
* (c) Bobbi 2022 GPLv3
*
* Applecorn audio code
*
COUNTER DW $0000 ; Centisecond counter
* Sound buffers
* Four bytes are enqueued for each note, as follows:
* - MS byte of channel number
* - LS byte of channel number
* - Frequency
* - Duration
SNDBUFSZ EQU 21 ; FOR 4 NOTES + spare byte
SNDBUF0 DS SNDBUFSZ
SNDBUF1 DS SNDBUFSZ
SNDBUF2 DS SNDBUFSZ
SNDBUF3 DS SNDBUFSZ
* Pointers for circular buffers
* Buffers 4-7 correspond to audio channels 0 to 3
* Buffers 0-3 are currently unused
SND0STARTIDX DB $00 ; Start indices for sound bufs
SND1STARTIDX DB $00
SND2STARTIDX DB $00
SND3STARTIDX DB $00
STARTINDICES EQU SND0STARTIDX - 4
SND0ENDIDX DB $00 ; End indices for sound bufs
SND1ENDIDX DB $00
SND2ENDIDX DB $00
SND3ENDIDX DB $00
ENDINDICES EQU SND0ENDIDX - 4
* Envelope buffers 0-3
ENVBUF0 DS 13 ; 13 bytes not including env num
ENVBUF1 DS 13
ENVBUF2 DS 13
ENVBUF3 DS 13
* Offsets of parameters in each envelope buffer
ENVT EQU 0 ; Len of step in 1/100 sec
ENVPI1 EQU 1 ; Change pitch/step section 1
ENVPI2 EQU 2 ; Change pitch/step section 2
ENVPI3 EQU 3 ; Change pitch/step section 3
ENVPN1 EQU 4 ; Num steps section 1
ENVPN2 EQU 5 ; Num steps section 2
ENVPN3 EQU 6 ; Num steps section 3
ENVAA EQU 7 ; Attack: change/step
ENVAD EQU 8 ; Decay: change/step
ENVAS EQU 9 ; Sustain: change/step
ENVAR EQU 10 ; Release: change/step
ENVALA EQU 11 ; Target at end of attack
ENVALD EQU 12 ; Target at end of decay
* Time remaining for current note, in 1/20th of second
CHANTIMES DB $00
DB $00
DB $00
DB $00
* Envelope number for current note. $FF if no envelope.
2022-10-16 00:07:41 +00:00
CHANENV DB $FF
DB $FF
DB $FF
DB $FF
* Envelope step counter for current note.
* This is used in order to invoke the envelope processing at the requested
* rate in 1/100th of a second.
CHANCTR DB $00
DB $00
DB $00
DB $00
* Pitch envelope section (0..4)
PITCHSECT DB $00
DB $00
DB $00
DB $00
* Step within pitch envelope section
PITCHSTEP DB $00
DB $00
DB $00
DB $00
* Current pitch
CURRPITCH DB $00
DB $00
DB $00
DB $00
* Amplitude envelope section (0..4)
AMPSECT DB $00
DB $00
DB $00
DB $00
* Current amplitude
CURRAMP DB $00
DB $00
DB $00
DB $00
* Get address of sound buffer
* On entry: X is buffer number (4..7)
* On exit: A1L,A1H points to start of buffer
* Called with interrupts disabled
GETBUFADDR LDA :BUFADDRL,X
STA A1L
LDA :BUFADDRH,X
STA A1H
RTS
:BUFADDRL DB $00
DB $00
DB $00
DB $00
DB <SNDBUF0
DB <SNDBUF1
DB <SNDBUF2
DB <SNDBUF3
DB $00
:BUFADDRH DB $00
DB $00
DB $00
DB $00
DB >SNDBUF0
DB >SNDBUF1
DB >SNDBUF2
DB >SNDBUF3
DB $00
* Insert value into buffer (API same as Acorn MOS INSV)
* On entry: A is value, X is buffer number.
* On exit: A, X, Y preserved. C clear on success.
INS PHP ; Save flags, turn off interrupts
SEI
PHY
PHA
LDY ENDINDICES,X ; Get input pointer
INY ; Next byte
CPY #SNDBUFSZ
BNE :NOTEND ; See if it's the end
LDY #0 ; If so, wraparound
:NOTEND TYA ; New input pointer in A
CMP STARTINDICES,X ; See if buffer is full
BEQ :FULL
LDY ENDINDICES,X ; Current position
STA ENDINDICES,X ; Write updated input pointer
JSR GETBUFADDR ; Buffer address into A1L,A1H
PLA ; Get value to write back
STA (A1L),Y ; Write to buffer
PLY
PLP ; Restore flags
CLC ; Exit with carry clear
RTS
:FULL PLA ; Restore A
PLY
PLP ; Restore flags
SEC ; Exit with carry set
RTS
* Entry point to INS for code running in aux
MAININS >>> ENTMAIN
PHY ; Y->X after transfer
PLX
JSR INS
PHP ; Flags->A before transfer back
PLA
>>> XF2AUX,INSHNDRET
* Remove value from buffer or examine buffer (API same as Acorn MOS REMV)
* NOTE OS1.20 has a bug in the EXAMINE path
* On entry: X is buffer number, V=1 if only examination is requested
* On exit: If examination, A next byte, X preserved, Y=next byte
* If removal, A undef, X preserved, Y=value of byte removed
* If buffer already empty C=1, else C=0
REM PHP ; Save flags, turn off interrupts
SEI
LDA STARTINDICES,X ; Output pointer for buf X
CMP ENDINDICES,X
BEQ :EMPTY ; Buffer is empty
TAY ; Buffer pointer into Y
JSR GETBUFADDR ; Buffer address into A1L,A1H
LDA (A1L),Y ; Read byte from buffer
PHA ; Stash for later
BVS :EXAM ; If only examination, done
INY ; Next byte
CPY #SNDBUFSZ
BNE :NOTEND ; See if it's the end
LDY #0 ; If so, wraparound
:NOTEND TYA
STA STARTINDICES,X ; Set output pointer
PLY ; Char read from buffer
PLP
CLC ; Success
RTS
:EXAM PLA ; Char read from buffer
TAY ; BUGFIX: Omitted on OS1.20
PLP
CLC ; Success
RTS
:EMPTY PLP
SEC ; Buffer already empty
RTS
* Remove value from buffer according to audio channel (0-4)
* On entry: X is audio channel number
* On exit: A undef, X preserved, Y value of byte removed
REMAUDIO PHX ; Preserve X
TXA ; Audio channel X->A
ORA #$04 ; Convert to queue number
TAX ; Queue number ->X
CLV ; Remove byte from queue
JSR REM
PLX ; Recover original X
RTS
* Inspect value in buffer according to audio channel (0-4)
* On entry: X is audio channel number
* On exit: A next byte, X preserved, Y offset to next char
PEEKAUDIO PHX ; Preserve X
TXA ; Audio channel X->A
ORA #$04 ; Convert to queue number
TAX ; Queue number ->X
BIT :RTS ; Set V, inspect queue
JSR REM
PLX ; Recover original X
:RTS RTS
* Count space in buffer or purge buffer (API same as Acorn MOS CNPV)
* On entry: X is buffer number. V set means purge, V clear means count.
* C set means space left, C clear means entries used
* On exit: For purge, X & Y are preserved.
* For count, value in X (Y=0).
* A undef. V,C flags preserved.
CNP PHP ; Preserve flags
BVS :PURGE ; Purge if V set
SEC ; Compute space used
LDA ENDINDICES,X
SBC STARTINDICES,X
BPL :POS ; No wrap-around
CLC ; Wrap-around - add SNDBUFSZ
ADC #SNDBUFSZ
:POS LDY #$00 ; MSB of count always zero
PLP ; Recover flags
BCS :CNTREM ; If C set on entry, count remainder
TAX ; Return value in X
RTS
:CNTREM EOR #$FF ; Negate and add SNDBUFSZ
SEC
ADC #SNDBUFSZ
TAX ; Return value in X
RTS
:PURGE LDA ENDINDICES,X ; Eat all buffer contents
STA STARTINDICES,X
STZ CHANTIMES-4,X ; Set to zero time remaining
PLP ; Recover flags
RTS
* Entry point to CNP for code running in aux
MAINCNP >>> ENTMAIN
PHY ; Y->X after transfer
PLX
PHA ; A->flags after transfer
PLP
BVS :PURGE
JSR CNP ; Count space
PHX ; X->Y for transfer back
PLY
>>> XF2AUX,CNPHNDRET1 ; Return for counting
:PURGE JSR CNP ; Purge buffer
PHX ; X->Y for transfer back
PLY
>>> XF2AUX,CNPHNDRET2 ; Return for purging
* Process releasing of notes once chord is complete.
* On entry: A chord sequence number, X audio channel
* Preserves all registers
CHORD PHA
PHX
PHY
*
* Part 1: Count all notes at head of queues with seq number = A
*
STA :SEQ ; Sequence number looking for
STZ :CNT ; Initialize counter
LDX #3 ; Process all audio queues
:L1 JSR PEEKAUDIO ; See byte at head of queue
BCS :NEXT ; Empty queue
AND #$0F ; Mask out hold nybble
CMP :SEQ ; If matches ..
BNE :NEXT
INC :CNT ; .. count it
:NEXT DEX
BPL :L1 ; Next audio queue
*
* Part 2: If count = seq number + 1
*
INC :SEQ ; Seq number + 1
LDA :CNT ; Compare with counter
CMP :SEQ
BEQ :RELCHORD ; Release notes
:DONE PLY
PLX
PLA
RTS
*
* Part 3: Overwrite seq numbers with zero to release notes.
*
:RELCHORD DEC :SEQ ; Put seq back how it was
LDX #3 ; All audio queues
:L2 JSR PEEKAUDIO ; See byte at head of queue
BCS :NEXT2 ; Empty queue
AND #$0F ; Mask out hold nybble
CMP :SEQ ; See if matches
BNE :NEXT2 ; Nope, skip
PHX
TXA
ORA #$04 ; Convert to buffer number
TAX
JSR GETBUFADDR ; Audio buf addr -> A1L,A1H
PLX
LDA #$00
STA (A1L),Y ; Zero sync nybble (+ hold nybble)
:NEXT2 DEX
BPL :L2 ; Next audio queue
BRA :DONE
:SEQ DB $00 ; Sequence number
:CNT DB $00 ; Counter
* Called from Ensoniq interrupt handler - process audio queue
* Should be called at 100Hz
ENSQISR INC COUNTER+0 ; Increment centisecond timer
BNE :S1
INC COUNTER+1
:S1 DEC :CNT ; Find every 5th cycle
BNE :AT100HZ
LDA #5
STA :CNT
LDX #3 ; Process four audio queues
:L1 LDA CHANTIMES,X ; Time remaining on current note
BEQ :NONOTE ; No note playing
DEC CHANTIMES,X
BRA :NEXT
:NONOTE LDA #$FF ; $FF means 'no envelope'
STA CHANENV,X
LDY #$00 ; Zero volume
LDA #$00 ; Zero freq
JSR ENSQNOTE ; Silence channel Y
JSR PEEKAUDIO ; Inspect byte at head of queue
BCS :NEXT ; Nothing in queue
; NOTE: A contains HS byte of &HSFC
AND #$0F ; Mask out hold nybble
BNE :SYNCSET ; Do not play if sync != 0
* The following is paranoid maybe. Perhaps can be removed once I am debugged. ***
PHX
PHY
INX ; Convert audio channel to buf num
INX
INX
INX
CLV ; Ask to count buffer
CLC ; Ask for space used
JSR CNP ; Go count it
TXA
PLY
PLX
CMP #3 ; At least 4 bytes used?
BMI :NEXT
* End paranoid section. ***
JSR REMAUDIO ; Remove byte from queue
JSR REMAUDIO ; Remove byte from queue
TYA ; Amplitude or envelope -> A
DEC A
BPL :HASENV ; If +ve, value was 1,2,3..
INC A
EOR #$FF ; Negate A
INC A ; ..
ASL ; Multiply by 16
ASL
ASL
ASL
PHA ; Amplitude to stack
LDA #$FF ; $FF means 'no envelope'
STA CHANENV,X
BRA :S2
:HASENV STA CHANENV,X ; Store envelope number
2022-10-16 03:20:23 +00:00
LDA #$01
STA CHANCTR,X ; Set envelope step counter to 1
STZ PITCHSECT,X ; Start on pitch section 0
STZ PITCHSTEP,X ; Start on step 0
2022-10-17 05:54:49 +00:00
STZ AMPSECT,X ; Start on amplitude section 0
LDA #$00 ; Initial amplitude is zero
PHA ; Zero amplitude to stack
:S2 JSR REMAUDIO ; Remove byte from queue
PHY ; Frequency
JSR REMAUDIO ; Remove byte from queue
TYA ; Duration
STA CHANTIMES,X
PLA ; Recover frequency
STA CURRPITCH,X ; Store for pitch envelope
STZ CURRAMP,X ; Amp envelope starts at zero
PLY ; Recover amplitude
JSR ENSQNOTE ; Start note playing
:NEXT DEX
BPL :L1 ; Next audio queue
:AT100HZ ; Here on every call (100Hz)
LDX #3 ; Iterate through channels
:L2 LDA CHANENV,X ; Envelope for this channel?
BMI :NOENV ; $FF means no envelope
JSR ENVTICKS ; Handle envelope tick counter
BCC :NOENV ; This cycle is not a tick
JSR PITCHENV ; Process pitch envelope
JSR ADSRENV ; Process amplitude envelope
:NOENV DEX
BPL :L2 ; Next audio queue
CLC
RTL
:SYNCSET JSR CHORD ; See if chord can be released
BRA :NEXT
:CNT DB $05 ; Used to determine 20Hz cycles
* Handle envelope tick counter
* On entry: X is audio channel #
* On return: CS if this cycle is an envelope tick, CC otherwise.
* X is preserved
ENVTICKS DEC CHANCTR,X ; Decrement counter
BEQ :ZERO ; Expired
CLC ; Not expired
RTS
:ZERO JSR RSTTICKS ; Reset counter
SEC ; Counter had expired
RTS
* Reset envelope tick counter
* On entry: X is audio channel #
* On return: Sets CHANCTR,X to length of each step in 1/100ths
RSTTICKS LDA CHANENV,X ; Get envelope number
TAY
JSR GETENVADDR ; Envelope address in A1L,A1H
LDY #ENVT ; Parm for length of each step
LDA (A1L),Y ; Get value of parm
AND #$7F ; Mask out MSB
STA CHANCTR,X ; Reset counter
RTS
* On entry: Y is envelope number
* On return: A1L,A1H point to start of buffer for this envelope
* X is preserved
GETENVADDR LDA #<ENVBUF0 ; Copy ENVBUF0 to A1L,A1H
STA A1L
LDA #>ENVBUF0
STA A1H
:L1 CPY #$00 ; See if Y is zero
BEQ :DONE ; If so, we are done
LDA A1L ; Add 13 to A1L,A1H
CLC
ADC #13
STA A1L
LDA A1H
ADC #00
STA A1H
DEY ; Decr envelopes remaining
BRA :L1 ; Go again
:DONE RTS
* Process pitch envelope
* On entry: X is audio channel #
* X is preserved
2022-10-18 04:15:39 +00:00
PITCHENV LDA CHANENV,X ; Get envelope number
TAY
JSR GETENVADDR ; Addr of envelope -> A1L,A1H
LDA PITCHSECT,X ; See what section we are in
BEQ :SECT1 ; Section 1, encoded as 0
CMP #$01
BEQ :SECT2 ; Section 2, encoded as 1
CMP #$02
BEQ :SECT3 ; Section 3, encoded as 2
RTS ; Other section, do nothing
:SECT1 LDY #ENVPI1 ; Parm: change pitch/step section 1
LDA (A1L),Y ; Get value of parm
JSR UPDPITCH ; Update the pitch
LDY #ENVPN1 ; Parm: num steps in section 1
LDA (A1L),Y ; Get value of parm
CMP PITCHSTEP,X ; Are we there yet?
BEQ :NXTSECT ; Yes!
INC PITCHSTEP,X ; One more step
RTS
:SECT2 LDY #ENVPI2 ; Parm: change pitch/step section 2
LDA (A1L),Y ; Get value of parm
JSR UPDPITCH ; Update the pitch
LDY #ENVPN2 ; Parm: num steps in section 2
LDA (A1L),Y ; Get value of parm
CMP PITCHSTEP,X ; Are we there yet?
BEQ :NXTSECT ; Yes!
INC PITCHSTEP,X ; One more step
RTS
:SECT3 LDY #ENVPI3 ; Parm: change pitch/step section 3
LDA (A1L),Y ; Get value of parm
JSR UPDPITCH ; Update the pitch
LDY #ENVPN3 ; Parm: num steps in section 3
LDA (A1L),Y ; Get value of parm
CMP PITCHSTEP,X ; Are we there yet?
BEQ :LASTSECT ; Yes!
INC PITCHSTEP,X ; One more step
RTS
:NXTSECT INC PITCHSECT,X ; Next section
STZ PITCHSTEP,X ; Back to step 0 of section
RTS
:LASTSECT LDY #ENVT ; Parm: length/step + autorepeat
LDA (A1L),Y ; Get value of parm
AND #$80 ; MSB is auto-repeat flag
BEQ :NXTSECT ; Not repeating
STZ PITCHSECT,X ; Go back to section 1
STZ PITCHSTEP,X ; Back to step 0 of section
RTS
* Update pitch value. Called by PITCHENV.
* On entry: A - Change of pitch/step, X is audio channel #
* X is preserved
2022-10-16 03:04:43 +00:00
UPDPITCH STX OSCNUM
CLC
ADC CURRPITCH,X ; Add change to current
STA CURRPITCH,X ; Update
2022-10-16 03:04:43 +00:00
TAY
JSR ENSQFREQ ; Update Ensoniq regs
RTS
* Process amplitude envelope
* On entry: X is audio channel #
* X is preserved
2022-10-18 04:15:39 +00:00
ADSRENV LDA CHANENV,X ; Get envelope number
TAY
JSR GETENVADDR ; Addr of envelope -> A1L,A1H
LDA AMPSECT,X ; See what section we are in
BEQ :ATTACK ; Attack, encoded as 0
CMP #$01
BEQ :DECAY ; Decay, encoded as 1
CMP #$02
BEQ :SUSTAIN ; Sustain, encoded as 2
* TODO: RELEASE logic here
RTS
2022-10-17 05:54:49 +00:00
:ATTACK LDY #ENVAA ; Parm: attack change/step
LDA (A1L),Y ; Get value of parm
2022-10-17 05:54:49 +00:00
PHA
LDY #ENVALA ; Parm: level at end of attack
LDA (A1L),Y ; Get value of parm
2022-10-17 05:54:49 +00:00
PLY
JSR ADSRPHASE ; Generic ADSR phase handler
BCS :NEXTSECT ; Phase done -> decay
RTS
2022-10-17 05:54:49 +00:00
:DECAY LDY #ENVAD ; Parm: delay change/step
LDA (A1L),Y ; Get value of parm
PHA
LDY #ENVALD ; Parm: level at end of delay
LDA (A1L),Y ; Get value of parm
PLY
2022-10-18 04:15:39 +00:00
JSR ADSRPHASE ; Generic ADSR phase handler
2022-10-17 05:54:49 +00:00
BCS :NEXTSECT ; Phase done -> sustain
RTS
2022-10-17 05:54:49 +00:00
:SUSTAIN LDY #ENVAS ; Parm: delay change/step
LDA (A1L),Y ; Get value of parm
TAY
LDA #$00 ; Target level zero
2022-10-18 04:15:39 +00:00
JSR ADSRPHASE ; Generic ADSR phase handler
RTS
:NEXTSECT INC AMPSECT,X ; Next section
RTS
2022-10-17 05:54:49 +00:00
* Handle any individual phase of the ADSR envelope. Called by ADSRENV.
* On entry: A - level at end of phase, X - audio channel, Y - change/step
* On return: CS if end of phase, CC otherwise. X preserved.
2022-10-18 04:15:39 +00:00
ADSRPHASE STX OSCNUM
STA :TARGET ; Stash target level for later
CPY #$00 ; Check sign of change/step
2022-10-25 18:36:02 +00:00
BEQ :DONE ; If change/step is zero
2022-10-18 04:15:39 +00:00
BMI :DESCEND ; Descending amplitude
:ASCEND CMP CURRAMP,X ; Compare tgt with current level
2022-10-17 05:54:49 +00:00
BNE :S1 ; Not equal to target, keep going
SEC ; CS to indicate phase is done
RTS
2022-10-18 04:15:39 +00:00
:S1 TYA ; Change/step -> A
2022-10-17 05:54:49 +00:00
CLC
ADC CURRAMP,X ; Add change to current amp
CMP :TARGET ; Compare with target
BCS :CLAMP ; If target < sum, clamp to target
BRA :UPDATE
2022-10-18 04:15:39 +00:00
:DESCEND CMP CURRAMP,X ; Compare tgt with current level
BNE :S2 ; Not equal to target, keep going
SEC ; CS to indicate phase is done
RTS
:S2 TYA ; Change/step -> A
CLC
ADC CURRAMP,X ; Add change to current amp
CMP :TARGET ; Compare with target
2022-10-17 05:54:49 +00:00
BCC :CLAMP ; If target >= sum, clamp to target
BRA :UPDATE
:CLAMP LDA :TARGET ; Recover target level
:UPDATE STA CURRAMP,X ; Store updated amplitude
TAY ; Tell the Ensoniq
JSR ENSQAMP
2022-10-25 18:36:02 +00:00
:DONE CLC ; CC to indicate phase continues
2022-10-17 05:54:49 +00:00
RTS
:TARGET DB $00
*****************************************************************************
* Ensoniq DOC Driver for Apple IIGS Follows ...
*****************************************************************************
* 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
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 #120 ; High value of square wave
LDX #$00
:L1 STA ENSQSNDDAT ; 128 cycles of high value
INX
CPX #128
BNE :L1
LDA #80 ; Low value of square wave
:L2 STA ENSQSNDDAT ; 128 cycles of low value
INX
CPX #0
BNE :L2
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 #$20 ; Frequency for osc #4 (timer)
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
* 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
PHA ; Stash orig freq
TAY
LDA FREQLOW,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 FREQHIGH,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 ; Free run, no IRQ, start
LDA #$A0 ; DOC register base $A0 (Control)
JSR ADDOSC ; Actual register in X
JSR ENSQWRTDOC
LDY #$00 ; For 256 byte wavetable
LDA #$C0 ; DOC register base $C0 (WT size)
JSR ADDOSC ; Actual register in X
JSR ENSQWRTDOC
PLY
PLX
PLA
RTS
* Adjust frequency of note already playing
* On entry: Y - frequency to set
* Preserves X & Y
ENSQFREQ PHX
PHY ; Gonna need it again
LDA FREQLOW,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 FREQHIGH,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: 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
* 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