Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SasAudio: Implement linear interpolation #8950

Merged
merged 8 commits into from
Dec 20, 2016
80 changes: 30 additions & 50 deletions Core/HW/SasAudio.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,6 @@ SasInstance::SasInstance()
sendBuffer(0),
sendBufferDownsampled(0),
sendBufferProcessed(0),
resampleBuffer(0),
grainSize(0) {
#ifdef AUDIO_TO_FILE
audioDump = fopen("D:\\audio.raw", "wb");
Expand Down Expand Up @@ -376,10 +375,8 @@ void SasInstance::ClearGrainSize() {
delete[] sendBuffer;
delete[] sendBufferDownsampled;
delete[] sendBufferProcessed;
delete[] resampleBuffer;
mixBuffer = nullptr;
sendBuffer = nullptr;
resampleBuffer = nullptr;
sendBufferDownsampled = nullptr;
sendBufferProcessed = nullptr;
}
Expand All @@ -392,7 +389,6 @@ void SasInstance::SetGrainSize(int newGrainSize) {
delete[] sendBuffer;
delete[] sendBufferDownsampled;
delete[] sendBufferProcessed;
delete[] resampleBuffer;

mixBuffer = new s32[grainSize * 2];
sendBuffer = new s32[grainSize * 2];
Expand All @@ -402,10 +398,6 @@ void SasInstance::SetGrainSize(int newGrainSize) {
memset(sendBuffer, 0, sizeof(int) * grainSize * 2);
memset(sendBufferDownsampled, 0, sizeof(s16) * grainSize);
memset(sendBufferProcessed, 0, sizeof(s16) * grainSize * 2);

// 2 samples padding at the start, that's where we copy the two last samples from the channel
// so that we can do bicubic resampling if necessary. Plus 1 for smoothness hackery.
resampleBuffer = new s16[grainSize * 4 + 3];
}

int SasInstance::EstimateMixUs() {
Expand Down Expand Up @@ -459,9 +451,7 @@ void SasVoice::ReadSamples(s16 *output, int numSamples) {
atrac3.getNextSamples(output, numSamples);
break;
default:
{
memset(output, 0, numSamples * sizeof(s16));
}
memset(output, 0, numSamples * sizeof(s16));
break;
}
}
Expand Down Expand Up @@ -493,47 +483,35 @@ void SasInstance::MixVoice(SasVoice &voice) {
break;
// else fallthrough! Don't change the check above.
default:
// Load resample history (so we can use a wide filter)
resampleBuffer[0] = voice.resampleHist[0];
resampleBuffer[1] = voice.resampleHist[1];

// Figure out number of samples to read.
// Actually this is not entirely correct - we need to get one extra sample, and store it
// for the next time around. A little complicated...
// But for now, see Smoothness HACKERY below :P
u32 numSamples = ((u32)voice.sampleFrac + (u32)grainSize * (u32)voice.pitch) >> PSP_SAS_PITCH_BASE_SHIFT;
if ((int)numSamples > grainSize * 4) {
ERROR_LOG(SASMIX, "numSamples too large, clamping: %i vs %i", numSamples, grainSize * 4);
numSamples = grainSize * 4;
}

// This feels a bit hacky. The first 32 samples after a keyon are 0s.
const bool ignorePitch = voice.type == VOICETYPE_PCM && voice.pitch > PSP_SAS_PITCH_BASE;
int delay = 0;
if (voice.envelope.NeedsKeyOn()) {
int delay = ignorePitch ? 32 : (32 * (u32)voice.pitch) >> PSP_SAS_PITCH_BASE_SHIFT;
const bool ignorePitch = voice.type == VOICETYPE_PCM && voice.pitch > PSP_SAS_PITCH_BASE;
delay = ignorePitch ? 32 : (32 * (u32)voice.pitch) >> PSP_SAS_PITCH_BASE_SHIFT;
// VAG seems to have an extra sample delay (not shared by PCM.)
if (voice.type == VOICETYPE_VAG)
++delay;
voice.ReadSamples(resampleBuffer + 2 + delay, numSamples - delay);
} else {
voice.ReadSamples(resampleBuffer + 2, numSamples);
}

// Smoothness HACKERY
resampleBuffer[2 + numSamples] = resampleBuffer[2 + numSamples - 1];

// Save resample history
voice.resampleHist[0] = resampleBuffer[2 + numSamples - 2];
voice.resampleHist[1] = resampleBuffer[2 + numSamples - 1];

// Resample to the correct pitch, writing exactly "grainSize" samples.
// This is a HORRIBLE resampler by the way.
// TODO: Special case no-resample case (and 2x and 0.5x) for speed, it's not uncommon
int16_t temp[PSP_SAS_MAX_GRAIN + 2];

// Two passes: First read, then resample.
u32 sampleFrac = voice.sampleFrac;
for (int i = 0; i < grainSize; i++) {
// For now: nearest neighbour, not even using the resample history at all.
int sample = resampleBuffer[sampleFrac / PSP_SAS_PITCH_BASE + 2];
temp[0] = voice.resampleHist[0];
temp[1] = voice.resampleHist[1];

int samplesToRead = (sampleFrac + voice.pitch * (grainSize - delay)) >> PSP_SAS_PITCH_BASE_SHIFT;
voice.ReadSamples(&temp[2], samplesToRead);
int tempPos = 2 + samplesToRead;

for (int i = delay; i < grainSize; i++) {
const int16_t *s = temp + (sampleFrac >> PSP_SAS_PITCH_BASE_SHIFT);

// Linear interpolation. Good enough. Need to make resampleHist bigger if we want more.
int f = sampleFrac & PSP_SAS_PITCH_MASK;
int sample = (s[0] * (PSP_SAS_PITCH_MASK - f) + s[1] * f) >> PSP_SAS_PITCH_BASE_SHIFT;
sampleFrac += voice.pitch;

// The maximum envelope height (PSP_SAS_ENVELOPE_HEIGHT_MAX) is (1 << 30) - 1.
Expand All @@ -549,21 +527,20 @@ void SasInstance::MixVoice(SasVoice &voice) {
// We mix into this 32-bit temp buffer and clip in a second loop
// Ideally, the shift right should be there too but for now I'm concerned about
// not overflowing.
mixBuffer[i * 2] += (sample * voice.volumeLeft ) >> 12;
mixBuffer[i * 2] += (sample * voice.volumeLeft) >> 12;
mixBuffer[i * 2 + 1] += (sample * voice.volumeRight) >> 12;
sendBuffer[i * 2] += sample * voice.effectLeft >> 12;
sendBuffer[i * 2 + 1] += sample * voice.effectRight >> 12;
}

voice.sampleFrac = sampleFrac;
// Let's hope grainSize is a power of 2.
//voice.sampleFrac &= grainSize * PSP_SAS_PITCH_BASE - 1;
voice.sampleFrac -= numSamples * PSP_SAS_PITCH_BASE;
voice.resampleHist[0] = temp[tempPos - 2];
voice.resampleHist[1] = temp[tempPos - 1];

voice.sampleFrac = sampleFrac - (tempPos - 2) * PSP_SAS_PITCH_BASE;;

if (voice.HaveSamplesEnded())
voice.envelope.End();
if (voice.envelope.HasEnded())
{
if (voice.envelope.HasEnded()) {
// NOTICE_LOG(SCESAS, "Hit end of envelope");
voice.playing = false;
voice.on = false;
Expand Down Expand Up @@ -711,8 +688,11 @@ void SasInstance::DoState(PointerWrap &p) {
if (sendBuffer != NULL && grainSize > 0) {
p.DoArray(sendBuffer, grainSize * 2);
}
if (resampleBuffer != NULL && grainSize > 0) {
p.DoArray(resampleBuffer, grainSize * 4 + 3);
if (sendBuffer != NULL && grainSize > 0) {
// Backwards compat
int16_t *resampleBuf = new int16_t[grainSize * 4 + 3]();
p.DoArray(resampleBuf, grainSize * 4 + 3);
delete[] resampleBuf;
}

int n = PSP_SAS_VOICES_MAX;
Expand Down
6 changes: 3 additions & 3 deletions Core/HW/SasAudio.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,12 @@ enum {

PSP_SAS_PITCH_MIN = 0x0000,
PSP_SAS_PITCH_BASE = 0x1000,
PSP_SAS_PITCH_MASK = 0xFFF,
PSP_SAS_PITCH_BASE_SHIFT = 12,
PSP_SAS_PITCH_MAX = 0x4000,

PSP_SAS_VOL_MAX = 0x1000,
PSP_SAS_MAX_GRAIN = 1024, // VERY conservative! 256 is quite common but don't think I've ever seen bigger.
Copy link
Collaborator

@unknownbrackets unknownbrackets Mar 20, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

__sceSasInit allows grainSize of 2048 at most, any higher values fail with ERROR_SAS_INVALID_GRAIN. But 2048 is allowed.

-[Unknown]

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh right, that's a pretty compelling argument. I'll change it.


PSP_SAS_ADSR_CURVE_MODE_LINEAR_INCREASE = 0,
PSP_SAS_ADSR_CURVE_MODE_LINEAR_DECREASE = 1,
Expand Down Expand Up @@ -250,7 +252,7 @@ struct SasVoice {
int pcmLoopPos;
int sampleRate;

int sampleFrac;
uint32_t sampleFrac;
int pitch;
bool loop;

Expand Down Expand Up @@ -289,8 +291,6 @@ class SasInstance {
s16 *sendBufferDownsampled;
s16 *sendBufferProcessed;

s16 *resampleBuffer;

FILE *audioDump;

void Mix(u32 outAddr, u32 inAddr = 0, int leftVol = 0, int rightVol = 0);
Expand Down