mirror of
https://github.com/wheremyfoodat/Panda3DS.git
synced 2025-04-08 23:25:40 +12:00
More HLE DSP work
This commit is contained in:
parent
07cee43a2b
commit
b299609a9b
2 changed files with 60 additions and 48 deletions
|
@ -16,6 +16,46 @@ namespace Audio {
|
||||||
using SampleFormat = HLE::SourceConfiguration::Configuration::Format;
|
using SampleFormat = HLE::SourceConfiguration::Configuration::Format;
|
||||||
using SourceType = HLE::SourceConfiguration::Configuration::MonoOrStereo;
|
using SourceType = HLE::SourceConfiguration::Configuration::MonoOrStereo;
|
||||||
|
|
||||||
|
class DSPMixer {
|
||||||
|
public:
|
||||||
|
template <typename T, usize channelCount = 1>
|
||||||
|
using Sample = std::array<T, channelCount>;
|
||||||
|
|
||||||
|
template <typename T, usize channelCount>
|
||||||
|
using Frame = std::array<Sample<T, channelCount>, 160>;
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
using MonoFrame = Frame<T, 1>;
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
using StereoFrame = Frame<T, 2>;
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
using QuadFrame = Frame<T, 4>;
|
||||||
|
|
||||||
|
// Internally the DSP uses four channels when mixing.
|
||||||
|
// Neatly, QuadFrame<s32> means that every sample is a uint32x4 value, which is particularly nice for SIMD mixing
|
||||||
|
using IntermediateMix = QuadFrame<s32>;
|
||||||
|
|
||||||
|
private:
|
||||||
|
using ChannelFormat = HLE::DspConfiguration::OutputFormat;
|
||||||
|
// The audio from each DSP voice is converted to quadraphonic and then fed into 3 intermediate mixing stages
|
||||||
|
// Two of these intermediate mixers (second and third) are used for effects, including custom effects done on the CPU
|
||||||
|
static constexpr usize mixerStageCount = 3;
|
||||||
|
|
||||||
|
public:
|
||||||
|
ChannelFormat channelFormat = ChannelFormat::Stereo;
|
||||||
|
std::array<float, mixerStageCount> volumes;
|
||||||
|
std::array<bool, 2> enableAuxStages;
|
||||||
|
|
||||||
|
void reset() {
|
||||||
|
channelFormat = ChannelFormat::Stereo;
|
||||||
|
|
||||||
|
volumes.fill(0.0);
|
||||||
|
enableAuxStages.fill(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
struct DSPSource {
|
struct DSPSource {
|
||||||
// Audio buffer information
|
// Audio buffer information
|
||||||
// https://www.3dbrew.org/wiki/DSP_Memory_Region
|
// https://www.3dbrew.org/wiki/DSP_Memory_Region
|
||||||
|
@ -49,6 +89,7 @@ namespace Audio {
|
||||||
using SampleBuffer = std::deque<std::array<s16, 2>>;
|
using SampleBuffer = std::deque<std::array<s16, 2>>;
|
||||||
using BufferQueue = std::priority_queue<Buffer>;
|
using BufferQueue = std::priority_queue<Buffer>;
|
||||||
|
|
||||||
|
DSPMixer::StereoFrame<s16> currentFrame;
|
||||||
BufferQueue buffers;
|
BufferQueue buffers;
|
||||||
|
|
||||||
SampleFormat sampleFormat = SampleFormat::ADPCM;
|
SampleFormat sampleFormat = SampleFormat::ADPCM;
|
||||||
|
@ -98,46 +139,6 @@ namespace Audio {
|
||||||
DSPSource() { reset(); }
|
DSPSource() { reset(); }
|
||||||
};
|
};
|
||||||
|
|
||||||
class DSPMixer {
|
|
||||||
public:
|
|
||||||
template <typename T, usize channelCount = 1>
|
|
||||||
using Sample = std::array<T, channelCount>;
|
|
||||||
|
|
||||||
template <typename T, usize channelCount>
|
|
||||||
using Frame = std::array<Sample<T, channelCount>, 160>;
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
using MonoFrame = Frame<T, 1>;
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
using StereoFrame = Frame<T, 2>;
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
using QuadFrame = Frame<T, 4>;
|
|
||||||
|
|
||||||
// Internally the DSP uses four channels when mixing.
|
|
||||||
// Neatly, QuadFrame<s32> means that every sample is a uint32x4 value, which is particularly nice for SIMD mixing
|
|
||||||
using IntermediateMix = QuadFrame<s32>;
|
|
||||||
|
|
||||||
private:
|
|
||||||
using ChannelFormat = HLE::DspConfiguration::OutputFormat;
|
|
||||||
// The audio from each DSP voice is converted to quadraphonic and then fed into 3 intermediate mixing stages
|
|
||||||
// Two of these intermediate mixers (second and third) are used for effects, including custom effects done on the CPU
|
|
||||||
static constexpr usize mixerStageCount = 3;
|
|
||||||
|
|
||||||
public:
|
|
||||||
ChannelFormat channelFormat = ChannelFormat::Stereo;
|
|
||||||
std::array<float, mixerStageCount> volumes;
|
|
||||||
std::array<bool, 2> enableAuxStages;
|
|
||||||
|
|
||||||
void reset() {
|
|
||||||
channelFormat = ChannelFormat::Stereo;
|
|
||||||
|
|
||||||
volumes.fill(0.0);
|
|
||||||
enableAuxStages.fill(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
class HLE_DSP : public DSPCore {
|
class HLE_DSP : public DSPCore {
|
||||||
// The audio frame types are public in case we want to use them for unit tests
|
// The audio frame types are public in case we want to use them for unit tests
|
||||||
public:
|
public:
|
||||||
|
|
|
@ -262,10 +262,10 @@ namespace Audio {
|
||||||
for (usize sampleIndex = 0; sampleIndex < Audio::samplesInFrame; sampleIndex++) {
|
for (usize sampleIndex = 0; sampleIndex < Audio::samplesInFrame; sampleIndex++) {
|
||||||
// Mono samples are in the format: (l, r)
|
// Mono samples are in the format: (l, r)
|
||||||
// When converting to quad, gain0 and gain2 are applied to the left sample, gain1 and gain3 to the right one
|
// When converting to quad, gain0 and gain2 are applied to the left sample, gain1 and gain3 to the right one
|
||||||
intermediateMix[sampleIndex][0] += s32(source.currentSamples[sampleIndex][0] * gains[0]);
|
intermediateMix[sampleIndex][0] += s32(source.currentFrame[sampleIndex][0] * gains[0]);
|
||||||
intermediateMix[sampleIndex][1] += s32(source.currentSamples[sampleIndex][1] * gains[1]);
|
intermediateMix[sampleIndex][1] += s32(source.currentFrame[sampleIndex][1] * gains[1]);
|
||||||
intermediateMix[sampleIndex][2] += s32(source.currentSamples[sampleIndex][0] * gains[2]);
|
intermediateMix[sampleIndex][2] += s32(source.currentFrame[sampleIndex][0] * gains[2]);
|
||||||
intermediateMix[sampleIndex][3] += s32(source.currentSamples[sampleIndex][1] * gains[3]);
|
intermediateMix[sampleIndex][3] += s32(source.currentFrame[sampleIndex][1] * gains[3]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -467,6 +467,9 @@ namespace Audio {
|
||||||
}
|
}
|
||||||
|
|
||||||
void HLE_DSP::generateFrame(DSPSource& source) {
|
void HLE_DSP::generateFrame(DSPSource& source) {
|
||||||
|
// Zero out all output samples at first. TODO: Don't zero out the entire frame initially, rather only zero-out the "unwritten" samples when the frame is done being processed.
|
||||||
|
source.currentFrame = {};
|
||||||
|
|
||||||
if (source.currentSamples.empty()) {
|
if (source.currentSamples.empty()) {
|
||||||
// There's no audio left to play, turn the voice off
|
// There's no audio left to play, turn the voice off
|
||||||
if (source.buffers.empty()) {
|
if (source.buffers.empty()) {
|
||||||
|
@ -480,7 +483,7 @@ namespace Audio {
|
||||||
|
|
||||||
decodeBuffer(source);
|
decodeBuffer(source);
|
||||||
} else {
|
} else {
|
||||||
uint maxSampleCount = uint(float(Audio::samplesInFrame) * source.rateMultiplier);
|
uint maxSampleCount = uint(float(Audio::samplesInFrame) * 1.0);
|
||||||
uint outputCount = 0;
|
uint outputCount = 0;
|
||||||
|
|
||||||
while (outputCount < maxSampleCount) {
|
while (outputCount < maxSampleCount) {
|
||||||
|
@ -494,8 +497,14 @@ namespace Audio {
|
||||||
|
|
||||||
const uint sampleCount = std::min<s32>(maxSampleCount - outputCount, source.currentSamples.size());
|
const uint sampleCount = std::min<s32>(maxSampleCount - outputCount, source.currentSamples.size());
|
||||||
|
|
||||||
// samples.insert(samples.end(), source.currentSamples.begin(), source.currentSamples.begin() + sampleCount);
|
// Copy samples to current frame buffer
|
||||||
|
// TODO: Implement linear/polyphase interpolation
|
||||||
|
std::copy(
|
||||||
|
source.currentSamples.begin(), std::next(source.currentSamples.begin(), sampleCount), source.currentFrame.begin() + outputCount
|
||||||
|
);
|
||||||
|
// Remove samples from sample buffer
|
||||||
source.currentSamples.erase(source.currentSamples.begin(), std::next(source.currentSamples.begin(), sampleCount));
|
source.currentSamples.erase(source.currentSamples.begin(), std::next(source.currentSamples.begin(), sampleCount));
|
||||||
|
// Advance sample position
|
||||||
source.samplePosition += sampleCount;
|
source.samplePosition += sampleCount;
|
||||||
outputCount += sampleCount;
|
outputCount += sampleCount;
|
||||||
}
|
}
|
||||||
|
@ -718,5 +727,7 @@ namespace Audio {
|
||||||
|
|
||||||
buffers = {};
|
buffers = {};
|
||||||
currentSamples.clear();
|
currentSamples.clear();
|
||||||
|
|
||||||
|
gains.fill({});
|
||||||
}
|
}
|
||||||
} // namespace Audio
|
} // namespace Audio
|
||||||
|
|
Loading…
Add table
Reference in a new issue