diff --git a/libs/JSystem/src/JAudio2/JASAiCtrl.cpp b/libs/JSystem/src/JAudio2/JASAiCtrl.cpp index e59c9d45fe..dc0289ce0f 100644 --- a/libs/JSystem/src/JAudio2/JASAiCtrl.cpp +++ b/libs/JSystem/src/JAudio2/JASAiCtrl.cpp @@ -46,7 +46,7 @@ DUSK_GAME_DATA u32 JASDriver::sOutputRate; DUSK_GAME_DATA JASMixMode JASDriver::sMixMode = MIX_MODE_EXTRA; -DUSK_GAME_DATA f32 JASDriver::sDacRate = 32028.5f; +DUSK_GAME_DATA f32 JASDriver::sDacRate = DUSK_IF_ELSE(32000.0f, 32028.5f); DUSK_GAME_DATA u32 JASDriver::sSubFrames = 0x00000007; @@ -111,9 +111,7 @@ void JASDriver::setOutputRate(JASOutputRate param_0) { sDacRate = 48000.0f; } -#if !TARGET_PC - sDacRate *= 1.0008897f; -#endif + IF_NOT_DUSK(sDacRate *= 1.0008897f); } DUSK_GAME_DATA const JASDriver::MixFunc JASDriver::sMixFuncs[4] = { diff --git a/src/dusk/audio/DuskAudioSystem.cpp b/src/dusk/audio/DuskAudioSystem.cpp index 20303b46ff..bdb2acaf65 100644 --- a/src/dusk/audio/DuskAudioSystem.cpp +++ b/src/dusk/audio/DuskAudioSystem.cpp @@ -191,6 +191,67 @@ static void InterleaveOutputData(const OutputSubframe& data, std::span targ } } +constexpr auto kExtChannels = 2; +constexpr auto kExtSrcInSampleCount = DSP_SUBFRAME_SIZE * 2; +constexpr auto kExtSrcOutSampleCount = DSP_SUBFRAME_SIZE * 3; + +// simple 3:2 resampler +struct LinearResampler { + f32 prev = 0.0f; + int phase = 0; + + void resample(std::span in, std::span out, int skip = 1, int offset = 0) { + const auto inSize = in.size() / skip; + const auto outSize = out.size() / skip; + assert(inSize * 3 == outSize * 2); + + int idx = -1; + int phi = phase; + int n = 0; + + while (n < outSize) { + f32 s0 = (idx < 0) ? prev : in[idx * skip + offset] / 32768.0f; + f32 s1 = in[idx * skip + skip + offset] / 32768.0f; + f32 frac = phi * (1.0f / 3.0f); + + out[n++ * skip + offset] = s0 + frac * (s1 - s0); + + phi += 2; + if (phi >= 3) { + phi -= 3; + idx++; + } + } + + prev = in[(inSize - 1) * skip + offset] / 32768.0f; + phase = phi; + } +}; + +static struct { + std::array buf; + int available = 0; + LinearResampler leftSrc; + LinearResampler rightSrc; + + bool hungry() const { return available <= 0; } + + void feed(std::span in) { + const auto out = std::span{buf}; + leftSrc.resample(in, out, kExtChannels, 0); + rightSrc.resample(in, out, kExtChannels, 1); + available = buf.size(); + } + + std::span drain_subframe() { + constexpr auto kDrainSize = DSP_SUBFRAME_SIZE * kExtChannels; + assert(available >= kDrainSize); + const auto rv = std::span{&buf[buf.size() - available], kDrainSize}; + available -= kDrainSize; + return rv; + } +} extResampler; + int RenderAudioSubframe() { ZoneScoped; OutBuffer = {}; @@ -202,16 +263,19 @@ int RenderAudioSubframe() { InterleaveOutputData(OutBuffer, OutInterleaveBuffer); if (JASDriver::extMixCallback != nullptr && JASDriver::sMixMode == MIX_MODE_INTERLEAVE) { - // NOTE: In the real game, this gets called on the entire audio frame, rather than the subframe. - // That's probably more efficient, but I didn't wanna change the code to calculate the - // entire audio buffers at once. - // This is only used for the movie player, and it seems to work fine with the smaller calls. - const auto mixData = JASDriver::extMixCallback(DSP_SUBFRAME_SIZE); - if (mixData) { + if (extResampler.hungry()) { + const auto mixData = JASDriver::extMixCallback(kExtSrcInSampleCount); + if (mixData) { + extResampler.feed(std::span{mixData, kExtSrcInSampleCount * kExtChannels}); + } + } + + if (!extResampler.hungry()) { + const auto inBuf = extResampler.drain_subframe(); for (int i = 0; i < DSP_SUBFRAME_SIZE; i++) { const auto oi = i * OutChannelCount; - OutInterleaveBuffer[oi] += static_cast(mixData[i * 2]) / 32767.0f; - OutInterleaveBuffer[oi + 1] += static_cast(mixData[i * 2 + 1]) / 32767.0f; + OutInterleaveBuffer[oi] += inBuf[i * kExtChannels]; + OutInterleaveBuffer[oi + 1] += inBuf[i * kExtChannels + 1]; } } } diff --git a/src/dusk/audio/DuskDsp.cpp b/src/dusk/audio/DuskDsp.cpp index cb43f9512b..92bfb74cb3 100644 --- a/src/dusk/audio/DuskDsp.cpp +++ b/src/dusk/audio/DuskDsp.cpp @@ -393,9 +393,9 @@ static void FillDecodeBuf(JASDsp::TChannel& channel, ChannelAuxData& aux, int ne static BiquadCoeffs RemapBiquad32to48(float b1, float b2, float a1, float a2) { using cplx = std::complex; - constexpr f64 kOldRate = 32000.0; - constexpr f64 kNewRate = f64(SampleRate); - constexpr f64 kGamma = kOldRate / kNewRate; + constexpr auto kOldRate = 32000.0; + constexpr auto kNewRate = f64(SampleRate); + constexpr auto kGamma = kOldRate / kNewRate; if (b1 == 0 && b2 == 0 && a1 == 0 && a2 == 0) { return {0, 0, 0, 0}; @@ -416,12 +416,12 @@ static BiquadCoeffs RemapBiquad32to48(float b1, float b2, float a1, float a2) { cplx p1, p2; if (disc >= 0.0) { f64 sq = std::sqrt(disc); - p1 = { (a1 + sq) * 0.5, 0.0 }; - p2 = { (a1 - sq) * 0.5, 0.0 }; + p1 = {(a1 + sq) * 0.5, 0.0}; + p2 = {(a1 - sq) * 0.5, 0.0}; } else { f64 sq = std::sqrt(-disc); - p1 = { a1 * 0.5, sq * 0.5 }; - p2 = { a1 * 0.5, -sq * 0.5 }; + p1 = {a1 * 0.5, sq * 0.5}; + p2 = {a1 * 0.5, -sq * 0.5}; } cplx p1n = mapPoint(p1); cplx p2n = mapPoint(p2); @@ -445,7 +445,7 @@ static BiquadCoeffs RemapBiquad32to48(float b1, float b2, float a1, float a2) { f64 K = (hOldRef / shapeNewRef).real(); - return { float(K), float(-K * z0n), float(a1n), float(a2n) }; + return {float(K), float(-K * z0n), float(a1n), float(a2n)}; } /** @@ -504,10 +504,10 @@ static void RenderChannel( std::span newCoefs{channel.iir_filter_params, 4}; if (!std::ranges::equal(newCoefs, aux.curBiquadCoefs)) { aux.remappedBiquadCoefs = RemapBiquad32to48( - newCoefs[0] / 32768.0, - newCoefs[1] / 32768.0, - newCoefs[2] / 32768.0, - newCoefs[3] / 32768.0 + newCoefs[0] / 32768.0f, + newCoefs[1] / 32768.0f, + newCoefs[2] / 32768.0f, + newCoefs[3] / 32768.0f ); std::ranges::copy(newCoefs, aux.curBiquadCoefs.begin()); }