diff --git a/include/dusk/audio/DuskAudioSystem.h b/include/dusk/audio/DuskAudioSystem.h index b06bdbe84e..c41762e146 100644 --- a/include/dusk/audio/DuskAudioSystem.h +++ b/include/dusk/audio/DuskAudioSystem.h @@ -1,5 +1,8 @@ #pragma once namespace dusk::audio { + /** + * Initialize the audio system and start playing audio. + */ void Initialize(); } diff --git a/src/dusk/audio/DuskAudioSystem.cpp b/src/dusk/audio/DuskAudioSystem.cpp index e01d427901..7c2572dd53 100644 --- a/src/dusk/audio/DuskAudioSystem.cpp +++ b/src/dusk/audio/DuskAudioSystem.cpp @@ -16,7 +16,7 @@ #include "DuskDsp.hpp" #include "JSystem/JAudio2/JASAudioThread.h" -#define DUSK_DUMP_AUDIO +// #define DUSK_DUMP_AUDIO using namespace dusk::audio; @@ -25,13 +25,25 @@ static std::array OutInte static SDL_AudioStream* PlaybackStream; +/** + * SDL audiostream callback to trigger rendering of new audio data. + */ static void SDLCALL GetNewAudio( - void *userdata, - SDL_AudioStream *stream, - int additional_amount, - int total_amount); + void*, + SDL_AudioStream*, + int needed, + int); +/** + * Render an entire new frame of audio and output it to SDL3. + * Note: "audio frames" are unrelated to video frames. + * @return Amount of audio samples rendered. + */ static int RenderNewAudioFrame(); + +/** + * Render an audio subframe and output it to SDL3. + */ static void RenderAudioSubframe(); static void InitSDL3Output() { diff --git a/src/dusk/audio/DuskDsp.cpp b/src/dusk/audio/DuskDsp.cpp index f52d191d09..97c6f1c6d1 100644 --- a/src/dusk/audio/DuskDsp.cpp +++ b/src/dusk/audio/DuskDsp.cpp @@ -16,6 +16,9 @@ using namespace dusk::audio; ChannelAuxData dusk::audio::ChannelAux[DSP_CHANNELS] = {}; +/** + * Validate that a DSP channel's format is actually something we know how to play. + */ static bool ValidateChannelWaveFormat(const JASDsp::TChannel& channel) { if (channel.mSamplesPerBlock == AdpcmSampleCount && channel.mBytesPerBlock == Adpcm4FrameSize) return true; @@ -30,6 +33,9 @@ static bool ValidateChannelWaveFormat(const JASDsp::TChannel& channel) { return false; } +/** + * Validate that a DSP channel is actually something we know how to play. + */ static void ValidateChannel(const JASDsp::TChannel& channel) { if (!ValidateChannelWaveFormat(channel)) { CRASH( @@ -39,39 +45,27 @@ static void ValidateChannel(const JASDsp::TChannel& channel) { } } -static u32 ConvertDataLengthToSamples(const JASDsp::TChannel& channel, u32 dataLen) { - if (dataLen % channel.mBytesPerBlock != 0) { - CRASH("Indivisible data length: %d\n", dataLen); - } - - return (dataLen / channel.mBytesPerBlock) * channel.mSamplesPerBlock; -} - static u32 ConvertSamplesToDataLength(const JASDsp::TChannel& channel, u32 samples) { - if (channel.mSamplesPerBlock == 1) { - if (channel.mBytesPerBlock == 16) { - return samples * 2; - } - if (channel.mBytesPerBlock == 8) { - return samples; - } - CRASH("Unknown format"); - } - if (samples % channel.mSamplesPerBlock != 0) { // Ensure we round up. samples += channel.mSamplesPerBlock; //CRASH("Indivisible sample count: %d\n", samples); } - return (samples / channel.mSamplesPerBlock) * channel.mBytesPerBlock; + return (samples / channel.mSamplesPerBlock) * BlockBytes(channel); } +/** + * Render the audio data contributed by a single DSP channel. Reads & decodes new input samples. + */ static void RenderChannel( JASDsp::TChannel& channel, ChannelAuxData& channelAux, OutputSubframe& subframe); +/** + * Converts a pitch value on a DSP channel to a sample rate. + */ constexpr static int PitchToSampleRate(u16 value) { return static_cast(static_cast(SampleRate) * value / 4096); } @@ -89,6 +83,9 @@ static void UpdateSampleRate(const JASDsp::TChannel& channel, ChannelAuxData& au aux.prevPitch = channel.mPitch; } +/** + * Reset state for a DSP channel between independent playbacks. + */ static void ResetChannel(JASDsp::TChannel& channel, ChannelAuxData& aux) { channel.mSamplesLeft = channel.mEndSample - channel.mSamplePosition; @@ -101,6 +98,9 @@ static void ResetChannel(JASDsp::TChannel& channel, ChannelAuxData& aux) { channel.mResetFlag = false; } +/** + * Mix subframe data from src into dst. + */ static void MixSubframe(DspSubframe& dst, const DspSubframe& src) { for (int i = 0; i < dst.size(); i++) { dst[i] += src[i]; @@ -142,14 +142,16 @@ void dusk::audio::DspRender(OutputSubframe& subframe) { } } -static void ReadSamplesCore( +/** + * Actually decode samples from memory for the given audio channel. + */ +static void ReadSampleData( const JASDsp::TChannel& channel, + ChannelAuxData& aux, const u8* data, size_t dataLength, s16* pcm, - size_t pcmLength, - s16& hist1, - s16& hist0) { + size_t pcmLength) { if (channel.mSamplesPerBlock == 1) { if (channel.mBytesPerBlock == 0x10) { // PCM16 @@ -166,16 +168,62 @@ static void ReadSamplesCore( } } else { if (channel.mBytesPerBlock == 9) { - Adpcm4ToPcm16(data, dataLength, pcm, pcmLength, hist1, hist0); + Adpcm4ToPcm16(data, dataLength, pcm, pcmLength, aux.hist1, aux.hist0); } else { CRASH("Unsupported format: ADPCM2"); } } } +/** + * Read a single *contiguous* chunk of sample data from a channel, + * writes the samples to the channel's resampler stream. + * + * @returns Amount of samples actually read. + */ +static int ReadChannelSamplesChunk( + JASDsp::TChannel& channel, + ChannelAuxData& aux, + int desiredSamples) { + + assert(desiredSamples >= 0); + + auto aramBase = static_cast(ARGetStorageAddress()) + channel.mWaveAramAddress; + + // Streaming logic directly modifies mSamplesLeft. + // So we use that as our tracking of where we are. + auto curSamplePosition = channel.mEndSample - channel.mSamplesLeft; + assert(curSamplePosition % channel.mSamplesPerBlock == 0); + auto dataPosition = ConvertSamplesToDataLength(channel, curSamplePosition); + + u32 renderSamples = std::min(channel.mSamplesLeft, static_cast(desiredSamples)); + + int renderSize = static_cast(sizeof(s16) * renderSamples); + auto renderData = static_cast(alloca(renderSize)); + memset(renderData, 0, renderSize); + + ReadSampleData( + channel, + aux, + aramBase + dataPosition, + ConvertSamplesToDataLength(channel, renderSamples), + renderData, + renderSamples); + + channel.mSamplesLeft -= renderSamples; + channel.mSamplePosition += renderSamples; + + SDL_PutAudioStreamData(aux.resampleStream, renderData, renderSize); + + return static_cast(renderSamples); +} + +/** + * Reads new audio channels from a DSP channel and writes them to the resampler stream. + */ static void SDLCALL ReadChannelSamples( void *userdata, - SDL_AudioStream *stream, + SDL_AudioStream*, int additional_amount, int) { @@ -195,30 +243,8 @@ static void SDLCALL ReadChannelSamples( additional_amount = ALIGN_NEXT(additional_amount, channel.mSamplesPerBlock); - int requestedSize = static_cast(sizeof(s16) * additional_amount); - auto requested = static_cast(alloca(requestedSize)); - memset(requested, 0, requestedSize); - - auto aramBase = static_cast(ARGetStorageAddress()) + channel.mWaveAramAddress; - - // Streaming logic directly modifies mSamplesLeft. - // So we use that as our tracking of where we are. - auto curSamplePosition = channel.mEndSample - channel.mSamplesLeft; - auto dataPosition = ConvertSamplesToDataLength(channel, curSamplePosition); - - u32 renderSamples = std::min(channel.mSamplesLeft, static_cast(additional_amount)); - - ReadSamplesCore( - channel, - aramBase + dataPosition, - ConvertSamplesToDataLength(channel, renderSamples), - requested, - renderSamples, - aux.hist1, - aux.hist0); - - channel.mSamplesLeft -= renderSamples; - channel.mSamplePosition += renderSamples; + auto samplesRead = ReadChannelSamplesChunk(channel, aux, additional_amount); + additional_amount -= samplesRead; if (channel.mSamplesLeft == 0) { // Reached end of buffer. @@ -228,28 +254,19 @@ static void SDLCALL ReadChannelSamples( channel.mSamplesLeft = channel.mEndSample - channel.mLoopStartSample; channel.mSamplePosition = channel.mLoopStartSample; - curSamplePosition = channel.mEndSample - channel.mSamplesLeft; - dataPosition = ConvertSamplesToDataLength(channel, curSamplePosition); + } - ReadSamplesCore( - channel, - aramBase + dataPosition, - ConvertSamplesToDataLength(channel, additional_amount - renderSamples), - requested + renderSamples, - additional_amount - renderSamples, - aux.hist1, - aux.hist0); - - channel.mSamplesLeft -= (additional_amount - renderSamples); - channel.mSamplePosition += (additional_amount - renderSamples); + if (additional_amount >= 0) { + ReadChannelSamplesChunk(channel, aux, additional_amount); } channel.mAramStreamPosition = channel.mWaveAramAddress + ConvertSamplesToDataLength(channel, channel.mSamplePosition); - - SDL_PutAudioStreamData(stream, requested, requestedSize); } +/** + * Get the expected BusConnect value needed to define the given output channel in a DSP channel. + */ constexpr u16 GetBusConnect(const OutputChannel channel) { switch (channel) { // TODO: This is a guess for now. @@ -262,6 +279,10 @@ constexpr u16 GetBusConnect(const OutputChannel channel) { } } +/** + * For a DSP channel the JASDsp::OutputChannelConfig value targeting the given output channel. + * Returns null if the DSP channel does not output to this output channel. + */ static const JASDsp::OutputChannelConfig* GetOutputConfig( const JASDsp::TChannel& sourceChannel, OutputChannel channel) { @@ -277,6 +298,9 @@ static const JASDsp::OutputChannelConfig* GetOutputConfig( return nullptr; } +/** + * Get the volume that the given DSP channel should render to the given output channel at. + */ static f32 GetVolumeForOutputChannel( const JASDsp::TChannel& sourceChannel, OutputChannel outputChannel) { @@ -315,6 +339,9 @@ static f32 GetVolumeForOutputChannel( return ratio; } +/** + * Given decoded & resampled input samples, render a DSP channel to a given output channel. + */ static void RenderOutputChannel( const JASDsp::TChannel& sourceChannel, OutputChannel outputChannel, diff --git a/src/dusk/audio/DuskDsp.hpp b/src/dusk/audio/DuskDsp.hpp index 23e05b60e6..9dbffbfec4 100644 --- a/src/dusk/audio/DuskDsp.hpp +++ b/src/dusk/audio/DuskDsp.hpp @@ -16,6 +16,9 @@ namespace dusk::audio { OutputChannel_MAX }; + /** + * Data stored by DSP implementation for each DSP channel. + */ struct ChannelAuxData { s16 hist1; s16 hist0; @@ -25,8 +28,14 @@ namespace dusk::audio { extern ChannelAuxData ChannelAux[DSP_CHANNELS]; + /** + * Data storage for a single subframe and output channel's worth of samples. + */ using DspSubframe = std::array; + /** + * Data storage for a single subframe's worth of samples, across all output channels. + */ struct OutputSubframe { static constexpr int NUM_CHANNELS = static_cast(OutputChannel::OutputChannel_MAX); @@ -38,6 +47,38 @@ namespace dusk::audio { } }; + /** + * Initialize the DSP system, creating data storage needed for channels and such. + */ void DspInit(); + + /** + * Render a subframe of audio with the current DSP state. + */ void DspRender(OutputSubframe& subframe); + + /** + * Get the amount of samples a single "block" of this channel's data has. + */ + constexpr u32 BlockSamples(const JASDsp::TChannel& channel) { + return channel.mSamplesPerBlock; + } + + /** + * Get the amount of bytes a single "block" of this channel's data has. + */ + constexpr u32 BlockBytes(const JASDsp::TChannel& channel) { + if (channel.mSamplesPerBlock == 1) { + if (channel.mBytesPerBlock == 16) { + return 2; + } + if (channel.mBytesPerBlock == 8) { + return 1; + } + + CRASH("Unknown format"); + } + + return channel.mBytesPerBlock; + } }