Audio system cleanup and comments

This commit is contained in:
PJB3005
2026-03-20 17:51:42 +01:00
parent c940e04b56
commit 6443b9fded
4 changed files with 152 additions and 69 deletions
+3
View File
@@ -1,5 +1,8 @@
#pragma once
namespace dusk::audio {
/**
* Initialize the audio system and start playing audio.
*/
void Initialize();
}
+17 -5
View File
@@ -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<f32, DSP_SUBFRAME_SIZE * OutputSubframe::NUM_CHANNELS> 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() {
+91 -64
View File
@@ -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<int>(static_cast<u64>(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<u8*>(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<u32>(desiredSamples));
int renderSize = static_cast<int>(sizeof(s16) * renderSamples);
auto renderData = static_cast<s16*>(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<int>(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<int>(sizeof(s16) * additional_amount);
auto requested = static_cast<s16*>(alloca(requestedSize));
memset(requested, 0, requestedSize);
auto aramBase = static_cast<u8*>(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<u32>(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,
+41
View File
@@ -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<f32, DSP_SUBFRAME_SIZE>;
/**
* Data storage for a single subframe's worth of samples, across all output channels.
*/
struct OutputSubframe {
static constexpr int NUM_CHANNELS = static_cast<int>(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;
}
}