* MAINMEM.AUDIO.S * (c) Bobbi 2022 GPLv3 * * Applecorn audio code * SYSCLOCK DB $00 ; Centisecond counter (5 bytes) DB $00 DB $00 DB $00 DB $00 * 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 ; 5 notes, plus a 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-15 ENVBUF0 DS 13 ; 13 bytes not including env num ENVBUF1 DS 13 ENVBUF2 DS 13 ENVBUF3 DS 13 ENVBUF4 DS 13 ENVBUF5 DS 13 ENVBUF6 DS 13 ENVBUF7 DS 13 ENVBUF8 DS 13 ENVBUF9 DS 13 ENVBUF10 DS 13 ENVBUF11 DS 13 ENVBUF12 DS 13 ENVBUF13 DS 13 ENVBUF14 DS 13 ENVBUF15 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 * Does not include the release phase of the envelope (if any) CHANTIMES DB $00 DB $00 DB $00 DB $00 * Envelope number for current note. $FF if no envelope. 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..3) * 0: Attack * 1: Decay * 2: Sustain * 3: Release 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 * 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 >>> IENTMAIN ; Do NOT enable interrupts!! 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 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 * Release a suspended note by overwriting its sequence number with zero * On entry: X is audio channel number * On exit: X preserved RELNOTE PHX ; Preserve X TXA ; Audio channel X->A ORA #$04 ; Convert to queue number TAX ; Queue number ->X JSR GETBUFADDR ; Buffer address into A1L,A1H LDA STARTINDICES,X ; Output pointer for buf X TAY LDA (A1L),Y ; Obtain Hold/Sync byte AND #$F0 ; Set sync nybble to zero .. STA (A1L),Y ; .. to release the note 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 >>> IENTMAIN ; Do NOT enable interrupts 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 JSR RELNOTE ; Release the note :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 SYSCLOCK+0 ; Increment system clock BNE :S1 INC SYSCLOCK+1 BNE :S1 INC SYSCLOCK+2 BNE :S1 INC SYSCLOCK+3 BNE :S1 INC SYSCLOCK+4 :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 JSR NONOTE ; Handle end note / release phase :PEEK JSR PEEKAUDIO ; Inspect byte at head of queue BCS :NEXT ; Nothing in queue ; NOTE: A contains HS byte of &HSFC TAY ; Stash for later AND #$0F ; Mask out hold nybble BNE :SYNCSET ; Do not play if sync != 0 TYA ; HS byte AND #$F0 ; Mask out sync nybble BNE :HOLDSET ; Handle hold function JSR CHECK4BYTES ; Check queue has a note BCS :NEXT ; Less than 4 bytes, skip JSR REMAUDIO ; Remove HS byte from queue JSR REMAUDIO ; Remove amplitude 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 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 STZ AMPSECT,X ; Start on amplitude section 0 LDA #$00 ; Initial amplitude is zero PHA ; Zero amplitude to stack :S2 JSR REMAUDIO ; Remove freq byte from queue PHY ; Frequency JSR REMAUDIO ; Remove dur byte from queue TYA ; Duration DEC A ; EXPERIMENT STA CHANTIMES,X PLA ; Recover frequency STA CURRPITCH,X ; Store for pitch envelope 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 :HOLDSET LDA CURRAMP,X ; Get current amplitude BNE :NEXT ; If non zero, hold JSR REMAUDIO ; Dequeue four bytes JSR REMAUDIO JSR REMAUDIO JSR REMAUDIO JMP :PEEK ; Immediately dispatch next note :SYNCSET JSR CHORD ; See if chord can be released BRA :NEXT :CNT DB $05 ; Used to determine 20Hz cycles * Helper function for ENSQISR - called when no note playing * On entry: X is audio channel # NONOTE LDA CHANENV,X ; See if envelope is in effect CMP #$FF BNE :RELEASE ; If envelope -> start rel phase STZ CURRAMP,X ; Next env will start at zero vol LDY #$00 ; Zero volume LDA #$00 ; Zero freq JSR ENSQNOTE ; Silence channel Y RTS :RELEASE LDA #3 ; Phase 3 is release phase STA AMPSECT,X ; Force release phase RTS * Helper function for ENSQISR * On entry: X is audio channel # * On return: CS if there are <= 4 bytes in queue, CC otherwise * X is preserved CHECK4BYTES PHX 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 PLX CMP #3 ; At least 4 bytes used? BMI :NO CLC RTS :NO SEC RTS * 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 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 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 #ENVPN1 ; Parm: num steps in section 1 LDA (A1L),Y ; Get value of parm CMP PITCHSTEP,X ; Are we there yet? BEQ :NXTSECT ; Yes! LDY #ENVPI1 ; Parm: change pitch/step section 1 LDA (A1L),Y ; Get value of parm JSR UPDPITCH ; Update the pitch INC PITCHSTEP,X ; One more step RTS :SECT2 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! LDY #ENVPI2 ; Parm: change pitch/step section 2 LDA (A1L),Y ; Get value of parm JSR UPDPITCH ; Update the pitch INC PITCHSTEP,X ; One more step RTS :SECT3 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! LDY #ENVPI3 ; Parm: change pitch/step section 3 LDA (A1L),Y ; Get value of parm JSR UPDPITCH ; Update the pitch 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 BNE :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 UPDPITCH STX OSCNUM CLC ADC CURRPITCH,X ; Add change to current STA CURRPITCH,X ; Update TAY JSR ENSQFREQ ; Update Ensoniq regs RTS * Process amplitude envelope * On entry: X is audio channel # * X is preserved 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 CMP #$03 BEQ :RELEASE ; Release, encoded as 3 RTS ; Otherwise nothing to do :ATTACK LDY #ENVAA ; Parm: attack change/step LDA (A1L),Y ; Get value of parm PHA LDY #ENVALA ; Parm: level at end of attack LDA (A1L),Y ; Get value of parm PLY JSR ADSRPHASE ; Generic ADSR phase handler BCS :NEXTSECT ; Phase done -> decay RTS :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 JSR ADSRPHASE ; Generic ADSR phase handler BCS :NEXTSECT ; Phase done -> sustain RTS :SUSTAIN LDY #ENVAS ; Parm: delay change/step LDA (A1L),Y ; Get value of parm TAY LDA #$00 ; Target level zero JSR ADSRPHASE ; Generic ADSR phase handler RTS :RELEASE LDY #ENVAR ; Parm: attack change/step LDA (A1L),Y ; Get value of parm TAY LDA #$00 ; Target level zero JSR ADSRPHASE ; Generic ADSR phase handler BCS :FINISH ; Level is zero RTS :NEXTSECT INC AMPSECT,X ; Next section RTS :FINISH LDA #$FF ; Finished with envelope STA CHANENV,X RTS * 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. ADSRPHASE STX OSCNUM STA :TARGET ; Stash target level for later CPY #$00 ; Check sign of change/step BEQ :DONE ; Change/step is zero BMI :DESCEND ; Descending amplitude :ASCEND CMP CURRAMP,X ; Compare tgt with current level BNE :S1 ; Not equal to target, keep going SEC ; CS to indicate phase is done RTS :S1 TYA ; Change/step -> A CLC ADC CURRAMP,X ; Add change to current amp CMP :TARGET ; Compare with target BCS :CLAMP ; If target < sum, clamp to target BRA :UPDATE :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 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 :DONE CLC ; CC to indicate phase continues 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 $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 * 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