mirror of
https://github.com/TwilitRealm/dusklight
synced 2026-06-07 11:27:26 -04:00
do resampling manually
This commit is contained in:
+83
-93
@@ -74,19 +74,6 @@ constexpr static int PitchToSampleRate(u16 value) {
|
||||
return static_cast<int>(static_cast<u64>(SampleRate) * value / 4096);
|
||||
}
|
||||
|
||||
static void UpdateSampleRate(const JASDsp::TChannel& channel, ChannelAuxData& aux) {
|
||||
auto sampleRate = PitchToSampleRate(channel.mPitch);
|
||||
|
||||
const SDL_AudioSpec spec = {
|
||||
SDL_AUDIO_S16,
|
||||
1,
|
||||
sampleRate
|
||||
};
|
||||
|
||||
SDL_SetAudioStreamFormat(aux.resampleStream, &spec, nullptr);
|
||||
aux.prevPitch = channel.mPitch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset state for a DSP channel between independent playbacks.
|
||||
*/
|
||||
@@ -98,8 +85,9 @@ static void ResetChannel(JASDsp::TChannel& channel, ChannelAuxData& aux) {
|
||||
aux.hist0 = 0;
|
||||
aux.hist1 = 0;
|
||||
|
||||
SDL_ClearAudioStream(aux.resampleStream);
|
||||
UpdateSampleRate(channel, aux);
|
||||
aux.decodeBufCount = 0;
|
||||
aux.resamplePos = 0.0;
|
||||
aux.resamplePrev = 0;
|
||||
|
||||
for (auto& volume : aux.prevVolume) {
|
||||
volume = NAN;
|
||||
@@ -196,15 +184,16 @@ static void ReadSampleData(
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a single *contiguous* chunk of sample data from a channel,
|
||||
* writes the samples to the channel's resampler stream.
|
||||
* Read a single *contiguous* chunk of sample data from a channel into outBuf
|
||||
*
|
||||
* @returns Amount of samples actually read. Can be greater than the amount requested.
|
||||
* @returns Amount of samples written to outBuf. May be less than desiredSamples
|
||||
*/
|
||||
static int ReadChannelSamplesChunk(
|
||||
JASDsp::TChannel& channel,
|
||||
ChannelAuxData& aux,
|
||||
int desiredSamples) {
|
||||
int desiredSamples,
|
||||
s16* outBuf,
|
||||
int outBufSize) {
|
||||
|
||||
assert(desiredSamples >= 0);
|
||||
|
||||
@@ -249,61 +238,49 @@ static int ReadChannelSamplesChunk(
|
||||
channel.mSamplesLeft -= renderSamples;
|
||||
channel.mSamplePosition += renderSamples;
|
||||
|
||||
SDL_PutAudioStreamData(
|
||||
aux.resampleStream,
|
||||
renderData + skipSamples,
|
||||
static_cast<int>(renderSize - skipSamples * sizeof(u16)));
|
||||
int outputCount = static_cast<int>(renderSamples - skipSamples);
|
||||
|
||||
// this should never be hit with the limits on pitch shift (i think) but just in case!!
|
||||
outputCount = std::min(outputCount, outBufSize);
|
||||
if (outputCount > 0) {
|
||||
memcpy(outBuf, renderData + skipSamples, outputCount * sizeof(s16));
|
||||
}
|
||||
|
||||
assert(curSamplePosition % channel.mSamplesPerBlock == 0 || channel.mSamplesLeft == 0);
|
||||
|
||||
return static_cast<int>(renderSamples - skipSamples);
|
||||
return outputCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads new audio channels from a DSP channel and writes them to the resampler stream.
|
||||
* Fill decodeBuf with at least `needed` samples, fewer may be written if the channel has no loop and its data ends
|
||||
*/
|
||||
static void SDLCALL ReadChannelSamples(
|
||||
void *userdata,
|
||||
SDL_AudioStream*,
|
||||
int additional_amount,
|
||||
int) {
|
||||
|
||||
if (additional_amount == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto index = static_cast<u32>(reinterpret_cast<uintptr_t>(userdata));
|
||||
auto& channel = JASDsp::CH_BUF[index];
|
||||
auto& aux = ChannelAux[index];
|
||||
|
||||
if (channel.mSamplesLeft == 0 && !channel.mLoopFlag) {
|
||||
// May get called when we're out of data to read.
|
||||
// This is expected, as we need to drain the resampler channel before we mark the channel as finished.
|
||||
return;
|
||||
}
|
||||
|
||||
auto samplesRead = ReadChannelSamplesChunk(channel, aux, additional_amount);
|
||||
additional_amount -= samplesRead;
|
||||
|
||||
if (channel.mSamplesLeft == 0) {
|
||||
// Reached end of buffer.
|
||||
if (!channel.mLoopFlag) {
|
||||
return;
|
||||
static void FillDecodeBuf(JASDsp::TChannel& channel, ChannelAuxData& aux, int needed) {
|
||||
while (aux.decodeBufCount < needed) {
|
||||
if (channel.mSamplesLeft == 0) {
|
||||
if (!channel.mLoopFlag) {
|
||||
// we aren't a looping channel and there's no samples left, we out of this fuckin loop
|
||||
break;
|
||||
} else {
|
||||
// we are looping, handle loop logic
|
||||
channel.mSamplesLeft = channel.mEndSample - channel.mLoopStartSample;
|
||||
channel.mSamplePosition = channel.mLoopStartSample;
|
||||
aux.hist1 = channel.mpPenult;
|
||||
aux.hist0 = channel.mpLast;
|
||||
}
|
||||
}
|
||||
|
||||
channel.mSamplesLeft = channel.mEndSample - channel.mLoopStartSample;
|
||||
channel.mSamplePosition = channel.mLoopStartSample;
|
||||
int remainingDecodeSpace = ChannelAuxData::DECODE_BUF_SIZE - aux.decodeBufCount;
|
||||
if (remainingDecodeSpace == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
aux.hist1 = channel.mpPenult;
|
||||
aux.hist0 = channel.mpLast;
|
||||
aux.decodeBufCount += ReadChannelSamplesChunk(
|
||||
channel, aux, std::min(remainingDecodeSpace, needed - aux.decodeBufCount),
|
||||
aux.decodeBuf + aux.decodeBufCount, remainingDecodeSpace
|
||||
);
|
||||
}
|
||||
|
||||
if (additional_amount >= 0) {
|
||||
ReadChannelSamplesChunk(channel, aux, additional_amount);
|
||||
}
|
||||
|
||||
channel.mAramStreamPosition = channel.mWaveAramAddress
|
||||
+ ConvertSamplesToDataLength(channel, channel.mSamplePosition);
|
||||
channel.mAramStreamPosition = channel.mWaveAramAddress + ConvertSamplesToDataLength(channel, channel.mSamplePosition);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -422,57 +399,70 @@ static void RenderOutputChannel(
|
||||
prevVolume = targetVolume;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch, decode, resample, output
|
||||
*/
|
||||
static void RenderChannel(
|
||||
JASDsp::TChannel& channel,
|
||||
ChannelAuxData& channelAux,
|
||||
OutputSubframe& subframe) {
|
||||
|
||||
if (channel.mResetFlag) {
|
||||
ResetChannel(channel, channelAux);
|
||||
} else if (channelAux.prevPitch != channel.mPitch) {
|
||||
UpdateSampleRate(channel, channelAux);
|
||||
}
|
||||
|
||||
DspSubframe audioLoadBuffer = {};
|
||||
// how many input samples we step per output sample, aka the resampling ratio
|
||||
f32 step = (f32)PitchToSampleRate(channel.mPitch) / SampleRate;
|
||||
|
||||
int wantRead = sizeof(audioLoadBuffer);
|
||||
auto read = SDL_GetAudioStreamData(
|
||||
channelAux.resampleStream,
|
||||
&audioLoadBuffer,
|
||||
wantRead);
|
||||
// how many input samples to resample to DSP_SUBFRAME_SIZE output samples
|
||||
int needed = static_cast<int>(channelAux.resamplePos + DSP_SUBFRAME_SIZE * step) + 2;
|
||||
|
||||
if (read < wantRead) {
|
||||
FillDecodeBuf(channel, channelAux, needed);
|
||||
|
||||
// source ran dry, channel is finished
|
||||
if(channelAux.decodeBufCount < needed) {
|
||||
channel.mIsFinished = true;
|
||||
}
|
||||
|
||||
auto hasReadSamples = std::span(audioLoadBuffer).subspan(0, wantRead / sizeof(f32));
|
||||
DspSubframe audioLoadBuffer = {};
|
||||
f64 pos = channelAux.resamplePos;
|
||||
s16 prev = channelAux.resamplePrev;
|
||||
s16 next = channelAux.decodeBufCount > 0 ? channelAux.decodeBuf[0] : prev;
|
||||
int srcIdx = 0;
|
||||
|
||||
// linear resampling and f32 conversion
|
||||
for (int i = 0; i < DSP_SUBFRAME_SIZE; i++) {
|
||||
audioLoadBuffer[i] = static_cast<f32>(prev + pos * (next - prev)) / 32768.0f;
|
||||
pos += step;
|
||||
while (pos >= 1.0) {
|
||||
pos -= 1.0;
|
||||
prev = next;
|
||||
srcIdx++;
|
||||
next = srcIdx < channelAux.decodeBufCount ? channelAux.decodeBuf[srcIdx] : prev;
|
||||
}
|
||||
}
|
||||
|
||||
// save resampler state for the next subframe, prevents popping on pitch change
|
||||
channelAux.resamplePos = pos;
|
||||
channelAux.resamplePrev = prev;
|
||||
|
||||
// move any remaining samples in the decode buf to the beginning
|
||||
int remainingDecodeBuf = channelAux.decodeBufCount - srcIdx;
|
||||
if (remainingDecodeBuf > 0) {
|
||||
memmove(channelAux.decodeBuf, channelAux.decodeBuf + srcIdx, remainingDecodeBuf * sizeof(s16));
|
||||
}
|
||||
|
||||
channelAux.decodeBufCount = std::max(0, remainingDecodeBuf);
|
||||
|
||||
auto hasReadSamples = std::span(audioLoadBuffer).subspan(0, DSP_SUBFRAME_SIZE);
|
||||
|
||||
static_assert(OutputSubframe::NUM_CHANNELS == 2, "Keep RenderChannel in sync!");
|
||||
|
||||
RenderOutputChannel(channel, channelAux, OutputChannel::LEFT, hasReadSamples, subframe);
|
||||
RenderOutputChannel(channel, channelAux,OutputChannel::RIGHT, hasReadSamples, subframe);
|
||||
RenderOutputChannel(channel, channelAux, OutputChannel::RIGHT, hasReadSamples, subframe);
|
||||
}
|
||||
|
||||
void dusk::audio::DspInit() {
|
||||
constexpr SDL_AudioSpec srcSpec = {
|
||||
SDL_AUDIO_S16,
|
||||
1,
|
||||
SampleRate
|
||||
};
|
||||
constexpr SDL_AudioSpec dstSpec = {
|
||||
SDL_AUDIO_F32,
|
||||
1,
|
||||
SampleRate
|
||||
};
|
||||
|
||||
for (u32 i = 0; i < DSP_CHANNELS; i++) {
|
||||
auto& aux = ChannelAux[i];
|
||||
aux.resampleStream = SDL_CreateAudioStream(&srcSpec, &dstSpec);
|
||||
|
||||
SDL_SetAudioStreamGetCallback(
|
||||
aux.resampleStream,
|
||||
ReadChannelSamples,
|
||||
reinterpret_cast<void*>(static_cast<uintptr_t>(i)));
|
||||
}
|
||||
}
|
||||
|
||||
void dusk::audio::ApplyVolume(
|
||||
|
||||
@@ -26,8 +26,6 @@ namespace dusk::audio {
|
||||
struct ChannelAuxData {
|
||||
s16 hist1;
|
||||
s16 hist0;
|
||||
SDL_AudioStream* resampleStream;
|
||||
u16 prevPitch;
|
||||
|
||||
// Used for debugging tools.
|
||||
u32 resetCount;
|
||||
@@ -43,6 +41,17 @@ namespace dusk::audio {
|
||||
assert(channel < OutputChannel::OutputChannel_MAX);
|
||||
return prevVolume[static_cast<int>(channel)];
|
||||
}
|
||||
|
||||
// buffer for decoding before resampling, size is chosen based on how many input samples we would need to fetch for the highest possible pitch
|
||||
// to fill one subframe of output samples after resampling
|
||||
static constexpr int DECODE_BUF_SIZE = 2048;
|
||||
s16 decodeBuf[DECODE_BUF_SIZE];
|
||||
int decodeBufCount;
|
||||
|
||||
// basically stores our position between resamplePrev and decodeBuf[0] so we don't lose that fractional resampler position next subframe
|
||||
f32 resamplePos;
|
||||
// last consumed sample from decodeBuf
|
||||
s16 resamplePrev;
|
||||
};
|
||||
|
||||
extern ChannelAuxData ChannelAux[DSP_CHANNELS];
|
||||
|
||||
@@ -50,9 +50,10 @@ static void DisplayDspChannel(int i) {
|
||||
auto dolby = (channel.mAutoMixerPanDolby & 0xFF) / 127.5f;
|
||||
auto fxMix = (channel.mAutoMixerFxMix >> 8) / 127.5f;
|
||||
auto volume = VolumeFromU16(channel.mAutoMixerVolume);
|
||||
auto pitch = channel.mPitch / 4096.0f;
|
||||
ImGui::Text(
|
||||
"Auto mixer active (pan: %f, dolby: %f, fx: %f, volume: %f)",
|
||||
pan, dolby, fxMix, volume);
|
||||
"Auto mixer active (pan: %f, dolby: %f, fx: %f, volume: %f, pitch %f)",
|
||||
pan, dolby, fxMix, volume, pitch);
|
||||
} else {
|
||||
ImGui::Text(
|
||||
"Bus connect: %04X(%.2f),%04X(%.2f),%04X(%.2f),%04X(%.2f),%04X(%.2f),%04X(%.2f)",
|
||||
@@ -249,4 +250,4 @@ void dusk::ImGuiMenuTools::ShowAudioDebug() {
|
||||
}
|
||||
|
||||
ImGui::End();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user