diff --git a/Source/AliveLibAE/Sound/Midi.cpp b/Source/AliveLibAE/Sound/Midi.cpp index 7c209c81a..cd0854220 100644 --- a/Source/AliveLibAE/Sound/Midi.cpp +++ b/Source/AliveLibAE/Sound/Midi.cpp @@ -19,7 +19,7 @@ #include "PsxSpuApi.hpp" #include "AmbientSound.hpp" - +#if !AUDIO_SPU_EMULATION EXPORT void CC SFX_SetPitch_4CA510(const SfxDefinition* pSfx, s32 channelsBits, s16 pitch); const s32 kSeqTableSizeAE = 144; @@ -867,3 +867,229 @@ EXPORT void CC SND_Restart_4CB0E0() // Next -> SND_Init_Ambiance_4CB480, SND_Reset_Ambiance_4CB4B0, Start_Sounds_for_TLV_4CB530, Start_Slig_sounds_4CB980 // Stop_slig_sounds_4CBA70 Path::Start_Sounds_For_Objects_In_Camera_4CBAF0, Start_Sounds_For_Objects_In_Near_Cameras_4CBB60 +#else + +#include "../../AliveLibCommon/audio/MidiPlayer.hpp" + +class AEResourceProvider : public psx::ResourceProvider +{ +public: + psx::ResourceData* readFile(char_type* name) + { + LvlFileRecord* fileRecord = sLvlArchive_5BC520.Find_File_Record_433160(name); + s32 size = fileRecord->field_10_num_sectors << 11; + u8* data = new u8[size]; + sLvlArchive_5BC520.Read_File_4330A0(fileRecord, data); + + psx::ResourceData* resource = new psx::ResourceData(); + resource->data = data; + resource->size = size; + return resource; + } + + psx::ResourceData* readSeq(const char_type* fileName, const char_type* sequenceName) + { + psx::ResourceData* resource = new psx::ResourceData(); + resource->data = 0; + resource->size = 0; + + if (!sequenceName || sequenceName == nullptr) + { + return resource; + } + + // TODO - maybe this load should be stored in memory for faster access? + // Need to check underlying implementation + if (mLoadedFile != fileName) + { + mLoadedFile = fileName; + ResourceManager::LoadResourceFile_49C170(fileName, nullptr); + } + + s32 hash = ResourceManager::SEQ_HashName_49BE30(sequenceName); + u8** ppSeq = ResourceManager::GetLoadedResource_49C2A0(ResourceManager::Resource_Seq, hash, 1, 1); + if (!ppSeq) + { + return resource; + } + + u32 size = ResourceManager::Get_Header_49C410(ppSeq)->field_0_size; + u8* data = new u8[size]; + memcpy(data, *ppSeq, size); + resource->data = data; + resource->size = size; + ResourceManager::FreeResource_49C330(ppSeq); + return resource; + } + + s32 sequenceCount() + { + return 144; + } + +private: + const char_type* mLoadedFile = NULL; +}; + +/* + AE is different than AO. AE reads sample data from a separate + sounds.dat file whereas AO reads it inline of the vab body. +*/ +class AESoundSampleParser : public psx::SoundSampleParser +{ +public: + std::vector parseSamples(psx::VabHeader* vabHeader, u8* ppVabBody) + { + std::vector samples; + std::ifstream soundDatFile; + soundDatFile.open("sounds.dat", std::ios::binary); + if (!soundDatFile.is_open()) + { + throw std::invalid_argument("Could not find sounds.dat"); + } + soundDatFile.seekg(0, std::ios::end); + std::streamsize fileSize = soundDatFile.tellg(); + soundDatFile.seekg(0, std::ios::beg); + char* soundData = new char[(s32) fileSize + 1]; // Plus one, just in case interpolating tries to go that one byte further! + + if (!soundDatFile.read(soundData, fileSize)) + { + throw std::invalid_argument("Could not read sounds.dat"); + } + + int pos = 0; + for (s32 i = 0; i < vabHeader->field_16_num_vags; i++) + { + u32 size = *reinterpret_cast(&ppVabBody[pos]); + pos += sizeof(u32); + + u32 sampleRate = *reinterpret_cast(&ppVabBody[pos]); + pos += sizeof(u32); + + // In AO the body is at this part. + // IN AE instead it's an offset in sounds.dat + u32 offset = *reinterpret_cast(&ppVabBody[pos]); + pos += sizeof(u32); + + // Read data from sounds.dat + u8* data = new u8[size]; + memcpy(data, &soundData[offset], size); + + psx::Sample* sample = new psx::Sample(); + sample->m_SampleBuffer = reinterpret_cast(data); + sample->i_SampleSize = size / 2; + sample->sampleRate = 8000; // non standard? Doesn't use sampleRate field? + sample->loop = sampleRate > 44100; // non-standard? + samples.push_back(sample); + } + + soundDatFile.close(); + delete[] soundData; + + return samples; + } + + void applyFix(char_type* headerName, s32 vag, s32 vagOffset, SPU::Sample* sample) + { + // TODO - need to fix AE vag attrs similar to what is down for AO + // The below lines are to get around compiler warnings of unused parameters + headerName; + vag; + vagOffset; + sample; + } +}; + +psx::MidiPlayer* player = new psx::MidiPlayer(new AEResourceProvider(), new AESoundSampleParser()); + +EXPORT void CC SND_StopAll_4CB060() +{ + MusicController::EnableMusic_47FE10(FALSE); + BackgroundMusic::Stop_4CB000(); + player->SND_StopAll(); +} + +EXPORT void CC SND_Init_4CA1F0() +{ + player->SND_Init(); +} + +EXPORT void CC SND_Shutdown_4CA280() +{ + player->SND_Shutdown(); +} + +EXPORT void CC SND_Stop_Channels_Mask_4CA810(u32 bitMask) +{ + player->SND_Stop_Channels_Mask(bitMask); +} + +EXPORT void SND_Reset_4C9FB0() +{ + player->SND_Reset(); +} + +EXPORT void CC SND_Load_VABS_4CA350(SoundBlockInfo* pSoundBlockInfo, s32 reverb) +{ + player->SND_Load_VABS(reinterpret_cast(pSoundBlockInfo), reverb); +} + +EXPORT s32 CC SND_4CA5D0(s32 program, s32 vabId, s32 note, s16 vol, s16 min, s16 max) +{ + return player->SND(program, vabId, note, vol, min, max); +} + +EXPORT void CC SND_Restart_4CB0E0() +{ + MusicController::EnableMusic_47FE10(TRUE); + BackgroundMusic::Play_4CB030(); + Start_Sounds_For_Objects_In_Near_Cameras_4CBB60(); + player->SND_Restart(); +} + +EXPORT void CC SND_Load_Seqs_4CAED0(OpenSeqHandle* pSeqTable, const char_type* bsqFileName) +{ + player->SND_Load_Seqs(reinterpret_cast(pSeqTable), bsqFileName); +} + +EXPORT void CC SND_SEQ_Stop_4CAE60(u16 idx) +{ + player->SND_SEQ_Stop(idx); +} + +EXPORT s8 CC SND_Seq_Table_Valid_4CAFE0() +{ + return player->SND_Seq_Table_Valid(); +} + +EXPORT s16 CC SND_SEQ_PlaySeq_4CA960(u16 idx, s16 repeatCount, s16 bDontStop) +{ + return player->SND_SEQ_PlaySeq(idx, repeatCount, bDontStop); +} + +EXPORT void CC SND_SEQ_SetVol_4CAD20(s32 idx, s16 volLeft, s16 volRight) +{ + player->SND_SEQ_SetVol(idx, volLeft, volRight); +} + +EXPORT s16 CC SND_SEQ_Play_4CAB10(u16 idx, s16 repeatCount, s16 volLeft, s16 volRight) +{ + return player->SND_SEQ_Play(idx, repeatCount, volLeft, volRight); +} + +EXPORT s32 CC SND_SsIsEos_DeInlined_4CACD0(u16 idx) +{ + return player->SND_SsIsEos_DeInlined(idx); +} + +EXPORT s32 CC SFX_SfxDefinition_Play_4CA700(const SfxDefinition* sfxDef, s16 volLeft, s16 volRight, s16 pitch_min, s16 pitch_max) +{ + return player->SFX_SfxDefinition_Play(reinterpret_cast(const_cast(sfxDef)), volLeft, volRight, pitch_min, pitch_max); +} + +EXPORT s32 CC SFX_SfxDefinition_Play_4CA420(const SfxDefinition* sfxDef, s16 volume, s16 pitch_min, s16 pitch_max) +{ + return player->SFX_SfxDefinition_Play(reinterpret_cast(const_cast(sfxDef)), volume, pitch_min, pitch_max); +} + +#endif diff --git a/Source/AliveLibAE/Sound/Midi.hpp b/Source/AliveLibAE/Sound/Midi.hpp index 7cf5eb358..d6b7a7248 100644 --- a/Source/AliveLibAE/Sound/Midi.hpp +++ b/Source/AliveLibAE/Sound/Midi.hpp @@ -39,9 +39,11 @@ class IMidiVars virtual s16 LoadResourceFile(const char_type* pFileName, Camera* pCamera) = 0; }; +#if !AUDIO_SPU_EMULATION EXPORT IMidiVars* GetMidiVars(); EXPORT void SetMidiApiVars(IMidiVars* pVars); + using TReclaimMemoryFn = void(CC*)(u32); using TLoadResourceFileFn = s16(CC*)(const s8*, Camera*); using TGetLoadedResourceFn = u8**(CC*) (u32, u32, u16, u16); @@ -58,6 +60,7 @@ EXPORT void SND_Restart_SetCallBack(TSNDRestart cb); EXPORT void CC SND_Load_Seqs_Impl(OpenSeqHandle* pSeqTable, const char_type* bsqFileName); EXPORT void SND_Stop_All_Seqs_4CA850(); +#endif EXPORT void CC SND_StopAll_4CB060(); EXPORT void CC SND_Init_4CA1F0(); @@ -75,6 +78,10 @@ EXPORT s32 CC SND_SsIsEos_DeInlined_4CACD0(u16 idx); EXPORT s32 CC SFX_SfxDefinition_Play_4CA700(const SfxDefinition* sfxDef, s16 volLeft, s16 volRight, s16 pitch_min, s16 pitch_max); EXPORT s32 CC SFX_SfxDefinition_Play_4CA420(const SfxDefinition* sfxDef, s16 volume, s16 pitch_min, s16 pitch_max); +// required additional? +EXPORT s32 CC SND_4CA5D0(s32 program, s32 vabId, s32 note, s16 vol, s16 min, s16 max); +EXPORT void CC SND_Restart_4CB0E0(); + enum SeqId { MainMenuAmbient_0 = 0, diff --git a/Source/AliveLibAE/Sound/PsxSpuApi.cpp b/Source/AliveLibAE/Sound/PsxSpuApi.cpp index 97ba7f71c..c2fc026f7 100644 --- a/Source/AliveLibAE/Sound/PsxSpuApi.cpp +++ b/Source/AliveLibAE/Sound/PsxSpuApi.cpp @@ -686,7 +686,9 @@ EXPORT s32 CC MIDI_PlayMidiNote_4FCB30(s32 vabId, s32 program, s32 note, s32 lef { MIDI_Wait_4FCE50(); } - + + // reuse a field that is never accessed for passing the samples pan + GetSpuApiVars()->sSoundEntryTable16().table[vabId][pVagIter->field_10_vag].field_1F = pVagIter->field_11_pad; GetSoundAPI().SND_PlayEx( &gSpuVars->sSoundEntryTable16().table[vabId][pVagIter->field_10_vag], panLeft, diff --git a/Source/AliveLibAE/Sound/Sound.cpp b/Source/AliveLibAE/Sound/Sound.cpp index 647fb2d6a..0cebcdaf0 100644 --- a/Source/AliveLibAE/Sound/Sound.cpp +++ b/Source/AliveLibAE/Sound/Sound.cpp @@ -228,8 +228,39 @@ EXPORT s32 CC SND_PlayEx_4EF740(const SoundEntry* pSnd, s32 panLeft, s32 panRigh pDSoundBuffer->SetFrequency(freqHz); pDSoundBuffer->SetVolume(sVolumeTable_BBBD38[panRightConverted]); + // OLD PAN const s32 panConverted = (DSBPAN_RIGHT * (panLeft2 - panRight)) / 127; // From PSX pan range to DSound pan range pDSoundBuffer->SetPan(-panConverted); // Fix Inverted Stereo + // OLD PAN END + + // NEW PAN + // If the sample is panned we must use the pan of the sample. + // It seems all sfx are center and only music samples are panned. + s8 samplePan = pSnd->field_1F; + if (samplePan < 64) + { + panRight = ((s32) (panRight * (samplePan / 64.0))); + } + else if (samplePan > 64) + { + panLeft = ((s32) ((panLeft * (64 - (samplePan - 64))) / 64.0)); + } + + // convert the 0-64-127 pan value into a value + // that makes sense for SDL + s32 sdlPan = 0; + if (panRight < panLeft) + { + double p = (double) panRight / panLeft * DSBPAN_RIGHT; + sdlPan = -(DSBPAN_RIGHT - ((s32) p)); + } + else if (panRight > panLeft) + { + double p = (double) panLeft / panRight * DSBPAN_RIGHT; + sdlPan = DSBPAN_RIGHT - ((s32) p); + } + pDSoundBuffer->SetPan(sdlPan); + // NEW PAN END if (playFlags & DSBPLAY_LOOPING) { diff --git a/Source/AliveLibAO/Midi.cpp b/Source/AliveLibAO/Midi.cpp index e51a9eff2..6ebcec5f8 100644 --- a/Source/AliveLibAO/Midi.cpp +++ b/Source/AliveLibAO/Midi.cpp @@ -23,9 +23,8 @@ #include "../AliveLibAE/Sound/Sound.hpp" #include "../AliveLibAE/PathData.hpp" - +#if !AUDIO_SPU_EMULATION namespace AO { - const s32 kSeqTableSizeAO = 164; ALIVE_VAR(1, 0x9F12D8, SeqIds, sSeq_Ids_word_9F12D8, {}); @@ -507,7 +506,10 @@ EXPORT s32 CC MIDI_PlayerPlayMidiNote_49D730(s32 vabId, s32 program, s32 note, s auto v29 = pVagOff->field_A_shift_cen; pChannel->field_1C_adsr.field_2_note_byte1 = BYTE1(note) & 0x7F; auto freq = pow(1.059463094359, (f64)(note - v29) * 0.00390625); - pChannel->field_10_freq = (f32) freq; + pChannel->field_10_freq = (f32) freq; + + // reuse a field that is never accessed for passing the samples pan + GetSpuApiVars()->sSoundEntryTable16().table[vabId][vag_num].field_1F = pVagOff->field_11_pad; SND_PlayEx_493040( &GetSpuApiVars()->sSoundEntryTable16().table[vabId][vag_num], panLeft, @@ -1145,5 +1147,160 @@ EXPORT void CC SND_StopAll_4762D0() SsUtAllKeyOff_49EDE0(0); } +} +#else + +#include "../../AliveLibCommon/audio/MidiPlayer.hpp" + +namespace AO { + +class AOResourceProvider : public psx::ResourceProvider +{ +public: + psx::ResourceData* readFile(char_type* name) + { + LvlFileRecord* fileRecord = sLvlArchive_4FFD60.Find_File_Record_41BED0(name); + s32 size = fileRecord->field_10_num_sectors << 11; + u8* data = new u8[size]; + sLvlArchive_4FFD60.Read_File_41BE40(fileRecord, data); + + psx::ResourceData* resource = new psx::ResourceData(); + resource->data = data; + resource->size = size; + return resource; + } + + psx::ResourceData* readSeq(const char_type* fileName, const char_type* sequenceName) + { + psx::ResourceData* resource = new psx::ResourceData(); + resource->data = NULL; + resource->size = 0; + + // TODO - maybe this load should be stored in memory for faster access? + // Need to check underlying implementation + if (mLoadedFileName != fileName) + { + mLoadedFileName = fileName; + ResourceManager::LoadResourceFileWrapper(fileName, nullptr); + } + s32 hash = ResourceManager::SEQ_HashName_454EA0(sequenceName); + u8** ppSeq = ResourceManager::GetLoadedResource_4554F0(ResourceManager::Resource_Seq, hash, 1, 1); + if (!ppSeq) + { + return resource; + } + + u32 size = ResourceManager::Get_Header_455620(ppSeq)->field_0_size; + u8* data = new u8[size]; + memcpy(data, *ppSeq, size); + resource->data = data; + resource->size = size; + resource->optionalHash = hash; + ResourceManager::FreeResource_455550(ppSeq); + return resource; + } + + s32 sequenceCount() + { + return 164; + } + +private: + const char_type* mLoadedFileName = NULL; +}; + +static void SfxDefToPsxSfxDef(const SfxDefinition* src, psx::SfxDefinition* dst) +{ + dst->block_idx = s8(src->field_0_block_idx); + dst->note = s8(src->field_8_note); + dst->pitch_max = src->field_10_pitch_max; + dst->pitch_min = src->field_E_pitch_min; + dst->program = s8(src->field_4_program); + dst->volume = s8(src->field_C_default_volume); +} + +psx::MidiPlayer* midiPlayer = new psx::MidiPlayer(new AOResourceProvider()); + +EXPORT void CC SsUtAllKeyOff_49EDE0(s32 mode) +{ + midiPlayer->SsUtAllKeyOff(mode); +} + +EXPORT void CC SND_Reset_476BA0() +{ + midiPlayer->SND_Reset(); +} + +EXPORT void CC SND_Load_VABS_477040(SoundBlockInfo* pSoundBlockInfo, s32 reverb) +{ + midiPlayer->SND_Load_VABS(reinterpret_cast(pSoundBlockInfo), reverb); +} + +EXPORT void CC SND_Stop_Channels_Mask_4774A0(s32 mask) +{ + midiPlayer->SND_Stop_Channels_Mask(mask); +} + +EXPORT void CC SND_Load_Seqs_477AB0(OpenSeqHandleAE* pSeqTable, const char_type* bsqFileName) +{ + midiPlayer->SND_Load_Seqs(reinterpret_cast(pSeqTable), bsqFileName); +} + +EXPORT s16 CC SND_SEQ_PlaySeq_4775A0(SeqId idx, s32 repeatCount, s16 bDontStop) +{ + return midiPlayer->SND_SEQ_PlaySeq(s16(idx), repeatCount, bDontStop); +} + +EXPORT void CC SND_Seq_Stop_477A60(SeqId idx) +{ + midiPlayer->SND_SEQ_Stop(s16(idx)); +} + +EXPORT s16 CC SND_SEQ_Play_477760(SeqId idx, s32 repeatCount, s16 volLeft, s16 volRight) + +{ + return midiPlayer->SND_SEQ_Play(s16(idx), repeatCount, volLeft, volRight); +} + +EXPORT s16 CC SND_SsIsEos_DeInlined_477930(SeqId idx) +{ + return midiPlayer->SND_SsIsEos_DeInlined(s16(idx)); +} + +EXPORT s32 CC SFX_SfxDefinition_Play_477330(const SfxDefinition* sfxDef, s16 volLeft, s16 volRight, s16 pitch_min, s16 pitch_max) +{ + psx::SfxDefinition* def = new psx::SfxDefinition(); + SfxDefToPsxSfxDef(sfxDef, def); + return midiPlayer->SFX_SfxDefinition_Play(def, volLeft, volRight, pitch_min, pitch_max); +} + +EXPORT s32 CC SFX_SfxDefinition_Play_4770F0(const SfxDefinition* sfxDef, s32 vol, s32 pitch_min, s32 pitch_max) +{ + psx::SfxDefinition* def = new psx::SfxDefinition(); + SfxDefToPsxSfxDef(sfxDef, def); + return midiPlayer->SFX_SfxDefinition_Play(def, vol, pitch_min, pitch_max); +} + +EXPORT void CC SND_Init_476E40() +{ + midiPlayer->SND_Init(); +} + +EXPORT void CC SND_Shutdown_476EC0() +{ + midiPlayer->SND_Shutdown(); +} + +EXPORT void CC SND_SEQ_SetVol_477970(SeqId idx, s16 volLeft, s16 volRight) +{ + midiPlayer->SND_SEQ_SetVol(s16(idx), volLeft, volRight); +} + +EXPORT void CC SND_StopAll_4762D0() +{ + midiPlayer->SND_StopAll(); +} } // namespace AO +#endif + diff --git a/Source/AliveLibAO/ResourceManager.hpp b/Source/AliveLibAO/ResourceManager.hpp index 034261d67..9478122eb 100644 --- a/Source/AliveLibAO/ResourceManager.hpp +++ b/Source/AliveLibAO/ResourceManager.hpp @@ -46,6 +46,8 @@ class ResourceManager final Resource_End = 0x21646E45, Resource_Plbk = 0x6B626C50, Resource_Play = 0x79616C50, + Resource_Seq = 0x20716553, + Resource_SEQp = 0x53455170, }; enum ResourceHeaderFlags : s16 diff --git a/Source/AliveLibCommon/CMakeLists.txt b/Source/AliveLibCommon/CMakeLists.txt index ab6b278dc..bebed71c7 100644 --- a/Source/AliveLibCommon/CMakeLists.txt +++ b/Source/AliveLibCommon/CMakeLists.txt @@ -35,6 +35,17 @@ SET(AliveLibSrcCommon PSXMDECDecoder.h Psx_common.hpp W32CrashHandler.hpp + audio/Sequencer.hpp + audio/Sequencer.cpp + audio/Stream.hpp + audio/Stream.cpp + audio/MidiPlayer.hpp + audio/MidiPlayer.cpp + audio/oddio.h + audio/Interpolation.hpp + audio/Interpolation.cpp + audio/Reverb.hpp + audio/Reverb.cpp ) add_library(AliveLibCommon ${AliveLibSrcCommon}) diff --git a/Source/AliveLibCommon/audio/Interpolation.cpp b/Source/AliveLibCommon/audio/Interpolation.cpp new file mode 100644 index 000000000..7ac691d0c --- /dev/null +++ b/Source/AliveLibCommon/audio/Interpolation.cpp @@ -0,0 +1,347 @@ +#pragma once + +#include "Interpolation.hpp" +#include + +namespace SPU { + +u32 mask(u32 num) +{ + u32 res = 0; + while (num-- > 0) + { + res = (res << 1) | 1; + } + return res; +} + +/////////////////////// +// DUCKSTATION +static constexpr std::array gauss = {{ + -0x001, -0x001, -0x001, -0x001, -0x001, -0x001, -0x001, -0x001, // + -0x001, -0x001, -0x001, -0x001, -0x001, -0x001, -0x001, -0x001, // + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0001, // + 0x0001, 0x0001, 0x0001, 0x0002, 0x0002, 0x0002, 0x0003, 0x0003, // + 0x0003, 0x0004, 0x0004, 0x0005, 0x0005, 0x0006, 0x0007, 0x0007, // + 0x0008, 0x0009, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, // + 0x000F, 0x0010, 0x0011, 0x0012, 0x0013, 0x0015, 0x0016, 0x0018, // entry + 0x0019, 0x001B, 0x001C, 0x001E, 0x0020, 0x0021, 0x0023, 0x0025, // 000..07F + 0x0027, 0x0029, 0x002C, 0x002E, 0x0030, 0x0033, 0x0035, 0x0038, // + 0x003A, 0x003D, 0x0040, 0x0043, 0x0046, 0x0049, 0x004D, 0x0050, // + 0x0054, 0x0057, 0x005B, 0x005F, 0x0063, 0x0067, 0x006B, 0x006F, // + 0x0074, 0x0078, 0x007D, 0x0082, 0x0087, 0x008C, 0x0091, 0x0096, // + 0x009C, 0x00A1, 0x00A7, 0x00AD, 0x00B3, 0x00BA, 0x00C0, 0x00C7, // + 0x00CD, 0x00D4, 0x00DB, 0x00E3, 0x00EA, 0x00F2, 0x00FA, 0x0101, // + 0x010A, 0x0112, 0x011B, 0x0123, 0x012C, 0x0135, 0x013F, 0x0148, // + 0x0152, 0x015C, 0x0166, 0x0171, 0x017B, 0x0186, 0x0191, 0x019C, // + 0x01A8, 0x01B4, 0x01C0, 0x01CC, 0x01D9, 0x01E5, 0x01F2, 0x0200, // + 0x020D, 0x021B, 0x0229, 0x0237, 0x0246, 0x0255, 0x0264, 0x0273, // + 0x0283, 0x0293, 0x02A3, 0x02B4, 0x02C4, 0x02D6, 0x02E7, 0x02F9, // + 0x030B, 0x031D, 0x0330, 0x0343, 0x0356, 0x036A, 0x037E, 0x0392, // + 0x03A7, 0x03BC, 0x03D1, 0x03E7, 0x03FC, 0x0413, 0x042A, 0x0441, // + 0x0458, 0x0470, 0x0488, 0x04A0, 0x04B9, 0x04D2, 0x04EC, 0x0506, // + 0x0520, 0x053B, 0x0556, 0x0572, 0x058E, 0x05AA, 0x05C7, 0x05E4, // entry + 0x0601, 0x061F, 0x063E, 0x065C, 0x067C, 0x069B, 0x06BB, 0x06DC, // 080..0FF + 0x06FD, 0x071E, 0x0740, 0x0762, 0x0784, 0x07A7, 0x07CB, 0x07EF, // + 0x0813, 0x0838, 0x085D, 0x0883, 0x08A9, 0x08D0, 0x08F7, 0x091E, // + 0x0946, 0x096F, 0x0998, 0x09C1, 0x09EB, 0x0A16, 0x0A40, 0x0A6C, // + 0x0A98, 0x0AC4, 0x0AF1, 0x0B1E, 0x0B4C, 0x0B7A, 0x0BA9, 0x0BD8, // + 0x0C07, 0x0C38, 0x0C68, 0x0C99, 0x0CCB, 0x0CFD, 0x0D30, 0x0D63, // + 0x0D97, 0x0DCB, 0x0E00, 0x0E35, 0x0E6B, 0x0EA1, 0x0ED7, 0x0F0F, // + 0x0F46, 0x0F7F, 0x0FB7, 0x0FF1, 0x102A, 0x1065, 0x109F, 0x10DB, // + 0x1116, 0x1153, 0x118F, 0x11CD, 0x120B, 0x1249, 0x1288, 0x12C7, // + 0x1307, 0x1347, 0x1388, 0x13C9, 0x140B, 0x144D, 0x1490, 0x14D4, // + 0x1517, 0x155C, 0x15A0, 0x15E6, 0x162C, 0x1672, 0x16B9, 0x1700, // + 0x1747, 0x1790, 0x17D8, 0x1821, 0x186B, 0x18B5, 0x1900, 0x194B, // + 0x1996, 0x19E2, 0x1A2E, 0x1A7B, 0x1AC8, 0x1B16, 0x1B64, 0x1BB3, // + 0x1C02, 0x1C51, 0x1CA1, 0x1CF1, 0x1D42, 0x1D93, 0x1DE5, 0x1E37, // + 0x1E89, 0x1EDC, 0x1F2F, 0x1F82, 0x1FD6, 0x202A, 0x207F, 0x20D4, // + 0x2129, 0x217F, 0x21D5, 0x222C, 0x2282, 0x22DA, 0x2331, 0x2389, // entry + 0x23E1, 0x2439, 0x2492, 0x24EB, 0x2545, 0x259E, 0x25F8, 0x2653, // 100..17F + 0x26AD, 0x2708, 0x2763, 0x27BE, 0x281A, 0x2876, 0x28D2, 0x292E, // + 0x298B, 0x29E7, 0x2A44, 0x2AA1, 0x2AFF, 0x2B5C, 0x2BBA, 0x2C18, // + 0x2C76, 0x2CD4, 0x2D33, 0x2D91, 0x2DF0, 0x2E4F, 0x2EAE, 0x2F0D, // + 0x2F6C, 0x2FCC, 0x302B, 0x308B, 0x30EA, 0x314A, 0x31AA, 0x3209, // + 0x3269, 0x32C9, 0x3329, 0x3389, 0x33E9, 0x3449, 0x34A9, 0x3509, // + 0x3569, 0x35C9, 0x3629, 0x3689, 0x36E8, 0x3748, 0x37A8, 0x3807, // + 0x3867, 0x38C6, 0x3926, 0x3985, 0x39E4, 0x3A43, 0x3AA2, 0x3B00, // + 0x3B5F, 0x3BBD, 0x3C1B, 0x3C79, 0x3CD7, 0x3D35, 0x3D92, 0x3DEF, // + 0x3E4C, 0x3EA9, 0x3F05, 0x3F62, 0x3FBD, 0x4019, 0x4074, 0x40D0, // + 0x412A, 0x4185, 0x41DF, 0x4239, 0x4292, 0x42EB, 0x4344, 0x439C, // + 0x43F4, 0x444C, 0x44A3, 0x44FA, 0x4550, 0x45A6, 0x45FC, 0x4651, // + 0x46A6, 0x46FA, 0x474E, 0x47A1, 0x47F4, 0x4846, 0x4898, 0x48E9, // + 0x493A, 0x498A, 0x49D9, 0x4A29, 0x4A77, 0x4AC5, 0x4B13, 0x4B5F, // + 0x4BAC, 0x4BF7, 0x4C42, 0x4C8D, 0x4CD7, 0x4D20, 0x4D68, 0x4DB0, // + 0x4DF7, 0x4E3E, 0x4E84, 0x4EC9, 0x4F0E, 0x4F52, 0x4F95, 0x4FD7, // entry + 0x5019, 0x505A, 0x509A, 0x50DA, 0x5118, 0x5156, 0x5194, 0x51D0, // 180..1FF + 0x520C, 0x5247, 0x5281, 0x52BA, 0x52F3, 0x532A, 0x5361, 0x5397, // + 0x53CC, 0x5401, 0x5434, 0x5467, 0x5499, 0x54CA, 0x54FA, 0x5529, // + 0x5558, 0x5585, 0x55B2, 0x55DE, 0x5609, 0x5632, 0x565B, 0x5684, // + 0x56AB, 0x56D1, 0x56F6, 0x571B, 0x573E, 0x5761, 0x5782, 0x57A3, // + 0x57C3, 0x57E2, 0x57FF, 0x581C, 0x5838, 0x5853, 0x586D, 0x5886, // + 0x589E, 0x58B5, 0x58CB, 0x58E0, 0x58F4, 0x5907, 0x5919, 0x592A, // + 0x593A, 0x5949, 0x5958, 0x5965, 0x5971, 0x597C, 0x5986, 0x598F, // + 0x5997, 0x599E, 0x59A4, 0x59A9, 0x59AD, 0x59B0, 0x59B2, 0x59B3 // +}}; + +USHORT _svm_ptable[] = { + 4096, 4110, 4125, 4140, 4155, 4170, 4185, 4200, + 4216, 4231, 4246, 4261, 4277, 4292, 4308, 4323, + 4339, 4355, 4371, 4386, 4402, 4418, 4434, 4450, + 4466, 4482, 4499, 4515, 4531, 4548, 4564, 4581, + 4597, 4614, 4630, 4647, 4664, 4681, 4698, 4715, + 4732, 4749, 4766, 4783, 4801, 4818, 4835, 4853, + 4870, 4888, 4906, 4924, 4941, 4959, 4977, 4995, + 5013, 5031, 5050, 5068, 5086, 5105, 5123, 5142, + 5160, 5179, 5198, 5216, 5235, 5254, 5273, 5292, + 5311, 5331, 5350, 5369, 5389, 5408, 5428, 5447, + 5467, 5487, 5507, 5527, 5547, 5567, 5587, 5607, + 5627, 5648, 5668, 5688, 5709, 5730, 5750, 5771, + 5792, 5813, 5834, 5855, 5876, 5898, 5919, 5940, + 5962, 5983, 6005, 6027, 6049, 6070, 6092, 6114, + 6137, 6159, 6181, 6203, 6226, 6248, 6271, 6294, + 6316, 6339, 6362, 6385, 6408, 6431, 6455, 6478, + 6501, 6525, 6549, 6572, 6596, 6620, 6644, 6668, + 6692, 6716, 6741, 6765, 6789, 6814, 6839, 6863, + 6888, 6913, 6938, 6963, 6988, 7014, 7039, 7064, + 7090, 7116, 7141, 7167, 7193, 7219, 7245, 7271, + 7298, 7324, 7351, 7377, 7404, 7431, 7458, 7485, + 7512, 7539, 7566, 7593, 7621, 7648, 7676, 7704, + 7732, 7760, 7788, 7816, 7844, 7873, 7901, 7930, + 7958, 7987, 8016, 8045, 8074, 8103, 8133, 8162}; // 192 total (0-191) +// 8192}; This was originally here. Do we need? + +u32 VoiceCounterDuckstation::CalculateNoteStep(s32 root, s32 rootFine, u32 srcRate, u8 note, u32 pitch) +{ + // NOTE CALC FROM VgmTrans + // This code seems to be producing the same adpcm_sample_rate values + // as duckstation. Believe it or not, this code was found on pastebin + // by searching for "SsPitchFromNote" - I think this is from VGMTrans? + // https://pastebin.com/aq7wxDdr + + // NOTE: The original does not always produce correct pitches + // It creates values that go beyond _svm_ptable bounds; Examples + // 1. SecurityOrbs produce indexes >_svm_ptable.size (199, limit is 192) + // 2. Leaves coming out of wells produce indexes below 0 (-104, limit is 0) + + u32 pitchA; + s16 calc, type; + s32 add, sfine; + s32 fine = pitch; + sfine = fine + ((s32) rootFine); + if (sfine < 0) + { + sfine += 7; + } + sfine >>= 3; + + add = 0; + if (sfine > 15) + { + add = 1; + sfine -= 16; + } + + calc = (s16) (add + (note - (((s32) root) - 60))); //((center + 60) - note) + add; + int pos = (16 * (calc % 12) + sfine); + + // START: ADDED + // Security Orbs, Pause menu, and well leaves had the + // wrong pitch. The values produced went beyond the + // table range. Try to correct them. Seems to work ok... + // I just messed around with what made sense and sounded right. + if (pos >= 192) + { + pitchA = _svm_ptable[pos % 192] << ((pos / 192)); + } + else if (pos < 0) + { + pitchA = _svm_ptable[192 + pos] >> 1; + } + else + { + pitchA = _svm_ptable[pos]; + } + // END: Added + + type = calc / 12 - 5; + + // regular shift + s32 ret = pitchA; + if (type > 0) + { + ret = pitchA << type; + } + + // negative shift + if (type < 0) + { + ret = pitchA >> -type; + } + + // step seems to be calculated for 8000hz samples hence the division + // of the samples srcRate. Example, Abe's "Hello" is 22050hz instead of 8000hz. + return (u16) ((srcRate / 8000.0) * double(ret)); +} + +u32 VoiceCounterDuckstation::InterpolationIndex() +{ + return (bits >> 4) & mask(8); +} + +u32 VoiceCounterDuckstation::SampleIndex() +{ + return (bits >> 12) & mask(5); +} + +bool VoiceCounterDuckstation::NextSampleBlock(u32 step) +{ + step = std::min(step, 0x3FFF); + bits += step; + if (this->SampleIndex() >= NUM_SAMPLES_PER_ADPCM_BLOCK) + { + bits -= (NUM_SAMPLES_PER_ADPCM_BLOCK << 12); + return true; + } + + return false; +} + +f32 VoiceCounterDuckstation::Sample(s16* block) +{ + // the interpolation index is based on the source files sample rate + const u32 i = InterpolationIndex(); + const u32 s = SampleIndex(); + + // interpolate on the 4 most recent samples from current position + s32 out = 0; + out += s32(gauss[0x0FF - i]) * s32(block[(int) (s + NUM_SAMPLES_FROM_LAST_ADPCM_BLOCK - 3)]); + out += s32(gauss[0x1FF - i]) * s32(block[(int) (s + NUM_SAMPLES_FROM_LAST_ADPCM_BLOCK - 2)]); + out += s32(gauss[0x100 + i]) * s32(block[(int) (s + NUM_SAMPLES_FROM_LAST_ADPCM_BLOCK - 1)]); + out += s32(gauss[0x000 + i]) * s32(block[(int) (s + NUM_SAMPLES_FROM_LAST_ADPCM_BLOCK - 0)]); + out = out >> 15; + + // vounter.bits has two purposes + // 1. change how samples are skipped to change the samples note + // 2. maintain gauss table positioning for correct interpolation + return (f32) out; +} + +void VoiceCounterDuckstation::Reset() +{ + bits = 0; +} + + +/////////////////////// +// Illeprih's magic + +// Interpolation window is centered and first 3 samples are from the previous block, +// so the first block has to start at index 2. +static const u32 IlleprihFinePitchResolution = 128; +static const s32 IlleprihSampleRateCount = IlleprihFinePitchResolution * 12; + +static const u32 IlleprihInterpolationBitDept = 9; +static const u32 IlleprihInterpolationWindowLen = 1 << IlleprihInterpolationBitDept; + +static const u32 IlleprihSampleRateBitDepth = 26; +static const u32 IlleprihBaseSampleRate = 1 << IlleprihSampleRateBitDepth; + +static const u32 IlleprihStartOffset = 2 << IlleprihInterpolationBitDept; +static const u32 IlleprihInterpolationAnd = (1 << IlleprihInterpolationBitDept) - 1; +static const u32 IlleprihInterpolationShift = IlleprihSampleRateBitDepth - IlleprihInterpolationBitDept; + +// interpolation weights +constexpr std::array, 2> GenerateInterpolationWeights() +{ + std::array, 2> weights = {}; + for (size_t i = 0; i < IlleprihInterpolationWindowLen + 1; i++) + { + double pow1 = i / (double) IlleprihInterpolationWindowLen; + double pow2 = pow1 * pow1; + double pow3 = pow2 * pow1; + + weights[0][i] = (f32) (0.5f * (-pow3 + 2 * pow2 - pow1)); + weights[1][i] = (f32) (0.5f * (3 * pow3 - 5 * pow2 + 2)); + } + return weights; +} +std::array, 2> interpWeights = GenerateInterpolationWeights(); + +// sample rate +constexpr std::array GenerateSampleRates() +{ + std::array sampleRates = {}; + for (size_t i = 0; i < IlleprihSampleRateCount; i++) + { + sampleRates[i] = (u32) std::round(IlleprihBaseSampleRate * std::pow(2, i / (double) IlleprihSampleRateCount)); + } + return sampleRates; +} +const std::array SampleRates = GenerateSampleRates(); + +u32 VoiceCounterIlleprih::CalculateNoteStep(s32 root, s32 rootFine, u32 srcRate, u8 note, u32 pitch) +{ + // TODO - this needs to be used somewhere. PC version has some non 8000hz samples + + f64 fix = (std::log(srcRate / 8000.0f) / std::log(2)) * 12; + f64 fixRoot = (u32) fix; + u32 fixPitch = (u32) ((std::modf(fix, &fixRoot)) * IlleprihFinePitchResolution); + + s32 fineOffset = (note - root + ((s32) fixRoot)) * IlleprihFinePitchResolution + pitch + (rootFine + fixPitch - 64) * 2; + + u32 octaveOffset; + u32 sampleRateOffset; + if (fineOffset >= 0) + { + octaveOffset = fineOffset / IlleprihSampleRateCount; + sampleRateOffset = fineOffset - octaveOffset * IlleprihSampleRateCount; + return SampleRates[sampleRateOffset] << octaveOffset; + } + + octaveOffset = (fineOffset + 1) / -IlleprihSampleRateCount + 1; + sampleRateOffset = fineOffset + octaveOffset * IlleprihSampleRateCount; + return SampleRates[sampleRateOffset] >> octaveOffset; + +} + +u32 VoiceCounterIlleprih::SampleIndex() +{ + return this->Counter >> IlleprihSampleRateBitDepth; +} + +u32 VoiceCounterIlleprih::InterpolationIndex() +{ + return (this->Counter >> IlleprihInterpolationShift) & IlleprihInterpolationAnd; +} + +bool VoiceCounterIlleprih::NextSampleBlock(u32 sampleRate) +{ + this->Counter += sampleRate; + + if (this->SampleIndex() >= 28) + { + this->Counter -= (28 << IlleprihSampleRateBitDepth); + return true; + } + + return false; +} + +f32 VoiceCounterIlleprih::Sample(s16* block) +{ + const u32 i = InterpolationIndex(); + const u32 s = SampleIndex(); + return interpWeights[0][i] * block[NUM_SAMPLES_FROM_LAST_ADPCM_BLOCK - 3 + s] + + interpWeights[1][i] * block[NUM_SAMPLES_FROM_LAST_ADPCM_BLOCK - 3 + s + 1] + + interpWeights[1][IlleprihInterpolationWindowLen - i] * block[NUM_SAMPLES_FROM_LAST_ADPCM_BLOCK - 3 + s + 2] + + interpWeights[0][IlleprihInterpolationWindowLen - i] * block[NUM_SAMPLES_FROM_LAST_ADPCM_BLOCK - 3 + s + 3]; +} + +void VoiceCounterIlleprih::Reset() +{ + this->Counter = IlleprihStartOffset; +} + + +} // namespace diff --git a/Source/AliveLibCommon/audio/Interpolation.hpp b/Source/AliveLibCommon/audio/Interpolation.hpp new file mode 100644 index 000000000..d386410f7 --- /dev/null +++ b/Source/AliveLibCommon/audio/Interpolation.hpp @@ -0,0 +1,86 @@ +#pragma once + +namespace SPU { + +const u32 NUM_SAMPLES_FROM_LAST_ADPCM_BLOCK = 3; +const u32 NUM_SAMPLES_PER_ADPCM_BLOCK = 28; + +u32 mask(u32 num); + +class VoiceCounter +{ +public: + virtual ~VoiceCounter() = default; + virtual u32 CalculateNoteStep(s32 root, s32 rootFine, u32 srcRate, u8 note, u32 pitch) = 0; + virtual u32 SampleIndex() = 0; + virtual u32 InterpolationIndex() = 0; + virtual bool NextSampleBlock(u32 sampleRate) = 0; + virtual f32 Sample(s16* block) = 0; + virtual void Reset() = 0; +}; + + +/** +* This is the same implementation that duckstation uses (with some stuff taken from vgmtrans) +*/ +class VoiceCounterDuckstation : public VoiceCounter +{ +public: + u32 CalculateNoteStep(s32 root, s32 rootFine, u32 srcRate, u8 note, u32 pitch) override; + u32 SampleIndex() override; + u32 InterpolationIndex() override; + bool NextSampleBlock(u32 sampleRate) override; + f32 Sample(s16* block) override; + void Reset() override; + +private: + u32 bits; +}; + + +/** +* This is Illeprih's implementation from discord server +*/ +class VoiceCounterIlleprih : public VoiceCounter +{ +public: + u32 CalculateNoteStep(s32 root, s32 rootFine, u32 srcRate, u8 note, u32 pitch) override; + u32 SampleIndex() override; + u32 InterpolationIndex() override; + bool NextSampleBlock(u32 sampleRate) override; + f32 Sample(s16* block) override; + void Reset() override; + +private: + u32 Counter = 0; +}; + +/// +/// A provider class for creating the different voice types +/// +class InterpolationProvider +{ +public: + virtual std::unique_ptr Create() = 0; +}; + +class InterpolationProviderDuckstation : public InterpolationProvider +{ +public: + std::unique_ptr Create() override + { + return std::make_unique(); + } +}; + +class InterpolationProviderIlleprih : public InterpolationProvider +{ +public: + std::unique_ptr Create() override + { + return std::make_unique(); + } +}; + + +} // namespace diff --git a/Source/AliveLibCommon/audio/MidiPlayer.cpp b/Source/AliveLibCommon/audio/MidiPlayer.cpp new file mode 100644 index 000000000..c9fed6749 --- /dev/null +++ b/Source/AliveLibCommon/audio/MidiPlayer.cpp @@ -0,0 +1,676 @@ +#pragma once + +#include "MidiPlayer.hpp" +#include "oddio.h" + +namespace psx { + +/* +* Used for AO, but the assumption is this is the default psx implementation. +* AE uses a custom version reading from an external sounds.dat file +*/ +class DefaultSoundSampleParser : public SoundSampleParser +{ +public: + std::vector parseSamples(VabHeader* vabHeader, u8* ppVabBody) + { + std::vector samples; + int pos = 0; + for (int i = 0; i < vabHeader->field_16_num_vags; ++i) + { + // SAMPLE + u32 size = *reinterpret_cast(&ppVabBody[pos]); + pos += sizeof(u32); + + u32 sampleRate = *reinterpret_cast(&ppVabBody[pos]); + pos += sizeof(u32); + + u8* data = new u8[size]; + memcpy(data, &ppVabBody[pos], size); + pos += size; + + Sample* sample = new Sample(); + sample->m_SampleBuffer = reinterpret_cast(data); + sample->i_SampleSize = size / 2; + sample->sampleRate = 8000; // All PC samples expect 8000hz regardless of actual rate + sample->loop = sampleRate > 44100; // non-standard? + samples.push_back(sample); + } + return samples; + } + + void applyFix(char_type* headerName, s32 vag, s32 vagOffset, SPU::Sample* sample) + { + std::vector> lookupVag; + std::vector> lookupRate; + + if (strcmp(headerName, "D1SNDFX.VH") == 0) + { + lookupVag = ODDIO::AO_D1SNDFX_VH; + lookupRate = ODDIO::AO_D1SNDFX_RATE; + } + else if (strcmp(headerName, "D2SNDFX.VH") == 0) + { + lookupVag = ODDIO::AO_D2SNDFX_VH; + lookupRate = ODDIO::AO_D2SNDFX_RATE; + } + else if (strcmp(headerName, "D2ENDER.VH") == 0) + { + lookupVag = ODDIO::AO_D2ENDER_VH; + lookupRate = ODDIO::AO_D2ENDER_RATE; + } + else if (strcmp(headerName, "E1SNDFX.VH") == 0) + { + lookupVag = ODDIO::AO_E1SNDFX_VH; + lookupRate = ODDIO::AO_E1SNDFX_RATE; + } + else if (strcmp(headerName, "E2SNDFX.VH") == 0) + { + lookupVag = ODDIO::AO_E2SNDFX_VH; + lookupRate = ODDIO::AO_E2SNDFX_RATE; + } + else if (strcmp(headerName, "F1SNDFX.VH") == 0) + { + lookupVag = ODDIO::AO_F1SNDFX_VH; + lookupRate = ODDIO::AO_F1SNDFX_RATE; + } + else if (strcmp(headerName, "F2SNDFX.VH") == 0) + { + lookupVag = ODDIO::AO_F2SNDFX_VH; + lookupRate = ODDIO::AO_F2SNDFX_RATE; + } + else if (strcmp(headerName, "F2ENDER.VH") == 0) + { + lookupVag = ODDIO::AO_F2ENDER_VH; + lookupRate = ODDIO::AO_F2ENDER_RATE; + } + else if (strcmp(headerName, "MLSNDFX.VH") == 0) + { + lookupVag = ODDIO::AO_MLSNDFX_VH; + lookupRate = ODDIO::AO_MLSNDFX_RATE; + } + else if (strcmp(headerName, "OPTSNDFX.VH") == 0) + { + lookupVag = ODDIO::AO_OPTSNDFX_VH; + lookupRate = ODDIO::AO_OPTSNDFX_RATE; + } + else if (strcmp(headerName, "RFSNDFX.VH") == 0) + { + lookupVag = ODDIO::AO_RFSNDFX_VH; + lookupRate = ODDIO::AO_RFSNDFX_RATE; + } + else if (strcmp(headerName, "RFENDER.VH") == 0) + { + // TODO - These can't be used - the vag atr are messed up + //lookupVag = ODDIO::AO_RFENDER_VH; + //lookupRate = ODDIO::AO_RFENDER_RATE; + } + + for (auto entry : lookupVag) + { + if ((s32) entry[1] == vagOffset) + { + VagAtr* fixed = (VagAtr*) &entry[2]; + sample->adsr = SPU::parseADSR(fixed->field_10_adsr1, fixed->field_12_adsr2); + sample->volume = fixed->field_2_vol == 0 ? 127 : fixed->field_2_vol; + sample->pan = fixed->field_3_pan; + sample->reverb = fixed->field_1_mode; + sample->rootNote = fixed->field_4_centre; + sample->rootNotePitchShift = fixed->field_5_shift; + sample->minNote = fixed->field_6_min; + sample->maxNote = fixed->field_7_max; + sample->priority = fixed->field_0_priority; + break; + } + } + + for (int i = 0; i < (int) lookupRate.size(); i++) + { + if ((s32) lookupRate[i][0] == vag) + { + sample->SampleRate = lookupRate[i][1]; + break; + } + } + } +}; + +MidiPlayer::MidiPlayer(ResourceProvider* provider) +{ + mResourceProvider = provider; + mSoundSampleParser = new DefaultSoundSampleParser(); +} + +MidiPlayer::MidiPlayer(ResourceProvider* provider, SoundSampleParser* sampleParser) +{ + mResourceProvider = provider; + mSoundSampleParser = sampleParser; +} + +void MidiPlayer::SND_Init() +{ + SPU::Init(); +} + +void MidiPlayer::SND_Shutdown() +{ + SPU::DeInit(); +} + +void MidiPlayer::SND_Load_VABS(SoundBlockInfo* pSoundBlockInfo, s32 reverb) +{ + reverb; // TODO - what do we do with this? override the patch/sample? + + SPU::Reset(); + for (auto s : mActiveSamples) + { + delete s; + } + mActiveSamples.clear(); + + while (1) + { + if (!pSoundBlockInfo->header_name) + { + break; + } + + // Read header + ResourceData* vabHeaderResource = mResourceProvider->readFile(pSoundBlockInfo->header_name); + VabHeader* vabHeader = reinterpret_cast(vabHeaderResource->data); + + // Read body + ResourceData* vabBodyResource = mResourceProvider->readFile(pSoundBlockInfo->body_name); + u8* ppVabBody = vabBodyResource->data; + + /////////// + // BODY + std::vector samples = mSoundSampleParser->parseSamples(vabHeader, ppVabBody); + + /////////// + // HEADER + VagAtr* vagAttr = (VagAtr*) &vabHeader[1]; + std::vector programs; + s32 vagOffset = 0; + for (s32 pIdx = 0; pIdx < vabHeader->field_12_num_progs; pIdx++) + { + ProgAtr progAtr = vabHeader->field_20_progs[pIdx]; + u8 progVol = progAtr.field_1_vol; + progVol = progVol == 0 ? 127 : progVol; + u8 progPan = progAtr.field_4_pan; + progPan = progPan == 0 ? 64 : progPan; + SPU::Patch* patch = new SPU::Patch((u8) vagAttr->field_14_prog, progVol, progPan); + SPU::PatchAdd(patch); + + /////////// + // PATCH (Instruments) + for (s32 vagIdx = 0; vagIdx < 16; vagIdx++) + { + /////////// + // SAMPLE + if (vagAttr->field_2_vol > 0 && vagAttr->field_16_vag > 0) + { + unsigned short ADSR1 = vagAttr->field_10_adsr1; + unsigned short ADSR2 = vagAttr->field_12_adsr2; + Sample* s = samples.at(vagAttr->field_16_vag - 1); + + SPU::ADSR adsr = SPU::parseADSR(ADSR1, ADSR2); + SPU::Sample* sample = new SPU::Sample(s->m_SampleBuffer, s->i_SampleSize, s->sampleRate); + + patch->samples[vagIdx] = sample; + + sample->adsr = adsr; + sample->volume = vagAttr->field_2_vol == 0 ? 127 : vagAttr->field_2_vol; + sample->pan = vagAttr->field_3_pan; + sample->reverb = vagAttr->field_1_mode; + sample->rootNote = vagAttr->field_4_centre; + sample->rootNotePitchShift = vagAttr->field_5_shift; + sample->minNote = vagAttr->field_6_min; + sample->maxNote = vagAttr->field_7_max; + sample->priority = vagAttr->field_0_priority; + sample->loop = s->loop; + + mSoundSampleParser->applyFix(pSoundBlockInfo->header_name, vagAttr->field_16_vag, vagOffset, sample); + } + vagOffset++; + vagAttr++; + } + } + + + for (auto s : samples) + { + mActiveSamples.push_back(s); + } + delete vabBodyResource; + pSoundBlockInfo++; + } +} + +void MidiPlayer::SND_Load_Seqs(OpenSeqHandle* pSeqTable, const char_type* bsqFileName) +{ + if (!pSeqTable || !bsqFileName) + { + return; + } + + OpenSeqHandle* seq = pSeqTable; + for (s32 i = 0; i < mResourceProvider->sequenceCount(); i++) // AO = 164 AE = 144 + { + ResourceData* resource = mResourceProvider->readSeq(bsqFileName, seq[i].mBsqName); + + if (resource->data) + { + // 32 length + seq[i].ppSeq_Data = resource->data; + seq[i].generated_res_id = resource->optionalHash; + + std::vector vec; + for (u32 x = 0; x < resource->size; x++) + { + vec.push_back((Uint8) resource->data[x]); + } + + // SEQUENCE STREAM + SPU::Sequence* sequence = new SPU::Sequence(); + parseMidiStream(sequence, vec, i); + SPU::SeqAdd(sequence); + } + + delete resource; + } +} + +void MidiPlayer::SND_StopAll() +{ + // called when pause menu is open + SPU::StopAll(); +} + +void MidiPlayer::SND_Reset() +{ + // called when quiting to main menu or going into a new level + SPU::Reset(); +} + +void MidiPlayer::SND_Restart() +{ + // TODO - don't know when called + std::cout << "restart" << std::endl; +} + +void MidiPlayer::SND_Stop_Channels_Mask(u32 mask) +{ + SPU::OneShotStop(mask); +} + +void MidiPlayer::SND_SEQ_Stop(u16 idx) +{ + SPU::SeqStop(idx); +} + +s8 MidiPlayer::SND_Seq_Table_Valid() +{ + return 1; +} + +s16 MidiPlayer::SND_SEQ_PlaySeq(u16 idx, s32 repeatCount, s16 stopDuplicateSeq) +{ + // Exmaples + // 1. Continuosly called while chanting + // 2. called twice when you blow up two bombs quickly (beginning of Rupture Farms) + // The sequence is played twice for both bombs and must not stop duplicate sequence + return SPU::SeqPlay(idx, repeatCount, stopDuplicateSeq) ? 1 : 0; +} + +void MidiPlayer::sanitizeVolume(s32* src, s32 low, s32 high) +{ + if (*src < low) + { + *src = low; + } + else if (*src >= high) + { + *src = high; + } +} + +void MidiPlayer::sanitizePitch(s32* src, s16 defaultPitch) +{ + if (*src == 0x7FFF) + { + *src = defaultPitch; + } + + if (*src == 0x7FFF) + { + *src = defaultPitch; + } +} + +void MidiPlayer::SND_SEQ_SetVol(s32 idx, s32 volLeft, s32 volRight) +{ + sanitizeVolume(&volLeft, 0, 127); + sanitizeVolume(&volRight, 0, 127); + SPU::SeqSetVolume(idx, (s16) volLeft, (s16) volRight); +} + +s16 MidiPlayer::SND_SEQ_Play(u16 idx, s32 repeatCount, s16 volLeft, s16 volRight) +{ + return SPU::SeqPlay(idx, repeatCount, volLeft, volRight) ? 1 : 0; +} + +s16 MidiPlayer::SND_SsIsEos_DeInlined(u16 idx) +{ + return SPU::SeqIsDone(idx) ? 0 : 1; +} + +s32 MidiPlayer::SFX_SfxDefinition_Play(SfxDefinition* sfxDef, s32 volLeft, s32 volRight, s32 pitch_min, s32 pitch_max) +{ + sanitizePitch(&pitch_min, sfxDef->pitch_min); + sanitizePitch(&pitch_max, sfxDef->pitch_max); + + sanitizeVolume(&volLeft, 0, 127); + sanitizeVolume(&volRight, 0, 127); + + return SPU::OneShotPlay(sfxDef->program, sfxDef->note, (s16) volLeft, (s16) volRight, std::max(pitch_min, pitch_max), pitch_min, pitch_max); +} + +s32 MidiPlayer::SFX_SfxDefinition_Play(SfxDefinition* sfxDef, s32 volume, s32 pitch_min, s32 pitch_max) +{ + if (!volume) + { + volume = sfxDef->volume; + } + sanitizePitch(&pitch_min, sfxDef->pitch_min); + sanitizePitch(&pitch_max, sfxDef->pitch_max); + sanitizeVolume(&volume, 1, 127); + + return SPU::OneShotPlay(sfxDef->program, sfxDef->note, (s16) volume, (s16) volume, std::max(pitch_min, pitch_max), pitch_min, pitch_max); +} + +s32 MidiPlayer::SND(s32 program, s32 vabId, s32 note, s16 vol, s16 min, s16 max) +{ + vabId; // TODO - why is this not used? + return SPU::OneShotPlay(program, (u8) note, vol, vol, 0, min, max); +} + + void MidiPlayer::SsUtAllKeyOff(s32 mode) + { + // TODO - don't know when this is called + mode; + // SPU::StopAll(); + } + + + + ////////////////////////////// +// Midi stuff +static void _SndMidiSkipLength(Stream& stream, int skip) +{ + stream.Seek(stream.Pos() + skip); +} + +// Midi stuff +static Uint32 _MidiReadVarLen(Stream& stream) +{ + Uint32 ret = 0; + Uint8 byte = 0; + for (int i = 0; i < 4; ++i) + { + stream.ReadUInt8(byte); + ret = (ret << 7) | (byte & 0x7f); + if (!(byte & 0x80)) + { + break; + } + } + return ret; +} + +// https://github.com/mlgthatsme/AliveSoundLib/blob/master/MidiPlayer/src/SequencePlayer.cpp +// https://github.com/vgmtrans/vgmtrans/blob/master/src/main/formats/PS1Seq.cpp +void parseMidiStream(SPU::Sequence* seq, std::vector seqData, s32 trackId) +{ + Stream stream(std::move(seqData)); + seq->id = trackId; + + SeqHeader seqHeader; + + // Read the header + if (stream.Size() == 0) + { + std::cout << "no stream!?\n"; + return; + } + + stream.ReadUInt32(seqHeader.mMagic); + stream.ReadUInt32(seqHeader.mVersion); + stream.ReadBytes(seqHeader.mResolutionOfQuaterNote, sizeof(seqHeader.mResolutionOfQuaterNote)); + stream.ReadBytes(seqHeader.mTempo, sizeof(seqHeader.mTempo)); + stream.ReadUInt8(seqHeader.mTimeSignatureBars); + stream.ReadUInt8(seqHeader.mTimeSignatureBeats); + + int tempoValue = 0; + for (int i = 0; i < 3; i++) + { + tempoValue += seqHeader.mTempo[2 - i] << (8 * i); + } + + int ticksPerBeat = 0; + for (int i = 0; i < 2; i++) + { + ticksPerBeat += seqHeader.mResolutionOfQuaterNote[1 - i] << (8 * i); + } + + //m_TimeSignatureBars = seqHeader.mTimeSignatureBars; + seq->tempoUs = (float) tempoValue; // tempo (length of quarter note in microseconds) 0.000001us/s vs 0.001ms/s + seq->ticksPerBeat = float(ticksPerBeat); + + unsigned int deltaTime = 0; + + const size_t midiDataStart = stream.Pos(); + midiDataStart; + + // Context state + SeqInfo gSeqInfo = {}; + + for (;;) + { + // Read event delta time + Uint32 delta = _MidiReadVarLen(stream); + deltaTime += delta; + //std::cout << "Delta: " << delta << " over all " << deltaTime << std::endl; + + // Obtain the event/status byte + Uint8 eventByte = 0; + stream.ReadUInt8(eventByte); + if (eventByte < 0x80) + { + // Use running status + if (!gSeqInfo.running_status) // v1 + { + return; // error if no running status? + } + eventByte = gSeqInfo.running_status; + + // Go back one byte as the status byte isn't a status + stream.Seek(stream.Pos() - 1); + } + else + { + // Update running status + gSeqInfo.running_status = eventByte; + } + + if (eventByte == 0xff) + { + // Meta event + Uint8 metaCommand = 0; + stream.ReadUInt8(metaCommand); + + Uint8 metaCommandLength = 0; + stream.ReadUInt8(metaCommandLength); + + switch (metaCommand) + { + case 0x2f: + { + + // This will make sure END_TRACK is on time + s32 bars = 0; + u32 onTime = 0; + while (onTime < deltaTime || bars != 0) + { + onTime += ticksPerBeat; + bars = (bars + 1) % seqHeader.mTimeSignatureBeats; + } + + SPU::MIDIMessage* msg = seq->createMIDIMessage(); + msg->type = SPU::END_TRACK; + msg->tick = ticksPerBeat; + msg->tick = deltaTime + (onTime - deltaTime); + return; + } + + case 0x51: // Tempo in microseconds per quarter note (24-bit value) + { + //std::cout << "Temp change" << std::endl; + // TODO: Not sure if this is correct + Uint8 tempoByte = 0; + int t = 0; + for (int i = 0; i < 3; i++) + { + stream.ReadUInt8(tempoByte); + t = tempoByte << 8 * i; + } + } + break; + + default: + { + //std::cout << "Unknown meta event " << Uint32(metaCommand) << std::endl; + // Skip unknown events + // TODO Might be wrong + _SndMidiSkipLength(stream, metaCommandLength); + } + } + } + else if (eventByte < 0x80) + { + // Error + throw std::runtime_error("Unknown midi event"); + } + else + { + const Uint8 channel = eventByte & 0xf; + switch (eventByte >> 4) + { + case 0x9: // Note On + { + Uint8 note = 0; + stream.ReadUInt8(note); + + Uint8 velocity = 0; + stream.ReadUInt8(velocity); + + SPU::MIDIMessage* msg = seq->createMIDIMessage(); + msg->tick = deltaTime; + msg->channelId = channel; + msg->note = note; + msg->velocity = velocity; + if (velocity == 0) // If velocity is 0, then the sequence means to do "Note Off" + { + msg->type = SPU::NOTE_OFF; + } + else + { + msg->type = SPU::NOTE_ON; + } + } + break; + case 0x8: // Note Off + { + Uint8 note = 0; + stream.ReadUInt8(note); + Uint8 velocity = 0; + stream.ReadUInt8(velocity); + + SPU::MIDIMessage* msg = seq->createMIDIMessage(); + msg->type = SPU::NOTE_OFF; + msg->tick = deltaTime; + msg->channelId = channel; + msg->note = note; + msg->velocity = velocity; + } + break; + case 0xc: // Program Change + { + Uint8 prog = 0; + stream.ReadUInt8(prog); + + SPU::MIDIMessage* msg = seq->createMIDIMessage(); + msg->type = SPU::PATCH_CHANGE; + msg->tick = deltaTime; + msg->channelId = channel; + msg->patchId = prog; + } + break; + case 0xa: // Polyphonic key pressure (after touch) + { + Uint8 note = 0; + Uint8 pressure = 0; + + stream.ReadUInt8(note); + stream.ReadUInt8(pressure); + } + break; + case 0xb: // Controller Change + { + Uint8 controller = 0; + Uint8 value = 0; + stream.ReadUInt8(controller); + stream.ReadUInt8(value); + } + break; + case 0xd: // After touch + { + Uint8 value = 0; + stream.ReadUInt8(value); + } + break; + case 0xe: // Pitch Bend + { + s16 bend = 0; + stream.ReadSInt16(bend); + + // 0 is x semitones down + // 16383 is center + // 32767 is x semitones up. + // convert to 127 value where 0 is center + + float multi = bend / 16383.0f; + multi = multi * (127 * 4); // (127*4) is 4 semitones (or 4 half steps). + multi = multi - (127 * 4); // Possibly 4 is not correct for pitch bend range? + + SPU::MIDIMessage* msg = seq->createMIDIMessage(); + msg->type = SPU::PITCH_BEND; + msg->bend = (s16) multi; + msg->channelId = channel; + msg->tick = deltaTime; + } + break; + case 0xf: // Sysex len + { + const Uint32 length = _MidiReadVarLen(stream); + _SndMidiSkipLength(stream, length); + } + break; + default: + throw std::runtime_error("Unknown MIDI command"); + } + } + } +} + +} // namespace psx diff --git a/Source/AliveLibCommon/audio/MidiPlayer.hpp b/Source/AliveLibCommon/audio/MidiPlayer.hpp new file mode 100644 index 000000000..d7440ab3d --- /dev/null +++ b/Source/AliveLibCommon/audio/MidiPlayer.hpp @@ -0,0 +1,286 @@ +#pragma once + +#include "Stream.hpp" +#include "Sequencer.hpp" + +namespace psx { + + void parseMidiStream(SPU::Sequence* seq, std::vector seqData, s32 trackId); + + /* + * Raw audio data. An audio sample. + */ + class Sample + { + public: + ~Sample() + { + delete m_SampleBuffer; + } + + s16* m_SampleBuffer; + u32 i_SampleSize; + u32 sampleRate; + bool loop; + float GetSample(float sampleOffset); + }; + + /* +* Describes how to play a sample (ADSR curves and pitch shift) +*/ + class Tone + { + public: + // volume 0-1 + float f_Volume; + s8 mode; + + // panning -1 - 1 + float f_Pan; + + // Root Key + u8 c_Center; + u8 c_Shift; + + // Key range + unsigned char Min; + unsigned char Max; + + float Pitch; + + double AttackTime; + double ReleaseTime; + bool ReleaseExponential = false; + double DecayTime; + double SustainTime; + + bool Loop = false; + + Sample* m_Sample; + }; + + /* + * A collection of tones (how to play samples) + */ + class Program + { + public: + int prog_id; + std::vector m_Tones; + }; + + + struct VagAtr final + { + s8 field_0_priority; + s8 field_1_mode; // reverb? + s8 field_2_vol; + s8 field_3_pan; + u8 field_4_centre; // center + u8 field_5_shift; // pitch + s8 field_6_min; // min note + s8 field_7_max; // max note + s8 field_8_vibW; + s8 field_9_vibT; + s8 field_A_porW; + s8 field_B_porT; + s8 field_C_pitch_bend_min; + s8 field_D_pitch_bend_max; + s8 field_E_reserved1; + s8 field_F_reserved2; + s16 field_10_adsr1; + s16 field_12_adsr2; + s16 field_14_prog; + s16 field_16_vag; + s16 field_18_reserved[4]; + }; + struct AoVag + { + u32 iSize; + u32 iSampleRate; + std::vector iSampleData; + }; + + struct ProgAtr final + { + u8 field_0_num_tones; + u8 field_1_vol; + s8 field_2_priority; + s8 field_3_mode; + s8 field_4_pan; + s8 field_5_reserved0; + s16 field_6_attr; + s32 field_8_reserved1; + s32 field_C_reserved2; + }; + + struct VabHeader final + { + s32 field_0_form; + s32 field_4_version; + s32 field_8_id; + s32 field_C_file_size; + s16 field_10_reserved0; + s16 field_12_num_progs; + s16 field_14_num_tones; + u16 field_16_num_vags; + s8 field_18_master_vol; + s8 field_19_master_pan; + s8 field_1A_attr1; + s8 field_1B_attr2; + s32 field_1C_reserved1; + ProgAtr field_20_progs[128]; + }; + + struct SoundBlockInfo final + { + char_type* header_name; + char_type* body_name; + s32 vab_id; + u8* VabHeader; + }; + + struct SeqHeader + { + Uint32 mMagic; // SEQp + Uint32 mVersion; // Seems to always be 1 + Uint8 mResolutionOfQuaterNote[2]; + Uint8 mTempo[3]; + Uint8 mTimeSignatureBars; + Uint8 mTimeSignatureBeats; + }; + + struct SeqInfo + { + Uint32 iLastTime = 0; + Sint32 iNumTimesToLoop = 0; + Uint8 running_status = 0; + }; + + struct OpenSeqHandle final + { + char_type* mBsqName; + s32 generated_res_id; // A hash of the named which matches the resource Id + s8 sound_block_idx; + s8 volume; + s16 seqOpenId; + u8* ppSeq_Data; + }; + + struct SfxDefinition final + { + s8 block_idx; + s8 program; + s8 note; + s8 volume; + s16 pitch_min; + s16 pitch_max; + }; + + struct Vag final + { + u16 adsr_attack; + u16 adsr_sustain_level; + u16 adsr_decay; + u16 adsr_release; + u8 min; + u8 max; + s16 shift_cen; + u8 C; + u8 vol; + u8 priority; + u8 prog; + u8 vag; + s8 pad; + }; + + /* + Represents some data read from disk + */ + struct ResourceData final + { + ~ResourceData() + { + delete data; + } + u8* data; + u32 size; + s32 optionalHash; + }; + + /* + Provides a way to read files from a "psx" disk. + */ + class ResourceProvider + { + public: + /* Read a file within the game, example a .VB file */ + virtual ResourceData* readFile(char_type* name) = 0; + + /* AO reads sequence inline with data. AE reads from a sounds.dat file */ + virtual ResourceData* readSeq(const char_type* fileName, const char_type* sequenceName) = 0; + + /* AO and AE seem to have different hardcoded sequence counts */ + virtual s32 sequenceCount() = 0; + }; + + /* + In the case of AO and AE sound samples are + loaded differently. In AO the samples are inline of + the disk data file. In the case of AE it uses the disk + data file but references an offset in a separate sounds.dat file. + MidiPlayers default is AO style. AE provides its own + */ + class SoundSampleParser + { + public: + virtual std::vector parseSamples(VabHeader* vabHeader, u8* ppVabBody) = 0; + + virtual void applyFix(char_type* headerName, s32 vag, s32 vagOffset, SPU::Sample* sample) = 0; + }; + + /* + The PSX midi player (SND LIB) API + */ + class MidiPlayer + { + public: + MidiPlayer(ResourceProvider* provider); + MidiPlayer(ResourceProvider* provider, SoundSampleParser* sampleParser); + ~MidiPlayer(); + + void SND_Init(); + void SND_Shutdown(); + + void SND_Load_VABS(SoundBlockInfo* pSoundBlockInfo, s32 reverb); + void SND_Load_Seqs(OpenSeqHandle* pSeqTable, const char_type* bsqFileName); + + void SND_StopAll(); + void SND_Reset(); + void SND_Restart(); + + void SND_Stop_Channels_Mask(u32 bitMask); + + void SND_SEQ_Stop(u16 idx); + s8 SND_Seq_Table_Valid(); + s16 SND_SEQ_PlaySeq(u16 idx, s32 repeatCount, s16 bDontStop); + void SND_SEQ_SetVol(s32 idx, s32 volLeft, s32 volRight); + s16 SND_SEQ_Play(u16 idx, s32 repeatCount, s16 volLeft, s16 volRight); + + s16 SND_SsIsEos_DeInlined(u16 idx); + s32 SFX_SfxDefinition_Play(SfxDefinition* sfxDef, s32 volLeft, s32 volRight, s32 pitch_min, s32 pitch_max); + s32 SFX_SfxDefinition_Play(SfxDefinition* sfxDef, s32 volume, s32 pitch_min, s32 pitch_max); + s32 SND(s32 program, s32 vabId, s32 note, s16 vol, s16 min, s16 max); + void SsUtAllKeyOff(s32 mode); + + private: + ResourceProvider* mResourceProvider; + SoundSampleParser* mSoundSampleParser; + std::vector mActiveSamples; + + void sanitizePitch(s32* src, s16 defaultPitch); + void sanitizeVolume(s32* src, s32 low, s32 high); + }; + + +} // namespace psx diff --git a/Source/AliveLibCommon/audio/Reverb.cpp b/Source/AliveLibCommon/audio/Reverb.cpp new file mode 100644 index 000000000..cfc23d432 --- /dev/null +++ b/Source/AliveLibCommon/audio/Reverb.cpp @@ -0,0 +1,506 @@ +#pragma once + +#include "Reverb.hpp" +#include +#include + +namespace SPU { +#pragma warning(disable : 4244) + +////////////////////////// +// Duckstation's reverb +static const u32 NUM_REVERB_REGS = 32; +void ProcessReverb(s16 left_in, s16 right_in, s32* left_out, s32* right_out); +static s16 ReverbRead(u32 address, s32 offset = 0); + +struct ReverbRegisters +{ + s16 vLOUT; + s16 vROUT; + u16 mBASE; + + union + { + struct + { + u16 FB_SRC_A; + u16 FB_SRC_B; + s16 IIR_ALPHA; + s16 ACC_COEF_A; + s16 ACC_COEF_B; + s16 ACC_COEF_C; + s16 ACC_COEF_D; + s16 IIR_COEF; + s16 FB_ALPHA; + s16 FB_X; + u16 IIR_DEST_A[2]; + u16 ACC_SRC_A[2]; + u16 ACC_SRC_B[2]; + u16 IIR_SRC_A[2]; + u16 IIR_DEST_B[2]; + u16 ACC_SRC_C[2]; + u16 ACC_SRC_D[2]; + u16 IIR_SRC_B[2]; + u16 MIX_DEST_A[2]; + u16 MIX_DEST_B[2]; + s16 IN_COEF[2]; + } a; + + u16 rev[NUM_REVERB_REGS]; + }; +}; + +// bit mask of voices with reverb +static const u32 RAM_SIZE = 512 * 1024; +static std::array s_ram{}; +//static u32 s_reverb_on_register = 0; +static u32 s_reverb_base_address = 0; +static u32 s_reverb_current_address = 0; +static ReverbRegisters s_reverb_registers{}; +static std::array, 2> s_reverb_downsample_buffer; +static std::array, 2> s_reverb_upsample_buffer; +static s32 s_reverb_resample_buffer_position = 0; + + +// Zeroes optimized out; middle removed too(it's 16384) +static constexpr std::array s_reverb_resample_coefficients = { + -1, + 2, + -10, + 35, + -103, + 266, + -616, + 1332, + -2960, + 10246, + 10246, + -2960, + 1332, + -616, + 266, + -103, + 35, + -10, + 2, + -1, +}; +static s16 s_last_reverb_input[2]; +static s32 s_last_reverb_output[2]; + +static s32 Reverb4422(const s16* src) +{ + s32 out = 0; // 32-bits is adequate(it won't overflow) + for (u32 i = 0; i < 20; i++) + out += s_reverb_resample_coefficients[i] * src[i * 2]; + + // Middle non-zero + out += 0x4000 * src[19]; + out >>= 15; + return std::clamp(out, -32768, 32767); +} + +template +static s32 Reverb2244(const s16* src) +{ + s32 out; // 32-bits is adequate(it won't overflow) + if (phase) + { + // Middle non-zero + out = src[9]; + } + else + { + out = 0; + for (u32 i = 0; i < 20; i++) + out += s_reverb_resample_coefficients[i] * src[i]; + + out >>= 14; + out = std::clamp(out, -32768, 32767); + } + + return out; +} + +static s16 ReverbSat(s32 val) +{ + return static_cast(std::clamp(val, -0x8000, 0x7FFF)); +} + +static s16 ReverbNeg(s16 samp) +{ + if (samp == -32768) + return 0x7FFF; + + return -samp; +} + +static s32 IIASM(const s16 IIR_ALPHA, const s16 insamp) +{ + if (IIR_ALPHA == -32768) + { + if (insamp == -32768) + return 0; + else + return insamp * -65536; + } + else + return insamp * (32768 - IIR_ALPHA); +} + +u32 ReverbMemoryAddress(u32 address) +{ + // Ensures address does not leave the reverb work area. + static constexpr u32 MASK = (RAM_SIZE - 1) / 2; + u32 offset = s_reverb_current_address + (address & MASK); + offset += s_reverb_base_address & ((s32) (offset << 13) >> 31); + + // We address RAM in bytes. TODO: Change this to words. + return (offset & MASK) * 2u; +} + +s16 ReverbRead(u32 address, s32 offset) +{ + // TODO: This should check interrupts. + const u32 real_address = ReverbMemoryAddress((address << 2) + offset); + + s16 data; + std::memcpy(&data, &s_ram[real_address], sizeof(data)); + return data; +} + +void ReverbWrite(u32 address, s16 data) +{ + // TODO: This should check interrupts. + const u32 real_address = ReverbMemoryAddress(address << 2); + std::memcpy(&s_ram[real_address], &data, sizeof(data)); +} + +static constexpr s32 ApplyVolume(s32 sample, s16 volume) +{ + return (sample * s32(volume)) >> 15; +} + +void ReverbDuckstation::ChangeVolume(u8 left, u8 right) +{ + left; + right; + // Not implemented +} + +void ReverbDuckstation::ChangeSetting(u8 setting) +{ + setting; + // Not implemented +} + +std::pair ReverbDuckstation::Process(f32 _left_in, f32 _right_in) +{ + s16 left_in = (s16) _left_in; + s16 right_in = (s16) _right_in; + + s_last_reverb_input[0] = left_in; + s_last_reverb_input[1] = right_in; + s_reverb_downsample_buffer[0][s_reverb_resample_buffer_position | 0x00] = left_in; + s_reverb_downsample_buffer[0][s_reverb_resample_buffer_position | 0x40] = left_in; + s_reverb_downsample_buffer[1][s_reverb_resample_buffer_position | 0x00] = right_in; + s_reverb_downsample_buffer[1][s_reverb_resample_buffer_position | 0x40] = right_in; + std::array, 2> test; + + // these values seem to be stable in duckstation - move them to be set once + s_reverb_registers.vLOUT = 4128; + s_reverb_registers.vROUT = 4128; + s_reverb_registers.mBASE = 61956; + s_reverb_registers.a.IIR_SRC_A[0] = 2905; + s_reverb_registers.a.IIR_SRC_A[1] = 2266; + s_reverb_registers.a.IIR_COEF = -22912; + s_reverb_registers.a.IN_COEF[0] = -32768; + s_reverb_registers.a.IN_COEF[1] = -32768; + s_reverb_registers.a.IIR_SRC_B[0] = 1514; + s_reverb_registers.a.IIR_SRC_B[1] = 797; + s_reverb_registers.a.IIR_ALPHA = 28512; + s_reverb_registers.a.IIR_DEST_A[0] = 3579; + s_reverb_registers.a.IIR_DEST_A[1] = 2904; + s_reverb_registers.a.IIR_DEST_B[0] = 2265; + s_reverb_registers.a.IIR_DEST_B[1] = 1513; + s_reverb_registers.a.ACC_SRC_A[0] = 3337; + s_reverb_registers.a.ACC_SRC_A[1] = 2620; + s_reverb_registers.a.ACC_SRC_B[0] = 3033; + s_reverb_registers.a.ACC_SRC_B[1] = 2419; + s_reverb_registers.a.ACC_SRC_C[0] = 2028; + s_reverb_registers.a.ACC_SRC_C[1] = 1200; + s_reverb_registers.a.ACC_SRC_D[0] = 1775; + s_reverb_registers.a.ACC_SRC_D[1] = 978; + s_reverb_registers.a.ACC_COEF_A = 20392; + s_reverb_registers.a.ACC_COEF_B = -17184; + s_reverb_registers.a.ACC_COEF_C = 17680; + s_reverb_registers.a.ACC_COEF_D = -16656; + s_reverb_registers.a.MIX_DEST_A[0] = 796; + s_reverb_registers.a.MIX_DEST_A[1] = 568; + s_reverb_registers.a.MIX_DEST_B[0] = 340; + s_reverb_registers.a.MIX_DEST_B[1] = 170; + s_reverb_registers.a.FB_SRC_A = 227; + s_reverb_registers.a.FB_SRC_B = 169; + s_reverb_registers.a.FB_ALPHA = 22144; + s_reverb_registers.a.FB_X = 21184; + + + test = s_reverb_downsample_buffer; + //std::cout << "L:" << left_in << " R:" << right_in << std::endl; + //std::cout << s_reverb_registers.a.IIR_SRC_A[0] << std::endl; + + s32 out[2]; + if (s_reverb_resample_buffer_position & 1u) + { + //std::cout << "here1" << std::endl; + std::array downsampled; + for (unsigned lr = 0; lr < 2; lr++) + downsampled[lr] = Reverb4422(&s_reverb_downsample_buffer[lr][(s_reverb_resample_buffer_position - 38) & 0x3F]); + + for (unsigned lr = 0; lr < 2; lr++) + { + //if (s_SPUCNT.a.reverb_master_enable) + { + const s16 IIR_INPUT_A = ReverbSat((((ReverbRead(s_reverb_registers.a.IIR_SRC_A[lr ^ 0]) * s_reverb_registers.a.IIR_COEF) >> 14) + ((downsampled[lr] * s_reverb_registers.a.IN_COEF[lr]) >> 14)) >> 1); + const s16 IIR_INPUT_B = ReverbSat((((ReverbRead(s_reverb_registers.a.IIR_SRC_B[lr ^ 1]) * s_reverb_registers.a.IIR_COEF) >> 14) + ((downsampled[lr] * s_reverb_registers.a.IN_COEF[lr]) >> 14)) >> 1); + const s16 IIR_A = ReverbSat((((IIR_INPUT_A * s_reverb_registers.a.IIR_ALPHA) >> 14) + (IIASM(s_reverb_registers.a.IIR_ALPHA, ReverbRead(s_reverb_registers.a.IIR_DEST_A[lr], -1)) >> 14)) >> 1); + const s16 IIR_B = ReverbSat((((IIR_INPUT_B * s_reverb_registers.a.IIR_ALPHA) >> 14) + (IIASM(s_reverb_registers.a.IIR_ALPHA, ReverbRead(s_reverb_registers.a.IIR_DEST_B[lr], -1)) >> 14)) >> 1); + ReverbWrite(s_reverb_registers.a.IIR_DEST_A[lr], IIR_A); + ReverbWrite(s_reverb_registers.a.IIR_DEST_B[lr], IIR_B); + } + + const s32 ACC = ((ReverbRead(s_reverb_registers.a.ACC_SRC_A[lr]) * s_reverb_registers.a.ACC_COEF_A) >> 14) + ((ReverbRead(s_reverb_registers.a.ACC_SRC_B[lr]) * s_reverb_registers.a.ACC_COEF_B) >> 14) + ((ReverbRead(s_reverb_registers.a.ACC_SRC_C[lr]) * s_reverb_registers.a.ACC_COEF_C) >> 14) + ((ReverbRead(s_reverb_registers.a.ACC_SRC_D[lr]) * s_reverb_registers.a.ACC_COEF_D) >> 14); + + const s16 FB_A = ReverbRead(s_reverb_registers.a.MIX_DEST_A[lr] - s_reverb_registers.a.FB_SRC_A); + const s16 FB_B = ReverbRead(s_reverb_registers.a.MIX_DEST_B[lr] - s_reverb_registers.a.FB_SRC_B); + const s16 MDA = ReverbSat((ACC + ((FB_A * ReverbNeg(s_reverb_registers.a.FB_ALPHA)) >> 14)) >> 1); + const s16 MDB = ReverbSat( + FB_A + ((((MDA * s_reverb_registers.a.FB_ALPHA) >> 14) + ((FB_B * ReverbNeg(s_reverb_registers.a.FB_X)) >> 14)) >> 1)); + const s16 IVB = ReverbSat(FB_B + ((MDB * s_reverb_registers.a.FB_X) >> 15)); + + //if (s_SPUCNT.reverb_master_enable) + { + ReverbWrite(s_reverb_registers.a.MIX_DEST_A[lr], MDA); + ReverbWrite(s_reverb_registers.a.MIX_DEST_B[lr], MDB); + } + + s_reverb_upsample_buffer[lr][(s_reverb_resample_buffer_position >> 1) | 0x20] = s_reverb_upsample_buffer[lr][s_reverb_resample_buffer_position >> 1] = IVB; + } + + s_reverb_current_address = (s_reverb_current_address + 1) & 0x3FFFFu; + if (s_reverb_current_address == 0) + s_reverb_current_address = s_reverb_base_address; + + for (unsigned lr = 0; lr < 2; lr++) + out[lr] = Reverb2244(&s_reverb_upsample_buffer[lr][((s_reverb_resample_buffer_position >> 1) - 19) & 0x1F]); + } + else + { + //std::cout << "here2" << std::endl; + for (unsigned lr = 0; lr < 2; lr++) + out[lr] = Reverb2244(&s_reverb_upsample_buffer[lr][((s_reverb_resample_buffer_position >> 1) - 19) & 0x1F]); + } + + s_reverb_resample_buffer_position = (s_reverb_resample_buffer_position + 1) & 0x3F; + + s32 left_out; + s32 right_out; + s_last_reverb_output[0] = left_out = ApplyVolume(out[0], s_reverb_registers.vLOUT); + s_last_reverb_output[1] = right_out = ApplyVolume(out[1], s_reverb_registers.vROUT); + + return std::pair(left_out, right_out); +} + + +////////////////////////// +// Illeprih's reverb +#pragma warning(push) // anoying visual studio warnings about overflow u16 +#pragma warning(disable : 4838) +#pragma warning(push) +#pragma warning(disable : 4309) +const ReverbType ReverbIlleprih::reverbTypes[10] = { + {// OFF + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, + 0x0000, 0x0000, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, + 0x0000, 0x0000, 0x0001, 0x0001, 0x0001, 0x0001, 0x0000, 0x0000}, + {// ROOM + 0x007D, 0x005B, 0x6D80, 0x54B8, 0xBED0, 0x0000, 0x0000, 0xBA80, + 0x5800, 0x5300, 0x04D6, 0x0333, 0x03F0, 0x0227, 0x0374, 0x01EF, + 0x0334, 0x01B5, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x01B4, 0x0136, 0x00B8, 0x005C, 0x8000, 0x8000}, + {// STUDIO_A (small) + 0x0033, 0x0025, 0x70F0, 0x4FA8, 0xBCE0, 0x4410, 0xC0F0, 0x9C00, + 0x5280, 0x4EC0, 0x03E4, 0x031B, 0x03A4, 0x02AF, 0x0372, 0x0266, + 0x031C, 0x025D, 0x025C, 0x018E, 0x022F, 0x0135, 0x01D2, 0x00B7, + 0x018F, 0x00B5, 0x00B4, 0x0080, 0x004C, 0x0026, 0x8000, 0x8000}, + {// STUDIO_B (medium) + 0x00B1, 0x007F, 0x70F0, 0x4FA8, 0xBCE0, 0x4510, 0xBEF0, 0xB4C0, + 0x5280, 0x4EC0, 0x0904, 0x076B, 0x0824, 0x065F, 0x07A2, 0x0616, + 0x076C, 0x05ED, 0x05EC, 0x042E, 0x050F, 0x0305, 0x0462, 0x02B7, + 0x042F, 0x0265, 0x0264, 0x01B2, 0x0100, 0x0080, 0x8000, 0x8000}, + {// STUDIO_C (large) + 0x00E3, 0x00A9, 0x6F60, 0x4FA8, 0xBCE0, 0x4510, 0xBEF0, 0xA680, + 0x5680, 0x52C0, 0x0DFB, 0x0B58, 0x0D09, 0x0A3C, 0x0BD9, 0x0973, + 0x0B59, 0x08DA, 0x08D9, 0x05E9, 0x07EC, 0x04B0, 0x06EF, 0x03D2, + 0x05EA, 0x031D, 0x031C, 0x0238, 0x0154, 0x00AA, 0x8000, 0x8000}, + {// HALL + 0x01A5, 0x0139, 0x6000, 0x5000, 0x4C00, 0xB800, 0xBC00, 0xC000, + 0x6000, 0x5C00, 0x15BA, 0x11BB, 0x14C2, 0x10BD, 0x11BC, 0x0DC1, + 0x11C0, 0x0DC3, 0x0DC0, 0x09C1, 0x0BC4, 0x07C1, 0x0A00, 0x06CD, + 0x09C2, 0x05C1, 0x05C0, 0x041A, 0x0274, 0x013A, 0x8000, 0x8000}, + {// SPACE + 0x033D, 0x0231, 0x7E00, 0x5000, 0xB400, 0xB000, 0x4C00, 0xB000, + 0x6000, 0x5400, 0x1ED6, 0x1A31, 0x1D14, 0x183B, 0x1BC2, 0x16B2, + 0x1A32, 0x15EF, 0x15EE, 0x1055, 0x1334, 0x0F2D, 0x11F6, 0x0C5D, + 0x1056, 0x0AE1, 0x0AE0, 0x07A2, 0x0464, 0x0232, 0x8000, 0x8000}, + {// ECHO + 0x0001, 0x0001, 0x7FFF, 0x7FFF, 0x0000, 0x0000, 0x0000, 0x8100, + 0x0000, 0x0000, 0x1FFF, 0x0FFF, 0x1005, 0x0005, 0x0000, 0x0000, + 0x1005, 0x0005, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x1004, 0x1002, 0x0004, 0x0002, 0x8000, 0x8000}, + {// DELAY + 0x0001, 0x0001, 0x7FFF, 0x7FFF, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x1FFF, 0x0FFF, 0x1005, 0x0005, 0x0000, 0x0000, + 0x1005, 0x0005, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x1004, 0x1002, 0x0004, 0x0002, 0x8000, 0x8000}, + {// PIPE + 0x0017, 0x0013, 0x70F0, 0x4FA8, 0xBCE0, 0x4510, 0xBEF0, 0x8500, + 0x5F80, 0x54C0, 0x0371, 0x02AF, 0x02E5, 0x01DF, 0x02B0, 0x01D7, + 0x0358, 0x026A, 0x01D6, 0x011E, 0x012D, 0x00B1, 0x011F, 0x0059, + 0x01A0, 0x00E3, 0x0058, 0x0040, 0x0028, 0x0014, 0x8000, 0x8000}}; +#pragma warning(pop) +#pragma warning(pop) + +ReverbIlleprih::ReverbIlleprih() +{ + this->ChangeSetting(4); + // TODO look into the acual code to find the proper reverb volume to be used + this->ChangeVolume(0x18, 0x18); +} + +std::pair ReverbIlleprih::Process(f32 left, f32 right) +{ + // TODO consider diving input to fit the -1.0f - 1.0f range and then multiplying back to s16 range at the end. But it shouldn't make any difference. + + // Input from mixer + const float Lin = this->currentSetting.vLIN * left; + const float Rin = this->currentSetting.vRIN * right; + + // TODO Verify the -1 is actually correct in this case. Looks like most emulators might just get it wrong. Off addresses have 0001h there so that the value isn't negative, however, it's being << 3, so should't 8 be subtracted here?? + // Maybe the value is there just to 0 fill the work area and it's not really necessary and getting the previous address is intended. + + // Same side reflection L->L and R->R + float mlSame = SaturateSample((Lin + LoadReverb(this->currentSetting.dLSAME) * this->currentSetting.vWALL - LoadReverb(this->currentSetting.mLSAME - 1)) * this->currentSetting.vIIR + LoadReverb(this->currentSetting.mLSAME - 1)); + float mrSame = SaturateSample((Rin + LoadReverb(this->currentSetting.dRSAME) * this->currentSetting.vWALL - LoadReverb(this->currentSetting.mRSAME - 1)) * this->currentSetting.vIIR + LoadReverb(this->currentSetting.mRSAME - 1)); + + WriteReverb(this->currentSetting.mLSAME, mlSame); + WriteReverb(this->currentSetting.mRSAME, mrSame); + + // Different side reflection L->R and R->L + float mlDiff = SaturateSample((Lin + LoadReverb(this->currentSetting.dRDIFF) * this->currentSetting.vWALL - LoadReverb(this->currentSetting.mLDIFF - 1)) * this->currentSetting.vIIR + LoadReverb(this->currentSetting.mLDIFF - 1)); + float mrDiff = SaturateSample((Rin + LoadReverb(this->currentSetting.dLDIFF) * this->currentSetting.vWALL - LoadReverb(this->currentSetting.mRDIFF - 1)) * this->currentSetting.vIIR + LoadReverb(this->currentSetting.mRDIFF - 1)); + + WriteReverb(this->currentSetting.mLDIFF, mlDiff); + WriteReverb(this->currentSetting.mRDIFF, mrDiff); + + // Early echo (comb filter with input from buffer) + float l = SaturateSample(this->currentSetting.vCOMB1 * LoadReverb(this->currentSetting.mLCOMB1) + this->currentSetting.vCOMB2 * LoadReverb(this->currentSetting.mLCOMB2) + this->currentSetting.vCOMB3 * LoadReverb(this->currentSetting.mLCOMB3) + this->currentSetting.vCOMB4 * LoadReverb(this->currentSetting.mLCOMB4)); + float r = SaturateSample(this->currentSetting.vCOMB1 * LoadReverb(this->currentSetting.mRCOMB1) + this->currentSetting.vCOMB2 * LoadReverb(this->currentSetting.mRCOMB2) + this->currentSetting.vCOMB3 * LoadReverb(this->currentSetting.mRCOMB3) + this->currentSetting.vCOMB4 * LoadReverb(this->currentSetting.mRCOMB4)); + + // Late reverb APF1 (All pass filter 1 with input from COMB) + l = SaturateSample(l - SaturateSample(this->currentSetting.vAPF1 * LoadReverb(this->currentSetting.mLAPF1 - this->currentSetting.dAPF1))); + r = SaturateSample(r - SaturateSample(this->currentSetting.vAPF1 * LoadReverb(this->currentSetting.mRAPF1 - this->currentSetting.dAPF1))); + + WriteReverb(this->currentSetting.mLAPF1, l); + WriteReverb(this->currentSetting.mRAPF1, r); + + l = SaturateSample(l * this->currentSetting.vAPF1 + LoadReverb(this->currentSetting.mLAPF1 - this->currentSetting.dAPF1)); + r = SaturateSample(r * this->currentSetting.vAPF1 + LoadReverb(this->currentSetting.mRAPF1 - this->currentSetting.dAPF1)); + + // Late reverb APF2 (All pass filter 2 with input from APF1) + l = SaturateSample(l - SaturateSample(this->currentSetting.vAPF2 * LoadReverb(this->currentSetting.mLAPF2 - this->currentSetting.dAPF2))); + r = SaturateSample(r - SaturateSample(this->currentSetting.vAPF2 * LoadReverb(this->currentSetting.mRAPF2 - this->currentSetting.dAPF2))); + + WriteReverb(this->currentSetting.mLAPF2, l); + WriteReverb(this->currentSetting.mRAPF2, r); + + l = SaturateSample(l * this->currentSetting.vAPF2 + LoadReverb(this->currentSetting.mLAPF2 - this->currentSetting.dAPF2)); + r = SaturateSample(r * this->currentSetting.vAPF2 + LoadReverb(this->currentSetting.mRAPF2 - this->currentSetting.dAPF2)); + + l *= volumeLeft; + r *= volumeRight; + + // Overflow here is intended. It wraps to the start of the workArea due to it's size being max value of u16 + 1; + this->currentAddress++; + + return std::pair(l, r); +} + +float ReverbIlleprih::LoadReverb(u16 address) +{ + return this->workArea[(u16)(this->currentAddress + address)]; +} + +void ReverbIlleprih::WriteReverb(u16 address, float value) +{ + this->workArea[(u16)(currentAddress + address)] = value; +} + +// TODO consider removing this. It shouldn't cause any overflow issues and should make reverb +// slightly less fuzzy in the edge cases +float ReverbIlleprih::SaturateSample(float sample) +{ + return std::clamp(sample, -32768.0f, 32767.0f); +} + +// PSX is doing << 8 to get it to the s16 range, so we can just div by 128 +void ReverbIlleprih::ChangeVolume(u8 _volumeLeft, u8 _volumeRight) +{ + this->volumeLeft = _volumeLeft / 128.0f; + this->volumeRight = _volumeRight / 128.0f; +} + + +// PSX << 3 these to get the address into a s16 array. This would mean we only have to << 2, +// but since it's setup to tick at 44_100 Hz instead of 22_050 Hz, the area has to be doubled +// and so do the addresses, to get back the echo at the correct time. +void ReverbIlleprih::ChangeSetting(u8 reverbIndex) +{ + const ReverbType reverbType = this->reverbTypes[reverbIndex]; + + this->currentSetting.dAPF1 = reverbType.dAPF1 << 3; + this->currentSetting.dAPF2 = reverbType.dAPF2 << 3; + this->currentSetting.vIIR = reverbType.vIIR / 32768.0f; + this->currentSetting.vCOMB1 = reverbType.vCOMB1 / 32768.0f; + this->currentSetting.vCOMB2 = reverbType.vCOMB2 / 32768.0f; + this->currentSetting.vCOMB3 = reverbType.vCOMB3 / 32768.0f; + this->currentSetting.vCOMB4 = reverbType.vCOMB4 / 32768.0f; + this->currentSetting.vWALL = reverbType.vWALL / 32768.0f; + this->currentSetting.vAPF1 = reverbType.vAPF1 / 32768.0f; + this->currentSetting.vAPF2 = reverbType.vAPF2 / 32768.0f; + this->currentSetting.mLSAME = reverbType.mLSAME << 3; + this->currentSetting.mRSAME = reverbType.mRSAME << 3; + this->currentSetting.mLCOMB1 = reverbType.mLCOMB1 << 3; + this->currentSetting.mRCOMB1 = reverbType.mRCOMB1 << 3; + this->currentSetting.mLCOMB2 = reverbType.mLCOMB2 << 3; + this->currentSetting.mRCOMB2 = reverbType.mRCOMB2 << 3; + this->currentSetting.dLSAME = reverbType.dLSAME << 3; + this->currentSetting.dRSAME = reverbType.dRSAME << 3; + this->currentSetting.mLDIFF = reverbType.mLDIFF << 3; + this->currentSetting.mRDIFF = reverbType.mRDIFF << 3; + this->currentSetting.mLCOMB3 = reverbType.mLCOMB3 << 3; + this->currentSetting.mRCOMB3 = reverbType.mRCOMB3 << 3; + this->currentSetting.mLCOMB4 = reverbType.mLCOMB4 << 3; + this->currentSetting.mRCOMB4 = reverbType.mRCOMB4 << 3; + this->currentSetting.dLDIFF = reverbType.dLDIFF << 3; + this->currentSetting.dRDIFF = reverbType.dRDIFF << 3; + this->currentSetting.mLAPF1 = reverbType.mLAPF1 << 3; + this->currentSetting.mRAPF1 = reverbType.mRAPF1 << 3; + this->currentSetting.mLAPF2 = reverbType.mLAPF2 << 3; + this->currentSetting.mRAPF2 = reverbType.mRAPF2 << 3; + this->currentSetting.vLIN = reverbType.vLIN / 32768.0f; + this->currentSetting.vRIN = reverbType.vRIN / 32768.0f; +} + +} // namespace SPU diff --git a/Source/AliveLibCommon/audio/Reverb.hpp b/Source/AliveLibCommon/audio/Reverb.hpp new file mode 100644 index 000000000..cd35d66c2 --- /dev/null +++ b/Source/AliveLibCommon/audio/Reverb.hpp @@ -0,0 +1,122 @@ +#pragma once + +#include + +namespace SPU { + +class Reverb +{ +public: + virtual std::pair Process(f32 left, f32 right) = 0; + virtual void ChangeVolume(u8 left, u8 right) = 0; + virtual void ChangeSetting(u8 setting) = 0; +}; + +/////////////////////// +// Duckstation +class ReverbDuckstation : public Reverb +{ +public: + std::pair Process(f32 left, f32 right) override; + void ChangeVolume(u8 left, u8 right) override; + void ChangeSetting(u8 setting) override; +}; + +/////////////////////// +// Illeprih +struct ReverbSetting +{ + u16 dAPF1; + u16 dAPF2; + f32 vIIR; + f32 vCOMB1; + f32 vCOMB2; + f32 vCOMB3; + f32 vCOMB4; + f32 vWALL; + f32 vAPF1; + f32 vAPF2; + u16 mLSAME; + u16 mRSAME; + u16 mLCOMB1; + u16 mRCOMB1; + u16 mLCOMB2; + u16 mRCOMB2; + u16 dLSAME; + u16 dRSAME; + u16 mLDIFF; + u16 mRDIFF; + u16 mLCOMB3; + u16 mRCOMB3; + u16 mLCOMB4; + u16 mRCOMB4; + u16 dLDIFF; + u16 dRDIFF; + u16 mLAPF1; + u16 mRAPF1; + u16 mLAPF2; + u16 mRAPF2; + f32 vLIN; + f32 vRIN; +}; + +struct ReverbType +{ + u16 dAPF1; + u16 dAPF2; + s16 vIIR; + s16 vCOMB1; + s16 vCOMB2; + s16 vCOMB3; + s16 vCOMB4; + s16 vWALL; + s16 vAPF1; + s16 vAPF2; + u16 mLSAME; + u16 mRSAME; + u16 mLCOMB1; + u16 mRCOMB1; + u16 mLCOMB2; + u16 mRCOMB2; + u16 dLSAME; + u16 dRSAME; + u16 mLDIFF; + u16 mRDIFF; + u16 mLCOMB3; + u16 mRCOMB3; + u16 mLCOMB4; + u16 mRCOMB4; + u16 dLDIFF; + u16 dRDIFF; + u16 mLAPF1; + u16 mRAPF1; + u16 mLAPF2; + u16 mRAPF2; + s16 vLIN; + s16 vRIN; +}; + +class ReverbIlleprih : public Reverb +{ +public: + ReverbIlleprih(); + std::pair Process(f32 left, f32 right) override; + void ChangeVolume(u8 left, u8 right) override; + void ChangeSetting(u8 setting) override; + +private: + // This is max value of u16 + 1. Making it so overflow can handle the looping + static const u32 workAreaSize = 0x10000; + float workArea[workAreaSize] = {0.0f}; + u16 currentAddress = 0; + static const ReverbType reverbTypes[10]; + ReverbSetting currentSetting; + float volumeLeft = 0.0f; + float volumeRight = 0.0f; + + float LoadReverb(u16 address); + void WriteReverb(u16 address, float value); + float SaturateSample(float sample); +}; + +} diff --git a/Source/AliveLibCommon/audio/Sequencer.cpp b/Source/AliveLibCommon/audio/Sequencer.cpp new file mode 100644 index 000000000..4e48fff0b --- /dev/null +++ b/Source/AliveLibCommon/audio/Sequencer.cpp @@ -0,0 +1,1074 @@ +#pragma once + +#include "Sequencer.hpp" +#include "SDL.h" +#include "Reverb.hpp" + +namespace SPU { + +// modified version of: +// https://github.com/stenzek/duckstation/blob/master/src/core/spu.cpp + + +////////////////////////// +// SPU state +std::mutex mutex; + +const int VOICE_SIZE_LIMIT = 32; // Can be increased to 32, but 24 is what PSX has +std::array voices; + +const int SEQUENCE_SIZE_LIMIT = 256; +std::vector sequences; + +const int PATCH_SIZE_LIMIT = 128; +std::array patches; + +// Can swap these out for testing. +// We could make them dependencies and configurable in game +// +//Reverb* reverb = new ReverbDuckstation(); +Reverb* reverb = new ReverbIlleprih(); +//InterpolationProvider* interpolation = new InterpolationProviderDuckstation(); +InterpolationProvider* interpolation = new InterpolationProviderIlleprih(); + +////////////////////////// +// SPU Internal mangement methods +void SPUInit(); +void SPUStopAll(); +void SPUReset(); + +Voice* SPUObtainVoice(s8 priority, u8 note, u8 patchId); +void SPUReleaseVoice(Voice* v); + +void SPUPatchAdd(Patch* patch); + +void SPUSeqAdd(Sequence* seq); +bool SPUSeqPlay(s32 seqId, s32 repeats); +void SPUSeqStop(s32 seqId); +void SPUSeqSetVolume(s32 seqId, s16 voll, s16 volr); +bool SPUSeqIsDone(s32 seqId); + +s32 SPUOneShotPlay(s32 patchId, u8 note, s16 voll, s16 volr, s32 pitch, s32 pitchMin, s32 pitchMax); +void SPUOneShotStop(s32 mask); + +void SPUTick(void* udata, Uint8* stream, int len); +void SPUTickSequences(); + + +////////////////////////// +// Public SPU methods +// Basically just a wrapper for mutex lock +void SPU::Init() +{ + mutex.lock(); + SPUInit(); + mutex.unlock(); +} + +void SPU::DeInit() +{ + mutex.lock(); + // This is called on AE quit and exit to desktop. + // We could gracefully stop SDL audio rendering, + // but AO never calls this on app exit, ignoring for now + mutex.unlock(); +} + +void SPU::Reset() +{ + mutex.lock(); + SPUReset(); + mutex.unlock(); +} + +void SPU::StopAll() +{ + mutex.lock(); + SPUStopAll(); + mutex.unlock(); +} + +void PatchAdd(Patch* patch) +{ + if (patch->id >= PATCH_SIZE_LIMIT) + { + throw std::runtime_error("PatchId is above PATCH_SIZE_LIMIT"); + } + mutex.lock(); + SPUPatchAdd(patch); + mutex.unlock(); +} + +void SPU::SeqAdd(Sequence* seq) +{ + mutex.lock(); + SPUSeqAdd(seq); + mutex.unlock(); +} + +bool SeqPlay(s32 seqId, s32 repeats, bool stopDuplicateSeq) +{ + mutex.lock(); + if (stopDuplicateSeq) + { + SPUSeqStop(seqId); + } + bool res = SPUSeqPlay(seqId, repeats); + mutex.unlock(); + return res; +} + +bool SeqPlay(s32 seqId, s32 repeats, s16 voll, s16 volr) +{ + mutex.lock(); + SPUSeqSetVolume(seqId, voll, volr); + bool res = SPUSeqPlay(seqId, repeats); + mutex.unlock(); + return res; +} + +void SeqStop(s32 seqId) +{ + mutex.lock(); + SPUSeqStop(seqId); + mutex.unlock(); +} + +void SeqSetVolume(s32 seqId, s16 voll, s16 volr) +{ + mutex.lock(); + SPUSeqSetVolume(seqId, voll, volr); + mutex.unlock(); +} + +bool SeqIsDone(s32 seqId) +{ + mutex.lock(); + bool res = SPUSeqIsDone(seqId); + mutex.unlock(); + return res; +} + +s32 SPU::OneShotPlay(s32 patchId, u8 note, s16 voll, s16 volr, s32 pitch, s32 pitchMin, s32 pitchMax) +{ + mutex.lock(); + s32 res = SPUOneShotPlay(patchId, note, voll, volr, pitch, pitchMin, pitchMax); + mutex.unlock(); + return res; +} + +void SPU::OneShotStop(s32 mask) +{ + mutex.lock(); + SPUOneShotStop(mask); + mutex.unlock(); +} + +void SDLCallback(void* udata, Uint8* stream, int len) +{ + mutex.lock(); + SPUTick(udata, stream, len); + mutex.unlock(); +} + + +////////////////////////// +// SEQUENCE +MIDIMessage* Sequence::createMIDIMessage() +{ + MIDIMessage* msg = new MIDIMessage(); + messages.push_back(msg); + return msg; +} + +void Sequence::Reset() +{ + play = false; + actionPos = 0; + trackStartTime = 0; + voll = 127; + volr = 127; + repeats = 0; + repeatLimit = 1; +} + +MIDIMessage* Sequence::next(u64 now) +{ + if (messages.size() <= 0) + { + return NULL; + } + + if (actionPos == 0) + { + // the track is just starting if we are at pos 0 + trackStartTime = now; + } + + u64 runtimeUs = std::max((u64) 0, now - trackStartTime) * 1000; // x1000 to conver to microseconds + float midiTickUs = tempoUs / ticksPerBeat; + u64 trackTick = u64(runtimeUs / midiTickUs); + + MIDIMessage* msg = messages.at(actionPos); + if (msg->tick <= trackTick) + { + actionPos++; + if (actionPos == messages.size()) + { + actionPos = 0; + repeats++; // we've looped the sequence + } + return msg; + } + return NULL; +} + + +////////////////////////// +// Duckstation helpers +template +constexpr u16 Truncate16(TValue value) +{ + return static_cast(static_cast::type>(value)); +} + +template +constexpr TReturn SignExtend(TValue value) +{ + return static_cast( + static_cast::type>(static_cast::type>(value))); +} + +template +constexpr u32 SignExtend32(TValue value) +{ + return SignExtend(value); +} + +template +constexpr TReturn ZeroExtend(TValue value) +{ + return static_cast(static_cast::type>( + static_cast::type>(value))); +} + +template +constexpr u32 ZeroExtend32(TValue value) +{ + return ZeroExtend(value); +} + +static constexpr s32 Clamp16(s32 value) +{ + return (value < -0x8000) ? -0x8000 : (value > 0x7FFF) ? 0x7FFF + : value; +} + +static constexpr s32 ApplyVolume(s32 sample, s16 volume) +{ + return (sample * s32(volume)) >> 15; +} + + +///////////////////////// +// SPU management +u64 timeSinceEpochMillisec() +{ + using namespace std::chrono; + return duration_cast(system_clock::now().time_since_epoch()).count(); +} + +void SPUInit() +{ + // Open SDL + SDL_Init(SDL_INIT_AUDIO); + + SDL_AudioSpec waveSpec; + waveSpec.callback = SDLCallback; + waveSpec.userdata = nullptr; + waveSpec.channels = 2; + waveSpec.freq = 44100; + waveSpec.samples = 512; + waveSpec.format = AUDIO_F32; + + /* Open the audio device */ + if (SDL_OpenAudio(&waveSpec, NULL) < 0) + { + fprintf(stderr, "Failed to initialize audio: %s\n", SDL_GetError()); + exit(-1); + } + + SDL_PauseAudio(0); + + int id = 1; + for (int i = 0; i < VOICE_SIZE_LIMIT; i++) + { + voices[i] = new Voice(interpolation); + voices[i]->id = id; + id = id << 1; + } + + for (int i = 0; i < PATCH_SIZE_LIMIT; i++) + { + patches[i] = nullptr; + } +} + +void SPUReset() +{ + SPUStopAll(); + + for (s16 i = 0; i < PATCH_SIZE_LIMIT; i++) + { + delete patches[i]; + patches[i] = nullptr; + } + + for (Sequence* seq : sequences) + { + delete seq; + } + sequences.clear(); +} + +void SPUStopAll() +{ + for (Sequence* seq : sequences) + { + seq->play = false; + } + + for (s16 i = 0; i < VOICE_SIZE_LIMIT; i++) + { + SPUReleaseVoice(voices[i]); + } +} + +Voice* SPUObtainVoice(s8 priority, u8 note, u8 patchId) +{ + note; + patchId; + + // 1. Always try to use a free voice + // 2. If no voice can be found - try using a repeated note that has the furthest offset + // 3. Reap voices that have 'x' many playing? Shooting with a slig non-stop uses too many voices + + Voice* available = NULL; + for (int i = 0; i < VOICE_SIZE_LIMIT; i++) + { + Voice* v = voices[i]; + if (v->complete) + { + SPUReleaseVoice(v); + } + + if (!v->inUse) + { + v->inUse = true; + return v; + } + + // Overlapping voices are a problem when sligs shoot continously. + // This is a simple implementation to overwrite some lower priority sound. + if (v->sample->priority < priority) + { + available = v; + } + } + + if (!available) + { + return NULL; + } + + // this is voice in use that we can reuse + SPUReleaseVoice(available); + available->inUse = true; + return available; +} + +void SPUReleaseVoice(Voice* v) +{ + v->Reset(); +} + +void SPUPatchAdd(Patch* patch) +{ + if (patches[patch->id]) + { + //delete patches[patch->_id]; + } + patches[patch->id] = patch; +} + +void SPUSeqAdd(Sequence* seq) +{ + sequences.push_back(seq); +} + +bool SPUSeqPlay(s32 seqId, s32 repeats) +{ + bool res = false; + for (Sequence* seq : sequences) + { + if (seq->id == seqId) + { + seq->repeatLimit = repeats; + seq->play = true; + res |= true; + } + } + return res; +} + +void SPUSeqStop(s32 seqId) +{ + for (Sequence* seq : sequences) + { + if (seq->id == seqId) + { + seq->Reset(); + for (Voice* v : voices) + { + if (v && v->sequence == seq) + { + v->offTime = timeSinceEpochMillisec(); + + // TODO - If I add volume sweeps this should use + // that instead of a hard stop which can cause popping + // SPUReleaseVoice(v); + } + } + } + } +} + +void SPUSeqSetVolume(s32 seqId, s16 voll, s16 volr) +{ + for (Sequence* seq : sequences) + { + if (seq->id == seqId) + { + seq->voll = voll; + seq->volr = volr; + } + } + for (Voice* v : voices) + { + if (v->sequence && v->sequence->id == seqId) + { + v->ConfigureVolume(127, 127); // max volume, just calling configure to calc again + } + } +} + +bool SPUSeqIsDone(s32 seqId) +{ + bool res = false; + for (Sequence* seq : sequences) + { + if (seq->id == seqId) + { + res |= seq->repeats >= seq->repeatLimit || !seq->play; + } + } + return res; +} + +s32 SPUOneShotPlay(s32 patchId, u8 note, s16 voll, s16 volr, s32 pitch, s32 pitchMin, s32 pitchMax) +{ + // - SoundEfx: apparently use sweeps and reuse the voice register (slig walking offscreen) + // - OneShots: do not reuse voice regester (slig turning) + // - Notes : reuese register if possible (chanting) + Patch* patch = patches[patchId]; + if (!patch) + { + return 0; + } + + int ids = 0; + for (Sample* sample : patch->samples) + { + if (!sample) + { + continue; + } + + if (note > sample->maxNote || note < sample->minNote) + { + continue; + } + + Voice* v = SPUObtainVoice(sample->priority, note, (u8) patchId); + if (!v) + { + return 0; + } + + pitchMax; // Not needed? + v->Configure(patch, sample); + v->ConfigureNote(note, pitch < pitchMin ? pitchMin : pitch); + v->ConfigureVolume((u8) voll, (u8) volr); + ids |= v->id; + } + + return ids; +} + +void SPUOneShotStop(s32 mask) +{ + for (int i = 0; i < VOICE_SIZE_LIMIT; i++) + { + if ((voices[i]->id & mask) != 0) + { + // TODO - not sure if this is right. + // Maybe it should trigger a release? + SPUReleaseVoice(voices[i]); + } + } +} + +void SPUTick(void* udata, Uint8* stream, int len) +{ + udata; + + // This runs at 44100hz + // 1. tick voices + // a. interpolate + // b. adsr + // c. pitch + // 2. run reverb + + // start/stop any sequence notes + SPUTickSequences(); + + float* AudioStream = (float*) stream; + int StreamLength = len / sizeof(float); + + f32 reverb_out_left = 0; + f32 reverb_out_right = 0; + + // Prepare voices for every position SDL is expecting + for (int i = 0; i < StreamLength; i += 2) + { + // set silence incase no voices are played + AudioStream[i] = 0; + AudioStream[i + 1] = 0; + + f32 leftSample = 0; + f32 rightSample = 0; + f32 reverb_in_left = 0; + f32 reverb_in_right = 0; + + // 1. Prepare all voices with reverb + for (Voice* v : voices) + { + if (!v->sample || v->complete || !v->inUse) + { + continue; + } + + std::tuple s = v->Tick(); + leftSample += std::get<0>(s); + rightSample += std::get<1>(s); + + // 0 Off + // 1 Vibrate + // 2 Portamento + // 3 1 & 2(Portamento and Vibrate on) + // 4 Reverb + if (v->sample->reverb != 0) + { + reverb->ChangeSetting(v->sample->reverb); + reverb_in_left += std::get<0>(s); + reverb_in_right += std::get<1>(s); + } + } + + // make value usable by SDL + leftSample = leftSample / 32768.0f; + rightSample = rightSample / 32768.0f; + + // 2. Process and mix in the reverb + std::tuple r = reverb->Process(reverb_in_left, reverb_in_right); + reverb_out_left = std::get<0>(r); + reverb_out_right = std::get<1>(r); + + //leftSample = reverb_out_left / 32768.0f; + //rightSample = reverb_out_right / 32768.0f; + leftSample += reverb_out_left / 32768.0f; + rightSample += reverb_out_right / 32768.0f; + + SDL_MixAudioFormat((Uint8*) (AudioStream + i), (const Uint8*) &leftSample, AUDIO_F32, sizeof(float), 80); + SDL_MixAudioFormat((Uint8*) (AudioStream + i + 1), (const Uint8*) &rightSample, AUDIO_F32, sizeof(float), 80); + } +} + +void SPUTickSequences() +{ + // TODO - convert this to be based on 44100hz instead of ms timestamp + + u64 now = timeSinceEpochMillisec(); + + // Tick sequences + for (Sequence* seq : sequences) + { + if (!seq || !seq->play) + { + continue; + } + + if (seq->repeats >= seq->repeatLimit && seq->repeatLimit > 0) + { + SPUSeqStop(seq->id); + continue; + } + + MIDIMessage* message; + while ((message = seq->next(now)) != NULL) + { + switch (message->type) + { + case NOTE_ON: + { + if (!seq->channels[message->channelId]->patch) + { + break; + } + + Channel* channel = seq->channels[message->channelId]; + Patch* patch = channel->patch; + for (Sample* sample : patch->samples) + { + if (!sample) + { + continue; + } + + if (message->note > sample->maxNote || message->note < sample->minNote) + { + continue; + } + + Voice* v = SPUObtainVoice(sample->priority, message->note, message->patchId); + if (!v) + { + continue; + } + + v->Configure(message, seq, channel, patch, sample); + v->ConfigureVolume(127, 127); // max volume, seq message data has calculated volume data + v->ConfigureNote(message->note, 0); + } + + break; + } + case NOTE_OFF: + { + for (Voice* v : voices) + { + if (v->inUse && v->IsMatch(seq, message->channelId, message->note)) + { + v->offTime = now; + } + } + break; + } + case PATCH_CHANGE: + { + seq->channels[message->channelId]->patch = patches[message->patchId]; + break; + } + case END_TRACK: + { + // repeats are handled in the sequence when messages restart at position 0 + break; + } + case PITCH_BEND: + { + for (Voice* v : voices) + { + if (v->inUse && v->IsMatch(seq, message->channelId)) + { + v->ConfigureNote(v->GetNote(), message->bend); + } + } + break; + } + } + } + } +} + + +////////////////////////// +// ADSR +ADSR parseADSR(u16 adsr1, u16 adsr2) +{ + ADSR adsr; + adsr.sustainLevel = (adsr1) & mask(4); + adsr.decayRate = (adsr1 >> 4) & mask(4); + adsr.attackRate = (adsr1 >> 8) & mask(7); + adsr.attackExponential = (adsr1 >> 15) & mask(1); + + adsr.releaseRate = adsr2 & mask(5); + adsr.releaseExponential = (adsr2 >> 5) & mask(1); + adsr.sustainRate = (adsr2 >> 6) & mask(7); + adsr.sustainDirection = (adsr2 >> 14) & mask(1); + adsr.sustainExponential = (adsr2 >> 15) & mask(1); + return adsr; +} + +struct ADSRTableEntry +{ + s32 ticks; + s32 step; +}; +enum : u32 +{ + NUM_ADSR_TABLE_ENTRIES = 128, + NUM_ADSR_DIRECTIONS = 2 // increasing, decreasing +}; +using ADSRTableEntries = std::array, NUM_ADSR_DIRECTIONS>; + +static constexpr ADSRTableEntries ComputeADSRTableEntries() +{ + ADSRTableEntries entries = {}; + for (u32 decreasing = 0; decreasing < 2; decreasing++) + { + for (u32 rate = 0; rate < NUM_ADSR_TABLE_ENTRIES; rate++) + { + if (rate < 48) + { + entries[decreasing][rate].ticks = 1; + if (decreasing != 0) + entries[decreasing][rate].step = static_cast(static_cast(-8 + static_cast(rate & 3)) << (11 - (rate >> 2))); + else + entries[decreasing][rate].step = (7 - static_cast(rate & 3)) << (11 - (rate >> 2)); + } + else + { + entries[decreasing][rate].ticks = 1 << (static_cast(rate >> 2) - 11); + if (decreasing != 0) + entries[decreasing][rate].step = (-8 + static_cast(rate & 3)); + else + entries[decreasing][rate].step = (7 - static_cast(rate & 3)); + } + } + } + + return entries; +} + +static constexpr ADSRTableEntries s_adsr_table = ComputeADSRTableEntries(); + + +////////////////////////// +// Voice +USHORT panMergeTable[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, + 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, + 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, + 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32, 0x33, + 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x40, 0x41, 0x42, 0x43, + 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, + 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, 0x60, 0x61, 0x62, 0x63, + 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x5B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, + 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, + 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, + 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, + 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, + 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F}; + + +static u16 MergePan(u16 pan1, u16 pan2) +{ + return panMergeTable[pan1 + pan2]; +} + +void Voice::Configure(MIDIMessage* msg, Sequence* seq, Channel* chan, Patch* pat, Sample* samp) +{ + this->message = msg; + this->sequence = seq; + this->channel = chan; + Configure(pat, samp); +} + +void Voice::Configure(Patch* pat, Sample* samp) +{ + this->patch = pat; + this->sample = samp; + + // current sample block is 28 samples in this block + 3 from the next block + // to achieve interpolation without going out of bounds + for (int i = 0; i < NUM_SAMPLES_FROM_LAST_ADPCM_BLOCK; i++) + { + currSamples[i] = 0; + } + memcpy(&currSamples[NUM_SAMPLES_FROM_LAST_ADPCM_BLOCK], sample->buffer, NUM_SAMPLES_PER_ADPCM_BLOCK * sizeof(s16)); +} + +void Voice::Reset() +{ + message = nullptr; + sequence = nullptr; + channel = nullptr; + patch = nullptr; + sample = nullptr; + + offTime = 0; + pitch = 0; + note = 0; + + adsrPhase = NONE; + adsrCounter = 0; + adsrCurrentLevel = 0; + adsrTargetLevel = MAX_VOLUME; + f_SampleOffset = 0; + vounter->Reset(); + HasSamples = true; + complete = false; + inUse = false; +} + +void Voice::ConfigureNote(u8 _note, u32 _pitch) +{ + this->note = _note; + this->pitch = _pitch; + noteStep = vounter->CalculateNoteStep(sample->rootNote, sample->rootNotePitchShift, sample->SampleRate, note, pitch); +} + +u8 Voice::GetNote() +{ + return note; +} + +// PSX table with lerped inbeatween values +f32 panTable[0x80] = { + 0x00, 0x02, 0x04, 0x06, 0x08, 0x0A, 0x0C, 0x0E, + 0x10, 0x12, 0x14, 0x16, 0x18, 0x1A, 0x1C, 0x1E, + 0x20, 0x22, 0x24, 0x26, 0x28, 0x2A, 0x2C, 0x2E, + 0x30, 0x32, 0x34, 0x36, 0x38, 0x3A, 0x3C, 0x3E, + 0x40, 0x42, 0x44, 0x46, 0x48, 0x4A, 0x4C, 0x4E, + 0x50, 0x52, 0x54, 0x56, 0x58, 0x5A, 0x5C, 0x5E, + 0x60, 0x62, 0x64, 0x66, 0x68, 0x6A, 0x6C, 0x6E, + 0x70, 0x72, 0x74, 0x76, 0x78, 0x78, 0x78, 0x78, + 0x78, 0x7A, 0x7C, 0x7E, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}; + +void Voice::ConfigureVolume(u8 volLeft, u8 volRight) +{ + f32 panVolumes[0x80]; + + for (size_t i = 0; i < 0x80; i++) + { + panVolumes[i] = panTable[i] / 128.0f; + } + + u8 channelPan = 64; + u8 patchPan = patch->pan; + u8 tonePan = sample->pan; + + f32 volumeLeft = 1.0f; + f32 volumeRight = 1.0f; + + if (sequence) + { + channelPan = channel->pan; + f32 volume = (channel->vol / 128.0f) * (message->velocity / 128.0f); + volumeLeft = volume * (sequence->voll / 128.0f); + volumeRight = volume * (sequence->volr / 128.0f); + } + + u16 panIndex = MergePan(channelPan, MergePan(patchPan, tonePan)); + volumeLeft = volumeLeft * panVolumes[127 - panIndex]; + volumeRight = volumeRight * panVolumes[panIndex]; + + volumeLeftModifier = volumeLeft * (volLeft / 128.0f) * (patch->vol / 128.0f) * (sample->volume / 128.0f); + volumeRightModifier = volumeRight * (volRight / 128.0f) * (patch->vol / 128.0f) * (sample->volume / 128.0f); +} + +bool Voice::IsMatch(Sequence* seq, u8 channelId) +{ + if (!this->sequence || !this->channel) + { + return false; + } + return this->sequence->id == seq->id && this->sequence->channels[channelId] == this->channel; +} + +bool Voice::IsMatch(Sequence* seq, u8 channelId, u8 _note) +{ + if (!IsMatch(seq, channelId)) + { + return false; + } + return this->note == _note; +} + +float Voice::Interpolate() +{ + // vounter (gauss interpolation table) runs at 28 byte blocks (NUM_SAMPLES_PER_ADPCM_BLOCK). + // move the sample offset forward every 28 bytes + u32 sampOffset = NUM_SAMPLES_FROM_LAST_ADPCM_BLOCK; + if (!this->HasSamples) + { + f_SampleOffset += NUM_SAMPLES_PER_ADPCM_BLOCK; + + // If it doesn't loop and we don't have enough in the last block, complete the voice + if (!sample->loop && f_SampleOffset + NUM_SAMPLES_PER_ADPCM_BLOCK >= sample->len) + { + complete = true; + return 0; + } + + // if the new offset is greater than the audio data we are looping back to the front + if (f_SampleOffset >= sample->len) + { + f_SampleOffset = f_SampleOffset - sample->len; + } + + // Copy the last 3 samples from the previous block to the front + memcpy(&currSamples[0], &currSamples[NUM_SAMPLES_PER_ADPCM_BLOCK], sampOffset * sizeof(u16)); + + s32 diff = f_SampleOffset + NUM_SAMPLES_PER_ADPCM_BLOCK - sample->len; + if (diff >= 0) + { + // the next block overflows, essentially the same as a for loop doing + // sample->buffer[(f_SampleOffset + i) % sample->len]; + memcpy(&currSamples[sampOffset], &sample->buffer[f_SampleOffset], (NUM_SAMPLES_PER_ADPCM_BLOCK - diff) * sizeof(s16)); + memcpy(&currSamples[sampOffset + NUM_SAMPLES_PER_ADPCM_BLOCK - diff], &sample->buffer[0], (diff) * sizeof(s16)); + } + else + { + memcpy(&currSamples[sampOffset], &sample->buffer[f_SampleOffset], NUM_SAMPLES_PER_ADPCM_BLOCK * sizeof(s16)); + } + } + + f32 out = vounter->Sample(currSamples); + + // noteStep is called in ConfigureNote() to save CPU cycles as this is called at 44100hz + this->HasSamples = !this->vounter->NextSampleBlock(noteStep); + return out; +} + +s16 Voice::TickAdsr() +{ + // UPDATE ADSR STATE - done at 441000hz + if (adsrPhase == NONE) + { + adsrPhase = ATTACK; + adsrDecreasing = false; + adsrRate = sample->adsr.attackRate; + adsrExponential = sample->adsr.attackExponential; + adsrTargetLevel = MAX_VOLUME; + adsrCounter = s_adsr_table[adsrDecreasing][adsrRate].ticks; + //adsrCurrentLevel = MAX_VOLUME; + } + else if (adsrPhase == ATTACK && adsrCurrentLevel >= adsrTargetLevel) + { + adsrPhase = DECAY; + adsrDecreasing = true; + adsrRate = sample->adsr.decayRate << 2; + adsrExponential = true; + adsrTargetLevel = static_cast(std::min((u32(sample->adsr.sustainLevel) + 1) * 0x800, MAX_VOLUME)); + adsrCounter = s_adsr_table[adsrDecreasing][adsrRate].ticks; + } + else if (adsrPhase == DECAY && adsrCurrentLevel <= adsrTargetLevel) + { + adsrPhase = SUSTAIN; + adsrDecreasing = sample->adsr.sustainDirection; + adsrRate = sample->adsr.sustainRate; + adsrExponential = sample->adsr.sustainExponential; + adsrTargetLevel = 0; + adsrCounter = s_adsr_table[adsrDecreasing][adsrRate].ticks; + } + else if (offTime != 0 && adsrPhase != RELEASE) + { + adsrPhase = RELEASE; + adsrDecreasing = true; + adsrRate = sample->adsr.releaseRate << 2; + adsrExponential = sample->adsr.releaseExponential; + adsrTargetLevel = 0; + adsrCounter = s_adsr_table[adsrDecreasing][adsrRate].ticks; + //adsrCurrentLevel = MIN_VOLUME; + } + else if (adsrPhase == RELEASE && adsrCurrentLevel <= adsrTargetLevel) + { + complete = true; + return 0; + } + + // UPDATE ADSR TICK STATE + adsrCounter--; + if (adsrCounter <= 0) + { + const ADSRTableEntry& table_entry = s_adsr_table[adsrDecreasing][adsrRate]; + s32 this_step = table_entry.step; + adsrCounter = table_entry.ticks; + + if (adsrExponential) + { + if (adsrDecreasing) + { + this_step = (this_step * adsrCurrentLevel) >> 15; + } + else + { + if (adsrCurrentLevel >= 0x6000) + { + if (adsrRate < 40) + { + this_step >>= 2; + } + else if (adsrRate >= 44) + { + adsrCounter >>= 2; + } + else + { + this_step >>= 1; + adsrCounter >>= 1; + } + } + } + } + + adsrCurrentLevel = static_cast( + std::clamp(static_cast(adsrCurrentLevel) + this_step, MIN_VOLUME, MAX_VOLUME)); + } + + return adsrCurrentLevel; +} + +std::tuple Voice::Tick() +{ + if (!sample) + { + return std::make_tuple(0.0f, 0.0f); + } + + // INTERPOLATION + s32 sampleData = (s32) Interpolate(); + + // Set the volume of the sample + s32 vol = ApplyVolume(sampleData, TickAdsr()); + + // TODO - apply voll and volr as sweeps.tick()? (VolumeEnvelope) + // it would be similar to the ADSR tick + // I believe sligs walking offscreen use a vol sweep + f32 leftA = vol * volumeLeftModifier; + f32 rightA = vol * volumeRightModifier; + return std::make_tuple(leftA, rightA); +} + +} // namespace diff --git a/Source/AliveLibCommon/audio/Sequencer.hpp b/Source/AliveLibCommon/audio/Sequencer.hpp new file mode 100644 index 000000000..f11a966cb --- /dev/null +++ b/Source/AliveLibCommon/audio/Sequencer.hpp @@ -0,0 +1,298 @@ +#pragma once + +#include "Interpolation.hpp" +#include "SDL.h" +#include +#include +#include +#include + +namespace SPU { + +const s16 MIN_VOLUME = 0; +const s16 MAX_VOLUME = 32767; + +struct ASDR +{ + // adsr1 + u32 sustainLevel; + u32 decayRate; + u32 attackRate; + u32 attackExponential; + + // adsr2 + u32 releaseRate; + u32 releaseExponential; + u32 sustainRate; + u32 sustainDirection; + u32 sustainExponential; +} typedef ADSR; +ADSR parseADSR(u16 adsr1, u16 adsr2); + + +/* +* Defines an audio sample. This contains raw audio data and its information +*/ +class Sample +{ +public: + Sample(s16* buf, u32 size, u32 sampleRate) + { + buffer = buf; + len = size; + SampleRate = sampleRate; + } + ~Sample() + { + + } + + u8 priority; + u8 volume; + u8 pan; + + // reverb style - 0 is none + s8 reverb; + bool loop; + + // Root Key + s32 rootNote; + s32 rootNotePitchShift; + + // Key range + u8 minNote; + u8 maxNote; + + ADSR adsr; + + s16* buffer; + u32 len; + u32 SampleRate; +}; + + +/* +* A patch is an instrument, think of a piano/keyboard. A piano may be configured +* to play drums on lower keys and guitar on upper keys. The instrument +* defines which sample(s) to play when a note is pressed. +*/ +class Patch +{ +public: + Patch(const u8 _id, u8 _vol, u8 _pan) + : id(_id) + , vol(_vol) + , pan(_pan) + { + for (int i = 0; i < SAMPLE_SIZE_LIMIT; i++) + { + samples[i] = nullptr; + } + } + ~Patch() + { + for (int i = 0; i < SAMPLE_SIZE_LIMIT; i++) + { + delete samples[i]; + samples[i] = nullptr; + } + } + + const u8 id; + const u8 vol; + const u8 pan; + + static const int SAMPLE_SIZE_LIMIT = 128; + std::array samples; +}; + + +enum MIDIMessageType +{ + NOTE_ON, + NOTE_OFF, + PATCH_CHANGE, + END_TRACK, + PITCH_BEND +}; + + +/* +* Defines what to do in a MIDI sequence (play note, stop note, etc) +*/ +class MIDIMessage +{ +public: + MIDIMessageType type; + u32 tick; + u8 channelId = 0; + u8 patchId = 0; + u8 note = 0; + u8 velocity = 0; + s16 bend; +}; + + +struct Channel +{ + // channels may have volums and pans too, but not implemented + Patch* patch; + u8 vol = 127; // I almost think this should default to something lower (64?), but I have no proof + u8 pan = 64; +}; + +/* +* A MIDI sequence (a song). Contains information on the tones (Notes/percussion) +* in the song. +*/ +class Sequence +{ +public: + Sequence() + { + for (int i = 0; i < 16; i++) + { + channels[i] = new Channel(); + } + } + ~Sequence() + { + for (int i = 0; i < 16; i++) + { + delete channels[i]; + } + for (auto msg : messages) + { + delete msg; + } + } + + s32 id; + std::atomic_bool play = false; + + s16 voll = 127; + s16 volr = 127; + + float tempoUs; // BPM - defined in in microseconds (us) + float ticksPerBeat; + + s32 repeatLimit = 1; + s32 repeats = 0; + + void Reset(); + MIDIMessage* createMIDIMessage(); + MIDIMessage* next(u64 now); + Channel* channels[16]; + +private: + std::vector messages; + u32 actionPos = 0; + u64 trackStartTime = 0; +}; + +enum ADSRPhase +{ + NONE, + ATTACK, + DECAY, + SUSTAIN, + RELEASE +}; + +class VolumeEnvelope +{ +public: + s16 current_level; + s16 target_level; + s32 counter; + bool decreasing; + void Tick(); +}; + +class Voice +{ +public: + Voice(InterpolationProvider* provider) + { + this->vounter = provider->Create(); + } + + s32 id; + + Sequence* sequence = nullptr; + Channel* channel = nullptr; + Patch* patch = nullptr; + Sample* sample = nullptr; + MIDIMessage* message = nullptr; + + bool inUse = false; + bool complete = false; + u64 offTime = 0; // when the note was released + + std::tuple Tick(); + void Configure(MIDIMessage* message, Sequence* seq, Channel* channel, Patch* patch, Sample* sample); + void Configure(Patch* patch, Sample* sample); + void ConfigureNote(u8 note, u32 pitch); + void ConfigureVolume(u8 volLeft, u8 volRight); + void Reset(); + u8 GetNote(); + + bool IsMatch(Sequence* seq, u8 channelId); + bool IsMatch(Sequence* seq, u8 channelId, u8 note); + +private: + // Volume modifer (multiple it by sample data) + f32 volumeLeftModifier; + f32 volumeRightModifier; + + // ADSR calculations + ADSRPhase adsrPhase = NONE; + s32 adsrCounter = 0; // decremented each midi tick + u32 adsrRate; + bool adsrDecreasing; + bool adsrExponential; + s16 adsrCurrentLevel = 0; + s16 adsrTargetLevel = MAX_VOLUME; // attack we want to reach max + + // Interpolation/pitch Calculations + std::unique_ptr vounter; + s16 currSamples[NUM_SAMPLES_FROM_LAST_ADPCM_BLOCK + NUM_SAMPLES_PER_ADPCM_BLOCK]; + u8 note; + s32 pitch; + u32 noteStep; + u32 f_SampleOffset = 0; + bool HasSamples; + + std::tuple CalculateVolume(); + f32 Interpolate(); + s16 TickAdsr(); +}; + + +void Init(); +void DeInit(); + +void Reset(); +void StopAll(); + +// returns an 'id' mask - more than one note may play during a oneshot. +// Each of the 24 voices have an id of (1 << x) where x is the voice num +s32 OneShotPlay(s32 patchId, u8 note, s16 voll, s16 volr, s32 pitch, s32 pitchMin, s32 pitchMax); +void OneShotStop(s32 mask); + +// Patches are instruments that contain samples. +// Once a patch is added it will be managed by the SPU +// do not modify or delete it. +void PatchAdd(Patch* patch); + +// Sequences define what notes to play "in a sequence" - a song. +// Once a sequence is added it will be managed by the SPU +// do not modify or delete it. +void SeqAdd(Sequence* seq); +bool SeqPlay(s32 seqId, s32 repeats, bool stopDuplicateSeq); +bool SeqPlay(s32 seqId, s32 repeats, s16 voll, s16 volr); +void SeqSetVolume(s32 seqId, s16 voll, s16 volr); +void SeqStop(s32 seqId); +bool SeqIsDone(s32 seqId); + +} // namespace diff --git a/Source/AliveLibCommon/audio/Stream.cpp b/Source/AliveLibCommon/audio/Stream.cpp new file mode 100644 index 000000000..d272a1c08 --- /dev/null +++ b/Source/AliveLibCommon/audio/Stream.cpp @@ -0,0 +1,92 @@ +#include +#include +#include +#include +#include "logger.hpp" +#include "Stream.hpp" + +namespace psx { + +Stream::Stream(std::vector&& data) +{ + mSize = data.size(); + auto s = std::make_unique(); + std::copy(data.begin(), data.end(), std::ostream_iterator(*s)); + mStream.reset(s.release()); + Seek(0); +} + +void Stream::ReadUInt8(Uint8& output) +{ + if (!mStream->read(reinterpret_cast(&output), sizeof(output))) + { + throw Exception("ReadUInt8 failure"); + } +} + +void Stream::ReadUInt32(Uint32& output) +{ + if (!mStream->read(reinterpret_cast(&output), sizeof(output))) + { + throw Exception("ReadUInt32 failure"); + } +} + +void Stream::ReadUInt16(Uint16& output) +{ + if (!mStream->read(reinterpret_cast(&output), sizeof(output))) + { + throw Exception("ReadUInt32 failure"); + } +} + +void Stream::ReadSInt16(Sint16& output) +{ + if (!mStream->read(reinterpret_cast(&output), sizeof(output))) + { + throw Exception("ReadSInt16 failure"); + } +} + +void Stream::ReadBytes(Sint8* pDest, size_t destSize) +{ + if (!mStream->read(reinterpret_cast(pDest), destSize)) + { + throw Exception("ReadBytes failure"); + } +} + +void Stream::ReadBytes(Uint8* pDest, size_t destSize) +{ + if (!mStream->read(reinterpret_cast(pDest), destSize)) + { + throw Exception("ReadBytes failure"); + } +} + +void Stream::Seek(size_t pos) +{ + if (!mStream->seekg(pos)) + { + throw Exception("Seek failure"); + } +} + +bool Stream::AtEnd() const +{ + const int c = mStream->peek(); + return (c == EOF); +} + +size_t Stream::Pos() const +{ + const size_t pos = static_cast(mStream->tellg()); + return pos; +} + +size_t Stream::Size() const +{ + return mSize; +} + +} // namespace psx diff --git a/Source/AliveLibCommon/audio/Stream.hpp b/Source/AliveLibCommon/audio/Stream.hpp new file mode 100644 index 000000000..29a312207 --- /dev/null +++ b/Source/AliveLibCommon/audio/Stream.hpp @@ -0,0 +1,47 @@ +#pragma once + +#include +#include +#include +#include "SDL_types.h" + +namespace psx { + +class Exception : public std::exception +{ +public: + explicit Exception(const char* msg) + : mMsg(msg) + { + } + + const char* what() const throw() override + { + return mMsg; + } + +private: + const char* mMsg; +}; + +class Stream +{ +public: + Stream(std::vector&& data); + void ReadUInt8(Uint8& output); + void ReadUInt32(Uint32& output); + void ReadUInt16(Uint16& output); + void ReadSInt16(Sint16& output); + void ReadBytes(Sint8* pDest, size_t destSize); + void ReadBytes(Uint8* pDest, size_t destSize); + void Seek(size_t pos); + size_t Pos() const; + size_t Size() const; + bool AtEnd() const; + +private: + mutable std::unique_ptr mStream; + size_t mSize = 0; +}; + +} // namespace psx diff --git a/Source/AliveLibCommon/audio/oddio.h b/Source/AliveLibCommon/audio/oddio.h new file mode 100644 index 000000000..150bb8d53 --- /dev/null +++ b/Source/AliveLibCommon/audio/oddio.h @@ -0,0 +1,567 @@ +#pragma once + +namespace ODDIO { + +// All patches in this file are based on Steams US version. + +// VH files contain vag attributes +// VB files contain audio data + +// SAMPLE RATE LOOKUP TABLES +// [0] == vag (unique per .VH, even if the audio sample is duplicated in other VH) +// [1] == sample rate of pc audio file +// if a sound is not found within these tables (which a lot aren't) +// the sample rate is the default psx sample rate of 8000hz + +// VAG ATTRIBUTE FIXES +// The pc vag attributes were assumingly altered to account +// for higher sample rate audio files. Assumingly during that +// process volumes, root notes, pitches, etc were altered. These +// tables correct the pc sound attributes to be like psx. + +// First column (vag) number can be correlated between rate and attr tables +// for a given VH/VB pair (Example, D1SNDFX.VH and D1SNDFX.VB) + +// RATE TABLES +// {vag, sampleRate} + +// VH TABLES +// {vag, vagOffset, [256 bits of vag data]} + +// vagOffset is based on counting the index when loading the pc +// vag attributes from level files. There are many indexes that are +// skipped, so you may see numbers go in order from [2, 35, 123] + +// Interesting differences +// 1. Whistle - on psx it's one sample for a sequence, on pc it's full sample for each low/high whistles +// 2. Slog sleeping sounds are missing on pc, uses slig sleeping sound instead +// 3. Minor differences in Abe turning sounde +// 4. psx seems to have a couple extra general efx lick rock throws and stuff + + +//////////////////////////////////////// +// D1.lvl - D1SNDFX.VH +const std::vector> AO_D1SNDFX_RATE = {{ + {41, 22050}, + {49, 16000}, + {50, 16000}, + {83, 16000}, + {107, 22050}, + {109, 16000}, + {110, 22050}, + {114, 22050}, + {118, 22050}, + {119, 22050}}}; + +const std::vector> AO_D1SNDFX_VH = {{ + // Whistle {106, 496, 0x407f007f, 0x7f000048, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x6a003a, 0xc100c0, 0xc300c2}, + // {0, 497, 0x0, 0x0, 0x0, 0xb2b10000, 0x5fc080ff, 0x3a, 0xc100c0, 0xc300c2}, + {107, 512, 0x407f007f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x6b003b, 0xc100c0, 0xc300c2}, + {109, 514, 0x407f007f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x6d003b, 0xc100c0, 0xc300c2}, + {110, 515, 0x407f007f, 0x3f3f005b, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x6e003b, 0xc100c0, 0xc300c2}, + {114, 545, 0x407f007f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x72003d, 0xc100c0, 0xc300c2}, + {118, 549, 0x407f007f, 0x3b3b4658, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x76003d, 0xc100c0, 0xc300c2}, + {119, 550, 0x407f007f, 0x3a3a4657, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x77003d, 0xc100c0, 0xc300c2}, + {49, 720, 0x6e047f, 0x7f005a48, 0x0, 0xb2b10000, 0xdfe780ff, 0x310050, 0xc100c0, 0xc300c2}, + {49, 721, 0x7f6e047f, 0x7f006148, 0x0, 0xb2b10000, 0xdfe780ff, 0x310050, 0xc100c0, 0xc300c2}, + {41, 736, 0x407f047f, 0x7f00465a, 0x0, 0xb2b10000, 0x5febb4ff, 0x290051, 0xc100c0, 0xc300c2}, + {69, 752, 0x7f047f, 0x7e000454, 0x0, 0xb2b10000, 0x5fefbbff, 0x450052, 0xc100c0, 0xc300c2}, + {69, 753, 0x7f7f045b, 0x7e000e54, 0x0, 0xb2b10000, 0x5fefbbff, 0x450052, 0xc100c0, 0xc300c2}, + {69, 800, 0x407f0465, 0x7f000054, 0x0, 0xb2b10000, 0xdfec80ff, 0x450055, 0xc100c0, 0xc300c2}}}; + + +//////////////////////////////////////// +// D2.lvl - D2SNDFX.VH +const std::vector> AO_D2SNDFX_RATE = {{ + {51, 22050}, + {61, 16000}, + {84, 16000}, + {98, 22050}, + {100, 16000}, + {101, 22050}, + {105, 22050}, + {109, 22050}, + {110, 22050}}}; + +const std::vector> AO_D2SNDFX_VH = {{ + {93, 272, 0x2c7f047f, 0x24240060, 0x0, 0xb2b10000, 0x1fc080ff, 0x5d0014, 0xc100c0, 0xc300c2}, + {93, 274, 0x407f047f, 0x2b2b0060, 0x0, 0xb2b10000, 0x1fc080ff, 0x5d0014, 0xc100c0, 0xc300c2}, + {93, 276, 0x547f047f, 0x30300060, 0x0, 0xb2b10000, 0x1fc080ff, 0x5d0014, 0xc100c0, 0xc300c2}, + // Whistle {97, 576, 0x407f047f, 0x7f000048, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x61003a, 0xc100c0, 0xc300c2}, + // {0, 577, 0x0, 0x0, 0x0, 0xb2b10000, 0x5fc080ff, 0x3a, 0xc100c0, 0xc300c2}, + {98, 592, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x62003b, 0xc100c0, 0xc300c2}, + {100, 594, 0x407f047f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x64003b, 0xc100c0, 0xc300c2}, + {101, 595, 0x407f047f, 0x3f3f005b, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x65003b, 0xc100c0, 0xc300c2}, + {105, 625, 0x407f047f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x69003d, 0xc100c0, 0xc300c2}, + {109, 629, 0x407f047f, 0x3b3b4658, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x6d003d, 0xc100c0, 0xc300c2}, + {110, 630, 0x407f047f, 0x3a3a4657, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x6e003d, 0xc100c0, 0xc300c2}, + {61, 800, 0x6e047f, 0x7f005a48, 0x0, 0xb2b10000, 0xdfe780ff, 0x3d0050, 0xc100c0, 0xc300c2}, + {61, 801, 0x7f6e047f, 0x7f006148, 0x0, 0xb2b10000, 0xdfe780ff, 0x3d0050, 0xc100c0, 0xc300c2}, + {51, 816, 0x407f047f, 0x7f00465a, 0x0, 0xb2b10000, 0x5febb4ff, 0x330051, 0xc100c0, 0xc300c2}, + {85, 832, 0x407f0465, 0x7f000460, 0x0, 0xb2b10000, 0xdfefbbff, 0x550052, 0xc100c0, 0xc300c2}, + {85, 864, 0x407f0465, 0x7f000454, 0x0, 0xb2b10000, 0xdfec80ff, 0x550055, 0xc100c0, 0xc300c2}}}; + + +//////////////////////////////////////// +// D7.LVL - D2ENDER.VH +const std::vector> AO_D2ENDER_RATE = {{ + {18, 22050}, + {25, 16000}, + {41, 16000}, + {46, 22050}, + {48, 16000}, + {49, 22050}, + {53, 22050}, + {57, 22050}, + {58, 22050}}}; + +const std::vector> AO_D2ENDER_VH = {{ + // Whistle {45, 320, 0x407f047f, 0x7f000048, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x2d003a, 0xc100c0, 0xc300c2}, + // {0, 321, 0x0, 0x0, 0x0, 0xb2b10000, 0x5fc080ff, 0x3a, 0xc100c0, 0xc300c2}, + {46, 336, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x2e003b, 0xc100c0, 0xc300c2}, + {48, 338, 0x407f047f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x30003b, 0xc100c0, 0xc300c2}, + {49, 339, 0x407f047f, 0x3f3f005b, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x31003b, 0xc100c0, 0xc300c2}, + {53, 369, 0x407f047f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x35003d, 0xc100c0, 0xc300c2}, + {57, 373, 0x407f047f, 0x3b3b4658, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x39003d, 0xc100c0, 0xc300c2}, + {58, 374, 0x407f047f, 0x3a3a4657, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x3a003d, 0xc100c0, 0xc300c2}, + {25, 608, 0x6e047f, 0x7f005a48, 0x0, 0xb2b10000, 0xdfe780ff, 0x190050, 0xc100c0, 0xc300c2}, + {25, 609, 0x7f6e047f, 0x7f006148, 0x0, 0xb2b10000, 0xdfe780ff, 0x190050, 0xc100c0, 0xc300c2}, + {18, 624, 0x407f047f, 0x7f00465a, 0x0, 0xb2b10000, 0x5febb4ff, 0x120051, 0xc100c0, 0xc300c2}, + {42, 640, 0x407f0465, 0x7f000460, 0x0, 0xb2b10000, 0xdfefbbff, 0x2a0052, 0xc100c0, 0xc300c2}, + {42, 656, 0x407f0465, 0x7f000454, 0x0, 0xb2b10000, 0xdfec80ff, 0x2a0055, 0xc100c0, 0xc300c2}}}; + + +//////////////////////////////////////// +// E1.LVL - E1SNDFX.VH +const std::vector> AO_E1SNDFX_RATE = {{ + {49, 44100}, + {50, 44100}, + {51, 22050}, + {60, 16000}, + {61, 16000}, + {85, 22050}, + {87, 16000}, + {88, 22050}, + {92, 22050}, + {96, 22050}, + {97, 22050}}}; + +const std::vector> AO_E1SNDFX_VH = {{ + // Whistle {84, 448, 0x407f007f, 0x7f000048, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x54003a, 0xc100c0, 0xc300c2}, + // {0, 449, 0x0, 0x0, 0x0, 0xb2b10000, 0x5fc080ff, 0x3a, 0xc100c0, 0xc300c2}, + {85, 464, 0x407f007f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x55003b, 0xc100c0, 0xc300c2}, + {87, 466, 0x407f007f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x57003b, 0xc100c0, 0xc300c2}, + {88, 467, 0x407f007f, 0x3f3f005b, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x58003b, 0xc100c0, 0xc300c2}, + {92, 497, 0x407f007f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x5c003d, 0xc100c0, 0xc300c2}, + {96, 501, 0x407f007f, 0x3b3b4658, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x60003d, 0xc100c0, 0xc300c2}, + {97, 502, 0x407f007f, 0x3a3a4657, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x61003d, 0xc100c0, 0xc300c2}, + {51, 608, 0x407f0400, 0x7f000066, 0x0, 0xb2b10000, 0xdfec80ff, 0x33004a, 0xc100c0, 0xc300c2}, + {49, 624, 0x7f007f, 0x30304659, 0x0, 0xb2b10101, 0xdff080ff, 0x31004b, 0xc100c0, 0xc300c2}, + {50, 625, 0x7f7f047f, 0x3c3c4665, 0x0, 0xb2b10101, 0xdff080ff, 0x32004b, 0xc100c0, 0xc300c2}, + {50, 626, 0x407f047f, 0x48484671, 0x0, 0xb2b10101, 0xdff080ff, 0x32004b, 0xc100c0, 0xc300c2}, + {51, 640, 0x407f044f, 0x7f00465a, 0x0, 0xb2b10101, 0xdff0c0ff, 0x33004c, 0xc100c0, 0xc300c2}, + {60, 704, 0x6e047f, 0x7f005a48, 0x0, 0xb2b10000, 0xdfe780ff, 0x3c0050, 0xc100c0, 0xc300c2}, + {60, 705, 0x7f6e047f, 0x7f006148, 0x0, 0xb2b10000, 0xdfe780ff, 0x3c0050, 0xc100c0, 0xc300c2}, + {51, 720, 0x407f047f, 0x7f00465a, 0x0, 0xb2b10000, 0x5febb4ff, 0x330051, 0xc100c0, 0xc300c2}}}; + + +//////////////////////////////////////// +// E2.LVL - E2SNDFX.VH +const std::vector> AO_E2SNDFX_RATE = {{ + {50, 44100}, + {51, 44100}, + {52, 22050}, + {63, 16000}, + {64, 16000}, + {76, 22050}, + {78, 16000}, + {79, 22050}, + {83, 22050}, + {87, 22050}, + {88, 22050}}}; + +const std::vector> AO_E2SNDFX_VH = {{ + // Whistle {75, 432, 0x407f007f, 0x7f000048, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x4b003a, 0xc100c0, 0xc300c2}, + // {0, 433, 0x0, 0x0, 0x0, 0xb2b10000, 0x5fc080ff, 0x3a, 0xc100c0, 0xc300c2}, + {76, 448, 0x407f007f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x4c003b, 0xc100c0, 0xc300c2}, + {78, 450, 0x407f007f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x4e003b, 0xc100c0, 0xc300c2}, + {79, 451, 0x407f007f, 0x3f3f005b, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x4f003b, 0xc100c0, 0xc300c2}, + {83, 481, 0x407f007f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x53003d, 0xc100c0, 0xc300c2}, + {87, 485, 0x407f007f, 0x3b3b4658, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x57003d, 0xc100c0, 0xc300c2}, + {88, 486, 0x407f007f, 0x3a3a4657, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x58003d, 0xc100c0, 0xc300c2}, + {52, 608, 0x407f044f, 0x7f00465a, 0x0, 0xb2b10101, 0x5ff0c0ff, 0x34004c, 0xc100c0, 0xc300c2}, + {63, 672, 0x6e047f, 0x7f005a48, 0x0, 0xb2b10000, 0xdfe780ff, 0x3f0050, 0xc100c0, 0xc300c2}, + {63, 673, 0x7f6e047f, 0x7f006148, 0x0, 0xb2b10000, 0xdfe780ff, 0x3f0050, 0xc100c0, 0xc300c2}, + {52, 688, 0x407f047f, 0x7f00465a, 0x0, 0xb2b10000, 0x5febb4ff, 0x340051, 0xc100c0, 0xc300c2}}}; + + +//////////////////////////////////////// +// F1.LVL - F1SNDFX.VH +const std::vector> AO_F1SNDFX_RATE = {{ + {55, 16000}, + {56, 16000}, + {92, 22050}, + {94, 22050}, + {96, 16000}, + {97, 22050}, + {101, 22050}, + {105, 22050}, + {106, 22050}}}; + +const std::vector> AO_F1SNDFX_VH = {{ + // Whistle {93, 416, 0x407f007f, 0x7f000048, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x5d003a, 0xc100c0, 0xc300c2}, + // {0, 417, 0x0, 0x0, 0x0, 0xb2b10000, 0x5fc080ff, 0x3a, 0xc100c0, 0xc300c2}, + {94, 432, 0x407f007f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x5e003b, 0xc100c0, 0xc300c2}, + {96, 434, 0x407f007f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x60003b, 0xc100c0, 0xc300c2}, + {97, 435, 0x407f007f, 0x3f3f005b, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x61003b, 0xc100c0, 0xc300c2}, + {101, 465, 0x407f007f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x65003d, 0xc100c0, 0xc300c2}, + {105, 469, 0x407f007f, 0x3b3b4658, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x69003d, 0xc100c0, 0xc300c2}, + {106, 470, 0x407f007f, 0x3a3a4657, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x6a003d, 0xc100c0, 0xc300c2}, + {55, 688, 0x6e047f, 0x7f005a48, 0x0, 0xb2b10000, 0xdfe780ff, 0x370050, 0xc100c0, 0xc300c2}, + {55, 689, 0x7f6e047f, 0x7f006148, 0x0, 0xb2b10000, 0xdfe780ff, 0x370050, 0xc100c0, 0xc300c2}, + {92, 704, 0x407f047f, 0x7f00465a, 0x0, 0xb2b10000, 0xdfebb4ff, 0x5c0051, 0xc100c0, 0xc300c2}}}; + + +//////////////////////////////////////// +// F2.LVL - F2SNDFX.VH +const std::vector> AO_F2SNDFX_RATE = {{ + {46, 22050}, + {57, 16000}, + {58, 16000}, + {72, 11025}, + {97, 16000}, + {101, 22050}, + {103, 16000}, + {104, 22050}, + {108, 22050}, + {112, 22050}, + {113, 22050}}}; + +const std::vector> AO_F2SNDFX_VH = {{ + {6, 99, 0x407f0402, 0x2b240060, 0x0, 0xb2b10202, 0x5feb80ff, 0x60007, 0xc100c0, 0xc300c2}, + {10, 100, 0x407f0402, 0x2d2c0074, 0x0, 0xb2b10202, 0x5feb80ff, 0xa0007, 0xc100c0, 0xc300c2}, + {11, 101, 0x407f0402, 0x2e2e0060, 0x0, 0xb2b10202, 0x5ffd80ff, 0xb0007, 0xc100c0, 0xc300c2}, + {26, 102, 0x407f0402, 0x2f2f0060, 0x0, 0xb2b10202, 0x5feb80ff, 0x1a0007, 0xc100c0, 0xc300c2}, + {27, 103, 0x407f0402, 0x30300060, 0x0, 0xb2b10202, 0x5feb80ff, 0x1b0007, 0xc100c0, 0xc300c2}, + {40, 104, 0x407f0402, 0x37310060, 0x0, 0xb2b10202, 0x5feb80ff, 0x280007, 0xc100c0, 0xc300c2}, + {49, 105, 0x407f0402, 0x3938006c, 0x0, 0xb2b10202, 0x5feb80ff, 0x310007, 0xc100c0, 0xc300c2}, + {53, 106, 0x407f0402, 0x4341007f, 0x0, 0xb2b10202, 0x5feb80ff, 0x350007, 0xc100c0, 0xc300c2}, + {59, 108, 0x407f0402, 0x4b45007f, 0x0, 0xb2b10202, 0x5feb80ff, 0x3b0007, 0xc100c0, 0xc300c2}, + {97, 272, 0x2c7f047f, 0x24240060, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x610014, 0xc100c0, 0xc300c2}, + {97, 274, 0x407f047f, 0x2b2b0060, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x610014, 0xc100c0, 0xc300c2}, + {97, 276, 0x547f047f, 0x30300060, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x610014, 0xc100c0, 0xc300c2}, + // Whistle {100, 608, 0x407f047f, 0x7f000048, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x64003a, 0xc100c0, 0xc300c2}, + // {0, 609, 0x0, 0x0, 0x0, 0xb2b10000, 0x5fc080ff, 0x3a, 0xc100c0, 0xc300c2}, + {101, 624, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x65003b, 0xc100c0, 0xc300c2}, + {103, 626, 0x407f047f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x67003b, 0xc100c0, 0xc300c2}, + {104, 627, 0x407f047f, 0x3f3f005b, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x68003b, 0xc100c0, 0xc300c2}, + {108, 657, 0x407f047f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x6c003d, 0xc100c0, 0xc300c2}, + {112, 661, 0x407f047f, 0x3b3b4658, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x70003d, 0xc100c0, 0xc300c2}, + {113, 662, 0x407f047f, 0x3a3a4657, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x71003d, 0xc100c0, 0xc300c2}, + {57, 864, 0x6e047f, 0x7f005a48, 0x0, 0xb2b10000, 0xdfe780ff, 0x390050, 0xc100c0, 0xc300c2}, + {57, 865, 0x7f6e047f, 0x7f006148, 0x0, 0xb2b10000, 0xdfe780ff, 0x390050, 0xc100c0, 0xc300c2}, + {46, 880, 0x407f047f, 0x7f00465a, 0x0, 0xb2b10000, 0x5febb4ff, 0x2e0051, 0xc100c0, 0xc300c2}}}; + + +//////////////////////////////////////// +// F4.LVL - F2ENDER.VH +const std::vector> AO_F2ENDER_RATE = {{ + {27, 22050}, + {35, 16000}, + {36, 16000}, + {46, 11025}, + {60, 22050}, + {62, 16000}, + {63, 22050}, + {67, 22050}, + {71, 22050}, + {72, 22050}, + {93, 3800}}}; + +const std::vector> AO_F2ENDER_VH = {{ + // Whistle {59, 352, 0x407f047f, 0x7f000048, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x3b003a, 0xc100c0, 0xc300c2}, + // {0, 353, 0x0, 0x0, 0x0, 0xb2b10000, 0x5fc080ff, 0x3a, 0xc100c0, 0xc300c2}, + {60, 368, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x3c003b, 0xc100c0, 0xc300c2}, + {62, 370, 0x407f047f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x3e003b, 0xc100c0, 0xc300c2}, + {63, 371, 0x407f047f, 0x3f3f005b, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x3f003b, 0xc100c0, 0xc300c2}, + {67, 401, 0x407f047f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x43003d, 0xc100c0, 0xc300c2}, + {71, 405, 0x407f047f, 0x3b3b4658, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x47003d, 0xc100c0, 0xc300c2}, + {72, 406, 0x407f047f, 0x3a3a4657, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x48003d, 0xc100c0, 0xc300c2}, + {35, 624, 0x6e047f, 0x7f005a48, 0x0, 0xb2b10000, 0xdfe780ff, 0x230050, 0xc100c0, 0xc300c2}, + {35, 625, 0x7f6e047f, 0x7f006148, 0x0, 0xb2b10000, 0xdfe780ff, 0x230050, 0xc100c0, 0xc300c2}, + {27, 640, 0x407f047f, 0x7f00465a, 0x0, 0xb2b10000, 0x5febb4ff, 0x1b0051, 0xc100c0, 0xc300c2}}}; + + +//////////////////////////////////////// +// L1.LVL - MLSNDFX.VH +const std::vector> AO_MLSNDFX_RATE = {{ + {41, 16000}, + {74, 16000}, + {96, 22050}, + {98, 16000}, + {99, 22050}, + {103, 22050}, + {107, 22050}, + {108, 22050}, + {130, 22050}}}; + +const std::vector> AO_MLSNDFX_VH = {{ + {134, 224, 0x2c7f047f, 0x24240060, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x860014, 0xc100c0, 0xc300c2}, + {134, 226, 0x407f047f, 0x2b2b0060, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x860014, 0xc100c0, 0xc300c2}, + {134, 228, 0x547f047f, 0x30300060, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x860014, 0xc100c0, 0xc300c2}, + // Whistle {95, 496, 0x407f047f, 0x7f000048, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x5f003a, 0xc100c0, 0xc300c2}, + // {0, 497, 0x0, 0x0, 0x0, 0xb2b10000, 0x5fc080ff, 0x3a, 0xc100c0, 0xc300c2}, + {96, 512, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x60003b, 0xc100c0, 0xc300c2}, + {98, 514, 0x407f047f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x62003b, 0xc100c0, 0xc300c2}, + {99, 515, 0x407f047f, 0x3f3f005b, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x63003b, 0xc100c0, 0xc300c2}, + {103, 545, 0x407f047f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x67003d, 0xc100c0, 0xc300c2}, + {107, 549, 0x407f047f, 0x3b3b4658, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x6b003d, 0xc100c0, 0xc300c2}, + {108, 550, 0x407f047f, 0x3a3a4657, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x6c003d, 0xc100c0, 0xc300c2}, + {74, 752, 0x6e047f, 0x7f005a48, 0x0, 0xb2b10000, 0xdfe780ff, 0x4a0050, 0xc100c0, 0xc300c2}, + {74, 753, 0x7f6e047f, 0x7f006148, 0x0, 0xb2b10000, 0xdfe780ff, 0x4a0050, 0xc100c0, 0xc300c2}, + {130, 768, 0x407f047f, 0x7f00465a, 0x0, 0xb2b10000, 0xdfebb4ff, 0x820051, 0xc100c0, 0xc300c2}}}; + + +//////////////////////////////////////// +// S1.LVL / C1.LVL - OPTSNDEFX.VH and MUNK.VH +const std::vector> AO_MUNK_RATE = {{}}; + +const std::vector> AO_MUNK_VH = {{}}; + +const std::vector> AO_OPTSNDFX_RATE = {{ + {1, 4000}, + {7, 44100}, + {8, 44100}, + {9, 22050}, + {14, 16000}, + {16, 22050}, + {18, 16000}, + {19, 22050}, + {23, 22050}, + {27, 22050}, + {28, 22050}}}; + +const std::vector> AO_OPTSNDFX_VH = {{ + {7, 16, 0x7f007f, 0x30304659, 0x0, 0xb2b10101, 0xdfe680ff, 0x70001, 0xc100c0, 0xc300c2}, + {8, 17, 0x7f7f047f, 0x3c3c4665, 0x0, 0xb2b10101, 0xdfe680ff, 0x80001, 0xc100c0, 0xc300c2}, + {8, 18, 0x407f047f, 0x48484671, 0x0, 0xb2b10101, 0xdfe680ff, 0x80001, 0xc100c0, 0xc300c2}, + {9, 32, 0x407f044f, 0x7f00465a, 0x0, 0xb2b10101, 0xdff0c0ff, 0x90002, 0xc100c0, 0xc300c2}, + {14, 64, 0x6e047f, 0x7f005a48, 0x0, 0xb2b17f7f, 0xdfe780ff, 0xe0004, 0xc100c0, 0xc300c2}, + {14, 65, 0x7f6e047f, 0x7f006148, 0x0, 0xb2b17f7f, 0xdfe780ff, 0xe0004, 0xc100c0, 0xc300c2}, + // Whistle {15, 144, 0x407f007f, 0x7f000048, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0xf003a, 0xc100c0, 0xc300c2}, + // {0, 145, 0x0, 0x0, 0x0, 0xb2b10000, 0x5fc080ff, 0x3a, 0xc100c0, 0xc300c2}, + {16, 160, 0x407f007f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0xdffd80ff, 0x10003b, 0xc100c0, 0xc300c2}, + {18, 162, 0x407f007f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0xdffd80ff, 0x12003b, 0xc100c0, 0xc300c2}, + {19, 163, 0x407f007f, 0x3f3f005b, 0x0, 0xb2b17f7f, 0xdffd80ff, 0x13003b, 0xc100c0, 0xc300c2}, + {23, 193, 0x407f007f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0xdffd80ff, 0x17003d, 0xc100c0, 0xc300c2}, + {27, 197, 0x407f007f, 0x3b3b4658, 0x0, 0xb2b17f7f, 0xdffd80ff, 0x1b003d, 0xc100c0, 0xc300c2}, + {28, 198, 0x407f007f, 0x3a3a4657, 0x0, 0xb2b17f7f, 0xdffd80ff, 0x1c003d, 0xc100c0, 0xc300c2}}}; + + +//////////////////////////////////////// +// R1.LVL / R2.LVL - RFSNDEFX.VH +const std::vector> AO_RFSNDFX_RATE = {{ + {44, 22050}, + {54, 16000}, + {55, 16000}, + {100, 22050}, + {102, 16000}, + {103, 22050}, + {107, 22050}, + {111, 22050}, + {112, 22050}}}; + +const std::vector> AO_RFSNDFX_VH = {{ + {88, 25, 0x407f0400, 0x39390045, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x580001, 0xc100c0, 0xc300c2}, + {132, 80, 0x4064047f, 0x3030006c, 0x0, 0xb2b17f7f, 0xdff580ff, 0x840006, 0xc100c0, 0xc300c2}, + {133, 81, 0x4053047f, 0x30300060, 0x0, 0xb2b17f7f, 0xd170bebf, 0x850006, 0xc100c0, 0xc300c2}, + {133, 82, 0x407f047f, 0x24240054, 0x0, 0xb2b17f7f, 0xdff0bdff, 0x850006, 0xc100c0, 0xc300c2}, + {94, 97, 0x407f0402, 0x30240054, 0x0, 0xb2b10000, 0xdfeb80ff, 0x5e0007, 0xc100c0, 0xc300c2}, + {1, 98, 0x407f0402, 0x23236a54, 0x0, 0xb2b10000, 0xdfe080ff, 0x10007, 0xc100c0, 0xc300c2}, + {2, 99, 0x407f0402, 0x2222005e, 0x0, 0xb2b10000, 0x1fc080ff, 0x20007, 0xc100c0, 0xc300c2}, + {3, 100, 0x407f0402, 0x21210051, 0x0, 0xb2b10000, 0x1fc080ff, 0x30007, 0xc100c0, 0xc300c2}, + {7, 101, 0x407f0402, 0x2020004a, 0x0, 0xb2b10000, 0x1fc080ff, 0x70007, 0xc100c0, 0xc300c2}, + {9, 102, 0x407f0402, 0x1f1f0049, 0x0, 0xb2b10000, 0x1fc080ff, 0x90007, 0xc100c0, 0xc300c2}, + {12, 103, 0x407f0402, 0x1e1e0042, 0x0, 0xb2b10000, 0x1fc080ff, 0xc0007, 0xc100c0, 0xc300c2}, + {16, 104, 0x407f0402, 0x33310061, 0x0, 0xb2b10000, 0x1fc080ff, 0x100007, 0xc100c0, 0xc300c2}, + {17, 105, 0x407f0402, 0x3f340060, 0x0, 0xb2b10000, 0x1fc080ff, 0x110007, 0xc100c0, 0xc300c2}, + {88, 193, 0x407f047f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x58000f, 0xc100c0, 0xc300c2}, + {72, 194, 0x407f007f, 0x3c3c0048, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x48000f, 0xc100c0, 0xc300c2}, + {93, 226, 0x407f047f, 0x3b3b4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x5d0011, 0xc100c0, 0xc300c2}, + {73, 256, 0x407f047f, 0x30300054, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x490013, 0xc100c0, 0xc300c2}, + {73, 368, 0x407f047f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x49001e, 0xc100c0, 0xc300c2}, + {78, 384, 0x407f0452, 0x3c3c0054, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x4e001f, 0xc100c0, 0xc300c2}, + {85, 400, 0x407f047f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x550020, 0xc100c0, 0xc300c2}, + {73, 448, 0x407f047f, 0x3c3c005a, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x490026, 0xc100c0, 0xc300c2}, + {68, 464, 0x407f047f, 0x3c3c0448, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x440027, 0xc100c0, 0xc300c2}, + {69, 465, 0x407f047f, 0x4040044c, 0x0, 0xb2b17f7f, 0x5feb80ff, 0x450027, 0xc100c0, 0xc300c2}, + {70, 466, 0x407f0451, 0x4242045a, 0x0, 0xb2b17f7f, 0x5feb80ff, 0x460027, 0xc100c0, 0xc300c2}, + {71, 467, 0x407f044b, 0x4343044f, 0x0, 0xb2b17f7f, 0x5feb80ff, 0x470027, 0xc100c0, 0xc300c2}, + {72, 468, 0x407f047f, 0x41410459, 0x0, 0xb2b17f7f, 0x5feb80ff, 0x480027, 0xc100c0, 0xc300c2}, + {74, 469, 0x4075047f, 0x4444005c, 0x0, 0xb2b17f7f, 0x5fe080ff, 0x4a0027, 0xc100c0, 0xc300c2}, + {75, 470, 0x407f047f, 0x3d3d0457, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x4b0027, 0xc100c0, 0xc300c2}, + {76, 480, 0x407f047f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0x5fe080ff, 0x4c0028, 0xc100c0, 0xc300c2}, + {77, 481, 0x407f047f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x4d0028, 0xc100c0, 0xc300c2}, + {87, 496, 0x407f047f, 0x40404659, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x57002a, 0xc100c0, 0xc300c2}, + {129, 512, 0x407f0419, 0x3e3e465b, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x81002c, 0xc100c0, 0xc300c2}, + {130, 513, 0x407f0419, 0x3f3f465c, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x82002c, 0xc100c0, 0xc300c2}, + {84, 528, 0x407f047f, 0x30304659, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x54002d, 0xc100c0, 0xc300c2}, + {86, 544, 0x407f0400, 0x3c3c003c, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x56002e, 0xc100c0, 0xc300c2}, + {89, 560, 0x407f047f, 0x7f000054, 0x0, 0xb2b10000, 0x1fc080ff, 0x59002f, 0xc100c0, 0xc300c2}, + {89, 561, 0x5450047f, 0x7f000048, 0x0, 0xb2b10000, 0x1fc080ff, 0x59002f, 0xc100c0, 0xc300c2}, + {89, 562, 0x4064047f, 0x7f00005a, 0x0, 0xb2b10000, 0x1fc080ff, 0x59002f, 0xc100c0, 0xc300c2}, + {90, 563, 0x2c6e047f, 0x7f000060, 0x0, 0xb2b10000, 0x1fc080ff, 0x5a002f, 0xc100c0, 0xc300c2}, + {90, 564, 0x2c55047f, 0x7f00006c, 0x0, 0xb2b10000, 0x1fc080ff, 0x5a002f, 0xc100c0, 0xc300c2}, + {90, 565, 0x544b047f, 0x7f000072, 0x0, 0xb2b10000, 0x1fc080ff, 0x5a002f, 0xc100c0, 0xc300c2}, + {91, 576, 0x407f047f, 0x24240054, 0x0, 0xb2b17f7f, 0x5fcb80ff, 0x5b0030, 0xc100c0, 0xc300c2}, + {92, 577, 0x407f047f, 0x2a2a0054, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x5c0030, 0xc100c0, 0xc300c2}, + {92, 578, 0x407f047f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x5c0030, 0xc100c0, 0xc300c2}, + {91, 579, 0x407f047f, 0x3e3e0056, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x5b0030, 0xc100c0, 0xc300c2}, + {97, 580, 0x407f047f, 0x3f3f0057, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x610030, 0xc100c0, 0xc300c2}, + {95, 592, 0x407f047f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x5f0031, 0xc100c0, 0xc300c2}, + {96, 593, 0x407f0440, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x600031, 0xc100c0, 0xc300c2}, + // Whistle {98, 608, 0x407f047f, 0x7f000048, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x62003a, 0xc100c0, 0xc300c2}, + // {0, 609, 0x0, 0x0, 0x0, 0xb2b10000, 0x5fc080ff, 0x3a, 0xc100c0, 0xc300c2}, + {99, 624, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x63003b, 0xc100c0, 0xc300c2}, + {100, 625, 0x407f047f, 0x3e3e465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x64003b, 0xc100c0, 0xc300c2}, + {101, 626, 0x407f047f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x65003b, 0xc100c0, 0xc300c2}, + {102, 627, 0x407f047f, 0x3f3f005b, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x66003b, 0xc100c0, 0xc300c2}, + {103, 640, 0x407f047f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x67003c, 0xc100c0, 0xc300c2}, + {104, 641, 0x407f047f, 0x3f3f465c, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x68003c, 0xc100c0, 0xc300c2}, + {105, 656, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x69003d, 0xc100c0, 0xc300c2}, + {106, 657, 0x407f047f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x6a003d, 0xc100c0, 0xc300c2}, + {107, 658, 0x407f047f, 0x37374653, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x6b003d, 0xc100c0, 0xc300c2}, + {108, 659, 0x407f047f, 0x38384655, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x6c003d, 0xc100c0, 0xc300c2}, + {109, 660, 0x407f047f, 0x39394656, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x6d003d, 0xc100c0, 0xc300c2}, + {110, 661, 0x407f047f, 0x3b3b4658, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x6e003d, 0xc100c0, 0xc300c2}, + {111, 662, 0x407f047f, 0x3a3a4657, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x6f003d, 0xc100c0, 0xc300c2}, + {112, 663, 0x407f047f, 0x4040465d, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x70003d, 0xc100c0, 0xc300c2}, + {113, 664, 0x407f047f, 0x4141004d, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x71003d, 0xc100c0, 0xc300c2}, + {114, 672, 0x407f047f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x72003e, 0xc100c0, 0xc300c2}, + {115, 673, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x73003e, 0xc100c0, 0xc300c2}, + {116, 674, 0x407f047f, 0x3e3e465b, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x74003e, 0xc100c0, 0xc300c2}, + {117, 675, 0x407f047f, 0x3f3f465c, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x75003e, 0xc100c0, 0xc300c2}, + {118, 688, 0x407f047f, 0x25254642, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x76003f, 0xc100c0, 0xc300c2}, + {119, 689, 0x407f047f, 0x26264643, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x77003f, 0xc100c0, 0xc300c2}, + {120, 690, 0x407f047f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x78003f, 0xc100c0, 0xc300c2}, + {121, 691, 0x407f047f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x79003f, 0xc100c0, 0xc300c2}, + {122, 704, 0x407f047f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x7a0040, 0xc100c0, 0xc300c2}, + {123, 705, 0x407f047f, 0x3e3e465b, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x7b0040, 0xc100c0, 0xc300c2}, + {124, 706, 0x407f047f, 0x27274644, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x7c0040, 0xc100c0, 0xc300c2}, + {125, 707, 0x407f047f, 0x3f3f465c, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x7d0040, 0xc100c0, 0xc300c2}, + {126, 720, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x7e0041, 0xc100c0, 0xc300c2}, + {127, 721, 0x407f047f, 0x4040465d, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x7f0041, 0xc100c0, 0xc300c2}, + {128, 736, 0x407f047f, 0x7f004667, 0x0, 0xb2b17f7f, 0xdfefaaff, 0x800042, 0xc100c0, 0xc300c2}, + {83, 752, 0x7f0404, 0x5050046c, 0x0, 0xb2b10000, 0xdfeb80ff, 0x530046, 0xc100c0, 0xc300c2}, + {83, 753, 0x7f7f0404, 0x5151046d, 0x0, 0xb2b10000, 0xdfeb80ff, 0x530046, 0xc100c0, 0xc300c2}, + {79, 768, 0x407f005a, 0x2f00003c, 0x0, 0xb2b10000, 0xdfe680ff, 0x4f004a, 0xc100c0, 0xc300c2}, + {80, 769, 0x407f045a, 0x36300048, 0x0, 0xb2b10000, 0xdfe680ff, 0x50004a, 0xc100c0, 0xc300c2}, + {81, 770, 0x407f0456, 0x43370054, 0x0, 0xb2b10000, 0xdfe680ff, 0x51004a, 0xc100c0, 0xc300c2}, + {82, 771, 0x407f0456, 0x4f440060, 0x0, 0xb2b10000, 0xdfe680ff, 0x52004a, 0xc100c0, 0xc300c2}, + {83, 772, 0x7f0455, 0x5050006c, 0x0, 0xb2b10000, 0xdfe680ff, 0x53004a, 0xc100c0, 0xc300c2}, + {83, 773, 0x7f7f0453, 0x5151006d, 0x0, 0xb2b10000, 0xdfe680ff, 0x53004a, 0xc100c0, 0xc300c2}, + {131, 786, 0x406e0055, 0x3c3c0048, 0x0, 0xb2b10000, 0xdfeb80ff, 0x83004d, 0xc100c0, 0xc300c2}, + {54, 832, 0x6e047f, 0x7f005a48, 0x0, 0xb2b10000, 0xdfe780ff, 0x360050, 0xc100c0, 0xc300c2}, + {54, 833, 0x7f6e047f, 0x7f006148, 0x0, 0xb2b10000, 0xdfe780ff, 0x360050, 0xc100c0, 0xc300c2}, + {44, 848, 0x407f047f, 0x7f00465a, 0x0, 0xb2b10000, 0x5febb4ff, 0x2c0051, 0xc100c0, 0xc300c2}}}; + + +//////////////////////////////////////// +// R6.LVL - RFENDER.VH +// NOTE: psx and pc differ a lot in this one and +// it seems unusable. Did I screw something up here? +const std::vector> AO_RFENDER_RATE = {{ + {36, 22050}, + {45, 16000}, + {46, 16000}, + {70, 22050}, + {72, 16000}, + {73, 22050}, + {77, 22050}, + {81, 22050}, + {82, 22050}}}; + +const std::vector> AO_RFENDER_VH = {{ + {60, 25, 0x407f0400, 0x39390045, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x3c0001, 0xc100c0, 0xc300c2}, + {64, 81, 0x407f0402, 0x30240054, 0x0, 0xb2b10000, 0xdfeb80ff, 0x400007, 0xc100c0, 0xc300c2}, + {63, 178, 0x407f047f, 0x3b3b4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x3f0011, 0xc100c0, 0xc300c2}, + {54, 208, 0x407f047f, 0x30300054, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x360013, 0xc100c0, 0xc300c2}, + {107, 272, 0x2c7f047f, 0x24240030, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x6b0019, 0xc100c0, 0xc300c2}, + {107, 274, 0x547f047f, 0x24246e31, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x6b0019, 0xc100c0, 0xc300c2}, + {57, 304, 0x407f0452, 0x3c3c0054, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x39001f, 0xc100c0, 0xc300c2}, + {58, 320, 0x407f047f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x3a0020, 0xc100c0, 0xc300c2}, + {54, 352, 0x407f047f, 0x3c3c005a, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x360026, 0xc100c0, 0xc300c2}, + {110, 368, 0x407f047f, 0x3c3c0448, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x6e0027, 0xc100c0, 0xc300c2}, + {111, 369, 0x4075047f, 0x4444005c, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x6f0027, 0xc100c0, 0xc300c2}, + {55, 384, 0x407f047f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0x5fe080ff, 0x370028, 0xc100c0, 0xc300c2}, + {59, 400, 0x407f0400, 0x3c3c003c, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x3b002e, 0xc100c0, 0xc300c2}, + {0, 401, 0x0, 0x0, 0x0, 0xb2b10000, 0x5fc080ff, 0x2e, 0xc100c0, 0xc300c2}, + {0, 402, 0x0, 0x0, 0x0, 0xb2b10000, 0x5fc080ff, 0x2e, 0xc100c0, 0xc300c2}, + {0, 403, 0x0, 0x0, 0x0, 0xb2b10000, 0x5fc080ff, 0x2e, 0xc100c0, 0xc300c2}, + {0, 404, 0x0, 0x0, 0x0, 0xb2b10000, 0x5fc080ff, 0x2e, 0xc100c0, 0xc300c2}, + {61, 416, 0x407f047f, 0x24240054, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x3d0030, 0xc100c0, 0xc300c2}, + {62, 417, 0x407f047f, 0x2a2a0054, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x3e0030, 0xc100c0, 0xc300c2}, + {65, 432, 0x407f047f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x410031, 0xc100c0, 0xc300c2}, + {66, 433, 0x407f0440, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x420031, 0xc100c0, 0xc300c2}, + {66, 434, 0x407f047f, 0x3e3e0056, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x420031, 0xc100c0, 0xc300c2}, + {100, 448, 0x407f047f, 0x24244641, 0x0, 0xb2b17f7f, 0x5fc380ff, 0x640032, 0xc100c0, 0xc300c2}, + {101, 449, 0x407f047e, 0x3030464d, 0x0, 0xb2b17f7f, 0x5fcb80ff, 0x650032, 0xc100c0, 0xc300c2}, + // Whistle {68, 464, 0x407f047f, 0x7f000048, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x44003a, 0xc100c0, 0xc300c2}, + // {0, 465, 0x0, 0x0, 0x0, 0xb2b10000, 0x5fc080ff, 0x3a, 0xc100c0, 0xc300c2}, + {0, 466, 0x0, 0x0, 0x0, 0xb2b10000, 0x5fc080ff, 0x3a, 0xc100c0, 0xc300c2}, + {0, 467, 0x0, 0x0, 0x0, 0xb2b10000, 0x5fc080ff, 0x3a, 0xc100c0, 0xc300c2}, + {69, 480, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x45003b, 0xc100c0, 0xc300c2}, + {70, 481, 0x407f047f, 0x3e3e465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x46003b, 0xc100c0, 0xc300c2}, + {73, 496, 0x407f047f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x49003c, 0xc100c0, 0xc300c2}, + {74, 497, 0x407f047f, 0x3f3f465c, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x4a003c, 0xc100c0, 0xc300c2}, + {0, 498, 0x0, 0x0, 0x0, 0xb2b10000, 0x5fc080ff, 0x3c, 0xc100c0, 0xc300c2}, + {0, 499, 0x0, 0x0, 0x0, 0xb2b10000, 0x5fc080ff, 0x3c, 0xc100c0, 0xc300c2}, + {0, 500, 0x0, 0x0, 0x0, 0xb2b10000, 0x5fc080ff, 0x3c, 0xc100c0, 0xc300c2}, + {0, 501, 0x0, 0x0, 0x0, 0xb2b10000, 0x5fc080ff, 0x3c, 0xc100c0, 0xc300c2}, + {0, 502, 0x0, 0x0, 0x0, 0xb2b10000, 0x5fc080ff, 0x3c, 0xc100c0, 0xc300c2}, + {0, 503, 0x0, 0x0, 0x0, 0xb2b10000, 0x5fc080ff, 0x3c, 0xc100c0, 0xc300c2}, + {0, 504, 0x0, 0x0, 0x0, 0xb2b10000, 0x5fc080ff, 0x3c, 0xc100c0, 0xc300c2}, + {75, 512, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x4b003d, 0xc100c0, 0xc300c2}, + {76, 513, 0x407f047f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x4c003d, 0xc100c0, 0xc300c2}, + {77, 514, 0x407f047f, 0x37374653, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x4d003d, 0xc100c0, 0xc300c2}, + {78, 515, 0x407f047f, 0x38384655, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x4e003d, 0xc100c0, 0xc300c2}, + {84, 528, 0x407f047f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x54003e, 0xc100c0, 0xc300c2}, + {85, 529, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x55003e, 0xc100c0, 0xc300c2}, + {86, 530, 0x407f047f, 0x3e3e465b, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x56003e, 0xc100c0, 0xc300c2}, + {87, 531, 0x407f047f, 0x3f3f465c, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x57003e, 0xc100c0, 0xc300c2}, + {88, 544, 0x407f047f, 0x25254642, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x58003f, 0xc100c0, 0xc300c2}, + {89, 545, 0x407f047f, 0x26264643, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x59003f, 0xc100c0, 0xc300c2}, + {90, 546, 0x407f047f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x5a003f, 0xc100c0, 0xc300c2}, + {91, 547, 0x407f047f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x5b003f, 0xc100c0, 0xc300c2}, + {92, 560, 0x407f047f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x5c0040, 0xc100c0, 0xc300c2}, + {93, 561, 0x407f047f, 0x3e3e465b, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x5d0040, 0xc100c0, 0xc300c2}, + {96, 576, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x600041, 0xc100c0, 0xc300c2}, + {98, 592, 0x407f047f, 0x7f004667, 0x0, 0xb2b17f7f, 0xdfefaaff, 0x620042, 0xc100c0, 0xc300c2}, + {0, 593, 0x0, 0x0, 0x0, 0xb2b10000, 0x5fc080ff, 0x42, 0xc100c0, 0xc300c2}, + {0, 594, 0x0, 0x0, 0x0, 0xb2b10000, 0x5fc080ff, 0x42, 0xc100c0, 0xc300c2}, + {0, 595, 0x0, 0x0, 0x0, 0xb2b10000, 0x5fc080ff, 0x42, 0xc100c0, 0xc300c2}, + {102, 608, 0x4064047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x660043, 0xc100c0, 0xc300c2}, + {103, 609, 0x4064047f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x670043, 0xc100c0, 0xc300c2}, + {104, 610, 0x4064047f, 0x3e3e465b, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x680043, 0xc100c0, 0xc300c2}, + {105, 611, 0x4064047f, 0x3f3f465c, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x690043, 0xc100c0, 0xc300c2}, + {108, 612, 0x4064047f, 0x40404660, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x6c0043, 0xc100c0, 0xc300c2}, + {109, 613, 0x4064047f, 0x41414661, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x6d0043, 0xc100c0, 0xc300c2}, + {0, 614, 0x0, 0x0, 0x0, 0xb2b10000, 0x5fc080ff, 0x43, 0xc100c0, 0xc300c2}, + {0, 615, 0x0, 0x0, 0x0, 0xb2b10000, 0x5fc080ff, 0x43, 0xc100c0, 0xc300c2}, + {0, 616, 0x0, 0x0, 0x0, 0xb2b10000, 0x5fc080ff, 0x43, 0xc100c0, 0xc300c2}, + {0, 617, 0x0, 0x0, 0x0, 0xb2b10000, 0x5fc080ff, 0x43, 0xc100c0, 0xc300c2}, + {0, 618, 0x0, 0x0, 0x0, 0xb2b10000, 0x5fc080ff, 0x43, 0xc100c0, 0xc300c2}, + {0, 619, 0x0, 0x0, 0x0, 0xb2b10000, 0x5fc080ff, 0x43, 0xc100c0, 0xc300c2}, + {112, 624, 0x7f0404, 0x5050046c, 0x0, 0xb2b10000, 0xdfeb80ff, 0x700046, 0xc100c0, 0xc300c2}, + {112, 625, 0x7f7f0404, 0x5151046d, 0x0, 0xb2b10000, 0xdfeb80ff, 0x700046, 0xc100c0, 0xc300c2}, + {0, 626, 0x0, 0x0, 0x0, 0xb2b10000, 0x5fc080ff, 0x46, 0xc100c0, 0xc300c2}, + {113, 640, 0x407f005a, 0x2f00003c, 0x0, 0xb2b10000, 0xdfe680ff, 0x71004a, 0xc100c0, 0xc300c2}, + {114, 641, 0x407f045a, 0x36300048, 0x0, 0xb2b10000, 0xdfe680ff, 0x72004a, 0xc100c0, 0xc300c2}, + {37, 656, 0x407f0454, 0x3f3f004b, 0x0, 0xb2b10000, 0x5feb80ff, 0x25004d, 0xc100c0, 0xc300c2}, + {38, 657, 0x407f005a, 0x3b00003c, 0x0, 0xb2b10000, 0xdfeb80ff, 0x26004d, 0xc100c0, 0xc300c2}, + {99, 658, 0x406e0055, 0x3c3c0048, 0x0, 0xb2b10000, 0xdfeb80ff, 0x63004d, 0xc100c0, 0xc300c2}, + {42, 672, 0x407f0478, 0x47005a49, 0x0, 0xb2b10000, 0xdff0a0ff, 0x2a004e, 0xc100c0, 0xc300c2}, + {43, 673, 0x407f0478, 0x7f48005d, 0x0, 0xb2b10000, 0xdfeb80ff, 0x2b004e, 0xc100c0, 0xc300c2}, + {44, 688, 0x407f0455, 0x7f250048, 0x0, 0xb2b10000, 0xdfeb80ff, 0x2c004f, 0xc100c0, 0xc300c2}}}; + +} diff --git a/Source/relive_config.h.in b/Source/relive_config.h.in index a8a8eebb8..4cd177e65 100644 --- a/Source/relive_config.h.in +++ b/Source/relive_config.h.in @@ -17,5 +17,6 @@ #cmakedefine01 USE_SDL2_SOUND #cmakedefine01 USE_SDL2_IO #cmakedefine01 RENDERER_OPENGL +#cmakedefine01 AUDIO_SPU_EMULATION #cmakedefine BUILD_NUMBER @BUILD_NUMBER@ #cmakedefine CI_PROVIDER "@CI_PROVIDER@" diff --git a/options.cmake b/options.cmake index 3cb55d185..3f2a3a28e 100644 --- a/options.cmake +++ b/options.cmake @@ -17,4 +17,5 @@ option(ORIGINAL_GAME_FIXES "Fixes ALL known gameplay bugs" ON) option(ORIGINAL_GAME_FIX_AUTO_TURN "Fixes the auto-turn bug commonly used in speedruns" OFF) option(ORIGINAL_GAME_FIX_DEATH_DELAY_AO "Fixes the death delay glitch commonly used in speedruns" OFF) option(RENDERER_OPENGL "Use OpenGL hardware accelerated rendering." OFF) +option(AUDIO_SPU_EMULATION "Emulate the PSX SPU audio - SDL2 is required" ON) CONFIGURE_FILE(${CMAKE_CURRENT_SOURCE_DIR}/Source/relive_config.h.in ${CMAKE_CURRENT_SOURCE_DIR}/Source/AliveLibCommon/relive_config.h)