Skip to content

Commit

Permalink
Merge pull request wheremyfoodat#566 from wheremyfoodat/hle-dsp
Browse files Browse the repository at this point in the history
Fixing remaining HLE DSP hangs
  • Loading branch information
wheremyfoodat authored Aug 4, 2024
2 parents e9f98bf + 85bae2e commit 0cf5687
Show file tree
Hide file tree
Showing 7 changed files with 85 additions and 26 deletions.
2 changes: 1 addition & 1 deletion include/audio/dsp_core.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ namespace Audio {
virtual ~DSPCore() {}

virtual void reset() = 0;
virtual void runAudioFrame() = 0;
virtual void runAudioFrame(u64 eventTimestamp) = 0;
virtual u8* getDspMemory() = 0;

virtual u16 recvData(u32 regId) = 0;
Expand Down
6 changes: 4 additions & 2 deletions include/audio/hle_core.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ namespace Audio {
return this->bufferID > other.bufferID;
}
};

// Buffer of decoded PCM16 samples. TODO: Are there better alternatives to use over deque?
using SampleBuffer = std::deque<std::array<s16, 2>>;

Expand All @@ -53,6 +54,7 @@ namespace Audio {

std::array<float, 3> gain0, gain1, gain2;
u32 samplePosition; // Sample number into the current audio buffer
float rateMultiplier;
u16 syncCount;
u16 currentBufferID;
u16 previousBufferID;
Expand Down Expand Up @@ -142,7 +144,7 @@ namespace Audio {
} else if (counter1 == 0xffff && counter0 != 0xfffe) {
return 0;
} else {
return counter0 > counter1 ? 0 : 0;
return (counter0 > counter1) ? 0 : 1;
}
}

Expand Down Expand Up @@ -185,7 +187,7 @@ namespace Audio {
~HLE_DSP() override {}

void reset() override;
void runAudioFrame() override;
void runAudioFrame(u64 eventTimestamp) override;

u8* getDspMemory() override { return dspRam.rawMemory.data(); }

Expand Down
2 changes: 1 addition & 1 deletion include/audio/null_core.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ namespace Audio {
~NullDSP() override {}

void reset() override;
void runAudioFrame() override;
void runAudioFrame(u64 eventTimestamp) override;

u8* getDspMemory() override { return dspRam.data(); }

Expand Down
2 changes: 1 addition & 1 deletion include/audio/teakra_core.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ namespace Audio {
void reset() override;

// Run 1 slice of DSP instructions and schedule the next audio frame
void runAudioFrame() override {
void runAudioFrame(u64 eventTimestamp) override {
runSlice();
scheduler.addEvent(Scheduler::EventType::RunDSP, scheduler.currentTimestamp + Audio::lleSlice * 2);
}
Expand Down
95 changes: 76 additions & 19 deletions src/core/audio/hle_core.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#include <algorithm>
#include <cassert>
#include <iterator>
#include <thread>
#include <utility>

Expand Down Expand Up @@ -94,15 +95,18 @@ namespace Audio {
scheduler.removeEvent(Scheduler::EventType::RunDSP);
}

void HLE_DSP::runAudioFrame() {
void HLE_DSP::runAudioFrame(u64 eventTimestamp) {
// Signal audio pipe when an audio frame is done
if (dspState == DSPState::On) [[likely]] {
dspService.triggerPipeEvent(DSPPipeType::Audio);
}

// TODO: Should this be called if dspState != DSPState::On?
outputFrame();
scheduler.addEvent(Scheduler::EventType::RunDSP, scheduler.currentTimestamp + Audio::cyclesPerFrame);

// How many cycles we were late
const u64 cycleDrift = scheduler.currentTimestamp - eventTimestamp;
scheduler.addEvent(Scheduler::EventType::RunDSP, scheduler.currentTimestamp + Audio::cyclesPerFrame - cycleDrift);
}

u16 HLE_DSP::recvData(u32 regId) {
Expand Down Expand Up @@ -216,6 +220,11 @@ namespace Audio {
SharedMemory& read = readRegion();
SharedMemory& write = writeRegion();

// TODO: Properly implement mixers
// The DSP checks the DSP configuration dirty bits on every frame, applies them, and clears them
read.dspConfiguration.dirtyRaw = 0;
read.dspConfiguration.dirtyRaw2 = 0;

for (int i = 0; i < sourceCount; i++) {
// Update source configuration from the read region of shared memory
auto& config = read.sourceConfigurations.config[i];
Expand All @@ -231,10 +240,9 @@ namespace Audio {
auto& status = write.sourceStatuses.status[i];
status.enabled = source.enabled;
status.syncCount = source.syncCount;
status.currentBufferIDDirty = source.isBufferIDDirty ? 1 : 0;
status.currentBufferIDDirty = (source.isBufferIDDirty ? 1 : 0);
status.currentBufferID = source.currentBufferID;
status.previousBufferID = source.previousBufferID;
// TODO: Properly update sample position
status.samplePosition = source.samplePosition;

source.isBufferIDDirty = false;
Expand All @@ -247,6 +255,17 @@ namespace Audio {
return;
}

// The reset flags take priority, as you can reset a source and set it up to be played again at the same time
if (config.resetFlag) {
config.resetFlag = 0;
source.reset();
}

if (config.partialResetFlag) {
config.partialResetFlag = 0;
source.buffers = {};
}

if (config.enableDirty) {
config.enableDirty = 0;
source.enabled = config.enable != 0;
Expand All @@ -266,16 +285,6 @@ namespace Audio {
);
}

if (config.resetFlag) {
config.resetFlag = 0;
source.reset();
}

if (config.partialResetFlag) {
config.partialResetFlag = 0;
source.buffers = {};
}

// TODO: Should we check bufferQueueDirty here too?
if (config.formatDirty || config.embeddedBufferDirty) {
source.sampleFormat = config.format;
Expand All @@ -285,7 +294,14 @@ namespace Audio {
source.sourceType = config.monoOrStereo;
}

if (config.rateMultiplierDirty) {
source.rateMultiplier = (config.rateMultiplier > 0.f) ? config.rateMultiplier : 1.f;
}

if (config.embeddedBufferDirty) {
// Annoyingly, and only for embedded buffer, whether we use config.playPosition depends on the relevant dirty bit
const u32 playPosition = config.playPositionDirty ? config.playPosition : 0;

config.embeddedBufferDirty = 0;
if (s32(config.length) >= 0) [[likely]] {
// TODO: Add sample format and channel count
Expand All @@ -297,7 +313,7 @@ namespace Audio {
.adpcmDirty = config.adpcmDirty != 0,
.looping = config.isLooping != 0,
.bufferID = config.bufferID,
.playPosition = config.playPosition,
.playPosition = playPosition,
.format = source.sampleFormat,
.sourceType = source.sourceType,
.fromQueue = false,
Expand All @@ -316,8 +332,40 @@ namespace Audio {
}

if (config.bufferQueueDirty) {
config.bufferQueueDirty = 0;
// printf("Buffer queue dirty for voice %d\n", source.index);

u16 dirtyBuffers = config.buffersDirty;
config.bufferQueueDirty = 0;
config.buffersDirty = 0;

for (int i = 0; i < 4; i++) {
bool dirty = ((dirtyBuffers >> i) & 1) != 0;
if (dirty) {
const auto& buffer = config.buffers[i];

if (s32(buffer.length) >= 0) [[likely]] {
// TODO: Add sample format and channel count
Source::Buffer newBuffer{
.paddr = buffer.physicalAddress,
.sampleCount = buffer.length,
.adpcmScale = u8(buffer.adpcm_ps),
.previousSamples = {s16(buffer.adpcm_yn[0]), s16(buffer.adpcm_yn[1])},
.adpcmDirty = buffer.adpcmDirty != 0,
.looping = buffer.isLooping != 0,
.bufferID = buffer.bufferID,
.playPosition = 0,
.format = source.sampleFormat,
.sourceType = source.sourceType,
.fromQueue = true,
.hasPlayedOnce = false,
};

source.buffers.emplace(std::move(newBuffer));
} else {
printf("Buffer queue dirty: Invalid buffer size for DSP voice %d\n", source.index);
}
}
}
}

config.dirtyRaw = 0;
Expand Down Expand Up @@ -369,6 +417,13 @@ namespace Audio {
if (buffer.looping) {
source.pushBuffer(buffer);
}

// We're skipping the first samplePosition samples, so remove them from the buffer so as not to consume them later
if (source.samplePosition > 0) {
auto start = source.currentSamples.begin();
auto end = std::next(start, source.samplePosition);
source.currentSamples.erase(start, end);
}
}

void HLE_DSP::generateFrame(DSPSource& source) {
Expand All @@ -385,7 +440,7 @@ namespace Audio {

decodeBuffer(source);
} else {
constexpr uint maxSampleCount = Audio::samplesInFrame;
uint maxSampleCount = uint(float(Audio::samplesInFrame) * source.rateMultiplier);
uint outputCount = 0;

while (outputCount < maxSampleCount) {
Expand All @@ -398,9 +453,10 @@ namespace Audio {
}

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);

// samples.insert(samples.end(), source.currentSamples.begin(), source.currentSamples.begin() + sampleCount);
source.currentSamples.erase(source.currentSamples.begin(), std::next(source.currentSamples.begin(), sampleCount));
source.samplePosition += sampleCount;
outputCount += sampleCount;
}
}
Expand Down Expand Up @@ -568,6 +624,7 @@ namespace Audio {
previousBufferID = 0;
currentBufferID = 0;
syncCount = 0;
rateMultiplier = 1.f;

buffers = {};
}
Expand Down
2 changes: 1 addition & 1 deletion src/core/audio/null_core.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ namespace Audio {
scheduler.removeEvent(Scheduler::EventType::RunDSP);
}

void NullDSP::runAudioFrame() {
void NullDSP::runAudioFrame(u64 eventTimestamp) {
// Signal audio pipe when an audio frame is done
if (dspState == DSPState::On) [[likely]] {
dspService.triggerPipeEvent(DSPPipeType::Audio);
Expand Down
2 changes: 1 addition & 1 deletion src/emulator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ void Emulator::pollScheduler() {

case Scheduler::EventType::UpdateTimers: kernel.pollTimers(); break;
case Scheduler::EventType::RunDSP: {
dsp->runAudioFrame();
dsp->runAudioFrame(time);
break;
}

Expand Down

0 comments on commit 0cf5687

Please sign in to comment.