The Terrific Audio Driver ca65 API has been implemented and can be found at https://github.com/undisbeliever/terrific-audio-driver/tree/main/audio-driver/ca65-api
The ca65 API used in Terrific Audio Driver might differ from this document.
DRAFT
Last updated: 15 February 2024
- Should I gate
Tad_QueueCommand_Pause
,Tad_QueueCommand_Unpause
, etc behind a.ifdef
? - Should I add
Tad_PauseCommandOverride
,Tad_UnpauseCommandOverride
, etc functions? - Should
Tad_Init
detect if the loader is running?- Less likely to deadlock if
Tad_Init
accidentally called. - This will require a multi-scanline delay waiting for the loader and IPL to be ready.
(No easy way to detect if there was a delay between reset andTad_Init
call (as copiers do not start at scanline 0))
- Less likely to deadlock if
- Should
Tad_Init
disable interrupts?
All functions in this document are called with the following register state:
- Native mode
- 8 bit Memory
- 16 bit Index (unless otherwise stated)
D
= 0DBR
accesses low-RAM (0x7e or 0x00..=0x3F or 0x80..=0xBF)- Not in an interrupt ISR
The following is embedded (uncompressed) in the ROM:
- Loader
- Audio Driver
- Blank Song
The following data is returned by the LoadSongData
callback:
- Common Audio Data
- samples, pitch table and sound effects
- Song Data
- only 1 song is loaded into audio-RAM at any given time
All variables MUST ONLY be written to by the TAD subroutines.
max_transfer_per_frame
- The maximum number of bytes to transfer to Audio-RAM perTad_Process
call.next_song
- the next song to load after the common audio data is loaded into memory
There are 4 queues, in order of priority:
- The remaining data to transfer into Audio-RAM
- The data is allowed to cross bank boundaries.
- If
data_ptr + max_transfer_per_frame
crosses a bank boundary:0x10000 - (data_ptr & 0xffff)
bytes will be transferred in theTad_Process
call.data_ptr
is advanced to the start of the next bank (based on the memory map)- Subsequent calls to
Tad_Process
will continue the transfer until data size == 0.
- Only accessed in
LOADING_*
states
- The next song to load into Audio-RAM
- If this value is 0, a blank (silent) song will be used
- Accessed after common audio data is loaded into Audio-RAM
- The next command to send to the audio driver (and command parameter)
- The queue is empty of
next_command
is negative
- The queue is empty of
- The next sound effect to play (and sfx pan)
- Lower sound effect indexes take priority over higher sound effect indexes (as defined by the project file sound effect export order)
- No queued sound effect = 0xff
All four queues have a maximum size of 1.
NULL
WAITING_FOR_LOADER
LOADING_COMMON_AUDIO_DATA
LOADING_SONG_DATA
PAUSED
- Must be second last so I can use
cmp
and return carry inTad_IsSongLoaded
- Must be second last so I can use
PLAYING
- Must be last so I can use
cmp
and return carry inTad_IsPlaying
- Must be last so I can use
- Stereo
- If set, the next song will be played in stereo
- If clear, the next song will be played in mono
- Default: Mono
- PlaySongImmediatly
- If set, the audio driver will play the song after the next song is loaded into Audio-RAM
- If clear, the audio driver will be paused after the next song is loaded into Audio-RAM
- Default: Set
- ReloadCommonAudioData
- If set, the common audio data will be loaded into audio-RAM the next time a song is requested.
- If clear, the common audio data will remain unchanged.
HIROM
- The game uses HiROM mappingLOROM
- The game uses LoROM mapping
-
Pause (no parameter)
- Stop song and sound effect execution
- The audio driver can still process commands while paused
-
Unpause (no parameter)
- Unpauses the audio driver
- Resets the S-SMP timer counters
- Can cause issues if this command is repeatedly sent to the audio driver
-
Stop sound effects (no parameter)
- Stop all active sound effects
-
Set Main Volume (signed i8 parameter)
- NOTE: The main volume is reset whenever a new song is loaded into Audio-RAM
-
Set Enabled Channels (8 channel u8 bitmask)
- Useful for disabling channels in a song
- NOTE: The enabled channels bitmask is reset whenever a song is loaded
- NOTE: Channels 6 & 7 are used by sound effects
-
Set Song tempo (u8 TIMER0 register value)
- NOTE: The song can still change the tempo
Initialises the audio driver:
- Loads the loader into Audio-RAM
- Loads the audio driver into Audio-RAM
- Sets the song to 0 (silence)
- Resets the state and flags
- Queues a common audio data transfer
TIMING:
- Should be called more than 40 scanlines after reset.
- Otherwise
Tad_Init
will need to wait until the S-SMP IPL is ready.
- Otherwise
- MUST be called ONCE
- Calling
Tad_Init
more than once will hardlock.
- Calling
- MUST be called with INTERRUPTS DISABLED
- MUST be called while the S-SMP is running the IPL.
- MUST be called after the LoadAudioData callback is setup (if necessary)
Tad_Init
MUST be called before any other TAD subroutine.
Processes the next queue. This function will do one of the following, depending on the state:
- Transfer up to
max_transfer_per_frame
bytes of common audio data or song data to Audio-RAM.- May call
LoadAudioData
.
- May call
- Send a command to the audio driver.
- Send a play-sound effect command to the audio driver.
NOTES: The command and sound-effect queues will be reset after a new song is loaded into Audio-RAM.
TIMING:
- Should be called once per frame.
- MUST be called after
Tad_Init
. - MUST NOT be called in an interrupt.
Finish loading the data into audio-RAM.
This function can be safely called by LoadAudioData
.
This function may require multiple frames of execution time.
Adds a command to the queue if the queue is empty.
MUST NOT be used to send a play-sound-effect command.
The command queue can only hold 1 command.
INPUT:
- A = command
- X = command parameter (if any). Only the lower 8 bits will be sent to the Audio Driver.
OUTPUT:
- Carry set if the command was added to the queue
REGISTER STATE:
- 8 bit Memory
- 8 or 16 bit Index
Adds a command to the queue, overriding the any previously queued commands.
MUST NOT be used to send a play-sound-effect command.
CAUTION: This subroutine can override any PAUSE or UNPAUSE commands.
INPUT:
- A = command
- X = command parameter (if any). Only the lower 8 bits will be sent to the Audio Driver.
OUTPUT:
- Carry set
REGISTER STATE:
- 8 bit Memory
- 8 or 16 bit Index
Only 1 sound effect can be queued at a time. Sound effects with a lower sound effect index will override the queue.
Queued sound effects might not be played. They could be overridden by a future QueueSoundEffect
call or the audio driver might not have a free channel to play the sound effect with.
INPUT:
- A - sound effect index (as determined by the sound effect export order in the project file)
- X - pan (only the lower 8 bits are used. If pan is > MAX_PAN, the sound effect will use center pan)
REGISTER STATE:
- 8 bit Memory
- 8 or 16 bit Index
Calls QueuePannedSoundEffect
with center pan (MAX_PAN/2).
REGISTER STATE:
- 8 bit Memory
- 8 or 16 bit Index
Disables the audio driver, starts the loader and queues a song transfer.
CAUTION: The audio driver starts in the paused state if the PlaySongImmediatly
flag is false.
CAUTION: Tad_Process
will call LoadAudioData
(which can take a while if it decompresses song data).
INPUT:
- A = 0 - Play a blank (silent) song
- A >= 1 - Play song number
A
If this subroutine is called, the common audio data will be reloaded into Audio-RAM.
This will not take effect until the next LoadSong
call.
Clears the stereo flag.
This will not take effect until the next LoadSong
call.
Sets the stereo flag.
This will not take effect until the next LoadSong
call.
Reads the mono/stereo flag
OUTPUT:
- Carry set if stereo
Sets the PlaySongImmediatly
flag
Clears the PlaySongImmediatly
flag
Sets the number of bytes to transfer to Audio-RAM per Tad_Process
call.
The value will be clamped from MIN_TRANSFER_PER_FRAME
to MAX_TRANSFER_PER_FRAME
.
INPUT: X = number of bytes to transfer on every Tad_Process
call.
OUTPUT:
- Carry set if the loader is still using data returned by
LoadAudioData
(state == LOADING_*)
OUTPUT:
- Carry set if the song is loaded into memory (state >= PAUSED)
OUTPUT:
- Carry set if state == PLAYING
The following subroutines will call Tad_QueueCommand
(returning carry set if command queued successfully):
Tad_QueueCommand_Pause
Tad_QueueCommand_Unpause
Tad_QueueCommand_StopSoundEffects
Tad_QueueCommand_SetMainVolume
Tad_QueueCommand_SetEnableChannels
Tad_QueueCommand_SetSongTempo
This callback will be called by Tad_Process
when the common audio data or song data need to be
loaded into Audio-RAM.
This subroutine is responsible for determining if the input is a valid song.
If the input is common audio data (A = 0
) this function MUST return data with carry set.
This subroutine can be generated by tad-compiler
. Alternatively, the developer can create their
own LoadAudioData
subroutine if they wish to use their own resources subsystem or want to use
compressed song data.
The data is allowed to cross bank boundaries. When the data crosses a bank boundary, data_ptr
is advanced to start of the next bank (as determined by the bank byte and memory map).
Called with JSL long addressing (return with RTL).
INPUT:
- A = 0 - load common audio data (This function MUST return carry set with a valid address/size if A = 0)
- A >= 1 - load song data
OUTPUT:
- Carry set if input (
A
) was valid - A:X = far address (only read if carry set)
- Y = data size (only read if carry set)
LIFETIME:
- The data MUST remain in memory while it is being transferred to Audio-RAM (which may take several frames).
- The data can be freed on the next
LoadAudioData
call. - The data can be freed when the state changes to PAUSED or PLAYING.
- The data can be freed if the
Tad_IsLoaderActive
function returns false. - The
Tad_FinishLoadingData
subroutine can be used to flush decompressed memory into audio-RAM. The data can be freed immediately after aTad_FinishLoadingData
call.
This function MUST NOT call Tad_Process
or Tad_LoadSong
. It is allowed to call Tad_FinishLoadingData
.
tad-compiler ca65-enums
will output an include file that contains:
- Song enum
- SoundEffect enum
tad-compiler
will have also an option to output a single binary file and generate the
LoadAudioData callback subroutine.
tad-compiler ca65-export
will compile the entire project and output it to an uncompressed binary file,
along with an assembly file that contains:
.incbin
statements to load the binary file into the ROMLoadAudioData
callback- link-time
.assert
statements to ensure the memory map is correct
Alternatively, the developer can create their own LoadAudioData callback subroutine and
populate the ROM with the output of the tad-compiler common
and tad-compiler song
commands.