Skip to content

Commit

Permalink
Merge pull request #502 from wheremyfoodat/moar-hle-dsp
Browse files Browse the repository at this point in the history
More HLE DSP work
  • Loading branch information
wheremyfoodat authored Apr 30, 2024
2 parents 8e303d8 + fb8130a commit 21492f8
Show file tree
Hide file tree
Showing 3 changed files with 125 additions and 26 deletions.
2 changes: 1 addition & 1 deletion include/audio/dsp_shared_mem.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
};
Expand Down
25 changes: 19 additions & 6 deletions include/audio/hle_core.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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());
Expand Down Expand Up @@ -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?

Expand Down Expand Up @@ -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:
Expand Down
124 changes: 105 additions & 19 deletions src/core/audio/hle_core.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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;
}
}

Expand Down Expand Up @@ -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) {
Expand All @@ -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,
};
Expand Down Expand Up @@ -327,15 +326,93 @@ 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;

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"); 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) {
static constexpr uint samplesPerBlock = 14;
// An ADPCM block is comprised of a single header which contains the scale and predictor value for the block, and then 14 4bpp samples (hence
Expand Down Expand Up @@ -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 = {};
Expand Down

0 comments on commit 21492f8

Please # to comment.