mirror of
https://github.com/wheremyfoodat/Panda3DS.git
synced 2025-04-06 14:15:41 +12:00
Merge pull request #502 from wheremyfoodat/moar-hle-dsp
More HLE DSP work
This commit is contained in:
commit
21492f81a9
3 changed files with 126 additions and 27 deletions
|
@ -297,7 +297,7 @@ namespace Audio::HLE {
|
|||
u8 isEnabled; ///< Is this channel enabled? (Doesn't have to be playing anything.)
|
||||
u8 currentBufferIDDirty; ///< Non-zero when current_buffer_id changes
|
||||
u16_le syncCount; ///< Is set by the DSP to the value of SourceConfiguration::sync_count
|
||||
u32_dsp bufferPosition; ///< Number of samples into the current buffer
|
||||
u32_dsp samplePosition; ///< Number of samples into the current buffer
|
||||
u16_le currentBufferID; ///< Updated when a buffer finishes playing
|
||||
u16_le lastBufferID; ///< Updated when all buffers in the queue finish playing
|
||||
};
|
||||
|
|
|
@ -32,8 +32,8 @@ namespace Audio {
|
|||
SampleFormat format;
|
||||
SourceType sourceType;
|
||||
|
||||
bool fromQueue = false; // Is this buffer from the buffer queue or an embedded buffer?
|
||||
bool hasPlayedOnce = false; // Has the buffer been played at least once before?
|
||||
bool fromQueue = false; // Is this buffer from the buffer queue or an embedded buffer?
|
||||
bool hasPlayedOnce = false; // Has the buffer been played at least once before?
|
||||
|
||||
bool operator<(const Buffer& other) const {
|
||||
// Lower ID = Higher priority
|
||||
|
@ -47,9 +47,17 @@ namespace Audio {
|
|||
using BufferQueue = std::priority_queue<Buffer>;
|
||||
BufferQueue buffers;
|
||||
|
||||
SampleFormat sampleFormat = SampleFormat::ADPCM;
|
||||
SourceType sourceType = SourceType::Stereo;
|
||||
|
||||
std::array<float, 3> gain0, gain1, gain2;
|
||||
u32 samplePosition; // Sample number into the current audio buffer
|
||||
u16 syncCount;
|
||||
bool enabled; // Is the source enabled?
|
||||
u16 currentBufferID;
|
||||
u16 previousBufferID;
|
||||
|
||||
bool enabled; // Is the source enabled?
|
||||
bool isBufferIDDirty = false; // Did we change buffers?
|
||||
|
||||
// ADPCM decoding info:
|
||||
// An array of fixed point S5.11 coefficients. These provide "weights" for the history samples
|
||||
|
@ -65,6 +73,10 @@ namespace Audio {
|
|||
int index = 0; // Index of the voice in [0, 23] for debugging
|
||||
|
||||
void reset();
|
||||
|
||||
// Push a buffer to the buffer queue
|
||||
void pushBuffer(const Buffer& buffer) { buffers.push(buffer); }
|
||||
|
||||
// Pop a buffer from the buffer queue and return it
|
||||
Buffer popBuffer() {
|
||||
assert(!buffers.empty());
|
||||
|
@ -114,9 +126,6 @@ namespace Audio {
|
|||
std::array<Source, Audio::HLE::sourceCount> sources; // DSP voices
|
||||
Audio::HLE::DspMemory dspRam;
|
||||
|
||||
SampleFormat sampleFormat = SampleFormat::ADPCM;
|
||||
SourceType sourceType = SourceType::Stereo;
|
||||
|
||||
void resetAudioPipe();
|
||||
bool loaded = false; // Have we loaded a component?
|
||||
|
||||
|
@ -159,9 +168,13 @@ namespace Audio {
|
|||
|
||||
void updateSourceConfig(Source& source, HLE::SourceConfiguration::Configuration& config, s16_le* adpcmCoefficients);
|
||||
void generateFrame(StereoFrame<s16>& frame);
|
||||
void generateFrame(DSPSource& source);
|
||||
void outputFrame();
|
||||
|
||||
// Decode an entire buffer worth of audio
|
||||
void decodeBuffer(DSPSource& source);
|
||||
|
||||
SampleBuffer decodePCM16(const u8* data, usize sampleCount, Source& source);
|
||||
SampleBuffer decodeADPCM(const u8* data, usize sampleCount, Source& source);
|
||||
|
||||
public:
|
||||
|
|
|
@ -64,10 +64,6 @@ namespace Audio {
|
|||
dspState = DSPState::Off;
|
||||
loaded = false;
|
||||
|
||||
// Initialize these to some sane defaults
|
||||
sampleFormat = SampleFormat::ADPCM;
|
||||
sourceType = SourceType::Stereo;
|
||||
|
||||
for (auto& e : pipeData) {
|
||||
e.clear();
|
||||
}
|
||||
|
@ -104,6 +100,7 @@ namespace Audio {
|
|||
dspService.triggerPipeEvent(DSPPipeType::Audio);
|
||||
}
|
||||
|
||||
// TODO: Should this be called if dspState != DSPState::On?
|
||||
outputFrame();
|
||||
scheduler.addEvent(Scheduler::EventType::RunDSP, scheduler.currentTimestamp + Audio::cyclesPerFrame);
|
||||
}
|
||||
|
@ -212,19 +209,21 @@ namespace Audio {
|
|||
updateSourceConfig(source, config, read.adpcmCoefficients.coeff[i]);
|
||||
|
||||
// Generate audio
|
||||
if (source.enabled && !source.buffers.empty()) {
|
||||
const auto& buffer = source.buffers.top();
|
||||
const u8* data = getPointerPhys<u8>(buffer.paddr);
|
||||
|
||||
if (data != nullptr) {
|
||||
// TODO
|
||||
}
|
||||
if (source.enabled) {
|
||||
generateFrame(source);
|
||||
}
|
||||
|
||||
// Update write region of shared memory
|
||||
auto& status = write.sourceStatuses.status[i];
|
||||
status.isEnabled = source.enabled;
|
||||
status.syncCount = source.syncCount;
|
||||
status.currentBufferIDDirty = source.isBufferIDDirty ? 1 : 0;
|
||||
status.currentBufferID = source.currentBufferID;
|
||||
status.lastBufferID = source.previousBufferID;
|
||||
// TODO: Properly update sample position
|
||||
status.samplePosition = source.samplePosition;
|
||||
|
||||
source.isBufferIDDirty = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -265,11 +264,11 @@ namespace Audio {
|
|||
|
||||
// TODO: Should we check bufferQueueDirty here too?
|
||||
if (config.formatDirty || config.embeddedBufferDirty) {
|
||||
sampleFormat = config.format;
|
||||
source.sampleFormat = config.format;
|
||||
}
|
||||
|
||||
if (config.monoOrStereoDirty || config.embeddedBufferDirty) {
|
||||
sourceType = config.monoOrStereo;
|
||||
source.sourceType = config.monoOrStereo;
|
||||
}
|
||||
|
||||
if (config.embeddedBufferDirty) {
|
||||
|
@ -285,8 +284,8 @@ namespace Audio {
|
|||
.looping = config.isLooping != 0,
|
||||
.bufferID = config.bufferID,
|
||||
.playPosition = config.playPosition,
|
||||
.format = sampleFormat,
|
||||
.sourceType = sourceType,
|
||||
.format = source.sampleFormat,
|
||||
.sourceType = source.sourceType,
|
||||
.fromQueue = false,
|
||||
.hasPlayedOnce = false,
|
||||
};
|
||||
|
@ -327,13 +326,91 @@ namespace Audio {
|
|||
return;
|
||||
}
|
||||
|
||||
switch (buffer.format) {
|
||||
case SampleFormat::PCM8:
|
||||
case SampleFormat::PCM16: Helpers::warn("Unimplemented sample format!"); break;
|
||||
source.currentBufferID = buffer.bufferID;
|
||||
source.previousBufferID = 0;
|
||||
// For looping buffers, this is only set for the first time we play it. Loops do not set the dirty bit.
|
||||
source.isBufferIDDirty = !buffer.hasPlayedOnce && buffer.fromQueue;
|
||||
|
||||
case SampleFormat::ADPCM: source.currentSamples = decodeADPCM(data, buffer.sampleCount, source); break;
|
||||
default: Helpers::warn("Invalid DSP sample format"); break;
|
||||
if (buffer.hasPlayedOnce) {
|
||||
source.samplePosition = 0;
|
||||
} else {
|
||||
// Mark that the buffer has already been played once, needed for looping buffers
|
||||
buffer.hasPlayedOnce = true;
|
||||
// Play position is only used for the initial time the buffer is played. Loops will start from the beginning of the buffer.
|
||||
source.samplePosition = buffer.playPosition;
|
||||
}
|
||||
|
||||
switch (buffer.format) {
|
||||
case SampleFormat::PCM8: Helpers::warn("Unimplemented sample format!"); break;
|
||||
case SampleFormat::PCM16: source.currentSamples = decodePCM16(data, buffer.sampleCount, source); break;
|
||||
case SampleFormat::ADPCM: source.currentSamples = decodeADPCM(data, buffer.sampleCount, source); break;
|
||||
|
||||
default:
|
||||
Helpers::warn("Invalid DSP sample format");
|
||||
source.currentSamples = {};
|
||||
break;
|
||||
}
|
||||
|
||||
// If the buffer is a looping buffer, re-push it
|
||||
if (buffer.looping) {
|
||||
source.pushBuffer(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
void HLE_DSP::generateFrame(DSPSource& source) {
|
||||
if (source.currentSamples.empty()) {
|
||||
// There's no audio left to play, turn the voice off
|
||||
if (source.buffers.empty()) {
|
||||
source.enabled = false;
|
||||
source.isBufferIDDirty = true;
|
||||
source.previousBufferID = source.currentBufferID;
|
||||
source.currentBufferID = 0;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
decodeBuffer(source);
|
||||
} else {
|
||||
constexpr uint maxSampleCount = Audio::samplesInFrame;
|
||||
uint outputCount = 0;
|
||||
|
||||
while (outputCount < maxSampleCount) {
|
||||
if (source.currentSamples.empty()) {
|
||||
if (source.buffers.empty()) {
|
||||
break;
|
||||
} else {
|
||||
decodeBuffer(source);
|
||||
}
|
||||
}
|
||||
|
||||
const uint sampleCount = std::min<s32>(maxSampleCount - outputCount, source.currentSamples.size());
|
||||
// samples.insert(samples.end(), source.currentSamples.begin(), source.currentSamples.begin() + sampleCount);
|
||||
source.currentSamples.erase(source.currentSamples.begin(), source.currentSamples.begin() + sampleCount);
|
||||
|
||||
outputCount += sampleCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HLE_DSP::SampleBuffer HLE_DSP::decodePCM16(const u8* data, usize sampleCount, Source& source) {
|
||||
SampleBuffer decodedSamples(sampleCount);
|
||||
const s16* data16 = reinterpret_cast<const s16*>(data);
|
||||
|
||||
if (source.sourceType == SourceType::Stereo) {
|
||||
for (usize i = 0; i < sampleCount; i++) {
|
||||
const s16 left = *data16++;
|
||||
const s16 right = *data16++;
|
||||
decodedSamples[i] = {left, right};
|
||||
}
|
||||
} else {
|
||||
// Mono
|
||||
for (usize i = 0; i < sampleCount; i++) {
|
||||
const s16 sample = *data16++;
|
||||
decodedSamples[i] = {sample, sample};
|
||||
}
|
||||
}
|
||||
|
||||
return decodedSamples;
|
||||
}
|
||||
|
||||
HLE_DSP::SampleBuffer HLE_DSP::decodeADPCM(const u8* data, usize sampleCount, Source& source) {
|
||||
|
@ -413,6 +490,15 @@ namespace Audio {
|
|||
|
||||
void DSPSource::reset() {
|
||||
enabled = false;
|
||||
isBufferIDDirty = false;
|
||||
|
||||
// Initialize these to some sane defaults
|
||||
sampleFormat = SampleFormat::ADPCM;
|
||||
sourceType = SourceType::Stereo;
|
||||
|
||||
samplePosition = 0;
|
||||
previousBufferID = 0;
|
||||
currentBufferID = 0;
|
||||
syncCount = 0;
|
||||
|
||||
buffers = {};
|
||||
|
|
Loading…
Add table
Reference in a new issue