mirror of
https://github.com/TwilitRealm/dusklight
synced 2026-07-13 05:49:21 -04:00
859d99d657
Idk if these show up yet but they did when the BMS stuff was broken
226 lines
6.9 KiB
C++
226 lines
6.9 KiB
C++
#include <ar.h>
|
|
#include <dolphin/os.h>
|
|
|
|
#include "DuskDsp.hpp"
|
|
|
|
#include <algorithm>
|
|
#include <cassert>
|
|
|
|
#include "Adpcm.hpp"
|
|
#include "JSystem/JAudio2/JASDriverIF.h"
|
|
#include "global.h"
|
|
|
|
using namespace dusk::audio;
|
|
|
|
ChannelAuxData dusk::audio::ChannelAux[DSP_CHANNELS] = {};
|
|
|
|
static bool ValidateChannelWaveFormat(const JASDsp::TChannel& channel) {
|
|
if (channel.mSamplesPerBlock == AdpcmSampleCount && channel.mBytesPerBlock == Adpcm4FrameSize)
|
|
return true;
|
|
/*
|
|
if (channel.mSamplesPerBlock == AdpcmSampleCount && channel.mBytesPerBlock == Adpcm2FrameSize)
|
|
return true;
|
|
if (channel.mSamplesPerBlock == 1 && channel.mBytesPerBlock == 8)
|
|
return true;
|
|
if (channel.mSamplesPerBlock == 1 && channel.mBytesPerBlock == 16)
|
|
return true;
|
|
*/
|
|
return false;
|
|
}
|
|
|
|
static void ValidateChannel(const JASDsp::TChannel& channel) {
|
|
if (!ValidateChannelWaveFormat(channel)) {
|
|
CRASH(
|
|
"Unable to handle channel format: %02x, %02x\n",
|
|
channel.mSamplesPerBlock,
|
|
channel.mBytesPerBlock);
|
|
}
|
|
}
|
|
|
|
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 (samples % channel.mSamplesPerBlock != 0) {
|
|
// Ensure we round up.
|
|
samples += channel.mSamplesPerBlock;
|
|
//CRASH("Indivisible sample count: %d\n", samples);
|
|
}
|
|
|
|
return (samples / channel.mSamplesPerBlock) * channel.mBytesPerBlock;
|
|
}
|
|
|
|
static void RenderChannel(
|
|
JASDsp::TChannel& channel,
|
|
ChannelAuxData& channelAux,
|
|
DspSubframe& subframe);
|
|
|
|
static void ResetChannel(JASDsp::TChannel& channel, const ChannelAuxData& aux) {
|
|
channel.mSamplesLeft = channel.mEndSample - channel.mSamplePosition;
|
|
|
|
const SDL_AudioSpec spec = {
|
|
SDL_AUDIO_S16,
|
|
1,
|
|
static_cast<int>(static_cast<u64>(SampleRate) * channel.mPitch / 4096)
|
|
};
|
|
|
|
SDL_ClearAudioStream(aux.resampleStream);
|
|
SDL_SetAudioStreamFormat(aux.resampleStream, &spec, nullptr);
|
|
|
|
channel.mResetFlag = false;
|
|
}
|
|
|
|
static void MixSubframe(DspSubframe& dst, const DspSubframe& src) {
|
|
for (int i = 0; i < dst.size(); i++) {
|
|
dst[i] = static_cast<s16>(dst[i] + src[i]);
|
|
}
|
|
}
|
|
|
|
void dusk::audio::DspRender(DspSubframe& subframe) {
|
|
subframe.fill(0);
|
|
|
|
// This cast half exists because my debugger sucks and this is an easy way to look at the data.
|
|
auto& channels = *reinterpret_cast<std::array<JASDsp::TChannel, DSP_CHANNELS>*>(JASDsp::CH_BUF);
|
|
|
|
for (int i = 0; i < channels.size(); i++) {
|
|
auto& channel = channels[i];
|
|
auto& channelAux = ChannelAux[i];
|
|
|
|
if (!channel.mIsActive) {
|
|
continue;
|
|
}
|
|
|
|
if (channel.mPauseFlag) {
|
|
// Not really sure what the practical difference between pause and
|
|
// deactivation is. Either avoids clearing state or allows the DSP to avoid popping?
|
|
continue;
|
|
}
|
|
|
|
if (channel.mBytesPerBlock == 0) {
|
|
// I think these are oscillator channels? Not backed by audio.
|
|
channel.mIsFinished = true;
|
|
continue;
|
|
}
|
|
|
|
ValidateChannel(channel);
|
|
|
|
DspSubframe channelSubframe = {};
|
|
RenderChannel(channel, channelAux, channelSubframe);
|
|
MixSubframe(subframe, channelSubframe);
|
|
}
|
|
}
|
|
|
|
static void SDLCALL ReadChannelSamples(
|
|
void *userdata,
|
|
SDL_AudioStream *stream,
|
|
int additional_amount,
|
|
int) {
|
|
|
|
const auto index = static_cast<u32>(reinterpret_cast<uintptr_t>(userdata));
|
|
auto& channel = JASDsp::CH_BUF[index];
|
|
auto& aux = ChannelAux[index];
|
|
|
|
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));
|
|
|
|
Adpcm4ToPcm16(
|
|
aramBase + dataPosition,
|
|
ConvertSamplesToDataLength(channel, renderSamples),
|
|
requested,
|
|
renderSamples,
|
|
aux.hist1,
|
|
aux.hist0);
|
|
|
|
channel.mSamplesLeft -= renderSamples;
|
|
channel.mSamplePosition += renderSamples;
|
|
|
|
if (channel.mSamplesLeft == 0) {
|
|
// Reached end of buffer.
|
|
if (!channel.mLoopFlag) {
|
|
// Finish.
|
|
channel.mIsFinished = true;
|
|
return;
|
|
}
|
|
|
|
channel.mSamplesLeft = channel.mEndSample - channel.mLoopStartSample;
|
|
channel.mSamplePosition = channel.mLoopStartSample;
|
|
curSamplePosition = channel.mEndSample - channel.mSamplesLeft;
|
|
dataPosition = ConvertSamplesToDataLength(channel, curSamplePosition);
|
|
|
|
Adpcm4ToPcm16(
|
|
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);
|
|
}
|
|
|
|
channel.mAramStreamPosition = channel.mWaveAramAddress
|
|
+ ConvertSamplesToDataLength(channel, channel.mSamplePosition);
|
|
|
|
SDL_PutAudioStreamData(stream, requested, requestedSize);
|
|
}
|
|
|
|
static void RenderChannel(
|
|
JASDsp::TChannel& channel,
|
|
ChannelAuxData& channelAux,
|
|
DspSubframe& subframe) {
|
|
if (channel.mResetFlag) {
|
|
ResetChannel(channel, channelAux);
|
|
}
|
|
|
|
SDL_GetAudioStreamData(
|
|
channelAux.resampleStream,
|
|
subframe.data(),
|
|
static_cast<int>(subframe.size() * sizeof(s16)));
|
|
|
|
for (auto& sample : subframe) {
|
|
u16 volume;
|
|
if (channel.mAutoMixerBeenSet) {
|
|
volume = channel.mAutoMixerVolume;
|
|
} else {
|
|
volume = channel.mOutputChannels[0].mTargetVolume;
|
|
}
|
|
sample = (s16)((s64)sample * volume / JASDriver::getChannelLevel_dsp());
|
|
}
|
|
}
|
|
|
|
void dusk::audio::DspInit() {
|
|
constexpr SDL_AudioSpec spec = {
|
|
SDL_AUDIO_S16,
|
|
1,
|
|
SampleRate
|
|
};
|
|
|
|
for (int i = 0; i < DSP_CHANNELS; i++) {
|
|
auto& aux = ChannelAux[i];
|
|
aux.resampleStream = SDL_CreateAudioStream(&spec, &spec);
|
|
|
|
SDL_SetAudioStreamGetCallback(
|
|
aux.resampleStream,
|
|
ReadChannelSamples,
|
|
reinterpret_cast<void*>(i));
|
|
}
|
|
}
|