diff --git a/include/audio/dsp_shared_mem.hpp b/include/audio/dsp_shared_mem.hpp index 148986f9..25806ea1 100644 --- a/include/audio/dsp_shared_mem.hpp +++ b/include/audio/dsp_shared_mem.hpp @@ -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 }; diff --git a/include/audio/hle_core.hpp b/include/audio/hle_core.hpp index 257ab5ac..cee2b0c8 100644 --- a/include/audio/hle_core.hpp +++ b/include/audio/hle_core.hpp @@ -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; BufferQueue buffers; + SampleFormat sampleFormat = SampleFormat::ADPCM; + SourceType sourceType = SourceType::Stereo; + std::array 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 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& 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: diff --git a/src/core/audio/hle_core.cpp b/src/core/audio/hle_core.cpp index 4ee5a1dc..d6ba21ec 100644 --- a/src/core/audio/hle_core.cpp +++ b/src/core/audio/hle_core.cpp @@ -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(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(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(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 = {};