From 1400237d0e192e2476a04752ef3125eb5ebb9078 Mon Sep 17 00:00:00 2001 From: Henrik Rydgard Date: Mon, 17 Dec 2012 18:05:10 +0100 Subject: [PATCH] Lots of various work on sceSas. ADSR envelopes, correct looping, etc etc... Still far from perfect but better. --- Common/Log.h | 1 + Common/LogManager.cpp | 1 + Core/CoreTiming.cpp | 24 +-- Core/Dialog/PSPSaveDialog.cpp | 2 +- Core/HLE/sceIo.cpp | 1 - Core/HLE/sceSas.cpp | 125 ++++++++----- Core/HW/SasAudio.cpp | 336 ++++++++++++++++++++++++++++++---- Core/HW/SasAudio.h | 100 +++++----- 8 files changed, 447 insertions(+), 143 deletions(-) diff --git a/Common/Log.h b/Common/Log.h index 07c90072f4..7085f4179c 100644 --- a/Common/Log.h +++ b/Common/Log.h @@ -45,6 +45,7 @@ enum LOG_TYPE { INTC, MEMMAP, SOUND, + SAS, HLE, TIMER, VIDEO, diff --git a/Common/LogManager.cpp b/Common/LogManager.cpp index 48843266fb..0918cebf9d 100644 --- a/Common/LogManager.cpp +++ b/Common/LogManager.cpp @@ -56,6 +56,7 @@ LogManager::LogManager() m_Log[LogTypes::INTC] = new LogContainer("INTC", "Interrupts"); m_Log[LogTypes::MEMMAP] = new LogContainer("MM", "Memory Map"); m_Log[LogTypes::SOUND] = new LogContainer("SND", "Sound"); + m_Log[LogTypes::SAS] = new LogContainer("SAS", "Sound Mixer (Sas)"); m_Log[LogTypes::HLE] = new LogContainer("HLE", "HLE"); m_Log[LogTypes::TIMER] = new LogContainer("TMR", "Timer"); m_Log[LogTypes::VIDEO] = new LogContainer("VID", "Video"); diff --git a/Core/CoreTiming.cpp b/Core/CoreTiming.cpp index 99b41441f6..c9bc5a6db7 100644 --- a/Core/CoreTiming.cpp +++ b/Core/CoreTiming.cpp @@ -486,19 +486,19 @@ void Idle(int maxIdle) if (maxIdle != 0 && cyclesDown > maxIdle) cyclesDown = maxIdle; - if (first && cyclesDown > 0) - { - int cyclesExecuted = slicelength - downcount; - int cyclesNextEvent = (int) (first->time - globalTimer); + if (first && cyclesDown > 0) + { + int cyclesExecuted = slicelength - downcount; + int cyclesNextEvent = (int) (first->time - globalTimer); - if (cyclesNextEvent < cyclesExecuted + cyclesDown) - { - cyclesDown = cyclesNextEvent - cyclesExecuted; - // Now, now... no time machines, please. - if (cyclesDown < 0) - cyclesDown = 0; - } - } + if (cyclesNextEvent < cyclesExecuted + cyclesDown) + { + cyclesDown = cyclesNextEvent - cyclesExecuted; + // Now, now... no time machines, please. + if (cyclesDown < 0) + cyclesDown = 0; + } + } DEBUG_LOG(CPU, "Idle for %i cycles! (%f ms)", cyclesDown, cyclesDown / (float)(CPU_HZ * 0.001f)); diff --git a/Core/Dialog/PSPSaveDialog.cpp b/Core/Dialog/PSPSaveDialog.cpp index f8193be97d..421ebd12b4 100644 --- a/Core/Dialog/PSPSaveDialog.cpp +++ b/Core/Dialog/PSPSaveDialog.cpp @@ -74,7 +74,7 @@ void PSPSaveDialog::Init(int paramAddr) case SCE_UTILITY_SAVEDATA_TYPE_LIST: display = DS_NONE; break; - case SCE_UTILITY_SAVEDATA_TYPE_DELETE: // This run on PSP display a list of all save on the PSP. Weird. + case SCE_UTILITY_SAVEDATA_TYPE_DELETE: // This run on PSP display a list of all save on the PSP. Weird. (Not really, it's to let you free up space) default: { ERROR_LOG(HLE, "Load/Save function %d not coded. Title: %s Save: %s File: %s", param.GetPspParam()->mode, param.GetGameName(param.GetPspParam()).c_str(), param.GetGameName(param.GetPspParam()).c_str(), param.GetFileName(param.GetPspParam()).c_str()); diff --git a/Core/HLE/sceIo.cpp b/Core/HLE/sceIo.cpp index 462c40b278..17817b760b 100644 --- a/Core/HLE/sceIo.cpp +++ b/Core/HLE/sceIo.cpp @@ -436,7 +436,6 @@ struct DeviceSize { }; u32 sceIoDevctl(const char *name, int cmd, u32 argAddr, int argLen, u32 outPtr, int outLen) { - if (strcmp(name, "emulator:")) { DEBUG_LOG(HLE,"sceIoDevctl(\"%s\", %08x, %08x, %i, %08x, %i)", name, cmd,argAddr,argLen,outPtr,outLen); } diff --git a/Core/HLE/sceSas.cpp b/Core/HLE/sceSas.cpp index 84cbdd6d61..b8902626bc 100644 --- a/Core/HLE/sceSas.cpp +++ b/Core/HLE/sceSas.cpp @@ -18,6 +18,8 @@ // SAS is a software mixing engine that runs on the Media Engine CPU. We just HLE it. // This is a very rough implementation that needs lots of work. // +// This file just contains the API, the real stuff is in HW/SasAudio.cpp/h. +// // JPCSP is, as it often is, a pretty good reference although I didn't actually use it much yet: // http://code.google.com/p/jpcsp/source/browse/trunk/src/jpcsp/HLE/modules150/sceSasCore.java @@ -30,6 +32,15 @@ #include "sceSas.h" #include "sceKernel.h" +enum { + ERROR_SAS_INVALID_VOICE = 0x80420010, + ERROR_SAS_INVALID_ADSR_CURVE_MODE = 0x80420013, + ERROR_SAS_INVALID_PARAMETER = 0x80420014, + ERROR_SAS_VOICE_PAUSED = 0x80420016, + ERROR_SAS_INVALID_SIZE = 0x8042001A, + ERROR_SAS_BUSY = 0x80420030, + ERROR_SAS_NOT_INIT = 0x80420100, +}; // TODO - allow more than one, associating each with one Core pointer (passed in to all the functions) // No known games use more than one instance of Sas though. @@ -65,6 +76,9 @@ u32 sceSasGetEndFlag(u32 core) u32 _sceSasCore(u32 core, u32 outAddr) { DEBUG_LOG(HLE,"0=sceSasCore(, %08x) (grain: %i samples)", outAddr, sas.GetGrainSize()); + if (!Memory::IsValidAddress(outAddr)) { + return ERROR_SAS_INVALID_PARAMETER; + } Memory::Memset(outAddr, 0, sas.GetGrainSize() * 2 * 2); sas.Mix(outAddr); return 0; @@ -74,6 +88,9 @@ u32 _sceSasCore(u32 core, u32 outAddr) u32 _sceSasCoreWithMix(u32 core, u32 outAddr) { DEBUG_LOG(HLE,"0=sceSasCoreWithMix(, %08x) (grain: %i samples)", outAddr, sas.GetGrainSize()); + if (!Memory::IsValidAddress(outAddr)) { + return ERROR_SAS_INVALID_PARAMETER; + } sas.Mix(outAddr); return 0; } @@ -86,7 +103,7 @@ u32 sceSasSetVoice(u32 core, int voiceNum, u32 vagAddr, int size, int loop) if (voiceNum >= PSP_SAS_VOICES_MAX || voiceNum < 0) { WARN_LOG(HLE, "%s: invalid voicenum %d", __FUNCTION__, voiceNum); - return 0; + return ERROR_SAS_INVALID_VOICE; } //Real VAG header is 0x30 bytes behind the vagAddr @@ -94,11 +111,22 @@ u32 sceSasSetVoice(u32 core, int voiceNum, u32 vagAddr, int size, int loop) v.type = VOICETYPE_VAG; v.vagAddr = vagAddr; v.vagSize = size; - v.loop = loop; - v.playing = false; + v.loop = loop ? true : false; + v.ChangedParams(); return 0; } +u32 sceSasGetPauseFlag(u32 core) +{ + u32 pauseFlag = 0; + for (int i = 0; i < sas.maxVoices; i++) { + if (sas.voices[i].paused) + pauseFlag |= 1 << i; + } + DEBUG_LOG(HLE,"%08x=sceSasGetPauseFlag()", pauseFlag); + return pauseFlag; +} + u32 sceSasSetPause(u32 core, int voicebit, int pause) { DEBUG_LOG(HLE,"0=sceSasSetPause(core=%08x, voicebit=%08x, pause=%i)", core, voicebit, pause); @@ -107,11 +135,11 @@ u32 sceSasSetPause(u32 core, int voicebit, int pause) if (i < PSP_SAS_VOICES_MAX && i >= 0) { if ((voicebit & 1) != 0) - sas.voices[i].setPaused = pause; + sas.voices[i].paused = pause ? true : false; } // TODO: Correct error code? Mimana crashes otherwise. else - return -1; + return ERROR_SAS_INVALID_VOICE; } return 0; @@ -124,7 +152,7 @@ u32 sceSasSetVolume(u32 core, int voiceNum, int l, int r, int el, int er) if (voiceNum >= PSP_SAS_VOICES_MAX || voiceNum < 0) { WARN_LOG(HLE, "%s: invalid voicenum %d", __FUNCTION__, voiceNum); - return 0; + return ERROR_SAS_INVALID_VOICE; } SasVoice &v = sas.voices[voiceNum]; @@ -140,7 +168,7 @@ u32 sceSasSetPitch(u32 core, int voiceNum, int pitch) if (voiceNum >= PSP_SAS_VOICES_MAX || voiceNum < 0) { WARN_LOG(HLE, "%s: invalid voicenum %d", __FUNCTION__, voiceNum); - return 0; + return ERROR_SAS_INVALID_VOICE; } SasVoice &v = sas.voices[voiceNum]; @@ -155,16 +183,14 @@ u32 sceSasSetKeyOn(u32 core, int voiceNum) if (voiceNum >= PSP_SAS_VOICES_MAX || voiceNum < 0) { WARN_LOG(HLE, "%s: invalid voicenum %d", __FUNCTION__, voiceNum); - return 0; + return ERROR_SAS_INVALID_VOICE; } SasVoice &v = sas.voices[voiceNum]; - v.vag.Start(Memory::GetPointer(v.vagAddr)); - v.playing = true; + v.KeyOn(); return 0; } -// TODO: We really need ADSR work: // sceSasSetKeyOff can be used to start sounds, that just sound during the Release phase! u32 sceSasSetKeyOff(u32 core, int voiceNum) { @@ -173,11 +199,11 @@ u32 sceSasSetKeyOff(u32 core, int voiceNum) if (voiceNum >= PSP_SAS_VOICES_MAX || voiceNum < 0) { WARN_LOG(HLE, "%s: invalid voicenum %d", __FUNCTION__, voiceNum); - return 0; + return ERROR_SAS_INVALID_VOICE; } SasVoice &v = sas.voices[voiceNum]; - v.playing = false; // not right! Should directly enter Release envelope stage instead! + v.KeyOff(); return 0; } @@ -188,7 +214,7 @@ u32 sceSasSetNoise(u32 core, int voiceNum, int freq) if (voiceNum >= PSP_SAS_VOICES_MAX || voiceNum < 0) { WARN_LOG(HLE, "%s: invalid voicenum %d", __FUNCTION__, voiceNum); - return 0; + return ERROR_SAS_INVALID_VOICE; } SasVoice &v = sas.voices[voiceNum]; @@ -204,7 +230,7 @@ u32 sceSasSetSL(u32 core, int voiceNum, int level) if (voiceNum >= PSP_SAS_VOICES_MAX || voiceNum < 0) { WARN_LOG(HLE, "%s: invalid voicenum %d", __FUNCTION__, voiceNum); - return 0; + return ERROR_SAS_INVALID_VOICE; } SasVoice &v = sas.voices[voiceNum]; @@ -219,7 +245,7 @@ u32 sceSasSetADSR(u32 core, int voiceNum,int flag ,int a, int d, int s, int r) if (voiceNum >= PSP_SAS_VOICES_MAX || voiceNum < 0) { WARN_LOG(HLE, "%s: invalid voicenum %d", __FUNCTION__, voiceNum); - return 0; + return ERROR_SAS_INVALID_VOICE; } SasVoice &v = sas.voices[voiceNum]; @@ -237,7 +263,7 @@ u32 sceSasSetADSRMode(u32 core, int voiceNum,int flag ,int a, int d, int s, int if (voiceNum >= PSP_SAS_VOICES_MAX || voiceNum < 0) { WARN_LOG(HLE, "%s: invalid voicenum %d", __FUNCTION__, voiceNum); - return 0; + return ERROR_SAS_INVALID_VOICE; } SasVoice &v = sas.voices[voiceNum]; @@ -264,43 +290,34 @@ u32 sceSasGetEnvelopeHeight(u32 core, u32 voiceNum) { DEBUG_LOG(HLE,"UNIMPL 0=sceSasGetEnvelopeHeight(core=%08x, voicenum=%i)", core, voiceNum); } - SasVoice &v = sas.voices[voiceNum]; + if (voiceNum >= PSP_SAS_VOICES_MAX || voiceNum < 0) + { + WARN_LOG(HLE, "%s: invalid voicenum %d", __FUNCTION__, voiceNum); + return ERROR_SAS_INVALID_VOICE; + } - return v.playing ? 0x3fffffff : 0; + SasVoice &v = sas.voices[voiceNum]; + return v.envelope.GetHeight(); } u32 sceSasRevType(u32 core, int type) { DEBUG_LOG(HLE,"0=sceSasRevType(core=%08x, type=%i)", core, type); - - sas.waveformEffect.type=type; + sas.waveformEffect.type = type; return 0; } u32 sceSasRevParam(u32 core, int delay, int feedback) { DEBUG_LOG(HLE,"0=sceSasRevParam(core=%08x, delay=%i, feedback=%i)", core, delay, feedback); - sas.waveformEffect.delay = delay; sas.waveformEffect.feedback = feedback; return 0; } -u32 sceSasGetPauseFlag(u32 core) -{ - u32 PauseFlag = 0; - for (int i = 0; i < sas.maxVoices; i++) { - if (!sas.voices[i].playing) - PauseFlag |= 1 << i; - } - DEBUG_LOG(HLE,"%08x=sceSasGetPauseFlag()", PauseFlag); - return PauseFlag; -} - u32 sceSasRevEVOL(u32 core, int lv, int rv) { DEBUG_LOG(HLE,"0=sceSasRevEVOL(core=%08x, leftVolume=%i, rightVolume=%i)", core, lv, rv); - sas.waveformEffect.leftVol = lv; sas.waveformEffect.rightVol = rv; return 0; @@ -309,7 +326,6 @@ u32 sceSasRevEVOL(u32 core, int lv, int rv) u32 sceSasRevVON(u32 core, int dry, int wet) { DEBUG_LOG(HLE,"0=sceSasRevVON(core=%08x, dry=%i, wet=%i)", core, dry, wet); - sas.waveformEffect.isDryOn = (dry > 0); sas.waveformEffect.isWetOn = (wet > 0); return 0; @@ -345,9 +361,10 @@ u32 sceSasSetVoicePCM(u32 core, int voiceNum, u32 pcmAddr, int size, int loop) { DEBUG_LOG(HLE,"0=sceSasSetVoicePCM(core=%08x, voicenum=%i, pcmAddr=%08x, size=%i, loop=%i)",core, voiceNum, pcmAddr, size, loop); SasVoice &v = sas.voices[voiceNum]; + v.type = VOICETYPE_PCM; v.pcmAddr = pcmAddr; v.pcmSize = size; - v.loop = loop; + v.loop = loop ? true : false; v.playing = true; return 0; } @@ -357,22 +374,34 @@ u32 sceSasGetAllEnvelopeHeights(u32 core, u32 heightsAddr) DEBUG_LOG(HLE,"0=sceSasGetAllEnvelopeHeights(core=%08x, heightsAddr=%i)", core, heightsAddr); if (Memory::IsValidAddress(heightsAddr)) { for (int i = 0; i < sas.length; i++) { - int voiceHeight = sas.voices[i].envelope.height; + int voiceHeight = sas.voices[i].envelope.GetHeight(); Memory::Write_U32(voiceHeight, heightsAddr + i * 4); } } return 0; } +u32 sceSasSetTriangularWave(u32 sasCore, int voice, int unknown) +{ + ERROR_LOG(HLE,"UNIMPL 0=sceSasSetTriangularWave(core=%08x, voice=%i, unknown=%i)", sasCore, voice, unknown); + return 0; +} + +u32 sceSasSetSteepWave(u32 sasCore, int voice, int unknown) +{ + ERROR_LOG(HLE,"UNIMPL 0=sceSasSetSteepWave(core=%08x, voice=%i, unknown=%i)", sasCore, voice, unknown); + return 0; +} + const HLEFunction sceSasCore[] = { {0x42778a9f, WrapU_UUUUU, "__sceSasInit"}, // (SceUID * sasCore, int grain, int maxVoices, int outputMode, int sampleRate) {0xa3589d81, WrapU_UU<_sceSasCore>, "__sceSasCore"}, {0x50a14dfc, WrapU_UU<_sceSasCoreWithMix>, "__sceSasCoreWithMix"}, // Process and mix into buffer (int sasCore, int sasInOut, int leftVolume, int rightVolume) - {0x68a46b95, WrapU_U, "__sceSasGetEndFlag"}, // int sasCore + {0x68a46b95, WrapU_U, "__sceSasGetEndFlag"}, {0x440ca7d8, WrapU_UIIIII, "__sceSasSetVolume"}, {0xad84d37f, WrapU_UII, "__sceSasSetPitch"}, - {0x99944089, WrapU_UIUII, "__sceSasSetVoice"}, // (int sasCore, int voice, int vagAddr, int size, int loopmode) + {0x99944089, WrapU_UIUII, "__sceSasSetVoice"}, {0xb7660a23, WrapU_UII, "__sceSasSetNoise"}, {0x019b25eb, WrapU_UIIIIII, "__sceSasSetADSR"}, {0x9ec3676a, WrapU_UIIIIII, "__sceSasSetADSRmode"}, @@ -380,20 +409,20 @@ const HLEFunction sceSasCore[] = {0x74ae582a, WrapU_UU, "__sceSasGetEnvelopeHeight"}, {0xcbcd4f79, WrapU_UUUU, "__sceSasSetSimpleADSR"}, {0xa0cf2fa4, WrapU_UI, "__sceSasSetKeyOff"}, - {0x76f01aca, WrapU_UI, "__sceSasSetKeyOn"}, // (int sasCore, int voice) - {0xf983b186, WrapU_UII, "__sceSasRevVON"}, // int sasCore, int dry, int wet - {0xd5a229c9, WrapU_UII, "__sceSasRevEVOL"}, // (int sasCore, int leftVol, int rightVol) // effect volume - {0x33d4ab37, WrapU_UI, "__sceSasRevType"}, // (int sasCore, int type) - {0x267a6dd2, WrapU_UII, "__sceSasRevParam"}, // (int sasCore, int delay, int feedback) - {0x2c8e6ab3, WrapU_U, "__sceSasGetPauseFlag"}, // int sasCore + {0x76f01aca, WrapU_UI, "__sceSasSetKeyOn"}, + {0xf983b186, WrapU_UII, "__sceSasRevVON"}, + {0xd5a229c9, WrapU_UII, "__sceSasRevEVOL"}, // effect volume + {0x33d4ab37, WrapU_UI, "__sceSasRevType"}, + {0x267a6dd2, WrapU_UII, "__sceSasRevParam"}, + {0x2c8e6ab3, WrapU_U, "__sceSasGetPauseFlag"}, {0x787d04d5, WrapU_UII, "__sceSasSetPause"}, - {0xa232cbe6, 0, "__sceSasSetTriangularWave"}, // (int sasCore, int voice, int unknown) - {0xd5ebbbcd, 0, "__sceSasSetSteepWave"}, // (int sasCore, int voice, int unknown) // square wave? + {0xa232cbe6, WrapU_UII, "__sceSasSetTriangularWave"}, + {0xd5ebbbcd, WrapU_UII, "__sceSasSetSteepWave"}, {0xBD11B7C2, WrapU_U, "__sceSasGetGrain"}, {0xd1e0a01e, WrapU_UI, "__sceSasSetGrain"}, {0xe175ef66, WrapU_U, "__sceSasGetOutputmode"}, {0xe855bf76, WrapU_UU, "__sceSasSetOutputmode"}, - {0x07f58c24, WrapU_UU, "__sceSasGetAllEnvelopeHeights"}, // (int sasCore, int heightAddr) 32-bit heights, 0-0x40000000 + {0x07f58c24, WrapU_UU, "__sceSasGetAllEnvelopeHeights"}, {0xE1CD9561, WrapU_UIUII, "__sceSasSetVoicePCM"}, }; diff --git a/Core/HW/SasAudio.cpp b/Core/HW/SasAudio.cpp index b151373e18..6ee4dc95cc 100644 --- a/Core/HW/SasAudio.cpp +++ b/Core/HW/SasAudio.cpp @@ -27,15 +27,21 @@ static const double f[5][2] = { 98.0 / 64.0, -55.0 / 64.0 }, { 122.0 / 64.0, -60.0 / 64.0 } }; -void VagDecoder::Start(u8 *data) +void VagDecoder::Start(u8 *data, bool loopEnabled) { + loopEnabled_ = loopEnabled; + loopAtNextBlock_ = false; + loopStartBlock_ = 0; + end_ = false; data_ = data; + read_ = data; curSample = 28; + curBlock_ = -1; s_1 = 0.0; // per block? s_2 = 0.0; } -bool VagDecoder::Decode() +bool VagDecoder::DecodeBlock() { int predict_nr = GetByte(); int shift_factor = predict_nr & 0xf; @@ -46,6 +52,12 @@ bool VagDecoder::Decode() end_ = true; return false; } + else if (flags == 6) { + loopStartBlock_ = curBlock_; + } + else if (flags == 3 && loopEnabled_) { + loopAtNextBlock_ = true; + } for (int i = 0; i < 28; i += 2) { int d = GetByte(); @@ -65,9 +77,29 @@ bool VagDecoder::Decode() s_1 = samples[i]; } curSample = 0; + curBlock_++; return true; } +void VagDecoder::GetSamples(s16 *outSamples, int numSamples) { + if (end_) { + memset(outSamples, 0, numSamples * sizeof(int)); + return; + } + for (int i = 0; i < numSamples; i++) { + if (curSample == 28) { + if (loopAtNextBlock_) { + read_ = data_ + 16 * loopStartBlock_; + curBlock_ = loopStartBlock_; + s_1 = 0.0; + s_2 = 0.0; + } + DecodeBlock(); + } + outSamples[i] = end_ ? 0 : samples[curSample++]; + } +} + // http://code.google.com/p/jpcsp/source/browse/trunk/src/jpcsp/HLE/modules150/sceSasCore.java int simpleRate(int n) { @@ -105,7 +137,7 @@ static int getSustainType(int bitfield2) { case 4: return PSP_SAS_ADSR_CURVE_MODE_LINEAR_BENT; case 6: return PSP_SAS_ADSR_CURVE_MODE_EXPONENT_DECREASE; } - ERROR_LOG(HLE,"sasSetSimpleADSR,ERROR_SAS_INVALID_ADSR_CURVE_MODE"); + ERROR_LOG(SAS,"sasSetSimpleADSR,ERROR_SAS_INVALID_ADSR_CURVE_MODE"); return 0; } @@ -128,8 +160,7 @@ static int getSustainLevel(int bitfield1) { return ((bitfield1 & 0x000F) + 1) << 26; } -void ADSREnvelope::SetSimpleEnvelope(u32 ADSREnv1, u32 ADSREnv2) -{ +void ADSREnvelope::SetSimpleEnvelope(u32 ADSREnv1, u32 ADSREnv2) { attackRate = getAttackRate(ADSREnv1); attackType = getAttackType(ADSREnv1); decayRate = getDecayRate(ADSREnv1); @@ -141,12 +172,40 @@ void ADSREnvelope::SetSimpleEnvelope(u32 ADSREnv1, u32 ADSREnv2) sustainLevel = getSustainLevel(ADSREnv1); } -void SasInstance::Mix(u32 outAddr) -{ +SasInstance::SasInstance() : mixBuffer(0), sendBuffer(0), resampleBuffer(0), grainSize(0) { +} + +SasInstance::~SasInstance() { + delete [] mixBuffer; + delete [] sendBuffer; + delete [] resampleBuffer; +} + +void SasInstance::SetGrainSize(int newGrainSize) { + if (mixBuffer) + delete [] mixBuffer; + if (sendBuffer) + delete [] sendBuffer; + mixBuffer = new s32[newGrainSize * 2]; + sendBuffer = new s32[newGrainSize * 2]; + memset(mixBuffer, 0, sizeof(int) * newGrainSize * 2); + memset(sendBuffer, 0, sizeof(int) * newGrainSize * 2); + grainSize = newGrainSize; + if (resampleBuffer) + delete [] resampleBuffer; + + // 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. + resampleBuffer = new s16[grainSize * 4 + 2]; +} + +void SasInstance::Mix(u32 outAddr) { + int voicesPlayingCount = 0; for (int v = 0; v < PSP_SAS_VOICES_MAX; v++) { SasVoice &voice = voices[v]; - if (!voice.playing) + if (!voice.playing || voice.paused) continue; + voicesPlayingCount++; // TODO: Special case no-resample case for speed @@ -161,37 +220,44 @@ void SasInstance::Mix(u32 outAddr) int numSamples = lastSample - curSample; // Read N samples into the resample buffer. Could do either PCM or VAG here. - - for (int i = 0; i < numSamples; i++) { - int sample = voice.vag.GetSample(); - if (voice.samplePos >= voice.vagSize || voice.vag.End()) { - if (voice.loop) { - voice.Loop(); - } else { - voice.playing = false; - // TODO: clear rest of buffer - memset(resampleBuffer, 0, (numSamples - i) * sizeof(resampleBuffer[0])); - } - break; - } - resampleBuffer[i + 2] = sample; + voice.vag.GetSamples(resampleBuffer + 2, numSamples); + if (voice.vag.End()) { + // NOTICE_LOG(SAS, "Hit end of VAG audio"); + voice.playing = false; + voice.on = false; // ?? } // Save resample history - voice.resampleHist[0] = resampleBuffer[numSamples - 2]; - voice.resampleHist[1] = resampleBuffer[numSamples - 1]; + voice.resampleHist[0] = resampleBuffer[2 + numSamples - 2]; + voice.resampleHist[1] = resampleBuffer[2 + numSamples - 1]; // Resample to the correct pitch, writing exactly "grainSize" samples. - - int bufferPos = (voice.samplePos & (PSP_SAS_PITCH_BASE - 1)) + 2 * PSP_SAS_PITCH_BASE; + u32 bufferPos = (voice.samplePos & (PSP_SAS_PITCH_BASE - 1)) + 2 * PSP_SAS_PITCH_BASE; for (int i = 0; i < grainSize; i++) { // For now: nearest neighbour, not even using the resample history at all. int sample = resampleBuffer[bufferPos / PSP_SAS_PITCH_BASE]; bufferPos += voice.pitch; + // Reduce envelope to 15 bits, rounding down. + int envelopeValue = voice.envelope.GetHeight(); + envelopeValue = ((envelopeValue >> 15) + 1) >> 1; + + // We just scale by the envelope before we scale by volumes. + sample = sample * envelopeValue >> 15; + // 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 >> 15; mixBuffer[i * 2 + 1] += sample * voice.volumeRight >> 15; + sendBuffer[i * 2] += sample * voice.volumeLeftSend >> 15; + sendBuffer[i * 2 + 1] += sample * voice.volumeRightSend >> 15; + voice.envelope.Step(); + } + if (voice.envelope.HasEnded()) + { + // NOTICE_LOG(SAS, "Hit end of envelope"); + voice.playing = false; } } else if (voice.pcmAddr != 0) { @@ -202,27 +268,217 @@ void SasInstance::Mix(u32 outAddr) } } + //if (voicesPlayingCount) + // NOTICE_LOG(SAS, "Sas mixed %i voices", voicesPlayingCount); + // Okay, apply effects processing to the Send buffer alone here. + // Reverb, echo, what have you. + // TODO + // Alright, all voices mixed. Let's convert and clip, and at the same time, wipe mixBuffer for next time. Could also dither. - for (int i = 0; i < grainSize * 2; i++) { - s16 *out = (s16 *)Memory::GetPointer(outAddr); - int sample = mixBuffer[i]; - mixBuffer[i] = 0; - if (sample > 32767) out[i] = 32767; - else if (sample < -32768) out[i] = -32768; - else out[i] = sample; + for (int i = 0; i < grainSize; i++) { + int sampleL = mixBuffer[i * 2] + sendBuffer[i * 2]; + int sampleR = mixBuffer[i * 2 + 1] + sendBuffer[i * 2 + 1]; + mixBuffer[i * 2] = 0; + mixBuffer[i * 2 + 1] = 0; + s16 outL, outR; + if (sampleL > 32767) outL = 32767; + else if (sampleL < -32768) outL = -32768; + else outL = sampleL; + if (sampleR > 32767) outR = 32767; + else if (sampleR < -32768) outR = -32768; + else outR = sampleR; + Memory::WriteUnchecked_U16(outL, outAddr + i * 2 * 2); + Memory::WriteUnchecked_U16(outR, outAddr + i * 2 * 2 + 2); } } -void SasVoice::Reset() -{ +void SasVoice::Reset() { resampleHist[0] = 0; resampleHist[1] = 0; } -void SasVoice::Loop() -{ - if (vagAddr) { - vag.Start(Memory::GetPointer(vagAddr)); - samplePos = 0; +void SasVoice::KeyOn() { + on = true; + playing = true; + paused = false; + envelope.KeyOn(); + switch (type) { + case VOICETYPE_VAG: + vag.Start(Memory::GetPointer(vagAddr), loop); + break; + default: + break; } +} + +void SasVoice::KeyOff() { + on = false; + envelope.KeyOff(); +} + +void SasVoice::ChangedParams() { + if (!playing && on) { + playing = true; + } + // TODO: restart VAG somehow +} + +// This is horribly stolen from JPCSP. +// Need to find a real solution. +static const short expCurve[] = { + 0x0000, 0x0380, 0x06E4, 0x0A2D, 0x0D5B, 0x1072, 0x136F, 0x1653, + 0x1921, 0x1BD9, 0x1E7B, 0x2106, 0x237F, 0x25E4, 0x2835, 0x2A73, + 0x2CA0, 0x2EBB, 0x30C6, 0x32C0, 0x34AB, 0x3686, 0x3852, 0x3A10, + 0x3BC0, 0x3D63, 0x3EF7, 0x4081, 0x41FC, 0x436E, 0x44D3, 0x462B, + 0x477B, 0x48BF, 0x49FA, 0x4B2B, 0x4C51, 0x4D70, 0x4E84, 0x4F90, + 0x5095, 0x5191, 0x5284, 0x5370, 0x5455, 0x5534, 0x5609, 0x56D9, + 0x57A3, 0x5867, 0x5924, 0x59DB, 0x5A8C, 0x5B39, 0x5BE0, 0x5C81, + 0x5D1C, 0x5DB5, 0x5E48, 0x5ED5, 0x5F60, 0x5FE5, 0x6066, 0x60E2, + 0x615D, 0x61D2, 0x6244, 0x62B2, 0x631D, 0x6384, 0x63E8, 0x644A, + 0x64A8, 0x6503, 0x655B, 0x65B1, 0x6605, 0x6653, 0x66A2, 0x66ED, + 0x6737, 0x677D, 0x67C1, 0x6804, 0x6844, 0x6882, 0x68BF, 0x68F9, + 0x6932, 0x6969, 0x699D, 0x69D2, 0x6A03, 0x6A34, 0x6A63, 0x6A8F, + 0x6ABC, 0x6AE6, 0x6B0E, 0x6B37, 0x6B5D, 0x6B84, 0x6BA7, 0x6BCB, + 0x6BED, 0x6C0E, 0x6C2D, 0x6C4D, 0x6C6B, 0x6C88, 0x6CA4, 0x6CBF, + 0x6CD9, 0x6CF3, 0x6D0C, 0x6D24, 0x6D3B, 0x6D52, 0x6D68, 0x6D7D, + 0x6D91, 0x6DA6, 0x6DB9, 0x6DCA, 0x6DDE, 0x6DEF, 0x6DFF, 0x6E10, + 0x6E20, 0x6E30, 0x6E3E, 0x6E4C, 0x6E5A, 0x6E68, 0x6E76, 0x6E82, + 0x6E8E, 0x6E9B, 0x6EA5, 0x6EB1, 0x6EBC, 0x6EC6, 0x6ED1, 0x6EDB, + 0x6EE4, 0x6EED, 0x6EF6, 0x6EFE, 0x6F07, 0x6F10, 0x6F17, 0x6F20, + 0x6F27, 0x6F2E, 0x6F35, 0x6F3C, 0x6F43, 0x6F48, 0x6F4F, 0x6F54, + 0x6F5B, 0x6F60, 0x6F66, 0x6F6B, 0x6F70, 0x6F74, 0x6F79, 0x6F7E, + 0x6F82, 0x6F87, 0x6F8A, 0x6F90, 0x6F93, 0x6F97, 0x6F9A, 0x6F9E, + 0x6FA1, 0x6FA5, 0x6FA8, 0x6FAC, 0x6FAD, 0x6FB1, 0x6FB4, 0x6FB6, + 0x6FBA, 0x6FBB, 0x6FBF, 0x6FC1, 0x6FC4, 0x6FC6, 0x6FC8, 0x6FC9, + 0x6FCD, 0x6FCF, 0x6FD0, 0x6FD2, 0x6FD4, 0x6FD6, 0x6FD7, 0x6FD9, + 0x6FDB, 0x6FDD, 0x6FDE, 0x6FDE, 0x6FE0, 0x6FE2, 0x6FE4, 0x6FE5, + 0x6FE5, 0x6FE7, 0x6FE9, 0x6FE9, 0x6FEB, 0x6FEC, 0x6FEC, 0x6FEE, + 0x6FEE, 0x6FF0, 0x6FF0, 0x6FF2, 0x6FF2, 0x6FF3, 0x6FF3, 0x6FF5, + 0x6FF5, 0x6FF7, 0x6FF7, 0x6FF7, 0x6FF9, 0x6FF9, 0x6FF9, 0x6FFA, + 0x6FFA, 0x6FFA, 0x6FFC, 0x6FFC, 0x6FFC, 0x6FFE, 0x6FFE, 0x6FFE, + 0x7000 +}; + +static int durationFromRate(int rate) +{ + if (rate == 0) { + return PSP_SAS_ENVELOPE_FREQ_MAX; + } else { + // From experimental tests on a PSP: + // rate=0x7FFFFFFF => duration=0x10 + // rate=0x3FFFFFFF => duration=0x22 + // rate=0x1FFFFFFF => duration=0x44 + // rate=0x0FFFFFFF => duration=0x81 + // rate=0x07FFFFFF => duration=0xF1 + // rate=0x03FFFFFF => duration=0x1B9 + // + // The correct curve model is still unknown. + // We use the following approximation: + // duration = 0x7FFFFFFF / rate * 0x10 + return PSP_SAS_ENVELOPE_FREQ_MAX / rate * 0x10; + } +} + +const short expCurveReference = 0x7000; + +static int getExpCurveAt(int index, int duration) { + const short curveLength = sizeof(expCurve) / sizeof(short); + + float curveIndex = (index * curveLength) / (float) duration; + int curveIndex1 = (int) curveIndex; + int curveIndex2 = curveIndex1 + 1; + float curveIndexFraction = curveIndex - curveIndex1; + + if (curveIndex1 < 0) { + return expCurve[0]; + } else if (curveIndex2 >= curveLength || curveIndex2 < 0) { + return expCurve[curveLength - 1]; + } + + float sample = expCurve[curveIndex1] * (1.f - curveIndexFraction) + expCurve[curveIndex2] * curveIndexFraction; + return (short)(sample); +} + +void ADSREnvelope::WalkCurve(int rate, int type) { + short expFactor; + int duration; + switch (type) { + case PSP_SAS_ADSR_CURVE_MODE_LINEAR_INCREASE: + height_ += rate; + break; + + case PSP_SAS_ADSR_CURVE_MODE_LINEAR_DECREASE: + height_ -= rate; + break; + + case PSP_SAS_ADSR_CURVE_MODE_LINEAR_BENT: + if (height_ < (s64)PSP_SAS_ENVELOPE_HEIGHT_MAX * 3 / 4) { + height_ += rate; + } else { + height_ += rate / 4; + } + break; + + case PSP_SAS_ADSR_CURVE_MODE_EXPONENT_DECREASE: + // NOTICE_LOG(SAS, "UNIMPL EXP DECR"); + duration = durationFromRate(rate); + expFactor = getExpCurveAt(steps_, duration); + height_ = (s64)expFactor * PSP_SAS_ENVELOPE_HEIGHT_MAX / expCurveReference; + height_ = PSP_SAS_ENVELOPE_HEIGHT_MAX - height_; + break; + + case PSP_SAS_ADSR_CURVE_MODE_EXPONENT_INCREASE: + duration = durationFromRate(rate); + expFactor = getExpCurveAt(steps_, duration); + height_ = (s64)expFactor * PSP_SAS_ENVELOPE_HEIGHT_MAX / expCurveReference; + break; + + case PSP_SAS_ADSR_CURVE_MODE_DIRECT: + height_ = rate; // Simple :) + break; + } +} + +void ADSREnvelope::SetState(ADSRState state) { + steps_ = 0; + state_ = state; +} + +void ADSREnvelope::Step() { + switch (state_) { + case STATE_ATTACK: + WalkCurve(attackRate, attackType); + if (height_ > PSP_SAS_ENVELOPE_HEIGHT_MAX || height_ < 0) + SetState(STATE_DECAY); + break; + case STATE_DECAY: + WalkCurve(decayRate, decayType); + if (height_ > PSP_SAS_ENVELOPE_HEIGHT_MAX || height_ < sustainLevel) + SetState(STATE_SUSTAIN); + break; + case STATE_SUSTAIN: + WalkCurve(sustainRate, sustainType); + break; + case STATE_RELEASE: + WalkCurve(releaseRate, releaseType); + if (height_ <= 0) { + height_ = 0; + SetState(STATE_OFF); + } + break; + case STATE_OFF: + // Do nothing + break; + } + steps_++; +} + +void ADSREnvelope::KeyOn() { + SetState(STATE_ATTACK); + height_ = 0; +} + +void ADSREnvelope::KeyOff() { + SetState(STATE_RELEASE); + height_ = sustainLevel; } \ No newline at end of file diff --git a/Core/HW/SasAudio.h b/Core/HW/SasAudio.h index 7b60a98e81..4478652cf8 100644 --- a/Core/HW/SasAudio.h +++ b/Core/HW/SasAudio.h @@ -44,6 +44,9 @@ enum { PSP_SAS_ADSR_DECAY = 2, PSP_SAS_ADSR_SUSTAIN = 4, PSP_SAS_ADSR_RELEASE = 8, + + PSP_SAS_ENVELOPE_HEIGHT_MAX = 0x40000000, + PSP_SAS_ENVELOPE_FREQ_MAX = 0x7FFFFFFF, }; struct WaveformEffect @@ -62,6 +65,8 @@ enum VoiceType { VOICETYPE_PCM = 1, VOICETYPE_NOISE = 2, VOICETYPE_ATRAC3 = 3, + VOICETYPE_TRIWAVE = 4, // are these used? there are functions for them (sceSetTriangularWave) + VOICETYPE_PULSEWAVE = 5, }; // VAG is a Sony ADPCM audio compression format, which goes all the way back to the PSX. @@ -71,24 +76,15 @@ enum VoiceType { class VagDecoder { public: - void Start(u8 *data); + void Start(u8 *data, bool loopEnabled); - int GetSample() - { - if (end_) - return 0; - if (curSample == 28) - Decode(); - if (end_) - return 0; - return samples[curSample++]; - } + void GetSamples(s16 *outSamples, int numSamples); - bool Decode(); + bool DecodeBlock(); bool End() const { return end_; } u8 GetByte() { - return *data_++; + return *read_++; } private: @@ -96,21 +92,38 @@ private: int curSample; u8 *data_; + u8 *read_; + int curBlock_; + int loopStartBlock_; // rolling state. start at 0, should probably reset to 0 on loops? double s_1; double s_2; + bool loopEnabled_; + bool loopAtNextBlock_; bool end_; }; - // Max height: 0x40000000 I think class ADSREnvelope { public: void SetSimpleEnvelope(u32 ADSREnv1, u32 ADSREnv2); + void WalkCurve(int rate, int type); + + void KeyOn(); + void KeyOff(); + + void Step(); + + int GetHeight() const { + return height_ > PSP_SAS_ENVELOPE_HEIGHT_MAX ? PSP_SAS_ENVELOPE_HEIGHT_MAX : height_; + } + bool HasEnded() const { + return state_ == STATE_OFF; + } int attackRate; int decayRate; int sustainRate; @@ -120,7 +133,20 @@ public: int sustainType; int sustainLevel; int releaseType; - int height; + +private: + enum ADSRState { + STATE_ATTACK, + STATE_DECAY, + STATE_SUSTAIN, + STATE_RELEASE, + STATE_OFF, + }; + void SetState(ADSRState state); + + ADSRState state_; + int steps_; + s64 height_; // s64 to avoid having to care about overflow when calculatimg. TODO: this should be fine as s32 }; // A SAS voice. @@ -129,6 +155,9 @@ public: struct SasVoice { bool playing; + bool paused; // a voice can be playing AND paused. In that case, it won't play. + bool on; // key-on, key-off. + VoiceType type; u32 vagAddr; @@ -136,9 +165,10 @@ struct SasVoice u32 pcmAddr; int pcmSize; int sampleRate; + int samplePos; int pitch; - int loop; + bool loop; int noiseFreq; @@ -147,11 +177,13 @@ struct SasVoice int volumeLeftSend; // volume to "Send" (audio-lingo) to the effects processing engine, like reverb int volumeRightSend; - int setPaused; - - void Loop(); void Reset(); + void KeyOn(); + void KeyOff(); + + void ChangedParams(); + s16 resampleHist[2]; ADSREnvelope envelope; @@ -162,28 +194,10 @@ struct SasVoice class SasInstance { public: - SasInstance() : mixBuffer(0), resampleBuffer(0), grainSize(0) {} - ~SasInstance() { - delete [] mixBuffer; - delete [] resampleBuffer; - } - SasVoice voices[PSP_SAS_VOICES_MAX]; - WaveformEffect waveformEffect; - - void SetGrainSize(int newGrainSize) { - if (mixBuffer) - delete [] mixBuffer; - mixBuffer = new s32[newGrainSize * 2]; - memset(mixBuffer, 0, sizeof(int) * newGrainSize * 2); - grainSize = newGrainSize; - if (resampleBuffer) - delete [] resampleBuffer; - - // 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. - resampleBuffer = new s16[grainSize * 4 + 2]; - } + SasInstance(); + ~SasInstance(); + void SetGrainSize(int newGrainSize); int GetGrainSize() const { return grainSize; } int maxVoices; @@ -191,11 +205,15 @@ public: int outputMode; int length; - s16 *resampleBuffer; int *mixBuffer; + int *sendBuffer; + s16 *resampleBuffer; void Mix(u32 outAddr); + SasVoice voices[PSP_SAS_VOICES_MAX]; + WaveformEffect waveformEffect; + private: int grainSize; };