From d8309d9d4467155ac244ac9053122d160c3c6f85 Mon Sep 17 00:00:00 2001 From: Sean Horton <3359434+sean-horton@users.noreply.github.com> Date: Mon, 18 Apr 2022 17:17:43 -0400 Subject: [PATCH 01/82] get audio panning working --- Source/AliveLibAE/Sound/PsxSpuApi.cpp | 4 +++- Source/AliveLibAE/Sound/Sound.cpp | 33 ++++++++++++++++++++++++++- Source/AliveLibAO/Midi.cpp | 5 +++- 3 files changed, 39 insertions(+), 3 deletions(-) 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..e104b9b6f 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 + 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..156704121 100644 --- a/Source/AliveLibAO/Midi.cpp +++ b/Source/AliveLibAO/Midi.cpp @@ -507,7 +507,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, From 5def8d71d0bf347c1875b752ddadd7b056acaba0 Mon Sep 17 00:00:00 2001 From: Sean Horton <3359434+sean-horton@users.noreply.github.com> Date: Mon, 18 Apr 2022 18:26:33 -0400 Subject: [PATCH 02/82] revert accidental change of prev code --- Source/AliveLibAE/Sound/Sound.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/AliveLibAE/Sound/Sound.cpp b/Source/AliveLibAE/Sound/Sound.cpp index e104b9b6f..0cebcdaf0 100644 --- a/Source/AliveLibAE/Sound/Sound.cpp +++ b/Source/AliveLibAE/Sound/Sound.cpp @@ -230,7 +230,7 @@ EXPORT s32 CC SND_PlayEx_4EF740(const SoundEntry* pSnd, s32 panLeft, s32 panRigh // OLD PAN const s32 panConverted = (DSBPAN_RIGHT * (panLeft2 - panRight)) / 127; // From PSX pan range to DSound pan range - pDSoundBuffer->SetPan(panConverted); // Fix Inverted Stereo + pDSoundBuffer->SetPan(-panConverted); // Fix Inverted Stereo // OLD PAN END // NEW PAN From c2cbbb108628826b2b3768e1da1463106cb8b45d Mon Sep 17 00:00:00 2001 From: Sean Horton <3359434+sean-horton@users.noreply.github.com> Date: Sat, 1 Oct 2022 22:20:01 -0400 Subject: [PATCH 03/82] stubs for alternate audio --- Source/AliveLibAO/Midi.cpp | 100 +++++++++++++++++++++++++++++++++++++ Source/relive_config.h.in | 1 + options.cmake | 1 + 3 files changed, 102 insertions(+) diff --git a/Source/AliveLibAO/Midi.cpp b/Source/AliveLibAO/Midi.cpp index 156704121..e17927af5 100644 --- a/Source/AliveLibAO/Midi.cpp +++ b/Source/AliveLibAO/Midi.cpp @@ -26,6 +26,7 @@ namespace AO { +#if !ALTERNATE_AUDIO const s32 kSeqTableSizeAO = 164; ALIVE_VAR(1, 0x9F12D8, SeqIds, sSeq_Ids_word_9F12D8, {}); @@ -1148,5 +1149,104 @@ EXPORT void CC SND_StopAll_4762D0() SsUtAllKeyOff_49EDE0(0); } +#else +EXPORT void CC SsUtAllKeyOff_49EDE0(s32 mode) +{ + mode; +} + +EXPORT void CC SND_Reset_476BA0() +{ + +} + +EXPORT void CC SND_Load_VABS_477040(SoundBlockInfo* pSoundBlockInfo, s32 reverb) +{ + pSoundBlockInfo; + reverb; + + LvlFileRecord* pVabHeaderFile = sLvlArchive_4FFD60.Find_File_Record_41BED0(pSoundBlockInfo->field_0_vab_header_name); + pVabHeaderFile; +} + +EXPORT void CC SND_Stop_Channels_Mask_4774A0(s32 mask) +{ + mask; +} + +EXPORT void CC SND_Load_Seqs_477AB0(OpenSeqHandleAE* pSeqTable, const char_type* bsqFileName) +{ + pSeqTable; + bsqFileName; +} + +EXPORT s16 CC SND_SEQ_PlaySeq_4775A0(SeqId idx, s32 repeatCount, s16 bDontStop) +{ + idx; + repeatCount; + bDontStop; + return 1; +} + +EXPORT void CC SND_Seq_Stop_477A60(SeqId idx) +{ + idx; +} + +EXPORT s16 CC SND_SEQ_Play_477760(SeqId idx, s32 repeatCount, s16 volLeft, s16 volRight) +{ + idx; + repeatCount; + volLeft; + volRight; + return 1; +} + +EXPORT s16 CC SND_SsIsEos_DeInlined_477930(SeqId idx) +{ + idx; + return 1; +} + +EXPORT s32 CC SFX_SfxDefinition_Play_477330(const SfxDefinition* sfxDef, s16 volLeft, s16 volRight, s16 pitch_min, s16 pitch_max) +{ + sfxDef; + volLeft; + volRight; + pitch_min; + pitch_max; + return 1; +} + +EXPORT s32 CC SFX_SfxDefinition_Play_4770F0(const SfxDefinition* sfxDef, s32 vol, s32 pitch_min, s32 pitch_max) +{ + sfxDef; + vol; + pitch_min; + pitch_max; + return 1; +} +EXPORT void CC SND_Init_476E40() +{ + // 1 +} + +EXPORT void CC SND_Shutdown_476EC0() +{ + +} + +EXPORT void CC SND_SEQ_SetVol_477970(SeqId idx, s16 volLeft, s16 volRight) +{ + idx; + volLeft; + volRight; +} + +EXPORT void CC SND_StopAll_4762D0() +{ + +} +#endif } // namespace AO diff --git a/Source/relive_config.h.in b/Source/relive_config.h.in index a8a8eebb8..4022eb546 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 ALTERNATE_AUDIO #cmakedefine BUILD_NUMBER @BUILD_NUMBER@ #cmakedefine CI_PROVIDER "@CI_PROVIDER@" diff --git a/options.cmake b/options.cmake index 3cb55d185..7952e3ffd 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(ALTERNATE_AUDIO "Use an audio replacement for better, but not 1:1 sound." ON) CONFIGURE_FILE(${CMAKE_CURRENT_SOURCE_DIR}/Source/relive_config.h.in ${CMAKE_CURRENT_SOURCE_DIR}/Source/AliveLibCommon/relive_config.h) From f2713c0d48c50748a41031d3bb9419e539c5ae53 Mon Sep 17 00:00:00 2001 From: Sean Horton <3359434+sean-horton@users.noreply.github.com> Date: Sun, 2 Oct 2022 17:31:59 -0400 Subject: [PATCH 04/82] parse vab --- Source/AliveLibAO/Midi.cpp | 168 ++++++++++++++++++++++++++++++++++++- 1 file changed, 166 insertions(+), 2 deletions(-) diff --git a/Source/AliveLibAO/Midi.cpp b/Source/AliveLibAO/Midi.cpp index e17927af5..7d82b8306 100644 --- a/Source/AliveLibAO/Midi.cpp +++ b/Source/AliveLibAO/Midi.cpp @@ -22,6 +22,7 @@ #include "../AliveLibAE/Sound/Midi.hpp" #include "../AliveLibAE/Sound/Sound.hpp" #include "../AliveLibAE/PathData.hpp" +#include "SDL_Mixer.h" namespace AO { @@ -1150,6 +1151,11 @@ EXPORT void CC SND_StopAll_4762D0() SsUtAllKeyOff_49EDE0(0); } #else + + + +// https://github.com/mlgthatsme/AliveSoundLib + EXPORT void CC SsUtAllKeyOff_49EDE0(s32 mode) { mode; @@ -1160,15 +1166,173 @@ EXPORT void CC SND_Reset_476BA0() } +struct AoVag +{ + u32 iSize; + u32 iSampleRate; + std::vector iSampleData; +}; + EXPORT void CC SND_Load_VABS_477040(SoundBlockInfo* pSoundBlockInfo, s32 reverb) { pSoundBlockInfo; reverb; - LvlFileRecord* pVabHeaderFile = sLvlArchive_4FFD60.Find_File_Record_41BED0(pSoundBlockInfo->field_0_vab_header_name); - pVabHeaderFile; + + + while (1) + { + if (!pSoundBlockInfo->field_0_vab_header_name) + { + break; + } + + if (Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 1, 1024)) + { + return; + } + + // Read header + LvlFileRecord* pVabHeaderFile = sLvlArchive_4FFD60.Find_File_Record_41BED0(pSoundBlockInfo->field_0_vab_header_name); + u8* ppVabHeader = new u8[pVabHeaderFile->field_10_num_sectors << 11]; + sLvlArchive_4FFD60.Read_File_41BE40(pVabHeaderFile, ppVabHeader); + VabHeader* vabHeader = reinterpret_cast(ppVabHeader); + + // Read body + LvlFileRecord* pVabBodyFile = sLvlArchive_4FFD60.Find_File_Record_41BED0(pSoundBlockInfo->field_4_vab_body_name); + s32 bodySize = pVabBodyFile->field_10_num_sectors << 11; + u8* ppVabBody = new u8[bodySize]; + sLvlArchive_4FFD60.Read_File_41BE40(pVabBodyFile, ppVabBody); + + int pos = 0; + while (pos < bodySize) + { + VabBodyRecord* record = reinterpret_cast(&ppVabBody[pos]); + + 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; + + // offset + // data + + size; + sampleRate; + record; + vabHeader; + + + Uint8* dst = new Uint8[size * 2]; + for (u32 ix = 0; ix < size; ix++) + { // not sure of the bounds + dst[2 * ix] = data[ix]; + + u32 o1 = data[ix]; + u32 o2 = data[ix + 1]; + + dst[2 * ix + 1] = ((Uint8)((o1 + o2) / 2)); + } + + Mix_Chunk* chunk = Mix_QuickLoad_RAW(dst, size * 2); + Mix_PlayChannel(-1, chunk, 0); + SDL_Delay(1000); + } + + pSoundBlockInfo++; + } } +//EXPORT void CC SsVabTransBody_49D3E0(VabBodyRecord* pVabBody, s16 vabId) +//{ +// if (vabId < 0) +// { +// return; +// } +// +// VabHeader* pVabHeader = GetSpuApiVars()->spVabHeaders()[vabId]; +// const s32 vagCount = GetSpuApiVars()->sVagCounts()[vabId]; +// +// for (s32 i = 0; i < vagCount; i++) +// { +// SoundEntry* pEntry = &GetSpuApiVars()->sSoundEntryTable16().table[vabId][i]; +// +// if (!(i & 7)) +// { +// SsSeqCalledTbyT_49E9F0(); +// } +// +// memset(pEntry, 0, sizeof(SoundEntry)); +// +// s32 sampleLen = -1; +// if (pVabHeader && i >= 0) +// { +// sampleLen = (8 * IterateVBRecords(pVabBody, i)->field_0_length_or_duration) / 16; +// } +// +// if (sampleLen > 0) +// { +// VabBodyRecord* v10 = nullptr; +// if (pVabHeader && i >= 0) +// { +// v10 = IterateVBRecords(pVabBody, i); +// } +// +// const u8 unused_field = v10->field_4_unused >= 0 ? 0 : 4; +// for (s32 prog = 0; prog < 128; prog++) +// { +// for (s32 tone = 0; tone < 16; tone++) +// { +// auto pVag = &GetSpuApiVars()->sConvertedVagTable().table[vabId][prog][tone]; +// if (pVag->field_10_vag == i) +// { +// pVag->field_C = unused_field; +// +// if (!(unused_field & 4) && !pVag->field_0_adsr_attack && pVag->field_6_adsr_release) +// { +// pVag->field_6_adsr_release = 0; +// } +// } +// } +// } +// +// if (!SND_New_492790(pEntry, sampleLen, 44100, 16u, 0)) +// { +// auto pTempBuffer = (u32*) malloc(sampleLen * pEntry->field_1D_blockAlign); +// if (pTempBuffer) +// { +// u32* pSrcVB = nullptr; +// if (pVabHeader && i >= 0) +// { +// pSrcVB = &IterateVBRecords(pVabBody, i)->field_8_fileOffset; +// } +// +// s32 sampleLen2 = -1; +// if (pVabHeader && i >= 0) +// { +// sampleLen2 = (8 * IterateVBRecords(pVabBody, i)->field_0_length_or_duration) / 16; +// } +// +// const s32 len = (16 * sampleLen2) / 8; +// memcpy(pTempBuffer, pSrcVB, len); +// +// if (sampleLen2) +// { +// SND_Load_492F40(pEntry, pTempBuffer, sampleLen2); +// } +// +// free(pTempBuffer); +// } +// } +// } +// } +//} + EXPORT void CC SND_Stop_Channels_Mask_4774A0(s32 mask) { mask; From 527e26e78e8699a6497ff0555e5e14f4fc2ae8db Mon Sep 17 00:00:00 2001 From: Sean Horton <3359434+sean-horton@users.noreply.github.com> Date: Mon, 3 Oct 2022 21:06:27 -0400 Subject: [PATCH 05/82] loading and parsing audio stuff --- Source/AliveLibAE/Sound/Midi.cpp | 261 +++++++++++++++++++++++++- Source/AliveLibAE/Sound/Midi.hpp | 7 + Source/AliveLibAO/Midi.cpp | 236 +++++++++++------------ Source/AliveLibAO/ResourceManager.hpp | 1 + 4 files changed, 380 insertions(+), 125 deletions(-) diff --git a/Source/AliveLibAE/Sound/Midi.cpp b/Source/AliveLibAE/Sound/Midi.cpp index 7c209c81a..9e6f78253 100644 --- a/Source/AliveLibAE/Sound/Midi.cpp +++ b/Source/AliveLibAE/Sound/Midi.cpp @@ -18,8 +18,9 @@ #include "PsxSpuApi.hpp" #include "AmbientSound.hpp" +#include "SDL_Mixer.h" - +#if !ALTERNATE_AUDIO EXPORT void CC SFX_SetPitch_4CA510(const SfxDefinition* pSfx, s32 channelsBits, s16 pitch); const s32 kSeqTableSizeAE = 144; @@ -867,3 +868,261 @@ 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 +struct VagAtr final +{ + s8 field_0_priority; + s8 field_1_mode; + s8 field_2_vol; + s8 field_3_pan; + u8 field_4_centre; + u8 field_5_shift; + s8 field_6_min; + s8 field_7_max; + 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; +}; + +EXPORT void CC SND_StopAll_4CB060() +{ + +} +EXPORT void CC SND_Init_4CA1F0() +{ + +} +EXPORT void CC SND_Shutdown_4CA280() +{ + +} +EXPORT void CC SND_Stop_Channels_Mask_4CA810(u32 bitMask) +{ + bitMask; +} +EXPORT void SND_Reset_4C9FB0() +{ + +} + +EXPORT void CC SND_Load_VABS_4CA350(SoundBlockInfo* pSoundBlockInfo, s32 reverb) +{ + reverb; + while (1) + { + if (!pSoundBlockInfo->field_0_vab_header_name) + { + break; + } + + // field_14_prog is what is passed to "playsound" + // I then have to look up field_16_vag to play the sample + + // Read header + LvlFileRecord* pVabHeaderFile = sLvlArchive_5BC520.Find_File_Record_433160(pSoundBlockInfo->field_0_vab_header_name); + u8* ppVabHeader = new u8[pVabHeaderFile->field_10_num_sectors << 11]; + sLvlArchive_5BC520.Read_File_4330A0(pVabHeaderFile, ppVabHeader); + VabHeader* vabHeader = reinterpret_cast(ppVabHeader); + VagAtr* vagAttr = (VagAtr*) &vabHeader[1]; + for (s32 i = 0; i < vabHeader->field_12_num_progs; i++) + { + for (s32 toneCounter = 0; toneCounter < 16; toneCounter++) + { + if (vagAttr->field_2_vol > 0) + { + Converted_Vag* pData = new Converted_Vag; + + pData->field_F_prog = static_cast(vagAttr->field_14_prog); + pData->field_10_vag = LOBYTE(vagAttr->field_16_vag) - 1; + pData->field_C = 0; + pData->field_D_vol = vagAttr->field_2_vol; + pData->field_E_priority = vagAttr->field_0_priority; + pData->field_8_min = vagAttr->field_6_min; + pData->field_9_max = vagAttr->field_7_max; + + const s16 centre = vagAttr->field_4_centre; + pData->field_A_shift_cen = 2 * (vagAttr->field_5_shift + (centre << 7)); + + f32 sustain_level = static_cast((2 * (~(u8) vagAttr->field_10_adsr1 & 0xF))); + + pData->field_0_adsr_attack = std::min(static_cast((powf(2.0f, ((vagAttr->field_10_adsr1 >> 8) & 0x7F) * 0.25f) * 0.09f)), static_cast(32767)); + pData->field_4_adsr_decay = static_cast((((vagAttr->field_10_adsr1 >> 4) & 0xF) / 15.0f) * 16.0); + pData->field_2_adsr_sustain_level = std::min(static_cast((sustain_level / 15.0f) * 600.0), static_cast(32767)); + pData->field_6_adsr_release = std::min(static_cast(pow(2, vagAttr->field_12_adsr2 & 0x1F) * 0.045f), static_cast(32767)); + + // If decay is at max, then nothing should play. So mute sustain too ? + if (pData->field_4_adsr_decay == 16) + { + pData->field_2_adsr_sustain_level = 0; + } + + pData->field_11_pad = vagAttr->field_3_pan; + } + ++vagAttr; + } + } + + // Read body + // THIS IS THE DIFFERENCE BETWEEN AO. For some reason audio is stored in sounds.dat instead of the sector + std::ifstream soundDatFile; + soundDatFile.open("sounds.dat", std::ios::binary); + if (!soundDatFile.is_open()) + { + abort(); + } + 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)) + { + // Tada! + } + + LvlFileRecord* pVabBodyFile = sLvlArchive_5BC520.Find_File_Record_433160(pSoundBlockInfo->field_4_vab_body_name); + s32 bodySize = pVabBodyFile->field_10_num_sectors << 11; + u8* ppVabBody = new u8[bodySize]; + sLvlArchive_5BC520.Read_File_4330A0(pVabBodyFile, ppVabBody); + + int pos = 0; + for (s32 i = 0; i < vabHeader->field_12_num_progs; i++) + { + // VAB + + VabBodyRecord* record = reinterpret_cast(&ppVabBody[pos]); + + 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 + // Not sure why they did this. + u32 offset = *reinterpret_cast(&ppVabBody[pos]); + pos += sizeof(u32); + + // Read data from sounds.dat + u8* data = new u8[size]; + memcpy(data, &soundData[offset], size); + + size; + sampleRate; + record; + vabHeader; + } + + delete[] ppVabBody; + pSoundBlockInfo++; + } +} +EXPORT s32 CC SND_4CA5D0(s32 program, s32 vabId, s32 note, s16 vol, s16 min, s16 max) +{ + program; + vabId; + note; + vol; + min; + max; + return 1; +} +EXPORT void CC SND_Restart_4CB0E0() +{ + +} +EXPORT void CC SND_Load_Seqs_4CAED0(OpenSeqHandle* pSeqTable, const char_type* bsqFileName) +{ + if (!pSeqTable || !bsqFileName) + { + return; + } + + OpenSeqHandle* seq = reinterpret_cast<::OpenSeqHandle*>(pSeqTable); + ResourceManager::LoadResourceFile_49C170(bsqFileName, nullptr); + for (s32 i = 0; i < 144; i++) // 144 is a hardcoded value + { + seq[i].field_A_id_seqOpenId = -1; + seq[i].field_4_generated_res_id = ResourceManager::SEQ_HashName_49BE30(seq[i].field_0_mBsqName); + + u8** ppSeq = ResourceManager::GetLoadedResource_49C2A0(ResourceManager::Resource_Seq, seq[i].field_4_generated_res_id, 1, 1); + if (ppSeq) + { + seq[i].field_C_ppSeq_Data = *ppSeq; + } + else + { + seq[i].field_C_ppSeq_Data = nullptr; + } + } +} +EXPORT void CC SND_SEQ_Stop_4CAE60(u16 idx) +{ + idx; +} +EXPORT s8 CC SND_Seq_Table_Valid_4CAFE0() +{ + return 1; +} +EXPORT s16 CC SND_SEQ_PlaySeq_4CA960(u16 idx, s16 repeatCount, s16 bDontStop) +{ + idx; + repeatCount; + bDontStop; + return 1; +} +EXPORT void CC SND_SEQ_SetVol_4CAD20(s32 idx, s16 volLeft, s16 volRight) +{ + idx; + volLeft; + volRight; +} +EXPORT s16 CC SND_SEQ_Play_4CAB10(u16 idx, s16 repeatCount, s16 volLeft, s16 volRight) +{ + idx; + repeatCount; + volLeft; + volRight; + return 1; +} +EXPORT s32 CC SND_SsIsEos_DeInlined_4CACD0(u16 idx) +{ + idx; + return 1; +} +EXPORT s32 CC SFX_SfxDefinition_Play_4CA700(const SfxDefinition* sfxDef, s16 volLeft, s16 volRight, s16 pitch_min, s16 pitch_max) +{ + sfxDef; + volLeft; + volRight; + pitch_min; + pitch_max; + return 1; +} +EXPORT s32 CC SFX_SfxDefinition_Play_4CA420(const SfxDefinition* sfxDef, s16 volume, s16 pitch_min, s16 pitch_max) +{ + sfxDef; + volume; + pitch_min; + pitch_max; + return 1; +} +#endif \ No newline at end of file diff --git a/Source/AliveLibAE/Sound/Midi.hpp b/Source/AliveLibAE/Sound/Midi.hpp index 7cf5eb358..783b98511 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 !ALTERNATE_AUDIO 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/AliveLibAO/Midi.cpp b/Source/AliveLibAO/Midi.cpp index 7d82b8306..4fc77b6c4 100644 --- a/Source/AliveLibAO/Midi.cpp +++ b/Source/AliveLibAO/Midi.cpp @@ -1152,7 +1152,36 @@ EXPORT void CC SND_StopAll_4762D0() } #else - +struct VagAtr final +{ + s8 field_0_priority; + s8 field_1_mode; + s8 field_2_vol; + s8 field_3_pan; + u8 field_4_centre; + u8 field_5_shift; + s8 field_6_min; + s8 field_7_max; + 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; +}; // https://github.com/mlgthatsme/AliveSoundLib @@ -1166,20 +1195,9 @@ EXPORT void CC SND_Reset_476BA0() } -struct AoVag -{ - u32 iSize; - u32 iSampleRate; - std::vector iSampleData; -}; - EXPORT void CC SND_Load_VABS_477040(SoundBlockInfo* pSoundBlockInfo, s32 reverb) { - pSoundBlockInfo; reverb; - - - while (1) { if (!pSoundBlockInfo->field_0_vab_header_name) @@ -1187,16 +1205,49 @@ EXPORT void CC SND_Load_VABS_477040(SoundBlockInfo* pSoundBlockInfo, s32 reverb) break; } - if (Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 1, 1024)) - { - return; - } - // Read header LvlFileRecord* pVabHeaderFile = sLvlArchive_4FFD60.Find_File_Record_41BED0(pSoundBlockInfo->field_0_vab_header_name); u8* ppVabHeader = new u8[pVabHeaderFile->field_10_num_sectors << 11]; sLvlArchive_4FFD60.Read_File_41BE40(pVabHeaderFile, ppVabHeader); VabHeader* vabHeader = reinterpret_cast(ppVabHeader); + VagAtr* vagAttr = (VagAtr*) &vabHeader[1]; + for (s32 i = 0; i < vabHeader->field_12_num_progs; i++) + { + for (s32 toneCounter = 0; toneCounter < 16; toneCounter++) + { + if (vagAttr->field_2_vol > 0) + { + Converted_Vag* pData = new Converted_Vag; + + pData->field_F_prog = static_cast(vagAttr->field_14_prog); + pData->field_10_vag = LOBYTE(vagAttr->field_16_vag) - 1; + pData->field_C = 0; + pData->field_D_vol = vagAttr->field_2_vol; + pData->field_E_priority = vagAttr->field_0_priority; + pData->field_8_min = vagAttr->field_6_min; + pData->field_9_max = vagAttr->field_7_max; + + const s16 centre = vagAttr->field_4_centre; + pData->field_A_shift_cen = 2 * (vagAttr->field_5_shift + (centre << 7)); + + f32 sustain_level = static_cast((2 * (~(u8) vagAttr->field_10_adsr1 & 0xF))); + + pData->field_0_adsr_attack = std::min(static_cast((powf(2.0f, ((vagAttr->field_10_adsr1 >> 8) & 0x7F) * 0.25f) * 0.09f)), static_cast(32767)); + pData->field_4_adsr_decay = static_cast((((vagAttr->field_10_adsr1 >> 4) & 0xF) / 15.0f) * 16.0); + pData->field_2_adsr_sustain_level = std::min(static_cast((sustain_level / 15.0f) * 600.0), static_cast(32767)); + pData->field_6_adsr_release = std::min(static_cast(pow(2, vagAttr->field_12_adsr2 & 0x1F) * 0.045f), static_cast(32767)); + + // If decay is at max, then nothing should play. So mute sustain too ? + if (pData->field_4_adsr_decay == 16) + { + pData->field_2_adsr_sustain_level = 0; + } + + pData->field_11_pad = vagAttr->field_3_pan; + } + ++vagAttr; + } + } // Read body LvlFileRecord* pVabBodyFile = sLvlArchive_4FFD60.Find_File_Record_41BED0(pSoundBlockInfo->field_4_vab_body_name); @@ -1205,8 +1256,10 @@ EXPORT void CC SND_Load_VABS_477040(SoundBlockInfo* pSoundBlockInfo, s32 reverb) sLvlArchive_4FFD60.Read_File_41BE40(pVabBodyFile, ppVabBody); int pos = 0; - while (pos < bodySize) + for (int i = 0; i < vabHeader->field_16_num_vags; ++i) { + // VAB + VabBodyRecord* record = reinterpret_cast(&ppVabBody[pos]); u32 size = *reinterpret_cast(&ppVabBody[pos]); @@ -1219,120 +1272,17 @@ EXPORT void CC SND_Load_VABS_477040(SoundBlockInfo* pSoundBlockInfo, s32 reverb) memcpy(data, &ppVabBody[pos], size); pos += size; - // offset - // data - size; sampleRate; record; vabHeader; - - - Uint8* dst = new Uint8[size * 2]; - for (u32 ix = 0; ix < size; ix++) - { // not sure of the bounds - dst[2 * ix] = data[ix]; - - u32 o1 = data[ix]; - u32 o2 = data[ix + 1]; - - dst[2 * ix + 1] = ((Uint8)((o1 + o2) / 2)); - } - - Mix_Chunk* chunk = Mix_QuickLoad_RAW(dst, size * 2); - Mix_PlayChannel(-1, chunk, 0); - SDL_Delay(1000); } + delete[] ppVabBody; pSoundBlockInfo++; } } -//EXPORT void CC SsVabTransBody_49D3E0(VabBodyRecord* pVabBody, s16 vabId) -//{ -// if (vabId < 0) -// { -// return; -// } -// -// VabHeader* pVabHeader = GetSpuApiVars()->spVabHeaders()[vabId]; -// const s32 vagCount = GetSpuApiVars()->sVagCounts()[vabId]; -// -// for (s32 i = 0; i < vagCount; i++) -// { -// SoundEntry* pEntry = &GetSpuApiVars()->sSoundEntryTable16().table[vabId][i]; -// -// if (!(i & 7)) -// { -// SsSeqCalledTbyT_49E9F0(); -// } -// -// memset(pEntry, 0, sizeof(SoundEntry)); -// -// s32 sampleLen = -1; -// if (pVabHeader && i >= 0) -// { -// sampleLen = (8 * IterateVBRecords(pVabBody, i)->field_0_length_or_duration) / 16; -// } -// -// if (sampleLen > 0) -// { -// VabBodyRecord* v10 = nullptr; -// if (pVabHeader && i >= 0) -// { -// v10 = IterateVBRecords(pVabBody, i); -// } -// -// const u8 unused_field = v10->field_4_unused >= 0 ? 0 : 4; -// for (s32 prog = 0; prog < 128; prog++) -// { -// for (s32 tone = 0; tone < 16; tone++) -// { -// auto pVag = &GetSpuApiVars()->sConvertedVagTable().table[vabId][prog][tone]; -// if (pVag->field_10_vag == i) -// { -// pVag->field_C = unused_field; -// -// if (!(unused_field & 4) && !pVag->field_0_adsr_attack && pVag->field_6_adsr_release) -// { -// pVag->field_6_adsr_release = 0; -// } -// } -// } -// } -// -// if (!SND_New_492790(pEntry, sampleLen, 44100, 16u, 0)) -// { -// auto pTempBuffer = (u32*) malloc(sampleLen * pEntry->field_1D_blockAlign); -// if (pTempBuffer) -// { -// u32* pSrcVB = nullptr; -// if (pVabHeader && i >= 0) -// { -// pSrcVB = &IterateVBRecords(pVabBody, i)->field_8_fileOffset; -// } -// -// s32 sampleLen2 = -1; -// if (pVabHeader && i >= 0) -// { -// sampleLen2 = (8 * IterateVBRecords(pVabBody, i)->field_0_length_or_duration) / 16; -// } -// -// const s32 len = (16 * sampleLen2) / 8; -// memcpy(pTempBuffer, pSrcVB, len); -// -// if (sampleLen2) -// { -// SND_Load_492F40(pEntry, pTempBuffer, sampleLen2); -// } -// -// free(pTempBuffer); -// } -// } -// } -// } -//} - EXPORT void CC SND_Stop_Channels_Mask_4774A0(s32 mask) { mask; @@ -1340,8 +1290,28 @@ EXPORT void CC SND_Stop_Channels_Mask_4774A0(s32 mask) EXPORT void CC SND_Load_Seqs_477AB0(OpenSeqHandleAE* pSeqTable, const char_type* bsqFileName) { - pSeqTable; - bsqFileName; + if (!pSeqTable || !bsqFileName) + { + return; + } + + OpenSeqHandle* seq = reinterpret_cast<::OpenSeqHandle*>(pSeqTable); + ResourceManager::LoadResourceFileWrapper(bsqFileName, nullptr); + for (s32 i = 0; i < 164; i++) // 164 is a hardcoded value + { + seq[i].field_A_id_seqOpenId = -1; + seq[i].field_4_generated_res_id = ResourceManager::SEQ_HashName_454EA0(seq[i].field_0_mBsqName); + + u8** ppSeq = ResourceManager::GetLoadedResource_4554F0(ResourceManager::Resource_Seq, seq[i].field_4_generated_res_id, 1, 1); + if (ppSeq) + { + seq[i].field_C_ppSeq_Data = *ppSeq; + } + else + { + seq[i].field_C_ppSeq_Data = nullptr; + } + } } EXPORT s16 CC SND_SEQ_PlaySeq_4775A0(SeqId idx, s32 repeatCount, s16 bDontStop) @@ -1358,6 +1328,7 @@ EXPORT void CC SND_Seq_Stop_477A60(SeqId idx) } EXPORT s16 CC SND_SEQ_Play_477760(SeqId idx, s32 repeatCount, s16 volLeft, s16 volRight) + { idx; repeatCount; @@ -1379,6 +1350,15 @@ EXPORT s32 CC SFX_SfxDefinition_Play_477330(const SfxDefinition* sfxDef, s16 vol volRight; pitch_min; pitch_max; + + if (sfxDef->field_0_block_idx != 0) + { + std::cout << "HEY\n"; + } + + //Mix_Chunk* chunk = Mix_QuickLoad_RAW(samples[sfxDef->field_4_program].data, samples[sfxDef->field_4_program].size); + //Mix_PlayChannel(-1, chunk, 0); + return 1; } @@ -1388,6 +1368,14 @@ EXPORT s32 CC SFX_SfxDefinition_Play_4770F0(const SfxDefinition* sfxDef, s32 vol vol; pitch_min; pitch_max; + if (sfxDef->field_0_block_idx != 0) + { + std::cout << "HEY\n"; + } + + //Mix_Chunk* chunk = Mix_QuickLoad_RAW(samples[sfxDef->field_4_program >> 7].data, samples[sfxDef->field_4_program >> 7].size); + //Mix_PlayChannel(-1, chunk, 0); + return 1; } diff --git a/Source/AliveLibAO/ResourceManager.hpp b/Source/AliveLibAO/ResourceManager.hpp index 034261d67..679f7a5cb 100644 --- a/Source/AliveLibAO/ResourceManager.hpp +++ b/Source/AliveLibAO/ResourceManager.hpp @@ -46,6 +46,7 @@ class ResourceManager final Resource_End = 0x21646E45, Resource_Plbk = 0x6B626C50, Resource_Play = 0x79616C50, + Resource_Seq = 0x20716553, }; enum ResourceHeaderFlags : s16 From 97ddf3e3e7a3780180856f1e4e96c1f00979b1e8 Mon Sep 17 00:00:00 2001 From: Sean Horton <3359434+sean-horton@users.noreply.github.com> Date: Tue, 4 Oct 2022 21:54:32 -0400 Subject: [PATCH 06/82] audio is somewhat working in testing --- Source/AliveLibAE/Sound/Midi.cpp | 4 +- Source/AliveLibAO/Midi.cpp | 160 +++++--- Source/AliveLibAO/ResourceManager.hpp | 1 + Source/AliveLibCommon/audio/ADSR.cpp | 336 +++++++++++++++++ Source/AliveLibCommon/audio/ADSR.hpp | 77 ++++ Source/AliveLibCommon/audio/Exceptions.hpp | 20 + Source/AliveLibCommon/audio/MidiPlayer.cpp | 0 Source/AliveLibCommon/audio/MidiPlayer.hpp | 46 +++ .../AliveLibCommon/audio/SequencePlayer.cpp | 353 ++++++++++++++++++ .../AliveLibCommon/audio/SequencePlayer.hpp | 98 +++++ Source/AliveLibCommon/audio/Soundbank.cpp | 24 ++ Source/AliveLibCommon/audio/Soundbank.hpp | 90 +++++ Source/AliveLibCommon/audio/Stream.cpp | 91 +++++ Source/AliveLibCommon/audio/Stream.hpp | 28 ++ .../AliveLibCommon/audio/mixer/AliveAudio.cpp | 227 +++++++++++ .../AliveLibCommon/audio/mixer/AliveAudio.hpp | 46 +++ Source/AliveLibCommon/audio/mixer/Voice.cpp | 52 +++ Source/AliveLibCommon/audio/mixer/Voice.hpp | 29 ++ 18 files changed, 1627 insertions(+), 55 deletions(-) create mode 100644 Source/AliveLibCommon/audio/ADSR.cpp create mode 100644 Source/AliveLibCommon/audio/ADSR.hpp create mode 100644 Source/AliveLibCommon/audio/Exceptions.hpp create mode 100644 Source/AliveLibCommon/audio/MidiPlayer.cpp create mode 100644 Source/AliveLibCommon/audio/MidiPlayer.hpp create mode 100644 Source/AliveLibCommon/audio/SequencePlayer.cpp create mode 100644 Source/AliveLibCommon/audio/SequencePlayer.hpp create mode 100644 Source/AliveLibCommon/audio/Soundbank.cpp create mode 100644 Source/AliveLibCommon/audio/Soundbank.hpp create mode 100644 Source/AliveLibCommon/audio/Stream.cpp create mode 100644 Source/AliveLibCommon/audio/Stream.hpp create mode 100644 Source/AliveLibCommon/audio/mixer/AliveAudio.cpp create mode 100644 Source/AliveLibCommon/audio/mixer/AliveAudio.hpp create mode 100644 Source/AliveLibCommon/audio/mixer/Voice.cpp create mode 100644 Source/AliveLibCommon/audio/mixer/Voice.hpp diff --git a/Source/AliveLibAE/Sound/Midi.cpp b/Source/AliveLibAE/Sound/Midi.cpp index 9e6f78253..d5db926ea 100644 --- a/Source/AliveLibAE/Sound/Midi.cpp +++ b/Source/AliveLibAE/Sound/Midi.cpp @@ -19,6 +19,7 @@ #include "PsxSpuApi.hpp" #include "AmbientSound.hpp" #include "SDL_Mixer.h" +#include "../../AliveLibCommon/audio/MidiPlayer.hpp" #if !ALTERNATE_AUDIO EXPORT void CC SFX_SetPitch_4CA510(const SfxDefinition* pSfx, s32 channelsBits, s16 pitch); @@ -1005,8 +1006,6 @@ EXPORT void CC SND_Load_VABS_4CA350(SoundBlockInfo* pSoundBlockInfo, s32 reverb) int pos = 0; for (s32 i = 0; i < vabHeader->field_12_num_progs; i++) { - // VAB - VabBodyRecord* record = reinterpret_cast(&ppVabBody[pos]); u32 size = *reinterpret_cast(&ppVabBody[pos]); @@ -1063,6 +1062,7 @@ EXPORT void CC SND_Load_Seqs_4CAED0(OpenSeqHandle* pSeqTable, const char_type* b seq[i].field_A_id_seqOpenId = -1; seq[i].field_4_generated_res_id = ResourceManager::SEQ_HashName_49BE30(seq[i].field_0_mBsqName); + // ppSeq is what can be played u8** ppSeq = ResourceManager::GetLoadedResource_49C2A0(ResourceManager::Resource_Seq, seq[i].field_4_generated_res_id, 1, 1); if (ppSeq) { diff --git a/Source/AliveLibAO/Midi.cpp b/Source/AliveLibAO/Midi.cpp index 4fc77b6c4..1a518da10 100644 --- a/Source/AliveLibAO/Midi.cpp +++ b/Source/AliveLibAO/Midi.cpp @@ -22,12 +22,9 @@ #include "../AliveLibAE/Sound/Midi.hpp" #include "../AliveLibAE/Sound/Sound.hpp" #include "../AliveLibAE/PathData.hpp" -#include "SDL_Mixer.h" - - -namespace AO { #if !ALTERNATE_AUDIO +namespace AO { const s32 kSeqTableSizeAO = 164; ALIVE_VAR(1, 0x9F12D8, SeqIds, sSeq_Ids_word_9F12D8, {}); @@ -1150,8 +1147,17 @@ EXPORT void CC SND_StopAll_4762D0() SsUtAllKeyOff_49EDE0(0); } +} #else +#include "../../AliveLibCommon/audio/MidiPlayer.hpp" +#include "../../AliveLibCommon/audio/Soundbank.hpp" +#include "../../AliveLibCommon/audio/ADSR.hpp" +#include "../../AliveLibCommon/audio/mixer/AliveAudio.hpp" +#include "../../AliveLibCommon/audio/SequencePlayer.hpp" + +namespace AO { + struct VagAtr final { s8 field_0_priority; @@ -1183,6 +1189,10 @@ struct AoVag std::vector iSampleData; }; +Soundbank* theBank; +SequencePlayer* player = new SequencePlayer(); +std::vector> gSeqs; + // https://github.com/mlgthatsme/AliveSoundLib EXPORT void CC SsUtAllKeyOff_49EDE0(s32 mode) @@ -1192,7 +1202,6 @@ EXPORT void CC SsUtAllKeyOff_49EDE0(s32 mode) EXPORT void CC SND_Reset_476BA0() { - } EXPORT void CC SND_Load_VABS_477040(SoundBlockInfo* pSoundBlockInfo, s32 reverb) @@ -1211,43 +1220,8 @@ EXPORT void CC SND_Load_VABS_477040(SoundBlockInfo* pSoundBlockInfo, s32 reverb) sLvlArchive_4FFD60.Read_File_41BE40(pVabHeaderFile, ppVabHeader); VabHeader* vabHeader = reinterpret_cast(ppVabHeader); VagAtr* vagAttr = (VagAtr*) &vabHeader[1]; - for (s32 i = 0; i < vabHeader->field_12_num_progs; i++) - { - for (s32 toneCounter = 0; toneCounter < 16; toneCounter++) - { - if (vagAttr->field_2_vol > 0) - { - Converted_Vag* pData = new Converted_Vag; + std::vector programs; - pData->field_F_prog = static_cast(vagAttr->field_14_prog); - pData->field_10_vag = LOBYTE(vagAttr->field_16_vag) - 1; - pData->field_C = 0; - pData->field_D_vol = vagAttr->field_2_vol; - pData->field_E_priority = vagAttr->field_0_priority; - pData->field_8_min = vagAttr->field_6_min; - pData->field_9_max = vagAttr->field_7_max; - - const s16 centre = vagAttr->field_4_centre; - pData->field_A_shift_cen = 2 * (vagAttr->field_5_shift + (centre << 7)); - - f32 sustain_level = static_cast((2 * (~(u8) vagAttr->field_10_adsr1 & 0xF))); - - pData->field_0_adsr_attack = std::min(static_cast((powf(2.0f, ((vagAttr->field_10_adsr1 >> 8) & 0x7F) * 0.25f) * 0.09f)), static_cast(32767)); - pData->field_4_adsr_decay = static_cast((((vagAttr->field_10_adsr1 >> 4) & 0xF) / 15.0f) * 16.0); - pData->field_2_adsr_sustain_level = std::min(static_cast((sustain_level / 15.0f) * 600.0), static_cast(32767)); - pData->field_6_adsr_release = std::min(static_cast(pow(2, vagAttr->field_12_adsr2 & 0x1F) * 0.045f), static_cast(32767)); - - // If decay is at max, then nothing should play. So mute sustain too ? - if (pData->field_4_adsr_decay == 16) - { - pData->field_2_adsr_sustain_level = 0; - } - - pData->field_11_pad = vagAttr->field_3_pan; - } - ++vagAttr; - } - } // Read body LvlFileRecord* pVabBodyFile = sLvlArchive_4FFD60.Find_File_Record_41BED0(pSoundBlockInfo->field_4_vab_body_name); @@ -1255,29 +1229,80 @@ EXPORT void CC SND_Load_VABS_477040(SoundBlockInfo* pSoundBlockInfo, s32 reverb) u8* ppVabBody = new u8[bodySize]; sLvlArchive_4FFD60.Read_File_41BE40(pVabBodyFile, ppVabBody); + // BODY int pos = 0; + std::vector samples; for (int i = 0; i < vabHeader->field_16_num_vags; ++i) { - // VAB - - VabBodyRecord* record = reinterpret_cast(&ppVabBody[pos]); - + // SAMPLE u32 size = *reinterpret_cast(&ppVabBody[pos]); pos += sizeof(u32); u32 sampleRate = *reinterpret_cast(&ppVabBody[pos]); pos += sizeof(u32); + sampleRate; u8* data = new u8[size]; memcpy(data, &ppVabBody[pos], size); pos += size; - size; - sampleRate; - record; - vabHeader; + Sample* sample = new Sample(); + sample->m_SampleBuffer = reinterpret_cast(data); + sample->i_SampleSize = size / 2; + samples.push_back(sample); } + // HEADER + for (s32 i = 0; i < vabHeader->field_12_num_progs; i++) + { + // PROGRAM + Program* program = new Program; + programs.push_back(program); + for (s32 toneCounter = 0; toneCounter < 16; toneCounter++) + { + + // TONE + + Tone* tone = new Tone(); + program->m_Tones.push_back(tone); + + if (vagAttr->field_2_vol > 0) + { + std::cout << vagAttr->field_16_vag << "\n"; + program->prog_id = vagAttr->field_14_prog; + tone->f_Volume = vagAttr->field_2_vol / 127.0f; + tone->c_Center = vagAttr->field_4_centre; + tone->c_Shift = vagAttr->field_5_shift; + tone->f_Pan = (vagAttr->field_3_pan / 64.0f) - 1.0f; + tone->Min = vagAttr->field_6_min; + tone->Max = vagAttr->field_7_max; + tone->Pitch = vagAttr->field_5_shift / 100.0f; + tone->m_Sample = samples.at(vagAttr->field_16_vag - 1); + + unsigned short ADSR1 = vagAttr->field_10_adsr1; + unsigned short ADSR2 = vagAttr->field_12_adsr2; + + REAL_ADSR realADSR = {}; + PSXConvADSR(&realADSR, ADSR1, ADSR2, false); + + tone->AttackTime = realADSR.attack_time; + tone->DecayTime = realADSR.decay_time; + tone->ReleaseTime = realADSR.release_time; + tone->SustainTime = realADSR.sustain_time; + + if (realADSR.attack_time > 1) + { // This works until the loop database is added. + tone->Loop = true; + } + } + ++vagAttr; + } + } + + Soundbank* soundbank = new Soundbank(samples, programs); + theBank = soundbank; + AliveAudio::m_CurrentSoundbank = theBank; + delete[] ppVabBody; pSoundBlockInfo++; } @@ -1299,17 +1324,36 @@ EXPORT void CC SND_Load_Seqs_477AB0(OpenSeqHandleAE* pSeqTable, const char_type* ResourceManager::LoadResourceFileWrapper(bsqFileName, nullptr); for (s32 i = 0; i < 164; i++) // 164 is a hardcoded value { + OpenSeqHandle next = seq[i]; seq[i].field_A_id_seqOpenId = -1; seq[i].field_4_generated_res_id = ResourceManager::SEQ_HashName_454EA0(seq[i].field_0_mBsqName); + // ppSeq is what can be played u8** ppSeq = ResourceManager::GetLoadedResource_4554F0(ResourceManager::Resource_Seq, seq[i].field_4_generated_res_id, 1, 1); if (ppSeq) { + u32 size = ResourceManager::Get_Header_455620(ppSeq)->field_0_size; + + + // 32 length seq[i].field_C_ppSeq_Data = *ppSeq; + + std::vector vec; + std::cout << "size: " << size << "\n"; + for (u32 x = 0; x < size; x++) + { + std::cout << x << "\n"; + vec.push_back((Uint8) (*ppSeq)[x]); + } + + // SEQUENCE STREAM + gSeqs.push_back(vec); } else { seq[i].field_C_ppSeq_Data = nullptr; + std::vector test; + gSeqs.push_back(test); } } } @@ -1334,6 +1378,11 @@ EXPORT s16 CC SND_SEQ_Play_477760(SeqId idx, s32 repeatCount, s16 volLeft, s16 v repeatCount; volLeft; volRight; + + player->StopSequence(); + player->LoadSequenceData(gSeqs.at(s16(idx))); + player->PlaySequence(); + return 1; } @@ -1355,6 +1404,9 @@ EXPORT s32 CC SFX_SfxDefinition_Play_477330(const SfxDefinition* sfxDef, s16 vol { std::cout << "HEY\n"; } + + + AliveAudio::PlayOneShot(sfxDef->field_4_program, sfxDef->field_8_note, volLeft, 0); //Mix_Chunk* chunk = Mix_QuickLoad_RAW(samples[sfxDef->field_4_program].data, samples[sfxDef->field_4_program].size); //Mix_PlayChannel(-1, chunk, 0); @@ -1373,6 +1425,9 @@ EXPORT s32 CC SFX_SfxDefinition_Play_4770F0(const SfxDefinition* sfxDef, s32 vol std::cout << "HEY\n"; } + std::cout << theBank->m_Programs.size() << "\n"; + AliveAudio::PlayOneShot(sfxDef->field_4_program, sfxDef->field_8_note, vol, 0); + //Mix_Chunk* chunk = Mix_QuickLoad_RAW(samples[sfxDef->field_4_program >> 7].data, samples[sfxDef->field_4_program >> 7].size); //Mix_PlayChannel(-1, chunk, 0); @@ -1381,12 +1436,11 @@ EXPORT s32 CC SFX_SfxDefinition_Play_4770F0(const SfxDefinition* sfxDef, s32 vol EXPORT void CC SND_Init_476E40() { - // 1 + AliveInitAudio(); } EXPORT void CC SND_Shutdown_476EC0() { - } EXPORT void CC SND_SEQ_SetVol_477970(SeqId idx, s16 volLeft, s16 volRight) @@ -1398,7 +1452,7 @@ EXPORT void CC SND_SEQ_SetVol_477970(SeqId idx, s16 volLeft, s16 volRight) EXPORT void CC SND_StopAll_4762D0() { - } -#endif } // namespace AO +#endif + diff --git a/Source/AliveLibAO/ResourceManager.hpp b/Source/AliveLibAO/ResourceManager.hpp index 679f7a5cb..9478122eb 100644 --- a/Source/AliveLibAO/ResourceManager.hpp +++ b/Source/AliveLibAO/ResourceManager.hpp @@ -47,6 +47,7 @@ class ResourceManager final Resource_Plbk = 0x6B626C50, Resource_Play = 0x79616C50, Resource_Seq = 0x20716553, + Resource_SEQp = 0x53455170, }; enum ResourceHeaderFlags : s16 diff --git a/Source/AliveLibCommon/audio/ADSR.cpp b/Source/AliveLibCommon/audio/ADSR.cpp new file mode 100644 index 000000000..35fbca765 --- /dev/null +++ b/Source/AliveLibCommon/audio/ADSR.cpp @@ -0,0 +1,336 @@ +#pragma once + +#include "ADSR.hpp" + +// All of the ADSR calculations herein (except where inaccurate) are derived from Neill Corlett's work in +// reverse-engineering the Playstation 1/2 SPU unit. + +//************************************************************************************************** +// Type Redefinitions + +// This next function converts seconds to full attenuation in a linear amplitude decay scale +// and approximates the time to full attenuation in a linear DB decay scale. +double LinAmpDecayTimeToLinDBDecayTime(double secondsToFullAtten, int linearVolumeRange) +{ + double expMinDecibel = -100.0; + double linearMinDecibel = log10(1.0 / linearVolumeRange) * 20.0; + double linearToExpScale = log(linearMinDecibel - expMinDecibel) / log(2.0); + return secondsToFullAtten * linearToExpScale; +} + +//InitADSR is shamelessly ripped from P.E.Op.S. cause I'm lazy +static void InitADSR(void) // INIT ADSR +{ + unsigned long r, rs, rd; + int i; + + memset(RateTable, 0, sizeof(unsigned long) * 160); // build the rate table according to Neill's rules + + r = 3; + rs = 1; + rd = 0; + + for (i = 32; i < 160; i++) // we start at pos 32 with the real values... everything before is 0 + { + if (r < 0x3FFFFFFF) + { + r += rs; + rd++; + if (rd == 5) + { + rd = 1; + rs *= 2; + } + } + if (r > 0x3FFFFFFF) + r = 0x3FFFFFFF; + + RateTable[i] = r; + } +} + +void PSXConvADSR(REAL_ADSR* realADSR, unsigned short ADSR1, unsigned short ADSR2, bool bPS2) +{ + uint8_t Am = (ADSR1 & 0x8000) >> 15; // if 1, then Exponential, else linear + uint8_t Ar = (ADSR1 & 0x7F00) >> 8; + uint8_t Dr = (ADSR1 & 0x00F0) >> 4; + uint8_t Sl = ADSR1 & 0x000F; + uint8_t Rm = (ADSR2 & 0x0020) >> 5; + uint8_t Rr = ADSR2 & 0x001F; + + // The following are unimplemented in conversion (because DLS does not support Sustain Rate) + uint8_t Sm = (ADSR2 & 0x8000) >> 15; + uint8_t Sd = (ADSR2 & 0x4000) >> 14; + uint8_t Sr = (ADSR2 >> 6) & 0x7F; + + PSXConvADSR(realADSR, Am, Ar, Dr, Sl, Sm, Sd, Sr, Rm, Rr, bPS2); +} + + +void PSXConvADSR(REAL_ADSR* realADSR, + uint8_t Am, uint8_t Ar, uint8_t Dr, uint8_t Sl, + uint8_t Sm, uint8_t Sd, uint8_t Sr, uint8_t Rm, uint8_t Rr, bool bPS2) +{ + // Make sure all the ADSR values are within the valid ranges + if (((Am & ~0x01) != 0) || ((Ar & ~0x7F) != 0) || ((Dr & ~0x0F) != 0) || ((Sl & ~0x0F) != 0) || ((Rm & ~0x01) != 0) || ((Rr & ~0x1F) != 0) || ((Sm & ~0x01) != 0) || ((Sd & ~0x01) != 0) || ((Sr & ~0x7F) != 0)) + { + return; + } + + // PS1 games use 44k, PS2 uses 48k + double sampleRate = bPS2 ? 48000 : 44100; + + + int rateIncTable[8] = {0, 4, 6, 8, 9, 10, 11, 12}; + long envelope_level; + //long sustain_envelope_level; + double samples = 0; + unsigned long rate; + unsigned long remainder; + double timeInSecs; + //double theRate; + int l; + + if (!bRateTableInitialized) + { + InitADSR(); + bRateTableInitialized = true; + } + + //to get the dls 32 bit time cents, take log base 2 of number of seconds * 1200 * 65536 (dls1v11a.pdf p25). + + // if (RateTable[(Ar^0x7F)-0x10 + 32] == 0) + // realADSR->attack_time = 0; + // else + // { + if ((Ar ^ 0x7F) < 0x10) + Ar = 0; + if (Am == 0) //if linear Ar Mode + { + rate = RateTable[RoundToZero((Ar ^ 0x7F) - 0x10) + 32]; + samples = ceil(0x7FFFFFFF / (double) rate); + } + else if (Am == 1) + { + rate = RateTable[RoundToZero((Ar ^ 0x7F) - 0x10) + 32]; + samples = 0x60000000 / rate; + remainder = 0x60000000 % rate; + rate = RateTable[RoundToZero((Ar ^ 0x7F) - 0x18) + 32]; + samples += ceil(fmax(0, 0x1FFFFFFF - remainder) / (double) rate); + } + timeInSecs = samples / sampleRate; + realADSR->attack_time = timeInSecs; + // } + + + //Decay Time + + envelope_level = 0x7FFFFFFF; + + bool bSustainLevFound = false; + uint32_t realSustainLevel = 0; + for (l = 0; envelope_level > 0; l++) //DLS decay rate value is to -96db (silence) not the sustain level + { + if (4 * (Dr ^ 0x1F) < 0x18) + Dr = 0; + switch ((envelope_level >> 28) & 0x7) + { + case 0: + envelope_level -= RateTable[RoundToZero((4 * (Dr ^ 0x1F)) - 0x18 + 0) + 32]; + break; + case 1: + envelope_level -= RateTable[RoundToZero((4 * (Dr ^ 0x1F)) - 0x18 + 4) + 32]; + break; + case 2: + envelope_level -= RateTable[RoundToZero((4 * (Dr ^ 0x1F)) - 0x18 + 6) + 32]; + break; + case 3: + envelope_level -= RateTable[RoundToZero((4 * (Dr ^ 0x1F)) - 0x18 + 8) + 32]; + break; + case 4: + envelope_level -= RateTable[RoundToZero((4 * (Dr ^ 0x1F)) - 0x18 + 9) + 32]; + break; + case 5: + envelope_level -= RateTable[RoundToZero((4 * (Dr ^ 0x1F)) - 0x18 + 10) + 32]; + break; + case 6: + envelope_level -= RateTable[RoundToZero((4 * (Dr ^ 0x1F)) - 0x18 + 11) + 32]; + break; + case 7: + envelope_level -= RateTable[RoundToZero((4 * (Dr ^ 0x1F)) - 0x18 + 12) + 32]; + break; + } + if (!bSustainLevFound && ((envelope_level >> 27) & 0xF) <= Sl) + { + realSustainLevel = envelope_level; + bSustainLevFound = true; + } + } + samples = l; + timeInSecs = samples / sampleRate; + realADSR->decay_time = timeInSecs; + + // Sustain Rate + + envelope_level = 0x7FFFFFFF; + if (Sd == 0) // increasing... we won't even bother + { + realADSR->sustain_time = -1; + } + else + { + if (Sr == 0x7F) + realADSR->sustain_time = -1; // this is actually infinite + else + { + if (Sm == 0) // linear + { + rate = RateTable[RoundToZero((Sr ^ 0x7F) - 0x0F) + 32]; + samples = ceil(0x7FFFFFFF / (double) rate); + } + else + { + l = 0; + while (envelope_level > 0) //DLS decay rate value is to -96db (silence) not the sustain level + { + long envelope_level_diff = 0; + long envelope_level_target = 0; + + switch ((envelope_level >> 28) & 0x7) + { + case 0: + envelope_level_target = 0x00000000; + envelope_level_diff = RateTable[RoundToZero((Sr ^ 0x7F) - 0x1B + 0) + 32]; + break; + case 1: + envelope_level_target = 0x0fffffff; + envelope_level_diff = RateTable[RoundToZero((Sr ^ 0x7F) - 0x1B + 4) + 32]; + break; + case 2: + envelope_level_target = 0x1fffffff; + envelope_level_diff = RateTable[RoundToZero((Sr ^ 0x7F) - 0x1B + 6) + 32]; + break; + case 3: + envelope_level_target = 0x2fffffff; + envelope_level_diff = RateTable[RoundToZero((Sr ^ 0x7F) - 0x1B + 8) + 32]; + break; + case 4: + envelope_level_target = 0x3fffffff; + envelope_level_diff = RateTable[RoundToZero((Sr ^ 0x7F) - 0x1B + 9) + 32]; + break; + case 5: + envelope_level_target = 0x4fffffff; + envelope_level_diff = RateTable[RoundToZero((Sr ^ 0x7F) - 0x1B + 10) + 32]; + break; + case 6: + envelope_level_target = 0x5fffffff; + envelope_level_diff = RateTable[RoundToZero((Sr ^ 0x7F) - 0x1B + 11) + 32]; + break; + case 7: + envelope_level_target = 0x6fffffff; + envelope_level_diff = RateTable[RoundToZero((Sr ^ 0x7F) - 0x1B + 12) + 32]; + break; + } + + long steps = (envelope_level - envelope_level_target + (envelope_level_diff - 1)) / envelope_level_diff; + envelope_level -= (envelope_level_diff * steps); + l += steps; + } + samples = l; + } + timeInSecs = samples / sampleRate; + realADSR->sustain_time = /*Sm ? timeInSecs : */ LinAmpDecayTimeToLinDBDecayTime(timeInSecs, 0x800); + } + } + + //Sustain Level + //realADSR->sustain_level = (double)envelope_level/(double)0x7FFFFFFF;//(long)ceil((double)envelope_level * 0.030517578139210854); //in DLS, sustain level is measured as a percentage + if (Sl == 0) + realSustainLevel = 0x07FFFFFF; + realADSR->sustain_level = realSustainLevel / (double) 0x7FFFFFFF; + + + // If decay is going unused, and there's a sustain rate with sustain level close to max... + // we'll put the sustain_rate in place of the decay rate. + if ((realADSR->decay_time < 2 || (Dr == 0x0F && Sl >= 0x0C)) && Sr < 0x7E && Sd == 1) + { + realADSR->sustain_level = 0; + realADSR->decay_time = realADSR->sustain_time; + //realADSR->decay_time = 0.5; + } + + //Release Time + + //sustain_envelope_level = envelope_level; + + envelope_level = 0x7FFFFFFF; //We do this because we measure release time from max volume to 0, not from sustain level to 0 + + if (Rm == 0) //if linear Rr Mode + { + rate = RateTable[RoundToZero((4 * (Rr ^ 0x1F)) - 0x0C) + 32]; + + if (rate != 0) + samples = ceil((double) envelope_level / (double) rate); + else + samples = 0; + } + else if (Rm == 1) + { + if ((Rr ^ 0x1F) * 4 < 0x18) + Rr = 0; + for (l = 0; envelope_level > 0; l++) + { + switch ((envelope_level >> 28) & 0x7) + { + case 0: + envelope_level -= RateTable[RoundToZero((4 * (Rr ^ 0x1F)) - 0x18 + 0) + 32]; + break; + case 1: + envelope_level -= RateTable[RoundToZero((4 * (Rr ^ 0x1F)) - 0x18 + 4) + 32]; + break; + case 2: + envelope_level -= RateTable[RoundToZero((4 * (Rr ^ 0x1F)) - 0x18 + 6) + 32]; + break; + case 3: + envelope_level -= RateTable[RoundToZero((4 * (Rr ^ 0x1F)) - 0x18 + 8) + 32]; + break; + case 4: + envelope_level -= RateTable[RoundToZero((4 * (Rr ^ 0x1F)) - 0x18 + 9) + 32]; + break; + case 5: + envelope_level -= RateTable[RoundToZero((4 * (Rr ^ 0x1F)) - 0x18 + 10) + 32]; + break; + case 6: + envelope_level -= RateTable[RoundToZero((4 * (Rr ^ 0x1F)) - 0x18 + 11) + 32]; + break; + case 7: + envelope_level -= RateTable[RoundToZero((4 * (Rr ^ 0x1F)) - 0x18 + 12) + 32]; + break; + } + } + samples = l; + } + timeInSecs = samples / sampleRate; + + //theRate = timeInSecs / sustain_envelope_level; + //timeInSecs = 0x7FFFFFFF * theRate; //the release time value is more like a rate. It is the time from max value to 0, not from sustain level. + //if (Rm == 0) // if it's linear + // timeInSecs *= LINEAR_RELEASE_COMPENSATION; + + realADSR->release_time = /*Rm ? timeInSecs : */ LinAmpDecayTimeToLinDBDecayTime(timeInSecs, 0x800); + + // We need to compensate the decay and release times to represent them as the time from full vol to -100db + // where the drop in db is a fixed amount per time unit (SoundFont2 spec for vol envelopes, pg44.) + // We assume the psx envelope is using a linear scale wherein envelope_level / 2 == half loudness. + // For a linear release mode (Rm == 0), the time to reach half volume is simply half the time to reach 0. + // Half perceived loudness is -10db. Therefore, time_to_half_vol * 10 == full_time * 5 == the correct SF2 time + //realADSR->decay_time = LinAmpDecayTimeToLinDBDecayTime(realADSR->decay_time, 0x800); + //realADSR->sustain_time = LinAmpDecayTimeToLinDBDecayTime(realADSR->sustain_time, 0x800); + //realADSR->release_time = LinAmpDecayTimeToLinDBDecayTime(realADSR->release_time, 0x800); + + + + //Calculations are done, so now add the articulation data + //artic->AddADSR(attack_time, Am, decay_time, sustain_lev, release_time, 0); + return; +} \ No newline at end of file diff --git a/Source/AliveLibCommon/audio/ADSR.hpp b/Source/AliveLibCommon/audio/ADSR.hpp new file mode 100644 index 000000000..a1c72f817 --- /dev/null +++ b/Source/AliveLibCommon/audio/ADSR.hpp @@ -0,0 +1,77 @@ +#pragma once + +#include +#include "SDL_Types.h" + +// All of the ADSR calculations herein (except where inaccurate) are derived from Neill Corlett's work in +// reverse-engineering the Playstation 1/2 SPU unit. + +//************************************************************************************************** +// Type Redefinitions + + +class DLSArt; + +static unsigned long RateTable[160]; +static bool bRateTableInitialized = 0; + +// This next function converts seconds to full attenuation in a linear amplitude decay scale +// and approximates the time to full attenuation in a linear DB decay scale. +double LinAmpDecayTimeToLinDBDecayTime(double secondsToFullAtten, int linearVolumeRange); + +typedef struct _REAL_ADSR +{ + double attack_time; + double decay_time; + double sustain_level; + double sustain_time; + double release_time; +} REAL_ADSR; + +//VAG format ----------------------------------- +typedef struct _VAGHdr //File Header +{ + unsigned int id; //ID - "VAGp" + unsigned int ver; //Version - 0x20 + unsigned int __r1; + unsigned int len; //Length of data + unsigned int rate; //Sample rate + unsigned int __r2[3]; + char title[32]; +} VAGHdr; + + +typedef struct _VAGBlk //Sample Block +{ + struct + { + unsigned char range : 4; + unsigned char filter : 4; + } other; + + struct + { + unsigned char end : 1; //End block + unsigned char looping : 1; //VAG loops + unsigned char loop : 1; //Loop start point + } flag; + + char brr[14]; //Compressed samples +} VAGBlk; + + +//InitADSR is shamelessly ripped from P.E.Op.S. cause I'm lazy +static void InitADSR(void); + +inline int RoundToZero(int val) +{ + if (val < 0) + val = 0; + return val; +} + +void PSXConvADSR(REAL_ADSR* realADSR, unsigned short ADSR1, unsigned short ADSR2, bool bPS2); + +void PSXConvADSR(REAL_ADSR* realADSR, + uint8_t Am, uint8_t Ar, uint8_t Dr, uint8_t Sl, + uint8_t Sm, uint8_t Sd, uint8_t Sr, uint8_t Rm, uint8_t Rr, bool bPS2); \ No newline at end of file diff --git a/Source/AliveLibCommon/audio/Exceptions.hpp b/Source/AliveLibCommon/audio/Exceptions.hpp new file mode 100644 index 000000000..0e5d603ec --- /dev/null +++ b/Source/AliveLibCommon/audio/Exceptions.hpp @@ -0,0 +1,20 @@ +#include + +namespace Oddlib { +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; +}; +} // namespace Oddlib \ No newline at end of file diff --git a/Source/AliveLibCommon/audio/MidiPlayer.cpp b/Source/AliveLibCommon/audio/MidiPlayer.cpp new file mode 100644 index 000000000..e69de29bb diff --git a/Source/AliveLibCommon/audio/MidiPlayer.hpp b/Source/AliveLibCommon/audio/MidiPlayer.hpp new file mode 100644 index 000000000..b38b3c74a --- /dev/null +++ b/Source/AliveLibCommon/audio/MidiPlayer.hpp @@ -0,0 +1,46 @@ +#pragma once + +EXPORT void CC SND_Init(); +EXPORT void CC SND_Shutdown(); + +EXPORT void CC SND_Load_VABS(SoundBlockInfo* pSoundBlockInfo, s32 reverb); +EXPORT void CC SND_Load_Seqs(OpenSeqHandle* pSeqTable, const char_type* bsqFileName); + +EXPORT void CC SND_StopAll(); +EXPORT void SND_Reset(); +EXPORT void CC SND_Restart(); + +EXPORT void CC SND_Stop_Channels_Mask(u32 bitMask); + +EXPORT void CC SND_SEQ_Stop(u16 idx); +EXPORT s8 CC SND_Seq_Table_Valid(); +EXPORT s16 CC SND_SEQ_PlaySeq(u16 idx, s16 repeatCount, s16 bDontStop); +EXPORT void CC SND_SEQ_SetVol(s32 idx, s16 volLeft, s16 volRight); +EXPORT s16 CC SND_SEQ_Play(u16 idx, s16 repeatCount, s16 volLeft, s16 volRight); + +EXPORT s32 CC SND_SsIsEos_DeInlined(u16 idx); +EXPORT s32 CC SFX_SfxDefinition_Play(const SfxDefinition* sfxDef, s16 volLeft, s16 volRight, s16 pitch_min, s16 pitch_max); +EXPORT s32 CC SFX_SfxDefinition_Play(const SfxDefinition* sfxDef, s16 volume, s16 pitch_min, s16 pitch_max); +EXPORT s32 CC SND(s32 program, s32 vabId, s32 note, s16 vol, s16 min, s16 max); + +struct Vag final +{ + u16 field_0_adsr_attack; + u16 field_2_adsr_sustain_level; + u16 field_4_adsr_decay; + u16 field_6_adsr_release; + u8 field_8_min; + u8 field_9_max; + s16 field_A_shift_cen; + u8 field_C; + u8 field_D_vol; + u8 field_E_priority; + u8 field_F_prog; + u8 field_10_vag; + s8 field_11_pad; +}; + +//class Vab +//{ +//public: +//} \ No newline at end of file diff --git a/Source/AliveLibCommon/audio/SequencePlayer.cpp b/Source/AliveLibCommon/audio/SequencePlayer.cpp new file mode 100644 index 000000000..8c30a7f85 --- /dev/null +++ b/Source/AliveLibCommon/audio/SequencePlayer.cpp @@ -0,0 +1,353 @@ +#include "SequencePlayer.hpp" + +SequencePlayer::SequencePlayer() +{ + // Start the Sequencer thread. + m_SequenceThread = new std::thread(&SequencePlayer::m_PlayerThreadFunction, this); +} + +SequencePlayer::~SequencePlayer() +{ + m_KillThread = true; + m_SequenceThread->join(); + delete m_SequenceThread; +} + +// Midi stuff +static void _SndMidiSkipLength(Oddlib::Stream& stream, int skip) +{ + stream.Seek(stream.Pos() + skip); +} + +// Midi stuff +static Uint32 _MidiReadVarLen(Oddlib::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; +} + +float SequencePlayer::MidiTimeToSample(int time) +{ + // This may, or may not be correct. // TODO: Revise + return ((60 * time) / m_SongTempo) * (AliveAudioSampleRate / 500.0f); +} + +void SequencePlayer::m_PlayerThreadFunction() +{ + int channels[16]; + for (int i = 0; i < 16; i++) + { + channels[i] = 0; + } + + while (!m_KillThread) + { + m_PlayerStateMutex.lock(); + if (m_PlayerState == ALIVE_SEQUENCER_INIT_VOICES) + { + bool firstNote = true; + AliveAudio::LockNotes(); + for (int i = 0; i < (int)m_MessageList.size(); i++) + { + AliveAudioMidiMessage m = m_MessageList[i]; + switch (m.Type) + { + case ALIVE_MIDI_NOTE_ON: + AliveAudio::NoteOn(channels[m.Channel], m.Note, (char) m.Velocity, m_TrackID, MidiTimeToSample(m.TimeOffset)); + if (firstNote) + { + m_SongBeginSample = ((int) (AliveAudio::currentSampleIndex + MidiTimeToSample(m.TimeOffset))); + firstNote = false; + } + break; + case ALIVE_MIDI_NOTE_OFF: + AliveAudio::NoteOffDelay(channels[m.Channel], m.Note, m_TrackID, MidiTimeToSample(m.TimeOffset)); // Fix this. Make note off's have an offset in the voice timeline. + break; + case ALIVE_MIDI_PROGRAM_CHANGE: + channels[m.Channel] = m.Special; + break; + case ALIVE_MIDI_ENDTRACK: + m_PlayerState = ALIVE_SEQUENCER_PLAYING; + m_SongFinishSample = ((int) (AliveAudio::currentSampleIndex + MidiTimeToSample(m.TimeOffset))); + break; + } + } + AliveAudio::UnlockNotes(); + } + m_PlayerStateMutex.unlock(); + + if (m_PlayerState == ALIVE_SEQUENCER_PLAYING && AliveAudio::currentSampleIndex > m_SongFinishSample) + { + m_PlayerState = ALIVE_SEQUENCER_FINISHED; + + // Give a quarter beat anyway + if (m_QuarterCallback != nullptr) + m_QuarterCallback(); + } + + if (m_PlayerState == ALIVE_SEQUENCER_PLAYING) + { + int quarterBeat = (m_SongFinishSample - m_SongBeginSample) / m_TimeSignatureBars; + int currentQuarterBeat = (int) (floor(GetPlaybackPositionSample() / quarterBeat)); + + if (m_PrevBar != currentQuarterBeat) + { + m_PrevBar = currentQuarterBeat; + + if (m_QuarterCallback != nullptr) + m_QuarterCallback(); + } + } + } +} + +int SequencePlayer::GetPlaybackPositionSample() +{ + return ((int) (AliveAudio::currentSampleIndex - m_SongBeginSample)); +} + +void SequencePlayer::StopSequence() +{ + AliveAudio::ClearAllTrackVoices(m_TrackID); + m_PlayerStateMutex.lock(); + m_PlayerState = ALIVE_SEQUENCER_STOPPED; + m_PrevBar = 0; + m_PlayerStateMutex.unlock(); +} + +void SequencePlayer::PlaySequence() +{ + m_PlayerStateMutex.lock(); + if (m_PlayerState == ALIVE_SEQUENCER_STOPPED || m_PlayerState == ALIVE_SEQUENCER_FINISHED) + { + m_PrevBar = 0; + m_PlayerState = ALIVE_SEQUENCER_INIT_VOICES; + } + m_PlayerStateMutex.unlock(); +} + +int SequencePlayer::LoadSequenceData(std::vector seqData) +{ + Oddlib::Stream stream(std::move(seqData)); + + return LoadSequenceStream(stream); +} + +int SequencePlayer::LoadSequenceStream(Oddlib::Stream& stream) +{ + StopSequence(); + m_MessageList.clear(); + + SeqHeader seqHeader; + + // Read the header + + stream.ReadUInt32(seqHeader.mMagic); + stream.ReadUInt32(seqHeader.mVersion); + stream.ReadUInt16(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); + } + + m_TimeSignatureBars = seqHeader.mTimeSignatureBars; + + m_SongTempo = ((float) (60000000.0 / tempoValue)); + + + unsigned int deltaTime = 0; + + const size_t midiDataStart = stream.Pos(); + + // 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 0; // 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: + { + //std::cout << "end of track" << std::endl; + m_MessageList.push_back(AliveAudioMidiMessage(ALIVE_MIDI_ENDTRACK, deltaTime, 0, 0, 0)); + return 0; + //Sint32 loopCount = gSeqInfo.iNumTimesToLoop; // v1 some hard coded data?? or just a local static? + //if (loopCount) // If zero then loop forever + //{ + // --loopCount; + + // //char buf[256]; + // //sprintf(buf, "EOT: %d loops left\n", loopCount); + // // OutputDebugString(buf); + + // gSeqInfo.iNumTimesToLoop = loopCount; //v1 + // if (loopCount <= 0) + // { + // //getNext_q(aSeqIndex); // Done playing? Ptr not reset to start + // return 1; + // } + //} + + ////OutputDebugString("EOT: Loop forever\n"); + //// Must be a loop back to the start? + //stream.Seek(midiDataStart); + } + + 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); + if (velocity == 0) // If velocity is 0, then the sequence means to do "Note Off" + { + m_MessageList.push_back(AliveAudioMidiMessage(ALIVE_MIDI_NOTE_OFF, deltaTime, channel, note, velocity)); + } + else + { + m_MessageList.push_back(AliveAudioMidiMessage(ALIVE_MIDI_NOTE_ON, deltaTime, channel, note, velocity)); + } + } + break; + case 0x8: // Note Off + { + Uint8 note = 0; + stream.ReadUInt8(note); + Uint8 velocity = 0; + stream.ReadUInt8(velocity); + + m_MessageList.push_back(AliveAudioMidiMessage(ALIVE_MIDI_NOTE_OFF, deltaTime, channel, note, velocity)); + } + break; + case 0xc: // Program Change + { + Uint8 prog = 0; + stream.ReadUInt8(prog); + m_MessageList.push_back(AliveAudioMidiMessage(ALIVE_MIDI_PROGRAM_CHANGE, deltaTime, channel, 0, 0, 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 + { + Uint16 bend = 0; + stream.ReadUInt16(bend); + } + break; + case 0xf: // Sysex len + { + const Uint32 length = _MidiReadVarLen(stream); + _SndMidiSkipLength(stream, length); + } + break; + default: + throw std::runtime_error("Unknown MIDI command"); + } + } + } +} \ No newline at end of file diff --git a/Source/AliveLibCommon/audio/SequencePlayer.hpp b/Source/AliveLibCommon/audio/SequencePlayer.hpp new file mode 100644 index 000000000..bc4f652b3 --- /dev/null +++ b/Source/AliveLibCommon/audio/SequencePlayer.hpp @@ -0,0 +1,98 @@ +#pragma once + +#include +#include +#include +#include +#include "Stream.hpp" +#include "mixer/AliveAudio.hpp" + +struct SeqHeader +{ + Uint32 mMagic; // SEQp + Uint32 mVersion; // Seems to always be 1 + Uint16 mResolutionOfQuaterNote; + Uint8 mTempo[3]; + Uint8 mTimeSignatureBars; + Uint8 mTimeSignatureBeats; +}; + +struct SeqInfo +{ + Uint32 iLastTime = 0; + Sint32 iNumTimesToLoop = 0; + Uint8 running_status = 0; +}; + +// The types of Midi Messages the sequencer will play. +enum AliveAudioMidiMessageType +{ + ALIVE_MIDI_NOTE_ON = 1, + ALIVE_MIDI_NOTE_OFF = 2, + ALIVE_MIDI_PROGRAM_CHANGE = 3, + ALIVE_MIDI_ENDTRACK = 4, +}; + +// The current state of a SequencePlayer. +enum AliveAudioSequencerState +{ + ALIVE_SEQUENCER_STOPPED = 1, + ALIVE_SEQUENCER_PLAYING = 3, + ALIVE_SEQUENCER_FINISHED = 4, + ALIVE_SEQUENCER_INIT_VOICES = 5, +}; + +struct AliveAudioMidiMessage +{ + AliveAudioMidiMessage(AliveAudioMidiMessageType type, int timeOffset, int channel, int note, int velocity, int special = 0) + { + Type = type; + Channel = channel; + Note = note; + Velocity = velocity; + TimeOffset = timeOffset; + Special = special; + } + AliveAudioMidiMessageType Type; + int Channel; + int Note; + int Velocity; + int TimeOffset; + int Special = 0; +}; + +// Gets called every time the play position is at 1/4 of the song. +// Useful for changing sequences but keeping the time signature in sync. +typedef void (*AliveAudioQuarterCallback)(); + +class SequencePlayer +{ +public: + SequencePlayer(); + ~SequencePlayer(); + + int LoadSequenceData(std::vector seqData); + int LoadSequenceStream(Oddlib::Stream& stream); + void PlaySequence(); + void StopSequence(); + + float MidiTimeToSample(int time); + int GetPlaybackPositionSample(); + + int m_TrackID = 1; // The track ID. Use this to seperate SoundFX from Music. + AliveAudioSequencerState m_PlayerState = ALIVE_SEQUENCER_STOPPED; + AliveAudioQuarterCallback m_QuarterCallback; + + //private: + int m_KillThread = false; // If true, loop thread will exit. + int m_SongFinishSample = 0; // Not relative. + int m_SongBeginSample = 0; // Not relative. + int m_PrevBar = 0; + int m_TimeSignatureBars; + float m_SongTempo; + void m_PlayerThreadFunction(); + std::vector m_MessageList; + std::thread* m_SequenceThread; + std::mutex m_MessageListMutex; + std::mutex m_PlayerStateMutex; +}; diff --git a/Source/AliveLibCommon/audio/Soundbank.cpp b/Source/AliveLibCommon/audio/Soundbank.cpp new file mode 100644 index 000000000..c16b0ad06 --- /dev/null +++ b/Source/AliveLibCommon/audio/Soundbank.cpp @@ -0,0 +1,24 @@ +#pragma once + +#include "Soundbank.hpp" + +float Sample::GetSample(float sampleOffset) +{ + AliveAudioHelper helper; + + //if (!AliveAudio::Interpolation) + return helper.SampleSint16ToFloat(m_SampleBuffer[(int) sampleOffset]); // No interpolation. Faster but sounds jaggy. + + //int roundedOffset = (int) floor(sampleOffset); + //return helper.SampleSint16ToFloat(s16(helper.Lerp(m_SampleBuffer[roundedOffset], m_SampleBuffer[roundedOffset + 1], sampleOffset - float(roundedOffset)))); +} + +Soundbank::Soundbank(std::vector samples, std::vector programs) +{ + m_Samples = samples; + m_Programs = programs; +} +Soundbank::~Soundbank() +{ + +} \ No newline at end of file diff --git a/Source/AliveLibCommon/audio/Soundbank.hpp b/Source/AliveLibCommon/audio/Soundbank.hpp new file mode 100644 index 000000000..8643379cb --- /dev/null +++ b/Source/AliveLibCommon/audio/Soundbank.hpp @@ -0,0 +1,90 @@ +#pragma once + + class AliveAudioHelper +{ +public: + float Lerp(u16 from, u16 to, float t) + { + return from + ((to - from) * t); + } + float SampleSint16ToFloat(s16 v) + { + return (v / 32767.0f); + } + + float RandFloat(float a, float b) + { + return ((b - a) * ((float) rand() / RAND_MAX)) + a; + } +}; + +/* +* Raw audio data. An audio sample. +*/ +class Sample +{ +public: + u16* m_SampleBuffer; + u32 i_SampleSize; + float GetSample(float sampleOffset); +}; + + +/* +* Describes how to play a sample (ADSR curves and pitch shift) +*/ +class Tone +{ +public: + // volume 0-1 + float f_Volume; + + // panning -1 - 1 + float f_Pan; + + // Root Key + unsigned char c_Center; + unsigned char 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; +}; + + +/* +* Maintains a collection of Samples (raw audio) and +* Programs (how to play samples with ADSR and pitch) +*/ +class Soundbank +{ +public: + ~Soundbank(); + Soundbank(std::vector samples, std::vector programs); + + std::vector m_Samples; + std::vector m_Programs; +}; \ No newline at end of file diff --git a/Source/AliveLibCommon/audio/Stream.cpp b/Source/AliveLibCommon/audio/Stream.cpp new file mode 100644 index 000000000..84a904cb2 --- /dev/null +++ b/Source/AliveLibCommon/audio/Stream.cpp @@ -0,0 +1,91 @@ +#include +#include +#include +#include +#include "logger.hpp" +#include "Stream.hpp" +#include "Exceptions.hpp" + +namespace Oddlib { +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 Oddlib \ No newline at end of file diff --git a/Source/AliveLibCommon/audio/Stream.hpp b/Source/AliveLibCommon/audio/Stream.hpp new file mode 100644 index 000000000..d2ba0aa2a --- /dev/null +++ b/Source/AliveLibCommon/audio/Stream.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include +#include +#include +#include "SDL_types.h" + +namespace Oddlib { +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 Oddlib \ No newline at end of file diff --git a/Source/AliveLibCommon/audio/mixer/AliveAudio.cpp b/Source/AliveLibCommon/audio/mixer/AliveAudio.cpp new file mode 100644 index 000000000..8374af585 --- /dev/null +++ b/Source/AliveLibCommon/audio/mixer/AliveAudio.cpp @@ -0,0 +1,227 @@ +#pragma once + +#include +#include "AliveAudio.hpp" + +Soundbank* AliveAudio::m_CurrentSoundbank = nullptr; +std::mutex AliveAudio::voiceListMutex; +std::vector AliveAudio::m_Voices; +long long AliveAudio::currentSampleIndex = 20; +bool AliveAudio::voiceListLocked = false; + +void AliveInitAudio() +{ + SDL_Init(SDL_INIT_AUDIO); + + SDL_AudioSpec waveSpec; + waveSpec.callback = AliveAudioSDLCallback; + waveSpec.userdata = nullptr; + waveSpec.channels = 2; + waveSpec.freq = AliveAudioSampleRate; + 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); +} + +void AliveAudio::PlayOneShot(int programId, int note, s32 volume, float pitch) +{ + voiceListMutex.lock(); + for (auto program : m_CurrentSoundbank->m_Programs) + { + if (program->prog_id != programId) + { + continue; + } + + for (auto tone : program->m_Tones) + { + if (note >= tone->Min && note <= tone->Max) + { + Voice* voice = new Voice(); + voice->i_Note = note; + voice->f_Velocity = float(volume) / 127; + voice->m_Tone = tone; + voice->f_Pitch = pitch; + m_Voices.push_back(voice); + } + } + } + voiceListMutex.unlock(); +} +void AliveAudio::PlayOneShot(std::string soundID) +{ + soundID; +} + +void AliveAudio::NoteOn(int programId, int note, char velocity, float pitch , int trackID , float trackDelay ) +{ + if (!voiceListLocked) + { + voiceListMutex.lock(); + } + + for (auto program : m_CurrentSoundbank->m_Programs) + { + if (program->prog_id != programId) + { + continue; + } + for (auto tone : program->m_Tones) + { + if (note >= tone->Min && note <= tone->Max) + { + Voice* voice = new Voice(); + voice->i_Note = note; + voice->m_Tone = tone; + voice->i_Program = programId; + voice->f_Velocity = velocity / 127.0f; + voice->i_TrackID = trackID; + voice->f_Pitch = pitch; + voice->f_TrackDelay = trackDelay; + m_Voices.push_back(voice); + } + } + } + if (!voiceListLocked) + voiceListMutex.unlock(); +} +void AliveAudio::NoteOn(int program, int note, char velocity, int trackID , float trackDelay) +{ + NoteOn(program, note, velocity, 0, trackID, trackDelay); +} + +void AliveAudio::NoteOff(int program, int note, int trackID ) +{ + program; + note; + trackID; + std::cout << "off"; +} +void AliveAudio::NoteOffDelay(int program, int note, int trackID , float trackDelay) +{ + program; + note; + trackID; + trackDelay; +} +void AliveAudio::DebugPlayFirstToneSample(int program, int tone) +{ + program; + tone; +} +void AliveAudio::LockNotes() +{ +} +void AliveAudio::UnlockNotes() +{ +} + +void AliveAudio::ClearAllVoices(bool forceKill) +{ + forceKill; +} +void AliveAudio::ClearAllTrackVoices(int trackID, bool forceKill) +{ + trackID; + forceKill; +} + +void CleanVoices() +{ + AliveAudio::voiceListMutex.lock(); + std::vector deadVoices; + + for (auto voice : AliveAudio::m_Voices) + { + if (voice->b_Dead) + { + deadVoices.push_back(voice); + } + } + + for (auto obj : deadVoices) + { + delete obj; + + AliveAudio::m_Voices.erase(std::remove(AliveAudio::m_Voices.begin(), AliveAudio::m_Voices.end(), obj), AliveAudio::m_Voices.end()); + } + AliveAudio::voiceListMutex.unlock(); +} + +void AliveRenderAudio(float* AudioStream, int StreamLength) +{ + static float tick = 0; + static int note = 0; + + AliveAudio::voiceListMutex.lock(); + int voiceCount = AliveAudio::m_Voices.size(); + Voice** rawPointer = AliveAudio::m_Voices.data(); // Real nice speed boost here. + + for (int i = 0; i < StreamLength; i += 2) + { + for (int v = 0; v < voiceCount; v++) + { + Voice* voice = rawPointer[v]; // Raw pointer skips all that vector bottleneck crap + + voice->f_TrackDelay--; + + if (voice->m_UsesNoteOffDelay) + voice->f_NoteOffDelay--; + + if (voice->m_UsesNoteOffDelay && voice->f_NoteOffDelay <= 0 && voice->b_NoteOn == true) + { + voice->b_NoteOn = false; + //printf("off"); + } + + if (voice->b_Dead || voice->f_TrackDelay > 0) + continue; + + float centerPan = voice->m_Tone->f_Pan; + float leftPan = 1.0f; + float rightPan = 1.0f; + + if (centerPan > 0) + { + leftPan = 1.0f - abs(centerPan); + } + if (centerPan < 0) + { + rightPan = 1.0f - abs(centerPan); + } + + float s = voice->GetSample(); + + float leftSample = s * leftPan; + float rightSample = s * rightPan; + + SDL_MixAudioFormat((Uint8*) (AudioStream + i), (const Uint8*) &leftSample, AUDIO_F32, sizeof(float), 37); // Left Channel + SDL_MixAudioFormat((Uint8*) (AudioStream + i + 1), (const Uint8*) &rightSample, AUDIO_F32, sizeof(float), 37); // Right Channel + } + + AliveAudio::currentSampleIndex++; + } + AliveAudio::voiceListMutex.unlock(); + + CleanVoices(); +} + +void AliveAudioSDLCallback(void* udata, Uint8* stream, int len) +{ + udata; + memset(stream, 0, len); + AliveRenderAudio((float*) stream, len / sizeof(float)); + + //if (AliveAudio::EQEnabled) + // AliveEQEffect((float*) stream, len / sizeof(float)); +} + + diff --git a/Source/AliveLibCommon/audio/mixer/AliveAudio.hpp b/Source/AliveLibCommon/audio/mixer/AliveAudio.hpp new file mode 100644 index 000000000..744119ecc --- /dev/null +++ b/Source/AliveLibCommon/audio/mixer/AliveAudio.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include "SDL.h" + +#include +#include +#include +#include +#include +#include "../Soundbank.hpp" +#include "Voice.hpp" + + +void AliveInitAudio(); +void AliveAudioSDLCallback(void* udata, Uint8* stream, int len); + +const int AliveAudioSampleRate = 44100; + +class AliveAudio +{ +public: + static Soundbank* m_CurrentSoundbank; + static std::mutex voiceListMutex; + static std::vector m_Voices; + static bool AliveAudio::voiceListLocked; + + static void PlayOneShot(s32 program, s32 note, s32 volume, float pitch = 0); + static void PlayOneShot(std::string soundID); + + static void NoteOn(int program, int note, char velocity, float pitch = 0, int trackID = 0, float trackDelay = 0); + static void NoteOn(int program, int note, char velocity, int trackID = 0, float trackDelay = 0); + + static void NoteOff(int program, int note, int trackID = 0); + static void NoteOffDelay(int program, int note, int trackID = 0, float trackDelay = 0); + + static void DebugPlayFirstToneSample(int program, int tone); + + static void LockNotes(); + static void UnlockNotes(); + + static void CleanVoices(); + static void ClearAllVoices(bool forceKill = true); + static void ClearAllTrackVoices(int trackID, bool forceKill = false); + + static long long currentSampleIndex; +}; diff --git a/Source/AliveLibCommon/audio/mixer/Voice.cpp b/Source/AliveLibCommon/audio/mixer/Voice.cpp new file mode 100644 index 000000000..e0a0b3c59 --- /dev/null +++ b/Source/AliveLibCommon/audio/mixer/Voice.cpp @@ -0,0 +1,52 @@ +#pragma once +#include "Voice.hpp" +#include "AliveAudio.hpp" + +float Voice::GetSample() +{ + if (b_Dead) // Dont return anything if dead. This voice should now be removed. + return 0; + + // ActiveDecayLevel -= ((1.0 / AliveAudioSampleRate) / m_Tone->DecayTime); + + if (ActiveDecayLevel < 0) + ActiveDecayLevel = 0; + + if (!b_NoteOn) + { + ActiveReleaseLevel -= ((1.0 / AliveAudioSampleRate) / m_Tone->ReleaseTime); + + ActiveReleaseLevel -= ((1.0 / AliveAudioSampleRate) / m_Tone->DecayTime); // Hacks?!?! -------------------- + + if (ActiveReleaseLevel <= 0) // Release is done. So the voice is done. + { + b_Dead = true; + ActiveReleaseLevel = 0; + } + } + else + { + ActiveAttackLevel += ((1.0 / AliveAudioSampleRate) / m_Tone->AttackTime); + + if (ActiveAttackLevel > 1) + ActiveAttackLevel = 1; + } + + // For some reason, for samples that dont loop, they need to be cut off 1 sample earlier. + // Todo: Revise this. Maybe its the loop flag at the end of the sample!? + if ((m_Tone->Loop) ? f_SampleOffset >= m_Tone->m_Sample->i_SampleSize : (f_SampleOffset >= m_Tone->m_Sample->i_SampleSize - 1)) + { + if (m_Tone->Loop) + f_SampleOffset = 0; + else + { + b_Dead = true; + return 0; // Voice is dead now, so don't return anything. + } + } + + double sampleFrameRate = pow(1.059463, i_Note - m_Tone->c_Center + m_Tone->Pitch + f_Pitch) * (44100.0 / AliveAudioSampleRate); + f_SampleOffset += (sampleFrameRate); + + return ((float) (m_Tone->m_Sample->GetSample(float(f_SampleOffset)) * ActiveAttackLevel * ActiveDecayLevel * ((b_NoteOn) ? ActiveSustainLevel : ActiveReleaseLevel) * f_Velocity)); +} \ No newline at end of file diff --git a/Source/AliveLibCommon/audio/mixer/Voice.hpp b/Source/AliveLibCommon/audio/mixer/Voice.hpp new file mode 100644 index 000000000..fa71b1e10 --- /dev/null +++ b/Source/AliveLibCommon/audio/mixer/Voice.hpp @@ -0,0 +1,29 @@ +#pragma once +#include "../Soundbank.hpp" + +class Voice +{ +public: + Tone* m_Tone; + int i_Program; + int i_Note = 0; + bool b_Dead = false; + double f_SampleOffset = 0; + bool b_NoteOn = true; + double f_Velocity = 1.0f; + double f_Pitch = 0.0f; + + int i_TrackID = 0; // This is used to distinguish between sounds fx and music + double f_TrackDelay = 0; // Used by the sequencer for perfect timing + bool m_UsesNoteOffDelay = false; + double f_NoteOffDelay = 0; + + // Active ADSR Levels + + double ActiveAttackLevel = 0; + double ActiveReleaseLevel = 1; + double ActiveDecayLevel = 1; + double ActiveSustainLevel = 1; + + float GetSample(); +}; \ No newline at end of file From 5bd001712d01f990053028f834cbab1c4fb43235 Mon Sep 17 00:00:00 2001 From: Sean Horton <3359434+sean-horton@users.noreply.github.com> Date: Wed, 5 Oct 2022 19:22:06 -0400 Subject: [PATCH 07/82] Kind of working in AO and AE now --- Source/AliveLibAE/Sound/Midi.cpp | 300 +++++++---------- Source/AliveLibAO/Midi.cpp | 313 +++++------------- Source/AliveLibCommon/audio/Exceptions.hpp | 6 +- Source/AliveLibCommon/audio/MidiPlayer.cpp | 302 +++++++++++++++++ Source/AliveLibCommon/audio/MidiPlayer.hpp | 255 +++++++++++--- .../AliveLibCommon/audio/SequencePlayer.cpp | 43 ++- .../AliveLibCommon/audio/SequencePlayer.hpp | 6 +- Source/AliveLibCommon/audio/Soundbank.cpp | 6 +- Source/AliveLibCommon/audio/Soundbank.hpp | 8 +- Source/AliveLibCommon/audio/Stream.cpp | 6 +- Source/AliveLibCommon/audio/Stream.hpp | 6 +- .../AliveLibCommon/audio/mixer/AliveAudio.cpp | 113 +++++-- .../AliveLibCommon/audio/mixer/AliveAudio.hpp | 3 + Source/AliveLibCommon/audio/mixer/Voice.cpp | 6 +- Source/AliveLibCommon/audio/mixer/Voice.hpp | 6 +- 15 files changed, 873 insertions(+), 506 deletions(-) diff --git a/Source/AliveLibAE/Sound/Midi.cpp b/Source/AliveLibAE/Sound/Midi.cpp index d5db926ea..7ecbf24d4 100644 --- a/Source/AliveLibAE/Sound/Midi.cpp +++ b/Source/AliveLibAE/Sound/Midi.cpp @@ -19,7 +19,6 @@ #include "PsxSpuApi.hpp" #include "AmbientSound.hpp" #include "SDL_Mixer.h" -#include "../../AliveLibCommon/audio/MidiPlayer.hpp" #if !ALTERNATE_AUDIO EXPORT void CC SFX_SetPitch_4CA510(const SfxDefinition* pSfx, s32 channelsBits, s16 pitch); @@ -870,118 +869,78 @@ 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 -struct VagAtr final -{ - s8 field_0_priority; - s8 field_1_mode; - s8 field_2_vol; - s8 field_3_pan; - u8 field_4_centre; - u8 field_5_shift; - s8 field_6_min; - s8 field_7_max; - 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; -}; - -EXPORT void CC SND_StopAll_4CB060() -{ -} -EXPORT void CC SND_Init_4CA1F0() -{ - -} -EXPORT void CC SND_Shutdown_4CA280() -{ +#include "../../AliveLibCommon/audio/MidiPlayer.hpp" -} -EXPORT void CC SND_Stop_Channels_Mask_4CA810(u32 bitMask) -{ - bitMask; -} -EXPORT void SND_Reset_4C9FB0() +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; + } -EXPORT void CC SND_Load_VABS_4CA350(SoundBlockInfo* pSoundBlockInfo, s32 reverb) -{ - reverb; - while (1) + psx::ResourceData* readSeq(const char_type* fileName, const char_type* sequenceName) { - if (!pSoundBlockInfo->field_0_vab_header_name) + psx::ResourceData* resource = new psx::ResourceData(); + resource->data = 0; + resource->size = 0; + + if (!sequenceName || sequenceName == nullptr) { - break; + return resource; } - // field_14_prog is what is passed to "playsound" - // I then have to look up field_16_vag to play the sample + // 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); + } - // Read header - LvlFileRecord* pVabHeaderFile = sLvlArchive_5BC520.Find_File_Record_433160(pSoundBlockInfo->field_0_vab_header_name); - u8* ppVabHeader = new u8[pVabHeaderFile->field_10_num_sectors << 11]; - sLvlArchive_5BC520.Read_File_4330A0(pVabHeaderFile, ppVabHeader); - VabHeader* vabHeader = reinterpret_cast(ppVabHeader); - VagAtr* vagAttr = (VagAtr*) &vabHeader[1]; - for (s32 i = 0; i < vabHeader->field_12_num_progs; i++) + s32 hash = ResourceManager::SEQ_HashName_49BE30(sequenceName); + u8** ppSeq = ResourceManager::GetLoadedResource_49C2A0(ResourceManager::Resource_Seq, hash, 1, 1); + if (!ppSeq) { - for (s32 toneCounter = 0; toneCounter < 16; toneCounter++) - { - if (vagAttr->field_2_vol > 0) - { - Converted_Vag* pData = new Converted_Vag; - - pData->field_F_prog = static_cast(vagAttr->field_14_prog); - pData->field_10_vag = LOBYTE(vagAttr->field_16_vag) - 1; - pData->field_C = 0; - pData->field_D_vol = vagAttr->field_2_vol; - pData->field_E_priority = vagAttr->field_0_priority; - pData->field_8_min = vagAttr->field_6_min; - pData->field_9_max = vagAttr->field_7_max; - - const s16 centre = vagAttr->field_4_centre; - pData->field_A_shift_cen = 2 * (vagAttr->field_5_shift + (centre << 7)); - - f32 sustain_level = static_cast((2 * (~(u8) vagAttr->field_10_adsr1 & 0xF))); - - pData->field_0_adsr_attack = std::min(static_cast((powf(2.0f, ((vagAttr->field_10_adsr1 >> 8) & 0x7F) * 0.25f) * 0.09f)), static_cast(32767)); - pData->field_4_adsr_decay = static_cast((((vagAttr->field_10_adsr1 >> 4) & 0xF) / 15.0f) * 16.0); - pData->field_2_adsr_sustain_level = std::min(static_cast((sustain_level / 15.0f) * 600.0), static_cast(32767)); - pData->field_6_adsr_release = std::min(static_cast(pow(2, vagAttr->field_12_adsr2 & 0x1F) * 0.045f), static_cast(32767)); - - // If decay is at max, then nothing should play. So mute sustain too ? - if (pData->field_4_adsr_decay == 16) - { - pData->field_2_adsr_sustain_level = 0; - } - - pData->field_11_pad = vagAttr->field_3_pan; - } - ++vagAttr; - } + return resource; } - // Read body - // THIS IS THE DIFFERENCE BETWEEN AO. For some reason audio is stored in sounds.dat instead of the sector + u32 size = ResourceManager::Get_Header_49C410(ppSeq)->field_0_size; + resource->data = *ppSeq; + resource->size = size; + 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()) @@ -998,25 +957,19 @@ EXPORT void CC SND_Load_VABS_4CA350(SoundBlockInfo* pSoundBlockInfo, s32 reverb) // Tada! } - LvlFileRecord* pVabBodyFile = sLvlArchive_5BC520.Find_File_Record_433160(pSoundBlockInfo->field_4_vab_body_name); - s32 bodySize = pVabBodyFile->field_10_num_sectors << 11; - u8* ppVabBody = new u8[bodySize]; - sLvlArchive_5BC520.Read_File_4330A0(pVabBodyFile, ppVabBody); - int pos = 0; - for (s32 i = 0; i < vabHeader->field_12_num_progs; i++) + for (s32 i = 0; i < vabHeader->field_16_num_vags; i++) { - VabBodyRecord* record = reinterpret_cast(&ppVabBody[pos]); - u32 size = *reinterpret_cast(&ppVabBody[pos]); pos += sizeof(u32); u32 sampleRate = *reinterpret_cast(&ppVabBody[pos]); pos += sizeof(u32); + sampleRate; // In AO the body is at this part. // IN AE instead it's an offset in sounds.dat - // Not sure why they did this. + // Not sure of the reason. u32 offset = *reinterpret_cast(&ppVabBody[pos]); pos += sizeof(u32); @@ -1024,105 +977,106 @@ EXPORT void CC SND_Load_VABS_4CA350(SoundBlockInfo* pSoundBlockInfo, s32 reverb) u8* data = new u8[size]; memcpy(data, &soundData[offset], size); - size; - sampleRate; - record; - vabHeader; + psx::Sample* sample = new psx::Sample(); + sample->m_SampleBuffer = reinterpret_cast(data); + sample->i_SampleSize = size / 2; + samples.push_back(sample); } - delete[] ppVabBody; - pSoundBlockInfo++; + soundDatFile.close(); + + return samples; } +}; + +psx::MidiPlayer* player = new psx::MidiPlayer(new AEResourceProvider(), new AESoundSampleParser()); + +EXPORT void CC SND_StopAll_4CB060() +{ + player->SND_StopAll(); } -EXPORT s32 CC SND_4CA5D0(s32 program, s32 vabId, s32 note, s16 vol, s16 min, s16 max) + +EXPORT void CC SND_Init_4CA1F0() { - program; - vabId; - note; - vol; - min; - max; - return 1; + player->SND_Init(); } -EXPORT void CC SND_Restart_4CB0E0() + +EXPORT void CC SND_Shutdown_4CA280() { + player->SND_Shutdown(); +} +EXPORT void CC SND_Stop_Channels_Mask_4CA810(u32 bitMask) +{ + bitMask; + player->SND_Stop_Channels_Mask(bitMask); } -EXPORT void CC SND_Load_Seqs_4CAED0(OpenSeqHandle* pSeqTable, const char_type* bsqFileName) + +EXPORT void SND_Reset_4C9FB0() { - if (!pSeqTable || !bsqFileName) - { - return; - } + player->SND_Reset(); +} - OpenSeqHandle* seq = reinterpret_cast<::OpenSeqHandle*>(pSeqTable); - ResourceManager::LoadResourceFile_49C170(bsqFileName, nullptr); - for (s32 i = 0; i < 144; i++) // 144 is a hardcoded value - { - seq[i].field_A_id_seqOpenId = -1; - seq[i].field_4_generated_res_id = ResourceManager::SEQ_HashName_49BE30(seq[i].field_0_mBsqName); +EXPORT void CC SND_Load_VABS_4CA350(SoundBlockInfo* pSoundBlockInfo, s32 reverb) +{ + pSoundBlockInfo; + reverb; + player->SND_Load_VABS(reinterpret_cast(pSoundBlockInfo), reverb); +} - // ppSeq is what can be played - u8** ppSeq = ResourceManager::GetLoadedResource_49C2A0(ResourceManager::Resource_Seq, seq[i].field_4_generated_res_id, 1, 1); - if (ppSeq) - { - seq[i].field_C_ppSeq_Data = *ppSeq; - } - else - { - seq[i].field_C_ppSeq_Data = nullptr; - } - } +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() +{ + 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) { - idx; + player->SND_SEQ_Stop(idx); } + EXPORT s8 CC SND_Seq_Table_Valid_4CAFE0() { - return 1; + return player->SND_Seq_Table_Valid(); } + EXPORT s16 CC SND_SEQ_PlaySeq_4CA960(u16 idx, s16 repeatCount, s16 bDontStop) { - idx; - repeatCount; - bDontStop; - return 1; + return player->SND_SEQ_PlaySeq(idx, repeatCount, bDontStop); } + EXPORT void CC SND_SEQ_SetVol_4CAD20(s32 idx, s16 volLeft, s16 volRight) { - idx; - volLeft; - volRight; + player->SND_SEQ_SetVol(idx, volLeft, volRight); } + EXPORT s16 CC SND_SEQ_Play_4CAB10(u16 idx, s16 repeatCount, s16 volLeft, s16 volRight) { - idx; - repeatCount; - volLeft; - volRight; - return 1; + return player->SND_SEQ_Play(idx, repeatCount, volLeft, volRight); } + EXPORT s32 CC SND_SsIsEos_DeInlined_4CACD0(u16 idx) { - idx; - return 1; + 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) { - sfxDef; - volLeft; - volRight; - pitch_min; - pitch_max; - return 1; + 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) { - sfxDef; - volume; - pitch_min; - pitch_max; - return 1; + return player->SFX_SfxDefinition_Play(reinterpret_cast(const_cast(sfxDef)), volume, pitch_min, pitch_max); } + #endif \ No newline at end of file diff --git a/Source/AliveLibAO/Midi.cpp b/Source/AliveLibAO/Midi.cpp index 1a518da10..ab001e45f 100644 --- a/Source/AliveLibAO/Midi.cpp +++ b/Source/AliveLibAO/Midi.cpp @@ -1151,308 +1151,153 @@ EXPORT void CC SND_StopAll_4762D0() #else #include "../../AliveLibCommon/audio/MidiPlayer.hpp" -#include "../../AliveLibCommon/audio/Soundbank.hpp" -#include "../../AliveLibCommon/audio/ADSR.hpp" -#include "../../AliveLibCommon/audio/mixer/AliveAudio.hpp" -#include "../../AliveLibCommon/audio/SequencePlayer.hpp" namespace AO { -struct VagAtr final +class AOResourceProvider : public psx::ResourceProvider { - s8 field_0_priority; - s8 field_1_mode; - s8 field_2_vol; - s8 field_3_pan; - u8 field_4_centre; - u8 field_5_shift; - s8 field_6_min; - s8 field_7_max; - 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; +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; + resource->data = *ppSeq; + resource->size = size; + resource->optionalHash = hash; + return resource; + } + + s32 sequenceCount() + { + return 164; + } + +private: + const char_type* mLoadedFileName = NULL; }; -Soundbank* theBank; -SequencePlayer* player = new SequencePlayer(); -std::vector> gSeqs; +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); +} -// https://github.com/mlgthatsme/AliveSoundLib +psx::MidiPlayer* midiPlayer = new psx::MidiPlayer(new AOResourceProvider()); EXPORT void CC SsUtAllKeyOff_49EDE0(s32 mode) { - mode; + midiPlayer->SsUtAllKeyOff(mode); } EXPORT void CC SND_Reset_476BA0() { + midiPlayer->SND_Reset(); } EXPORT void CC SND_Load_VABS_477040(SoundBlockInfo* pSoundBlockInfo, s32 reverb) { - reverb; - while (1) - { - if (!pSoundBlockInfo->field_0_vab_header_name) - { - break; - } - - // Read header - LvlFileRecord* pVabHeaderFile = sLvlArchive_4FFD60.Find_File_Record_41BED0(pSoundBlockInfo->field_0_vab_header_name); - u8* ppVabHeader = new u8[pVabHeaderFile->field_10_num_sectors << 11]; - sLvlArchive_4FFD60.Read_File_41BE40(pVabHeaderFile, ppVabHeader); - VabHeader* vabHeader = reinterpret_cast(ppVabHeader); - VagAtr* vagAttr = (VagAtr*) &vabHeader[1]; - std::vector programs; - - - // Read body - LvlFileRecord* pVabBodyFile = sLvlArchive_4FFD60.Find_File_Record_41BED0(pSoundBlockInfo->field_4_vab_body_name); - s32 bodySize = pVabBodyFile->field_10_num_sectors << 11; - u8* ppVabBody = new u8[bodySize]; - sLvlArchive_4FFD60.Read_File_41BE40(pVabBodyFile, ppVabBody); - - // BODY - int pos = 0; - std::vector samples; - 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); - sampleRate; - - 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; - samples.push_back(sample); - } - - // HEADER - for (s32 i = 0; i < vabHeader->field_12_num_progs; i++) - { - // PROGRAM - Program* program = new Program; - programs.push_back(program); - for (s32 toneCounter = 0; toneCounter < 16; toneCounter++) - { - - // TONE - - Tone* tone = new Tone(); - program->m_Tones.push_back(tone); - - if (vagAttr->field_2_vol > 0) - { - std::cout << vagAttr->field_16_vag << "\n"; - program->prog_id = vagAttr->field_14_prog; - tone->f_Volume = vagAttr->field_2_vol / 127.0f; - tone->c_Center = vagAttr->field_4_centre; - tone->c_Shift = vagAttr->field_5_shift; - tone->f_Pan = (vagAttr->field_3_pan / 64.0f) - 1.0f; - tone->Min = vagAttr->field_6_min; - tone->Max = vagAttr->field_7_max; - tone->Pitch = vagAttr->field_5_shift / 100.0f; - tone->m_Sample = samples.at(vagAttr->field_16_vag - 1); - - unsigned short ADSR1 = vagAttr->field_10_adsr1; - unsigned short ADSR2 = vagAttr->field_12_adsr2; - - REAL_ADSR realADSR = {}; - PSXConvADSR(&realADSR, ADSR1, ADSR2, false); - - tone->AttackTime = realADSR.attack_time; - tone->DecayTime = realADSR.decay_time; - tone->ReleaseTime = realADSR.release_time; - tone->SustainTime = realADSR.sustain_time; - - if (realADSR.attack_time > 1) - { // This works until the loop database is added. - tone->Loop = true; - } - } - ++vagAttr; - } - } - - Soundbank* soundbank = new Soundbank(samples, programs); - theBank = soundbank; - AliveAudio::m_CurrentSoundbank = theBank; - - delete[] ppVabBody; - pSoundBlockInfo++; - } + midiPlayer->SND_Load_VABS(reinterpret_cast(pSoundBlockInfo), reverb); } EXPORT void CC SND_Stop_Channels_Mask_4774A0(s32 mask) { - mask; + midiPlayer->SND_Stop_Channels_Mask(mask); } EXPORT void CC SND_Load_Seqs_477AB0(OpenSeqHandleAE* pSeqTable, const char_type* bsqFileName) { - if (!pSeqTable || !bsqFileName) - { - return; - } - - OpenSeqHandle* seq = reinterpret_cast<::OpenSeqHandle*>(pSeqTable); - ResourceManager::LoadResourceFileWrapper(bsqFileName, nullptr); - for (s32 i = 0; i < 164; i++) // 164 is a hardcoded value - { - OpenSeqHandle next = seq[i]; - seq[i].field_A_id_seqOpenId = -1; - seq[i].field_4_generated_res_id = ResourceManager::SEQ_HashName_454EA0(seq[i].field_0_mBsqName); - - // ppSeq is what can be played - u8** ppSeq = ResourceManager::GetLoadedResource_4554F0(ResourceManager::Resource_Seq, seq[i].field_4_generated_res_id, 1, 1); - if (ppSeq) - { - u32 size = ResourceManager::Get_Header_455620(ppSeq)->field_0_size; - - - // 32 length - seq[i].field_C_ppSeq_Data = *ppSeq; - - std::vector vec; - std::cout << "size: " << size << "\n"; - for (u32 x = 0; x < size; x++) - { - std::cout << x << "\n"; - vec.push_back((Uint8) (*ppSeq)[x]); - } - - // SEQUENCE STREAM - gSeqs.push_back(vec); - } - else - { - seq[i].field_C_ppSeq_Data = nullptr; - std::vector test; - gSeqs.push_back(test); - } - } + midiPlayer->SND_Load_Seqs(reinterpret_cast(pSeqTable), bsqFileName); } EXPORT s16 CC SND_SEQ_PlaySeq_4775A0(SeqId idx, s32 repeatCount, s16 bDontStop) { - idx; - repeatCount; - bDontStop; - return 1; + return midiPlayer->SND_SEQ_PlaySeq(s16(idx), repeatCount, bDontStop); } EXPORT void CC SND_Seq_Stop_477A60(SeqId idx) { - idx; + midiPlayer->SND_SEQ_Stop(s16(idx)); } EXPORT s16 CC SND_SEQ_Play_477760(SeqId idx, s32 repeatCount, s16 volLeft, s16 volRight) { - idx; - repeatCount; - volLeft; - volRight; - - player->StopSequence(); - player->LoadSequenceData(gSeqs.at(s16(idx))); - player->PlaySequence(); - - return 1; + return midiPlayer->SND_SEQ_Play(s16(idx), repeatCount, volLeft, volRight); } EXPORT s16 CC SND_SsIsEos_DeInlined_477930(SeqId idx) { - idx; - return 1; + 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) { - sfxDef; - volLeft; - volRight; - pitch_min; - pitch_max; - - if (sfxDef->field_0_block_idx != 0) - { - std::cout << "HEY\n"; - } - - - AliveAudio::PlayOneShot(sfxDef->field_4_program, sfxDef->field_8_note, volLeft, 0); - - //Mix_Chunk* chunk = Mix_QuickLoad_RAW(samples[sfxDef->field_4_program].data, samples[sfxDef->field_4_program].size); - //Mix_PlayChannel(-1, chunk, 0); - - return 1; + 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) { - sfxDef; - vol; - pitch_min; - pitch_max; - if (sfxDef->field_0_block_idx != 0) - { - std::cout << "HEY\n"; - } - - std::cout << theBank->m_Programs.size() << "\n"; - AliveAudio::PlayOneShot(sfxDef->field_4_program, sfxDef->field_8_note, vol, 0); - - //Mix_Chunk* chunk = Mix_QuickLoad_RAW(samples[sfxDef->field_4_program >> 7].data, samples[sfxDef->field_4_program >> 7].size); - //Mix_PlayChannel(-1, chunk, 0); - - return 1; + 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() { - AliveInitAudio(); + 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) { - idx; - volLeft; - 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/AliveLibCommon/audio/Exceptions.hpp b/Source/AliveLibCommon/audio/Exceptions.hpp index 0e5d603ec..886c4deff 100644 --- a/Source/AliveLibCommon/audio/Exceptions.hpp +++ b/Source/AliveLibCommon/audio/Exceptions.hpp @@ -1,6 +1,7 @@ #include -namespace Oddlib { +namespace psx { + class Exception : public std::exception { public: @@ -17,4 +18,5 @@ class Exception : public std::exception private: const char* mMsg; }; -} // namespace Oddlib \ No newline at end of file + +} // namespace psx \ No newline at end of file diff --git a/Source/AliveLibCommon/audio/MidiPlayer.cpp b/Source/AliveLibCommon/audio/MidiPlayer.cpp index e69de29bb..a9ea7c6e8 100644 --- a/Source/AliveLibCommon/audio/MidiPlayer.cpp +++ b/Source/AliveLibCommon/audio/MidiPlayer.cpp @@ -0,0 +1,302 @@ +#pragma once + +#include "MidiPlayer.hpp" +#include "Soundbank.hpp" +#include "ADSR.hpp" +#include "mixer/AliveAudio.hpp" +#include "SequencePlayer.hpp" + +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); + sampleRate; + + 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; + samples.push_back(sample); + } + + return samples; + } +}; + +MidiPlayer::MidiPlayer(ResourceProvider* provider) +{ + mResourceProvider = provider; + mSoundSampleParser = new DefaultSoundSampleParser(); +} + +MidiPlayer::MidiPlayer(ResourceProvider* provider, SoundSampleParser* sampleParser) +{ + mResourceProvider = provider; + mSoundSampleParser = sampleParser; +} + + +void MidiPlayer::SND_Init() +{ + AliveInitAudio(); +} + +void MidiPlayer::SND_Shutdown() +{ +} + +void MidiPlayer::SND_Load_VABS(SoundBlockInfo* pSoundBlockInfo, s32 reverb) +{ + reverb; // TODO - reverb + + 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; + + for (s32 i = 0; i < vabHeader->field_12_num_progs; i++) + { + // PROGRAM + Program* program = new Program; + programs.push_back(program); + for (s32 toneCounter = 0; toneCounter < 16; toneCounter++) + { + // TONE + + Tone* tone = new Tone(); + program->m_Tones.push_back(tone); + + if (vagAttr->field_2_vol > 0) + { + program->prog_id = vagAttr->field_14_prog; + tone->f_Volume = vagAttr->field_2_vol / 127.0f; + tone->c_Center = vagAttr->field_4_centre; + tone->c_Shift = vagAttr->field_5_shift; + tone->f_Pan = (vagAttr->field_3_pan / 64.0f) - 1.0f; + tone->Min = vagAttr->field_6_min; + tone->Max = vagAttr->field_7_max; + tone->Pitch = vagAttr->field_5_shift / 100.0f; + tone->m_Sample = samples.at(vagAttr->field_16_vag - 1); + + unsigned short ADSR1 = vagAttr->field_10_adsr1; + unsigned short ADSR2 = vagAttr->field_12_adsr2; + + REAL_ADSR realADSR = {}; + PSXConvADSR(&realADSR, ADSR1, ADSR2, false); + + tone->AttackTime = realADSR.attack_time; + tone->DecayTime = realADSR.decay_time; + tone->ReleaseTime = realADSR.release_time; + tone->SustainTime = realADSR.sustain_time; + + if (realADSR.attack_time > 1) + { // This works until the loop database is added. + tone->Loop = true; + } + } + ++vagAttr; + } + } + + AliveAudio::LockNotes(); + delete mSoundbank; + mSoundbank = new Soundbank(samples, programs); + AliveAudio::ClearAllVoices(); + AliveAudio::m_CurrentSoundbank = mSoundbank; + AliveAudio::UnlockNotes(); + + delete[] ppVabBody; + pSoundBlockInfo++; + } +} + +void MidiPlayer::SND_Load_Seqs(OpenSeqHandle* pSeqTable, const char_type* bsqFileName) +{ + if (!pSeqTable || !bsqFileName) + { + return; + } + + mSequences.clear(); + 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 + mSequences.push_back(vec); + } + else + { + seq[i].ppSeq_Data = nullptr; + std::vector test; + mSequences.push_back(test); + } + } +} + +void MidiPlayer::SND_StopAll() +{ +} + +void MidiPlayer::SND_Reset() +{ +} + +void MidiPlayer::SND_Restart() +{ +} + +void MidiPlayer::SND_Stop_Channels_Mask(u32 bitMask) +{ + bitMask; +} + +void MidiPlayer::SND_SEQ_Stop(u16 idx) +{ + if (mSequenceMap[idx]) + { + mSequenceMap[idx]->StopSequence(); + mSequenceMap[idx] = NULL; + } +} + +s8 MidiPlayer::SND_Seq_Table_Valid() +{ + return 1; +} + +s16 MidiPlayer::SND_SEQ_PlaySeq(u16 idx, s32 repeatCount, s16 bDontStop) +{ + bDontStop; // TODO + repeatCount; + + if (mSequenceMap[idx]) + { + return 1; + } + + mSequenceMap[idx] = new SequencePlayer(); + mSequenceMap[idx]->LoadSequenceData(mSequences.at(s16(idx))); + mSequenceMap[idx]->PlaySequence(); + + return 1; +} + +void MidiPlayer::SND_SEQ_SetVol(s32 idx, s16 volLeft, s16 volRight) +{ + idx; + volLeft; + volRight; +} + +s16 MidiPlayer::SND_SEQ_Play(u16 idx, s32 repeatCount, s16 volLeft, s16 volRight) +{ + repeatCount; // TODO + volLeft; + volRight; + + if (mSequenceMap[idx]) + { + return 1; + } + + mSequenceMap[idx] = new SequencePlayer(); + mSequenceMap[idx]->LoadSequenceData(mSequences.at(s16(idx))); + mSequenceMap[idx]->PlaySequence(); + return 1; +} + +s16 MidiPlayer::SND_SsIsEos_DeInlined(u16 idx) +{ + idx; // TODO + return 1; +} + +s32 MidiPlayer::SFX_SfxDefinition_Play(SfxDefinition* sfxDef, s32 volLeft, s32 volRight, s32 pitch_min, s32 pitch_max) +{ + volLeft; // TODO + volRight; + pitch_min; + pitch_max; + AliveAudio::PlayOneShot(sfxDef->program, sfxDef->note, volLeft, 0); + return 1; +} + +s32 MidiPlayer::SFX_SfxDefinition_Play(SfxDefinition* sfxDef, s32 volume, s32 pitch_min, s32 pitch_max) +{ + pitch_min; // TODO + pitch_max; + AliveAudio::PlayOneShot(sfxDef->program, sfxDef->note, volume, 0); + return 1; +} + +s32 MidiPlayer::SND(s32 program, s32 vabId, s32 note, s16 vol, s16 min, s16 max) +{ + program; // TODO + vabId; + note; + vol; + min; + max; + //AliveAudio::PlayOneShot(program, note, vol, 0); + return 1; +} + + void MidiPlayer::SsUtAllKeyOff(s32 mode) + { + mode; // TODO + } + +} // namespace psx \ No newline at end of file diff --git a/Source/AliveLibCommon/audio/MidiPlayer.hpp b/Source/AliveLibCommon/audio/MidiPlayer.hpp index b38b3c74a..1b102b045 100644 --- a/Source/AliveLibCommon/audio/MidiPlayer.hpp +++ b/Source/AliveLibCommon/audio/MidiPlayer.hpp @@ -1,46 +1,213 @@ #pragma once -EXPORT void CC SND_Init(); -EXPORT void CC SND_Shutdown(); - -EXPORT void CC SND_Load_VABS(SoundBlockInfo* pSoundBlockInfo, s32 reverb); -EXPORT void CC SND_Load_Seqs(OpenSeqHandle* pSeqTable, const char_type* bsqFileName); - -EXPORT void CC SND_StopAll(); -EXPORT void SND_Reset(); -EXPORT void CC SND_Restart(); - -EXPORT void CC SND_Stop_Channels_Mask(u32 bitMask); - -EXPORT void CC SND_SEQ_Stop(u16 idx); -EXPORT s8 CC SND_Seq_Table_Valid(); -EXPORT s16 CC SND_SEQ_PlaySeq(u16 idx, s16 repeatCount, s16 bDontStop); -EXPORT void CC SND_SEQ_SetVol(s32 idx, s16 volLeft, s16 volRight); -EXPORT s16 CC SND_SEQ_Play(u16 idx, s16 repeatCount, s16 volLeft, s16 volRight); - -EXPORT s32 CC SND_SsIsEos_DeInlined(u16 idx); -EXPORT s32 CC SFX_SfxDefinition_Play(const SfxDefinition* sfxDef, s16 volLeft, s16 volRight, s16 pitch_min, s16 pitch_max); -EXPORT s32 CC SFX_SfxDefinition_Play(const SfxDefinition* sfxDef, s16 volume, s16 pitch_min, s16 pitch_max); -EXPORT s32 CC SND(s32 program, s32 vabId, s32 note, s16 vol, s16 min, s16 max); - -struct Vag final -{ - u16 field_0_adsr_attack; - u16 field_2_adsr_sustain_level; - u16 field_4_adsr_decay; - u16 field_6_adsr_release; - u8 field_8_min; - u8 field_9_max; - s16 field_A_shift_cen; - u8 field_C; - u8 field_D_vol; - u8 field_E_priority; - u8 field_F_prog; - u8 field_10_vag; - s8 field_11_pad; -}; - -//class Vab -//{ -//public: -//} \ No newline at end of file +#include "Soundbank.hpp" +#include "SequencePlayer.hpp" + +namespace psx { + + struct VagAtr final + { + s8 field_0_priority; + s8 field_1_mode; + s8 field_2_vol; + s8 field_3_pan; + u8 field_4_centre; + u8 field_5_shift; + s8 field_6_min; + s8 field_7_max; + 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; + s8 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 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 + { + 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) + { + name; + throw std::invalid_argument("readFile is not overridden"); + } + /* AO reads sequence inline with data. AE reads from a sounds.dat file */ + virtual ResourceData* readSeq(const char_type* fileName, const char_type* sequenceName) + { + fileName; + sequenceName; + throw std::invalid_argument("readSeq is not overridden"); + } + /* AO and AE seem to have different hardcoded sequence counts */ + virtual s32 sequenceCount() + { + throw std::invalid_argument("sequenceCount is not overridden"); + } + }; + + /* + 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) + { + vabHeader; + ppVabBody; + std::vector empty; + throw std::invalid_argument("parseSamples is not overridden"); + } + }; + + /* + 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, s16 volLeft, s16 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; + + Soundbank* mSoundbank = NULL; + std::vector> mSequences; + std::unordered_map mSequenceMap; + }; + +} // namespace psx \ No newline at end of file diff --git a/Source/AliveLibCommon/audio/SequencePlayer.cpp b/Source/AliveLibCommon/audio/SequencePlayer.cpp index 8c30a7f85..a5e17f43b 100644 --- a/Source/AliveLibCommon/audio/SequencePlayer.cpp +++ b/Source/AliveLibCommon/audio/SequencePlayer.cpp @@ -1,9 +1,12 @@ #include "SequencePlayer.hpp" +namespace psx { + SequencePlayer::SequencePlayer() { // Start the Sequencer thread. m_SequenceThread = new std::thread(&SequencePlayer::m_PlayerThreadFunction, this); + m_QuarterCallback = nullptr; } SequencePlayer::~SequencePlayer() @@ -14,13 +17,13 @@ SequencePlayer::~SequencePlayer() } // Midi stuff -static void _SndMidiSkipLength(Oddlib::Stream& stream, int skip) +static void _SndMidiSkipLength(Stream& stream, int skip) { stream.Seek(stream.Pos() + skip); } // Midi stuff -static Uint32 _MidiReadVarLen(Oddlib::Stream& stream) +static Uint32 _MidiReadVarLen(Stream& stream) { Uint32 ret = 0; Uint8 byte = 0; @@ -39,7 +42,7 @@ static Uint32 _MidiReadVarLen(Oddlib::Stream& stream) float SequencePlayer::MidiTimeToSample(int time) { // This may, or may not be correct. // TODO: Revise - return ((60 * time) / m_SongTempo) * (AliveAudioSampleRate / 500.0f); + return ((60.0f * float(time)) / float(m_SongTempo)) * (float(AliveAudioSampleRate) / 500.0f); } void SequencePlayer::m_PlayerThreadFunction() @@ -84,19 +87,24 @@ void SequencePlayer::m_PlayerThreadFunction() } AliveAudio::UnlockNotes(); } - m_PlayerStateMutex.unlock(); - if (m_PlayerState == ALIVE_SEQUENCER_PLAYING && AliveAudio::currentSampleIndex > m_SongFinishSample) + if (m_PlayerState == ALIVE_SEQUENCER_PLAYING) { - m_PlayerState = ALIVE_SEQUENCER_FINISHED; + AliveAudio::LockNotes(); + if (AliveAudio::currentSampleIndex > m_SongFinishSample) + { + m_PlayerState = ALIVE_SEQUENCER_FINISHED; - // Give a quarter beat anyway - if (m_QuarterCallback != nullptr) - m_QuarterCallback(); + // Give a quarter beat anyway + if (m_QuarterCallback != nullptr) + m_QuarterCallback(); + } + AliveAudio::UnlockNotes(); } if (m_PlayerState == ALIVE_SEQUENCER_PLAYING) { + AliveAudio::LockNotes(); int quarterBeat = (m_SongFinishSample - m_SongBeginSample) / m_TimeSignatureBars; int currentQuarterBeat = (int) (floor(GetPlaybackPositionSample() / quarterBeat)); @@ -105,9 +113,14 @@ void SequencePlayer::m_PlayerThreadFunction() m_PrevBar = currentQuarterBeat; if (m_QuarterCallback != nullptr) + { m_QuarterCallback(); + } } + AliveAudio::UnlockNotes(); } + + m_PlayerStateMutex.unlock(); } } @@ -118,10 +131,12 @@ int SequencePlayer::GetPlaybackPositionSample() void SequencePlayer::StopSequence() { - AliveAudio::ClearAllTrackVoices(m_TrackID); m_PlayerStateMutex.lock(); + AliveAudio::LockNotes(); + AliveAudio::ClearAllTrackVoices(m_TrackID); m_PlayerState = ALIVE_SEQUENCER_STOPPED; m_PrevBar = 0; + AliveAudio::UnlockNotes(); m_PlayerStateMutex.unlock(); } @@ -138,12 +153,12 @@ void SequencePlayer::PlaySequence() int SequencePlayer::LoadSequenceData(std::vector seqData) { - Oddlib::Stream stream(std::move(seqData)); + Stream stream(std::move(seqData)); return LoadSequenceStream(stream); } -int SequencePlayer::LoadSequenceStream(Oddlib::Stream& stream) +int SequencePlayer::LoadSequenceStream(Stream& stream) { StopSequence(); m_MessageList.clear(); @@ -350,4 +365,6 @@ int SequencePlayer::LoadSequenceStream(Oddlib::Stream& stream) } } } -} \ No newline at end of file +} + +} // namespace psx \ No newline at end of file diff --git a/Source/AliveLibCommon/audio/SequencePlayer.hpp b/Source/AliveLibCommon/audio/SequencePlayer.hpp index bc4f652b3..d21296bc0 100644 --- a/Source/AliveLibCommon/audio/SequencePlayer.hpp +++ b/Source/AliveLibCommon/audio/SequencePlayer.hpp @@ -7,6 +7,8 @@ #include "Stream.hpp" #include "mixer/AliveAudio.hpp" +namespace psx { + struct SeqHeader { Uint32 mMagic; // SEQp @@ -72,7 +74,7 @@ class SequencePlayer ~SequencePlayer(); int LoadSequenceData(std::vector seqData); - int LoadSequenceStream(Oddlib::Stream& stream); + int LoadSequenceStream(Stream& stream); void PlaySequence(); void StopSequence(); @@ -96,3 +98,5 @@ class SequencePlayer std::mutex m_MessageListMutex; std::mutex m_PlayerStateMutex; }; + +} // namespace psx \ No newline at end of file diff --git a/Source/AliveLibCommon/audio/Soundbank.cpp b/Source/AliveLibCommon/audio/Soundbank.cpp index c16b0ad06..ccf7f0d8a 100644 --- a/Source/AliveLibCommon/audio/Soundbank.cpp +++ b/Source/AliveLibCommon/audio/Soundbank.cpp @@ -2,6 +2,8 @@ #include "Soundbank.hpp" +namespace psx { + float Sample::GetSample(float sampleOffset) { AliveAudioHelper helper; @@ -21,4 +23,6 @@ Soundbank::Soundbank(std::vector samples, std::vector program Soundbank::~Soundbank() { -} \ No newline at end of file +} + +} // namespace psx \ No newline at end of file diff --git a/Source/AliveLibCommon/audio/Soundbank.hpp b/Source/AliveLibCommon/audio/Soundbank.hpp index 8643379cb..05f2559d1 100644 --- a/Source/AliveLibCommon/audio/Soundbank.hpp +++ b/Source/AliveLibCommon/audio/Soundbank.hpp @@ -1,6 +1,8 @@ #pragma once - class AliveAudioHelper +namespace psx { + +class AliveAudioHelper { public: float Lerp(u16 from, u16 to, float t) @@ -87,4 +89,6 @@ class Soundbank std::vector m_Samples; std::vector m_Programs; -}; \ No newline at end of file +}; + +} // namespace psx diff --git a/Source/AliveLibCommon/audio/Stream.cpp b/Source/AliveLibCommon/audio/Stream.cpp index 84a904cb2..c2382ab38 100644 --- a/Source/AliveLibCommon/audio/Stream.cpp +++ b/Source/AliveLibCommon/audio/Stream.cpp @@ -6,7 +6,8 @@ #include "Stream.hpp" #include "Exceptions.hpp" -namespace Oddlib { +namespace psx { + Stream::Stream(std::vector&& data) { mSize = data.size(); @@ -88,4 +89,5 @@ size_t Stream::Size() const { return mSize; } -} // namespace Oddlib \ No newline at end of file + +} // namespace psx \ No newline at end of file diff --git a/Source/AliveLibCommon/audio/Stream.hpp b/Source/AliveLibCommon/audio/Stream.hpp index d2ba0aa2a..cf3103af1 100644 --- a/Source/AliveLibCommon/audio/Stream.hpp +++ b/Source/AliveLibCommon/audio/Stream.hpp @@ -5,7 +5,8 @@ #include #include "SDL_types.h" -namespace Oddlib { +namespace psx { + class Stream { public: @@ -25,4 +26,5 @@ class Stream mutable std::unique_ptr mStream; size_t mSize = 0; }; -} // namespace Oddlib \ No newline at end of file + +} // namespace psx \ No newline at end of file diff --git a/Source/AliveLibCommon/audio/mixer/AliveAudio.cpp b/Source/AliveLibCommon/audio/mixer/AliveAudio.cpp index 8374af585..3ecb35d95 100644 --- a/Source/AliveLibCommon/audio/mixer/AliveAudio.cpp +++ b/Source/AliveLibCommon/audio/mixer/AliveAudio.cpp @@ -3,6 +3,8 @@ #include #include "AliveAudio.hpp" +namespace psx { + Soundbank* AliveAudio::m_CurrentSoundbank = nullptr; std::mutex AliveAudio::voiceListMutex; std::vector AliveAudio::m_Voices; @@ -33,7 +35,6 @@ void AliveInitAudio() void AliveAudio::PlayOneShot(int programId, int note, s32 volume, float pitch) { - voiceListMutex.lock(); for (auto program : m_CurrentSoundbank->m_Programs) { if (program->prog_id != programId) @@ -47,15 +48,15 @@ void AliveAudio::PlayOneShot(int programId, int note, s32 volume, float pitch) { Voice* voice = new Voice(); voice->i_Note = note; - voice->f_Velocity = float(volume) / 127; + voice->f_Velocity = float(volume == 0 ? 127 : volume) / 127; voice->m_Tone = tone; voice->f_Pitch = pitch; m_Voices.push_back(voice); } } } - voiceListMutex.unlock(); } + void AliveAudio::PlayOneShot(std::string soundID) { soundID; @@ -63,11 +64,6 @@ void AliveAudio::PlayOneShot(std::string soundID) void AliveAudio::NoteOn(int programId, int note, char velocity, float pitch , int trackID , float trackDelay ) { - if (!voiceListLocked) - { - voiceListMutex.lock(); - } - for (auto program : m_CurrentSoundbank->m_Programs) { if (program->prog_id != programId) @@ -82,7 +78,7 @@ void AliveAudio::NoteOn(int programId, int note, char velocity, float pitch , in voice->i_Note = note; voice->m_Tone = tone; voice->i_Program = programId; - voice->f_Velocity = velocity / 127.0f; + voice->f_Velocity = velocity == 0 ? 127 : velocity / 127.0f; voice->i_TrackID = trackID; voice->f_Pitch = pitch; voice->f_TrackDelay = trackDelay; @@ -90,8 +86,6 @@ void AliveAudio::NoteOn(int programId, int note, char velocity, float pitch , in } } } - if (!voiceListLocked) - voiceListMutex.unlock(); } void AliveAudio::NoteOn(int program, int note, char velocity, int trackID , float trackDelay) { @@ -100,17 +94,24 @@ void AliveAudio::NoteOn(int program, int note, char velocity, int trackID , floa void AliveAudio::NoteOff(int program, int note, int trackID ) { - program; - note; - trackID; - std::cout << "off"; + for (auto voice : m_Voices) + { + if (voice->i_Note == note && voice->i_Program == program && voice->i_TrackID == trackID) + { + voice->b_NoteOn = false; + } + } } void AliveAudio::NoteOffDelay(int program, int note, int trackID , float trackDelay) { - program; - note; - trackID; - trackDelay; + for (auto voice : m_Voices) + { + if (voice->i_Note == note && voice->i_Program == program && voice->i_TrackID == trackID && voice->f_TrackDelay < trackDelay && voice->f_NoteOffDelay <= 0) + { + voice->m_UsesNoteOffDelay = true; + voice->f_NoteOffDelay = trackDelay; + } + } } void AliveAudio::DebugPlayFirstToneSample(int program, int tone) { @@ -119,24 +120,74 @@ void AliveAudio::DebugPlayFirstToneSample(int program, int tone) } void AliveAudio::LockNotes() { + voiceListMutex.lock(); } void AliveAudio::UnlockNotes() { + voiceListMutex.unlock(); } void AliveAudio::ClearAllVoices(bool forceKill) { - forceKill; + std::vector deadVoices; + + for (auto voice : AliveAudio::m_Voices) + { + if (forceKill) + { + deadVoices.push_back(voice); + } + else + { + voice->b_NoteOn = false; // Send a note off to all of the notes though. + if (voice->f_SampleOffset == 0) // Let the voices that are CURRENTLY playing play. + { + deadVoices.push_back(voice); + } + } + } + + for (auto obj : deadVoices) + { + delete obj; + + AliveAudio::m_Voices.erase(std::remove(AliveAudio::m_Voices.begin(), AliveAudio::m_Voices.end(), obj), AliveAudio::m_Voices.end()); + } } + void AliveAudio::ClearAllTrackVoices(int trackID, bool forceKill) { - trackID; - forceKill; + std::vector deadVoices; + + for (auto voice : AliveAudio::m_Voices) + { + if (forceKill) + { + if (voice->i_TrackID == trackID) // Kill the voices no matter what. Cuts of any sounds = Ugly sound + { + deadVoices.push_back(voice); + } + } + else + { + voice->b_NoteOn = false; // Send a note off to all of the notes though. + if (voice->i_TrackID == trackID && voice->f_SampleOffset == 0) // Let the voices that are CURRENTLY playing play. + { + deadVoices.push_back(voice); + } + } + } + + for (auto obj : deadVoices) + { + delete obj; + + AliveAudio::m_Voices.erase(std::remove(AliveAudio::m_Voices.begin(), AliveAudio::m_Voices.end(), obj), AliveAudio::m_Voices.end()); + } } void CleanVoices() { - AliveAudio::voiceListMutex.lock(); std::vector deadVoices; for (auto voice : AliveAudio::m_Voices) @@ -153,7 +204,6 @@ void CleanVoices() AliveAudio::m_Voices.erase(std::remove(AliveAudio::m_Voices.begin(), AliveAudio::m_Voices.end(), obj), AliveAudio::m_Voices.end()); } - AliveAudio::voiceListMutex.unlock(); } void AliveRenderAudio(float* AudioStream, int StreamLength) @@ -161,7 +211,6 @@ void AliveRenderAudio(float* AudioStream, int StreamLength) static float tick = 0; static int note = 0; - AliveAudio::voiceListMutex.lock(); int voiceCount = AliveAudio::m_Voices.size(); Voice** rawPointer = AliveAudio::m_Voices.data(); // Real nice speed boost here. @@ -174,17 +223,19 @@ void AliveRenderAudio(float* AudioStream, int StreamLength) voice->f_TrackDelay--; if (voice->m_UsesNoteOffDelay) + { voice->f_NoteOffDelay--; + } if (voice->m_UsesNoteOffDelay && voice->f_NoteOffDelay <= 0 && voice->b_NoteOn == true) { voice->b_NoteOn = false; - //printf("off"); } - if (voice->b_Dead || voice->f_TrackDelay > 0) + if (voice->b_Dead || voice->f_TrackDelay > 0) { continue; - + } + float centerPan = voice->m_Tone->f_Pan; float leftPan = 1.0f; float rightPan = 1.0f; @@ -209,7 +260,6 @@ void AliveRenderAudio(float* AudioStream, int StreamLength) AliveAudio::currentSampleIndex++; } - AliveAudio::voiceListMutex.unlock(); CleanVoices(); } @@ -218,10 +268,13 @@ void AliveAudioSDLCallback(void* udata, Uint8* stream, int len) { udata; memset(stream, 0, len); + + AliveAudio::LockNotes(); AliveRenderAudio((float*) stream, len / sizeof(float)); + AliveAudio::UnlockNotes(); //if (AliveAudio::EQEnabled) // AliveEQEffect((float*) stream, len / sizeof(float)); } - +} // namespace psx \ No newline at end of file diff --git a/Source/AliveLibCommon/audio/mixer/AliveAudio.hpp b/Source/AliveLibCommon/audio/mixer/AliveAudio.hpp index 744119ecc..16428f721 100644 --- a/Source/AliveLibCommon/audio/mixer/AliveAudio.hpp +++ b/Source/AliveLibCommon/audio/mixer/AliveAudio.hpp @@ -10,6 +10,7 @@ #include "../Soundbank.hpp" #include "Voice.hpp" +namespace psx { void AliveInitAudio(); void AliveAudioSDLCallback(void* udata, Uint8* stream, int len); @@ -44,3 +45,5 @@ class AliveAudio static long long currentSampleIndex; }; + +} // namespace psx \ No newline at end of file diff --git a/Source/AliveLibCommon/audio/mixer/Voice.cpp b/Source/AliveLibCommon/audio/mixer/Voice.cpp index e0a0b3c59..286afd069 100644 --- a/Source/AliveLibCommon/audio/mixer/Voice.cpp +++ b/Source/AliveLibCommon/audio/mixer/Voice.cpp @@ -2,6 +2,8 @@ #include "Voice.hpp" #include "AliveAudio.hpp" +namespace psx { + float Voice::GetSample() { if (b_Dead) // Dont return anything if dead. This voice should now be removed. @@ -49,4 +51,6 @@ float Voice::GetSample() f_SampleOffset += (sampleFrameRate); return ((float) (m_Tone->m_Sample->GetSample(float(f_SampleOffset)) * ActiveAttackLevel * ActiveDecayLevel * ((b_NoteOn) ? ActiveSustainLevel : ActiveReleaseLevel) * f_Velocity)); -} \ No newline at end of file +} + +} // namespace psx \ No newline at end of file diff --git a/Source/AliveLibCommon/audio/mixer/Voice.hpp b/Source/AliveLibCommon/audio/mixer/Voice.hpp index fa71b1e10..7492a0009 100644 --- a/Source/AliveLibCommon/audio/mixer/Voice.hpp +++ b/Source/AliveLibCommon/audio/mixer/Voice.hpp @@ -1,6 +1,8 @@ #pragma once #include "../Soundbank.hpp" +namespace psx { + class Voice { public: @@ -26,4 +28,6 @@ class Voice double ActiveSustainLevel = 1; float GetSample(); -}; \ No newline at end of file +}; + +} // namespace psx \ No newline at end of file From 77e7b2b563f8faa051efa9057f08ba1e8a036db5 Mon Sep 17 00:00:00 2001 From: Sean Horton <3359434+sean-horton@users.noreply.github.com> Date: Wed, 5 Oct 2022 19:39:26 -0400 Subject: [PATCH 08/82] cleanup some build testing I had with SDL_Mixer --- Source/AliveLibAE/Sound/Midi.cpp | 1 - Source/AliveLibCommon/CMakeLists.txt | 15 +++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/Source/AliveLibAE/Sound/Midi.cpp b/Source/AliveLibAE/Sound/Midi.cpp index 7ecbf24d4..7410f3502 100644 --- a/Source/AliveLibAE/Sound/Midi.cpp +++ b/Source/AliveLibAE/Sound/Midi.cpp @@ -18,7 +18,6 @@ #include "PsxSpuApi.hpp" #include "AmbientSound.hpp" -#include "SDL_Mixer.h" #if !ALTERNATE_AUDIO EXPORT void CC SFX_SetPitch_4CA510(const SfxDefinition* pSfx, s32 channelsBits, s16 pitch); diff --git a/Source/AliveLibCommon/CMakeLists.txt b/Source/AliveLibCommon/CMakeLists.txt index ab6b278dc..704463da9 100644 --- a/Source/AliveLibCommon/CMakeLists.txt +++ b/Source/AliveLibCommon/CMakeLists.txt @@ -35,6 +35,21 @@ SET(AliveLibSrcCommon PSXMDECDecoder.h Psx_common.hpp W32CrashHandler.hpp + audio/mixer/AliveAudio.hpp + audio/mixer/AliveAudio.cpp + audio/mixer/Voice.hpp + audio/mixer/Voice.cpp + audio/ADSR.hpp + audio/ADSR.cpp + audio/Exceptions.hpp + audio/Stream.hpp + audio/Stream.cpp + audio/Soundbank.hpp + audio/Soundbank.cpp + audio/SequencePlayer.hpp + audio/SequencePlayer.cpp + audio/MidiPlayer.hpp + audio/MidiPlayer.cpp ) add_library(AliveLibCommon ${AliveLibSrcCommon}) From 13e7b87ba4e5a337f102742ed2357ca103f68453 Mon Sep 17 00:00:00 2001 From: Sean Horton <3359434+sean-horton@users.noreply.github.com> Date: Wed, 5 Oct 2022 19:47:03 -0400 Subject: [PATCH 09/82] thread performance --- Source/AliveLibCommon/audio/MidiPlayer.cpp | 1 + Source/AliveLibCommon/audio/SequencePlayer.cpp | 1 + 2 files changed, 2 insertions(+) diff --git a/Source/AliveLibCommon/audio/MidiPlayer.cpp b/Source/AliveLibCommon/audio/MidiPlayer.cpp index a9ea7c6e8..068d7ef0e 100644 --- a/Source/AliveLibCommon/audio/MidiPlayer.cpp +++ b/Source/AliveLibCommon/audio/MidiPlayer.cpp @@ -208,6 +208,7 @@ void MidiPlayer::SND_SEQ_Stop(u16 idx) if (mSequenceMap[idx]) { mSequenceMap[idx]->StopSequence(); + delete mSequenceMap[idx]; mSequenceMap[idx] = NULL; } } diff --git a/Source/AliveLibCommon/audio/SequencePlayer.cpp b/Source/AliveLibCommon/audio/SequencePlayer.cpp index a5e17f43b..f52a600e7 100644 --- a/Source/AliveLibCommon/audio/SequencePlayer.cpp +++ b/Source/AliveLibCommon/audio/SequencePlayer.cpp @@ -121,6 +121,7 @@ void SequencePlayer::m_PlayerThreadFunction() } m_PlayerStateMutex.unlock(); + std::this_thread::sleep_for(std::chrono::milliseconds(30)); } } From 1784d0c43623a3ae6eae27a2867a8bf2a5a7ab3a Mon Sep 17 00:00:00 2001 From: Sean Horton <3359434+sean-horton@users.noreply.github.com> Date: Wed, 5 Oct 2022 21:20:49 -0400 Subject: [PATCH 10/82] enable the other sound stuff --- Source/AliveLibAE/Sound/Midi.cpp | 2 +- Source/AliveLibCommon/audio/MidiPlayer.cpp | 2 +- Source/AliveLibCommon/audio/Soundbank.cpp | 6 +- .../AliveLibCommon/audio/{ => mixer}/ADSR.cpp | 0 .../AliveLibCommon/audio/{ => mixer}/ADSR.hpp | 0 .../AliveLibCommon/audio/mixer/AliveAudio.cpp | 28 +++- .../AliveLibCommon/audio/mixer/AliveAudio.hpp | 6 + Source/AliveLibCommon/audio/mixer/biquad.cpp | 144 ++++++++++++++++++ Source/AliveLibCommon/audio/mixer/biquad.hpp | 66 ++++++++ 9 files changed, 247 insertions(+), 7 deletions(-) rename Source/AliveLibCommon/audio/{ => mixer}/ADSR.cpp (100%) rename Source/AliveLibCommon/audio/{ => mixer}/ADSR.hpp (100%) create mode 100644 Source/AliveLibCommon/audio/mixer/biquad.cpp create mode 100644 Source/AliveLibCommon/audio/mixer/biquad.hpp diff --git a/Source/AliveLibAE/Sound/Midi.cpp b/Source/AliveLibAE/Sound/Midi.cpp index 7410f3502..04bd509c7 100644 --- a/Source/AliveLibAE/Sound/Midi.cpp +++ b/Source/AliveLibAE/Sound/Midi.cpp @@ -944,7 +944,7 @@ class AESoundSampleParser : public psx::SoundSampleParser soundDatFile.open("sounds.dat", std::ios::binary); if (!soundDatFile.is_open()) { - abort(); + throw std::invalid_argument("Could not find sounds.dat"); } soundDatFile.seekg(0, std::ios::end); std::streamsize fileSize = soundDatFile.tellg(); diff --git a/Source/AliveLibCommon/audio/MidiPlayer.cpp b/Source/AliveLibCommon/audio/MidiPlayer.cpp index 068d7ef0e..fde169be0 100644 --- a/Source/AliveLibCommon/audio/MidiPlayer.cpp +++ b/Source/AliveLibCommon/audio/MidiPlayer.cpp @@ -2,7 +2,7 @@ #include "MidiPlayer.hpp" #include "Soundbank.hpp" -#include "ADSR.hpp" +#include "mixer/ADSR.hpp" #include "mixer/AliveAudio.hpp" #include "SequencePlayer.hpp" diff --git a/Source/AliveLibCommon/audio/Soundbank.cpp b/Source/AliveLibCommon/audio/Soundbank.cpp index ccf7f0d8a..34b04d8a6 100644 --- a/Source/AliveLibCommon/audio/Soundbank.cpp +++ b/Source/AliveLibCommon/audio/Soundbank.cpp @@ -9,10 +9,10 @@ float Sample::GetSample(float sampleOffset) AliveAudioHelper helper; //if (!AliveAudio::Interpolation) - return helper.SampleSint16ToFloat(m_SampleBuffer[(int) sampleOffset]); // No interpolation. Faster but sounds jaggy. + //return helper.SampleSint16ToFloat(m_SampleBuffer[(int) sampleOffset]); // No interpolation. Faster but sounds jaggy. - //int roundedOffset = (int) floor(sampleOffset); - //return helper.SampleSint16ToFloat(s16(helper.Lerp(m_SampleBuffer[roundedOffset], m_SampleBuffer[roundedOffset + 1], sampleOffset - float(roundedOffset)))); + int roundedOffset = (int) floor(sampleOffset); + return helper.SampleSint16ToFloat(s16(helper.Lerp(m_SampleBuffer[roundedOffset], m_SampleBuffer[roundedOffset + 1], float(int(sampleOffset) - roundedOffset)))); } Soundbank::Soundbank(std::vector samples, std::vector programs) diff --git a/Source/AliveLibCommon/audio/ADSR.cpp b/Source/AliveLibCommon/audio/mixer/ADSR.cpp similarity index 100% rename from Source/AliveLibCommon/audio/ADSR.cpp rename to Source/AliveLibCommon/audio/mixer/ADSR.cpp diff --git a/Source/AliveLibCommon/audio/ADSR.hpp b/Source/AliveLibCommon/audio/mixer/ADSR.hpp similarity index 100% rename from Source/AliveLibCommon/audio/ADSR.hpp rename to Source/AliveLibCommon/audio/mixer/ADSR.hpp diff --git a/Source/AliveLibCommon/audio/mixer/AliveAudio.cpp b/Source/AliveLibCommon/audio/mixer/AliveAudio.cpp index 3ecb35d95..041f5cb03 100644 --- a/Source/AliveLibCommon/audio/mixer/AliveAudio.cpp +++ b/Source/AliveLibCommon/audio/mixer/AliveAudio.cpp @@ -10,6 +10,7 @@ std::mutex AliveAudio::voiceListMutex; std::vector AliveAudio::m_Voices; long long AliveAudio::currentSampleIndex = 20; bool AliveAudio::voiceListLocked = false; +biquad* AliveAudio::AliveAudioEQBiQuad = nullptr; void AliveInitAudio() { @@ -264,6 +265,28 @@ void AliveRenderAudio(float* AudioStream, int StreamLength) CleanVoices(); } + +void AliveAudioSetEQ(float cutoff) +{ + if (AliveAudio::AliveAudioEQBiQuad != nullptr) + delete AliveAudio::AliveAudioEQBiQuad; + + AliveAudio::AliveAudioEQBiQuad = BiQuad_new(PEQ, 8, cutoff, AliveAudioSampleRate, 1); +} + +void AliveEQEffect(float* stream, int len) +{ + if (AliveAudio::AliveAudioEQBiQuad == nullptr) + { + AliveAudioSetEQ(20500); + } + + for (int i = 0; i < len; i++) + { + stream[i] = BiQuad(stream[i], AliveAudio::AliveAudioEQBiQuad); + } +} + void AliveAudioSDLCallback(void* udata, Uint8* stream, int len) { udata; @@ -271,10 +294,11 @@ void AliveAudioSDLCallback(void* udata, Uint8* stream, int len) AliveAudio::LockNotes(); AliveRenderAudio((float*) stream, len / sizeof(float)); - AliveAudio::UnlockNotes(); //if (AliveAudio::EQEnabled) - // AliveEQEffect((float*) stream, len / sizeof(float)); + AliveEQEffect((float*) stream, len / sizeof(float)); + + AliveAudio::UnlockNotes(); } } // namespace psx \ No newline at end of file diff --git a/Source/AliveLibCommon/audio/mixer/AliveAudio.hpp b/Source/AliveLibCommon/audio/mixer/AliveAudio.hpp index 16428f721..9572783a0 100644 --- a/Source/AliveLibCommon/audio/mixer/AliveAudio.hpp +++ b/Source/AliveLibCommon/audio/mixer/AliveAudio.hpp @@ -9,6 +9,7 @@ #include #include "../Soundbank.hpp" #include "Voice.hpp" +#include "biquad.hpp" namespace psx { @@ -44,6 +45,11 @@ class AliveAudio static void ClearAllTrackVoices(int trackID, bool forceKill = false); static long long currentSampleIndex; + static biquad* AliveAudioEQBiQuad; + +private: + static void AliveAudioSetEQ(float cutoff); + static void AliveEQEffect(float* stream, int len); }; } // namespace psx \ No newline at end of file diff --git a/Source/AliveLibCommon/audio/mixer/biquad.cpp b/Source/AliveLibCommon/audio/mixer/biquad.cpp new file mode 100644 index 000000000..d4d9ffec4 --- /dev/null +++ b/Source/AliveLibCommon/audio/mixer/biquad.cpp @@ -0,0 +1,144 @@ +/* Simple implementation of Biquad filters -- Tom St Denis + * + * Based on the work + +Cookbook formulae for audio EQ biquad filter coefficients +--------------------------------------------------------- +by Robert Bristow-Johnson, pbjrbj@viconet.com a.k.a. robert@audioheads.com + + * Available on the web at + +http://www.smartelectronix.com/musicdsp/text/filters005.txt + + * Enjoy. + * + * This work is hereby placed in the public domain for all purposes, whether + * commercial, free [as in speech] or educational, etc. Use the code and please + * give me credit if you wish. + * + * Tom St Denis -- http://tomstdenis.home.dhs.org +*/ + +#include "biquad.hpp" + +namespace psx { + +/* Below this would be biquad.c */ +/* Computes a BiQuad filter on a sample */ +smp_type BiQuad(smp_type sample, biquad* b) +{ + smp_type result; + + /* compute result */ + result = b->a0 * sample + b->a1 * b->x1 + b->a2 * b->x2 - b->a3 * b->y1 - b->a4 * b->y2; + + /* shift x1 to x2, sample to x1 */ + b->x2 = b->x1; + b->x1 = sample; + + /* shift y1 to y2, result to y1 */ + b->y2 = b->y1; + b->y1 = result; + + return result; +} + +/* sets up a BiQuad Filter */ +biquad* BiQuad_new(int type, smp_type dbGain, smp_type freq, + smp_type srate, smp_type bandwidth) +{ + biquad* b; + smp_type A, omega, sn, cs, alpha, beta; + smp_type a0, a1, a2, b0, b1, b2; + + b = (biquad*) malloc(sizeof(biquad)); + if (b == NULL) + return NULL; + + /* setup variables */ + A = smp_type(pow(10, dbGain / 40)); + omega = smp_type(2 * M_PI * freq / srate); + sn = sin(omega); + cs = cos(omega); + alpha = smp_type(sn * sinh(M_LN2 / 2 * bandwidth * omega / sn)); + beta = sqrt(A + A); + + switch (type) + { + case LPF: + b0 = (1 - cs) / 2; + b1 = 1 - cs; + b2 = (1 - cs) / 2; + a0 = 1 + alpha; + a1 = -2 * cs; + a2 = 1 - alpha; + break; + case HPF: + b0 = (1 + cs) / 2; + b1 = -(1 + cs); + b2 = (1 + cs) / 2; + a0 = 1 + alpha; + a1 = -2 * cs; + a2 = 1 - alpha; + break; + case BPF: + b0 = alpha; + b1 = 0; + b2 = -alpha; + a0 = 1 + alpha; + a1 = -2 * cs; + a2 = 1 - alpha; + break; + case NOTCH: + b0 = 1; + b1 = -2 * cs; + b2 = 1; + a0 = 1 + alpha; + a1 = -2 * cs; + a2 = 1 - alpha; + break; + case PEQ: + b0 = 1 + (alpha * A); + b1 = -2 * cs; + b2 = 1 - (alpha * A); + a0 = 1 + (alpha / A); + a1 = -2 * cs; + a2 = 1 - (alpha / A); + break; + case LSH: + b0 = A * ((A + 1) - (A - 1) * cs + beta * sn); + b1 = 2 * A * ((A - 1) - (A + 1) * cs); + b2 = A * ((A + 1) - (A - 1) * cs - beta * sn); + a0 = (A + 1) + (A - 1) * cs + beta * sn; + a1 = -2 * ((A - 1) + (A + 1) * cs); + a2 = (A + 1) + (A - 1) * cs - beta * sn; + break; + case HSH: + b0 = A * ((A + 1) + (A - 1) * cs + beta * sn); + b1 = -2 * A * ((A - 1) + (A + 1) * cs); + b2 = A * ((A + 1) + (A - 1) * cs - beta * sn); + a0 = (A + 1) - (A - 1) * cs + beta * sn; + a1 = 2 * ((A - 1) - (A + 1) * cs); + a2 = (A + 1) - (A - 1) * cs - beta * sn; + break; + default: + free(b); + return NULL; + } + + /* precompute the coefficients */ + b->a0 = b0 / a0; + b->a1 = b1 / a0; + b->a2 = b2 / a0; + b->a3 = a1 / a0; + b->a4 = a2 / a0; + + /* zero initial samples */ + b->x1 = b->x2 = 0; + b->y1 = b->y2 = 0; + + return b; +} +/* crc==3062280887, version==4, Sat Jul 7 00:03:23 2001 */ + +} // namespace psx \ No newline at end of file diff --git a/Source/AliveLibCommon/audio/mixer/biquad.hpp b/Source/AliveLibCommon/audio/mixer/biquad.hpp new file mode 100644 index 000000000..5e61c3cd4 --- /dev/null +++ b/Source/AliveLibCommon/audio/mixer/biquad.hpp @@ -0,0 +1,66 @@ +/* Simple implementation of Biquad filters -- Tom St Denis + * + * Based on the work + +Cookbook formulae for audio EQ biquad filter coefficients +--------------------------------------------------------- +by Robert Bristow-Johnson, pbjrbj@viconet.com a.k.a. robert@audioheads.com + + * Available on the web at + +http://www.smartelectronix.com/musicdsp/text/filters005.txt + + * Enjoy. + * + * This work is hereby placed in the public domain for all purposes, whether + * commercial, free [as in speech] or educational, etc. Use the code and please + * give me credit if you wish. + * + * Tom St Denis -- http://tomstdenis.home.dhs.org +*/ + +#pragma once + +namespace psx { + +/* this would be biquad.h */ +#include +#include + +#ifndef M_LN2 + #define M_LN2 0.69314718055994530942 +#endif + +#ifndef M_PI + #define M_PI 3.14159265358979323846 +#endif + +/* whatever sample type you want */ +typedef float smp_type; + +/* this holds the data required to update samples thru a filter */ +typedef struct +{ + smp_type a0, a1, a2, a3, a4; + smp_type x1, x2, y1, y2; +} biquad; + +/* filter types */ +enum +{ + LPF, /* low pass filter */ + HPF, /* High pass filter */ + BPF, /* band pass filter */ + NOTCH, /* Notch Filter */ + PEQ, /* Peaking band EQ filter */ + LSH, /* Low shelf filter */ + HSH /* High shelf filter */ +}; + +smp_type BiQuad(smp_type sample, biquad* b); +biquad* BiQuad_new(int type, smp_type dbGain, /* gain of filter */ + smp_type freq, /* center frequency */ + smp_type srate, /* sampling rate */ + smp_type bandwidth); /* bandwidth in octaves */ + +} // namespace psx \ No newline at end of file From d2b8947954b0a4cfedd154f1420f0d08eb0dbd19 Mon Sep 17 00:00:00 2001 From: Sean Horton <3359434+sean-horton@users.noreply.github.com> Date: Thu, 6 Oct 2022 21:17:39 -0400 Subject: [PATCH 11/82] I think songs are playing right --- Source/AliveLibAE/Sound/Midi.cpp | 5 + Source/AliveLibCommon/audio/MidiPlayer.cpp | 112 +++++++++++++++--- Source/AliveLibCommon/audio/MidiPlayer.hpp | 5 +- .../AliveLibCommon/audio/SequencePlayer.cpp | 29 +++-- .../AliveLibCommon/audio/SequencePlayer.hpp | 8 +- .../AliveLibCommon/audio/mixer/AliveAudio.cpp | 11 +- 6 files changed, 134 insertions(+), 36 deletions(-) diff --git a/Source/AliveLibAE/Sound/Midi.cpp b/Source/AliveLibAE/Sound/Midi.cpp index 04bd509c7..a5e76adfb 100644 --- a/Source/AliveLibAE/Sound/Midi.cpp +++ b/Source/AliveLibAE/Sound/Midi.cpp @@ -992,6 +992,8 @@ psx::MidiPlayer* player = new psx::MidiPlayer(new AEResourceProvider(), new AESo EXPORT void CC SND_StopAll_4CB060() { + MusicController::EnableMusic_47FE10(FALSE); + BackgroundMusic::Stop_4CB000(); player->SND_StopAll(); } @@ -1030,6 +1032,9 @@ EXPORT s32 CC SND_4CA5D0(s32 program, s32 vabId, s32 note, s16 vol, s16 min, s16 EXPORT void CC SND_Restart_4CB0E0() { + MusicController::EnableMusic_47FE10(TRUE); + BackgroundMusic::Play_4CB030(); + Start_Sounds_For_Objects_In_Near_Cameras_4CBB60(); player->SND_Restart(); } diff --git a/Source/AliveLibCommon/audio/MidiPlayer.cpp b/Source/AliveLibCommon/audio/MidiPlayer.cpp index fde169be0..be4f80b56 100644 --- a/Source/AliveLibCommon/audio/MidiPlayer.cpp +++ b/Source/AliveLibCommon/audio/MidiPlayer.cpp @@ -188,6 +188,16 @@ void MidiPlayer::SND_Load_Seqs(OpenSeqHandle* pSeqTable, const char_type* bsqFil void MidiPlayer::SND_StopAll() { + for (SequencePlayer* player : mSequencePlayers) + { + player->StopSequence(); + delete player; + } + mSequencePlayers.clear(); + + //AliveAudio::LockNotes(); + //AliveAudio::ClearAllVoices(); + //AliveAudio::UnlockNotes(); } void MidiPlayer::SND_Reset() @@ -205,11 +215,12 @@ void MidiPlayer::SND_Stop_Channels_Mask(u32 bitMask) void MidiPlayer::SND_SEQ_Stop(u16 idx) { - if (mSequenceMap[idx]) + SequencePlayer* player = GetSequencePlayer(idx); + if (player) { - mSequenceMap[idx]->StopSequence(); - delete mSequenceMap[idx]; - mSequenceMap[idx] = NULL; + player->StopSequence(); + RemoveSequencePlayer(player); + delete player; } } @@ -222,16 +233,26 @@ s16 MidiPlayer::SND_SEQ_PlaySeq(u16 idx, s32 repeatCount, s16 bDontStop) { bDontStop; // TODO repeatCount; + idx; + + SequencePlayer* player = GetSequencePlayer(idx); - if (mSequenceMap[idx]) + // When chanting starts bDontStop is 1 + // and then 0 is called every frame until chanting stops. + // I think we can return if it's 0 + if (player && bDontStop == 0) { - return 1; + return 1; // still playing } - mSequenceMap[idx] = new SequencePlayer(); - mSequenceMap[idx]->LoadSequenceData(mSequences.at(s16(idx))); - mSequenceMap[idx]->PlaySequence(); + if (!player) + { + player = new SequencePlayer(); + mSequencePlayers.push_back(player); + } + player->LoadSequenceData(mSequences.at(s16(idx)), s32(idx), repeatCount); + player->PlaySequence(); return 1; } @@ -248,38 +269,60 @@ s16 MidiPlayer::SND_SEQ_Play(u16 idx, s32 repeatCount, s16 volLeft, s16 volRight volLeft; volRight; - if (mSequenceMap[idx]) + SequencePlayer* player = GetSequencePlayer(idx); + if (player) { - return 1; + return 0; } - mSequenceMap[idx] = new SequencePlayer(); - mSequenceMap[idx]->LoadSequenceData(mSequences.at(s16(idx))); - mSequenceMap[idx]->PlaySequence(); + player = new SequencePlayer(); + player->LoadSequenceData(mSequences.at(s16(idx)), s32(idx), repeatCount); + player->PlaySequence(); + mSequencePlayers.push_back(player); return 1; } s16 MidiPlayer::SND_SsIsEos_DeInlined(u16 idx) { idx; // TODO - return 1; + + SequencePlayer* player = GetSequencePlayer(idx); + if (!player) + { + return 0; + } + if (player->mRepeatCount) + { + // 1 means we're still playing + return player->completedRepeats() < player->mRepeatCount ? 1 : 0; + } + return 9; } s32 MidiPlayer::SFX_SfxDefinition_Play(SfxDefinition* sfxDef, s32 volLeft, s32 volRight, s32 pitch_min, s32 pitch_max) { + sfxDef; volLeft; // TODO volRight; pitch_min; pitch_max; - AliveAudio::PlayOneShot(sfxDef->program, sfxDef->note, volLeft, 0); + // AliveAudio::PlayOneShot(sfxDef->program, sfxDef->note, volLeft, 0); + AliveAudio::LockNotes(); + AliveAudio::NoteOn(sfxDef->program, sfxDef->note, char(((volLeft + volRight) / 2 )), 0); + AliveAudio::UnlockNotes(); return 1; } s32 MidiPlayer::SFX_SfxDefinition_Play(SfxDefinition* sfxDef, s32 volume, s32 pitch_min, s32 pitch_max) { + sfxDef; + volume; pitch_min; // TODO pitch_max; - AliveAudio::PlayOneShot(sfxDef->program, sfxDef->note, volume, 0); + //AliveAudio::PlayOneShot(sfxDef->program, sfxDef->note, volume, 0); + AliveAudio::LockNotes(); + AliveAudio::NoteOn(sfxDef->program, sfxDef->note, char(volume), 0); + AliveAudio::UnlockNotes(); return 1; } @@ -291,7 +334,9 @@ s32 MidiPlayer::SND(s32 program, s32 vabId, s32 note, s16 vol, s16 min, s16 max) vol; min; max; - //AliveAudio::PlayOneShot(program, note, vol, 0); + AliveAudio::LockNotes(); + AliveAudio::NoteOn(program, note, char(vol), 0); + AliveAudio::UnlockNotes(); return 1; } @@ -300,4 +345,35 @@ s32 MidiPlayer::SND(s32 program, s32 vabId, s32 note, s16 vol, s16 min, s16 max) mode; // TODO } + SequencePlayer* MidiPlayer::GetSequencePlayer(u16 idx) + { + for (SequencePlayer* player : mSequencePlayers) + { + if (player->m_TrackID == idx) + { + return player; + } + } + return NULL; + } + + void MidiPlayer::RemoveSequencePlayer(SequencePlayer* player) + { + int offset = 0; + int found = 0; + for (SequencePlayer* iter : mSequencePlayers) + { + if (iter->m_TrackID == player->m_TrackID) + { + found = 1; + break; + } + offset++; + } + if (found) + { + mSequencePlayers.erase(mSequencePlayers.begin() + offset); + } + } + } // namespace psx \ No newline at end of file diff --git a/Source/AliveLibCommon/audio/MidiPlayer.hpp b/Source/AliveLibCommon/audio/MidiPlayer.hpp index 1b102b045..04e1e37b8 100644 --- a/Source/AliveLibCommon/audio/MidiPlayer.hpp +++ b/Source/AliveLibCommon/audio/MidiPlayer.hpp @@ -207,7 +207,10 @@ namespace psx { Soundbank* mSoundbank = NULL; std::vector> mSequences; - std::unordered_map mSequenceMap; + std::vector mSequencePlayers; + + SequencePlayer* GetSequencePlayer(u16 idx); + void RemoveSequencePlayer(SequencePlayer* player); }; } // namespace psx \ No newline at end of file diff --git a/Source/AliveLibCommon/audio/SequencePlayer.cpp b/Source/AliveLibCommon/audio/SequencePlayer.cpp index f52a600e7..8ef5ed484 100644 --- a/Source/AliveLibCommon/audio/SequencePlayer.cpp +++ b/Source/AliveLibCommon/audio/SequencePlayer.cpp @@ -45,6 +45,11 @@ float SequencePlayer::MidiTimeToSample(int time) return ((60.0f * float(time)) / float(m_SongTempo)) * (float(AliveAudioSampleRate) / 500.0f); } +s32 SequencePlayer::completedRepeats() +{ + return mCompletedRepeats.load(); +} + void SequencePlayer::m_PlayerThreadFunction() { int channels[16]; @@ -56,10 +61,11 @@ void SequencePlayer::m_PlayerThreadFunction() while (!m_KillThread) { m_PlayerStateMutex.lock(); + AliveAudio::LockNotes(); + if (m_PlayerState == ALIVE_SEQUENCER_INIT_VOICES) { bool firstNote = true; - AliveAudio::LockNotes(); for (int i = 0; i < (int)m_MessageList.size(); i++) { AliveAudioMidiMessage m = m_MessageList[i]; @@ -85,26 +91,23 @@ void SequencePlayer::m_PlayerThreadFunction() break; } } - AliveAudio::UnlockNotes(); } if (m_PlayerState == ALIVE_SEQUENCER_PLAYING) { - AliveAudio::LockNotes(); if (AliveAudio::currentSampleIndex > m_SongFinishSample) { m_PlayerState = ALIVE_SEQUENCER_FINISHED; + mCompletedRepeats.store(mCompletedRepeats.load() + 1); // Give a quarter beat anyway if (m_QuarterCallback != nullptr) m_QuarterCallback(); } - AliveAudio::UnlockNotes(); } if (m_PlayerState == ALIVE_SEQUENCER_PLAYING) { - AliveAudio::LockNotes(); int quarterBeat = (m_SongFinishSample - m_SongBeginSample) / m_TimeSignatureBars; int currentQuarterBeat = (int) (floor(GetPlaybackPositionSample() / quarterBeat)); @@ -117,10 +120,11 @@ void SequencePlayer::m_PlayerThreadFunction() m_QuarterCallback(); } } - AliveAudio::UnlockNotes(); } + AliveAudio::UnlockNotes(); m_PlayerStateMutex.unlock(); + std::this_thread::sleep_for(std::chrono::milliseconds(30)); } } @@ -134,8 +138,9 @@ void SequencePlayer::StopSequence() { m_PlayerStateMutex.lock(); AliveAudio::LockNotes(); - AliveAudio::ClearAllTrackVoices(m_TrackID); + AliveAudio::ClearAllTrackVoices(m_TrackID, false); m_PlayerState = ALIVE_SEQUENCER_STOPPED; + mCompletedRepeats.store(1); m_PrevBar = 0; AliveAudio::UnlockNotes(); m_PlayerStateMutex.unlock(); @@ -147,13 +152,16 @@ void SequencePlayer::PlaySequence() if (m_PlayerState == ALIVE_SEQUENCER_STOPPED || m_PlayerState == ALIVE_SEQUENCER_FINISHED) { m_PrevBar = 0; + mCompletedRepeats.store(0); m_PlayerState = ALIVE_SEQUENCER_INIT_VOICES; } m_PlayerStateMutex.unlock(); } -int SequencePlayer::LoadSequenceData(std::vector seqData) +int SequencePlayer::LoadSequenceData(std::vector seqData, s32 trackId, s32 repeatCount) { + m_TrackID = trackId; + mRepeatCount = repeatCount; Stream stream(std::move(seqData)); return LoadSequenceStream(stream); @@ -167,6 +175,11 @@ int SequencePlayer::LoadSequenceStream(Stream& stream) SeqHeader seqHeader; // Read the header + if (stream.Size() == 0) + { + std::cout << "no stream!?\n"; + return 1; + } stream.ReadUInt32(seqHeader.mMagic); stream.ReadUInt32(seqHeader.mVersion); diff --git a/Source/AliveLibCommon/audio/SequencePlayer.hpp b/Source/AliveLibCommon/audio/SequencePlayer.hpp index d21296bc0..0d2c4200b 100644 --- a/Source/AliveLibCommon/audio/SequencePlayer.hpp +++ b/Source/AliveLibCommon/audio/SequencePlayer.hpp @@ -73,10 +73,13 @@ class SequencePlayer SequencePlayer(); ~SequencePlayer(); - int LoadSequenceData(std::vector seqData); + s32 mRepeatCount; + + int LoadSequenceData(std::vector seqData, s32 trackId, s32 repeatCount); int LoadSequenceStream(Stream& stream); void PlaySequence(); void StopSequence(); + int completedRepeats(); float MidiTimeToSample(int time); int GetPlaybackPositionSample(); @@ -85,7 +88,8 @@ class SequencePlayer AliveAudioSequencerState m_PlayerState = ALIVE_SEQUENCER_STOPPED; AliveAudioQuarterCallback m_QuarterCallback; - //private: +private: + std::atomic mCompletedRepeats; int m_KillThread = false; // If true, loop thread will exit. int m_SongFinishSample = 0; // Not relative. int m_SongBeginSample = 0; // Not relative. diff --git a/Source/AliveLibCommon/audio/mixer/AliveAudio.cpp b/Source/AliveLibCommon/audio/mixer/AliveAudio.cpp index 041f5cb03..fb6234d31 100644 --- a/Source/AliveLibCommon/audio/mixer/AliveAudio.cpp +++ b/Source/AliveLibCommon/audio/mixer/AliveAudio.cpp @@ -9,7 +9,6 @@ Soundbank* AliveAudio::m_CurrentSoundbank = nullptr; std::mutex AliveAudio::voiceListMutex; std::vector AliveAudio::m_Voices; long long AliveAudio::currentSampleIndex = 20; -bool AliveAudio::voiceListLocked = false; biquad* AliveAudio::AliveAudioEQBiQuad = nullptr; void AliveInitAudio() @@ -73,7 +72,7 @@ void AliveAudio::NoteOn(int programId, int note, char velocity, float pitch , in } for (auto tone : program->m_Tones) { - if (note >= tone->Min && note <= tone->Max) + if (note >= tone->Min && note <= tone->Max) // this is funny `if (tone->f_Volume > 0)` { Voice* voice = new Voice(); voice->i_Note = note; @@ -169,10 +168,10 @@ void AliveAudio::ClearAllTrackVoices(int trackID, bool forceKill) deadVoices.push_back(voice); } } - else + else if (voice->i_TrackID == trackID) { - voice->b_NoteOn = false; // Send a note off to all of the notes though. - if (voice->i_TrackID == trackID && voice->f_SampleOffset == 0) // Let the voices that are CURRENTLY playing play. + voice->b_NoteOn = false; // Send a note off to all of the notes though. + if (voice->f_SampleOffset == 0) // Let the voices that are CURRENTLY playing play. { deadVoices.push_back(voice); } @@ -182,7 +181,6 @@ void AliveAudio::ClearAllTrackVoices(int trackID, bool forceKill) for (auto obj : deadVoices) { delete obj; - AliveAudio::m_Voices.erase(std::remove(AliveAudio::m_Voices.begin(), AliveAudio::m_Voices.end(), obj), AliveAudio::m_Voices.end()); } } @@ -249,7 +247,6 @@ void AliveRenderAudio(float* AudioStream, int StreamLength) { rightPan = 1.0f - abs(centerPan); } - float s = voice->GetSample(); float leftSample = s * leftPan; From fa928eadd8ad71c9821494459e1c016ea9265317 Mon Sep 17 00:00:00 2001 From: Sean Horton <3359434+sean-horton@users.noreply.github.com> Date: Thu, 6 Oct 2022 22:57:56 -0400 Subject: [PATCH 12/82] pitches working --- Source/AliveLibCommon/audio/MidiPlayer.cpp | 65 +++++++++++++++---- .../AliveLibCommon/audio/mixer/AliveAudio.cpp | 16 +++-- .../AliveLibCommon/audio/mixer/AliveAudio.hpp | 4 +- 3 files changed, 68 insertions(+), 17 deletions(-) diff --git a/Source/AliveLibCommon/audio/MidiPlayer.cpp b/Source/AliveLibCommon/audio/MidiPlayer.cpp index be4f80b56..a0b26bbca 100644 --- a/Source/AliveLibCommon/audio/MidiPlayer.cpp +++ b/Source/AliveLibCommon/audio/MidiPlayer.cpp @@ -301,27 +301,70 @@ s16 MidiPlayer::SND_SsIsEos_DeInlined(u16 idx) s32 MidiPlayer::SFX_SfxDefinition_Play(SfxDefinition* sfxDef, s32 volLeft, s32 volRight, s32 pitch_min, s32 pitch_max) { - sfxDef; - volLeft; // TODO - volRight; - pitch_min; - pitch_max; + if (pitch_min == 0x7FFF) + { + pitch_min = sfxDef->pitch_min; + } + + if (pitch_max == 0x7FFF) + { + pitch_max = sfxDef->pitch_max; + } + + if (volLeft < 10) + { + volLeft = 10; + } + else if (volLeft >= 127) + { + volLeft = 127; + } + + if (volRight < 10) + { + volRight = 10; + } + else if (volRight >= 127) + { + volRight = 127; + } + // AliveAudio::PlayOneShot(sfxDef->program, sfxDef->note, volLeft, 0); AliveAudio::LockNotes(); - AliveAudio::NoteOn(sfxDef->program, sfxDef->note, char(((volLeft + volRight) / 2 )), 0); + AliveAudio::PlayOneShot(sfxDef->program, sfxDef->note, volLeft, volRight, 0, pitch_min, pitch_max); AliveAudio::UnlockNotes(); return 1; } s32 MidiPlayer::SFX_SfxDefinition_Play(SfxDefinition* sfxDef, s32 volume, s32 pitch_min, s32 pitch_max) { - sfxDef; - volume; - pitch_min; // TODO - pitch_max; + if (!volume) + { + volume = sfxDef->volume; + } + + if (pitch_min == 0x7FFF) + { + pitch_min = sfxDef->pitch_min; + } + + if (pitch_max == 0x7FFF) + { + pitch_max = sfxDef->pitch_max; + } + + if (volume < 1) + { + volume = 1; + } + else if (volume >= 127) + { + volume = 127; + } + //AliveAudio::PlayOneShot(sfxDef->program, sfxDef->note, volume, 0); AliveAudio::LockNotes(); - AliveAudio::NoteOn(sfxDef->program, sfxDef->note, char(volume), 0); + AliveAudio::PlayOneShot(sfxDef->program, sfxDef->note, volume, volume, 0, pitch_min, pitch_max); AliveAudio::UnlockNotes(); return 1; } diff --git a/Source/AliveLibCommon/audio/mixer/AliveAudio.cpp b/Source/AliveLibCommon/audio/mixer/AliveAudio.cpp index fb6234d31..a26785945 100644 --- a/Source/AliveLibCommon/audio/mixer/AliveAudio.cpp +++ b/Source/AliveLibCommon/audio/mixer/AliveAudio.cpp @@ -33,7 +33,7 @@ void AliveInitAudio() SDL_PauseAudio(0); } -void AliveAudio::PlayOneShot(int programId, int note, s32 volume, float pitch) +void AliveAudio::PlayOneShot(int programId, int note, s32 volLeft, s32 volRight, float pitch, s32 pitch_min, s32 pitch_max) { for (auto program : m_CurrentSoundbank->m_Programs) { @@ -42,6 +42,10 @@ void AliveAudio::PlayOneShot(int programId, int note, s32 volume, float pitch) continue; } + pitch; + pitch_min; + pitch_max; + s32 volume = volLeft + volRight / 2; for (auto tone : program->m_Tones) { if (note >= tone->Min && note <= tone->Max) @@ -50,16 +54,20 @@ void AliveAudio::PlayOneShot(int programId, int note, s32 volume, float pitch) voice->i_Note = note; voice->f_Velocity = float(volume == 0 ? 127 : volume) / 127; voice->m_Tone = tone; - voice->f_Pitch = pitch; + + // TODO - something more is probably suppose to happen with pitch. + // From the looks of things pitch_min and pitch_max are always equal. + // and if pitch is 0, try using pitch_min instead. + voice->f_Pitch = pitch == 0 ? pitch_min / 127 : pitch / 127; m_Voices.push_back(voice); } } } } -void AliveAudio::PlayOneShot(std::string soundID) +void AliveAudio::PlayOneShot(int programId, int note, s32 volume, float pitch, s32 pitch_min, s32 pitch_max) { - soundID; + AliveAudio::PlayOneShot(programId, note, volume, volume, pitch, pitch_min, pitch_max); } void AliveAudio::NoteOn(int programId, int note, char velocity, float pitch , int trackID , float trackDelay ) diff --git a/Source/AliveLibCommon/audio/mixer/AliveAudio.hpp b/Source/AliveLibCommon/audio/mixer/AliveAudio.hpp index 9572783a0..f3a6c83e4 100644 --- a/Source/AliveLibCommon/audio/mixer/AliveAudio.hpp +++ b/Source/AliveLibCommon/audio/mixer/AliveAudio.hpp @@ -26,8 +26,8 @@ class AliveAudio static std::vector m_Voices; static bool AliveAudio::voiceListLocked; - static void PlayOneShot(s32 program, s32 note, s32 volume, float pitch = 0); - static void PlayOneShot(std::string soundID); + static void PlayOneShot(s32 program, s32 note, s32 volume, float pitch, s32 pitch_min, s32 pitch_max); + static void PlayOneShot(s32 program, s32 note, s32 volLeft, s32 volRight, float pitch, s32 pitch_min, s32 pitch_max); static void NoteOn(int program, int note, char velocity, float pitch = 0, int trackID = 0, float trackDelay = 0); static void NoteOn(int program, int note, char velocity, int trackID = 0, float trackDelay = 0); From aadf3a530414f731d82af6d28ef95218db69f2c3 Mon Sep 17 00:00:00 2001 From: Sean Horton <3359434+sean-horton@users.noreply.github.com> Date: Thu, 6 Oct 2022 23:03:59 -0400 Subject: [PATCH 13/82] sfx panning --- Source/AliveLibCommon/audio/mixer/AliveAudio.cpp | 3 ++- Source/AliveLibCommon/audio/mixer/Voice.hpp | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Source/AliveLibCommon/audio/mixer/AliveAudio.cpp b/Source/AliveLibCommon/audio/mixer/AliveAudio.cpp index a26785945..20f6aebd7 100644 --- a/Source/AliveLibCommon/audio/mixer/AliveAudio.cpp +++ b/Source/AliveLibCommon/audio/mixer/AliveAudio.cpp @@ -54,6 +54,7 @@ void AliveAudio::PlayOneShot(int programId, int note, s32 volLeft, s32 volRight, voice->i_Note = note; voice->f_Velocity = float(volume == 0 ? 127 : volume) / 127; voice->m_Tone = tone; + voice->f_Pan = float(volRight) / float(volLeft) - 1; // TODO - something more is probably suppose to happen with pitch. // From the looks of things pitch_min and pitch_max are always equal. @@ -243,7 +244,7 @@ void AliveRenderAudio(float* AudioStream, int StreamLength) continue; } - float centerPan = voice->m_Tone->f_Pan; + float centerPan = voice->f_Pan == 0 ? voice->m_Tone->f_Pan : voice->f_Pan; float leftPan = 1.0f; float rightPan = 1.0f; diff --git a/Source/AliveLibCommon/audio/mixer/Voice.hpp b/Source/AliveLibCommon/audio/mixer/Voice.hpp index 7492a0009..12ee90a30 100644 --- a/Source/AliveLibCommon/audio/mixer/Voice.hpp +++ b/Source/AliveLibCommon/audio/mixer/Voice.hpp @@ -14,6 +14,7 @@ class Voice bool b_NoteOn = true; double f_Velocity = 1.0f; double f_Pitch = 0.0f; + float f_Pan = 0.0f; int i_TrackID = 0; // This is used to distinguish between sounds fx and music double f_TrackDelay = 0; // Used by the sequencer for perfect timing From 29e1b3c70774c6371b630c6c0ed7590981e12cb4 Mon Sep 17 00:00:00 2001 From: Sean Horton <3359434+sean-horton@users.noreply.github.com> Date: Fri, 7 Oct 2022 20:09:29 -0400 Subject: [PATCH 14/82] more audio updates --- Source/AliveLibCommon/audio/MidiPlayer.cpp | 97 +++++++++---------- Source/AliveLibCommon/audio/MidiPlayer.hpp | 4 +- .../AliveLibCommon/audio/SequencePlayer.cpp | 41 +++++++- .../AliveLibCommon/audio/SequencePlayer.hpp | 3 + .../AliveLibCommon/audio/mixer/AliveAudio.cpp | 23 ++++- .../AliveLibCommon/audio/mixer/AliveAudio.hpp | 1 + Source/AliveLibCommon/audio/mixer/Voice.cpp | 2 +- Source/AliveLibCommon/audio/mixer/Voice.hpp | 1 + 8 files changed, 115 insertions(+), 57 deletions(-) diff --git a/Source/AliveLibCommon/audio/MidiPlayer.cpp b/Source/AliveLibCommon/audio/MidiPlayer.cpp index a0b26bbca..6c7d5cc5c 100644 --- a/Source/AliveLibCommon/audio/MidiPlayer.cpp +++ b/Source/AliveLibCommon/audio/MidiPlayer.cpp @@ -211,6 +211,7 @@ void MidiPlayer::SND_Restart() void MidiPlayer::SND_Stop_Channels_Mask(u32 bitMask) { bitMask; + std::cout << "bitmask " << bitMask << "\n"; } void MidiPlayer::SND_SEQ_Stop(u16 idx) @@ -250,17 +251,50 @@ s16 MidiPlayer::SND_SEQ_PlaySeq(u16 idx, s32 repeatCount, s16 bDontStop) player = new SequencePlayer(); mSequencePlayers.push_back(player); } + std::cout << "Play seq " << idx << "\n"; player->LoadSequenceData(mSequences.at(s16(idx)), s32(idx), repeatCount); player->PlaySequence(); - return 1; + return s16(mSequencePlayers.size() - 1); +} + +void MidiPlayer::sanitizeVolume(s32* src, s32 low, s32 high) +{ + if (*src < low) + { + *src = low; + } + else if (*src >= high) + { + *src = high; + } } -void MidiPlayer::SND_SEQ_SetVol(s32 idx, s16 volLeft, s16 volRight) +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) { idx; volLeft; volRight; + sanitizeVolume(&volLeft, 10, 127); + sanitizeVolume(&volRight, 10, 127); + SequencePlayer* player = GetSequencePlayer(u16(idx)); + if (player) + { + player->SetVolume(volLeft, volRight); + } } s16 MidiPlayer::SND_SEQ_Play(u16 idx, s32 repeatCount, s16 volLeft, s16 volRight) @@ -274,12 +308,13 @@ s16 MidiPlayer::SND_SEQ_Play(u16 idx, s32 repeatCount, s16 volLeft, s16 volRight { return 0; } + std::cout << "Play seq " << idx << "\n"; player = new SequencePlayer(); player->LoadSequenceData(mSequences.at(s16(idx)), s32(idx), repeatCount); player->PlaySequence(); mSequencePlayers.push_back(player); - return 1; + return s16(mSequencePlayers.size() - 1); } s16 MidiPlayer::SND_SsIsEos_DeInlined(u16 idx) @@ -301,39 +336,17 @@ s16 MidiPlayer::SND_SsIsEos_DeInlined(u16 idx) s32 MidiPlayer::SFX_SfxDefinition_Play(SfxDefinition* sfxDef, s32 volLeft, s32 volRight, s32 pitch_min, s32 pitch_max) { - if (pitch_min == 0x7FFF) - { - pitch_min = sfxDef->pitch_min; - } - - if (pitch_max == 0x7FFF) - { - pitch_max = sfxDef->pitch_max; - } - - if (volLeft < 10) - { - volLeft = 10; - } - else if (volLeft >= 127) - { - volLeft = 127; - } + sanitizePitch(&pitch_min, sfxDef->pitch_min); + sanitizePitch(&pitch_max, sfxDef->pitch_max); - if (volRight < 10) - { - volRight = 10; - } - else if (volRight >= 127) - { - volRight = 127; - } + sanitizeVolume(&volLeft, 10, 127); + sanitizeVolume(&volRight, 10, 127); // AliveAudio::PlayOneShot(sfxDef->program, sfxDef->note, volLeft, 0); AliveAudio::LockNotes(); AliveAudio::PlayOneShot(sfxDef->program, sfxDef->note, volLeft, volRight, 0, pitch_min, pitch_max); AliveAudio::UnlockNotes(); - return 1; + return 11; } s32 MidiPlayer::SFX_SfxDefinition_Play(SfxDefinition* sfxDef, s32 volume, s32 pitch_min, s32 pitch_max) @@ -343,30 +356,16 @@ s32 MidiPlayer::SFX_SfxDefinition_Play(SfxDefinition* sfxDef, s32 volume, s32 pi volume = sfxDef->volume; } - if (pitch_min == 0x7FFF) - { - pitch_min = sfxDef->pitch_min; - } + sanitizePitch(&pitch_min, sfxDef->pitch_min); + sanitizePitch(&pitch_max, sfxDef->pitch_max); - if (pitch_max == 0x7FFF) - { - pitch_max = sfxDef->pitch_max; - } - - if (volume < 1) - { - volume = 1; - } - else if (volume >= 127) - { - volume = 127; - } + sanitizeVolume(&volume, 1, 127); //AliveAudio::PlayOneShot(sfxDef->program, sfxDef->note, volume, 0); AliveAudio::LockNotes(); AliveAudio::PlayOneShot(sfxDef->program, sfxDef->note, volume, volume, 0, pitch_min, pitch_max); AliveAudio::UnlockNotes(); - return 1; + return 10; } s32 MidiPlayer::SND(s32 program, s32 vabId, s32 note, s16 vol, s16 min, s16 max) @@ -380,7 +379,7 @@ s32 MidiPlayer::SND(s32 program, s32 vabId, s32 note, s16 vol, s16 min, s16 max) AliveAudio::LockNotes(); AliveAudio::NoteOn(program, note, char(vol), 0); AliveAudio::UnlockNotes(); - return 1; + return 12; } void MidiPlayer::SsUtAllKeyOff(s32 mode) diff --git a/Source/AliveLibCommon/audio/MidiPlayer.hpp b/Source/AliveLibCommon/audio/MidiPlayer.hpp index 04e1e37b8..ad25ef9ca 100644 --- a/Source/AliveLibCommon/audio/MidiPlayer.hpp +++ b/Source/AliveLibCommon/audio/MidiPlayer.hpp @@ -192,7 +192,7 @@ namespace psx { 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, s16 volLeft, s16 volRight); + 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); @@ -209,6 +209,8 @@ namespace psx { std::vector> mSequences; std::vector mSequencePlayers; + void sanitizePitch(s32* src, s16 defaultPitch); + void sanitizeVolume(s32* src, s32 low, s32 high); SequencePlayer* GetSequencePlayer(u16 idx); void RemoveSequencePlayer(SequencePlayer* player); }; diff --git a/Source/AliveLibCommon/audio/SequencePlayer.cpp b/Source/AliveLibCommon/audio/SequencePlayer.cpp index 8ef5ed484..362c8e7f0 100644 --- a/Source/AliveLibCommon/audio/SequencePlayer.cpp +++ b/Source/AliveLibCommon/audio/SequencePlayer.cpp @@ -42,7 +42,9 @@ static Uint32 _MidiReadVarLen(Stream& stream) float SequencePlayer::MidiTimeToSample(int time) { // This may, or may not be correct. // TODO: Revise - return ((60.0f * float(time)) / float(m_SongTempo)) * (float(AliveAudioSampleRate) / 500.0f); + // Oct 7, 2022 - added (x1.041f). For some reason this seems to match AE playback speed... + // AO might be better with just times 1.000? + return ((60.0f * float(time)) / float(m_SongTempo)) * (float(AliveAudioSampleRate) / 500.0f) * 1.041f; } s32 SequencePlayer::completedRepeats() @@ -95,6 +97,11 @@ void SequencePlayer::m_PlayerThreadFunction() if (m_PlayerState == ALIVE_SEQUENCER_PLAYING) { + if (mVolLeft && mVolRight) + { + AliveAudio::SetVolume(m_TrackID, mVolLeft, mVolRight); + } + if (AliveAudio::currentSampleIndex > m_SongFinishSample) { m_PlayerState = ALIVE_SEQUENCER_FINISHED; @@ -134,6 +141,17 @@ int SequencePlayer::GetPlaybackPositionSample() return ((int) (AliveAudio::currentSampleIndex - m_SongBeginSample)); } +void SequencePlayer::SetVolume(s32 volLeft, s32 volRight) +{ + m_PlayerStateMutex.lock(); + mVolLeft = volLeft; + //AliveAudio::LockNotes(); + //AliveAudio::SetVolume(m_TrackID, mVolLeft, mVolRight); + //AliveAudio::UnlockNotes(); + mVolRight = volRight; + m_PlayerStateMutex.unlock(); +} + void SequencePlayer::StopSequence() { m_PlayerStateMutex.lock(); @@ -199,6 +217,7 @@ int SequencePlayer::LoadSequenceStream(Stream& stream) m_SongTempo = ((float) (60000000.0 / tempoValue)); + unsigned int prevDeltaTime = 0; unsigned int deltaTime = 0; const size_t midiDataStart = stream.Pos(); @@ -248,7 +267,24 @@ int SequencePlayer::LoadSequenceStream(Stream& stream) case 0x2f: { //std::cout << "end of track" << std::endl; - m_MessageList.push_back(AliveAudioMidiMessage(ALIVE_MIDI_ENDTRACK, deltaTime, 0, 0, 0)); + + // This may not be right, but I've found it lines up new sections well. + // If not, pass deltaTime instead of nextQuarter + unsigned int quarterDur = int(m_SongTempo); + unsigned int nextQuarter = 0; + + while (nextQuarter < prevDeltaTime) + { + nextQuarter += quarterDur; + } + nextQuarter -= quarterDur; + + if (nextQuarter == 0) + { + nextQuarter = deltaTime; + } + + m_MessageList.push_back(AliveAudioMidiMessage(ALIVE_MIDI_ENDTRACK, nextQuarter, 0, 0, 0)); return 0; //Sint32 loopCount = gSeqInfo.iNumTimesToLoop; // v1 some hard coded data?? or just a local static? //if (loopCount) // If zero then loop forever @@ -336,6 +372,7 @@ int SequencePlayer::LoadSequenceStream(Stream& stream) { Uint8 prog = 0; stream.ReadUInt8(prog); + prevDeltaTime = deltaTime; m_MessageList.push_back(AliveAudioMidiMessage(ALIVE_MIDI_PROGRAM_CHANGE, deltaTime, channel, 0, 0, prog)); } break; diff --git a/Source/AliveLibCommon/audio/SequencePlayer.hpp b/Source/AliveLibCommon/audio/SequencePlayer.hpp index 0d2c4200b..8222e020d 100644 --- a/Source/AliveLibCommon/audio/SequencePlayer.hpp +++ b/Source/AliveLibCommon/audio/SequencePlayer.hpp @@ -81,6 +81,7 @@ class SequencePlayer void StopSequence(); int completedRepeats(); + void SetVolume(s32 volLeft, s32 volRight); float MidiTimeToSample(int time); int GetPlaybackPositionSample(); @@ -89,6 +90,8 @@ class SequencePlayer AliveAudioQuarterCallback m_QuarterCallback; private: + s32 mVolLeft = 0; + s32 mVolRight = 0; std::atomic mCompletedRepeats; int m_KillThread = false; // If true, loop thread will exit. int m_SongFinishSample = 0; // Not relative. diff --git a/Source/AliveLibCommon/audio/mixer/AliveAudio.cpp b/Source/AliveLibCommon/audio/mixer/AliveAudio.cpp index 20f6aebd7..c9c2ec568 100644 --- a/Source/AliveLibCommon/audio/mixer/AliveAudio.cpp +++ b/Source/AliveLibCommon/audio/mixer/AliveAudio.cpp @@ -45,16 +45,15 @@ void AliveAudio::PlayOneShot(int programId, int note, s32 volLeft, s32 volRight, pitch; pitch_min; pitch_max; - s32 volume = volLeft + volRight / 2; for (auto tone : program->m_Tones) { if (note >= tone->Min && note <= tone->Max) { Voice* voice = new Voice(); voice->i_Note = note; - voice->f_Velocity = float(volume == 0 ? 127 : volume) / 127; + voice->f_Velocity = float(std::min(volLeft, volRight)) / 127; voice->m_Tone = tone; - voice->f_Pan = float(volRight) / float(volLeft) - 1; + voice->f_Pan = (float(volRight) / float(volLeft)) - 1; // TODO - something more is probably suppose to happen with pitch. // From the looks of things pitch_min and pitch_max are always equal. @@ -87,7 +86,7 @@ void AliveAudio::NoteOn(int programId, int note, char velocity, float pitch , in voice->i_Note = note; voice->m_Tone = tone; voice->i_Program = programId; - voice->f_Velocity = velocity == 0 ? 127 : velocity / 127.0f; + voice->f_Velocity = velocity == 0 ? tone->f_Volume : velocity / 127.0f; voice->i_TrackID = trackID; voice->f_Pitch = pitch; voice->f_TrackDelay = trackDelay; @@ -101,6 +100,22 @@ void AliveAudio::NoteOn(int program, int note, char velocity, int trackID , floa NoteOn(program, note, velocity, 0, trackID, trackDelay); } +// sets the volume and pan for all voices on a given track. +// since we are probably updating the volume of a sequence +// we will set a volume multiplier that will be used on the +// sequences voices volume. +void AliveAudio::SetVolume(int trackID, s32 volLeft, s32 volRight) +{ + for (auto voice : m_Voices) + { + if (voice->i_TrackID == trackID) + { + voice->f_VelocityMulti = double(std::min(volLeft, volRight)) / double(127); + voice->f_Pan = (float(volRight) / float(volLeft)) - 1; + } + } +} + void AliveAudio::NoteOff(int program, int note, int trackID ) { for (auto voice : m_Voices) diff --git a/Source/AliveLibCommon/audio/mixer/AliveAudio.hpp b/Source/AliveLibCommon/audio/mixer/AliveAudio.hpp index f3a6c83e4..ab2ce8bd6 100644 --- a/Source/AliveLibCommon/audio/mixer/AliveAudio.hpp +++ b/Source/AliveLibCommon/audio/mixer/AliveAudio.hpp @@ -34,6 +34,7 @@ class AliveAudio static void NoteOff(int program, int note, int trackID = 0); static void NoteOffDelay(int program, int note, int trackID = 0, float trackDelay = 0); + static void SetVolume(int trackID, s32 volLeft, s32 volRight); static void DebugPlayFirstToneSample(int program, int tone); diff --git a/Source/AliveLibCommon/audio/mixer/Voice.cpp b/Source/AliveLibCommon/audio/mixer/Voice.cpp index 286afd069..a384e4b8c 100644 --- a/Source/AliveLibCommon/audio/mixer/Voice.cpp +++ b/Source/AliveLibCommon/audio/mixer/Voice.cpp @@ -50,7 +50,7 @@ float Voice::GetSample() double sampleFrameRate = pow(1.059463, i_Note - m_Tone->c_Center + m_Tone->Pitch + f_Pitch) * (44100.0 / AliveAudioSampleRate); f_SampleOffset += (sampleFrameRate); - return ((float) (m_Tone->m_Sample->GetSample(float(f_SampleOffset)) * ActiveAttackLevel * ActiveDecayLevel * ((b_NoteOn) ? ActiveSustainLevel : ActiveReleaseLevel) * f_Velocity)); + return ((float) (m_Tone->m_Sample->GetSample(float(f_SampleOffset)) * ActiveAttackLevel * ActiveDecayLevel * ((b_NoteOn) ? ActiveSustainLevel : ActiveReleaseLevel) * (f_Velocity * f_VelocityMulti))); } } // namespace psx \ No newline at end of file diff --git a/Source/AliveLibCommon/audio/mixer/Voice.hpp b/Source/AliveLibCommon/audio/mixer/Voice.hpp index 12ee90a30..7b617047c 100644 --- a/Source/AliveLibCommon/audio/mixer/Voice.hpp +++ b/Source/AliveLibCommon/audio/mixer/Voice.hpp @@ -13,6 +13,7 @@ class Voice double f_SampleOffset = 0; bool b_NoteOn = true; double f_Velocity = 1.0f; + double f_VelocityMulti = 1; double f_Pitch = 0.0f; float f_Pan = 0.0f; From c3ed07f19094d858acaa66e86bd19fde37b9592e Mon Sep 17 00:00:00 2001 From: Sean Horton <3359434+sean-horton@users.noreply.github.com> Date: Sat, 18 Feb 2023 12:34:07 -0500 Subject: [PATCH 15/82] long time since I visited, commit what I have --- Source/AliveLibCommon/CMakeLists.txt | 6 +- Source/AliveLibCommon/audio/MidiPlayer.cpp | 58 ++++++++++++++++--- Source/AliveLibCommon/audio/MidiPlayer.hpp | 5 ++ .../AliveLibCommon/audio/SequencePlayer.cpp | 18 +++--- .../AliveLibCommon/audio/SequencePlayer.hpp | 3 +- Source/AliveLibCommon/audio/Soundbank.cpp | 6 +- .../AliveLibCommon/audio/mixer/AliveAudio.cpp | 9 ++- .../AliveLibCommon/audio/mixer/AliveAudio.hpp | 4 +- options.cmake | 2 +- 9 files changed, 83 insertions(+), 28 deletions(-) diff --git a/Source/AliveLibCommon/CMakeLists.txt b/Source/AliveLibCommon/CMakeLists.txt index 704463da9..265b9c5ef 100644 --- a/Source/AliveLibCommon/CMakeLists.txt +++ b/Source/AliveLibCommon/CMakeLists.txt @@ -39,8 +39,8 @@ SET(AliveLibSrcCommon audio/mixer/AliveAudio.cpp audio/mixer/Voice.hpp audio/mixer/Voice.cpp - audio/ADSR.hpp - audio/ADSR.cpp + audio/mixer/ADSR.hpp + audio/mixer/ADSR.cpp audio/Exceptions.hpp audio/Stream.hpp audio/Stream.cpp @@ -50,6 +50,8 @@ SET(AliveLibSrcCommon audio/SequencePlayer.cpp audio/MidiPlayer.hpp audio/MidiPlayer.cpp + audio/mixer/biquad.hpp + audio/mixer/biquad.cpp ) add_library(AliveLibCommon ${AliveLibSrcCommon}) diff --git a/Source/AliveLibCommon/audio/MidiPlayer.cpp b/Source/AliveLibCommon/audio/MidiPlayer.cpp index 6c7d5cc5c..417b45a7c 100644 --- a/Source/AliveLibCommon/audio/MidiPlayer.cpp +++ b/Source/AliveLibCommon/audio/MidiPlayer.cpp @@ -47,12 +47,22 @@ MidiPlayer::MidiPlayer(ResourceProvider* provider) { mResourceProvider = provider; mSoundSampleParser = new DefaultSoundSampleParser(); + idBank = new s32[idBankSize]; + for (int i = 0; i < idBankSize; i++) + { + idBank[i] = 0; + } } MidiPlayer::MidiPlayer(ResourceProvider* provider, SoundSampleParser* sampleParser) { mResourceProvider = provider; mSoundSampleParser = sampleParser; + idBank = new s32[idBankSize]; + for (int i = 0; i < idBankSize; i++) + { + idBank[i] = 0; + } } @@ -128,6 +138,12 @@ void MidiPlayer::SND_Load_VABS(SoundBlockInfo* pSoundBlockInfo, s32 reverb) tone->ReleaseTime = realADSR.release_time; tone->SustainTime = realADSR.sustain_time; + f32 sustain_level = static_cast((2 * (~(u8) vagAttr->field_10_adsr1 & 0xF))); + tone->AttackTime = std::min(static_cast((powf(2.0f, ((vagAttr->field_10_adsr1 >> 8) & 0x7F) * 0.25f) * 0.09f)), static_cast(32767)); + tone->DecayTime = static_cast((((vagAttr->field_10_adsr1 >> 4) & 0xF) / 15.0f) * 16.0); + tone->SustainTime = std::min(static_cast((sustain_level / 15.0f) * 600.0), static_cast(32767)); + //tone->ReleaseTime = std::min(static_cast(pow(2, vagAttr->field_12_adsr2 & 0x1F) * 0.045f), static_cast(32767)); + if (realADSR.attack_time > 1) { // This works until the loop database is added. tone->Loop = true; @@ -212,6 +228,10 @@ void MidiPlayer::SND_Stop_Channels_Mask(u32 bitMask) { bitMask; std::cout << "bitmask " << bitMask << "\n"; + AliveAudio::LockNotes(); + AliveAudio::ClearAllTrackVoices(bitMask, true); + AliveAudio::UnlockNotes(); + ReleaseId(bitMask); } void MidiPlayer::SND_SEQ_Stop(u16 idx) @@ -221,6 +241,7 @@ void MidiPlayer::SND_SEQ_Stop(u16 idx) { player->StopSequence(); RemoveSequencePlayer(player); + ReleaseId(player->m_PlayId); delete player; } } @@ -254,7 +275,7 @@ s16 MidiPlayer::SND_SEQ_PlaySeq(u16 idx, s32 repeatCount, s16 bDontStop) std::cout << "Play seq " << idx << "\n"; player->LoadSequenceData(mSequences.at(s16(idx)), s32(idx), repeatCount); - player->PlaySequence(); + player->PlaySequence(idx); return s16(mSequencePlayers.size() - 1); } @@ -312,7 +333,7 @@ s16 MidiPlayer::SND_SEQ_Play(u16 idx, s32 repeatCount, s16 volLeft, s16 volRight player = new SequencePlayer(); player->LoadSequenceData(mSequences.at(s16(idx)), s32(idx), repeatCount); - player->PlaySequence(); + player->PlaySequence(idx); mSequencePlayers.push_back(player); return s16(mSequencePlayers.size() - 1); } @@ -331,7 +352,7 @@ s16 MidiPlayer::SND_SsIsEos_DeInlined(u16 idx) // 1 means we're still playing return player->completedRepeats() < player->mRepeatCount ? 1 : 0; } - return 9; + return 1; } s32 MidiPlayer::SFX_SfxDefinition_Play(SfxDefinition* sfxDef, s32 volLeft, s32 volRight, s32 pitch_min, s32 pitch_max) @@ -344,9 +365,10 @@ s32 MidiPlayer::SFX_SfxDefinition_Play(SfxDefinition* sfxDef, s32 volLeft, s32 v // AliveAudio::PlayOneShot(sfxDef->program, sfxDef->note, volLeft, 0); AliveAudio::LockNotes(); - AliveAudio::PlayOneShot(sfxDef->program, sfxDef->note, volLeft, volRight, 0, pitch_min, pitch_max); + int playId = NextId(); + AliveAudio::PlayOneShot(playId, sfxDef->program, sfxDef->note, volLeft, volRight, 0, pitch_min, pitch_max); AliveAudio::UnlockNotes(); - return 11; + return playId; } s32 MidiPlayer::SFX_SfxDefinition_Play(SfxDefinition* sfxDef, s32 volume, s32 pitch_min, s32 pitch_max) @@ -363,9 +385,10 @@ s32 MidiPlayer::SFX_SfxDefinition_Play(SfxDefinition* sfxDef, s32 volume, s32 pi //AliveAudio::PlayOneShot(sfxDef->program, sfxDef->note, volume, 0); AliveAudio::LockNotes(); - AliveAudio::PlayOneShot(sfxDef->program, sfxDef->note, volume, volume, 0, pitch_min, pitch_max); + int playId = NextId(); + AliveAudio::PlayOneShot(playId, sfxDef->program, sfxDef->note, volume, volume, 0, pitch_min, pitch_max); AliveAudio::UnlockNotes(); - return 10; + return playId; } s32 MidiPlayer::SND(s32 program, s32 vabId, s32 note, s16 vol, s16 min, s16 max) @@ -377,9 +400,10 @@ s32 MidiPlayer::SND(s32 program, s32 vabId, s32 note, s16 vol, s16 min, s16 max) min; max; AliveAudio::LockNotes(); + int playId = NextId(); AliveAudio::NoteOn(program, note, char(vol), 0); AliveAudio::UnlockNotes(); - return 12; + return playId; } void MidiPlayer::SsUtAllKeyOff(s32 mode) @@ -418,4 +442,22 @@ s32 MidiPlayer::SND(s32 program, s32 vabId, s32 note, s16 vol, s16 min, s16 max) } } +s32 MidiPlayer::NextId() +{ + for (int i = 0; i < idBankSize; i++) + { + if (idBank[i] == 0) + { + idBank[i] = 1; + return i + 255; + } + } + return 55; +} + +void MidiPlayer::ReleaseId(s32 id) +{ + idBank[id - 255] = 0; +} + } // namespace psx \ No newline at end of file diff --git a/Source/AliveLibCommon/audio/MidiPlayer.hpp b/Source/AliveLibCommon/audio/MidiPlayer.hpp index ad25ef9ca..80c45907f 100644 --- a/Source/AliveLibCommon/audio/MidiPlayer.hpp +++ b/Source/AliveLibCommon/audio/MidiPlayer.hpp @@ -213,6 +213,11 @@ namespace psx { void sanitizeVolume(s32* src, s32 low, s32 high); SequencePlayer* GetSequencePlayer(u16 idx); void RemoveSequencePlayer(SequencePlayer* player); + + s32 idBankSize = 1024; + s32* idBank; + s32 NextId(); + void ReleaseId(s32 id); }; } // namespace psx \ No newline at end of file diff --git a/Source/AliveLibCommon/audio/SequencePlayer.cpp b/Source/AliveLibCommon/audio/SequencePlayer.cpp index 362c8e7f0..a8f3d5946 100644 --- a/Source/AliveLibCommon/audio/SequencePlayer.cpp +++ b/Source/AliveLibCommon/audio/SequencePlayer.cpp @@ -74,7 +74,7 @@ void SequencePlayer::m_PlayerThreadFunction() switch (m.Type) { case ALIVE_MIDI_NOTE_ON: - AliveAudio::NoteOn(channels[m.Channel], m.Note, (char) m.Velocity, m_TrackID, MidiTimeToSample(m.TimeOffset)); + AliveAudio::NoteOn(channels[m.Channel], m.Note, (char) m.Velocity, m_PlayId, MidiTimeToSample(m.TimeOffset)); if (firstNote) { m_SongBeginSample = ((int) (AliveAudio::currentSampleIndex + MidiTimeToSample(m.TimeOffset))); @@ -82,7 +82,7 @@ void SequencePlayer::m_PlayerThreadFunction() } break; case ALIVE_MIDI_NOTE_OFF: - AliveAudio::NoteOffDelay(channels[m.Channel], m.Note, m_TrackID, MidiTimeToSample(m.TimeOffset)); // Fix this. Make note off's have an offset in the voice timeline. + AliveAudio::NoteOffDelay(channels[m.Channel], m.Note, m_PlayId, MidiTimeToSample(m.TimeOffset)); // Fix this. Make note off's have an offset in the voice timeline. break; case ALIVE_MIDI_PROGRAM_CHANGE: channels[m.Channel] = m.Special; @@ -99,7 +99,7 @@ void SequencePlayer::m_PlayerThreadFunction() { if (mVolLeft && mVolRight) { - AliveAudio::SetVolume(m_TrackID, mVolLeft, mVolRight); + AliveAudio::SetVolume(m_PlayId, mVolLeft, mVolRight); } if (AliveAudio::currentSampleIndex > m_SongFinishSample) @@ -156,7 +156,7 @@ void SequencePlayer::StopSequence() { m_PlayerStateMutex.lock(); AliveAudio::LockNotes(); - AliveAudio::ClearAllTrackVoices(m_TrackID, false); + AliveAudio::ClearAllTrackVoices(m_PlayId, false); m_PlayerState = ALIVE_SEQUENCER_STOPPED; mCompletedRepeats.store(1); m_PrevBar = 0; @@ -164,9 +164,10 @@ void SequencePlayer::StopSequence() m_PlayerStateMutex.unlock(); } -void SequencePlayer::PlaySequence() +void SequencePlayer::PlaySequence(s32 playId) { m_PlayerStateMutex.lock(); + m_PlayId = playId; if (m_PlayerState == ALIVE_SEQUENCER_STOPPED || m_PlayerState == ALIVE_SEQUENCER_FINISHED) { m_PrevBar = 0; @@ -277,9 +278,9 @@ int SequencePlayer::LoadSequenceStream(Stream& stream) { nextQuarter += quarterDur; } - nextQuarter -= quarterDur; + //nextQuarter -= quarterDur; - if (nextQuarter == 0) + if (nextQuarter - quarterDur <= 0) { nextQuarter = deltaTime; } @@ -354,6 +355,7 @@ int SequencePlayer::LoadSequenceStream(Stream& stream) } else { + prevDeltaTime = deltaTime; m_MessageList.push_back(AliveAudioMidiMessage(ALIVE_MIDI_NOTE_ON, deltaTime, channel, note, velocity)); } } @@ -372,7 +374,7 @@ int SequencePlayer::LoadSequenceStream(Stream& stream) { Uint8 prog = 0; stream.ReadUInt8(prog); - prevDeltaTime = deltaTime; + //prevDeltaTime = deltaTime; m_MessageList.push_back(AliveAudioMidiMessage(ALIVE_MIDI_PROGRAM_CHANGE, deltaTime, channel, 0, 0, prog)); } break; diff --git a/Source/AliveLibCommon/audio/SequencePlayer.hpp b/Source/AliveLibCommon/audio/SequencePlayer.hpp index 8222e020d..149c65757 100644 --- a/Source/AliveLibCommon/audio/SequencePlayer.hpp +++ b/Source/AliveLibCommon/audio/SequencePlayer.hpp @@ -74,10 +74,11 @@ class SequencePlayer ~SequencePlayer(); s32 mRepeatCount; + s32 m_PlayId; int LoadSequenceData(std::vector seqData, s32 trackId, s32 repeatCount); int LoadSequenceStream(Stream& stream); - void PlaySequence(); + void PlaySequence(s32 playId); void StopSequence(); int completedRepeats(); diff --git a/Source/AliveLibCommon/audio/Soundbank.cpp b/Source/AliveLibCommon/audio/Soundbank.cpp index 34b04d8a6..2820a6912 100644 --- a/Source/AliveLibCommon/audio/Soundbank.cpp +++ b/Source/AliveLibCommon/audio/Soundbank.cpp @@ -9,10 +9,10 @@ float Sample::GetSample(float sampleOffset) AliveAudioHelper helper; //if (!AliveAudio::Interpolation) - //return helper.SampleSint16ToFloat(m_SampleBuffer[(int) sampleOffset]); // No interpolation. Faster but sounds jaggy. + return helper.SampleSint16ToFloat(m_SampleBuffer[(int) sampleOffset]); // No interpolation. Faster but sounds jaggy. - int roundedOffset = (int) floor(sampleOffset); - return helper.SampleSint16ToFloat(s16(helper.Lerp(m_SampleBuffer[roundedOffset], m_SampleBuffer[roundedOffset + 1], float(int(sampleOffset) - roundedOffset)))); + //int roundedOffset = (int) floor(sampleOffset); + //return helper.SampleSint16ToFloat(s16(helper.Lerp(m_SampleBuffer[roundedOffset], m_SampleBuffer[roundedOffset + 1], float(int(sampleOffset) - roundedOffset)))); } Soundbank::Soundbank(std::vector samples, std::vector programs) diff --git a/Source/AliveLibCommon/audio/mixer/AliveAudio.cpp b/Source/AliveLibCommon/audio/mixer/AliveAudio.cpp index c9c2ec568..af484a6ce 100644 --- a/Source/AliveLibCommon/audio/mixer/AliveAudio.cpp +++ b/Source/AliveLibCommon/audio/mixer/AliveAudio.cpp @@ -33,7 +33,7 @@ void AliveInitAudio() SDL_PauseAudio(0); } -void AliveAudio::PlayOneShot(int programId, int note, s32 volLeft, s32 volRight, float pitch, s32 pitch_min, s32 pitch_max) +void AliveAudio::PlayOneShot(s32 playId, int programId, int note, s32 volLeft, s32 volRight, float pitch, s32 pitch_min, s32 pitch_max) { for (auto program : m_CurrentSoundbank->m_Programs) { @@ -50,6 +50,7 @@ void AliveAudio::PlayOneShot(int programId, int note, s32 volLeft, s32 volRight, if (note >= tone->Min && note <= tone->Max) { Voice* voice = new Voice(); + voice->i_TrackID = playId; voice->i_Note = note; voice->f_Velocity = float(std::min(volLeft, volRight)) / 127; voice->m_Tone = tone; @@ -65,9 +66,9 @@ void AliveAudio::PlayOneShot(int programId, int note, s32 volLeft, s32 volRight, } } -void AliveAudio::PlayOneShot(int programId, int note, s32 volume, float pitch, s32 pitch_min, s32 pitch_max) +void AliveAudio::PlayOneShot(s32 playId, int programId, int note, s32 volume, float pitch, s32 pitch_min, s32 pitch_max) { - AliveAudio::PlayOneShot(programId, note, volume, volume, pitch, pitch_min, pitch_max); + AliveAudio::PlayOneShot(playId, programId, note, volume, volume, pitch, pitch_min, pitch_max); } void AliveAudio::NoteOn(int programId, int note, char velocity, float pitch , int trackID , float trackDelay ) @@ -187,6 +188,8 @@ void AliveAudio::ClearAllTrackVoices(int trackID, bool forceKill) { if (forceKill) { + //voice->b_NoteOn = false; + //voice->ActiveReleaseLevel = 1; if (voice->i_TrackID == trackID) // Kill the voices no matter what. Cuts of any sounds = Ugly sound { deadVoices.push_back(voice); diff --git a/Source/AliveLibCommon/audio/mixer/AliveAudio.hpp b/Source/AliveLibCommon/audio/mixer/AliveAudio.hpp index ab2ce8bd6..628c6beae 100644 --- a/Source/AliveLibCommon/audio/mixer/AliveAudio.hpp +++ b/Source/AliveLibCommon/audio/mixer/AliveAudio.hpp @@ -26,8 +26,8 @@ class AliveAudio static std::vector m_Voices; static bool AliveAudio::voiceListLocked; - static void PlayOneShot(s32 program, s32 note, s32 volume, float pitch, s32 pitch_min, s32 pitch_max); - static void PlayOneShot(s32 program, s32 note, s32 volLeft, s32 volRight, float pitch, s32 pitch_min, s32 pitch_max); + static void PlayOneShot(s32 playId, s32 program, s32 note, s32 volume, float pitch, s32 pitch_min, s32 pitch_max); + static void PlayOneShot(s32 playId, s32 program, s32 note, s32 volLeft, s32 volRight, float pitch, s32 pitch_min, s32 pitch_max); static void NoteOn(int program, int note, char velocity, float pitch = 0, int trackID = 0, float trackDelay = 0); static void NoteOn(int program, int note, char velocity, int trackID = 0, float trackDelay = 0); diff --git a/options.cmake b/options.cmake index 7952e3ffd..a9161128d 100644 --- a/options.cmake +++ b/options.cmake @@ -17,5 +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(ALTERNATE_AUDIO "Use an audio replacement for better, but not 1:1 sound." ON) +option(ALTERNATE_AUDIO "Use alternate audio processing." ON) CONFIGURE_FILE(${CMAKE_CURRENT_SOURCE_DIR}/Source/relive_config.h.in ${CMAKE_CURRENT_SOURCE_DIR}/Source/AliveLibCommon/relive_config.h) From b319132940cadd33874349c81b17ea5b62dfd694 Mon Sep 17 00:00:00 2001 From: Sean Horton <3359434+sean-horton@users.noreply.github.com> Date: Mon, 20 Feb 2023 08:14:37 -0500 Subject: [PATCH 16/82] openal checkpoint --- .gitmodules | 3 + Source/AliveLibCommon/CMakeLists.txt | 7 +- Source/AliveLibCommon/audio/MidiPlayer.cpp | 5 +- Source/AliveLibCommon/audio/MidiPlayer.hpp | 10 +- .../AliveLibCommon/audio/SequencePlayer.cpp | 106 ++--- .../AliveLibCommon/audio/SequencePlayer.hpp | 9 +- Source/AliveLibCommon/audio/Soundbank.hpp | 5 +- .../AliveLibCommon/audio/mixer/AliveAudio.cpp | 394 +++++++++++++++++- .../AliveLibCommon/audio/mixer/AliveAudio.hpp | 4 +- Source/AliveLibCommon/openal-soft | 1 + 10 files changed, 476 insertions(+), 68 deletions(-) create mode 160000 Source/AliveLibCommon/openal-soft diff --git a/.gitmodules b/.gitmodules index c681d75bf..ea1e14646 100644 --- a/.gitmodules +++ b/.gitmodules @@ -31,3 +31,6 @@ [submodule "3rdParty/json"] path = 3rdParty/json url = https://github.com/nlohmann/json.git +[submodule "Source/AliveLibCommon/openal-soft"] + path = Source/AliveLibCommon/openal-soft + url = https://github.com/kcat/openal-soft.git diff --git a/Source/AliveLibCommon/CMakeLists.txt b/Source/AliveLibCommon/CMakeLists.txt index 265b9c5ef..dd0e8e8a3 100644 --- a/Source/AliveLibCommon/CMakeLists.txt +++ b/Source/AliveLibCommon/CMakeLists.txt @@ -114,4 +114,9 @@ target_link_libraries(AliveLibCommon project_warnings ) -export(TARGETS AliveLibCommon FILE AliveLibCommon.cmake) +add_subdirectory(openal-soft) +target_include_directories(AliveLibCommon PUBLIC openal_soft) +target_link_directories(AliveLibCommon PRIVATE openal_soft) +target_link_libraries(AliveLibCommon OpenAL) + +export(TARGETS AliveLibCommon FILE AliveLibCommon.cmake) \ No newline at end of file diff --git a/Source/AliveLibCommon/audio/MidiPlayer.cpp b/Source/AliveLibCommon/audio/MidiPlayer.cpp index 417b45a7c..51f543d7d 100644 --- a/Source/AliveLibCommon/audio/MidiPlayer.cpp +++ b/Source/AliveLibCommon/audio/MidiPlayer.cpp @@ -77,7 +77,7 @@ void MidiPlayer::SND_Shutdown() void MidiPlayer::SND_Load_VABS(SoundBlockInfo* pSoundBlockInfo, s32 reverb) { - reverb; // TODO - reverb + reverb; // TODO - reverb - this seems to be the only spot while (1) { @@ -118,8 +118,11 @@ void MidiPlayer::SND_Load_VABS(SoundBlockInfo* pSoundBlockInfo, s32 reverb) if (vagAttr->field_2_vol > 0) { program->prog_id = vagAttr->field_14_prog; + tone->mode = vagAttr->field_1_mode; tone->f_Volume = vagAttr->field_2_vol / 127.0f; tone->c_Center = vagAttr->field_4_centre; + //tone->c_Shift = (vagAttr->field_5_shift | (vagAttr->field_4_centre << 7)); + tone->c_Shift = vagAttr->field_5_shift; tone->f_Pan = (vagAttr->field_3_pan / 64.0f) - 1.0f; tone->Min = vagAttr->field_6_min; diff --git a/Source/AliveLibCommon/audio/MidiPlayer.hpp b/Source/AliveLibCommon/audio/MidiPlayer.hpp index 80c45907f..33a365723 100644 --- a/Source/AliveLibCommon/audio/MidiPlayer.hpp +++ b/Source/AliveLibCommon/audio/MidiPlayer.hpp @@ -8,13 +8,13 @@ namespace psx { struct VagAtr final { s8 field_0_priority; - s8 field_1_mode; + s8 field_1_mode; // reverb? s8 field_2_vol; s8 field_3_pan; - u8 field_4_centre; - u8 field_5_shift; - s8 field_6_min; - s8 field_7_max; + 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; diff --git a/Source/AliveLibCommon/audio/SequencePlayer.cpp b/Source/AliveLibCommon/audio/SequencePlayer.cpp index a8f3d5946..4a0aad16e 100644 --- a/Source/AliveLibCommon/audio/SequencePlayer.cpp +++ b/Source/AliveLibCommon/audio/SequencePlayer.cpp @@ -44,7 +44,7 @@ float SequencePlayer::MidiTimeToSample(int time) // This may, or may not be correct. // TODO: Revise // Oct 7, 2022 - added (x1.041f). For some reason this seems to match AE playback speed... // AO might be better with just times 1.000? - return ((60.0f * float(time)) / float(m_SongTempo)) * (float(AliveAudioSampleRate) / 500.0f) * 1.041f; + return ((60.0f * float(time)) / float(m_SongTempo)) * (float(AliveAudioSampleRate) / 500.0f) * 1000.0f; } s32 SequencePlayer::completedRepeats() @@ -52,6 +52,12 @@ s32 SequencePlayer::completedRepeats() return mCompletedRepeats.load(); } +u64 timeSinceEpochMillisec() +{ + using namespace std::chrono; + return duration_cast(system_clock::now().time_since_epoch()).count(); +} + void SequencePlayer::m_PlayerThreadFunction() { int channels[16]; @@ -60,26 +66,42 @@ void SequencePlayer::m_PlayerThreadFunction() channels[i] = 0; } + u64 ms_tick = timeSinceEpochMillisec(); + int trackPosition = 0; while (!m_KillThread) { + m_PlayerStateMutex.lock(); AliveAudio::LockNotes(); + float multi = float(std::min(mVolLeft, mVolRight)) / 127.0f; - if (m_PlayerState == ALIVE_SEQUENCER_INIT_VOICES) + if ((int) m_MessageList.size() > 0) { - bool firstNote = true; - for (int i = 0; i < (int)m_MessageList.size(); i++) + if (trackPosition >= (int) m_MessageList.size()) + { + trackPosition = 0; + ms_tick = timeSinceEpochMillisec(); + } + + u64 ms_now = timeSinceEpochMillisec() - ms_tick; + float tickDurationUs = m_RawTempo / m_ticksPerBeat; + u64 diffUs = ms_now * 1000; + u64 track_tick = u64(diffUs / tickDurationUs); + + + while (trackPosition < (int) m_MessageList.size()) { - AliveAudioMidiMessage m = m_MessageList[i]; + AliveAudioMidiMessage m = m_MessageList[trackPosition]; + if (m.TimeOffset > track_tick) + { + break; + } + + trackPosition++; switch (m.Type) { case ALIVE_MIDI_NOTE_ON: - AliveAudio::NoteOn(channels[m.Channel], m.Note, (char) m.Velocity, m_PlayId, MidiTimeToSample(m.TimeOffset)); - if (firstNote) - { - m_SongBeginSample = ((int) (AliveAudio::currentSampleIndex + MidiTimeToSample(m.TimeOffset))); - firstNote = false; - } + AliveAudio::NoteOn(channels[m.Channel], m.Note, (char) m.Velocity, m_PlayId, MidiTimeToSample(m.TimeOffset), multi); break; case ALIVE_MIDI_NOTE_OFF: AliveAudio::NoteOffDelay(channels[m.Channel], m.Note, m_PlayId, MidiTimeToSample(m.TimeOffset)); // Fix this. Make note off's have an offset in the voice timeline. @@ -87,44 +109,17 @@ void SequencePlayer::m_PlayerThreadFunction() case ALIVE_MIDI_PROGRAM_CHANGE: channels[m.Channel] = m.Special; break; + //break; case ALIVE_MIDI_ENDTRACK: m_PlayerState = ALIVE_SEQUENCER_PLAYING; m_SongFinishSample = ((int) (AliveAudio::currentSampleIndex + MidiTimeToSample(m.TimeOffset))); + mCompletedRepeats.store(mCompletedRepeats.load() + 1); + m_PlayerState = ALIVE_SEQUENCER_FINISHED; break; - } - } - } - - if (m_PlayerState == ALIVE_SEQUENCER_PLAYING) - { - if (mVolLeft && mVolRight) - { - AliveAudio::SetVolume(m_PlayId, mVolLeft, mVolRight); - } - - if (AliveAudio::currentSampleIndex > m_SongFinishSample) - { - m_PlayerState = ALIVE_SEQUENCER_FINISHED; - mCompletedRepeats.store(mCompletedRepeats.load() + 1); - - // Give a quarter beat anyway - if (m_QuarterCallback != nullptr) - m_QuarterCallback(); - } - } - - if (m_PlayerState == ALIVE_SEQUENCER_PLAYING) - { - int quarterBeat = (m_SongFinishSample - m_SongBeginSample) / m_TimeSignatureBars; - int currentQuarterBeat = (int) (floor(GetPlaybackPositionSample() / quarterBeat)); - - if (m_PrevBar != currentQuarterBeat) - { - m_PrevBar = currentQuarterBeat; - - if (m_QuarterCallback != nullptr) - { - m_QuarterCallback(); + default: + { + } + // Nothin } } } @@ -132,8 +127,14 @@ void SequencePlayer::m_PlayerThreadFunction() AliveAudio::UnlockNotes(); m_PlayerStateMutex.unlock(); - std::this_thread::sleep_for(std::chrono::milliseconds(30)); + // THIS IS RIGHT! + // int t = (int) (m_RawTempo * m.TimeOffset / m_ticksPerBeat); + //std::this_thread::sleep_for(std::chrono::microseconds((int) t)); + + std::this_thread::sleep_for(std::chrono::milliseconds(10)); } + + } int SequencePlayer::GetPlaybackPositionSample() @@ -202,7 +203,7 @@ int SequencePlayer::LoadSequenceStream(Stream& stream) stream.ReadUInt32(seqHeader.mMagic); stream.ReadUInt32(seqHeader.mVersion); - stream.ReadUInt16(seqHeader.mResolutionOfQuaterNote); + stream.ReadBytes(seqHeader.mResolutionOfQuaterNote, sizeof(seqHeader.mResolutionOfQuaterNote)); stream.ReadBytes(seqHeader.mTempo, sizeof(seqHeader.mTempo)); stream.ReadUInt8(seqHeader.mTimeSignatureBars); stream.ReadUInt8(seqHeader.mTimeSignatureBeats); @@ -213,10 +214,17 @@ int SequencePlayer::LoadSequenceStream(Stream& stream) tempoValue += seqHeader.mTempo[2 - i] << (8 * i); } + int q = 0; + for (int i = 0; i < 2; i++) + { + q += seqHeader.mResolutionOfQuaterNote[1 - i] << (8 * i); + } + m_TimeSignatureBars = seqHeader.mTimeSignatureBars; m_SongTempo = ((float) (60000000.0 / tempoValue)); - + m_RawTempo = (float)tempoValue; // tempo (length of quarter note in microseconds) 0.000001us/s vs 0.001ms/s + m_ticksPerBeat = float(q); unsigned int prevDeltaTime = 0; unsigned int deltaTime = 0; @@ -285,7 +293,7 @@ int SequencePlayer::LoadSequenceStream(Stream& stream) nextQuarter = deltaTime; } - m_MessageList.push_back(AliveAudioMidiMessage(ALIVE_MIDI_ENDTRACK, nextQuarter, 0, 0, 0)); + m_MessageList.push_back(AliveAudioMidiMessage(ALIVE_MIDI_ENDTRACK, 480, 0, 0, 0)); return 0; //Sint32 loopCount = gSeqInfo.iNumTimesToLoop; // v1 some hard coded data?? or just a local static? //if (loopCount) // If zero then loop forever diff --git a/Source/AliveLibCommon/audio/SequencePlayer.hpp b/Source/AliveLibCommon/audio/SequencePlayer.hpp index 149c65757..b11d00a5d 100644 --- a/Source/AliveLibCommon/audio/SequencePlayer.hpp +++ b/Source/AliveLibCommon/audio/SequencePlayer.hpp @@ -13,7 +13,7 @@ struct SeqHeader { Uint32 mMagic; // SEQp Uint32 mVersion; // Seems to always be 1 - Uint16 mResolutionOfQuaterNote; + Uint8 mResolutionOfQuaterNote[2]; Uint8 mTempo[3]; Uint8 mTimeSignatureBars; Uint8 mTimeSignatureBeats; @@ -91,8 +91,8 @@ class SequencePlayer AliveAudioQuarterCallback m_QuarterCallback; private: - s32 mVolLeft = 0; - s32 mVolRight = 0; + s32 mVolLeft = 64; + s32 mVolRight = 64; std::atomic mCompletedRepeats; int m_KillThread = false; // If true, loop thread will exit. int m_SongFinishSample = 0; // Not relative. @@ -100,6 +100,9 @@ class SequencePlayer int m_PrevBar = 0; int m_TimeSignatureBars; float m_SongTempo; + float m_RawTempo; + float m_ticksPerBeat; + int m_Tick = 0; void m_PlayerThreadFunction(); std::vector m_MessageList; std::thread* m_SequenceThread; diff --git a/Source/AliveLibCommon/audio/Soundbank.hpp b/Source/AliveLibCommon/audio/Soundbank.hpp index 05f2559d1..28f87ae18 100644 --- a/Source/AliveLibCommon/audio/Soundbank.hpp +++ b/Source/AliveLibCommon/audio/Soundbank.hpp @@ -40,13 +40,14 @@ class Tone public: // volume 0-1 float f_Volume; + s8 mode; // panning -1 - 1 float f_Pan; // Root Key - unsigned char c_Center; - unsigned char c_Shift; + u8 c_Center; + u8 c_Shift; // Key range unsigned char Min; diff --git a/Source/AliveLibCommon/audio/mixer/AliveAudio.cpp b/Source/AliveLibCommon/audio/mixer/AliveAudio.cpp index af484a6ce..e3a336642 100644 --- a/Source/AliveLibCommon/audio/mixer/AliveAudio.cpp +++ b/Source/AliveLibCommon/audio/mixer/AliveAudio.cpp @@ -3,6 +3,41 @@ #include #include "AliveAudio.hpp" + +#include "AL/alc.h" +#include "AL/al.h" +#include "AL/alext.h" +#include "AL/efx.h" +#include "AL/efx-presets.h" + +/* Effect object functions */ +static LPALGENEFFECTS alGenEffects; +static LPALDELETEEFFECTS alDeleteEffects; +static LPALISEFFECT alIsEffect; +static LPALEFFECTI alEffecti; +static LPALEFFECTIV alEffectiv; +static LPALEFFECTF alEffectf; +static LPALEFFECTFV alEffectfv; +static LPALGETEFFECTI alGetEffecti; +static LPALGETEFFECTIV alGetEffectiv; +static LPALGETEFFECTF alGetEffectf; +static LPALGETEFFECTFV alGetEffectfv; +static LPALFILTERF alFilterf; +static LPALFILTERI alFilteri; + +/* Auxiliary Effect Slot object functions */ +static LPALGENAUXILIARYEFFECTSLOTS alGenAuxiliaryEffectSlots; +static LPALDELETEAUXILIARYEFFECTSLOTS alDeleteAuxiliaryEffectSlots; +static LPALISAUXILIARYEFFECTSLOT alIsAuxiliaryEffectSlot; +static LPALAUXILIARYEFFECTSLOTI alAuxiliaryEffectSloti; +static LPALAUXILIARYEFFECTSLOTIV alAuxiliaryEffectSlotiv; +static LPALAUXILIARYEFFECTSLOTF alAuxiliaryEffectSlotf; +static LPALAUXILIARYEFFECTSLOTFV alAuxiliaryEffectSlotfv; +static LPALGETAUXILIARYEFFECTSLOTI alGetAuxiliaryEffectSloti; +static LPALGETAUXILIARYEFFECTSLOTIV alGetAuxiliaryEffectSlotiv; +static LPALGETAUXILIARYEFFECTSLOTF alGetAuxiliaryEffectSlotf; +static LPALGETAUXILIARYEFFECTSLOTFV alGetAuxiliaryEffectSlotfv; + namespace psx { Soundbank* AliveAudio::m_CurrentSoundbank = nullptr; @@ -11,6 +46,94 @@ std::vector AliveAudio::m_Voices; long long AliveAudio::currentSampleIndex = 20; biquad* AliveAudio::AliveAudioEQBiQuad = nullptr; +ALCdevice* device; +ALCcontext* ctx; +ALuint effectG; +ALuint slot = 0; +unsigned int alSource[64]; +ALenum error; +unsigned int alSampleSet[64]; +Sample* sampleId[64]; + +ALuint LoadEffect(const EFXEAXREVERBPROPERTIES* reverb) +{ + ALuint effect = 0; + ALenum err; + + /* Create the effect object and check if we can do EAX reverb. */ + alGenEffects(1, &effect); + //alFilteri(effect, AL_FILTER_TYPE, AL_FILTER_LOWPASS); + + if (alGetEnumValue("AL_EFFECT_EAXREVERB") != 0) + { + printf("Using EAX Reverb\n"); + + /* EAX Reverb is available. Set the EAX effect type then load the + * reverb properties. */ + alEffecti(effect, AL_EFFECT_TYPE, AL_EFFECT_EAXREVERB); + + alEffectf(effect, AL_EAXREVERB_DENSITY, reverb->flDensity); + alEffectf(effect, AL_EAXREVERB_DIFFUSION, reverb->flDiffusion); + alEffectf(effect, AL_EAXREVERB_GAIN, reverb->flGain); + alEffectf(effect, AL_EAXREVERB_GAINHF, reverb->flGainHF); + alEffectf(effect, AL_EAXREVERB_GAINLF, reverb->flGainLF); + alEffectf(effect, AL_EAXREVERB_DECAY_TIME, reverb->flDecayTime); + alEffectf(effect, AL_EAXREVERB_DECAY_HFRATIO, reverb->flDecayHFRatio); + alEffectf(effect, AL_EAXREVERB_DECAY_LFRATIO, reverb->flDecayLFRatio); + alEffectf(effect, AL_EAXREVERB_REFLECTIONS_GAIN, reverb->flReflectionsGain); + alEffectf(effect, AL_EAXREVERB_REFLECTIONS_DELAY, reverb->flReflectionsDelay); + alEffectfv(effect, AL_EAXREVERB_REFLECTIONS_PAN, reverb->flReflectionsPan); + alEffectf(effect, AL_EAXREVERB_LATE_REVERB_GAIN, reverb->flLateReverbGain); + alEffectf(effect, AL_EAXREVERB_LATE_REVERB_DELAY, reverb->flLateReverbDelay); + alEffectfv(effect, AL_EAXREVERB_LATE_REVERB_PAN, reverb->flLateReverbPan); + alEffectf(effect, AL_EAXREVERB_ECHO_TIME, reverb->flEchoTime); + alEffectf(effect, AL_EAXREVERB_ECHO_DEPTH, reverb->flEchoDepth); + alEffectf(effect, AL_EAXREVERB_MODULATION_TIME, reverb->flModulationTime); + alEffectf(effect, AL_EAXREVERB_MODULATION_DEPTH, reverb->flModulationDepth); + alEffectf(effect, AL_EAXREVERB_AIR_ABSORPTION_GAINHF, reverb->flAirAbsorptionGainHF); + alEffectf(effect, AL_EAXREVERB_HFREFERENCE, reverb->flHFReference); + alEffectf(effect, AL_EAXREVERB_LFREFERENCE, reverb->flLFReference); + alEffectf(effect, AL_EAXREVERB_ROOM_ROLLOFF_FACTOR, reverb->flRoomRolloffFactor); + alEffecti(effect, AL_EAXREVERB_DECAY_HFLIMIT, reverb->iDecayHFLimit); + } + else + { + printf("Using Standard Reverb\n"); + + /* No EAX Reverb. Set the standard reverb effect type then load the + * available reverb properties. */ + alEffecti(effect, AL_EFFECT_TYPE, AL_EFFECT_REVERB); + + alEffectf(effect, AL_REVERB_DENSITY, reverb->flDensity); + alEffectf(effect, AL_REVERB_DIFFUSION, reverb->flDiffusion); + alEffectf(effect, AL_REVERB_GAIN, reverb->flGain); + alEffectf(effect, AL_REVERB_GAINHF, reverb->flGainHF); + alEffectf(effect, AL_REVERB_DECAY_TIME, reverb->flDecayTime); + alEffectf(effect, AL_REVERB_DECAY_HFRATIO, reverb->flDecayHFRatio); + alEffectf(effect, AL_REVERB_REFLECTIONS_GAIN, reverb->flReflectionsGain); + alEffectf(effect, AL_REVERB_REFLECTIONS_DELAY, reverb->flReflectionsDelay); + alEffectf(effect, AL_REVERB_LATE_REVERB_GAIN, reverb->flLateReverbGain); + alEffectf(effect, AL_REVERB_LATE_REVERB_DELAY, reverb->flLateReverbDelay); + alEffectf(effect, AL_REVERB_AIR_ABSORPTION_GAINHF, reverb->flAirAbsorptionGainHF); + alEffectf(effect, AL_REVERB_ROOM_ROLLOFF_FACTOR, reverb->flRoomRolloffFactor); + alEffecti(effect, AL_REVERB_DECAY_HFLIMIT, reverb->iDecayHFLimit); + } + + + + /* Check if an error occured, and clean up if so. */ + err = alGetError(); + if (err != AL_NO_ERROR) + { + fprintf(stderr, "OpenAL error: %s\n", alGetString(err)); + if (alIsEffect(effect)) + alDeleteEffects(1, &effect); + return 0; + } + + return effect; +} + void AliveInitAudio() { SDL_Init(SDL_INIT_AUDIO); @@ -31,6 +154,92 @@ void AliveInitAudio() } SDL_PauseAudio(0); + + + device = alcOpenDevice(NULL); + ctx = alcCreateContext(device, NULL); + alcMakeContextCurrent(ctx); + + /* Define a macro to help load the function pointers. + https://github.com/kcat/openal-soft/blob/master/examples/alreverb.c */ + +#if __STDC_VERSION__ >= 199901L + #define FUNCTION_CAST(T, ptr) (union \ + { \ + void* p; \ + T f; \ + }){ptr} \ + .f +#elif defined(__cplusplus) + #define FUNCTION_CAST(T, ptr) reinterpret_cast(ptr) +#else + #define FUNCTION_CAST(T, ptr) (T)(ptr) +#endif + +#define LOAD_PROC(T, x) ((x) = FUNCTION_CAST(T, alGetProcAddress(#x))) + LOAD_PROC(LPALGENEFFECTS, alGenEffects); + LOAD_PROC(LPALDELETEEFFECTS, alDeleteEffects); + LOAD_PROC(LPALISEFFECT, alIsEffect); + LOAD_PROC(LPALEFFECTI, alEffecti); + LOAD_PROC(LPALEFFECTIV, alEffectiv); + LOAD_PROC(LPALEFFECTF, alEffectf); + LOAD_PROC(LPALEFFECTFV, alEffectfv); + LOAD_PROC(LPALGETEFFECTI, alGetEffecti); + LOAD_PROC(LPALGETEFFECTIV, alGetEffectiv); + LOAD_PROC(LPALGETEFFECTF, alGetEffectf); + LOAD_PROC(LPALGETEFFECTFV, alGetEffectfv); + + LOAD_PROC(LPALFILTERF, alFilterf); + LOAD_PROC(LPALFILTERI, alFilteri); + + LOAD_PROC(LPALGENAUXILIARYEFFECTSLOTS, alGenAuxiliaryEffectSlots); + LOAD_PROC(LPALDELETEAUXILIARYEFFECTSLOTS, alDeleteAuxiliaryEffectSlots); + LOAD_PROC(LPALISAUXILIARYEFFECTSLOT, alIsAuxiliaryEffectSlot); + LOAD_PROC(LPALAUXILIARYEFFECTSLOTI, alAuxiliaryEffectSloti); + LOAD_PROC(LPALAUXILIARYEFFECTSLOTIV, alAuxiliaryEffectSlotiv); + LOAD_PROC(LPALAUXILIARYEFFECTSLOTF, alAuxiliaryEffectSlotf); + LOAD_PROC(LPALAUXILIARYEFFECTSLOTFV, alAuxiliaryEffectSlotfv); + LOAD_PROC(LPALGETAUXILIARYEFFECTSLOTI, alGetAuxiliaryEffectSloti); + LOAD_PROC(LPALGETAUXILIARYEFFECTSLOTIV, alGetAuxiliaryEffectSlotiv); + LOAD_PROC(LPALGETAUXILIARYEFFECTSLOTF, alGetAuxiliaryEffectSlotf); + LOAD_PROC(LPALGETAUXILIARYEFFECTSLOTFV, alGetAuxiliaryEffectSlotfv); +#undef LOAD_PROC + + //EFXEAXREVERBPROPERTIES reverb = EFX_REVERB_PRESET_FACTORY_MEDIUMROOM; + EFXEAXREVERBPROPERTIES reverb = EFX_REVERB_PRESET_AUDITORIUM; + + effectG = LoadEffect(&reverb); + alGenAuxiliaryEffectSlots(1, &slot); + alAuxiliaryEffectSloti(slot, AL_EFFECTSLOT_EFFECT, (ALint) effectG); + + + alGenBuffers(64, alSampleSet); + if ((error = alGetError()) != AL_NO_ERROR) + { + return; + } + + alGenSources(64, alSource); + if ((error = alGetError()) != AL_NO_ERROR) + { + return; + } +} + +int getSource() +{ + ALenum state; + int i; + for (i = 0; i < 64; i++) + { + alGetSourcei(alSource[i], AL_SOURCE_STATE, &state); + if (state != AL_PLAYING) + { + alSourceStop(alSource[i]); + return i; + } + } + return -1; } void AliveAudio::PlayOneShot(s32 playId, int programId, int note, s32 volLeft, s32 volRight, float pitch, s32 pitch_min, s32 pitch_max) @@ -60,7 +269,93 @@ void AliveAudio::PlayOneShot(s32 playId, int programId, int note, s32 volLeft, s // From the looks of things pitch_min and pitch_max are always equal. // and if pitch is 0, try using pitch_min instead. voice->f_Pitch = pitch == 0 ? pitch_min / 127 : pitch / 127; - m_Voices.push_back(voice); + //m_Voices.push_back(voice); + + alGetError(); // clear error code + Sample* s = voice->m_Tone->m_Sample; + + int i = 0; + int pos = -1; + for (i = 0; i < 64; ++i) + { + if (sampleId[i] == s || !sampleId[i]) + { + pos = i; + } + } + + int a = getSource(); + if (a == -1) + { + return; + } + + if (!sampleId[pos]) + { + alBufferData(alSampleSet[pos], AL_FORMAT_MONO16, s->m_SampleBuffer, s->i_SampleSize * 2, 22050); + if ((error = alGetError()) != AL_NO_ERROR) + { + return; + } + sampleId[pos] = s; + } + + //float center = float(tone->c_Center); + //float pitch1 = float(tone->Pitch); + //float shift = float(tone->c_Shift); + //float min = tone->Min; + //float max = tone->Max; + float freq = float(pow(1.059463094359, (f64) (note - tone->c_Shift) * 0.00390625)); + + freq = (float(note) / float(tone->c_Shift)); + freq = tone->c_Shift == 0 ? 1 : freq; + freq = (note / float(tone->c_Shift / 256.0)); + freq = tone->c_Shift == 0 ? 1 : freq; + + + float noteFreq = float(pow(2.0, float(note) / 12.0)); + float sampleRootFreq = float(pow(2.0, float(tone->c_Center + (tone->c_Shift / 127.0)) / 12.0)); + freq = noteFreq / sampleRootFreq * 2.0f; + + ALfloat pan; + pan = voice->f_Pan; + alSource3f(alSource[a], AL_POSITION, pan, 0, -sqrtf(1.0f - pan * pan)); + alSourcef(alSource[a], AL_PITCH, (ALfloat) freq); + alSourcef(alSource[a], AL_GAIN, (ALfloat) voice->f_Velocity); + + alSourcei(alSource[a], AL_BUFFER, alSampleSet[pos]); + if (tone->mode != 0) + alSource3i(alSource[a], AL_AUXILIARY_SEND_FILTER, (ALint) slot, 0, AL_FILTER_NULL); + else + alSource3i(alSource[a], AL_AUXILIARY_SEND_FILTER, AL_EFFECTSLOT_NULL, 0, NULL); + + if ((error = alGetError()) != AL_NO_ERROR) + { + return; + } + + alSourcePlay(alSource[a]); + + //ALfloat adsr = ALfloat(0.1); + //while (adsr < 2) + //{ + // alSourcef(alSource[pos], AL_PITCH, (ALfloat) adsr); + // adsr += ALfloat(0.1); + // std::this_thread::sleep_for(std::chrono::milliseconds(100)); + //} + + //ALenum state; + //do + //{ + // // al_nssleep(10000000); + // alGetSourcei(alSource, AL_SOURCE_STATE, &state); + //} + //while (alGetError() == AL_NO_ERROR && state == AL_PLAYING); + + //alDeleteSources(1, &alSource); + //alDeleteAuxiliaryEffectSlots(1, &slot); + //alDeleteEffects(1, &effect); + //alDeleteBuffers(1, alSampleSet); } } } @@ -71,7 +366,8 @@ void AliveAudio::PlayOneShot(s32 playId, int programId, int note, s32 volume, fl AliveAudio::PlayOneShot(playId, programId, note, volume, volume, pitch, pitch_min, pitch_max); } -void AliveAudio::NoteOn(int programId, int note, char velocity, float pitch , int trackID , float trackDelay ) + +void AliveAudio::NoteOn(int programId, int note, char velocity, float pitch , int trackID , float trackDelay, float masterVolMulti) { for (auto program : m_CurrentSoundbank->m_Programs) { @@ -91,14 +387,99 @@ void AliveAudio::NoteOn(int programId, int note, char velocity, float pitch , in voice->i_TrackID = trackID; voice->f_Pitch = pitch; voice->f_TrackDelay = trackDelay; - m_Voices.push_back(voice); + //m_Voices.push_back(voice); + + alGetError(); // clear error code + Sample* s = voice->m_Tone->m_Sample; + + int i = 0; + int pos = -1; + for (i = 0; i < 64; ++i) + { + if (sampleId[i] == s || !sampleId[i]) + { + pos = i; + } + } + + int a = getSource(); + if (a == -1) + { + return; + } + + if (!sampleId[pos]) + { + alBufferData(alSampleSet[pos], AL_FORMAT_MONO16, s->m_SampleBuffer, s->i_SampleSize * 2, 22050); + if ((error = alGetError()) != AL_NO_ERROR) + { + return; + } + sampleId[pos] = s; + } + + // Centre: This one is the most important. This is the root note, or, the note that your sample is in. + // If you got your sample from a Sample CD, often it will have what note it is somewhere. + // If you don't know what note your sample is in, LoopAuditioneer can find it out for you. + + // Pitch: This is the pitch fine-tuning, it might be needed depending on the sample you are using. + + // Note: The VAB tool can't preview pitch fine-tuning if you are not using a dev board. + + // MinNote: The lowest note in which this tone will be played. + + //float center = float(tone->c_Center); + /* float pitch1 = float(tone->Pitch); + + float ii = center + (center * pitch1); + ii;*/ + + //float shift_cent = float( (tone->c_Shift + (tone->c_Center << 7))); + float freq = float(pow(1.059463094359, (f64) ((tone->c_Shift / 256.0)) * 0.00390625)); + //freq = (pow(1.059463094359, (f64) ((note / 256.0) - (tone->c_Shift / 256.0)))) * 40; + //freq = (note / float(tone->c_Shift / 256.0)); + //freq = tone->c_Shift == 0 ? 1 : freq; + + //freq = (float(note) / float(tone->c_Center)); + //freq = (float(note) / float(tone->c_Shift / 256.0)); + //freq = tone->c_Shift == 0 ? 1 : freq; + freq = float(note) / (float(pow(tone->c_Center, 2) / (pow(tone->c_Shift, 2)))); + freq = float(note) / float(2 * (tone->c_Shift + (tone->c_Center << 7))); + + float noteFreq = float(pow(2.0, float(note) / 12.0)); + float sampleRootFreq = float(pow(2.0, float(tone->c_Center + (tone->c_Shift / 127.0)) / 12.0)); + freq = noteFreq / sampleRootFreq * 2.0f; + //freq = note / float(float(tone->c_Center) + float(tone->c_Shift / 127.0)); + //freq = note / float(float(tone->c_Center) + float(tone->c_Shift / 127.0)); + + //freq = freq + 0.5; + //freq = float(note) / float(float(tone->c_Center)); + + ALfloat pan; + pan = voice->f_Pan == 0 ? voice->m_Tone->f_Pan : voice->f_Pan; + alSource3f(alSource[a], AL_POSITION, pan, 0, -sqrtf(1.0f - pan * pan)); + alSourcef(alSource[a], AL_PITCH, (ALfloat) freq); + alSourcef(alSource[a], AL_GAIN, (ALfloat) voice->f_Velocity * masterVolMulti); + alSourcei(alSource[a], AL_BUFFER, alSampleSet[pos]); + if (tone->mode != 0) + alSource3i(alSource[a], AL_AUXILIARY_SEND_FILTER, (ALint) slot, 0, AL_FILTER_NULL); + else + alSource3i(alSource[a], AL_AUXILIARY_SEND_FILTER, AL_EFFECTSLOT_NULL, 0, NULL); + + if ((error = alGetError()) != AL_NO_ERROR) + { + return; + } + + alSourcePlay(alSource[a]); + AliveAudio::currentSampleIndex++; } } } } -void AliveAudio::NoteOn(int program, int note, char velocity, int trackID , float trackDelay) +void AliveAudio::NoteOn(int program, int note, char velocity, int trackID , float trackDelay, float masterValMulti) { - NoteOn(program, note, velocity, 0, trackID, trackDelay); + NoteOn(program, note, velocity, 0, trackID, trackDelay, masterValMulti); } // sets the volume and pan for all voices on a given track. @@ -234,6 +615,9 @@ void CleanVoices() void AliveRenderAudio(float* AudioStream, int StreamLength) { + AudioStream; + StreamLength; + static float tick = 0; static int note = 0; diff --git a/Source/AliveLibCommon/audio/mixer/AliveAudio.hpp b/Source/AliveLibCommon/audio/mixer/AliveAudio.hpp index 628c6beae..bbf9dac0d 100644 --- a/Source/AliveLibCommon/audio/mixer/AliveAudio.hpp +++ b/Source/AliveLibCommon/audio/mixer/AliveAudio.hpp @@ -29,8 +29,8 @@ class AliveAudio static void PlayOneShot(s32 playId, s32 program, s32 note, s32 volume, float pitch, s32 pitch_min, s32 pitch_max); static void PlayOneShot(s32 playId, s32 program, s32 note, s32 volLeft, s32 volRight, float pitch, s32 pitch_min, s32 pitch_max); - static void NoteOn(int program, int note, char velocity, float pitch = 0, int trackID = 0, float trackDelay = 0); - static void NoteOn(int program, int note, char velocity, int trackID = 0, float trackDelay = 0); + static void NoteOn(int program, int note, char velocity, float pitch = 0, int trackID = 0, float trackDelay = 0, float masterVolMulti = 1); + static void NoteOn(int program, int note, char velocity, int trackID = 0, float trackDelay = 0, float masterValMulti = 1); static void NoteOff(int program, int note, int trackID = 0); static void NoteOffDelay(int program, int note, int trackID = 0, float trackDelay = 0); diff --git a/Source/AliveLibCommon/openal-soft b/Source/AliveLibCommon/openal-soft new file mode 160000 index 000000000..7e00d4a00 --- /dev/null +++ b/Source/AliveLibCommon/openal-soft @@ -0,0 +1 @@ +Subproject commit 7e00d4a00243e417ff59d3be001e1180ff55b72b From da53611849286fb640e031277b51f3a5d31a6fbb Mon Sep 17 00:00:00 2001 From: Sean Horton <3359434+sean-horton@users.noreply.github.com> Date: Sat, 4 Mar 2023 10:14:46 -0500 Subject: [PATCH 17/82] sequencer checkpoint - trying something new --- Source/AliveLibCommon/audio/Sequencer.cpp | 1065 +++++++++++++++++++++ Source/AliveLibCommon/audio/Sequencer.hpp | 320 +++++++ 2 files changed, 1385 insertions(+) create mode 100644 Source/AliveLibCommon/audio/Sequencer.cpp create mode 100644 Source/AliveLibCommon/audio/Sequencer.hpp diff --git a/Source/AliveLibCommon/audio/Sequencer.cpp b/Source/AliveLibCommon/audio/Sequencer.cpp new file mode 100644 index 000000000..5d81616cd --- /dev/null +++ b/Source/AliveLibCommon/audio/Sequencer.cpp @@ -0,0 +1,1065 @@ +#pragma once + +#include "Sequencer.hpp" +#include + +namespace sean { + +///////////////////////////////// +/// DuckStation +u32 mask(u32 num) +{ + u32 res = 0; + while (num-- > 0) + { + res = (res << 1) | 1; + } + return res; +} + +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(); + +//struct Voice +//{ +// u16 current_address; +// VoiceRegisters regs; +// VoiceCounter counter; +// ADPCMFlags current_block_flags; +// bool is_first_block; +// std::array current_block_samples; +// std::array adpcm_last_samples; +// s32 last_volume; +// +// VolumeSweep left_volume; +// VolumeSweep right_volume; +// +// VolumeEnvelope adsr_envelope; +// ADSRPhase adsr_phase; +// s16 adsr_target; +// bool has_samples; +// bool ignore_loop_address; +// +// bool IsOn() const +// { +// return adsr_phase != ADSRPhase::Off; +// } +// +// void KeyOn(); +// void KeyOff(); +// void ForceOff(); +// +// void DecodeBlock(const ADPCMBlock& block); +// s32 Interpolate() const; +// +// // Switches to the specified phase, filling in target. +// void UpdateADSREnvelope(); +// +// // Updates the ADSR volume/phase. +// void TickADSR(); +//}; +/// DuckStation END +///////////////////////////////// + +Sequencer::Sequencer() +{ + for (int i = 0; i < sourceCount; i++) + { + voices[i] = NULL; + patches[i] = NULL; + sourceLock[i] = false; + } + + // Open the AL device + ALenum error; + alGetError(); // clear out error state + device = alcOpenDevice(NULL); + ctx = alcCreateContext(device, NULL); + alcMakeContextCurrent(ctx); + if ((error = alGetError()) != AL_NO_ERROR) + { + return; + } + + // Prepare some known effects we will use + LOAD_EFFECT_FUNCTIONS(); + EFXEAXREVERBPROPERTIES reverb = EFX_REVERB_PRESET_AUDITORIUM; + efxReverb0 = PREPARE_REVERB(&reverb); + alGenAuxiliaryEffectSlots(1, &efxSlot0); + alAuxiliaryEffectSloti(efxSlot0, AL_EFFECTSLOT_EFFECT, (ALint) efxReverb0); + + // Prepare sources (these are streams buffers can be played in) + alGenSources(sourceCount, source); + if ((error = alGetError()) != AL_NO_ERROR) + { + return; + } +} + +Sequencer::~Sequencer() +{ + // Delete sources + alDeleteSources(sourceCount, source); + + // Delete effects + alDeleteAuxiliaryEffectSlots(1, &efxSlot0); + alDeleteEffects(1, &efxReverb0); + + // Close AL device + alcMakeContextCurrent(NULL); + alcDestroyContext(ctx); + alcCloseDevice(device); + + // Delete buffers (by killing the instrument) + for (Patch* patch : patches) + { + delete patch; + } +} + +u64 timeSinceEpochMillisec() +{ + using namespace std::chrono; + return duration_cast(system_clock::now().time_since_epoch()).count(); +} + + +////////////////////////// +// PRIVATE +void Sequencer::reset() +{ + alcSuspendContext(ctx); + + for (s16 i = 0; i < sourceCount; i++) + { + releaseSource(i); + releaseVoice(voices[i]); + } + + for (s16 i = 0; i < sourceCount; i++) + { + // must be deleted separately from sources + // since deleting a patch may delete a buffer + // attached to a source + delete patches[i]; + patches[i] = NULL; + } + + for (Sequence* seq : sequences) + { + delete seq; + } + sequences.clear(); + + alcProcessContext(ctx); +} + +void Sequencer::playSeq(s32 seqId) +{ + for (Sequence* seq : sequences) + { + if (seq->id == seqId) + { + seq->play = true; + } + } +} + +void Sequencer::stopSeq(s32 seqId) +{ + for (Sequence* seq : sequences) + { + if (seq->id == seqId) + { + seq->play = false; + for (Voice* v : voices) + { + if (v && v->sequence == seq) + { + //v->forceStopNow = true; + } + } + } + } +} + + +void Sequencer::tick() +{ + // pause openal changes to perform bulk update - more performant. + // unpause is called at the end. + alcSuspendContext(ctx); + + ADSRTableEntry e = s_adsr_table[0][0]; + ADSRTableEntries table = s_adsr_table; + + u64 now = timeSinceEpochMillisec(); + + // Tick sequences + for (Sequence* seq : sequences) + { + if (!seq || !seq->play) + { + continue; + } + + //if (seq->repeats >= seq->repeatLimit) + //{ + // stopSeq(seq->id); + // continue; + //} + + MIDIMessage* message; + while ((message = seq->next(now)) != NULL) + { + switch (message->type) + { + case NOTE_ON: + { + if (!seq->channels[message->channelId]->patch) + { + break; + } + + Voice* v = obtainVoice(); + if (v) + { + v->sequence = seq; + v->note = message->note; + v->noteVolume = message->velocity; + v->patch = seq->channels[message->channelId]->patch; + } + break; + } + case NOTE_OFF: + { + for (Voice* v : voices) + { + if (v && v->sequence == seq && v->note == 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 starts at position 1 again + break; + } + } + } + + // Tick voices + ALenum error; + int vCount = 0; + for (int c = 0; c < sourceCount; c++) + { + + Voice* voice = voices[c]; + if (!voice) + { + continue; + } + vCount++; + // we are just starting to play this note + if (voice->onTime == 0 && !voice->forceStopNow) + { + if (!voice->patch) + { + continue; + } + + voice->onTime = now; + for (Sample* sample : voice->patch->samples) + { + if (!sample) + { + continue; + } + + if (voice->note > sample->maxNote || voice->note < sample->minNote) + { + continue; + } + + s16 id = obtainSource(); + if (id < 0) + { + continue; + } + + alSourcei(source[id], AL_BUFFER, sample->alBuffer); + if ((error = alGetError()) != AL_NO_ERROR) + { + continue; + } + + alSourcePlay(source[id]); + if ((error = alGetError()) != AL_NO_ERROR) + { + continue; + } + + voice->sources.push_back(id); + voice->samples.push_back(sample); + voice->loop = sample->attack / 1000 > 1; + } + } + + // Update the play source paramters + int playCount = 0; + ALenum state; + for (int i = 0; i < (int) voice->sources.size(); i++) + { + Sample* sample = voice->samples.at(i); + s16 id = voice->sources.at(i); + + if (id < 0) + { + continue; + } + + // If we are past the duration of the notes playback + alGetSourcei(source[id], AL_SOURCE_STATE, &state); + if (state != AL_PLAYING + || voice->forceStopNow + //|| (voice->offTime != 0 && now > voice->offTime + sample->release) + //|| (voice->offTime != 0 && sample->release > 60000 && voice->loop) + ) + { + releaseSource(id); + voice->samples.at(i) = NULL; + voice->sources.at(i) = -1; + continue; + } + + u8 note = voice->note; + s32 notePitch = voice->pitch < voice->pitchMin ? voice->pitchMin : voice->pitch; // or sample? + u8 rootNote = sample->rootNote; + u8 rootPitch = sample->rootNotePitchShift; + // This figures out the frequency of midi notes (ex. 60 is middle C and 261.63 hz) + // noteFreq is the note we want to play + // rootFreq is the samples root note + // We then divide the two to figure out how to pitch shift the sample to match the + // the desired note. For some reason we have to multiply it by 2. don't know why. + float noteFreq = float(pow(2.0, float(note + (notePitch / 127.0f)) / 12.0f)); + float rootFreq = float(pow(2.0, float(rootNote + (rootPitch / 127.0f)) / 12.0f)); + float freq = noteFreq / rootFreq * 2.0f; + float pan = voice->pan; + + if (sample->pan) + { + pan = sample->pan; + } + + float volume = voice->velocity; + Sequence* seq = voice->sequence; + //if (seq) + //{ + // volume = seq->volume * volume; + //} + + + float attackMulti = 1; + if (sample->attack > 1000 && now < voice->onTime + sample->attack) + { + float offset = float(voice->onTime + sample->attack - now); + attackMulti = offset / float(sample->attack); + attackMulti = 1 - std::min(1.0f, std::max(0.0f, attackMulti)); + } + + float decayMulti = 1; + if (sample->decay != 0 && now < voice->onTime + sample->attack + sample->decay) + { + decayMulti; + } + + float releaseMulti = 1; + if (voice->offTime != 0 && now >= voice->offTime) + { + releaseMulti = 1 - (float(now - voice->offTime) / float(sample->release)); + //releaseMulti = exp(-5 * releaseMulti); + //std::cout << releaseMulti < 0) + // return current_level; + // + //const ADSRTableEntry& table_entry = s_adsr_table[BoolToUInt8(decreasing)][rate]; + //s32 this_step = table_entry.step; + //counter = table_entry.ticks; + // + //if (exponential) + //{ + // if (decreasing) + // { + // this_step = (this_step * current_level) >> 15; + // } + // else + // { + // if (current_level >= 0x6000) + // { + // if (rate < 40) + // { + // this_step >>= 2; + // } + // else if (rate >= 44) + // { + // counter >>= 2; + // } + // else + // { + // this_step >>= 1; + // counter >>= 1; + // } + // } + // } + //} + // + //return static_cast( + // std::clamp(static_cast(current_level) + this_step, ENVELOPE_MIN_VOLUME, ENVELOPE_MAX_VOLUME)); + // ENVELOPE_MIN_VOLUME = 0 and ENVELOPE_MAX_VOLUME = 0x7FFF (32767) + + + + + //void SPU::VolumeEnvelope::Reset(u8 rate_, bool decreasing_, bool exponential_) + //{ + // rate = rate_; + // decreasing = decreasing_; + // exponential = exponential_; + // + // const ADSRTableEntry& table_entry = s_adsr_table[BoolToUInt8(decreasing)][rate]; + // counter = table_entry.ticks; + //} + + + + //if (exponential) + //{ + // if (decreasing) + // { + // this_step = (this_step * current_level) >> 15; + // } + // else + // { + // if (current_level >= 0x6000) + // { + // if (rate < 40) + // { + // this_step >>= 2; + // } + // else if (rate >= 44) + // { + // counter >>= 2; + // } + // else + // { + // this_step >>= 1; + // counter >>= 1; + // } + // } + // } + //} + + // According to duckstation + // MASTER_CLOCK = 44100 * 0x300 // 33868800Hz or 33.8688MHz, also used as CPU clock + // SYSCLK_TICKS_PER_SPU_TICK = System::MASTER_CLOCK / SAMPLE_RATE, // 0x300 + + // 768 sys clock ticks per spu tick - sys clock tick is + // ----- so 768 spu ticks per second ----- + + + alSource3f(source[id], AL_POSITION, pan, 0, -sqrtf(1.0f - pan * pan)); + alSourcef(source[id], AL_PITCH, (ALfloat) freq); + alSourcef(source[id], AL_GAIN, (ALfloat) volume); + alSourcei(source[id], AL_LOOPING, voice->loop); + + // 0 Off + // 1 Vibrate + // 2 Portamento + // 3 1 & 2(Portamento and Vibrate on) + // 4 Reverb + if (sample->reverb != 0) + { + alSource3i(source[id], AL_AUXILIARY_SEND_FILTER, (ALint) efxSlot0, 0, AL_FILTER_NULL); + } + else + { + alSource3i(source[id], AL_AUXILIARY_SEND_FILTER, AL_EFFECTSLOT_NULL, 0, NULL); + } + + playCount++; + if (!seq) + { + continue; + } + + + u64 runtimeUs = std::max((u64) 0, now - voice->onTime) * 1000; // x1000 to conver to microseconds + float midiTickUs = seq->tempoUs / seq->ticksPerBeat; + u64 voiceTick = u64(runtimeUs / midiTickUs); + ADSRTableEntry entryAttack = s_adsr_table[false][sample->adsr.attackRate]; + ADSRTableEntry entryDecay = s_adsr_table[true][sample->adsr.decayRate << 2]; + ADSRTableEntry entrySustain = s_adsr_table[sample->adsr.sustainDirection][sample->adsr.sustainRate]; + ADSRTableEntry entryRelease = s_adsr_table[true][sample->adsr.releaseRate << 2]; + entryAttack; + entryDecay; + entrySustain; + entryRelease; + voiceTick; + + // sample voice seq + + float currentLevel = volume * 32767; + if (voice->lastProcessedTick == 0) + { + voice->lastProcessedTick = voiceTick; + voice->lastProcessedMs = voice->onTime; + voice->velocity = 0.0f; + currentLevel = 0.0f; + volume = 0.0f; + } + + + // std::clamp(static_cast(current_level) + this_step, ENVELOPE_MIN_VOLUME, ENVELOPE_MAX_VOLUME)); + // ENVELOPE_MIN_VOLUME = 0 and ENVELOPE_MAX_VOLUME = 0x7FFF (32767) + //if (voiceTick <= entryAttack.ticks) + //if (now <= voice->onTime + sample->attack) + //{ + + //if (sample->attack > 1000 && now < voice->onTime + sample->attack) + //{ + // float offset = float(voice->onTime + sample->attack - now); + // attackMulti = offset / float(sample->attack); + // attackMulti = 1 - std::min(1.0f, std::max(0.0f, attackMulti)); + // currentLevel = currentLevel * attackMulti + currentLevel; + //} + + //currentLevel = volume * 32767 + 1000; + + + //volume = volume * 0.5f; + // in attack + /* volume = volume * 32767.0f + float(entryAttack.step) / 32767.0f; + volume = volume + 0.01f;*/ + + + // NOTE: Default is 60 ticks per second? + //float ttt = 44100.0f / 4096.0f; + while (voice->offTime == 0 && voice->lastProcessedTick++ <= voiceTick + u64(entryAttack.ticks)) + //while (voice->lastProcessedMs++ <= (voice->onTime) + (entryAttack.ticks * 0.2441f)) + //while (voice->lastProcessedMs <= voice->onTime + (entryAttack.ticks * ttt)) + //float tmp = float(now - voice->lastProcessedMs) / 60; + //float cccccc = 0.0f; + //while (voice->offTime == 0 && cccccc++ <= tmp) + //if (voice->offTime == 0 ) + //s64 start = voice->lastProcessedMs * 1000; + //s64 end = s64(voice->onTime * 1000) + s64(s64(entryAttack.ticks) * s64(768)); + //while (voice->offTime == 0 && start <= end) + { + //start = start + 24; + //voice->lastProcessedMs = voice->lastProcessedMs + u64(midiTickUs / 1000.0f); + if (sample->adsr.attackExponential) + { + voice->counter--; + if (voice->counter <= 0) + { + s32 this_step = entryAttack.step; + voice->counter = entryAttack.ticks; + + if (currentLevel >= 0x6000) + { + if (sample->adsr.attackRate < 40) + { + this_step >>= 2; + } + else if (sample->adsr.attackRate >= 44) + { + voice->counter >>= 2; + } + else + { + this_step >>= 1; + voice->counter >>= 1; + } + } + + currentLevel = currentLevel + this_step; + } + } + else + { + currentLevel = currentLevel + entryAttack.step; + } + } + //voice->lastProcessedMs = start / 1000; + volume = currentLevel / 32767; + volume = std::min(volume, sample->volume * seq->volume * voice->noteVolume); + + if (voice->offTime == 0 && voiceTick <= u64(entryAttack.ticks) + u64(entryDecay.ticks)) + { + // in decay + //volume = volume * 0.75f; + //volume = volume * 32767.0f + float(entryDecay.step) / 32767.0f; + //volume = volume - 0.01f; + //volume = float(sample->adsr.sustainLevel / 127.0f * sample->volume * seq->volume); + } + else if (voice->offTime == 0 && voice->lastProcessedTick > voiceTick + u64(entryAttack.ticks) + u64(entryDecay.ticks)) + { + // in sustain + //volume = volume * 0.5f; + //volume = 1.0f; + volume = float((sample->adsr.sustainLevel / 127.0f) * sample->volume * seq->volume * voice->noteVolume); + } + else if (voice->offTime != 0) + { + // in release + //volume = volume * 0.1f; + //this_step = (this_step * current_level) >> 15; + if (sample->adsr.releaseExponential) + { + //while (voice->lastProcessedMs++ * 1000 < now * 1000) + //while (dur-- > 0) + //while (voice->lastProcessedMs <= now) + while (voice->lastProcessedTick++ <= voiceTick) + { + //voice->lastProcessedMs = voice->lastProcessedMs + u64(ttt); + s32 this_step = entryRelease.step; + this_step = s32(float(this_step) * currentLevel) >> 15; + currentLevel = currentLevel + this_step; + } + } + else + { + currentLevel = currentLevel + entryRelease.step; + } + + volume = currentLevel / 32767; + + //volume = volume * 32767 + this_step / 32767; + + + //volume = volume * 32767.0f + float(entryRelease.step) / 32767.0f; + //volume = volume - 0.01f; + } + volume = std::max(0.0f, std::min(1.0f, volume)); // clamp + volume = std::min(volume, sample->volume * seq->volume); + voice->velocity = volume; + voice->lastProcessedTick = voiceTick; + voice->lastProcessedMs = now; + if (volume < 0.00001f) + { + playCount--; + } + alSourcef(source[id], AL_GAIN, (ALfloat) volume); + + + //case ADSRPhase::Attack: + // adsr_target = 32767; // 0 -> max + // adsr_envelope.Reset(regs.adsr.attack_rate, false, regs.adsr.attack_exponential); + // break; + // + //case ADSRPhase::Decay: + // adsr_target = static_cast(std::min((u32(regs.adsr.sustain_level.GetValue()) + 1) * 0x800, + // ENVELOPE_MAX_VOLUME)); // max -> sustain level + // adsr_envelope.Reset(regs.adsr.decay_rate_shr2 << 2, true, true); + // break; + // + //case ADSRPhase::Sustain: + // adsr_target = 0; + // adsr_envelope.Reset(regs.adsr.sustain_rate, regs.adsr.sustain_direction_decrease, regs.adsr.sustain_exponential); + // break; + // + //case ADSRPhase::Release: + // adsr_target = 0; + // adsr_envelope.Reset(regs.adsr.release_rate_shr2 << 2, true, regs.adsr.release_exponential); + } + + if (playCount == 0) + { + releaseVoice(voice); + } + } + //std::cout << vCount << std::endl; + + alcProcessContext(ctx); +} + +Voice* Sequencer::obtainVoice() +{ + //if (activeVoice >= 32) + //{ + // return NULL; + //} + for (int i = 0; i < sourceCount; i++) + { + if (!voices[i]) + { + //activeVoice++; + Voice* v = new Voice(); + v->uuid = nextUuid(); + voices[i] = v; + return v; + } + } + return NULL; +} + +void Sequencer::releaseVoice(Voice* v) +{ + if (!v) + { + return; + } + + for (int i = 0; i < sourceCount; i++) + { + if (v == voices[i]) + { + //activeVoice--; + delete v; + voices[i] = NULL; + return; + } + } +} + +s16 Sequencer::obtainSource() +{ + //if (activeSource >= 32) + //{ + // return -1; + //} + for (s16 i = 0; i < sourceCount; i++) + { + if (!sourceLock[i]) + { + //activeSource++; + sourceLock[i] = true; + return i; + } + } + return -1; +} + +void Sequencer::releaseSource(s16 id) +{ + if (!sourceLock[id]) + { + return; + } + //activeSource--; + + sourceLock[id] = false; + alSourceStop(source[id]); + alSourcei(source[id], AL_BUFFER, NULL); +} + +s32 Sequencer::nextUuid() +{ + uuid++; + if (uuid % 50000) + { + uuid = 1; + } + return uuid; +} + +////////////////////////// +// PUBLIC +Patch* Sequencer::createPatch(s16 id) +{ + if (patches[id]) + { + return patches[id]; + } + + patches[id] = new Patch(); + return patches[id]; +} + +Sequence* Sequencer::createSequence() +{ + Sequence* seq = new Sequence(); + sequences.push_back(seq); + return seq; +} + +Sequence* Sequencer::getSequence(s32 id) +{ + for (Sequence* seq : sequences) + { + if (seq->id == id) + { + return seq; + } + } + return NULL; +} + +s32 Sequencer::playNote(s32 patchId, u8 note, float velocity, float pan, u8 pitch, s32 pitchMin, s32 pitchMax) +{ + Patch* patch = patches[patchId]; + if (!patch) + { + return 0; + } + + Voice* v = obtainVoice(); + if (!v) + { + return 0; + } + + v->patch = patch; + v->note = note; + v->velocity = velocity; + v->pan = pan; + v->pitch = pitch; + v->pitchMin = pitchMin; + v->pitchMax = pitchMax; + return v->uuid; +} + + +////////////////////////// +// OTHERS +MIDIMessage* Sequence::createMIDIMessage() +{ + MIDIMessage* msg = new MIDIMessage(); + messages.push_back(msg); + return msg; +} + +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; +} + +u64 Patch::getAdsrReleaseTime(u8 note) +{ + u64 max = 0; + for (Sample* sample : samples) + { + if (note >= sample->minNote && note <= sample->maxNote) + { + max = std::max(max, (u64) sample->attack); + } + } + return max; +} + + +////////////////////////// +// OPENAL BS +ALuint PREPARE_REVERB(const EFXEAXREVERBPROPERTIES* reverb) +{ + ALuint effect = 0; + ALenum err; + + /* Create the effect object and check if we can do EAX reverb. */ + alGenEffects(1, &effect); + if (alGetEnumValue("AL_EFFECT_EAXREVERB") != 0) + { + printf("Using EAX Reverb\n"); + + /* EAX Reverb is available. Set the EAX effect type then load the + * reverb properties. */ + alEffecti(effect, AL_EFFECT_TYPE, AL_EFFECT_EAXREVERB); + alEffectf(effect, AL_EAXREVERB_DENSITY, reverb->flDensity); + alEffectf(effect, AL_EAXREVERB_DIFFUSION, reverb->flDiffusion); + alEffectf(effect, AL_EAXREVERB_GAIN, reverb->flGain); + alEffectf(effect, AL_EAXREVERB_GAINHF, reverb->flGainHF); + alEffectf(effect, AL_EAXREVERB_GAINLF, reverb->flGainLF); + alEffectf(effect, AL_EAXREVERB_DECAY_TIME, reverb->flDecayTime); + alEffectf(effect, AL_EAXREVERB_DECAY_HFRATIO, reverb->flDecayHFRatio); + alEffectf(effect, AL_EAXREVERB_DECAY_LFRATIO, reverb->flDecayLFRatio); + alEffectf(effect, AL_EAXREVERB_REFLECTIONS_GAIN, reverb->flReflectionsGain); + alEffectf(effect, AL_EAXREVERB_REFLECTIONS_DELAY, reverb->flReflectionsDelay); + alEffectfv(effect, AL_EAXREVERB_REFLECTIONS_PAN, reverb->flReflectionsPan); + alEffectf(effect, AL_EAXREVERB_LATE_REVERB_GAIN, reverb->flLateReverbGain); + alEffectf(effect, AL_EAXREVERB_LATE_REVERB_DELAY, reverb->flLateReverbDelay); + alEffectfv(effect, AL_EAXREVERB_LATE_REVERB_PAN, reverb->flLateReverbPan); + alEffectf(effect, AL_EAXREVERB_ECHO_TIME, reverb->flEchoTime); + alEffectf(effect, AL_EAXREVERB_ECHO_DEPTH, reverb->flEchoDepth); + alEffectf(effect, AL_EAXREVERB_MODULATION_TIME, reverb->flModulationTime); + alEffectf(effect, AL_EAXREVERB_MODULATION_DEPTH, reverb->flModulationDepth); + alEffectf(effect, AL_EAXREVERB_AIR_ABSORPTION_GAINHF, reverb->flAirAbsorptionGainHF); + alEffectf(effect, AL_EAXREVERB_HFREFERENCE, reverb->flHFReference); + alEffectf(effect, AL_EAXREVERB_LFREFERENCE, reverb->flLFReference); + alEffectf(effect, AL_EAXREVERB_ROOM_ROLLOFF_FACTOR, reverb->flRoomRolloffFactor); + alEffecti(effect, AL_EAXREVERB_DECAY_HFLIMIT, reverb->iDecayHFLimit); + } + else + { + printf("Using Standard Reverb\n"); + + /* No EAX Reverb. Set the standard reverb effect type then load the + * available reverb properties. */ + alEffecti(effect, AL_EFFECT_TYPE, AL_EFFECT_REVERB); + + alEffectf(effect, AL_REVERB_DENSITY, reverb->flDensity); + alEffectf(effect, AL_REVERB_DIFFUSION, reverb->flDiffusion); + alEffectf(effect, AL_REVERB_GAIN, reverb->flGain); + alEffectf(effect, AL_REVERB_GAINHF, reverb->flGainHF); + alEffectf(effect, AL_REVERB_DECAY_TIME, reverb->flDecayTime); + alEffectf(effect, AL_REVERB_DECAY_HFRATIO, reverb->flDecayHFRatio); + alEffectf(effect, AL_REVERB_REFLECTIONS_GAIN, reverb->flReflectionsGain); + alEffectf(effect, AL_REVERB_REFLECTIONS_DELAY, reverb->flReflectionsDelay); + alEffectf(effect, AL_REVERB_LATE_REVERB_GAIN, reverb->flLateReverbGain); + alEffectf(effect, AL_REVERB_LATE_REVERB_DELAY, reverb->flLateReverbDelay); + alEffectf(effect, AL_REVERB_AIR_ABSORPTION_GAINHF, reverb->flAirAbsorptionGainHF); + alEffectf(effect, AL_REVERB_ROOM_ROLLOFF_FACTOR, reverb->flRoomRolloffFactor); + alEffecti(effect, AL_REVERB_DECAY_HFLIMIT, reverb->iDecayHFLimit); + } + + /* Check if an error occured, and clean up if so. */ + err = alGetError(); + if (err != AL_NO_ERROR) + { + fprintf(stderr, "OpenAL error: %s\n", alGetString(err)); + if (alIsEffect(effect)) + alDeleteEffects(1, &effect); + return 0; + } + + return effect; +} + +void LOAD_EFFECT_FUNCTIONS() +{ +#if __STDC_VERSION__ >= 199901L + #define FUNCTION_CAST(T, ptr) (union \ + { \ + void* p; \ + T f; \ + }){ptr} \ + .f +#elif defined(__cplusplus) + #define FUNCTION_CAST(T, ptr) reinterpret_cast(ptr) +#else + #define FUNCTION_CAST(T, ptr) (T)(ptr) +#endif + +#define LOAD_PROC(T, x) ((x) = FUNCTION_CAST(T, alGetProcAddress(#x))) + LOAD_PROC(LPALGENEFFECTS, alGenEffects); + LOAD_PROC(LPALDELETEEFFECTS, alDeleteEffects); + LOAD_PROC(LPALISEFFECT, alIsEffect); + LOAD_PROC(LPALEFFECTI, alEffecti); + LOAD_PROC(LPALEFFECTIV, alEffectiv); + LOAD_PROC(LPALEFFECTF, alEffectf); + LOAD_PROC(LPALEFFECTFV, alEffectfv); + LOAD_PROC(LPALGETEFFECTI, alGetEffecti); + LOAD_PROC(LPALGETEFFECTIV, alGetEffectiv); + LOAD_PROC(LPALGETEFFECTF, alGetEffectf); + LOAD_PROC(LPALGETEFFECTFV, alGetEffectfv); + + LOAD_PROC(LPALFILTERF, alFilterf); + LOAD_PROC(LPALFILTERI, alFilteri); + + LOAD_PROC(LPALGENAUXILIARYEFFECTSLOTS, alGenAuxiliaryEffectSlots); + LOAD_PROC(LPALDELETEAUXILIARYEFFECTSLOTS, alDeleteAuxiliaryEffectSlots); + LOAD_PROC(LPALISAUXILIARYEFFECTSLOT, alIsAuxiliaryEffectSlot); + LOAD_PROC(LPALAUXILIARYEFFECTSLOTI, alAuxiliaryEffectSloti); + LOAD_PROC(LPALAUXILIARYEFFECTSLOTIV, alAuxiliaryEffectSlotiv); + LOAD_PROC(LPALAUXILIARYEFFECTSLOTF, alAuxiliaryEffectSlotf); + LOAD_PROC(LPALAUXILIARYEFFECTSLOTFV, alAuxiliaryEffectSlotfv); + LOAD_PROC(LPALGETAUXILIARYEFFECTSLOTI, alGetAuxiliaryEffectSloti); + LOAD_PROC(LPALGETAUXILIARYEFFECTSLOTIV, alGetAuxiliaryEffectSlotiv); + LOAD_PROC(LPALGETAUXILIARYEFFECTSLOTF, alGetAuxiliaryEffectSlotf); + LOAD_PROC(LPALGETAUXILIARYEFFECTSLOTFV, alGetAuxiliaryEffectSlotfv); +#undef LOAD_PROC +} + +} // namespace \ No newline at end of file diff --git a/Source/AliveLibCommon/audio/Sequencer.hpp b/Source/AliveLibCommon/audio/Sequencer.hpp new file mode 100644 index 000000000..164dfd053 --- /dev/null +++ b/Source/AliveLibCommon/audio/Sequencer.hpp @@ -0,0 +1,320 @@ +#pragma once + +#include +#include +#include +#include "AL/al.h" +#include "AL/alc.h" +#include "AL/alext.h" +#include "AL/efx.h" +#include "AL/efx-presets.h" +#include "BitField.hpp" + +namespace sean { + +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(ALvoid* buffer, ALsizei size) + { + buf = buffer; + bufSize = size; + alGenBuffers(1, &alBuffer); + alBufferData(alBuffer, AL_FORMAT_MONO16, buffer, size, 22050); // TODO - are sample rates different? + } + ~Sample() + { + alDeleteBuffers(1, &alBuffer); + } + + float volume; + float pan; + + // reverb style - 0 is none + s8 reverb; + + // Root Key + u8 rootNote; + u8 rootNotePitchShift; + + // Key range + u8 minNote; + u8 maxNote; + + ADSR adsr; + + // ADSR time in seconds + double attack; + double release; + double decay; + double sustain; // is this actually level? + + bool releaseExponential = false; + double sustainLevel; + + ALvoid* buf; + ALsizei bufSize; + ALuint alBuffer; +}; + + +/* +* 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() + { + for (int i = 0; i < 128; i++) + { + samples[i] = NULL; + } + } + ~Patch() + { + for (Sample* sample : samples) + { + delete sample; + } + } + + Sample* samples[128]; + + u64 getAdsrReleaseTime(u8 note); +}; + + +enum MIDIMessageType +{ + NOTE_ON, + NOTE_OFF, + PATCH_CHANGE, + END_TRACK +}; + + +/* +* 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; +}; + + +struct Channel +{ + // channels may have volums and pans too + Patch* patch; +}; + +/* +* 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; + s32 repeatLimit = 1; + bool play = false; + + float volume = 1; + float pan = 0; + float tempoUs; // BPM - defined in in microseconds (us) + float ticksPerBeat; + + s32 repeats = 0; + + MIDIMessage* createMIDIMessage(); + MIDIMessage* next(u64 now); + Channel* channels[16]; + +private: + std::vector messages; + + +private: + u32 actionPos = 0; + u64 trackStartTime = 0; +}; + +enum AttackPhase +{ + NONE, + ATTACK, + DECAY, + SUSTAIN, + RELEASE +}; + +class Voice +{ +public: + s32 uuid; + Sequence* sequence = NULL; + Patch* patch; + u8 note; + u8 pitch; + s32 pitchMin = 0; + s32 pitchMax = 127; + float velocity = 1.0f; + float pan = 0.0f; + float noteVolume = 1.0f; + + bool loop = false; + u64 onTime = 0; // when the note was pressed + u64 offTime = 0; // when the note was released + bool forceStopNow = false; + u64 lastProcessedTick = 0; + u64 lastProcessedMs = 0; + + AttackPhase phase = NONE; + s32 counter = 0; // decremented each midi tick + u8 rate; + bool decreasing; + bool exponential; + + std::vector sources; + std::vector samples; +}; + +/* +* Can play MIDI +*/ +class Sequencer +{ +public: + Sequencer(); + ~Sequencer(); + + void reset(); + + Patch* createPatch(s16 id); + + Sequence* createSequence(); + Sequence* getSequence(s32 id); + + s32 playNote(s32 patchId, u8 note, float velocity, float pan, u8 pitch, s32 pitchMin, s32 pitchMax); + + void playSeq(s32 seqId); + void stopSeq(s32 seqId); + + void tick(); + +private: + + s32 uuid = 1; + s32 nextUuid(); + + Voice* obtainVoice(); + void releaseVoice(Voice* v); + + s16 obtainSource(); + void releaseSource(s16 id); + + ALCdevice* device; + ALCcontext* ctx; + + std::vector sequences; + + static const int sourceCount = 128; + //int activeSource = 0; + //int activeVoice = 0; + Voice* voices[sourceCount]; + bool sourceLock[sourceCount]; + ALuint source[sourceCount]; + Patch* patches[sourceCount]; + + ALuint efxReverb0; + ALuint efxSlot0; +}; + + +/* +* BELOW IS ALL OPENAL EFFECTS LOADING +* Not sure where else to put this... +*/ +/* Define a macro to help load the function pointers. + https://github.com/kcat/openal-soft/blob/master/examples/alreverb.c */ +/* Effect object functions */ +static LPALGENEFFECTS alGenEffects; +static LPALDELETEEFFECTS alDeleteEffects; +static LPALISEFFECT alIsEffect; +static LPALEFFECTI alEffecti; +static LPALEFFECTIV alEffectiv; +static LPALEFFECTF alEffectf; +static LPALEFFECTFV alEffectfv; +static LPALGETEFFECTI alGetEffecti; +static LPALGETEFFECTIV alGetEffectiv; +static LPALGETEFFECTF alGetEffectf; +static LPALGETEFFECTFV alGetEffectfv; +static LPALFILTERF alFilterf; +static LPALFILTERI alFilteri; + +/* Auxiliary Effect Slot object functions */ +static LPALGENAUXILIARYEFFECTSLOTS alGenAuxiliaryEffectSlots; +static LPALDELETEAUXILIARYEFFECTSLOTS alDeleteAuxiliaryEffectSlots; +static LPALISAUXILIARYEFFECTSLOT alIsAuxiliaryEffectSlot; +static LPALAUXILIARYEFFECTSLOTI alAuxiliaryEffectSloti; +static LPALAUXILIARYEFFECTSLOTIV alAuxiliaryEffectSlotiv; +static LPALAUXILIARYEFFECTSLOTF alAuxiliaryEffectSlotf; +static LPALAUXILIARYEFFECTSLOTFV alAuxiliaryEffectSlotfv; +static LPALGETAUXILIARYEFFECTSLOTI alGetAuxiliaryEffectSloti; +static LPALGETAUXILIARYEFFECTSLOTIV alGetAuxiliaryEffectSlotiv; +static LPALGETAUXILIARYEFFECTSLOTF alGetAuxiliaryEffectSlotf; +static LPALGETAUXILIARYEFFECTSLOTFV alGetAuxiliaryEffectSlotfv; + +ALuint PREPARE_REVERB(const EFXEAXREVERBPROPERTIES* reverb); +void LOAD_EFFECT_FUNCTIONS(); + +} // namespace \ No newline at end of file From 12cd95b4dccb1ed4bba4e15c7fe1d6bed8c66bd0 Mon Sep 17 00:00:00 2001 From: Sean Horton <3359434+sean-horton@users.noreply.github.com> Date: Sat, 4 Mar 2023 12:46:00 -0500 Subject: [PATCH 18/82] another checkpoint - ticks are 44100hz... --- Source/AliveLibCommon/audio/MidiPlayer.cpp | 622 ++++++++++++++++----- Source/AliveLibCommon/audio/MidiPlayer.hpp | 10 + Source/AliveLibCommon/audio/Sequencer.cpp | 383 +++---------- Source/AliveLibCommon/audio/Sequencer.hpp | 9 +- 4 files changed, 581 insertions(+), 443 deletions(-) diff --git a/Source/AliveLibCommon/audio/MidiPlayer.cpp b/Source/AliveLibCommon/audio/MidiPlayer.cpp index 51f543d7d..b83ed3b96 100644 --- a/Source/AliveLibCommon/audio/MidiPlayer.cpp +++ b/Source/AliveLibCommon/audio/MidiPlayer.cpp @@ -5,6 +5,7 @@ #include "mixer/ADSR.hpp" #include "mixer/AliveAudio.hpp" #include "SequencePlayer.hpp" +#include "mixer/psxadsr.hpp" namespace psx { @@ -65,20 +66,77 @@ MidiPlayer::MidiPlayer(ResourceProvider* provider, SoundSampleParser* samplePars } } +u64 timeSinceEpochMillisec() +{ + using namespace std::chrono; + return duration_cast(system_clock::now().time_since_epoch()).count(); +} + +void MidiPlayer::loop() +{ + // expected ticks = float(now - start) / 1000.0f * 768.0f + + u64 start = timeSinceEpochMillisec(); + u64 now = 0; + u64 ticks = 0; + u64 expectedTicks = 0; + while (running) + { + mutex.lock(); + now = timeSinceEpochMillisec(); + expectedTicks = u64(float(now - start) / 1000.0f * 44100); + while (ticks++ < expectedTicks) + { + sequencer->tick(expectedTicks - ticks); + } + //ticks = expectedTicks; + mutex.unlock(); + std::this_thread::sleep_for(std::chrono::milliseconds(30)); // SPU ticks every 1302.08333333 microseconds - SPU = 768 ticks per second + + //std::this_thread::sleep_for(std::chrono::microseconds(1302)); // SPU ticks every 1302.08333333 microseconds - SPU = 768 ticks per second + // According to duckstation + // MASTER_CLOCK = 44100 * 0x300 // 33868800Hz or 33.8688MHz, also used as CPU clock + // SYSCLK_TICKS_PER_SPU_TICK = System::MASTER_CLOCK / SAMPLE_RATE, // 0x300 + + // 768 sys clock ticks per spu tick - sys clock tick is + // ----- so 768 spu ticks per second ----- + + } +} + void MidiPlayer::SND_Init() { - AliveInitAudio(); + mutex.lock(); + if (sequencer) + { + delete sequencer; + } + sequencer = new sean::Sequencer(); + mutex.unlock(); + + running = true; + thread = new std::thread(&MidiPlayer::loop, this); + + //// Create sequencer + //AliveInitAudio(); } void MidiPlayer::SND_Shutdown() { + running = false; + thread->join(); + delete thread; + + delete sequencer; } void MidiPlayer::SND_Load_VABS(SoundBlockInfo* pSoundBlockInfo, s32 reverb) { + mutex.lock(); reverb; // TODO - reverb - this seems to be the only spot + sequencer->reset(); while (1) { if (!pSoundBlockInfo->header_name) @@ -105,67 +163,94 @@ void MidiPlayer::SND_Load_VABS(SoundBlockInfo* pSoundBlockInfo, s32 reverb) for (s32 i = 0; i < vabHeader->field_12_num_progs; i++) { - // PROGRAM - Program* program = new Program; - programs.push_back(program); - for (s32 toneCounter = 0; toneCounter < 16; toneCounter++) + // PATCH (Instruments) + for (s32 x = 0; x < 16; x++) { - // TONE - - Tone* tone = new Tone(); - program->m_Tones.push_back(tone); - + // SAMPLE if (vagAttr->field_2_vol > 0) { - program->prog_id = vagAttr->field_14_prog; - tone->mode = vagAttr->field_1_mode; - tone->f_Volume = vagAttr->field_2_vol / 127.0f; - tone->c_Center = vagAttr->field_4_centre; - //tone->c_Shift = (vagAttr->field_5_shift | (vagAttr->field_4_centre << 7)); - - tone->c_Shift = vagAttr->field_5_shift; - tone->f_Pan = (vagAttr->field_3_pan / 64.0f) - 1.0f; - tone->Min = vagAttr->field_6_min; - tone->Max = vagAttr->field_7_max; - tone->Pitch = vagAttr->field_5_shift / 100.0f; - tone->m_Sample = samples.at(vagAttr->field_16_vag - 1); - unsigned short ADSR1 = vagAttr->field_10_adsr1; unsigned short ADSR2 = vagAttr->field_12_adsr2; - REAL_ADSR realADSR = {}; PSXConvADSR(&realADSR, ADSR1, ADSR2, false); - tone->AttackTime = realADSR.attack_time; - tone->DecayTime = realADSR.decay_time; - tone->ReleaseTime = realADSR.release_time; - tone->SustainTime = realADSR.sustain_time; - - f32 sustain_level = static_cast((2 * (~(u8) vagAttr->field_10_adsr1 & 0xF))); - tone->AttackTime = std::min(static_cast((powf(2.0f, ((vagAttr->field_10_adsr1 >> 8) & 0x7F) * 0.25f) * 0.09f)), static_cast(32767)); - tone->DecayTime = static_cast((((vagAttr->field_10_adsr1 >> 4) & 0xF) / 15.0f) * 16.0); - tone->SustainTime = std::min(static_cast((sustain_level / 15.0f) * 600.0), static_cast(32767)); - //tone->ReleaseTime = std::min(static_cast(pow(2, vagAttr->field_12_adsr2 & 0x1F) * 0.045f), static_cast(32767)); - - if (realADSR.attack_time > 1) - { // This works until the loop database is added. - tone->Loop = true; - } + sean::ADSR adsr = sean::parseADSR(ADSR1, ADSR2); + //sean::REAL_ADSR adsr; + //sean::PSXConvADSR(&adsr, ADSR1, ADSR2, false); + + + sean::Patch* patch = sequencer->createPatch(vagAttr->field_14_prog); + Sample* s = samples.at(vagAttr->field_16_vag - 1); + sean::Sample* t = new sean::Sample(s->m_SampleBuffer, s->i_SampleSize * 2); + patch->samples[x] = t; + + u8 a = (u8) (vagAttr->field_10_adsr1 >> 8); + u8 d = (u8) vagAttr->field_10_adsr1; + a; + d; + + t->adsr = adsr; + t->volume = vagAttr->field_2_vol / 127.0f; + t->pan = (vagAttr->field_3_pan / 64.0f) - 1.0f; + t->reverb = vagAttr->field_1_mode; + t->rootNote = vagAttr->field_4_centre; + t->rootNotePitchShift = vagAttr->field_5_shift; + t->minNote = vagAttr->field_6_min; + t->maxNote = vagAttr->field_7_max; + t->attack = realADSR.attack_time * 1000; + t->release = realADSR.release_time * 1000; + t->decay = realADSR.decay_time * 1000; + t->sustain = realADSR.sustain_time * 1000; + t->releaseExponential = false; + t->sustainLevel = realADSR.sustain_level; + + + //program->prog_id = vagAttr->field_14_prog; + //tone->mode = vagAttr->field_1_mode; + //tone->f_Volume = vagAttr->field_2_vol / 127.0f; + //tone->c_Center = vagAttr->field_4_centre; + ////tone->c_Shift = (vagAttr->field_5_shift | (vagAttr->field_4_centre << 7)); + //tone->c_Shift = vagAttr->field_5_shift; + //tone->f_Pan = (vagAttr->field_3_pan / 64.0f) - 1.0f; + //tone->Min = vagAttr->field_6_min; + //tone->Max = vagAttr->field_7_max; + //tone->Pitch = vagAttr->field_5_shift / 100.0f; + //tone->m_Sample = s; + //tone->sample = sampleBuffers.at(vagAttr->field_16_vag - 1); + ////alGenBuffers(1, &tone->sample); + ////alBufferData(tone->sample, AL_FORMAT_MONO16, tone->m_Sample->m_SampleBuffer, tone->m_Sample->i_SampleSize * 2, 22050); + //// These are time in seconds + //tone->AttackTime = realADSR.attack_time; + //tone->DecayTime = realADSR.decay_time; + //tone->ReleaseTime = realADSR.release_time; + //tone->SustainTime = realADSR.sustain_time; + //tone->SustainLevel = realADSR.sustain_level; + //tone->ReleaseExponential = false; // TODO - where does this come from? + ////f32 sustain_level = static_cast((2 * (~(u8) vagAttr->field_10_adsr1 & 0xF))); + ////tone->AttackTime = std::min(static_cast((powf(2.0f, ((vagAttr->field_10_adsr1 >> 8) & 0x7F) * 0.25f) * 0.09f)), static_cast(32767)); + ////tone->DecayTime = static_cast((((vagAttr->field_10_adsr1 >> 4) & 0xF) / 15.0f) * 16.0); + ////tone->SustainTime = std::min(static_cast((sustain_level / 15.0f) * 600.0), static_cast(32767)); + ////tone->ReleaseTime = std::min(static_cast(pow(2, vagAttr->field_12_adsr2 & 0x1F) * 0.045f), static_cast(32767)); + //if (realADSR.attack_time > 1) + //{ // This works until the loop database is added. + // tone->Loop = true; + //} } ++vagAttr; } } - AliveAudio::LockNotes(); - delete mSoundbank; - mSoundbank = new Soundbank(samples, programs); - AliveAudio::ClearAllVoices(); - AliveAudio::m_CurrentSoundbank = mSoundbank; - AliveAudio::UnlockNotes(); + //AliveAudio::LockNotes(); + //delete mSoundbank; + //mSoundbank = new Soundbank(samples, programs); + //AliveAudio::ClearAllVoices(); + //AliveAudio::m_CurrentSoundbank = mSoundbank; + //AliveAudio::UnlockNotes(); delete[] ppVabBody; pSoundBlockInfo++; } + mutex.unlock(); } void MidiPlayer::SND_Load_Seqs(OpenSeqHandle* pSeqTable, const char_type* bsqFileName) @@ -175,7 +260,7 @@ void MidiPlayer::SND_Load_Seqs(OpenSeqHandle* pSeqTable, const char_type* bsqFil return; } - mSequences.clear(); + mutex.lock(); OpenSeqHandle* seq = pSeqTable; for (s32 i = 0; i < mResourceProvider->sequenceCount(); i++) // AO = 164 AE = 144 { @@ -194,6 +279,8 @@ void MidiPlayer::SND_Load_Seqs(OpenSeqHandle* pSeqTable, const char_type* bsqFil } // SEQUENCE STREAM + sean::Sequence* sequence = sequencer->createSequence(); + parseMidiStream(sequence, vec, i); mSequences.push_back(vec); } else @@ -203,16 +290,18 @@ void MidiPlayer::SND_Load_Seqs(OpenSeqHandle* pSeqTable, const char_type* bsqFil mSequences.push_back(test); } } + + mutex.unlock(); } void MidiPlayer::SND_StopAll() { - for (SequencePlayer* player : mSequencePlayers) - { - player->StopSequence(); - delete player; - } - mSequencePlayers.clear(); + //for (SequencePlayer* player : mSequencePlayers) + //{ + // player->StopSequence(); + // delete player; + //} + //mSequencePlayers.clear(); //AliveAudio::LockNotes(); //AliveAudio::ClearAllVoices(); @@ -231,22 +320,27 @@ void MidiPlayer::SND_Stop_Channels_Mask(u32 bitMask) { bitMask; std::cout << "bitmask " << bitMask << "\n"; - AliveAudio::LockNotes(); - AliveAudio::ClearAllTrackVoices(bitMask, true); - AliveAudio::UnlockNotes(); - ReleaseId(bitMask); + //AliveAudio::LockNotes(); + //AliveAudio::ClearAllTrackVoices(bitMask, true); + //AliveAudio::UnlockNotes(); + //ReleaseId(bitMask); } void MidiPlayer::SND_SEQ_Stop(u16 idx) { - SequencePlayer* player = GetSequencePlayer(idx); - if (player) - { - player->StopSequence(); - RemoveSequencePlayer(player); - ReleaseId(player->m_PlayId); - delete player; - } + idx; + + mutex.lock(); + sequencer->stopSeq(idx); + mutex.unlock(); + //SequencePlayer* player = GetSequencePlayer(idx); + //if (player) + //{ + // player->StopSequence(); + // RemoveSequencePlayer(player); + // ReleaseId(player->m_PlayId); + // delete player; + //} } s8 MidiPlayer::SND_Seq_Table_Valid() @@ -260,26 +354,30 @@ s16 MidiPlayer::SND_SEQ_PlaySeq(u16 idx, s32 repeatCount, s16 bDontStop) repeatCount; idx; - SequencePlayer* player = GetSequencePlayer(idx); - - // When chanting starts bDontStop is 1 - // and then 0 is called every frame until chanting stops. - // I think we can return if it's 0 - if (player && bDontStop == 0) - { - return 1; // still playing - } - - if (!player) - { - player = new SequencePlayer(); - mSequencePlayers.push_back(player); - } - std::cout << "Play seq " << idx << "\n"; - - player->LoadSequenceData(mSequences.at(s16(idx)), s32(idx), repeatCount); - player->PlaySequence(idx); - return s16(mSequencePlayers.size() - 1); + mutex.lock(); + sequencer->playSeq(idx); + mutex.unlock(); + return 1; + //SequencePlayer* player = GetSequencePlayer(idx); + + //// When chanting starts bDontStop is 1 + //// and then 0 is called every frame until chanting stops. + //// I think we can return if it's 0 + //if (player && bDontStop == 0) + //{ + // return 1; // still playing + //} + + //if (!player) + //{ + // player = new SequencePlayer(); + // mSequencePlayers.push_back(player); + //} + //std::cout << "Play seq " << idx << "\n"; + + //player->LoadSequenceData(mSequences.at(s16(idx)), s32(idx), repeatCount); + //player->PlaySequence(idx); + //return s16(mSequencePlayers.size() - 1); } void MidiPlayer::sanitizeVolume(s32* src, s32 low, s32 high) @@ -314,11 +412,20 @@ void MidiPlayer::SND_SEQ_SetVol(s32 idx, s32 volLeft, s32 volRight) volRight; sanitizeVolume(&volLeft, 10, 127); sanitizeVolume(&volRight, 10, 127); - SequencePlayer* player = GetSequencePlayer(u16(idx)); - if (player) + + mutex.lock(); + sean::Sequence* seq = sequencer->getSequence((s32) idx); + if (seq) { - player->SetVolume(volLeft, volRight); + seq->volume = std::min(volLeft, volRight) / 127.0f; } + mutex.unlock(); + + //SequencePlayer* player = GetSequencePlayer(u16(idx)); + //if (player) + //{ + // player->SetVolume(volLeft, volRight); + //} } s16 MidiPlayer::SND_SEQ_Play(u16 idx, s32 repeatCount, s16 volLeft, s16 volRight) @@ -326,36 +433,58 @@ s16 MidiPlayer::SND_SEQ_Play(u16 idx, s32 repeatCount, s16 volLeft, s16 volRight repeatCount; // TODO volLeft; volRight; + idx; - SequencePlayer* player = GetSequencePlayer(idx); - if (player) + mutex.lock(); + sean::Sequence* seq = sequencer->getSequence((s32) idx); + if (seq) { - return 0; + seq->volume = std::min(volLeft, volRight) / 127.0f; } - std::cout << "Play seq " << idx << "\n"; - - player = new SequencePlayer(); - player->LoadSequenceData(mSequences.at(s16(idx)), s32(idx), repeatCount); - player->PlaySequence(idx); - mSequencePlayers.push_back(player); - return s16(mSequencePlayers.size() - 1); + sequencer->playSeq(idx); + mutex.unlock(); + return 0; + + //SequencePlayer* player = GetSequencePlayer(idx); + //if (player) + //{ + // return 0; + //} + //std::cout << "Play seq " << idx << "\n"; + + //player = new SequencePlayer(); + //player->LoadSequenceData(mSequences.at(s16(idx)), s32(idx), repeatCount); + //player->PlaySequence(idx); + //player->SetVolume(volLeft, volRight); + //mSequencePlayers.push_back(player); + //return s16(mSequencePlayers.size() - 1); } s16 MidiPlayer::SND_SsIsEos_DeInlined(u16 idx) { idx; // TODO - SequencePlayer* player = GetSequencePlayer(idx); - if (!player) + //SequencePlayer* player = GetSequencePlayer(idx); + //if (!player) + //{ + // return 0; + //} + //if (player->mRepeatCount) + //{ + // // 1 means we're still playing + // return player->completedRepeats() < player->mRepeatCount ? 1 : 0; + //} + + s16 res = 0; + mutex.lock(); + sean::Sequence* seq = sequencer->getSequence((s32) idx); + if (seq) { - return 0; + res = seq->repeats < seq->repeatLimit ? 1 : 0; } - if (player->mRepeatCount) - { - // 1 means we're still playing - return player->completedRepeats() < player->mRepeatCount ? 1 : 0; - } - return 1; + mutex.unlock(); + + return res; } s32 MidiPlayer::SFX_SfxDefinition_Play(SfxDefinition* sfxDef, s32 volLeft, s32 volRight, s32 pitch_min, s32 pitch_max) @@ -365,13 +494,14 @@ s32 MidiPlayer::SFX_SfxDefinition_Play(SfxDefinition* sfxDef, s32 volLeft, s32 v sanitizeVolume(&volLeft, 10, 127); sanitizeVolume(&volRight, 10, 127); - - // AliveAudio::PlayOneShot(sfxDef->program, sfxDef->note, volLeft, 0); - AliveAudio::LockNotes(); - int playId = NextId(); - AliveAudio::PlayOneShot(playId, sfxDef->program, sfxDef->note, volLeft, volRight, 0, pitch_min, pitch_max); - AliveAudio::UnlockNotes(); - return playId; + std::cout << volLeft << " " << volRight << std::endl; + mutex.lock(); + // TODO - I don't think these pans and volumes are quite right + float volume = std::max(volLeft, volRight) / 127.0f; + float pan = (float(volRight) / float(volLeft)) - 1; + s32 id = sequencer->playNote(sfxDef->program, sfxDef->note, volume, pan, (u8)std::max(pitch_min, pitch_max), pitch_min, pitch_max); + mutex.unlock(); + return id; } s32 MidiPlayer::SFX_SfxDefinition_Play(SfxDefinition* sfxDef, s32 volume, s32 pitch_min, s32 pitch_max) @@ -383,15 +513,12 @@ s32 MidiPlayer::SFX_SfxDefinition_Play(SfxDefinition* sfxDef, s32 volume, s32 pi sanitizePitch(&pitch_min, sfxDef->pitch_min); sanitizePitch(&pitch_max, sfxDef->pitch_max); - sanitizeVolume(&volume, 1, 127); - //AliveAudio::PlayOneShot(sfxDef->program, sfxDef->note, volume, 0); - AliveAudio::LockNotes(); - int playId = NextId(); - AliveAudio::PlayOneShot(playId, sfxDef->program, sfxDef->note, volume, volume, 0, pitch_min, pitch_max); - AliveAudio::UnlockNotes(); - return playId; + mutex.lock(); + s32 id = sequencer->playNote(sfxDef->program, sfxDef->note, volume / 127.0f, 0, (u8) std::max(pitch_min, pitch_max), pitch_min, pitch_max); + mutex.unlock(); + return id; } s32 MidiPlayer::SND(s32 program, s32 vabId, s32 note, s16 vol, s16 min, s16 max) @@ -402,11 +529,12 @@ s32 MidiPlayer::SND(s32 program, s32 vabId, s32 note, s16 vol, s16 min, s16 max) vol; min; max; - AliveAudio::LockNotes(); - int playId = NextId(); - AliveAudio::NoteOn(program, note, char(vol), 0); - AliveAudio::UnlockNotes(); - return playId; + return 0; + //AliveAudio::LockNotes(); + //int playId = NextId(); + //AliveAudio::NoteOn(program, note, char(vol), 0); + //AliveAudio::UnlockNotes(); + //return playId; } void MidiPlayer::SsUtAllKeyOff(s32 mode) @@ -463,4 +591,248 @@ void MidiPlayer::ReleaseId(s32 id) idBank[id - 255] = 0; } + + +// 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; +} + +void parseMidiStream(sean::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 prevDeltaTime = 0; + unsigned int deltaTime = 0; + + const size_t midiDataStart = stream.Pos(); + + // 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: + { + sean::MIDIMessage* msg = seq->createMIDIMessage(); + msg->type = sean::END_TRACK; + msg->tick = ticksPerBeat; + 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); + + sean::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 = sean::NOTE_OFF; + } + else + { + prevDeltaTime = deltaTime; + msg->type = sean::NOTE_ON; + } + } + break; + case 0x8: // Note Off + { + Uint8 note = 0; + stream.ReadUInt8(note); + Uint8 velocity = 0; + stream.ReadUInt8(velocity); + + sean::MIDIMessage* msg = seq->createMIDIMessage(); + msg->type = sean::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); + //prevDeltaTime = deltaTime; + + sean::MIDIMessage* msg = seq->createMIDIMessage(); + msg->type = sean::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 + { + Uint16 bend = 0; + stream.ReadUInt16(bend); + } + break; + case 0xf: // Sysex len + { + const Uint32 length = _MidiReadVarLen(stream); + _SndMidiSkipLength(stream, length); + } + break; + default: + throw std::runtime_error("Unknown MIDI command"); + } + } + } +} + } // namespace psx \ No newline at end of file diff --git a/Source/AliveLibCommon/audio/MidiPlayer.hpp b/Source/AliveLibCommon/audio/MidiPlayer.hpp index 33a365723..81d18028a 100644 --- a/Source/AliveLibCommon/audio/MidiPlayer.hpp +++ b/Source/AliveLibCommon/audio/MidiPlayer.hpp @@ -2,9 +2,12 @@ #include "Soundbank.hpp" #include "SequencePlayer.hpp" +#include "Sequencer.hpp" namespace psx { + void parseMidiStream(sean::Sequence* seq, std::vector seqData, s32 trackId); + struct VagAtr final { s8 field_0_priority; @@ -202,9 +205,15 @@ namespace psx { void SsUtAllKeyOff(s32 mode); private: + std::thread* thread; + std::mutex mutex; + bool running; + void loop(); + ResourceProvider* mResourceProvider; SoundSampleParser* mSoundSampleParser; + sean::Sequencer* sequencer = NULL; Soundbank* mSoundbank = NULL; std::vector> mSequences; std::vector mSequencePlayers; @@ -220,4 +229,5 @@ namespace psx { void ReleaseId(s32 id); }; + } // namespace psx \ No newline at end of file diff --git a/Source/AliveLibCommon/audio/Sequencer.cpp b/Source/AliveLibCommon/audio/Sequencer.cpp index 5d81616cd..524c7fec6 100644 --- a/Source/AliveLibCommon/audio/Sequencer.cpp +++ b/Source/AliveLibCommon/audio/Sequencer.cpp @@ -240,7 +240,7 @@ void Sequencer::stopSeq(s32 seqId) } -void Sequencer::tick() +void Sequencer::tick(u64 ticks) { // pause openal changes to perform bulk update - more performant. // unpause is called at the end. @@ -321,6 +321,7 @@ void Sequencer::tick() continue; } vCount++; + // we are just starting to play this note if (voice->onTime == 0 && !voice->forceStopNow) { @@ -363,6 +364,11 @@ void Sequencer::tick() voice->sources.push_back(id); voice->samples.push_back(sample); voice->loop = sample->attack / 1000 > 1; + voice->decreasing = false; + voice->rate = sample->adsr.attackRate; + voice->exponential = sample->adsr.attackExponential; + voice->counter = s_adsr_table[voice->decreasing][voice->rate].ticks; + //voice->currentLevel = s16(sample->volume * 32767) / 2; // start at fixed_volume_shr1 } } @@ -374,18 +380,15 @@ void Sequencer::tick() Sample* sample = voice->samples.at(i); s16 id = voice->sources.at(i); + // we have no source, so kill the voice if (id < 0) { continue; } - // If we are past the duration of the notes playback + // If we are past the duration of the notes playback alGetSourcei(source[id], AL_SOURCE_STATE, &state); - if (state != AL_PLAYING - || voice->forceStopNow - //|| (voice->offTime != 0 && now > voice->offTime + sample->release) - //|| (voice->offTime != 0 && sample->release > 60000 && voice->loop) - ) + if (state != AL_PLAYING || voice->forceStopNow) { releaseSource(id); voice->samples.at(i) = NULL; @@ -393,15 +396,24 @@ void Sequencer::tick() continue; } + if (voice->offTime != 0 && voice->phase != RELEASE) + { + voice->phase = RELEASE; + voice->decreasing = true; + voice->rate = sample->adsr.releaseRate << 2; + voice->exponential = sample->adsr.releaseExponential; + voice->counter = s_adsr_table[voice->decreasing][voice->rate].ticks; + } + u8 note = voice->note; s32 notePitch = voice->pitch < voice->pitchMin ? voice->pitchMin : voice->pitch; // or sample? u8 rootNote = sample->rootNote; u8 rootPitch = sample->rootNotePitchShift; // This figures out the frequency of midi notes (ex. 60 is middle C and 261.63 hz) // noteFreq is the note we want to play - // rootFreq is the samples root note - // We then divide the two to figure out how to pitch shift the sample to match the - // the desired note. For some reason we have to multiply it by 2. don't know why. + // rootFreq is the samples root note + // We then divide the two to figure out how to pitch shift the sample to match the + // the desired note. For some reason we have to multiply it by 2. don't know why. float noteFreq = float(pow(2.0, float(note + (notePitch / 127.0f)) / 12.0f)); float rootFreq = float(pow(2.0, float(rootNote + (rootPitch / 127.0f)) / 12.0f)); float freq = noteFreq / rootFreq * 2.0f; @@ -412,137 +424,17 @@ void Sequencer::tick() pan = sample->pan; } - float volume = voice->velocity; - Sequence* seq = voice->sequence; - //if (seq) - //{ - // volume = seq->volume * volume; - //} - - - float attackMulti = 1; - if (sample->attack > 1000 && now < voice->onTime + sample->attack) - { - float offset = float(voice->onTime + sample->attack - now); - attackMulti = offset / float(sample->attack); - attackMulti = 1 - std::min(1.0f, std::max(0.0f, attackMulti)); - } - - float decayMulti = 1; - if (sample->decay != 0 && now < voice->onTime + sample->attack + sample->decay) - { - decayMulti; - } - - float releaseMulti = 1; - if (voice->offTime != 0 && now >= voice->offTime) - { - releaseMulti = 1 - (float(now - voice->offTime) / float(sample->release)); - //releaseMulti = exp(-5 * releaseMulti); - //std::cout << releaseMulti < 0) - // return current_level; - // - //const ADSRTableEntry& table_entry = s_adsr_table[BoolToUInt8(decreasing)][rate]; - //s32 this_step = table_entry.step; - //counter = table_entry.ticks; - // - //if (exponential) - //{ - // if (decreasing) - // { - // this_step = (this_step * current_level) >> 15; - // } - // else - // { - // if (current_level >= 0x6000) - // { - // if (rate < 40) - // { - // this_step >>= 2; - // } - // else if (rate >= 44) - // { - // counter >>= 2; - // } - // else - // { - // this_step >>= 1; - // counter >>= 1; - // } - // } - // } - //} - // - //return static_cast( - // std::clamp(static_cast(current_level) + this_step, ENVELOPE_MIN_VOLUME, ENVELOPE_MAX_VOLUME)); - // ENVELOPE_MIN_VOLUME = 0 and ENVELOPE_MAX_VOLUME = 0x7FFF (32767) - - - - - //void SPU::VolumeEnvelope::Reset(u8 rate_, bool decreasing_, bool exponential_) - //{ - // rate = rate_; - // decreasing = decreasing_; - // exponential = exponential_; - // - // const ADSRTableEntry& table_entry = s_adsr_table[BoolToUInt8(decreasing)][rate]; - // counter = table_entry.ticks; - //} - - - - //if (exponential) - //{ - // if (decreasing) - // { - // this_step = (this_step * current_level) >> 15; - // } - // else - // { - // if (current_level >= 0x6000) - // { - // if (rate < 40) - // { - // this_step >>= 2; - // } - // else if (rate >= 44) - // { - // counter >>= 2; - // } - // else - // { - // this_step >>= 1; - // counter >>= 1; - // } - // } - // } - //} - - // According to duckstation - // MASTER_CLOCK = 44100 * 0x300 // 33868800Hz or 33.8688MHz, also used as CPU clock - // SYSCLK_TICKS_PER_SPU_TICK = System::MASTER_CLOCK / SAMPLE_RATE, // 0x300 - - // 768 sys clock ticks per spu tick - sys clock tick is - // ----- so 768 spu ticks per second ----- - - + voice->currentLevel = voice->tick(ticks); + //std::cout << voice->currentLevel << std::endl; alSource3f(source[id], AL_POSITION, pan, 0, -sqrtf(1.0f - pan * pan)); alSourcef(source[id], AL_PITCH, (ALfloat) freq); - alSourcef(source[id], AL_GAIN, (ALfloat) volume); + alSourcef(source[id], AL_GAIN, (ALfloat) float(voice->currentLevel) / 32767.0f); alSourcei(source[id], AL_LOOPING, voice->loop); // 0 Off - // 1 Vibrate - // 2 Portamento - // 3 1 & 2(Portamento and Vibrate on) + // 1 Vibrate + // 2 Portamento + // 3 1 & 2(Portamento and Vibrate on) // 4 Reverb if (sample->reverb != 0) { @@ -552,202 +444,63 @@ void Sequencer::tick() { alSource3i(source[id], AL_AUXILIARY_SEND_FILTER, AL_EFFECTSLOT_NULL, 0, NULL); } - + playCount++; - if (!seq) - { - continue; - } + } - - u64 runtimeUs = std::max((u64) 0, now - voice->onTime) * 1000; // x1000 to conver to microseconds - float midiTickUs = seq->tempoUs / seq->ticksPerBeat; - u64 voiceTick = u64(runtimeUs / midiTickUs); - ADSRTableEntry entryAttack = s_adsr_table[false][sample->adsr.attackRate]; - ADSRTableEntry entryDecay = s_adsr_table[true][sample->adsr.decayRate << 2]; - ADSRTableEntry entrySustain = s_adsr_table[sample->adsr.sustainDirection][sample->adsr.sustainRate]; - ADSRTableEntry entryRelease = s_adsr_table[true][sample->adsr.releaseRate << 2]; - entryAttack; - entryDecay; - entrySustain; - entryRelease; - voiceTick; - - // sample voice seq - - float currentLevel = volume * 32767; - if (voice->lastProcessedTick == 0) - { - voice->lastProcessedTick = voiceTick; - voice->lastProcessedMs = voice->onTime; - voice->velocity = 0.0f; - currentLevel = 0.0f; - volume = 0.0f; - } + if (playCount == 0) + { + releaseVoice(voice); + } + } + //std::cout << vCount << std::endl; + alcProcessContext(ctx); +} - // std::clamp(static_cast(current_level) + this_step, ENVELOPE_MIN_VOLUME, ENVELOPE_MAX_VOLUME)); - // ENVELOPE_MIN_VOLUME = 0 and ENVELOPE_MAX_VOLUME = 0x7FFF (32767) - //if (voiceTick <= entryAttack.ticks) - //if (now <= voice->onTime + sample->attack) - //{ - - //if (sample->attack > 1000 && now < voice->onTime + sample->attack) - //{ - // float offset = float(voice->onTime + sample->attack - now); - // attackMulti = offset / float(sample->attack); - // attackMulti = 1 - std::min(1.0f, std::max(0.0f, attackMulti)); - // currentLevel = currentLevel * attackMulti + currentLevel; - //} - - //currentLevel = volume * 32767 + 1000; - - - //volume = volume * 0.5f; - // in attack - /* volume = volume * 32767.0f + float(entryAttack.step) / 32767.0f; - volume = volume + 0.01f;*/ - - - // NOTE: Default is 60 ticks per second? - //float ttt = 44100.0f / 4096.0f; - while (voice->offTime == 0 && voice->lastProcessedTick++ <= voiceTick + u64(entryAttack.ticks)) - //while (voice->lastProcessedMs++ <= (voice->onTime) + (entryAttack.ticks * 0.2441f)) - //while (voice->lastProcessedMs <= voice->onTime + (entryAttack.ticks * ttt)) - //float tmp = float(now - voice->lastProcessedMs) / 60; - //float cccccc = 0.0f; - //while (voice->offTime == 0 && cccccc++ <= tmp) - //if (voice->offTime == 0 ) - //s64 start = voice->lastProcessedMs * 1000; - //s64 end = s64(voice->onTime * 1000) + s64(s64(entryAttack.ticks) * s64(768)); - //while (voice->offTime == 0 && start <= end) - { - //start = start + 24; - //voice->lastProcessedMs = voice->lastProcessedMs + u64(midiTickUs / 1000.0f); - if (sample->adsr.attackExponential) - { - voice->counter--; - if (voice->counter <= 0) - { - s32 this_step = entryAttack.step; - voice->counter = entryAttack.ticks; +s16 Voice::tick(u64 ticks) +{ + ticks; - if (currentLevel >= 0x6000) - { - if (sample->adsr.attackRate < 40) - { - this_step >>= 2; - } - else if (sample->adsr.attackRate >= 44) - { - voice->counter >>= 2; - } - else - { - this_step >>= 1; - voice->counter >>= 1; - } - } + counter--; + if (counter > 0) + { + return currentLevel; + } - currentLevel = currentLevel + this_step; - } - } - else - { - currentLevel = currentLevel + entryAttack.step; - } - } - //voice->lastProcessedMs = start / 1000; - volume = currentLevel / 32767; - volume = std::min(volume, sample->volume * seq->volume * voice->noteVolume); + const ADSRTableEntry& table_entry = s_adsr_table[decreasing][rate]; + s32 this_step = table_entry.step; + counter = table_entry.ticks; - if (voice->offTime == 0 && voiceTick <= u64(entryAttack.ticks) + u64(entryDecay.ticks)) - { - // in decay - //volume = volume * 0.75f; - //volume = volume * 32767.0f + float(entryDecay.step) / 32767.0f; - //volume = volume - 0.01f; - //volume = float(sample->adsr.sustainLevel / 127.0f * sample->volume * seq->volume); - } - else if (voice->offTime == 0 && voice->lastProcessedTick > voiceTick + u64(entryAttack.ticks) + u64(entryDecay.ticks)) - { - // in sustain - //volume = volume * 0.5f; - //volume = 1.0f; - volume = float((sample->adsr.sustainLevel / 127.0f) * sample->volume * seq->volume * voice->noteVolume); - } - else if (voice->offTime != 0) + if (exponential) + { + if (decreasing) + { + this_step = (this_step * currentLevel) >> 15; + } + else + { + if (currentLevel >= 0x6000) { - // in release - //volume = volume * 0.1f; - //this_step = (this_step * current_level) >> 15; - if (sample->adsr.releaseExponential) + if (rate < 40) { - //while (voice->lastProcessedMs++ * 1000 < now * 1000) - //while (dur-- > 0) - //while (voice->lastProcessedMs <= now) - while (voice->lastProcessedTick++ <= voiceTick) - { - //voice->lastProcessedMs = voice->lastProcessedMs + u64(ttt); - s32 this_step = entryRelease.step; - this_step = s32(float(this_step) * currentLevel) >> 15; - currentLevel = currentLevel + this_step; - } + this_step >>= 2; + } + else if (rate >= 44) + { + counter >>= 2; } else { - currentLevel = currentLevel + entryRelease.step; + this_step >>= 1; + counter >>= 1; } - - volume = currentLevel / 32767; - - //volume = volume * 32767 + this_step / 32767; - - - //volume = volume * 32767.0f + float(entryRelease.step) / 32767.0f; - //volume = volume - 0.01f; } - volume = std::max(0.0f, std::min(1.0f, volume)); // clamp - volume = std::min(volume, sample->volume * seq->volume); - voice->velocity = volume; - voice->lastProcessedTick = voiceTick; - voice->lastProcessedMs = now; - if (volume < 0.00001f) - { - playCount--; - } - alSourcef(source[id], AL_GAIN, (ALfloat) volume); - - - //case ADSRPhase::Attack: - // adsr_target = 32767; // 0 -> max - // adsr_envelope.Reset(regs.adsr.attack_rate, false, regs.adsr.attack_exponential); - // break; - // - //case ADSRPhase::Decay: - // adsr_target = static_cast(std::min((u32(regs.adsr.sustain_level.GetValue()) + 1) * 0x800, - // ENVELOPE_MAX_VOLUME)); // max -> sustain level - // adsr_envelope.Reset(regs.adsr.decay_rate_shr2 << 2, true, true); - // break; - // - //case ADSRPhase::Sustain: - // adsr_target = 0; - // adsr_envelope.Reset(regs.adsr.sustain_rate, regs.adsr.sustain_direction_decrease, regs.adsr.sustain_exponential); - // break; - // - //case ADSRPhase::Release: - // adsr_target = 0; - // adsr_envelope.Reset(regs.adsr.release_rate_shr2 << 2, true, regs.adsr.release_exponential); - } - - if (playCount == 0) - { - releaseVoice(voice); } } - //std::cout << vCount << std::endl; - alcProcessContext(ctx); + return static_cast( + std::clamp(static_cast(currentLevel) + this_step, 0, 32767)); } Voice* Sequencer::obtainVoice() diff --git a/Source/AliveLibCommon/audio/Sequencer.hpp b/Source/AliveLibCommon/audio/Sequencer.hpp index 164dfd053..663f4d031 100644 --- a/Source/AliveLibCommon/audio/Sequencer.hpp +++ b/Source/AliveLibCommon/audio/Sequencer.hpp @@ -210,6 +210,7 @@ class Voice float velocity = 1.0f; float pan = 0.0f; float noteVolume = 1.0f; + s16 currentLevel = 0; bool loop = false; u64 onTime = 0; // when the note was pressed @@ -218,14 +219,16 @@ class Voice u64 lastProcessedTick = 0; u64 lastProcessedMs = 0; - AttackPhase phase = NONE; + AttackPhase phase = ATTACK; s32 counter = 0; // decremented each midi tick - u8 rate; + u32 rate; bool decreasing; bool exponential; std::vector sources; std::vector samples; + + s16 tick(u64 ticks); }; /* @@ -249,7 +252,7 @@ class Sequencer void playSeq(s32 seqId); void stopSeq(s32 seqId); - void tick(); + void tick(u64 ticks); private: From fb0c2caffc7d9fc2f0b6eb880bbd500b4550503c Mon Sep 17 00:00:00 2001 From: Sean Horton <3359434+sean-horton@users.noreply.github.com> Date: Sat, 4 Mar 2023 16:18:35 -0500 Subject: [PATCH 19/82] playback performance --- Source/AliveLibCommon/audio/MidiPlayer.cpp | 35 +- Source/AliveLibCommon/audio/Sequencer.cpp | 399 +++++++++++---------- Source/AliveLibCommon/audio/Sequencer.hpp | 40 +-- 3 files changed, 246 insertions(+), 228 deletions(-) diff --git a/Source/AliveLibCommon/audio/MidiPlayer.cpp b/Source/AliveLibCommon/audio/MidiPlayer.cpp index b83ed3b96..91632532a 100644 --- a/Source/AliveLibCommon/audio/MidiPlayer.cpp +++ b/Source/AliveLibCommon/audio/MidiPlayer.cpp @@ -82,25 +82,26 @@ void MidiPlayer::loop() u64 expectedTicks = 0; while (running) { - mutex.lock(); now = timeSinceEpochMillisec(); expectedTicks = u64(float(now - start) / 1000.0f * 44100); + + // tick sequence - i.e. new notes to play/stop? + sequencer->tickSequence(); + while (ticks++ < expectedTicks) { - sequencer->tick(expectedTicks - ticks); + // tick voices - just math calculations. + // this is ticked 44100 times per second. Possibly this + // can be done with a fast math calculation instead of + // a loop, but that's for another time... + sequencer->tickVoice(); } - //ticks = expectedTicks; - mutex.unlock(); - std::this_thread::sleep_for(std::chrono::milliseconds(30)); // SPU ticks every 1302.08333333 microseconds - SPU = 768 ticks per second - - //std::this_thread::sleep_for(std::chrono::microseconds(1302)); // SPU ticks every 1302.08333333 microseconds - SPU = 768 ticks per second - // According to duckstation - // MASTER_CLOCK = 44100 * 0x300 // 33868800Hz or 33.8688MHz, also used as CPU clock - // SYSCLK_TICKS_PER_SPU_TICK = System::MASTER_CLOCK / SAMPLE_RATE, // 0x300 - - // 768 sys clock ticks per spu tick - sys clock tick is - // ----- so 768 spu ticks per second ----- - + + // sync voices with openal + sequencer->syncVoice(); + + // defer this thread some amount of time + std::this_thread::sleep_for(std::chrono::milliseconds(30)); } } @@ -197,12 +198,6 @@ void MidiPlayer::SND_Load_VABS(SoundBlockInfo* pSoundBlockInfo, s32 reverb) t->rootNotePitchShift = vagAttr->field_5_shift; t->minNote = vagAttr->field_6_min; t->maxNote = vagAttr->field_7_max; - t->attack = realADSR.attack_time * 1000; - t->release = realADSR.release_time * 1000; - t->decay = realADSR.decay_time * 1000; - t->sustain = realADSR.sustain_time * 1000; - t->releaseExponential = false; - t->sustainLevel = realADSR.sustain_level; //program->prog_id = vagAttr->field_14_prog; diff --git a/Source/AliveLibCommon/audio/Sequencer.cpp b/Source/AliveLibCommon/audio/Sequencer.cpp index 524c7fec6..914ac03bd 100644 --- a/Source/AliveLibCommon/audio/Sequencer.cpp +++ b/Source/AliveLibCommon/audio/Sequencer.cpp @@ -139,7 +139,7 @@ Sequencer::Sequencer() // Prepare some known effects we will use LOAD_EFFECT_FUNCTIONS(); - EFXEAXREVERBPROPERTIES reverb = EFX_REVERB_PRESET_AUDITORIUM; + EFXEAXREVERBPROPERTIES reverb = EFX_REVERB_PRESET_GENERIC; efxReverb0 = PREPARE_REVERB(&reverb); alGenAuxiliaryEffectSlots(1, &efxSlot0); alAuxiliaryEffectSloti(efxSlot0, AL_EFFECTSLOT_EFFECT, (ALint) efxReverb0); @@ -186,26 +186,26 @@ void Sequencer::reset() { alcSuspendContext(ctx); - for (s16 i = 0; i < sourceCount; i++) - { - releaseSource(i); - releaseVoice(voices[i]); - } + //for (s16 i = 0; i < sourceCount; i++) + //{ + // releaseSource(i); + // releaseVoice(voices[i]); + //} - for (s16 i = 0; i < sourceCount; i++) - { - // must be deleted separately from sources - // since deleting a patch may delete a buffer - // attached to a source - delete patches[i]; - patches[i] = NULL; - } + //for (s16 i = 0; i < sourceCount; i++) + //{ + // // must be deleted separately from sources + // // since deleting a patch may delete a buffer + // // attached to a source + // delete patches[i]; + // patches[i] = NULL; + //} - for (Sequence* seq : sequences) - { - delete seq; - } - sequences.clear(); + //for (Sequence* seq : sequences) + //{ + // delete seq; + //} + //sequences.clear(); alcProcessContext(ctx); } @@ -239,17 +239,10 @@ void Sequencer::stopSeq(s32 seqId) } } - -void Sequencer::tick(u64 ticks) +void Sequencer::tickSequence() { - // pause openal changes to perform bulk update - more performant. - // unpause is called at the end. - alcSuspendContext(ctx); - - ADSRTableEntry e = s_adsr_table[0][0]; - ADSRTableEntries table = s_adsr_table; - u64 now = timeSinceEpochMillisec(); + ALenum error; // Tick sequences for (Sequence* seq : sequences) @@ -258,7 +251,7 @@ void Sequencer::tick(u64 ticks) { continue; } - + //if (seq->repeats >= seq->repeatLimit) //{ // stopSeq(seq->id); @@ -277,14 +270,58 @@ void Sequencer::tick(u64 ticks) break; } - Voice* v = obtainVoice(); - if (v) + for (Sample* sample : seq->channels[message->channelId]->patch->samples) { + if (!sample) + { + continue; + } + + if (message->note > sample->maxNote || message->note < sample->minNote) + { + continue; + } + + Voice* v = obtainVoice(); + if (!v) + { + continue; + } + + s16 id = obtainSource(); + if (id < 0) + { + releaseVoice(v); + continue; + } + + alSourcei(source[id], AL_BUFFER, sample->alBuffer); + if ((error = alGetError()) != AL_NO_ERROR) + { + releaseVoice(v); + releaseSource(id); + continue; + } + + alSourcef(source[id], AL_GAIN, (ALfloat) 0.0f); + alSourcePlay(source[id]); + if ((error = alGetError()) != AL_NO_ERROR) + { + releaseVoice(v); + releaseSource(id); + continue; + } + v->sequence = seq; v->note = message->note; v->noteVolume = message->velocity; v->patch = seq->channels[message->channelId]->patch; + v->source = id; + v->sample = sample; + v->loop = sample->adsr.attackRate > 1; // attack is greater than sample length? sample->adsr.attackRate / 1000 > 1; + v->onTime = now; } + break; } case NOTE_OFF: @@ -308,160 +345,141 @@ void Sequencer::tick(u64 ticks) } } } +} - // Tick voices - ALenum error; - int vCount = 0; +void Sequencer::tickVoice() +{ for (int c = 0; c < sourceCount; c++) { - Voice* voice = voices[c]; if (!voice) { continue; } - vCount++; - // we are just starting to play this note - if (voice->onTime == 0 && !voice->forceStopNow) - { - if (!voice->patch) - { - continue; - } - - voice->onTime = now; - for (Sample* sample : voice->patch->samples) - { - if (!sample) - { - continue; - } - - if (voice->note > sample->maxNote || voice->note < sample->minNote) - { - continue; - } - - s16 id = obtainSource(); - if (id < 0) - { - continue; - } + voice->currentLevel = voice->tick(); + } +} - alSourcei(source[id], AL_BUFFER, sample->alBuffer); - if ((error = alGetError()) != AL_NO_ERROR) - { - continue; - } +void Sequencer::syncVoice() +{ + // pause openal changes to perform bulk update - more performant. + // unpause is called at the end. + alcSuspendContext(ctx); - alSourcePlay(source[id]); - if ((error = alGetError()) != AL_NO_ERROR) - { - continue; - } + for (int c = 0; c < sourceCount; c++) + { + Voice* voice = voices[c]; + if (!voice) + { + continue; + } - voice->sources.push_back(id); - voice->samples.push_back(sample); - voice->loop = sample->attack / 1000 > 1; - voice->decreasing = false; - voice->rate = sample->adsr.attackRate; - voice->exponential = sample->adsr.attackExponential; - voice->counter = s_adsr_table[voice->decreasing][voice->rate].ticks; - //voice->currentLevel = s16(sample->volume * 32767) / 2; // start at fixed_volume_shr1 - } + if (!voice->sample) + { + continue; } // Update the play source paramters - int playCount = 0; ALenum state; - for (int i = 0; i < (int) voice->sources.size(); i++) - { - Sample* sample = voice->samples.at(i); - s16 id = voice->sources.at(i); + Sample* sample = voice->sample; + s16 id = voice->source; - // we have no source, so kill the voice - if (id < 0) - { - continue; - } - - // If we are past the duration of the notes playback - alGetSourcei(source[id], AL_SOURCE_STATE, &state); - if (state != AL_PLAYING || voice->forceStopNow) - { - releaseSource(id); - voice->samples.at(i) = NULL; - voice->sources.at(i) = -1; - continue; - } - - if (voice->offTime != 0 && voice->phase != RELEASE) - { - voice->phase = RELEASE; - voice->decreasing = true; - voice->rate = sample->adsr.releaseRate << 2; - voice->exponential = sample->adsr.releaseExponential; - voice->counter = s_adsr_table[voice->decreasing][voice->rate].ticks; - } - - u8 note = voice->note; - s32 notePitch = voice->pitch < voice->pitchMin ? voice->pitchMin : voice->pitch; // or sample? - u8 rootNote = sample->rootNote; - u8 rootPitch = sample->rootNotePitchShift; - // This figures out the frequency of midi notes (ex. 60 is middle C and 261.63 hz) - // noteFreq is the note we want to play - // rootFreq is the samples root note - // We then divide the two to figure out how to pitch shift the sample to match the - // the desired note. For some reason we have to multiply it by 2. don't know why. - float noteFreq = float(pow(2.0, float(note + (notePitch / 127.0f)) / 12.0f)); - float rootFreq = float(pow(2.0, float(rootNote + (rootPitch / 127.0f)) / 12.0f)); - float freq = noteFreq / rootFreq * 2.0f; - float pan = voice->pan; - - if (sample->pan) - { - pan = sample->pan; - } - - voice->currentLevel = voice->tick(ticks); - //std::cout << voice->currentLevel << std::endl; - alSource3f(source[id], AL_POSITION, pan, 0, -sqrtf(1.0f - pan * pan)); - alSourcef(source[id], AL_PITCH, (ALfloat) freq); - alSourcef(source[id], AL_GAIN, (ALfloat) float(voice->currentLevel) / 32767.0f); - alSourcei(source[id], AL_LOOPING, voice->loop); - - // 0 Off - // 1 Vibrate - // 2 Portamento - // 3 1 & 2(Portamento and Vibrate on) - // 4 Reverb - if (sample->reverb != 0) - { - alSource3i(source[id], AL_AUXILIARY_SEND_FILTER, (ALint) efxSlot0, 0, AL_FILTER_NULL); - } - else - { - alSource3i(source[id], AL_AUXILIARY_SEND_FILTER, AL_EFFECTSLOT_NULL, 0, NULL); - } + // If we are past the duration of the notes playback + alGetSourcei(source[id], AL_SOURCE_STATE, &state); + if (state != AL_PLAYING || voice->forceStopNow) + { + releaseSource(id); + releaseVoice(voice); + continue; + } - playCount++; + u8 note = voice->note; + s32 notePitch = voice->pitch < voice->pitchMin ? voice->pitchMin : voice->pitch; // or sample? + u8 rootNote = sample->rootNote; + u8 rootPitch = sample->rootNotePitchShift; + // This figures out the frequency of midi notes (ex. 60 is middle C and 261.63 hz) + // noteFreq is the note we want to play + // rootFreq is the samples root note + // We then divide the two to figure out how to pitch shift the sample to match the + // the desired note. For some reason we have to multiply it by 2. don't know why. + float noteFreq = float(pow(2.0, float(note + (notePitch / 127.0f)) / 12.0f)); + float rootFreq = float(pow(2.0, float(rootNote + (rootPitch / 127.0f)) / 12.0f)); + float freq = noteFreq / rootFreq * 2.0f; + float pan = voice->pan; + + if (sample->pan) + { + pan = sample->pan; } - if (playCount == 0) + alSource3f(source[id], AL_POSITION, pan, 0, -sqrtf(1.0f - pan * pan)); + alSourcef(source[id], AL_PITCH, (ALfloat) freq); + alSourcef(source[id], AL_GAIN, (ALfloat) float(voice->currentLevel) / 32767.0f); + alSourcei(source[id], AL_LOOPING, voice->loop); + + // 0 Off + // 1 Vibrate + // 2 Portamento + // 3 1 & 2(Portamento and Vibrate on) + // 4 Reverb + if (sample->reverb != 0) { - releaseVoice(voice); + alSource3i(source[id], AL_AUXILIARY_SEND_FILTER, (ALint) efxSlot0, 0, AL_FILTER_NULL); + } + else + { + alSource3i(source[id], AL_AUXILIARY_SEND_FILTER, AL_EFFECTSLOT_NULL, 0, NULL); } } - //std::cout << vCount << std::endl; alcProcessContext(ctx); } -s16 Voice::tick(u64 ticks) + +s16 Voice::tick() { - ticks; + + // UPDATE ADSR STATE - this probably doesn't need to be done every tick? + if (phase == NONE) + { + phase = ATTACK; + decreasing = false; + rate = sample->adsr.attackRate; + exponential = sample->adsr.attackExponential; + targetLevel = MAX_VOLUME; + counter = s_adsr_table[decreasing][rate].ticks; + } + else if (phase == ATTACK && currentLevel >= targetLevel) + { + phase = DECAY; + decreasing = true; + rate = sample->adsr.decayRate << 2; + exponential = true; + targetLevel = static_cast(std::min((u32(sample->adsr.sustainLevel) + 1) * 0x800, MAX_VOLUME)); + counter = s_adsr_table[decreasing][rate].ticks; + } + else if (phase == DECAY && currentLevel <= targetLevel) + { + phase = SUSTAIN; + decreasing = sample->adsr.sustainDirection; + rate = sample->adsr.sustainRate; + exponential = sample->adsr.sustainExponential; + targetLevel = 0; + counter = s_adsr_table[decreasing][rate].ticks; + } + else if (offTime != 0 && phase != RELEASE) + { + phase = RELEASE; + decreasing = true; + rate = sample->adsr.releaseRate << 2; + exponential = sample->adsr.releaseExponential; + targetLevel = 0; + counter = s_adsr_table[decreasing][rate].ticks; + } + + // UPDATE TICK STATE counter--; if (counter > 0) { @@ -500,7 +518,7 @@ s16 Voice::tick(u64 ticks) } return static_cast( - std::clamp(static_cast(currentLevel) + this_step, 0, 32767)); + std::clamp(static_cast(currentLevel) + this_step, MIN_VOLUME, MAX_VOLUME)); } Voice* Sequencer::obtainVoice() @@ -623,20 +641,50 @@ s32 Sequencer::playNote(s32 patchId, u8 note, float velocity, float pan, u8 pitc return 0; } - Voice* v = obtainVoice(); - if (!v) + for (Sample* s : patch->samples) { - return 0; - } + if (!s) + { + continue; + } - v->patch = patch; - v->note = note; - v->velocity = velocity; - v->pan = pan; - v->pitch = pitch; - v->pitchMin = pitchMin; - v->pitchMax = pitchMax; - return v->uuid; + if (note > s->maxNote || note < s->minNote) + { + continue; + } + + Voice* v = obtainVoice(); + if (!v) + { + return 0; + } + + s16 id = obtainSource(); + if (id < 0) + { + releaseVoice(v); + return 0; + } + + v->patch = patch; + v->note = note; + v->velocity = velocity; + v->pan = pan; + v->pitch = pitch; + v->pitchMin = pitchMin; + v->pitchMax = pitchMax; + v->sample = s; + v->source = id; + v->currentLevel = s16(velocity * 32767); + v->decreasing = false; + v->rate = s->adsr.attackRate; + v->exponential = s->adsr.attackExponential; + v->counter = s_adsr_table[v->decreasing][v->rate].ticks; + v->onTime = timeSinceEpochMillisec(); + return v->uuid; + } + + return 0; } @@ -680,19 +728,6 @@ MIDIMessage* Sequence::next(u64 now) return NULL; } -u64 Patch::getAdsrReleaseTime(u8 note) -{ - u64 max = 0; - for (Sample* sample : samples) - { - if (note >= sample->minNote && note <= sample->maxNote) - { - max = std::max(max, (u64) sample->attack); - } - } - return max; -} - ////////////////////////// // OPENAL BS diff --git a/Source/AliveLibCommon/audio/Sequencer.hpp b/Source/AliveLibCommon/audio/Sequencer.hpp index 663f4d031..90f2a5210 100644 --- a/Source/AliveLibCommon/audio/Sequencer.hpp +++ b/Source/AliveLibCommon/audio/Sequencer.hpp @@ -12,6 +12,9 @@ namespace sean { +const s16 MIN_VOLUME = 0; +const s16 MAX_VOLUME = 32767; + struct ASDR { // adsr1 @@ -38,8 +41,6 @@ class Sample public: Sample(ALvoid* buffer, ALsizei size) { - buf = buffer; - bufSize = size; alGenBuffers(1, &alBuffer); alBufferData(alBuffer, AL_FORMAT_MONO16, buffer, size, 22050); // TODO - are sample rates different? } @@ -64,17 +65,6 @@ class Sample ADSR adsr; - // ADSR time in seconds - double attack; - double release; - double decay; - double sustain; // is this actually level? - - bool releaseExponential = false; - double sustainLevel; - - ALvoid* buf; - ALsizei bufSize; ALuint alBuffer; }; @@ -103,8 +93,6 @@ class Patch } Sample* samples[128]; - - u64 getAdsrReleaseTime(u8 note); }; @@ -134,7 +122,7 @@ class MIDIMessage struct Channel { - // channels may have volums and pans too + // channels may have volums and pans too, but not implemented Patch* patch; }; @@ -181,14 +169,11 @@ class Sequence private: std::vector messages; - - -private: u32 actionPos = 0; u64 trackStartTime = 0; }; -enum AttackPhase +enum ADSRPhase { NONE, ATTACK, @@ -210,7 +195,6 @@ class Voice float velocity = 1.0f; float pan = 0.0f; float noteVolume = 1.0f; - s16 currentLevel = 0; bool loop = false; u64 onTime = 0; // when the note was pressed @@ -219,16 +203,18 @@ class Voice u64 lastProcessedTick = 0; u64 lastProcessedMs = 0; - AttackPhase phase = ATTACK; + ADSRPhase phase = NONE; s32 counter = 0; // decremented each midi tick u32 rate; bool decreasing; bool exponential; + s16 currentLevel = 0; + s16 targetLevel = MAX_VOLUME; // attack we want to reach max - std::vector sources; - std::vector samples; + s16 source; + Sample* sample; - s16 tick(u64 ticks); + s16 tick(); }; /* @@ -252,7 +238,9 @@ class Sequencer void playSeq(s32 seqId); void stopSeq(s32 seqId); - void tick(u64 ticks); + void tickSequence(); + void tickVoice(); + void syncVoice(); private: From 0dfd8670583e91f144ac3bba2df5dee1538aca6d Mon Sep 17 00:00:00 2001 From: Sean Horton <3359434+sean-horton@users.noreply.github.com> Date: Sat, 4 Mar 2023 19:45:00 -0500 Subject: [PATCH 20/82] going to try lock free --- Source/AliveLibCommon/audio/MidiPlayer.cpp | 96 +------ Source/AliveLibCommon/audio/MidiPlayer.hpp | 1 - Source/AliveLibCommon/audio/Sequencer.cpp | 278 +++++++++------------ Source/AliveLibCommon/audio/Sequencer.hpp | 43 ++-- 4 files changed, 148 insertions(+), 270 deletions(-) diff --git a/Source/AliveLibCommon/audio/MidiPlayer.cpp b/Source/AliveLibCommon/audio/MidiPlayer.cpp index 91632532a..cdababdda 100644 --- a/Source/AliveLibCommon/audio/MidiPlayer.cpp +++ b/Source/AliveLibCommon/audio/MidiPlayer.cpp @@ -74,11 +74,9 @@ u64 timeSinceEpochMillisec() void MidiPlayer::loop() { - // expected ticks = float(now - start) / 1000.0f * 768.0f - u64 start = timeSinceEpochMillisec(); u64 now = 0; - u64 ticks = 0; + u64 currentTicks = 0; u64 expectedTicks = 0; while (running) { @@ -88,7 +86,7 @@ void MidiPlayer::loop() // tick sequence - i.e. new notes to play/stop? sequencer->tickSequence(); - while (ticks++ < expectedTicks) + while (currentTicks++ < expectedTicks) { // tick voices - just math calculations. // this is ticked 44100 times per second. Possibly this @@ -101,26 +99,21 @@ void MidiPlayer::loop() sequencer->syncVoice(); // defer this thread some amount of time - std::this_thread::sleep_for(std::chrono::milliseconds(30)); + std::this_thread::sleep_for(std::chrono::milliseconds(1)); } } void MidiPlayer::SND_Init() { - mutex.lock(); if (sequencer) { delete sequencer; } sequencer = new sean::Sequencer(); - mutex.unlock(); running = true; thread = new std::thread(&MidiPlayer::loop, this); - - //// Create sequencer - //AliveInitAudio(); } void MidiPlayer::SND_Shutdown() @@ -134,8 +127,7 @@ void MidiPlayer::SND_Shutdown() void MidiPlayer::SND_Load_VABS(SoundBlockInfo* pSoundBlockInfo, s32 reverb) { - mutex.lock(); - reverb; // TODO - reverb - this seems to be the only spot + reverb; // TODO - what do we do with this? override the patch/sample? sequencer->reset(); while (1) @@ -164,32 +156,26 @@ void MidiPlayer::SND_Load_VABS(SoundBlockInfo* pSoundBlockInfo, s32 reverb) for (s32 i = 0; i < vabHeader->field_12_num_progs; i++) { + + /////////// // PATCH (Instruments) for (s32 x = 0; x < 16; x++) { + + /////////// // SAMPLE if (vagAttr->field_2_vol > 0) { unsigned short ADSR1 = vagAttr->field_10_adsr1; unsigned short ADSR2 = vagAttr->field_12_adsr2; - REAL_ADSR realADSR = {}; - PSXConvADSR(&realADSR, ADSR1, ADSR2, false); - sean::ADSR adsr = sean::parseADSR(ADSR1, ADSR2); - //sean::REAL_ADSR adsr; - //sean::PSXConvADSR(&adsr, ADSR1, ADSR2, false); - + sean::ADSR adsr = sean::parseADSR(ADSR1, ADSR2); sean::Patch* patch = sequencer->createPatch(vagAttr->field_14_prog); Sample* s = samples.at(vagAttr->field_16_vag - 1); sean::Sample* t = new sean::Sample(s->m_SampleBuffer, s->i_SampleSize * 2); patch->samples[x] = t; - u8 a = (u8) (vagAttr->field_10_adsr1 >> 8); - u8 d = (u8) vagAttr->field_10_adsr1; - a; - d; - t->adsr = adsr; t->volume = vagAttr->field_2_vol / 127.0f; t->pan = (vagAttr->field_3_pan / 64.0f) - 1.0f; @@ -198,54 +184,14 @@ void MidiPlayer::SND_Load_VABS(SoundBlockInfo* pSoundBlockInfo, s32 reverb) t->rootNotePitchShift = vagAttr->field_5_shift; t->minNote = vagAttr->field_6_min; t->maxNote = vagAttr->field_7_max; - - - //program->prog_id = vagAttr->field_14_prog; - //tone->mode = vagAttr->field_1_mode; - //tone->f_Volume = vagAttr->field_2_vol / 127.0f; - //tone->c_Center = vagAttr->field_4_centre; - ////tone->c_Shift = (vagAttr->field_5_shift | (vagAttr->field_4_centre << 7)); - //tone->c_Shift = vagAttr->field_5_shift; - //tone->f_Pan = (vagAttr->field_3_pan / 64.0f) - 1.0f; - //tone->Min = vagAttr->field_6_min; - //tone->Max = vagAttr->field_7_max; - //tone->Pitch = vagAttr->field_5_shift / 100.0f; - //tone->m_Sample = s; - //tone->sample = sampleBuffers.at(vagAttr->field_16_vag - 1); - ////alGenBuffers(1, &tone->sample); - ////alBufferData(tone->sample, AL_FORMAT_MONO16, tone->m_Sample->m_SampleBuffer, tone->m_Sample->i_SampleSize * 2, 22050); - //// These are time in seconds - //tone->AttackTime = realADSR.attack_time; - //tone->DecayTime = realADSR.decay_time; - //tone->ReleaseTime = realADSR.release_time; - //tone->SustainTime = realADSR.sustain_time; - //tone->SustainLevel = realADSR.sustain_level; - //tone->ReleaseExponential = false; // TODO - where does this come from? - ////f32 sustain_level = static_cast((2 * (~(u8) vagAttr->field_10_adsr1 & 0xF))); - ////tone->AttackTime = std::min(static_cast((powf(2.0f, ((vagAttr->field_10_adsr1 >> 8) & 0x7F) * 0.25f) * 0.09f)), static_cast(32767)); - ////tone->DecayTime = static_cast((((vagAttr->field_10_adsr1 >> 4) & 0xF) / 15.0f) * 16.0); - ////tone->SustainTime = std::min(static_cast((sustain_level / 15.0f) * 600.0), static_cast(32767)); - ////tone->ReleaseTime = std::min(static_cast(pow(2, vagAttr->field_12_adsr2 & 0x1F) * 0.045f), static_cast(32767)); - //if (realADSR.attack_time > 1) - //{ // This works until the loop database is added. - // tone->Loop = true; - //} } ++vagAttr; } } - //AliveAudio::LockNotes(); - //delete mSoundbank; - //mSoundbank = new Soundbank(samples, programs); - //AliveAudio::ClearAllVoices(); - //AliveAudio::m_CurrentSoundbank = mSoundbank; - //AliveAudio::UnlockNotes(); - delete[] ppVabBody; pSoundBlockInfo++; } - mutex.unlock(); } void MidiPlayer::SND_Load_Seqs(OpenSeqHandle* pSeqTable, const char_type* bsqFileName) @@ -255,7 +201,6 @@ void MidiPlayer::SND_Load_Seqs(OpenSeqHandle* pSeqTable, const char_type* bsqFil return; } - mutex.lock(); OpenSeqHandle* seq = pSeqTable; for (s32 i = 0; i < mResourceProvider->sequenceCount(); i++) // AO = 164 AE = 144 { @@ -285,8 +230,6 @@ void MidiPlayer::SND_Load_Seqs(OpenSeqHandle* pSeqTable, const char_type* bsqFil mSequences.push_back(test); } } - - mutex.unlock(); } void MidiPlayer::SND_StopAll() @@ -325,9 +268,7 @@ void MidiPlayer::SND_SEQ_Stop(u16 idx) { idx; - mutex.lock(); sequencer->stopSeq(idx); - mutex.unlock(); //SequencePlayer* player = GetSequencePlayer(idx); //if (player) //{ @@ -349,9 +290,7 @@ s16 MidiPlayer::SND_SEQ_PlaySeq(u16 idx, s32 repeatCount, s16 bDontStop) repeatCount; idx; - mutex.lock(); sequencer->playSeq(idx); - mutex.unlock(); return 1; //SequencePlayer* player = GetSequencePlayer(idx); @@ -408,13 +347,11 @@ void MidiPlayer::SND_SEQ_SetVol(s32 idx, s32 volLeft, s32 volRight) sanitizeVolume(&volLeft, 10, 127); sanitizeVolume(&volRight, 10, 127); - mutex.lock(); sean::Sequence* seq = sequencer->getSequence((s32) idx); if (seq) { seq->volume = std::min(volLeft, volRight) / 127.0f; } - mutex.unlock(); //SequencePlayer* player = GetSequencePlayer(u16(idx)); //if (player) @@ -430,14 +367,12 @@ s16 MidiPlayer::SND_SEQ_Play(u16 idx, s32 repeatCount, s16 volLeft, s16 volRight volRight; idx; - mutex.lock(); sean::Sequence* seq = sequencer->getSequence((s32) idx); if (seq) { seq->volume = std::min(volLeft, volRight) / 127.0f; } sequencer->playSeq(idx); - mutex.unlock(); return 0; //SequencePlayer* player = GetSequencePlayer(idx); @@ -471,13 +406,11 @@ s16 MidiPlayer::SND_SsIsEos_DeInlined(u16 idx) //} s16 res = 0; - mutex.lock(); sean::Sequence* seq = sequencer->getSequence((s32) idx); if (seq) { res = seq->repeats < seq->repeatLimit ? 1 : 0; } - mutex.unlock(); return res; } @@ -489,13 +422,10 @@ s32 MidiPlayer::SFX_SfxDefinition_Play(SfxDefinition* sfxDef, s32 volLeft, s32 v sanitizeVolume(&volLeft, 10, 127); sanitizeVolume(&volRight, 10, 127); - std::cout << volLeft << " " << volRight << std::endl; - mutex.lock(); // TODO - I don't think these pans and volumes are quite right float volume = std::max(volLeft, volRight) / 127.0f; float pan = (float(volRight) / float(volLeft)) - 1; s32 id = sequencer->playNote(sfxDef->program, sfxDef->note, volume, pan, (u8)std::max(pitch_min, pitch_max), pitch_min, pitch_max); - mutex.unlock(); return id; } @@ -510,9 +440,7 @@ s32 MidiPlayer::SFX_SfxDefinition_Play(SfxDefinition* sfxDef, s32 volume, s32 pi sanitizePitch(&pitch_max, sfxDef->pitch_max); sanitizeVolume(&volume, 1, 127); - mutex.lock(); s32 id = sequencer->playNote(sfxDef->program, sfxDef->note, volume / 127.0f, 0, (u8) std::max(pitch_min, pitch_max), pitch_min, pitch_max); - mutex.unlock(); return id; } @@ -524,6 +452,7 @@ s32 MidiPlayer::SND(s32 program, s32 vabId, s32 note, s16 vol, s16 min, s16 max) vol; min; max; + sequencer->playNote(program, (u8)note, vol / 127.0f, 0, 0, min, max); return 0; //AliveAudio::LockNotes(); //int playId = NextId(); @@ -648,7 +577,6 @@ void parseMidiStream(sean::Sequence* seq, std::vector seqData, s32 trackI seq->tempoUs = (float) tempoValue; // tempo (length of quarter note in microseconds) 0.000001us/s vs 0.001ms/s seq->ticksPerBeat = float(ticksPerBeat); - unsigned int prevDeltaTime = 0; unsigned int deltaTime = 0; const size_t midiDataStart = stream.Pos(); @@ -700,6 +628,7 @@ void parseMidiStream(sean::Sequence* seq, std::vector seqData, s32 trackI sean::MIDIMessage* msg = seq->createMIDIMessage(); msg->type = sean::END_TRACK; msg->tick = ticksPerBeat; + msg->tick = deltaTime; return; } @@ -755,7 +684,6 @@ void parseMidiStream(sean::Sequence* seq, std::vector seqData, s32 trackI } else { - prevDeltaTime = deltaTime; msg->type = sean::NOTE_ON; } } @@ -779,7 +707,6 @@ void parseMidiStream(sean::Sequence* seq, std::vector seqData, s32 trackI { Uint8 prog = 0; stream.ReadUInt8(prog); - //prevDeltaTime = deltaTime; sean::MIDIMessage* msg = seq->createMIDIMessage(); msg->type = sean::PATCH_CHANGE; @@ -813,6 +740,7 @@ void parseMidiStream(sean::Sequence* seq, std::vector seqData, s32 trackI break; case 0xe: // Pitch Bend { + // TODO - used in scrabania somewhere Uint16 bend = 0; stream.ReadUInt16(bend); } diff --git a/Source/AliveLibCommon/audio/MidiPlayer.hpp b/Source/AliveLibCommon/audio/MidiPlayer.hpp index 81d18028a..bc433ac7f 100644 --- a/Source/AliveLibCommon/audio/MidiPlayer.hpp +++ b/Source/AliveLibCommon/audio/MidiPlayer.hpp @@ -206,7 +206,6 @@ namespace psx { private: std::thread* thread; - std::mutex mutex; bool running; void loop(); diff --git a/Source/AliveLibCommon/audio/Sequencer.cpp b/Source/AliveLibCommon/audio/Sequencer.cpp index 914ac03bd..d2fa97da1 100644 --- a/Source/AliveLibCommon/audio/Sequencer.cpp +++ b/Source/AliveLibCommon/audio/Sequencer.cpp @@ -119,12 +119,6 @@ static constexpr ADSRTableEntries s_adsr_table = ComputeADSRTableEntries(); Sequencer::Sequencer() { - for (int i = 0; i < sourceCount; i++) - { - voices[i] = NULL; - patches[i] = NULL; - sourceLock[i] = false; - } // Open the AL device ALenum error; @@ -137,25 +131,35 @@ Sequencer::Sequencer() return; } + for (int i = 0; i < voiceCount; i++) + { + voices[i] = new Voice(); + + // Prepare sources (these are streams buffers can be played in) + alGenSources(1, &(voices[i])->alSourceId); + if ((error = alGetError()) != AL_NO_ERROR) + { + return; + } + } + + for (int i = 0; i < patchCount; i++) + { + patches[i] = NULL; + } + // Prepare some known effects we will use LOAD_EFFECT_FUNCTIONS(); EFXEAXREVERBPROPERTIES reverb = EFX_REVERB_PRESET_GENERIC; efxReverb0 = PREPARE_REVERB(&reverb); alGenAuxiliaryEffectSlots(1, &efxSlot0); alAuxiliaryEffectSloti(efxSlot0, AL_EFFECTSLOT_EFFECT, (ALint) efxReverb0); - - // Prepare sources (these are streams buffers can be played in) - alGenSources(sourceCount, source); - if ((error = alGetError()) != AL_NO_ERROR) - { - return; - } } Sequencer::~Sequencer() { // Delete sources - alDeleteSources(sourceCount, source); + //alDeleteSources(sourceCount, source); // Delete effects alDeleteAuxiliaryEffectSlots(1, &efxSlot0); @@ -232,7 +236,7 @@ void Sequencer::stopSeq(s32 seqId) { if (v && v->sequence == seq) { - //v->forceStopNow = true; + v->offTime = timeSinceEpochMillisec(); } } } @@ -288,38 +292,27 @@ void Sequencer::tickSequence() continue; } - s16 id = obtainSource(); - if (id < 0) - { - releaseVoice(v); - continue; - } - - alSourcei(source[id], AL_BUFFER, sample->alBuffer); + alSourcei(v->alSourceId, AL_BUFFER, sample->alBuffer); if ((error = alGetError()) != AL_NO_ERROR) { releaseVoice(v); - releaseSource(id); continue; } - alSourcef(source[id], AL_GAIN, (ALfloat) 0.0f); - alSourcePlay(source[id]); + alSourcef(v->alSourceId, AL_GAIN, (ALfloat) 0.0f); + alSourcePlay(v->alSourceId); if ((error = alGetError()) != AL_NO_ERROR) { releaseVoice(v); - releaseSource(id); continue; } v->sequence = seq; + v->pitchMin = 0; + v->velocity = message->velocity / 127.0f; v->note = message->note; - v->noteVolume = message->velocity; - v->patch = seq->channels[message->channelId]->patch; - v->source = id; v->sample = sample; v->loop = sample->adsr.attackRate > 1; // attack is greater than sample length? sample->adsr.attackRate / 1000 > 1; - v->onTime = now; } break; @@ -349,15 +342,15 @@ void Sequencer::tickSequence() void Sequencer::tickVoice() { - for (int c = 0; c < sourceCount; c++) + for (int c = 0; c < voiceCount; c++) { Voice* voice = voices[c]; - if (!voice) + if (!voice->inUse) { continue; } - voice->currentLevel = voice->tick(); + voice->adsrCurrentLevel = voice->tick(); } } @@ -367,10 +360,10 @@ void Sequencer::syncVoice() // unpause is called at the end. alcSuspendContext(ctx); - for (int c = 0; c < sourceCount; c++) + for (int c = 0; c < voiceCount; c++) { Voice* voice = voices[c]; - if (!voice) + if (!voice->inUse) { continue; } @@ -383,13 +376,12 @@ void Sequencer::syncVoice() // Update the play source paramters ALenum state; Sample* sample = voice->sample; - s16 id = voice->source; + ALuint id = voice->alSourceId; // If we are past the duration of the notes playback - alGetSourcei(source[id], AL_SOURCE_STATE, &state); - if (state != AL_PLAYING || voice->forceStopNow) + alGetSourcei(id, AL_SOURCE_STATE, &state); + if (state != AL_PLAYING) { - releaseSource(id); releaseVoice(voice); continue; } @@ -413,10 +405,17 @@ void Sequencer::syncVoice() pan = sample->pan; } - alSource3f(source[id], AL_POSITION, pan, 0, -sqrtf(1.0f - pan * pan)); - alSourcef(source[id], AL_PITCH, (ALfloat) freq); - alSourcef(source[id], AL_GAIN, (ALfloat) float(voice->currentLevel) / 32767.0f); - alSourcei(source[id], AL_LOOPING, voice->loop); + float seqVolume = 1.0f; + if (voice->sequence) + { + seqVolume = voice->sequence->volume; + } + float gain = float(voice->adsrCurrentLevel) / 32767.0f * voice->velocity * voice->sample->volume* seqVolume; + + alSource3f(id, AL_POSITION, pan, 0, -sqrtf(1.0f - pan * pan)); + alSourcef(id, AL_PITCH, (ALfloat) freq); + alSourcef(id, AL_GAIN, (ALfloat) gain); + alSourcei(id, AL_LOOPING, voice->loop); // 0 Off // 1 Vibrate @@ -425,11 +424,16 @@ void Sequencer::syncVoice() // 4 Reverb if (sample->reverb != 0) { - alSource3i(source[id], AL_AUXILIARY_SEND_FILTER, (ALint) efxSlot0, 0, AL_FILTER_NULL); + alSource3i(id, AL_AUXILIARY_SEND_FILTER, (ALint) efxSlot0, 0, AL_FILTER_NULL); } else { - alSource3i(source[id], AL_AUXILIARY_SEND_FILTER, AL_EFFECTSLOT_NULL, 0, NULL); + alSource3i(id, AL_AUXILIARY_SEND_FILTER, AL_EFFECTSLOT_NULL, 0, NULL); + } + + if (gain < 0.001f) + { + releaseVoice(voice); } } @@ -441,154 +445,116 @@ s16 Voice::tick() { // UPDATE ADSR STATE - this probably doesn't need to be done every tick? - if (phase == NONE) + if (adsrPhase == NONE) { - phase = ATTACK; - decreasing = false; - rate = sample->adsr.attackRate; - exponential = sample->adsr.attackExponential; - targetLevel = MAX_VOLUME; - counter = s_adsr_table[decreasing][rate].ticks; + adsrPhase = ATTACK; + adsrDecreasing = false; + adsrRate = sample->adsr.attackRate; + adsrExponential = sample->adsr.attackExponential; + adsrTargetLevel = MAX_VOLUME; + adsrCounter = s_adsr_table[adsrDecreasing][adsrRate].ticks; } - else if (phase == ATTACK && currentLevel >= targetLevel) + else if (adsrPhase == ATTACK && adsrCurrentLevel >= adsrTargetLevel) { - phase = DECAY; - decreasing = true; - rate = sample->adsr.decayRate << 2; - exponential = true; - targetLevel = static_cast(std::min((u32(sample->adsr.sustainLevel) + 1) * 0x800, MAX_VOLUME)); - counter = s_adsr_table[decreasing][rate].ticks; + 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 (phase == DECAY && currentLevel <= targetLevel) + else if (adsrPhase == DECAY && adsrCurrentLevel <= adsrTargetLevel) { - phase = SUSTAIN; - decreasing = sample->adsr.sustainDirection; - rate = sample->adsr.sustainRate; - exponential = sample->adsr.sustainExponential; - targetLevel = 0; - counter = s_adsr_table[decreasing][rate].ticks; + 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 && phase != RELEASE) + else if (offTime != 0 && adsrPhase != RELEASE) { - phase = RELEASE; - decreasing = true; - rate = sample->adsr.releaseRate << 2; - exponential = sample->adsr.releaseExponential; - targetLevel = 0; - counter = s_adsr_table[decreasing][rate].ticks; + adsrPhase = RELEASE; + adsrDecreasing = true; + adsrRate = sample->adsr.releaseRate << 2; + adsrExponential = sample->adsr.releaseExponential; + adsrTargetLevel = 0; + adsrCounter = s_adsr_table[adsrDecreasing][adsrRate].ticks; } // UPDATE TICK STATE - counter--; - if (counter > 0) + adsrCounter--; + if (adsrCounter > 0) { - return currentLevel; + return adsrCurrentLevel; } - const ADSRTableEntry& table_entry = s_adsr_table[decreasing][rate]; + const ADSRTableEntry& table_entry = s_adsr_table[adsrDecreasing][adsrRate]; s32 this_step = table_entry.step; - counter = table_entry.ticks; + adsrCounter = table_entry.ticks; - if (exponential) + if (adsrExponential) { - if (decreasing) + if (adsrDecreasing) { - this_step = (this_step * currentLevel) >> 15; + this_step = (this_step * adsrCurrentLevel) >> 15; } else { - if (currentLevel >= 0x6000) + if (adsrCurrentLevel >= 0x6000) { - if (rate < 40) + if (adsrRate < 40) { this_step >>= 2; } - else if (rate >= 44) + else if (adsrRate >= 44) { - counter >>= 2; + adsrCounter >>= 2; } else { this_step >>= 1; - counter >>= 1; + adsrCounter >>= 1; } } } } return static_cast( - std::clamp(static_cast(currentLevel) + this_step, MIN_VOLUME, MAX_VOLUME)); + std::clamp(static_cast(adsrCurrentLevel) + this_step, MIN_VOLUME, MAX_VOLUME)); } Voice* Sequencer::obtainVoice() { - //if (activeVoice >= 32) - //{ - // return NULL; - //} - for (int i = 0; i < sourceCount; i++) + for (int i = 0; i < voiceCount; i++) { - if (!voices[i]) + Voice* v = voices[i]; + if (v->inUse) { - //activeVoice++; - Voice* v = new Voice(); - v->uuid = nextUuid(); - voices[i] = v; - return v; + continue; } + + v->inUse = true; + v->uuid = nextUuid(); + return v; } return NULL; } void Sequencer::releaseVoice(Voice* v) { - if (!v) - { - return; - } - - for (int i = 0; i < sourceCount; i++) - { - if (v == voices[i]) - { - //activeVoice--; - delete v; - voices[i] = NULL; - return; - } - } -} - -s16 Sequencer::obtainSource() -{ - //if (activeSource >= 32) - //{ - // return -1; - //} - for (s16 i = 0; i < sourceCount; i++) - { - if (!sourceLock[i]) - { - //activeSource++; - sourceLock[i] = true; - return i; - } - } - return -1; -} - -void Sequencer::releaseSource(s16 id) -{ - if (!sourceLock[id]) - { - return; - } - //activeSource--; - - sourceLock[id] = false; - alSourceStop(source[id]); - alSourcei(source[id], AL_BUFFER, NULL); + v->inUse = false; + v->offTime = 0; + v->pitch = 0; + v->adsrPhase = NONE; + v->adsrCounter = 0; + v->adsrCurrentLevel = 0; + v->adsrTargetLevel = MAX_VOLUME; + v->sequence = NULL; + v->loop = false; + v->pan = 0; + alSourceStop(v->alSourceId); } s32 Sequencer::nextUuid() @@ -659,14 +625,10 @@ s32 Sequencer::playNote(s32 patchId, u8 note, float velocity, float pan, u8 pitc return 0; } - s16 id = obtainSource(); - if (id < 0) - { - releaseVoice(v); - return 0; - } + alSourcei(v->alSourceId, AL_BUFFER, s->alBuffer); + alSourcef(v->alSourceId, AL_GAIN, (ALfloat) 0.0f); + alSourcePlay(v->alSourceId); - v->patch = patch; v->note = note; v->velocity = velocity; v->pan = pan; @@ -674,13 +636,11 @@ s32 Sequencer::playNote(s32 patchId, u8 note, float velocity, float pan, u8 pitc v->pitchMin = pitchMin; v->pitchMax = pitchMax; v->sample = s; - v->source = id; - v->currentLevel = s16(velocity * 32767); - v->decreasing = false; - v->rate = s->adsr.attackRate; - v->exponential = s->adsr.attackExponential; - v->counter = s_adsr_table[v->decreasing][v->rate].ticks; - v->onTime = timeSinceEpochMillisec(); + //v->adsrCurrentLevel = s16(velocity * 32767); + //v->adsrDecreasing = false; + //v->adsrRate = s->adsr.attackRate; + //v->adsrExponential = s->adsr.attackExponential; + //v->adsrCounter = s_adsr_table[v->adsrDecreasing][v->adsrRate].ticks; return v->uuid; } diff --git a/Source/AliveLibCommon/audio/Sequencer.hpp b/Source/AliveLibCommon/audio/Sequencer.hpp index 90f2a5210..d9549a378 100644 --- a/Source/AliveLibCommon/audio/Sequencer.hpp +++ b/Source/AliveLibCommon/audio/Sequencer.hpp @@ -186,32 +186,28 @@ class Voice { public: s32 uuid; + Sequence* sequence = NULL; - Patch* patch; u8 note; u8 pitch; s32 pitchMin = 0; s32 pitchMax = 127; float velocity = 1.0f; float pan = 0.0f; - float noteVolume = 1.0f; + bool inUse = false; bool loop = false; - u64 onTime = 0; // when the note was pressed u64 offTime = 0; // when the note was released - bool forceStopNow = false; - u64 lastProcessedTick = 0; - u64 lastProcessedMs = 0; - - ADSRPhase phase = NONE; - s32 counter = 0; // decremented each midi tick - u32 rate; - bool decreasing; - bool exponential; - s16 currentLevel = 0; - s16 targetLevel = MAX_VOLUME; // attack we want to reach max - - s16 source; + + 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 + + ALuint alSourceId; Sample* sample; s16 tick(); @@ -250,21 +246,16 @@ class Sequencer Voice* obtainVoice(); void releaseVoice(Voice* v); - s16 obtainSource(); - void releaseSource(s16 id); - ALCdevice* device; ALCcontext* ctx; std::vector sequences; - static const int sourceCount = 128; - //int activeSource = 0; - //int activeVoice = 0; - Voice* voices[sourceCount]; - bool sourceLock[sourceCount]; - ALuint source[sourceCount]; - Patch* patches[sourceCount]; + static const int voiceCount = 24; + Voice* voices[voiceCount]; + + static const int patchCount = 128; + Patch* patches[patchCount]; ALuint efxReverb0; ALuint efxSlot0; From b420d62703518d783f80c5be80951e4c5c2900e7 Mon Sep 17 00:00:00 2001 From: Sean Horton <3359434+sean-horton@users.noreply.github.com> Date: Mon, 6 Mar 2023 20:27:13 -0500 Subject: [PATCH 21/82] another checkpoint --- Source/AliveLibCommon/audio/MidiPlayer.cpp | 257 +++++---------------- Source/AliveLibCommon/audio/MidiPlayer.hpp | 11 - Source/AliveLibCommon/audio/Sequencer.cpp | 204 +++++++++++----- Source/AliveLibCommon/audio/Sequencer.hpp | 51 +++- 4 files changed, 240 insertions(+), 283 deletions(-) diff --git a/Source/AliveLibCommon/audio/MidiPlayer.cpp b/Source/AliveLibCommon/audio/MidiPlayer.cpp index cdababdda..fb0f37460 100644 --- a/Source/AliveLibCommon/audio/MidiPlayer.cpp +++ b/Source/AliveLibCommon/audio/MidiPlayer.cpp @@ -48,62 +48,14 @@ MidiPlayer::MidiPlayer(ResourceProvider* provider) { mResourceProvider = provider; mSoundSampleParser = new DefaultSoundSampleParser(); - idBank = new s32[idBankSize]; - for (int i = 0; i < idBankSize; i++) - { - idBank[i] = 0; - } } MidiPlayer::MidiPlayer(ResourceProvider* provider, SoundSampleParser* sampleParser) { mResourceProvider = provider; mSoundSampleParser = sampleParser; - idBank = new s32[idBankSize]; - for (int i = 0; i < idBankSize; i++) - { - idBank[i] = 0; - } -} - -u64 timeSinceEpochMillisec() -{ - using namespace std::chrono; - return duration_cast(system_clock::now().time_since_epoch()).count(); -} - -void MidiPlayer::loop() -{ - u64 start = timeSinceEpochMillisec(); - u64 now = 0; - u64 currentTicks = 0; - u64 expectedTicks = 0; - while (running) - { - now = timeSinceEpochMillisec(); - expectedTicks = u64(float(now - start) / 1000.0f * 44100); - - // tick sequence - i.e. new notes to play/stop? - sequencer->tickSequence(); - - while (currentTicks++ < expectedTicks) - { - // tick voices - just math calculations. - // this is ticked 44100 times per second. Possibly this - // can be done with a fast math calculation instead of - // a loop, but that's for another time... - sequencer->tickVoice(); - } - - // sync voices with openal - sequencer->syncVoice(); - - // defer this thread some amount of time - std::this_thread::sleep_for(std::chrono::milliseconds(1)); - } } - void MidiPlayer::SND_Init() { if (sequencer) @@ -111,17 +63,10 @@ void MidiPlayer::SND_Init() delete sequencer; } sequencer = new sean::Sequencer(); - - running = true; - thread = new std::thread(&MidiPlayer::loop, this); } void MidiPlayer::SND_Shutdown() { - running = false; - thread->join(); - delete thread; - delete sequencer; } @@ -173,17 +118,18 @@ void MidiPlayer::SND_Load_VABS(SoundBlockInfo* pSoundBlockInfo, s32 reverb) sean::ADSR adsr = sean::parseADSR(ADSR1, ADSR2); sean::Patch* patch = sequencer->createPatch(vagAttr->field_14_prog); Sample* s = samples.at(vagAttr->field_16_vag - 1); - sean::Sample* t = new sean::Sample(s->m_SampleBuffer, s->i_SampleSize * 2); - patch->samples[x] = t; - - t->adsr = adsr; - t->volume = vagAttr->field_2_vol / 127.0f; - t->pan = (vagAttr->field_3_pan / 64.0f) - 1.0f; - t->reverb = vagAttr->field_1_mode; - t->rootNote = vagAttr->field_4_centre; - t->rootNotePitchShift = vagAttr->field_5_shift; - t->minNote = vagAttr->field_6_min; - t->maxNote = vagAttr->field_7_max; + sean::Sample* sample = new sean::Sample(s->m_SampleBuffer, s->i_SampleSize * 2); + patch->samples[x] = sample; + + sample->adsr = adsr; + sample->volume = vagAttr->field_2_vol / 127.0f; + sample->pan = (vagAttr->field_3_pan / 64.0f) - 1.0f; + 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->loop = } ++vagAttr; } @@ -234,49 +180,35 @@ void MidiPlayer::SND_Load_Seqs(OpenSeqHandle* pSeqTable, const char_type* bsqFil void MidiPlayer::SND_StopAll() { - //for (SequencePlayer* player : mSequencePlayers) - //{ - // player->StopSequence(); - // delete player; - //} - //mSequencePlayers.clear(); + // called when pause menu is open - //AliveAudio::LockNotes(); - //AliveAudio::ClearAllVoices(); - //AliveAudio::UnlockNotes(); + std::cout << "stop all" << std::endl; + sequencer->stopAll(); } void MidiPlayer::SND_Reset() { + // called when quiting to main menu or going into a new level + std::cout << "reset" << std::endl; + sequencer->reset(); } void MidiPlayer::SND_Restart() { + std::cout << "restart" << std::endl; + // TODO - don't know when called } void MidiPlayer::SND_Stop_Channels_Mask(u32 bitMask) { bitMask; std::cout << "bitmask " << bitMask << "\n"; - //AliveAudio::LockNotes(); - //AliveAudio::ClearAllTrackVoices(bitMask, true); - //AliveAudio::UnlockNotes(); - //ReleaseId(bitMask); + sequencer->stopNote(bitMask); } void MidiPlayer::SND_SEQ_Stop(u16 idx) { - idx; - sequencer->stopSeq(idx); - //SequencePlayer* player = GetSequencePlayer(idx); - //if (player) - //{ - // player->StopSequence(); - // RemoveSequencePlayer(player); - // ReleaseId(player->m_PlayId); - // delete player; - //} } s8 MidiPlayer::SND_Seq_Table_Valid() @@ -289,9 +221,23 @@ s16 MidiPlayer::SND_SEQ_PlaySeq(u16 idx, s32 repeatCount, s16 bDontStop) bDontStop; // TODO repeatCount; idx; + // TODO - still broken for chanting - revist below commented code + + sean::Sequence* seq = sequencer->getSequence(idx); + if (!seq) + { + return 0; + } + if (seq->play) + { + return 1; + } + sequencer->getSequence(idx)->repeatLimit = bDontStop ? 9999999 : 1; sequencer->playSeq(idx); return 1; + + //SequencePlayer* player = GetSequencePlayer(idx); //// When chanting starts bDontStop is 1 @@ -341,9 +287,6 @@ void MidiPlayer::sanitizePitch(s32* src, s16 defaultPitch) void MidiPlayer::SND_SEQ_SetVol(s32 idx, s32 volLeft, s32 volRight) { - idx; - volLeft; - volRight; sanitizeVolume(&volLeft, 10, 127); sanitizeVolume(&volRight, 10, 127); @@ -352,66 +295,28 @@ void MidiPlayer::SND_SEQ_SetVol(s32 idx, s32 volLeft, s32 volRight) { seq->volume = std::min(volLeft, volRight) / 127.0f; } - - //SequencePlayer* player = GetSequencePlayer(u16(idx)); - //if (player) - //{ - // player->SetVolume(volLeft, volRight); - //} } s16 MidiPlayer::SND_SEQ_Play(u16 idx, s32 repeatCount, s16 volLeft, s16 volRight) { - repeatCount; // TODO - volLeft; - volRight; - idx; - sean::Sequence* seq = sequencer->getSequence((s32) idx); if (seq) { + seq->repeatLimit = repeatCount; seq->volume = std::min(volLeft, volRight) / 127.0f; } sequencer->playSeq(idx); - return 0; - - //SequencePlayer* player = GetSequencePlayer(idx); - //if (player) - //{ - // return 0; - //} - //std::cout << "Play seq " << idx << "\n"; - - //player = new SequencePlayer(); - //player->LoadSequenceData(mSequences.at(s16(idx)), s32(idx), repeatCount); - //player->PlaySequence(idx); - //player->SetVolume(volLeft, volRight); - //mSequencePlayers.push_back(player); - //return s16(mSequencePlayers.size() - 1); + return 1; } s16 MidiPlayer::SND_SsIsEos_DeInlined(u16 idx) { - idx; // TODO - - //SequencePlayer* player = GetSequencePlayer(idx); - //if (!player) - //{ - // return 0; - //} - //if (player->mRepeatCount) - //{ - // // 1 means we're still playing - // return player->completedRepeats() < player->mRepeatCount ? 1 : 0; - //} - s16 res = 0; sean::Sequence* seq = sequencer->getSequence((s32) idx); if (seq) { res = seq->repeats < seq->repeatLimit ? 1 : 0; } - return res; } @@ -425,8 +330,7 @@ s32 MidiPlayer::SFX_SfxDefinition_Play(SfxDefinition* sfxDef, s32 volLeft, s32 v // TODO - I don't think these pans and volumes are quite right float volume = std::max(volLeft, volRight) / 127.0f; float pan = (float(volRight) / float(volLeft)) - 1; - s32 id = sequencer->playNote(sfxDef->program, sfxDef->note, volume, pan, (u8)std::max(pitch_min, pitch_max), pitch_min, pitch_max); - return id; + return sequencer->playNote(sfxDef->program, sfxDef->note, volume, pan, (u8) 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) @@ -440,25 +344,13 @@ s32 MidiPlayer::SFX_SfxDefinition_Play(SfxDefinition* sfxDef, s32 volume, s32 pi sanitizePitch(&pitch_max, sfxDef->pitch_max); sanitizeVolume(&volume, 1, 127); - s32 id = sequencer->playNote(sfxDef->program, sfxDef->note, volume / 127.0f, 0, (u8) std::max(pitch_min, pitch_max), pitch_min, pitch_max); - return id; + return sequencer->playNote(sfxDef->program, sfxDef->note, volume / 127.0f, 0, (u8) 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) { - program; // TODO - vabId; - note; - vol; - min; - max; - sequencer->playNote(program, (u8)note, vol / 127.0f, 0, 0, min, max); - return 0; - //AliveAudio::LockNotes(); - //int playId = NextId(); - //AliveAudio::NoteOn(program, note, char(vol), 0); - //AliveAudio::UnlockNotes(); - //return playId; + vabId; // TODO - why is this not used? + return sequencer->playNote(program, (u8) note, vol / 127.0f, 0, 0, min, max); } void MidiPlayer::SsUtAllKeyOff(s32 mode) @@ -466,57 +358,9 @@ s32 MidiPlayer::SND(s32 program, s32 vabId, s32 note, s16 vol, s16 min, s16 max) mode; // TODO } - SequencePlayer* MidiPlayer::GetSequencePlayer(u16 idx) - { - for (SequencePlayer* player : mSequencePlayers) - { - if (player->m_TrackID == idx) - { - return player; - } - } - return NULL; - } - - void MidiPlayer::RemoveSequencePlayer(SequencePlayer* player) - { - int offset = 0; - int found = 0; - for (SequencePlayer* iter : mSequencePlayers) - { - if (iter->m_TrackID == player->m_TrackID) - { - found = 1; - break; - } - offset++; - } - if (found) - { - mSequencePlayers.erase(mSequencePlayers.begin() + offset); - } - } - -s32 MidiPlayer::NextId() -{ - for (int i = 0; i < idBankSize; i++) - { - if (idBank[i] == 0) - { - idBank[i] = 1; - return i + 255; - } - } - return 55; -} - -void MidiPlayer::ReleaseId(s32 id) -{ - idBank[id - 255] = 0; -} - + ////////////////////////////// // Midi stuff static void _SndMidiSkipLength(Stream& stream, int skip) { @@ -587,8 +431,7 @@ void parseMidiStream(sean::Sequence* seq, std::vector seqData, s32 trackI for (;;) { // Read event delta time - Uint32 delta = _MidiReadVarLen(stream); - deltaTime += delta; + deltaTime += _MidiReadVarLen(stream); //std::cout << "Delta: " << delta << " over all " << deltaTime << std::endl; // Obtain the event/status byte @@ -740,9 +583,17 @@ void parseMidiStream(sean::Sequence* seq, std::vector seqData, s32 trackI break; case 0xe: // Pitch Bend { - // TODO - used in scrabania somewhere - Uint16 bend = 0; - stream.ReadUInt16(bend); + // TODO - used in scrabania somewhere - NOT WORKING + u8 patchId = 0; + u8 bend = 0; + stream.ReadUInt8(patchId); + stream.ReadUInt8(bend); + + sean::MIDIMessage* msg = seq->createMIDIMessage(); + msg->type = sean::PITCH_BEND; + msg->patchId = patchId; // TODO - thses are wrong... was original bend = u16... is it patch? + msg->bend = bend; + msg->tick = deltaTime; } break; case 0xf: // Sysex len diff --git a/Source/AliveLibCommon/audio/MidiPlayer.hpp b/Source/AliveLibCommon/audio/MidiPlayer.hpp index bc433ac7f..c44f93560 100644 --- a/Source/AliveLibCommon/audio/MidiPlayer.hpp +++ b/Source/AliveLibCommon/audio/MidiPlayer.hpp @@ -205,10 +205,6 @@ namespace psx { void SsUtAllKeyOff(s32 mode); private: - std::thread* thread; - bool running; - void loop(); - ResourceProvider* mResourceProvider; SoundSampleParser* mSoundSampleParser; @@ -219,13 +215,6 @@ namespace psx { void sanitizePitch(s32* src, s16 defaultPitch); void sanitizeVolume(s32* src, s32 low, s32 high); - SequencePlayer* GetSequencePlayer(u16 idx); - void RemoveSequencePlayer(SequencePlayer* player); - - s32 idBankSize = 1024; - s32* idBank; - s32 NextId(); - void ReleaseId(s32 id); }; diff --git a/Source/AliveLibCommon/audio/Sequencer.cpp b/Source/AliveLibCommon/audio/Sequencer.cpp index d2fa97da1..cf112e3c7 100644 --- a/Source/AliveLibCommon/audio/Sequencer.cpp +++ b/Source/AliveLibCommon/audio/Sequencer.cpp @@ -119,7 +119,6 @@ static constexpr ADSRTableEntries s_adsr_table = ComputeADSRTableEntries(); Sequencer::Sequencer() { - // Open the AL device ALenum error; alGetError(); // clear out error state @@ -131,9 +130,12 @@ Sequencer::Sequencer() return; } + int id = 1; for (int i = 0; i < voiceCount; i++) { voices[i] = new Voice(); + voices[i]->id = id; + id = id << 1; // Prepare sources (these are streams buffers can be played in) alGenSources(1, &(voices[i])->alSourceId); @@ -150,14 +152,21 @@ Sequencer::Sequencer() // Prepare some known effects we will use LOAD_EFFECT_FUNCTIONS(); - EFXEAXREVERBPROPERTIES reverb = EFX_REVERB_PRESET_GENERIC; + EFXEAXREVERBPROPERTIES reverb = EFX_REVERB_PRESET_CASTLE_MEDIUMROOM; efxReverb0 = PREPARE_REVERB(&reverb); alGenAuxiliaryEffectSlots(1, &efxSlot0); alAuxiliaryEffectSloti(efxSlot0, AL_EFFECTSLOT_EFFECT, (ALint) efxReverb0); + + running = true; + thread = new std::thread(&Sequencer::loop, this); } Sequencer::~Sequencer() { + running = false; + thread->join(); + delete thread; + // Delete sources //alDeleteSources(sourceCount, source); @@ -183,35 +192,70 @@ u64 timeSinceEpochMillisec() return duration_cast(system_clock::now().time_since_epoch()).count(); } +void Sequencer::loop() +{ + u64 start = timeSinceEpochMillisec(); + u64 now = 0; + u64 currentTicks = 0; + u64 expectedTicks = 0; + while (running) + { + now = timeSinceEpochMillisec(); + expectedTicks = u64(float(now - start) / 1000.0f * 44100); + + // tick sequence - i.e. new notes to play/stop? + tickSequence(); + + while (currentTicks++ < expectedTicks) + { + // tick voices - just math calculations. + // this is ticked 44100 times per second. Possibly this + // can be done with a fast math calculation instead of + // a loop, but that's for another time... + tickVoice(); + } + + // sync voices with openal + syncVoice(); + + // defer this thread some amount of time + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } +} ////////////////////////// // PRIVATE void Sequencer::reset() { - alcSuspendContext(ctx); + stopAll(); + + for (s16 i = 0; i < patchCount; i++) + { + // must be deleted separately from sources + // since deleting a patch may delete a buffer + // attached to a source + delete patches[i]; + patches[i] = NULL; + } + + for (Sequence* seq : sequences) + { + delete seq; + } + sequences.clear(); +} - //for (s16 i = 0; i < sourceCount; i++) - //{ - // releaseSource(i); - // releaseVoice(voices[i]); - //} - - //for (s16 i = 0; i < sourceCount; i++) - //{ - // // must be deleted separately from sources - // // since deleting a patch may delete a buffer - // // attached to a source - // delete patches[i]; - // patches[i] = NULL; - //} - - //for (Sequence* seq : sequences) - //{ - // delete seq; - //} - //sequences.clear(); +void Sequencer::stopAll() +{ + for (Sequence* seq : sequences) + { + seq->play = false; + } - alcProcessContext(ctx); + for (s16 i = 0; i < voiceCount; i++) + { + releaseVoice(voices[i]); + } } void Sequencer::playSeq(s32 seqId) @@ -225,6 +269,7 @@ void Sequencer::playSeq(s32 seqId) } } + void Sequencer::stopSeq(s32 seqId) { for (Sequence* seq : sequences) @@ -256,11 +301,11 @@ void Sequencer::tickSequence() continue; } - //if (seq->repeats >= seq->repeatLimit) - //{ - // stopSeq(seq->id); - // continue; - //} + if (seq->repeats > seq->repeatLimit) + { + stopSeq(seq->id); + continue; + } MIDIMessage* message; while ((message = seq->next(now)) != NULL) @@ -308,10 +353,13 @@ void Sequencer::tickSequence() } v->sequence = seq; + v->patchId = seq->channels[message->channelId]->patch->id; + v->channelId = message->channelId; v->pitchMin = 0; v->velocity = message->velocity / 127.0f; v->note = message->note; v->sample = sample; + v->pan = 0; v->loop = sample->adsr.attackRate > 1; // attack is greater than sample length? sample->adsr.attackRate / 1000 > 1; } @@ -329,12 +377,29 @@ void Sequencer::tickSequence() break; } case PATCH_CHANGE: + { seq->channels[message->channelId]->patch = patches[message->patchId]; break; - + } case END_TRACK: + { // repeats are handled in the sequence when messages starts at position 1 again break; + } + case PITCH_BEND: + { + //std::cout << seq << " " << (s16) message->patchId << " " << (s16) message->bend << std::endl; + for (int i = 0; i < voiceCount; i++) + { + if (voices[i]->inUse && voices[i]->patchId == message->patchId) + { + // TODO - not working + std::cout << (s16)message->bend << std::endl; + voices[i]->pitch = message->bend; + } + } + break; + } } } } @@ -431,7 +496,7 @@ void Sequencer::syncVoice() alSource3i(id, AL_AUXILIARY_SEND_FILTER, AL_EFFECTSLOT_NULL, 0, NULL); } - if (gain < 0.001f) + if (gain < 0.001f && voice->offTime > 0) { releaseVoice(voice); } @@ -443,6 +508,10 @@ void Sequencer::syncVoice() s16 Voice::tick() { + if (!sample) + { + return 0; + } // UPDATE ADSR STATE - this probably doesn't need to be done every tick? if (adsrPhase == NONE) @@ -527,24 +596,21 @@ s16 Voice::tick() Voice* Sequencer::obtainVoice() { + bool expected = false; for (int i = 0; i < voiceCount; i++) { Voice* v = voices[i]; - if (v->inUse) + if (v->inUse.compare_exchange_weak(expected, true)) { - continue; + return v; } - - v->inUse = true; - v->uuid = nextUuid(); - return v; } return NULL; } void Sequencer::releaseVoice(Voice* v) { - v->inUse = false; + alSourceStop(v->alSourceId); v->offTime = 0; v->pitch = 0; v->adsrPhase = NONE; @@ -554,17 +620,7 @@ void Sequencer::releaseVoice(Voice* v) v->sequence = NULL; v->loop = false; v->pan = 0; - alSourceStop(v->alSourceId); -} - -s32 Sequencer::nextUuid() -{ - uuid++; - if (uuid % 50000) - { - uuid = 1; - } - return uuid; + v->inUse = false; } ////////////////////////// @@ -577,6 +633,7 @@ Patch* Sequencer::createPatch(s16 id) } patches[id] = new Patch(); + patches[id]->id = (u8)id; return patches[id]; } @@ -607,6 +664,7 @@ s32 Sequencer::playNote(s32 patchId, u8 note, float velocity, float pan, u8 pitc return 0; } + int ids = 0; for (Sample* s : patch->samples) { if (!s) @@ -625,10 +683,7 @@ s32 Sequencer::playNote(s32 patchId, u8 note, float velocity, float pan, u8 pitc return 0; } - alSourcei(v->alSourceId, AL_BUFFER, s->alBuffer); - alSourcef(v->alSourceId, AL_GAIN, (ALfloat) 0.0f); - alSourcePlay(v->alSourceId); - + v->patchId = (u8) patchId; v->note = note; v->velocity = velocity; v->pan = pan; @@ -636,20 +691,30 @@ s32 Sequencer::playNote(s32 patchId, u8 note, float velocity, float pan, u8 pitc v->pitchMin = pitchMin; v->pitchMax = pitchMax; v->sample = s; - //v->adsrCurrentLevel = s16(velocity * 32767); - //v->adsrDecreasing = false; - //v->adsrRate = s->adsr.attackRate; - //v->adsrExponential = s->adsr.attackExponential; - //v->adsrCounter = s_adsr_table[v->adsrDecreasing][v->adsrRate].ticks; - return v->uuid; + ids |= v->id; + alSourcei(v->alSourceId, AL_BUFFER, s->alBuffer); + alSourcef(v->alSourceId, AL_GAIN, (ALfloat) 0.0f); + alSourcePlay(v->alSourceId); } - return 0; + return ids; } +void Sequencer::stopNote(s32 mask) +{ + for (int i = 0; i < voiceCount; i++) + { + if ((voices[i]->id & mask) != 0) + { + // TODO - not sure if this is right. + // Maybe it should trigger a release? + releaseVoice(voices[i]); + } + } +} ////////////////////////// -// OTHERS +// SEQUENCE MIDIMessage* Sequence::createMIDIMessage() { MIDIMessage* msg = new MIDIMessage(); @@ -689,6 +754,23 @@ MIDIMessage* Sequence::next(u64 now) } +////////////////////////// +// Event Ring +void EventRing::push(Event event) +{ + events[(head++) % size] = event; +} + +Event* EventRing::pop() +{ + if (tail == head) + { + return NULL; + } + return &events[(tail++)]; +} + + ////////////////////////// // OPENAL BS ALuint PREPARE_REVERB(const EFXEAXREVERBPROPERTIES* reverb) diff --git a/Source/AliveLibCommon/audio/Sequencer.hpp b/Source/AliveLibCommon/audio/Sequencer.hpp index d9549a378..67f71678e 100644 --- a/Source/AliveLibCommon/audio/Sequencer.hpp +++ b/Source/AliveLibCommon/audio/Sequencer.hpp @@ -92,6 +92,7 @@ class Patch } } + u8 id; Sample* samples[128]; }; @@ -101,7 +102,8 @@ enum MIDIMessageType NOTE_ON, NOTE_OFF, PATCH_CHANGE, - END_TRACK + END_TRACK, + PITCH_BEND }; @@ -117,6 +119,7 @@ class MIDIMessage u8 patchId = 0; u8 note = 0; u8 velocity = 0; + u8 bend; }; @@ -154,7 +157,7 @@ class Sequence s32 id; s32 repeatLimit = 1; - bool play = false; + std::atomic_bool play = false; float volume = 1; float pan = 0; @@ -185,16 +188,18 @@ enum ADSRPhase class Voice { public: - s32 uuid; + s32 id; Sequence* sequence = NULL; + u8 patchId; + u8 channelId; u8 note; u8 pitch; s32 pitchMin = 0; s32 pitchMax = 127; float velocity = 1.0f; float pan = 0.0f; - bool inUse = false; + std::atomic_bool inUse = false; bool loop = false; u64 offTime = 0; // when the note was released @@ -213,6 +218,30 @@ class Voice s16 tick(); }; + +/* +* Non blocking event ring for +* cross thread actions (oddworld to midi player) +*/ +class Event +{ + void (*func)(); +}; + +class EventRing +{ +public: + void push(Event event); + Event* pop(); + +private: + static const int size = 256; + Event events[size]; + std::atomic tail = 0; + std::atomic head = 0; +}; + + /* * Can play MIDI */ @@ -223,6 +252,7 @@ class Sequencer ~Sequencer(); void reset(); + void stopAll(); Patch* createPatch(s16 id); @@ -230,15 +260,16 @@ class Sequencer Sequence* getSequence(s32 id); s32 playNote(s32 patchId, u8 note, float velocity, float pan, u8 pitch, s32 pitchMin, s32 pitchMax); + void stopNote(s32 mask); void playSeq(s32 seqId); void stopSeq(s32 seqId); - void tickSequence(); - void tickVoice(); - void syncVoice(); - private: + std::thread* thread; + bool running; + void loop(); + EventRing eventRing; s32 uuid = 1; s32 nextUuid(); @@ -246,6 +277,10 @@ class Sequencer Voice* obtainVoice(); void releaseVoice(Voice* v); + void tickSequence(); + void tickVoice(); + void syncVoice(); + ALCdevice* device; ALCcontext* ctx; From 140e854e4217ea837b00c1460d44c75c7ea08506 Mon Sep 17 00:00:00 2001 From: Sean Horton <3359434+sean-horton@users.noreply.github.com> Date: Tue, 7 Mar 2023 07:40:35 -0500 Subject: [PATCH 22/82] another checkpoint --- Source/AliveLibCommon/audio/Sequencer.cpp | 4 ++-- Source/AliveLibCommon/audio/Sequencer.hpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Source/AliveLibCommon/audio/Sequencer.cpp b/Source/AliveLibCommon/audio/Sequencer.cpp index cf112e3c7..d58a37bcc 100644 --- a/Source/AliveLibCommon/audio/Sequencer.cpp +++ b/Source/AliveLibCommon/audio/Sequencer.cpp @@ -360,7 +360,7 @@ void Sequencer::tickSequence() v->note = message->note; v->sample = sample; v->pan = 0; - v->loop = sample->adsr.attackRate > 1; // attack is greater than sample length? sample->adsr.attackRate / 1000 > 1; + v->loop = sample->adsr.attackRate > 0; // attack is greater than sample length? sample->adsr.attackRate / 1000 > 1; } break; @@ -391,7 +391,7 @@ void Sequencer::tickSequence() //std::cout << seq << " " << (s16) message->patchId << " " << (s16) message->bend << std::endl; for (int i = 0; i < voiceCount; i++) { - if (voices[i]->inUse && voices[i]->patchId == message->patchId) + if (voices[i]->inUse && voices[i]->channelId == message->channelId) { // TODO - not working std::cout << (s16)message->bend << std::endl; diff --git a/Source/AliveLibCommon/audio/Sequencer.hpp b/Source/AliveLibCommon/audio/Sequencer.hpp index 67f71678e..7a8789be2 100644 --- a/Source/AliveLibCommon/audio/Sequencer.hpp +++ b/Source/AliveLibCommon/audio/Sequencer.hpp @@ -119,7 +119,7 @@ class MIDIMessage u8 patchId = 0; u8 note = 0; u8 velocity = 0; - u8 bend; + s16 bend; }; @@ -194,7 +194,7 @@ class Voice u8 patchId; u8 channelId; u8 note; - u8 pitch; + s32 pitch; s32 pitchMin = 0; s32 pitchMax = 127; float velocity = 1.0f; From 62bf87b456cd1cc35cd60e97f5b44bd4990e1569 Mon Sep 17 00:00:00 2001 From: Sean Horton <3359434+sean-horton@users.noreply.github.com> Date: Tue, 7 Mar 2023 17:59:54 -0500 Subject: [PATCH 23/82] midi bend kind of working --- Source/AliveLibCommon/audio/MidiPlayer.cpp | 34 +++++++++++++++------- Source/AliveLibCommon/audio/Sequencer.cpp | 5 +--- 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/Source/AliveLibCommon/audio/MidiPlayer.cpp b/Source/AliveLibCommon/audio/MidiPlayer.cpp index fb0f37460..75ca2052f 100644 --- a/Source/AliveLibCommon/audio/MidiPlayer.cpp +++ b/Source/AliveLibCommon/audio/MidiPlayer.cpp @@ -232,8 +232,8 @@ s16 MidiPlayer::SND_SEQ_PlaySeq(u16 idx, s32 repeatCount, s16 bDontStop) { return 1; } - - sequencer->getSequence(idx)->repeatLimit = bDontStop ? 9999999 : 1; + sequencer->getSequence(idx)->volume = 1.0f; + sequencer->getSequence(idx)->repeatLimit = repeatCount; sequencer->playSeq(idx); return 1; @@ -329,7 +329,15 @@ s32 MidiPlayer::SFX_SfxDefinition_Play(SfxDefinition* sfxDef, s32 volLeft, s32 v sanitizeVolume(&volRight, 10, 127); // TODO - I don't think these pans and volumes are quite right float volume = std::max(volLeft, volRight) / 127.0f; - float pan = (float(volRight) / float(volLeft)) - 1; + float pan; + if (volLeft < volRight) + { + pan = 1.0f - (float(volLeft) / float(volRight)); + } + else + { + pan = (float(volRight) / float(volLeft)) - 1.0f; + } return sequencer->playNote(sfxDef->program, sfxDef->note, volume, pan, (u8) std::max(pitch_min, pitch_max), pitch_min, pitch_max); } @@ -583,16 +591,22 @@ void parseMidiStream(sean::Sequence* seq, std::vector seqData, s32 trackI break; case 0xe: // Pitch Bend { - // TODO - used in scrabania somewhere - NOT WORKING - u8 patchId = 0; - u8 bend = 0; - stream.ReadUInt8(patchId); - stream.ReadUInt8(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? sean::MIDIMessage* msg = seq->createMIDIMessage(); msg->type = sean::PITCH_BEND; - msg->patchId = patchId; // TODO - thses are wrong... was original bend = u16... is it patch? - msg->bend = bend; + msg->bend = (s16) multi; + msg->channelId = channel; msg->tick = deltaTime; } break; diff --git a/Source/AliveLibCommon/audio/Sequencer.cpp b/Source/AliveLibCommon/audio/Sequencer.cpp index d58a37bcc..46bd1ec50 100644 --- a/Source/AliveLibCommon/audio/Sequencer.cpp +++ b/Source/AliveLibCommon/audio/Sequencer.cpp @@ -383,18 +383,15 @@ void Sequencer::tickSequence() } case END_TRACK: { - // repeats are handled in the sequence when messages starts at position 1 again + // repeats are handled in the sequence when messages starts at position 0 again break; } case PITCH_BEND: { - //std::cout << seq << " " << (s16) message->patchId << " " << (s16) message->bend << std::endl; for (int i = 0; i < voiceCount; i++) { if (voices[i]->inUse && voices[i]->channelId == message->channelId) { - // TODO - not working - std::cout << (s16)message->bend << std::endl; voices[i]->pitch = message->bend; } } From f1359e224e7f793800d7e4a0512b5df912caf346 Mon Sep 17 00:00:00 2001 From: Sean Horton <3359434+sean-horton@users.noreply.github.com> Date: Tue, 7 Mar 2023 21:47:41 -0500 Subject: [PATCH 24/82] not much better --- Source/AliveLibCommon/audio/MidiPlayer.cpp | 4 ++-- Source/AliveLibCommon/audio/Sequencer.cpp | 1 + Source/AliveLibCommon/audio/Sequencer.hpp | 6 +++--- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Source/AliveLibCommon/audio/MidiPlayer.cpp b/Source/AliveLibCommon/audio/MidiPlayer.cpp index 75ca2052f..c328804cc 100644 --- a/Source/AliveLibCommon/audio/MidiPlayer.cpp +++ b/Source/AliveLibCommon/audio/MidiPlayer.cpp @@ -28,7 +28,6 @@ class DefaultSoundSampleParser : public SoundSampleParser u32 sampleRate = *reinterpret_cast(&ppVabBody[pos]); pos += sizeof(u32); - sampleRate; u8* data = new u8[size]; memcpy(data, &ppVabBody[pos], size); @@ -37,6 +36,7 @@ class DefaultSoundSampleParser : public SoundSampleParser Sample* sample = new Sample(); sample->m_SampleBuffer = reinterpret_cast(data); sample->i_SampleSize = size / 2; + sample->sampleRate = sampleRate; samples.push_back(sample); } @@ -118,7 +118,7 @@ void MidiPlayer::SND_Load_VABS(SoundBlockInfo* pSoundBlockInfo, s32 reverb) sean::ADSR adsr = sean::parseADSR(ADSR1, ADSR2); sean::Patch* patch = sequencer->createPatch(vagAttr->field_14_prog); Sample* s = samples.at(vagAttr->field_16_vag - 1); - sean::Sample* sample = new sean::Sample(s->m_SampleBuffer, s->i_SampleSize * 2); + sean::Sample* sample = new sean::Sample(s->m_SampleBuffer, s->i_SampleSize * 2, 22050); // TODO sample-sampleRate? patch->samples[x] = sample; sample->adsr = adsr; diff --git a/Source/AliveLibCommon/audio/Sequencer.cpp b/Source/AliveLibCommon/audio/Sequencer.cpp index 46bd1ec50..964cdd4a3 100644 --- a/Source/AliveLibCommon/audio/Sequencer.cpp +++ b/Source/AliveLibCommon/audio/Sequencer.cpp @@ -264,6 +264,7 @@ void Sequencer::playSeq(s32 seqId) { if (seq->id == seqId) { + seq->repeats = 0; seq->play = true; } } diff --git a/Source/AliveLibCommon/audio/Sequencer.hpp b/Source/AliveLibCommon/audio/Sequencer.hpp index 7a8789be2..9f61cfe89 100644 --- a/Source/AliveLibCommon/audio/Sequencer.hpp +++ b/Source/AliveLibCommon/audio/Sequencer.hpp @@ -39,10 +39,10 @@ ADSR parseADSR(u16 adsr1, u16 adsr2); class Sample { public: - Sample(ALvoid* buffer, ALsizei size) + Sample(ALvoid* buffer, ALsizei size, ALsizei sampleRate) { alGenBuffers(1, &alBuffer); - alBufferData(alBuffer, AL_FORMAT_MONO16, buffer, size, 22050); // TODO - are sample rates different? + alBufferData(alBuffer, AL_FORMAT_MONO16, buffer, size, sampleRate); } ~Sample() { @@ -286,7 +286,7 @@ class Sequencer std::vector sequences; - static const int voiceCount = 24; + static const int voiceCount = 256; Voice* voices[voiceCount]; static const int patchCount = 128; From 546f403de3b1d201e9462b6a08df67f09e567d31 Mon Sep 17 00:00:00 2001 From: Sean Horton <3359434+sean-horton@users.noreply.github.com> Date: Sat, 18 Mar 2023 17:32:35 -0400 Subject: [PATCH 25/82] checkpoint again --- Source/AliveLibAE/Sound/Midi.cpp | 1 + Source/AliveLibCommon/CMakeLists.txt | 13 +- .../audio/mixer/AudioStream.cpp | 19 + .../audio/mixer/AudioStream.hpp | 8 + Source/AliveLibCommon/audio/mixer/SPU.cpp | 2582 +++++++++++++++++ Source/AliveLibCommon/audio/mixer/SPU.hpp | 60 + Source/AliveLibCommon/audio/mixer/SPUEMU.cpp | 1053 +++++++ Source/AliveLibCommon/audio/mixer/SPUEMU.hpp | 34 + 8 files changed, 3768 insertions(+), 2 deletions(-) create mode 100644 Source/AliveLibCommon/audio/mixer/AudioStream.cpp create mode 100644 Source/AliveLibCommon/audio/mixer/AudioStream.hpp create mode 100644 Source/AliveLibCommon/audio/mixer/SPU.cpp create mode 100644 Source/AliveLibCommon/audio/mixer/SPU.hpp create mode 100644 Source/AliveLibCommon/audio/mixer/SPUEMU.cpp create mode 100644 Source/AliveLibCommon/audio/mixer/SPUEMU.hpp diff --git a/Source/AliveLibAE/Sound/Midi.cpp b/Source/AliveLibAE/Sound/Midi.cpp index a5e76adfb..3112ebdc0 100644 --- a/Source/AliveLibAE/Sound/Midi.cpp +++ b/Source/AliveLibAE/Sound/Midi.cpp @@ -979,6 +979,7 @@ class AESoundSampleParser : public psx::SoundSampleParser psx::Sample* sample = new psx::Sample(); sample->m_SampleBuffer = reinterpret_cast(data); sample->i_SampleSize = size / 2; + sample->sampleRate = 22050; samples.push_back(sample); } diff --git a/Source/AliveLibCommon/CMakeLists.txt b/Source/AliveLibCommon/CMakeLists.txt index dd0e8e8a3..aacda2e37 100644 --- a/Source/AliveLibCommon/CMakeLists.txt +++ b/Source/AliveLibCommon/CMakeLists.txt @@ -35,6 +35,10 @@ SET(AliveLibSrcCommon PSXMDECDecoder.h Psx_common.hpp W32CrashHandler.hpp + audio/Sequencer.hpp + audio/Sequencer.cpp + audio/SequencePlayer.hpp + audio/SequencePlayer.cpp audio/mixer/AliveAudio.hpp audio/mixer/AliveAudio.cpp audio/mixer/Voice.hpp @@ -46,12 +50,17 @@ SET(AliveLibSrcCommon audio/Stream.cpp audio/Soundbank.hpp audio/Soundbank.cpp - audio/SequencePlayer.hpp - audio/SequencePlayer.cpp audio/MidiPlayer.hpp audio/MidiPlayer.cpp audio/mixer/biquad.hpp audio/mixer/biquad.cpp + audio/mixer/psxadsr.hpp + audio/mixer/psxadsr.cpp + audio/mixer/BitField.hpp + audio/mixer/SPUEMU.hpp + audio/mixer/SPUEMU.cpp + audio/mixer/AudioStream.hpp + audio/mixer/AudioStream.cpp ) add_library(AliveLibCommon ${AliveLibSrcCommon}) diff --git a/Source/AliveLibCommon/audio/mixer/AudioStream.cpp b/Source/AliveLibCommon/audio/mixer/AudioStream.cpp new file mode 100644 index 000000000..b2fb5b0a1 --- /dev/null +++ b/Source/AliveLibCommon/audio/mixer/AudioStream.cpp @@ -0,0 +1,19 @@ +#pragma once + +#include "AudioStream.hpp" + +// Constructor will prepare SDL and SDL callback +// Will need to maintain a buffer that gets copied to SDL +// Will probably have to be some 20ms behind so SDL +// will always have samples available + +void AudioStream::BeginWrite(s16** buffer_ptr, u32* num_frames) +{ + buffer_ptr; + num_frames; +} + +void AudioStream::EndWrite(u32 num_frames) +{ + num_frames; +} \ No newline at end of file diff --git a/Source/AliveLibCommon/audio/mixer/AudioStream.hpp b/Source/AliveLibCommon/audio/mixer/AudioStream.hpp new file mode 100644 index 000000000..1b50a8f7f --- /dev/null +++ b/Source/AliveLibCommon/audio/mixer/AudioStream.hpp @@ -0,0 +1,8 @@ +#pragma once + +class AudioStream +{ +public: + void BeginWrite(s16** buffer_ptr, u32* num_frames); + void EndWrite(u32 num_frames); +}; \ No newline at end of file diff --git a/Source/AliveLibCommon/audio/mixer/SPU.cpp b/Source/AliveLibCommon/audio/mixer/SPU.cpp new file mode 100644 index 000000000..2776f63ec --- /dev/null +++ b/Source/AliveLibCommon/audio/mixer/SPU.cpp @@ -0,0 +1,2582 @@ +// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin +// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) + +#include "spu.hpp" +#include "cdrom.h" +#include "common/bitfield.h" +#include "common/fifo_queue.h" +#include "common/file_system.h" +#include "common/log.h" +#include "dma.h" +#include "host.h" +#include "imgui.h" +#include "interrupt_controller.h" +#include "system.h" +#include "util/audio_stream.h" +#include "util/state_wrapper.h" +#include "util/wav_writer.h" +#include +Log_SetChannel(SPU); + +// Enable to dump all voices of the SPU audio individually. +// #define SPU_DUMP_ALL_VOICES 1 + +ALWAYS_INLINE static constexpr s32 Clamp16(s32 value) +{ + return (value < -0x8000) ? -0x8000 : (value > 0x7FFF) ? 0x7FFF + : value; +} + +ALWAYS_INLINE static constexpr s32 ApplyVolume(s32 sample, s16 volume) +{ + return (sample * s32(volume)) >> 15; +} + +namespace SPU { +enum : u32 +{ + SPU_BASE = 0x1F801C00, + NUM_CHANNELS = 2, + NUM_VOICES = 24, + NUM_VOICE_REGISTERS = 8, + VOICE_ADDRESS_SHIFT = 3, + NUM_SAMPLES_PER_ADPCM_BLOCK = 28, + NUM_SAMPLES_FROM_LAST_ADPCM_BLOCK = 3, + SYSCLK_TICKS_PER_SPU_TICK = System::MASTER_CLOCK / SAMPLE_RATE, // 0x300 + CAPTURE_BUFFER_SIZE_PER_CHANNEL = 0x400, + MINIMUM_TICKS_BETWEEN_KEY_ON_OFF = 2, + NUM_REVERB_REGS = 32, + FIFO_SIZE_IN_HALFWORDS = 32 +}; +enum : s16 +{ + ENVELOPE_MIN_VOLUME = 0, + ENVELOPE_MAX_VOLUME = 0x7FFF +}; +enum : TickCount +{ + TRANSFER_TICKS_PER_HALFWORD = 16 +}; + +enum class RAMTransferMode : u8 +{ + Stopped = 0, + ManualWrite = 1, + DMAWrite = 2, + DMARead = 3 +}; + +union SPUCNT +{ + u16 bits; + + BitField enable; + BitField mute_n; + BitField noise_clock; + BitField reverb_master_enable; + BitField irq9_enable; + BitField ram_transfer_mode; + BitField external_audio_reverb; + BitField cd_audio_reverb; + BitField external_audio_enable; + BitField cd_audio_enable; + + BitField mode; +}; + +union SPUSTAT +{ + u16 bits; + + BitField second_half_capture_buffer; + BitField transfer_busy; + BitField dma_write_request; + BitField dma_read_request; + BitField dma_request; + BitField irq9_flag; + BitField mode; +}; + +union TransferControl +{ + u16 bits; + + BitField mode; +}; + +union ADSRRegister +{ + u32 bits; + struct + { + u16 bits_low; + u16 bits_high; + }; + + BitField sustain_level; + BitField decay_rate_shr2; + BitField attack_rate; + BitField attack_exponential; + + BitField release_rate_shr2; + BitField release_exponential; + BitField sustain_rate; + BitField sustain_direction_decrease; + BitField sustain_exponential; +}; + +union VolumeRegister +{ + u16 bits; + + BitField sweep_mode; + BitField fixed_volume_shr1; // divided by 2 + + BitField sweep_exponential; + BitField sweep_direction_decrease; + BitField sweep_phase_negative; + BitField sweep_rate; +}; + +// organized so we can replace this with a u16 array in the future +union VoiceRegisters +{ + u16 index[NUM_VOICE_REGISTERS]; + + struct + { + VolumeRegister volume_left; + VolumeRegister volume_right; + + u16 adpcm_sample_rate; // VxPitch + u16 adpcm_start_address; // multiply by 8 + + ADSRRegister adsr; + s16 adsr_volume; + + u16 adpcm_repeat_address; // multiply by 8 + }; +}; + +union VoiceCounter +{ + // promoted to u32 because of overflow + u32 bits; + + BitField interpolation_index; + BitField sample_index; +}; + +union ADPCMFlags +{ + u8 bits; + + BitField loop_end; + BitField loop_repeat; + BitField loop_start; +}; + +struct ADPCMBlock +{ + union + { + u8 bits; + + BitField shift; + BitField filter; + } shift_filter; + ADPCMFlags flags; + u8 data[NUM_SAMPLES_PER_ADPCM_BLOCK / 2]; + + // For both 4bit and 8bit ADPCM, reserved shift values 13..15 will act same as shift=9). + u8 GetShift() const + { + const u8 shift = shift_filter.shift; + return (shift > 12) ? 9 : shift; + } + + u8 GetFilter() const + { + return std::min(shift_filter.filter, 4); + } + + u8 GetNibble(u32 index) const + { + return (data[index / 2] >> ((index % 2) * 4)) & 0x0F; + } +}; + +struct VolumeEnvelope +{ + s32 counter; + u8 rate; + bool decreasing; + bool exponential; + + void Reset(u8 rate_, bool decreasing_, bool exponential_); + s16 Tick(s16 current_level); +}; + +struct VolumeSweep +{ + VolumeEnvelope envelope; + bool envelope_active; + s16 current_level; + + void Reset(VolumeRegister reg); + void Tick(); +}; + +enum class ADSRPhase : u8 +{ + Off = 0, + Attack = 1, + Decay = 2, + Sustain = 3, + Release = 4 +}; + +struct Voice +{ + u16 current_address; + VoiceRegisters regs; + VoiceCounter counter; + ADPCMFlags current_block_flags; + bool is_first_block; + std::array current_block_samples; + std::array adpcm_last_samples; + s32 last_volume; + + VolumeSweep left_volume; + VolumeSweep right_volume; + + VolumeEnvelope adsr_envelope; + ADSRPhase adsr_phase; + s16 adsr_target; + bool has_samples; + bool ignore_loop_address; + + bool IsOn() const + { + return adsr_phase != ADSRPhase::Off; + } + + void KeyOn(); + void KeyOff(); + void ForceOff(); + + void DecodeBlock(const ADPCMBlock& block); + s32 Interpolate() const; + + // Switches to the specified phase, filling in target. + void UpdateADSREnvelope(); + + // Updates the ADSR volume/phase. + void TickADSR(); +}; + +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]; + }; + + u16 rev[NUM_REVERB_REGS]; + }; +}; + +static ADSRPhase GetNextADSRPhase(ADSRPhase phase); + +bool IsVoiceReverbEnabled(u32 i); +bool IsVoiceNoiseEnabled(u32 i); +bool IsPitchModulationEnabled(u32 i); +s16 GetVoiceNoiseLevel(); + +u16 ReadVoiceRegister(u32 offset); +void WriteVoiceRegister(u32 offset, u16 value); + +static bool IsRAMIRQTriggerable(); +static bool CheckRAMIRQ(u32 address); +static void TriggerRAMIRQ(); +static void CheckForLateRAMIRQs(); + +static void WriteToCaptureBuffer(u32 index, s16 value); +static void IncrementCaptureBufferPosition(); + +static void ReadADPCMBlock(u16 address, ADPCMBlock* block); +static std::tuple SampleVoice(u32 voice_index); + +static void UpdateNoise(); + +static u32 ReverbMemoryAddress(u32 address); +static s16 ReverbRead(u32 address, s32 offset = 0); +static void ReverbWrite(u32 address, s16 data); +static void ProcessReverb(s16 left_in, s16 right_in, s32* left_out, s32* right_out); + +static void Execute(void* param, TickCount ticks, TickCount ticks_late); +static void UpdateEventInterval(); + +static void ExecuteFIFOWriteToRAM(TickCount& ticks); +static void ExecuteFIFOReadFromRAM(TickCount& ticks); +static void ExecuteTransfer(void* param, TickCount ticks, TickCount ticks_late); +static void ManualTransferWrite(u16 value); +static void UpdateTransferEvent(); +static void UpdateDMARequest(); + +static void CreateOutputStream(); + +static std::unique_ptr s_tick_event; +static std::unique_ptr s_transfer_event; +static std::unique_ptr s_dump_writer; +static std::unique_ptr s_audio_stream; +static std::unique_ptr s_null_audio_stream; +static bool s_audio_output_muted = false; + +static TickCount s_ticks_carry = 0; +static TickCount s_cpu_ticks_per_spu_tick = 0; +static TickCount s_cpu_tick_divider = 0; + +static SPUCNT s_SPUCNT = {}; +static SPUSTAT s_SPUSTAT = {}; + +static TransferControl s_transfer_control = {}; +static u16 s_transfer_address_reg = 0; +static u32 s_transfer_address = 0; + +static u16 s_irq_address = 0; +static u16 s_capture_buffer_position = 0; + +static VolumeRegister s_main_volume_left_reg = {}; +static VolumeRegister s_main_volume_right_reg = {}; +static VolumeSweep s_main_volume_left = {}; +static VolumeSweep s_main_volume_right = {}; + +static s16 s_cd_audio_volume_left = 0; +static s16 s_cd_audio_volume_right = 0; + +static s16 s_external_volume_left = 0; +static s16 s_external_volume_right = 0; + +static u32 s_key_on_register = 0; +static u32 s_key_off_register = 0; +static u32 s_endx_register = 0; +static u32 s_pitch_modulation_enable_register = 0; + +static u32 s_noise_mode_register = 0; +static u32 s_noise_count = 0; +static u32 s_noise_level = 0; + +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; + +static std::array s_voices{}; + +static InlineFIFOQueue s_transfer_fifo; + +static std::array s_ram{}; + +#ifdef SPU_DUMP_ALL_VOICES +// +1 for reverb output +static std::array, NUM_VOICES + 1> s_voice_dump_writers; +#endif +} // namespace SPU + +void SPU::Initialize() +{ + // (X * D) / N / 768 -> (X * D) / (N * 768) + s_cpu_ticks_per_spu_tick = System::ScaleTicksToOverclock(SYSCLK_TICKS_PER_SPU_TICK); + s_cpu_tick_divider = static_cast(g_settings.cpu_overclock_numerator * SYSCLK_TICKS_PER_SPU_TICK); + s_tick_event = TimingEvents::CreateTimingEvent("SPU Sample", s_cpu_ticks_per_spu_tick, s_cpu_ticks_per_spu_tick, + &SPU::Execute, nullptr, false); + s_transfer_event = TimingEvents::CreateTimingEvent( + "SPU Transfer", TRANSFER_TICKS_PER_HALFWORD, TRANSFER_TICKS_PER_HALFWORD, &SPU::ExecuteTransfer, nullptr, false); + s_null_audio_stream = AudioStream::CreateNullStream(SAMPLE_RATE, NUM_CHANNELS, g_settings.audio_buffer_ms); + + CreateOutputStream(); + Reset(); +} + +void SPU::CreateOutputStream() +{ + Log_InfoPrintf( + "Creating '%s' audio stream, sample rate = %u, channels = %u, buffer = %u, latency = %u, stretching = %s", + Settings::GetAudioBackendName(g_settings.audio_backend), SAMPLE_RATE, NUM_CHANNELS, g_settings.audio_buffer_ms, + g_settings.audio_output_latency_ms, AudioStream::GetStretchModeName(g_settings.audio_stretch_mode)); + + s_audio_stream = Host::CreateAudioStream(g_settings.audio_backend, SAMPLE_RATE, NUM_CHANNELS, g_settings.audio_buffer_ms, + g_settings.audio_output_latency_ms, g_settings.audio_stretch_mode); + if (!s_audio_stream) + { + Host::ReportErrorAsync("Error", "Failed to create or configure audio stream, falling back to null output."); + s_audio_stream.reset(); + s_audio_stream = AudioStream::CreateNullStream(SAMPLE_RATE, NUM_CHANNELS, g_settings.audio_buffer_ms); + } + + s_audio_stream->SetOutputVolume(System::GetAudioOutputVolume()); + s_audio_stream->SetPaused(System::IsPaused()); +} + +void SPU::RecreateOutputStream() +{ + s_audio_stream.reset(); + CreateOutputStream(); +} + +void SPU::CPUClockChanged() +{ + // (X * D) / N / 768 -> (X * D) / (N * 768) + s_cpu_ticks_per_spu_tick = System::ScaleTicksToOverclock(SYSCLK_TICKS_PER_SPU_TICK); + s_cpu_tick_divider = static_cast(g_settings.cpu_overclock_numerator * SYSCLK_TICKS_PER_SPU_TICK); + s_ticks_carry = 0; + UpdateEventInterval(); +} + +void SPU::Shutdown() +{ + StopDumpingAudio(); + s_tick_event.reset(); + s_transfer_event.reset(); + s_audio_stream.reset(); +} + +void SPU::Reset() +{ + s_ticks_carry = 0; + + s_SPUCNT.bits = 0; + s_SPUSTAT.bits = 0; + s_transfer_address = 0; + s_transfer_address_reg = 0; + s_irq_address = 0; + s_capture_buffer_position = 0; + s_main_volume_left_reg.bits = 0; + s_main_volume_right_reg.bits = 0; + s_main_volume_left = {}; + s_main_volume_right = {}; + s_cd_audio_volume_left = 0; + s_cd_audio_volume_right = 0; + s_external_volume_left = 0; + s_external_volume_right = 0; + s_key_on_register = 0; + s_key_off_register = 0; + s_endx_register = 0; + s_pitch_modulation_enable_register = 0; + + s_noise_mode_register = 0; + s_noise_count = 0; + s_noise_level = 1; + + s_reverb_on_register = 0; + s_reverb_registers = {}; + s_reverb_registers.mBASE = 0; + s_reverb_base_address = s_reverb_current_address = ZeroExtend32(s_reverb_registers.mBASE) << 2; + s_reverb_downsample_buffer = {}; + s_reverb_upsample_buffer = {}; + s_reverb_resample_buffer_position = 0; + + for (u32 i = 0; i < NUM_VOICES; i++) + { + Voice& v = s_voices[i]; + v.current_address = 0; + std::fill_n(v.regs.index, NUM_VOICE_REGISTERS, u16(0)); + v.counter.bits = 0; + v.current_block_flags.bits = 0; + v.is_first_block = 0; + v.current_block_samples.fill(s16(0)); + v.adpcm_last_samples.fill(s32(0)); + v.adsr_envelope.Reset(0, false, false); + v.adsr_phase = ADSRPhase::Off; + v.adsr_target = 0; + v.has_samples = false; + v.ignore_loop_address = false; + } + + s_transfer_fifo.Clear(); + s_transfer_event->Deactivate(); + s_ram.fill(0); + UpdateEventInterval(); +} + +bool SPU::DoState(StateWrapper& sw) +{ + sw.Do(&s_ticks_carry); + sw.Do(&s_SPUCNT.bits); + sw.Do(&s_SPUSTAT.bits); + sw.Do(&s_transfer_control.bits); + sw.Do(&s_transfer_address); + sw.Do(&s_transfer_address_reg); + sw.Do(&s_irq_address); + sw.Do(&s_capture_buffer_position); + sw.Do(&s_main_volume_left_reg.bits); + sw.Do(&s_main_volume_right_reg.bits); + sw.DoPOD(&s_main_volume_left); + sw.DoPOD(&s_main_volume_right); + sw.Do(&s_cd_audio_volume_left); + sw.Do(&s_cd_audio_volume_right); + sw.Do(&s_external_volume_left); + sw.Do(&s_external_volume_right); + sw.Do(&s_key_on_register); + sw.Do(&s_key_off_register); + sw.Do(&s_endx_register); + sw.Do(&s_pitch_modulation_enable_register); + sw.Do(&s_noise_mode_register); + sw.Do(&s_noise_count); + sw.Do(&s_noise_level); + sw.Do(&s_reverb_on_register); + sw.Do(&s_reverb_base_address); + sw.Do(&s_reverb_current_address); + sw.Do(&s_reverb_registers.vLOUT); + sw.Do(&s_reverb_registers.vROUT); + sw.Do(&s_reverb_registers.mBASE); + sw.DoArray(s_reverb_registers.rev, NUM_REVERB_REGS); + for (u32 i = 0; i < 2; i++) + sw.DoArray(s_reverb_downsample_buffer.data(), s_reverb_downsample_buffer.size()); + for (u32 i = 0; i < 2; i++) + sw.DoArray(s_reverb_upsample_buffer.data(), s_reverb_upsample_buffer.size()); + sw.Do(&s_reverb_resample_buffer_position); + for (u32 i = 0; i < NUM_VOICES; i++) + { + Voice& v = s_voices[i]; + sw.Do(&v.current_address); + sw.DoArray(v.regs.index, NUM_VOICE_REGISTERS); + sw.Do(&v.counter.bits); + sw.Do(&v.current_block_flags.bits); + sw.DoEx(&v.is_first_block, 47, false); + sw.DoArray(&v.current_block_samples[NUM_SAMPLES_FROM_LAST_ADPCM_BLOCK], NUM_SAMPLES_PER_ADPCM_BLOCK); + sw.DoArray(&v.current_block_samples[0], NUM_SAMPLES_FROM_LAST_ADPCM_BLOCK); + sw.Do(&v.adpcm_last_samples); + sw.Do(&v.last_volume); + sw.DoPOD(&v.left_volume); + sw.DoPOD(&v.right_volume); + sw.DoPOD(&v.adsr_envelope); + sw.Do(&v.adsr_phase); + sw.Do(&v.adsr_target); + sw.Do(&v.has_samples); + sw.Do(&v.ignore_loop_address); + } + + sw.Do(&s_transfer_fifo); + sw.DoBytes(s_ram.data(), RAM_SIZE); + + if (sw.IsReading()) + { + UpdateEventInterval(); + UpdateTransferEvent(); + } + + return !sw.HasError(); +} + +u16 SPU::ReadRegister(u32 offset) +{ + switch (offset) + { + case 0x1F801D80 - SPU_BASE: + return s_main_volume_left_reg.bits; + + case 0x1F801D82 - SPU_BASE: + return s_main_volume_right_reg.bits; + + case 0x1F801D84 - SPU_BASE: + return s_reverb_registers.vLOUT; + + case 0x1F801D86 - SPU_BASE: + return s_reverb_registers.vROUT; + + case 0x1F801D88 - SPU_BASE: + return Truncate16(s_key_on_register); + + case 0x1F801D8A - SPU_BASE: + return Truncate16(s_key_on_register >> 16); + + case 0x1F801D8C - SPU_BASE: + return Truncate16(s_key_off_register); + + case 0x1F801D8E - SPU_BASE: + return Truncate16(s_key_off_register >> 16); + + case 0x1F801D90 - SPU_BASE: + return Truncate16(s_pitch_modulation_enable_register); + + case 0x1F801D92 - SPU_BASE: + return Truncate16(s_pitch_modulation_enable_register >> 16); + + case 0x1F801D94 - SPU_BASE: + return Truncate16(s_noise_mode_register); + + case 0x1F801D96 - SPU_BASE: + return Truncate16(s_noise_mode_register >> 16); + + case 0x1F801D98 - SPU_BASE: + return Truncate16(s_reverb_on_register); + + case 0x1F801D9A - SPU_BASE: + return Truncate16(s_reverb_on_register >> 16); + + case 0x1F801D9C - SPU_BASE: + return Truncate16(s_endx_register); + + case 0x1F801D9E - SPU_BASE: + return Truncate16(s_endx_register >> 16); + + case 0x1F801DA2 - SPU_BASE: + return s_reverb_registers.mBASE; + + case 0x1F801DA4 - SPU_BASE: + Log_TracePrintf("SPU IRQ address -> 0x%04X", ZeroExtend32(s_irq_address)); + return s_irq_address; + + case 0x1F801DA6 - SPU_BASE: + Log_TracePrintf("SPU transfer address register -> 0x%04X", ZeroExtend32(s_transfer_address_reg)); + return s_transfer_address_reg; + + case 0x1F801DA8 - SPU_BASE: + Log_TracePrintf("SPU transfer data register read"); + return UINT16_C(0xFFFF); + + case 0x1F801DAA - SPU_BASE: + Log_TracePrintf("SPU control register -> 0x%04X", ZeroExtend32(s_SPUCNT.bits)); + return s_SPUCNT.bits; + + case 0x1F801DAC - SPU_BASE: + Log_TracePrintf("SPU transfer control register -> 0x%04X", ZeroExtend32(s_transfer_control.bits)); + return s_transfer_control.bits; + + case 0x1F801DAE - SPU_BASE: + GeneratePendingSamples(); + Log_TracePrintf("SPU status register -> 0x%04X", ZeroExtend32(s_SPUCNT.bits)); + return s_SPUSTAT.bits; + + case 0x1F801DB0 - SPU_BASE: + return s_cd_audio_volume_left; + + case 0x1F801DB2 - SPU_BASE: + return s_cd_audio_volume_right; + + case 0x1F801DB4 - SPU_BASE: + return s_external_volume_left; + + case 0x1F801DB6 - SPU_BASE: + return s_external_volume_right; + + case 0x1F801DB8 - SPU_BASE: + GeneratePendingSamples(); + return s_main_volume_left.current_level; + + case 0x1F801DBA - SPU_BASE: + GeneratePendingSamples(); + return s_main_volume_right.current_level; + + default: + { + if (offset < (0x1F801D80 - SPU_BASE)) + return ReadVoiceRegister(offset); + + if (offset >= (0x1F801DC0 - SPU_BASE) && offset < (0x1F801E00 - SPU_BASE)) + return s_reverb_registers.rev[(offset - (0x1F801DC0 - SPU_BASE)) / 2]; + + if (offset >= (0x1F801E00 - SPU_BASE) && offset < (0x1F801E60 - SPU_BASE)) + { + const u32 voice_index = (offset - (0x1F801E00 - SPU_BASE)) / 4; + GeneratePendingSamples(); + if (offset & 0x02) + return s_voices[voice_index].left_volume.current_level; + else + return s_voices[voice_index].right_volume.current_level; + } + + Log_DevPrintf("Unknown SPU register read: offset 0x%X (address 0x%08X)", offset, offset | SPU_BASE); + return UINT16_C(0xFFFF); + } + } +} + +void SPU::WriteRegister(u32 offset, u16 value) +{ + switch (offset) + { + case 0x1F801D80 - SPU_BASE: + { + Log_DebugPrintf("SPU main volume left <- 0x%04X", ZeroExtend32(value)); + GeneratePendingSamples(); + s_main_volume_left_reg.bits = value; + s_main_volume_left.Reset(s_main_volume_left_reg); + return; + } + + case 0x1F801D82 - SPU_BASE: + { + Log_DebugPrintf("SPU main volume right <- 0x%04X", ZeroExtend32(value)); + GeneratePendingSamples(); + s_main_volume_right_reg.bits = value; + s_main_volume_right.Reset(s_main_volume_right_reg); + return; + } + + case 0x1F801D84 - SPU_BASE: + { + Log_DebugPrintf("SPU reverb output volume left <- 0x%04X", ZeroExtend32(value)); + GeneratePendingSamples(); + s_reverb_registers.vLOUT = value; + return; + } + + case 0x1F801D86 - SPU_BASE: + { + Log_DebugPrintf("SPU reverb output volume right <- 0x%04X", ZeroExtend32(value)); + GeneratePendingSamples(); + s_reverb_registers.vROUT = value; + return; + } + + case 0x1F801D88 - SPU_BASE: + { + Log_DebugPrintf("SPU key on low <- 0x%04X", ZeroExtend32(value)); + GeneratePendingSamples(); + s_key_on_register = (s_key_on_register & 0xFFFF0000) | ZeroExtend32(value); + } + break; + + case 0x1F801D8A - SPU_BASE: + { + Log_DebugPrintf("SPU key on high <- 0x%04X", ZeroExtend32(value)); + GeneratePendingSamples(); + s_key_on_register = (s_key_on_register & 0x0000FFFF) | (ZeroExtend32(value) << 16); + } + break; + + case 0x1F801D8C - SPU_BASE: + { + Log_DebugPrintf("SPU key off low <- 0x%04X", ZeroExtend32(value)); + GeneratePendingSamples(); + s_key_off_register = (s_key_off_register & 0xFFFF0000) | ZeroExtend32(value); + } + break; + + case 0x1F801D8E - SPU_BASE: + { + Log_DebugPrintf("SPU key off high <- 0x%04X", ZeroExtend32(value)); + GeneratePendingSamples(); + s_key_off_register = (s_key_off_register & 0x0000FFFF) | (ZeroExtend32(value) << 16); + } + break; + + case 0x1F801D90 - SPU_BASE: + { + GeneratePendingSamples(); + s_pitch_modulation_enable_register = (s_pitch_modulation_enable_register & 0xFFFF0000) | ZeroExtend32(value); + Log_DebugPrintf("SPU pitch modulation enable register <- 0x%08X", s_pitch_modulation_enable_register); + } + break; + + case 0x1F801D92 - SPU_BASE: + { + GeneratePendingSamples(); + s_pitch_modulation_enable_register = (s_pitch_modulation_enable_register & 0x0000FFFF) | (ZeroExtend32(value) << 16); + Log_DebugPrintf("SPU pitch modulation enable register <- 0x%08X", s_pitch_modulation_enable_register); + } + break; + + case 0x1F801D94 - SPU_BASE: + { + Log_DebugPrintf("SPU noise mode register <- 0x%04X", ZeroExtend32(value)); + GeneratePendingSamples(); + s_noise_mode_register = (s_noise_mode_register & 0xFFFF0000) | ZeroExtend32(value); + } + break; + + case 0x1F801D96 - SPU_BASE: + { + Log_DebugPrintf("SPU noise mode register <- 0x%04X", ZeroExtend32(value)); + GeneratePendingSamples(); + s_noise_mode_register = (s_noise_mode_register & 0x0000FFFF) | (ZeroExtend32(value) << 16); + } + break; + + case 0x1F801D98 - SPU_BASE: + { + Log_DebugPrintf("SPU reverb on register <- 0x%04X", ZeroExtend32(value)); + GeneratePendingSamples(); + s_reverb_on_register = (s_reverb_on_register & 0xFFFF0000) | ZeroExtend32(value); + } + break; + + case 0x1F801D9A - SPU_BASE: + { + Log_DebugPrintf("SPU reverb on register <- 0x%04X", ZeroExtend32(value)); + GeneratePendingSamples(); + s_reverb_on_register = (s_reverb_on_register & 0x0000FFFF) | (ZeroExtend32(value) << 16); + } + break; + + case 0x1F801DA2 - SPU_BASE: + { + Log_DebugPrintf("SPU reverb base address < 0x%04X", ZeroExtend32(value)); + GeneratePendingSamples(); + s_reverb_registers.mBASE = value; + s_reverb_base_address = ZeroExtend32(value << 2) & 0x3FFFFu; + s_reverb_current_address = s_reverb_base_address; + } + break; + + case 0x1F801DA4 - SPU_BASE: + { + Log_DebugPrintf("SPU IRQ address register <- 0x%04X", ZeroExtend32(value)); + GeneratePendingSamples(); + s_irq_address = value; + + if (IsRAMIRQTriggerable()) + CheckForLateRAMIRQs(); + + return; + } + + case 0x1F801DA6 - SPU_BASE: + { + Log_DebugPrintf("SPU transfer address register <- 0x%04X", ZeroExtend32(value)); + s_transfer_event->InvokeEarly(); + s_transfer_address_reg = value; + s_transfer_address = ZeroExtend32(value) * 8; + if (IsRAMIRQTriggerable() && CheckRAMIRQ(s_transfer_address)) + { + Log_DebugPrintf("Trigger IRQ @ %08X %04X from transfer address reg set", s_transfer_address, + s_transfer_address / 8); + TriggerRAMIRQ(); + } + return; + } + + case 0x1F801DA8 - SPU_BASE: + { + Log_TracePrintf("SPU transfer data register <- 0x%04X (RAM offset 0x%08X)", ZeroExtend32(value), + s_transfer_address); + + ManualTransferWrite(value); + return; + } + + case 0x1F801DAA - SPU_BASE: + { + Log_DebugPrintf("SPU control register <- 0x%04X", ZeroExtend32(value)); + GeneratePendingSamples(); + + const SPUCNT new_value{value}; + if (new_value.ram_transfer_mode != s_SPUCNT.ram_transfer_mode && new_value.ram_transfer_mode == RAMTransferMode::Stopped) + { + // clear the fifo here? + if (!s_transfer_fifo.IsEmpty()) + { + if (s_SPUCNT.ram_transfer_mode == RAMTransferMode::DMAWrite) + { + // I would guess on the console it would gradually write the FIFO out. Hopefully nothing relies on this + // level of timing granularity if we force it all out here. + Log_WarningPrintf("Draining write SPU transfer FIFO with %u bytes left", s_transfer_fifo.GetSize()); + TickCount ticks = std::numeric_limits::max(); + ExecuteFIFOWriteToRAM(ticks); + DebugAssert(s_transfer_fifo.IsEmpty()); + } + else + { + Log_DebugPrintf("Clearing read SPU transfer FIFO with %u bytes left", s_transfer_fifo.GetSize()); + s_transfer_fifo.Clear(); + } + } + } + + if (!new_value.enable && s_SPUCNT.enable) + { + // Mute all voices. + // Interestingly, hardware tests found this seems to happen immediately, not on the next 44100hz cycle. + for (u32 i = 0; i < NUM_VOICES; i++) + s_voices[i].ForceOff(); + } + + s_SPUCNT.bits = new_value.bits; + s_SPUSTAT.mode = s_SPUCNT.mode.GetValue(); + + if (!s_SPUCNT.irq9_enable) + s_SPUSTAT.irq9_flag = false; + else if (IsRAMIRQTriggerable()) + CheckForLateRAMIRQs(); + + UpdateEventInterval(); + UpdateDMARequest(); + UpdateTransferEvent(); + return; + } + + case 0x1F801DAC - SPU_BASE: + { + Log_DebugPrintf("SPU transfer control register <- 0x%04X", ZeroExtend32(value)); + s_transfer_control.bits = value; + return; + } + + case 0x1F801DB0 - SPU_BASE: + { + Log_DebugPrintf("SPU left cd audio register <- 0x%04X", ZeroExtend32(value)); + GeneratePendingSamples(); + s_cd_audio_volume_left = value; + } + break; + + case 0x1F801DB2 - SPU_BASE: + { + Log_DebugPrintf("SPU right cd audio register <- 0x%04X", ZeroExtend32(value)); + GeneratePendingSamples(); + s_cd_audio_volume_right = value; + } + break; + + case 0x1F801DB4 - SPU_BASE: + { + // External volumes aren't used, so don't bother syncing. + Log_DebugPrintf("SPU left external volume register <- 0x%04X", ZeroExtend32(value)); + s_external_volume_left = value; + } + break; + + case 0x1F801DB6 - SPU_BASE: + { + // External volumes aren't used, so don't bother syncing. + Log_DebugPrintf("SPU right external volume register <- 0x%04X", ZeroExtend32(value)); + s_external_volume_right = value; + } + break; + + // read-only registers + case 0x1F801DAE - SPU_BASE: + { + return; + } + + default: + { + if (offset < (0x1F801D80 - SPU_BASE)) + { + WriteVoiceRegister(offset, value); + return; + } + + if (offset >= (0x1F801DC0 - SPU_BASE) && offset < (0x1F801E00 - SPU_BASE)) + { + const u32 reg = (offset - (0x1F801DC0 - SPU_BASE)) / 2; + Log_DebugPrintf("SPU reverb register %u <- 0x%04X", reg, value); + GeneratePendingSamples(); + s_reverb_registers.rev[reg] = value; + return; + } + + Log_DevPrintf("Unknown SPU register write: offset 0x%X (address 0x%08X) value 0x%04X", offset, offset | SPU_BASE, + ZeroExtend32(value)); + return; + } + } +} + +u16 SPU::ReadVoiceRegister(u32 offset) +{ + const u32 reg_index = (offset % 0x10) / 2; //(offset & 0x0F) / 2; + const u32 voice_index = (offset / 0x10); //((offset >> 4) & 0x1F); + Assert(voice_index < 24); + + // ADSR volume needs to be updated when reading. A voice might be off as well, but key on is pending. + const Voice& voice = s_voices[voice_index]; + if (reg_index >= 6 && (voice.IsOn() || s_key_on_register & (1u << voice_index))) + GeneratePendingSamples(); + + Log_TracePrintf("Read voice %u register %u -> 0x%02X", voice_index, reg_index, voice.regs.index[reg_index]); + return voice.regs.index[reg_index]; +} + +void SPU::WriteVoiceRegister(u32 offset, u16 value) +{ + // per-voice registers + const u32 reg_index = (offset % 0x10); + const u32 voice_index = (offset / 0x10); + DebugAssert(voice_index < 24); + + Voice& voice = s_voices[voice_index]; + if (voice.IsOn() || s_key_on_register & (1u << voice_index)) + GeneratePendingSamples(); + + switch (reg_index) + { + case 0x00: // volume left + { + Log_DebugPrintf("SPU voice %u volume left <- 0x%04X", voice_index, value); + voice.regs.volume_left.bits = value; + voice.left_volume.Reset(voice.regs.volume_left); + } + break; + + case 0x02: // volume right + { + Log_DebugPrintf("SPU voice %u volume right <- 0x%04X", voice_index, value); + voice.regs.volume_right.bits = value; + voice.right_volume.Reset(voice.regs.volume_right); + } + break; + + case 0x04: // sample rate + { + Log_DebugPrintf("SPU voice %u ADPCM sample rate <- 0x%04X", voice_index, value); + voice.regs.adpcm_sample_rate = value; + } + break; + + case 0x06: // start address + { + Log_DebugPrintf("SPU voice %u ADPCM start address <- 0x%04X", voice_index, value); + voice.regs.adpcm_start_address = value; + } + break; + + case 0x08: // adsr low + { + Log_DebugPrintf("SPU voice %u ADSR low <- 0x%04X (was 0x%04X)", voice_index, value, voice.regs.adsr.bits_low); + voice.regs.adsr.bits_low = value; + if (voice.IsOn()) + voice.UpdateADSREnvelope(); + } + break; + + case 0x0A: // adsr high + { + Log_DebugPrintf("SPU voice %u ADSR high <- 0x%04X (was 0x%04X)", voice_index, value, voice.regs.adsr.bits_low); + voice.regs.adsr.bits_high = value; + if (voice.IsOn()) + voice.UpdateADSREnvelope(); + } + break; + + case 0x0C: // adsr volume + { + Log_DebugPrintf("SPU voice %u ADSR volume <- 0x%04X (was 0x%04X)", voice_index, value, voice.regs.adsr_volume); + voice.regs.adsr_volume = value; + } + break; + + case 0x0E: // repeat address + { + // There is a short window of time here between the voice being keyed on and the first block finishing decoding + // where setting the repeat address will *NOT* ignore the block/loop start flag. Games sensitive to this are: + // - The Misadventures of Tron Bonne + // - Re-Loaded - The Hardcore Sequel + // - Valkyrie Profile + + const bool ignore_loop_address = voice.IsOn() && !voice.is_first_block; + Log_DebugPrintf("SPU voice %u ADPCM repeat address <- 0x%04X", voice_index, value); + voice.regs.adpcm_repeat_address = value; + voice.ignore_loop_address |= ignore_loop_address; + + if (!ignore_loop_address) + { + Log_DevPrintf("Not ignoring loop address, the ADPCM repeat address of 0x%04X for voice %u will be overwritten", + value, voice_index); + } + } + break; + + default: + { + Log_ErrorPrintf("Unknown SPU voice %u register write: offset 0x%X (address 0x%08X) value 0x%04X", offset, + voice_index, offset | SPU_BASE, ZeroExtend32(value)); + } + break; + } +} + +bool SPU::IsVoiceReverbEnabled(u32 i) +{ + return ConvertToBoolUnchecked((s_reverb_on_register >> i) & u32(1)); +} + +bool SPU::IsVoiceNoiseEnabled(u32 i) +{ + return ConvertToBoolUnchecked((s_noise_mode_register >> i) & u32(1)); +} + +bool SPU::IsPitchModulationEnabled(u32 i) +{ + return ((i > 0) && ConvertToBoolUnchecked((s_pitch_modulation_enable_register >> i) & u32(1))); +} + +s16 SPU::GetVoiceNoiseLevel() +{ + return static_cast(static_cast(s_noise_level)); +} + +bool SPU::IsRAMIRQTriggerable() +{ + return s_SPUCNT.irq9_enable && !s_SPUSTAT.irq9_flag; +} + +bool SPU::CheckRAMIRQ(u32 address) +{ + return ((ZeroExtend32(s_irq_address) * 8) == address); +} + +void SPU::TriggerRAMIRQ() +{ + DebugAssert(IsRAMIRQTriggerable()); + s_SPUSTAT.irq9_flag = true; + InterruptController::InterruptRequest(InterruptController::IRQ::SPU); +} + +void SPU::CheckForLateRAMIRQs() +{ + if (CheckRAMIRQ(s_transfer_address)) + { + Log_DebugPrintf("Trigger IRQ @ %08X %04X from late transfer", s_transfer_address, s_transfer_address / 8); + TriggerRAMIRQ(); + return; + } + + for (u32 i = 0; i < NUM_VOICES; i++) + { + // we skip voices which haven't started this block yet - because they'll check + // the next time they're sampled, and the delay might be important. + const Voice& v = s_voices[i]; + if (!v.has_samples) + continue; + + const u32 address = v.current_address * 8; + if (CheckRAMIRQ(address) || CheckRAMIRQ((address + 8) & RAM_MASK)) + { + Log_DebugPrintf("Trigger IRQ @ %08X %04X from late", address, address / 8); + TriggerRAMIRQ(); + return; + } + } +} + +void SPU::WriteToCaptureBuffer(u32 index, s16 value) +{ + const u32 ram_address = (index * CAPTURE_BUFFER_SIZE_PER_CHANNEL) | ZeroExtend16(s_capture_buffer_position); + // Log_DebugPrintf("write to capture buffer %u (0x%08X) <- 0x%04X", index, ram_address, u16(value)); + std::memcpy(&s_ram[ram_address], &value, sizeof(value)); + if (IsRAMIRQTriggerable() && CheckRAMIRQ(ram_address)) + { + Log_DebugPrintf("Trigger IRQ @ %08X %04X from capture buffer", ram_address, ram_address / 8); + TriggerRAMIRQ(); + } +} + +void SPU::IncrementCaptureBufferPosition() +{ + s_capture_buffer_position += sizeof(s16); + s_capture_buffer_position %= CAPTURE_BUFFER_SIZE_PER_CHANNEL; + s_SPUSTAT.second_half_capture_buffer = s_capture_buffer_position >= (CAPTURE_BUFFER_SIZE_PER_CHANNEL / 2); +} + +ALWAYS_INLINE_RELEASE void SPU::ExecuteFIFOReadFromRAM(TickCount& ticks) +{ + while (ticks > 0 && !s_transfer_fifo.IsFull()) + { + u16 value; + std::memcpy(&value, &s_ram[s_transfer_address], sizeof(u16)); + s_transfer_address = (s_transfer_address + sizeof(u16)) & RAM_MASK; + s_transfer_fifo.Push(value); + ticks -= TRANSFER_TICKS_PER_HALFWORD; + + if (IsRAMIRQTriggerable() && CheckRAMIRQ(s_transfer_address)) + { + Log_DebugPrintf("Trigger IRQ @ %08X %04X from transfer read", s_transfer_address, s_transfer_address / 8); + TriggerRAMIRQ(); + } + } +} + +ALWAYS_INLINE_RELEASE void SPU::ExecuteFIFOWriteToRAM(TickCount& ticks) +{ + while (ticks > 0 && !s_transfer_fifo.IsEmpty()) + { + u16 value = s_transfer_fifo.Pop(); + std::memcpy(&s_ram[s_transfer_address], &value, sizeof(u16)); + s_transfer_address = (s_transfer_address + sizeof(u16)) & RAM_MASK; + ticks -= TRANSFER_TICKS_PER_HALFWORD; + + if (IsRAMIRQTriggerable() && CheckRAMIRQ(s_transfer_address)) + { + Log_DebugPrintf("Trigger IRQ @ %08X %04X from transfer write", s_transfer_address, s_transfer_address / 8); + TriggerRAMIRQ(); + } + } +} + +void SPU::ExecuteTransfer(void* param, TickCount ticks, TickCount ticks_late) +{ + const RAMTransferMode mode = s_SPUCNT.ram_transfer_mode; + DebugAssert(mode != RAMTransferMode::Stopped); + + if (mode == RAMTransferMode::DMARead) + { + while (ticks > 0 && !s_transfer_fifo.IsFull()) + { + ExecuteFIFOReadFromRAM(ticks); + + // this can result in the FIFO being emptied, hence double the while loop + UpdateDMARequest(); + } + + // we're done if we have no more data to read + if (s_transfer_fifo.IsFull()) + { + s_SPUSTAT.transfer_busy = false; + s_transfer_event->Deactivate(); + return; + } + + s_SPUSTAT.transfer_busy = true; + const TickCount ticks_until_complete = TickCount(s_transfer_fifo.GetSpace() * u32(TRANSFER_TICKS_PER_HALFWORD)) + ((ticks < 0) ? -ticks : 0); + s_transfer_event->Schedule(ticks_until_complete); + } + else + { + // write the fifo to ram, request dma again when empty + while (ticks > 0 && !s_transfer_fifo.IsEmpty()) + { + ExecuteFIFOWriteToRAM(ticks); + + // similar deal here, the FIFO can be written out in a long slice + UpdateDMARequest(); + } + + // we're done if we have no more data to write + if (s_transfer_fifo.IsEmpty()) + { + s_SPUSTAT.transfer_busy = false; + s_transfer_event->Deactivate(); + return; + } + + s_SPUSTAT.transfer_busy = true; + const TickCount ticks_until_complete = TickCount(s_transfer_fifo.GetSize() * u32(TRANSFER_TICKS_PER_HALFWORD)) + ((ticks < 0) ? -ticks : 0); + s_transfer_event->Schedule(ticks_until_complete); + } +} + +void SPU::ManualTransferWrite(u16 value) +{ + if (!s_transfer_fifo.IsEmpty() && s_SPUCNT.ram_transfer_mode != RAMTransferMode::DMARead) + { + Log_WarningPrintf("FIFO not empty on manual SPU write, draining to hopefully avoid corruption. Game is silly."); + if (s_SPUCNT.ram_transfer_mode != RAMTransferMode::Stopped) + ExecuteTransfer(nullptr, std::numeric_limits::max(), 0); + } + + std::memcpy(&s_ram[s_transfer_address], &value, sizeof(u16)); + s_transfer_address = (s_transfer_address + sizeof(u16)) & RAM_MASK; + + if (IsRAMIRQTriggerable() && CheckRAMIRQ(s_transfer_address)) + { + Log_DebugPrintf("Trigger IRQ @ %08X %04X from manual write", s_transfer_address, s_transfer_address / 8); + TriggerRAMIRQ(); + } +} + +void SPU::UpdateTransferEvent() +{ + const RAMTransferMode mode = s_SPUCNT.ram_transfer_mode; + if (mode == RAMTransferMode::Stopped) + { + s_transfer_event->Deactivate(); + } + else if (mode == RAMTransferMode::DMARead) + { + // transfer event fills the fifo + if (s_transfer_fifo.IsFull()) + s_transfer_event->Deactivate(); + else if (!s_transfer_event->IsActive()) + s_transfer_event->Schedule(TickCount(s_transfer_fifo.GetSpace() * u32(TRANSFER_TICKS_PER_HALFWORD))); + } + else + { + // transfer event copies from fifo to ram + if (s_transfer_fifo.IsEmpty()) + s_transfer_event->Deactivate(); + else if (!s_transfer_event->IsActive()) + s_transfer_event->Schedule(TickCount(s_transfer_fifo.GetSize() * u32(TRANSFER_TICKS_PER_HALFWORD))); + } + + s_SPUSTAT.transfer_busy = s_transfer_event->IsActive(); +} + +void SPU::UpdateDMARequest() +{ + switch (s_SPUCNT.ram_transfer_mode) + { + case RAMTransferMode::DMARead: + s_SPUSTAT.dma_read_request = s_transfer_fifo.IsFull(); + s_SPUSTAT.dma_write_request = false; + s_SPUSTAT.dma_request = s_SPUSTAT.dma_read_request; + break; + + case RAMTransferMode::DMAWrite: + s_SPUSTAT.dma_read_request = false; + s_SPUSTAT.dma_write_request = s_transfer_fifo.IsEmpty(); + s_SPUSTAT.dma_request = s_SPUSTAT.dma_write_request; + break; + + case RAMTransferMode::Stopped: + case RAMTransferMode::ManualWrite: + default: + s_SPUSTAT.dma_read_request = false; + s_SPUSTAT.dma_write_request = false; + s_SPUSTAT.dma_request = false; + break; + } + + // This might call us back directly. + DMA::SetRequest(DMA::Channel::SPU, s_SPUSTAT.dma_request); +} + +void SPU::DMARead(u32* words, u32 word_count) +{ + /* + From @JaCzekanski - behavior when block size is larger than the FIFO size + for blocks <= 0x16 - all data is transferred correctly + using block size 0x20 transfer behaves strange: + % Writing 524288 bytes to SPU RAM to 0x00000000 using DMA... ok + % Reading 256 bytes from SPU RAM from 0x00001000 using DMA... ok + % 0x00001000: 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f ................ + % 0x00001010: 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f ................ + % 0x00001020: 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f !"#$%&'()*+,-./ + % 0x00001030: 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f 0123456789:;<=>? + % 0x00001040: 3e 3f 3e 3f 3e 3f 3e 3f 3e 3f 3e 3f 3e 3f 3e 3f >?>?>?>?>?>?>?>? + % 0x00001050: 3e 3f 3e 3f 3e 3f 3e 3f 3e 3f 3e 3f 3e 3f 3e 3f >?>?>?>?>?>?>?>? + % 0x00001060: 3e 3f 3e 3f 3e 3f 3e 3f 3e 3f 3e 3f 3e 3f 3e 3f >?>?>?>?>?>?>?>? + % 0x00001070: 3e 3f 3e 3f 3e 3f 3e 3f 3e 3f 3e 3f 3e 3f 3e 3f >?>?>?>?>?>?>?>? + % 0x00001080: 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f @ABCDEFGHIJKLMNO + % 0x00001090: 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f PQRSTUVWXYZ[\]^_ + % 0x000010a0: 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f `abcdefghijklmno + % 0x000010b0: 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f pqrstuvwxyz{|}~. + % 0x000010c0: 7e 7f 7e 7f 7e 7f 7e 7f 7e 7f 7e 7f 7e 7f 7e 7f ~.~.~.~.~.~.~.~. + % 0x000010d0: 7e 7f 7e 7f 7e 7f 7e 7f 7e 7f 7e 7f 7e 7f 7e 7f ~.~.~.~.~.~.~.~. + % 0x000010e0: 7e 7f 7e 7f 7e 7f 7e 7f 7e 7f 7e 7f 7e 7f 7e 7f ~.~.~.~.~.~.~.~. + % 0x000010f0: 7e 7f 7e 7f 7e 7f 7e 7f 7e 7f 7e 7f 7e 7f 7e 7f ~.~.~.~.~.~.~.~. + Using Block size = 0x10 (correct data) + % Reading 256 bytes from SPU RAM from 0x00001000 using DMA... ok + % 0x00001000: 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f ................ + % 0x00001010: 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f ................ + % 0x00001020: 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f !"#$%&'()*+,-./ + % 0x00001030: 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f 0123456789:;<=>? + % 0x00001040: 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f @ABCDEFGHIJKLMNO + % 0x00001050: 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f PQRSTUVWXYZ[\]^_ + % 0x00001060: 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f `abcdefghijklmno + % 0x00001070: 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f pqrstuvwxyz{|}~. + % 0x00001080: 80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f ................ + % 0x00001090: 90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f ................ + % 0x000010a0: a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af ................ + % 0x000010b0: b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf ................ + % 0x000010c0: c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf ................ + % 0x000010d0: d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df ................ + % 0x000010e0: e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef ................ + % 0x000010f0: f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe ff ................ + */ + + u16* halfwords = reinterpret_cast(words); + u32 halfword_count = word_count * 2; + + const u32 size = s_transfer_fifo.GetSize(); + if (word_count > size) + { + u16 fill_value = 0; + if (size > 0) + { + s_transfer_fifo.PopRange(halfwords, size); + fill_value = halfwords[size - 1]; + } + + Log_WarningPrintf("Transfer FIFO underflow, filling with 0x%04X", fill_value); + std::fill_n(&halfwords[size], halfword_count - size, fill_value); + } + else + { + s_transfer_fifo.PopRange(halfwords, halfword_count); + } + + UpdateDMARequest(); + UpdateTransferEvent(); +} + +void SPU::DMAWrite(const u32* words, u32 word_count) +{ + const u16* halfwords = reinterpret_cast(words); + u32 halfword_count = word_count * 2; + + const u32 words_to_transfer = std::min(s_transfer_fifo.GetSpace(), halfword_count); + s_transfer_fifo.PushRange(halfwords, words_to_transfer); + + if (words_to_transfer != halfword_count) + Log_WarningPrintf("Transfer FIFO overflow, dropping %u halfwords", halfword_count - words_to_transfer); + + UpdateDMARequest(); + UpdateTransferEvent(); +} + +void SPU::GeneratePendingSamples() +{ + if (s_transfer_event->IsActive()) + s_transfer_event->InvokeEarly(); + + const TickCount ticks_pending = s_tick_event->GetTicksSinceLastExecution(); + TickCount frames_to_execute; + if (g_settings.cpu_overclock_active) + { + frames_to_execute = static_cast((static_cast(ticks_pending) * g_settings.cpu_overclock_denominator) + static_cast(s_ticks_carry)) / static_cast(s_cpu_tick_divider); + } + else + { + frames_to_execute = (s_tick_event->GetTicksSinceLastExecution() + s_ticks_carry) / SYSCLK_TICKS_PER_SPU_TICK; + } + + const bool force_exec = (frames_to_execute > 0); + s_tick_event->InvokeEarly(force_exec); +} + +//bool SPU::IsDumpingAudio() +//{ +// return static_cast(s_dump_writer); +//} +// +//bool SPU::StartDumpingAudio(const char* filename) +//{ +// s_dump_writer.reset(); +// s_dump_writer = std::make_unique(); +// if (!s_dump_writer->Open(filename, SAMPLE_RATE, 2)) +// { +// Log_ErrorPrintf("Failed to open '%s'", filename); +// s_dump_writer.reset(); +// return false; +// } +// +//#ifdef SPU_DUMP_ALL_VOICES +// for (size_t i = 0; i < s_voice_dump_writers.size(); i++) +// { +// s_voice_dump_writers[i].reset(); +// s_voice_dump_writers[i] = std::make_unique(); +// +// TinyString new_suffix; +// if (i == NUM_VOICES) +// new_suffix.Assign("reverb.wav"); +// else +// new_suffix.Format("voice%u.wav", i); +// +// std::string voice_filename(FileSystem::ReplaceExtension(filename, new_suffix)); +// if (!s_voice_dump_writers[i]->Open(voice_filename.c_str(), SAMPLE_RATE, 2)) +// { +// Log_ErrorPrintf("Failed to open voice dump filename '%s'", voice_filename.c_str()); +// s_voice_dump_writers[i].reset(); +// } +// } +//#endif +// +// return true; +//} +// +//bool SPU::StopDumpingAudio() +//{ +// if (!s_dump_writer) +// return false; +// +// s_dump_writer.reset(); +// +//#ifdef SPU_DUMP_ALL_VOICES +// for (size_t i = 0; i < s_voice_dump_writers.size(); i++) +// s_voice_dump_writers[i].reset(); +//#endif +// +// return true; +//} + +const std::array& SPU::GetRAM() +{ + return s_ram; +} + +std::array& SPU::GetWritableRAM() +{ + return s_ram; +} + +bool SPU::IsAudioOutputMuted() +{ + return s_audio_output_muted; +} + +void SPU::SetAudioOutputMuted(bool muted) +{ + s_audio_output_muted = muted; +} + +AudioStream* SPU::GetOutputStream() +{ + return s_audio_stream.get(); +} + +void SPU::Voice::KeyOn() +{ + current_address = regs.adpcm_start_address & ~u16(1); + counter.bits = 0; + regs.adsr_volume = 0; + adpcm_last_samples.fill(0); + + // Samples from the previous block for interpolation should be zero. Fixes clicks in audio in Breath of Fire III. + std::fill_n(¤t_block_samples[NUM_SAMPLES_PER_ADPCM_BLOCK], NUM_SAMPLES_FROM_LAST_ADPCM_BLOCK, + static_cast(0)); + + has_samples = false; + is_first_block = true; + ignore_loop_address = false; + adsr_phase = ADSRPhase::Attack; + UpdateADSREnvelope(); +} + +void SPU::Voice::KeyOff() +{ + if (adsr_phase == ADSRPhase::Off || adsr_phase == ADSRPhase::Release) + return; + + adsr_phase = ADSRPhase::Release; + UpdateADSREnvelope(); +} + +void SPU::Voice::ForceOff() +{ + if (adsr_phase == ADSRPhase::Off) + return; + + regs.adsr_volume = 0; + adsr_phase = ADSRPhase::Off; +} + +SPU::ADSRPhase SPU::GetNextADSRPhase(ADSRPhase phase) +{ + switch (phase) + { + case ADSRPhase::Attack: + // attack -> decay + return ADSRPhase::Decay; + + case ADSRPhase::Decay: + // decay -> sustain + return ADSRPhase::Sustain; + + case ADSRPhase::Sustain: + // sustain stays in sustain until key off + return ADSRPhase::Sustain; + + default: + case ADSRPhase::Release: + // end of release disables the voice + return ADSRPhase::Off; + } +} + +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(); + +void SPU::VolumeEnvelope::Reset(u8 rate_, bool decreasing_, bool exponential_) +{ + rate = rate_; + decreasing = decreasing_; + exponential = exponential_; + + const ADSRTableEntry& table_entry = s_adsr_table[BoolToUInt8(decreasing)][rate]; + counter = table_entry.ticks; +} + +s16 SPU::VolumeEnvelope::Tick(s16 current_level) +{ + counter--; + if (counter > 0) + return current_level; + + const ADSRTableEntry& table_entry = s_adsr_table[BoolToUInt8(decreasing)][rate]; + s32 this_step = table_entry.step; + counter = table_entry.ticks; + + if (exponential) + { + if (decreasing) + { + this_step = (this_step * current_level) >> 15; + } + else + { + if (current_level >= 0x6000) + { + if (rate < 40) + { + this_step >>= 2; + } + else if (rate >= 44) + { + counter >>= 2; + } + else + { + this_step >>= 1; + counter >>= 1; + } + } + } + } + + return static_cast( + std::clamp(static_cast(current_level) + this_step, ENVELOPE_MIN_VOLUME, ENVELOPE_MAX_VOLUME)); +} + +void SPU::VolumeSweep::Reset(VolumeRegister reg) +{ + if (!reg.sweep_mode) + { + current_level = reg.fixed_volume_shr1 * 2; + envelope_active = false; + return; + } + + envelope.Reset(reg.sweep_rate, reg.sweep_direction_decrease, reg.sweep_exponential); + envelope_active = true; +} + +void SPU::VolumeSweep::Tick() +{ + if (!envelope_active) + return; + + current_level = envelope.Tick(current_level); + envelope_active = (envelope.decreasing ? (current_level > ENVELOPE_MIN_VOLUME) : (current_level < ENVELOPE_MAX_VOLUME)); +} + +void SPU::Voice::UpdateADSREnvelope() +{ + switch (adsr_phase) + { + case ADSRPhase::Off: + adsr_target = 0; + adsr_envelope.Reset(0, false, false); + return; + + case ADSRPhase::Attack: + adsr_target = 32767; // 0 -> max + adsr_envelope.Reset(regs.adsr.attack_rate, false, regs.adsr.attack_exponential); + break; + + case ADSRPhase::Decay: + adsr_target = static_cast(std::min((u32(regs.adsr.sustain_level.GetValue()) + 1) * 0x800, + ENVELOPE_MAX_VOLUME)); // max -> sustain level + adsr_envelope.Reset(regs.adsr.decay_rate_shr2 << 2, true, true); + break; + + case ADSRPhase::Sustain: + adsr_target = 0; + adsr_envelope.Reset(regs.adsr.sustain_rate, regs.adsr.sustain_direction_decrease, regs.adsr.sustain_exponential); + break; + + case ADSRPhase::Release: + adsr_target = 0; + adsr_envelope.Reset(regs.adsr.release_rate_shr2 << 2, true, regs.adsr.release_exponential); + break; + + default: + break; + } +} + +void SPU::Voice::TickADSR() +{ + regs.adsr_volume = adsr_envelope.Tick(regs.adsr_volume); + + if (adsr_phase != ADSRPhase::Sustain) + { + const bool reached_target = adsr_envelope.decreasing ? (regs.adsr_volume <= adsr_target) : (regs.adsr_volume >= adsr_target); + if (reached_target) + { + adsr_phase = GetNextADSRPhase(adsr_phase); + UpdateADSREnvelope(); + } + } +} + +void SPU::Voice::DecodeBlock(const ADPCMBlock& block) +{ + static constexpr std::array filter_table_pos = {{0, 60, 115, 98, 122}}; + static constexpr std::array filter_table_neg = {{0, 0, -52, -55, -60}}; + + // store samples needed for interpolation + current_block_samples[2] = current_block_samples[NUM_SAMPLES_FROM_LAST_ADPCM_BLOCK + NUM_SAMPLES_PER_ADPCM_BLOCK - 1]; + current_block_samples[1] = current_block_samples[NUM_SAMPLES_FROM_LAST_ADPCM_BLOCK + NUM_SAMPLES_PER_ADPCM_BLOCK - 2]; + current_block_samples[0] = current_block_samples[NUM_SAMPLES_FROM_LAST_ADPCM_BLOCK + NUM_SAMPLES_PER_ADPCM_BLOCK - 3]; + + // pre-lookup + const u8 shift = block.GetShift(); + const u8 filter_index = block.GetFilter(); + const s32 filter_pos = filter_table_pos[filter_index]; + const s32 filter_neg = filter_table_neg[filter_index]; + s16 last_samples[2] = {adpcm_last_samples[0], adpcm_last_samples[1]}; + + // samples + for (u32 i = 0; i < NUM_SAMPLES_PER_ADPCM_BLOCK; i++) + { + // extend 4-bit to 16-bit, apply shift from header and mix in previous samples + s32 sample = s32(static_cast(ZeroExtend16(block.GetNibble(i)) << 12) >> shift); + sample += (last_samples[0] * filter_pos) >> 6; + sample += (last_samples[1] * filter_neg) >> 6; + + last_samples[1] = last_samples[0]; + current_block_samples[NUM_SAMPLES_FROM_LAST_ADPCM_BLOCK + i] = last_samples[0] = static_cast(Clamp16(sample)); + } + + std::copy(last_samples, last_samples + countof(last_samples), adpcm_last_samples.begin()); + current_block_flags.bits = block.flags.bits; +} + +s32 SPU::Voice::Interpolate() const +{ + 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 // + }}; + + const u8 i = counter.interpolation_index; + const u32 s = NUM_SAMPLES_FROM_LAST_ADPCM_BLOCK + ZeroExtend32(counter.sample_index.GetValue()); + + s32 out = s32(gauss[0x0FF - i]) * s32(current_block_samples[s - 3]); + out += s32(gauss[0x1FF - i]) * s32(current_block_samples[s - 2]); + out += s32(gauss[0x100 + i]) * s32(current_block_samples[s - 1]); + out += s32(gauss[0x000 + i]) * s32(current_block_samples[s - 0]); + return out >> 15; +} + +void SPU::ReadADPCMBlock(u16 address, ADPCMBlock* block) +{ + u32 ram_address = (ZeroExtend32(address) * 8) & RAM_MASK; + if (IsRAMIRQTriggerable() && (CheckRAMIRQ(ram_address) || CheckRAMIRQ((ram_address + 8) & RAM_MASK))) + { + Log_DebugPrintf("Trigger IRQ @ %08X %04X from ADPCM reader", ram_address, ram_address / 8); + TriggerRAMIRQ(); + } + + // fast path - no wrap-around + if ((ram_address + sizeof(ADPCMBlock)) <= RAM_SIZE) + { + std::memcpy(block, &s_ram[ram_address], sizeof(ADPCMBlock)); + return; + } + + block->shift_filter.bits = s_ram[ram_address]; + ram_address = (ram_address + 1) & RAM_MASK; + block->flags.bits = s_ram[ram_address]; + ram_address = (ram_address + 1) & RAM_MASK; + for (u32 i = 0; i < 14; i++) + { + block->data[i] = s_ram[ram_address]; + ram_address = (ram_address + 1) & RAM_MASK; + } +} + +ALWAYS_INLINE_RELEASE std::tuple SPU::SampleVoice(u32 voice_index) +{ + Voice& voice = s_voices[voice_index]; + if (!voice.IsOn() && !s_SPUCNT.irq9_enable) + { + voice.last_volume = 0; + +#ifdef SPU_DUMP_ALL_VOICES + if (s_voice_dump_writers[voice_index]) + { + const s16 dump_samples[2] = {0, 0}; + s_voice_dump_writers[voice_index]->WriteFrames(dump_samples, 1); + } +#endif + + return {}; + } + + if (!voice.has_samples) + { + ADPCMBlock block; + ReadADPCMBlock(voice.current_address, &block); + voice.DecodeBlock(block); + voice.has_samples = true; + + if (voice.current_block_flags.loop_start && !voice.ignore_loop_address) + { + Log_TracePrintf("Voice %u loop start @ 0x%08X", voice_index, ZeroExtend32(voice.current_address)); + voice.regs.adpcm_repeat_address = voice.current_address; + } + } + + // skip interpolation when the volume is muted anyway + s32 volume; + if (voice.regs.adsr_volume != 0) + { + // interpolate/sample and apply ADSR volume + s32 sample; + if (IsVoiceNoiseEnabled(voice_index)) + sample = GetVoiceNoiseLevel(); + else + sample = voice.Interpolate(); + + volume = ApplyVolume(sample, voice.regs.adsr_volume); + } + else + { + volume = 0; + } + + voice.last_volume = volume; + + if (voice.adsr_phase != ADSRPhase::Off) + voice.TickADSR(); + + // Pitch modulation + u16 step = voice.regs.adpcm_sample_rate; + if (IsPitchModulationEnabled(voice_index)) + { + const s32 factor = std::clamp(s_voices[voice_index - 1].last_volume, -0x8000, 0x7FFF) + 0x8000; + step = Truncate16(static_cast((SignExtend32(step) * factor) >> 15)); + } + step = std::min(step, 0x3FFF); + + // Shouldn't ever overflow because if sample_index == 27, step == 0x4000 there won't be a carry out from the + // interpolation index. If there is a carry out, bit 12 will never be 1, so it'll never add more than 4 to + // sample_index, which should never be >27. + DebugAssert(voice.counter.sample_index < NUM_SAMPLES_PER_ADPCM_BLOCK); + voice.counter.bits += step; + + if (voice.counter.sample_index >= NUM_SAMPLES_PER_ADPCM_BLOCK) + { + // next block + voice.counter.sample_index -= NUM_SAMPLES_PER_ADPCM_BLOCK; + voice.has_samples = false; + voice.is_first_block = false; + voice.current_address += 2; + + // handle flags + if (voice.current_block_flags.loop_end) + { + s_endx_register |= (u32(1) << voice_index); + voice.current_address = voice.regs.adpcm_repeat_address & ~u16(1); + + if (!voice.current_block_flags.loop_repeat) + { + Log_TracePrintf("Voice %u loop end+mute @ 0x%08X", voice_index, ZeroExtend32(voice.current_address)); + voice.ForceOff(); + } + else + { + Log_TracePrintf("Voice %u loop end+repeat @ 0x%08X", voice_index, ZeroExtend32(voice.current_address)); + } + } + } + + // apply per-channel volume + const s32 left = ApplyVolume(volume, voice.left_volume.current_level); + const s32 right = ApplyVolume(volume, voice.right_volume.current_level); + voice.left_volume.Tick(); + voice.right_volume.Tick(); + +#ifdef SPU_DUMP_ALL_VOICES + if (s_voice_dump_writers[voice_index]) + { + const s16 dump_samples[2] = {static_cast(Clamp16(left)), static_cast(Clamp16(right))}; + s_voice_dump_writers[voice_index]->WriteFrames(dump_samples, 1); + } +#endif + + return std::make_tuple(left, right); +} + +void SPU::UpdateNoise() +{ + // Dr Hell's noise waveform, implementation borrowed from pcsx-r. + static constexpr std::array noise_wave_add = { + {1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, + 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1}}; + static constexpr std::array noise_freq_add = {{0, 84, 140, 180, 210}}; + + const u32 noise_clock = s_SPUCNT.noise_clock; + const u32 level = (0x8000u >> (noise_clock >> 2)) << 16; + + s_noise_count += 0x10000u + noise_freq_add[noise_clock & 3u]; + if ((s_noise_count & 0xFFFFu) >= noise_freq_add[4]) + { + s_noise_count += 0x10000; + s_noise_count -= noise_freq_add[noise_clock & 3u]; + } + + if (s_noise_count < level) + return; + + s_noise_count %= level; + s_noise_level = (s_noise_level << 1) | noise_wave_add[(s_noise_level >> 10) & 63u]; +} + +/************************************************************************/ +/* Reverb algorithm from Mednafen-PSX */ +/************************************************************************/ + +u32 SPU::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 SPU::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 SPU::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)); +} + +// 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]; + +ALWAYS_INLINE 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 +ALWAYS_INLINE 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; +} + +ALWAYS_INLINE static s16 ReverbSat(s32 val) +{ + return static_cast(std::clamp(val, -0x8000, 0x7FFF)); +} + +ALWAYS_INLINE static s16 ReverbNeg(s16 samp) +{ + if (samp == -32768) + return 0x7FFF; + + return -samp; +} + +ALWAYS_INLINE 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); +} + +void SPU::ProcessReverb(s16 left_in, s16 right_in, s32* left_out, s32* right_out) +{ + 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; + + s32 out[2]; + if (s_reverb_resample_buffer_position & 1u) + { + 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.reverb_master_enable) + { + const s16 IIR_INPUT_A = ReverbSat((((ReverbRead(s_reverb_registers.IIR_SRC_A[lr ^ 0]) * s_reverb_registers.IIR_COEF) >> 14) + ((downsampled[lr] * s_reverb_registers.IN_COEF[lr]) >> 14)) >> 1); + const s16 IIR_INPUT_B = ReverbSat((((ReverbRead(s_reverb_registers.IIR_SRC_B[lr ^ 1]) * s_reverb_registers.IIR_COEF) >> 14) + ((downsampled[lr] * s_reverb_registers.IN_COEF[lr]) >> 14)) >> 1); + const s16 IIR_A = ReverbSat((((IIR_INPUT_A * s_reverb_registers.IIR_ALPHA) >> 14) + (IIASM(s_reverb_registers.IIR_ALPHA, ReverbRead(s_reverb_registers.IIR_DEST_A[lr], -1)) >> 14)) >> 1); + const s16 IIR_B = ReverbSat((((IIR_INPUT_B * s_reverb_registers.IIR_ALPHA) >> 14) + (IIASM(s_reverb_registers.IIR_ALPHA, ReverbRead(s_reverb_registers.IIR_DEST_B[lr], -1)) >> 14)) >> 1); + + ReverbWrite(s_reverb_registers.IIR_DEST_A[lr], IIR_A); + ReverbWrite(s_reverb_registers.IIR_DEST_B[lr], IIR_B); + } + + const s32 ACC = ((ReverbRead(s_reverb_registers.ACC_SRC_A[lr]) * s_reverb_registers.ACC_COEF_A) >> 14) + ((ReverbRead(s_reverb_registers.ACC_SRC_B[lr]) * s_reverb_registers.ACC_COEF_B) >> 14) + ((ReverbRead(s_reverb_registers.ACC_SRC_C[lr]) * s_reverb_registers.ACC_COEF_C) >> 14) + ((ReverbRead(s_reverb_registers.ACC_SRC_D[lr]) * s_reverb_registers.ACC_COEF_D) >> 14); + + const s16 FB_A = ReverbRead(s_reverb_registers.MIX_DEST_A[lr] - s_reverb_registers.FB_SRC_A); + const s16 FB_B = ReverbRead(s_reverb_registers.MIX_DEST_B[lr] - s_reverb_registers.FB_SRC_B); + const s16 MDA = ReverbSat((ACC + ((FB_A * ReverbNeg(s_reverb_registers.FB_ALPHA)) >> 14)) >> 1); + const s16 MDB = ReverbSat( + FB_A + ((((MDA * s_reverb_registers.FB_ALPHA) >> 14) + ((FB_B * ReverbNeg(s_reverb_registers.FB_X)) >> 14)) >> 1)); + const s16 IVB = ReverbSat(FB_B + ((MDB * s_reverb_registers.FB_X) >> 15)); + + if (s_SPUCNT.reverb_master_enable) + { + ReverbWrite(s_reverb_registers.MIX_DEST_A[lr], MDA); + ReverbWrite(s_reverb_registers.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 + { + 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; + + 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); + +#ifdef SPU_DUMP_ALL_VOICES + if (s_voice_dump_writers[NUM_VOICES]) + { + const s16 dump_samples[2] = {static_cast(Clamp16(s_last_reverb_output[0])), + static_cast(Clamp16(s_last_reverb_output[1]))}; + s_voice_dump_writers[NUM_VOICES]->WriteFrames(dump_samples, 1); + } +#endif +} + +void SPU::Execute(void* param, TickCount ticks, TickCount ticks_late) +{ + u32 remaining_frames; + if (g_settings.cpu_overclock_active) + { + // (X * D) / N / 768 -> (X * D) / (N * 768) + const u64 num = (static_cast(ticks) * g_settings.cpu_overclock_denominator) + static_cast(s_ticks_carry); + remaining_frames = static_cast(num / s_cpu_tick_divider); + s_ticks_carry = static_cast(num % s_cpu_tick_divider); + } + else + { + remaining_frames = static_cast((ticks + s_ticks_carry) / SYSCLK_TICKS_PER_SPU_TICK); + s_ticks_carry = (ticks + s_ticks_carry) % SYSCLK_TICKS_PER_SPU_TICK; + } + + AudioStream* output_stream = s_audio_output_muted ? s_null_audio_stream.get() : s_audio_stream.get(); + + while (remaining_frames > 0) + { + s16* output_frame_start; + u32 output_frame_space = remaining_frames; + output_stream->BeginWrite(&output_frame_start, &output_frame_space); + + s16* output_frame = output_frame_start; + const u32 frames_in_this_batch = std::min(remaining_frames, output_frame_space); + for (u32 i = 0; i < frames_in_this_batch; i++) + { + s32 left_sum = 0; + s32 right_sum = 0; + s32 reverb_in_left = 0; + s32 reverb_in_right = 0; + + u32 reverb_on_register = s_reverb_on_register; + + for (u32 voice = 0; voice < NUM_VOICES; voice++) + { + const auto [left, right] = SampleVoice(voice); + left_sum += left; + right_sum += right; + + if (reverb_on_register & 1u) + { + reverb_in_left += left; + reverb_in_right += right; + } + reverb_on_register >>= 1; + } + + if (!s_SPUCNT.mute_n) + { + left_sum = 0; + right_sum = 0; + } + + // Update noise once per frame. + UpdateNoise(); + + // Mix in CD audio. + const auto [cd_audio_left, cd_audio_right] = CDROM::GetAudioFrame(); + if (s_SPUCNT.cd_audio_enable) + { + const s32 cd_audio_volume_left = ApplyVolume(s32(cd_audio_left), s_cd_audio_volume_left); + const s32 cd_audio_volume_right = ApplyVolume(s32(cd_audio_right), s_cd_audio_volume_right); + + left_sum += cd_audio_volume_left; + right_sum += cd_audio_volume_right; + + if (s_SPUCNT.cd_audio_reverb) + { + reverb_in_left += cd_audio_volume_left; + reverb_in_right += cd_audio_volume_right; + } + } + + // Compute reverb. + s32 reverb_out_left, reverb_out_right; + ProcessReverb(static_cast(Clamp16(reverb_in_left)), static_cast(Clamp16(reverb_in_right)), + &reverb_out_left, &reverb_out_right); + + // Mix in reverb. + left_sum += reverb_out_left; + right_sum += reverb_out_right; + + // Apply main volume after clamping. A maximum volume should not overflow here because both are 16-bit values. + *(output_frame++) = static_cast(ApplyVolume(Clamp16(left_sum), s_main_volume_left.current_level)); + *(output_frame++) = static_cast(ApplyVolume(Clamp16(right_sum), s_main_volume_right.current_level)); + s_main_volume_left.Tick(); + s_main_volume_right.Tick(); + + // Write to capture buffers. + WriteToCaptureBuffer(0, cd_audio_left); + WriteToCaptureBuffer(1, cd_audio_right); + WriteToCaptureBuffer(2, static_cast(Clamp16(s_voices[1].last_volume))); + WriteToCaptureBuffer(3, static_cast(Clamp16(s_voices[3].last_volume))); + IncrementCaptureBufferPosition(); + + // Key off/on voices after the first frame. + if (i == 0 && (s_key_off_register != 0 || s_key_on_register != 0)) + { + u32 key_off_register = s_key_off_register; + s_key_off_register = 0; + + u32 key_on_register = s_key_on_register; + s_key_on_register = 0; + + for (u32 voice = 0; voice < NUM_VOICES; voice++) + { + if (key_off_register & 1u) + s_voices[voice].KeyOff(); + key_off_register >>= 1; + + if (key_on_register & 1u) + { + s_endx_register &= ~(1u << voice); + s_voices[voice].KeyOn(); + } + key_on_register >>= 1; + } + } + } + + if (s_dump_writer) + s_dump_writer->WriteFrames(output_frame_start, frames_in_this_batch); + + output_stream->EndWrite(frames_in_this_batch); + remaining_frames -= frames_in_this_batch; + } +} + +void SPU::UpdateEventInterval() +{ + // Don't generate more than the audio buffer since in a single slice, otherwise we'll both overflow the buffers when + // we do write it, and the audio thread will underflow since it won't have enough data it the game isn't messing with + // the SPU state. + const u32 max_slice_frames = s_audio_stream->GetBufferSize(); + + // TODO: Make this predict how long until the interrupt will be hit instead... + const u32 interval = (s_SPUCNT.enable && s_SPUCNT.irq9_enable) ? 1 : max_slice_frames; + const TickCount interval_ticks = static_cast(interval) * s_cpu_ticks_per_spu_tick; + if (s_tick_event->IsActive() && s_tick_event->GetInterval() == interval_ticks) + return; + + // Ensure all pending ticks have been executed, since we won't get them back after rescheduling. + s_tick_event->InvokeEarly(true); + s_tick_event->SetInterval(interval_ticks); + + TickCount downcount = interval_ticks; + if (!g_settings.cpu_overclock_active) + downcount -= s_ticks_carry; + + s_tick_event->Schedule(downcount); +} + +//void SPU::DrawDebugStateWindow() +//{ +// static const ImVec4 active_color{1.0f, 1.0f, 1.0f, 1.0f}; +// static const ImVec4 inactive_color{0.4f, 0.4f, 0.4f, 1.0f}; +// const float framebuffer_scale = Host::GetOSDScale(); +// +// ImGui::SetNextWindowSize(ImVec2(800.0f * framebuffer_scale, 800.0f * framebuffer_scale), ImGuiCond_FirstUseEver); +// if (!ImGui::Begin("SPU State", nullptr)) +// { +// ImGui::End(); +// return; +// } +// +// // status +// if (ImGui::CollapsingHeader("Status", ImGuiTreeNodeFlags_DefaultOpen)) +// { +// static constexpr std::array transfer_modes = { +// {"Transfer Stopped", "Manual Write", "DMA Write", "DMA Read"}}; +// const std::array offsets = {{100.0f * framebuffer_scale, 200.0f * framebuffer_scale, +// 300.0f * framebuffer_scale, 420.0f * framebuffer_scale, +// 500.0f * framebuffer_scale, 600.0f * framebuffer_scale}}; +// +// ImGui::Text("Control: "); +// ImGui::SameLine(offsets[0]); +// ImGui::TextColored(s_SPUCNT.enable ? active_color : inactive_color, "SPU Enable"); +// ImGui::SameLine(offsets[1]); +// ImGui::TextColored(s_SPUCNT.mute_n ? inactive_color : active_color, "Mute SPU"); +// ImGui::SameLine(offsets[2]); +// ImGui::TextColored(s_SPUCNT.external_audio_enable ? active_color : inactive_color, "External Audio"); +// ImGui::SameLine(offsets[3]); +// ImGui::TextColored(s_SPUCNT.ram_transfer_mode != RAMTransferMode::Stopped ? active_color : inactive_color, "%s", +// transfer_modes[static_cast(s_SPUCNT.ram_transfer_mode.GetValue())]); +// +// ImGui::Text("Status: "); +// ImGui::SameLine(offsets[0]); +// ImGui::TextColored(s_SPUSTAT.irq9_flag ? active_color : inactive_color, "IRQ9"); +// ImGui::SameLine(offsets[1]); +// ImGui::TextColored(s_SPUSTAT.dma_request ? active_color : inactive_color, "DMA Request"); +// ImGui::SameLine(offsets[2]); +// ImGui::TextColored(s_SPUSTAT.dma_read_request ? active_color : inactive_color, "DMA Read"); +// ImGui::SameLine(offsets[3]); +// ImGui::TextColored(s_SPUSTAT.dma_write_request ? active_color : inactive_color, "DMA Write"); +// ImGui::SameLine(offsets[4]); +// ImGui::TextColored(s_SPUSTAT.transfer_busy ? active_color : inactive_color, "Transfer Busy"); +// ImGui::SameLine(offsets[5]); +// ImGui::TextColored(s_SPUSTAT.second_half_capture_buffer ? active_color : inactive_color, "Second Capture Buffer"); +// +// ImGui::Text("Interrupt: "); +// ImGui::SameLine(offsets[0]); +// ImGui::TextColored(s_SPUCNT.irq9_enable ? active_color : inactive_color, +// s_SPUCNT.irq9_enable ? "Enabled @ 0x%04X (actual 0x%08X)" : "Disabled @ 0x%04X (actual 0x%08X)", +// s_irq_address, (ZeroExtend32(s_irq_address) * 8) & RAM_MASK); +// +// ImGui::Text("Volume: "); +// ImGui::SameLine(offsets[0]); +// ImGui::Text("Left: %d%%", ApplyVolume(100, s_main_volume_left.current_level)); +// ImGui::SameLine(offsets[1]); +// ImGui::Text("Right: %d%%", ApplyVolume(100, s_main_volume_right.current_level)); +// +// ImGui::Text("CD Audio: "); +// ImGui::SameLine(offsets[0]); +// ImGui::TextColored(s_SPUCNT.cd_audio_enable ? active_color : inactive_color, +// s_SPUCNT.cd_audio_enable ? "Enabled" : "Disabled"); +// ImGui::SameLine(offsets[1]); +// ImGui::TextColored(s_SPUCNT.cd_audio_enable ? active_color : inactive_color, "Left Volume: %d%%", +// ApplyVolume(100, s_cd_audio_volume_left)); +// ImGui::SameLine(offsets[3]); +// ImGui::TextColored(s_SPUCNT.cd_audio_enable ? active_color : inactive_color, "Right Volume: %d%%", +// ApplyVolume(100, s_cd_audio_volume_left)); +// +// ImGui::Text("Transfer FIFO: "); +// ImGui::SameLine(offsets[0]); +// ImGui::TextColored(s_transfer_event->IsActive() ? active_color : inactive_color, "%u halfwords (%u bytes)", +// s_transfer_fifo.GetSize(), s_transfer_fifo.GetSize() * 2); +// } +// +// // draw voice states +// if (ImGui::CollapsingHeader("Voice State", ImGuiTreeNodeFlags_DefaultOpen)) +// { +// static constexpr u32 NUM_COLUMNS = 12; +// +// ImGui::Columns(NUM_COLUMNS); +// +// // headers +// static constexpr std::array column_titles = { +// {"#", "InterpIndex", "SampleIndex", "CurAddr", "StartAddr", "RepeatAddr", "SampleRate", "VolLeft", "VolRight", +// "ADSRPhase", "ADSRVol", "ADSRTicks"}}; +// static constexpr std::array adsr_phases = {{"Off", "Attack", "Decay", "Sustain", "Release"}}; +// for (u32 i = 0; i < NUM_COLUMNS; i++) +// { +// ImGui::TextUnformatted(column_titles[i]); +// ImGui::NextColumn(); +// } +// +// // states +// for (u32 voice_index = 0; voice_index < NUM_VOICES; voice_index++) +// { +// const Voice& v = s_voices[voice_index]; +// ImVec4 color = v.IsOn() ? ImVec4(1.0f, 1.0f, 1.0f, 1.0f) : ImVec4(0.5f, 0.5f, 0.5f, 1.0f); +// ImGui::TextColored(color, "%u", ZeroExtend32(voice_index)); +// ImGui::NextColumn(); +// if (IsVoiceNoiseEnabled(voice_index)) +// ImGui::TextColored(color, "NOISE"); +// else +// ImGui::TextColored(color, "%u", ZeroExtend32(v.counter.interpolation_index.GetValue())); +// ImGui::NextColumn(); +// ImGui::TextColored(color, "%u", ZeroExtend32(v.counter.sample_index.GetValue())); +// ImGui::NextColumn(); +// ImGui::TextColored(color, "%04X", ZeroExtend32(v.current_address)); +// ImGui::NextColumn(); +// ImGui::TextColored(color, "%04X", ZeroExtend32(v.regs.adpcm_start_address)); +// ImGui::NextColumn(); +// ImGui::TextColored(color, "%04X", ZeroExtend32(v.regs.adpcm_repeat_address)); +// ImGui::NextColumn(); +// ImGui::TextColored(color, "%.2f", (float(v.regs.adpcm_sample_rate) / 4096.0f) * 44100.0f); +// ImGui::NextColumn(); +// ImGui::TextColored(color, "%d%%", ApplyVolume(100, v.left_volume.current_level)); +// ImGui::NextColumn(); +// ImGui::TextColored(color, "%d%%", ApplyVolume(100, v.right_volume.current_level)); +// ImGui::NextColumn(); +// ImGui::TextColored(color, "%s", adsr_phases[static_cast(v.adsr_phase)]); +// ImGui::NextColumn(); +// ImGui::TextColored(color, "%d%%", ApplyVolume(100, v.regs.adsr_volume)); +// ImGui::NextColumn(); +// ImGui::TextColored(color, "%d", v.adsr_envelope.counter); +// ImGui::NextColumn(); +// } +// +// ImGui::Columns(1); +// } +// +// if (ImGui::CollapsingHeader("Reverb", ImGuiTreeNodeFlags_DefaultOpen)) +// { +// ImGui::TextColored(s_SPUCNT.reverb_master_enable ? active_color : inactive_color, "Master Enable: %s", +// s_SPUCNT.reverb_master_enable ? "Yes" : "No"); +// ImGui::Text("Voices Enabled: "); +// +// for (u32 i = 0; i < NUM_VOICES; i++) +// { +// ImGui::SameLine(0.0f, 16.0f); +// +// const bool active = IsVoiceReverbEnabled(i); +// ImGui::TextColored(active ? active_color : inactive_color, "%u", i); +// } +// +// ImGui::TextColored(s_SPUCNT.cd_audio_reverb ? active_color : inactive_color, "CD Audio Enable: %s", +// s_SPUCNT.cd_audio_reverb ? "Yes" : "No"); +// +// ImGui::TextColored(s_SPUCNT.external_audio_reverb ? active_color : inactive_color, "External Audio Enable: %s", +// s_SPUCNT.external_audio_reverb ? "Yes" : "No"); +// +// ImGui::Text("Base Address: 0x%08X (%04X)", s_reverb_base_address, s_reverb_registers.mBASE); +// ImGui::Text("Current Address: 0x%08X", s_reverb_current_address); +// ImGui::Text("Current Amplitude: Input (%d, %d) Output (%d, %d)", s_last_reverb_input[0], s_last_reverb_input[1], +// s_last_reverb_output[0], s_last_reverb_output[1]); +// ImGui::Text("Output Volume: Left %d%% Right %d%%", ApplyVolume(100, s_reverb_registers.vLOUT), +// ApplyVolume(100, s_reverb_registers.vROUT)); +// +// ImGui::Text("Pitch Modulation: "); +// for (u32 i = 1; i < NUM_VOICES; i++) +// { +// ImGui::SameLine(0.0f, 16.0f); +// +// const bool active = IsPitchModulationEnabled(i); +// ImGui::TextColored(active ? active_color : inactive_color, "%u", i); +// } +// } +// +// if (ImGui::CollapsingHeader("Hacks", ImGuiTreeNodeFlags_DefaultOpen)) +// { +// if (ImGui::Button("Key Off All Voices")) +// { +// for (u32 i = 0; i < NUM_VOICES; i++) +// { +// s_voices[i].KeyOff(); +// s_voices[i].adsr_envelope.counter = 0; +// s_voices[i].regs.adsr_volume = 0; +// } +// } +// } +// +// ImGui::End(); +//} \ No newline at end of file diff --git a/Source/AliveLibCommon/audio/mixer/SPU.hpp b/Source/AliveLibCommon/audio/mixer/SPU.hpp new file mode 100644 index 000000000..d14f674c1 --- /dev/null +++ b/Source/AliveLibCommon/audio/mixer/SPU.hpp @@ -0,0 +1,60 @@ +// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin +// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) + +#pragma once +#include "types.h" +#include + +class StateWrapper; + +class AudioStream; + +namespace SPU { + +enum : u32 +{ + RAM_SIZE = 512 * 1024, + RAM_MASK = RAM_SIZE - 1, + SAMPLE_RATE = 44100, +}; + +void Initialize(); +void CPUClockChanged(); +void Shutdown(); +void Reset(); +bool DoState(StateWrapper& sw); + +u16 ReadRegister(u32 offset); +void WriteRegister(u32 offset, u16 value); + +void DMARead(u32* words, u32 word_count); +void DMAWrite(const u32* words, u32 word_count); + +//// Render statistics debug window. +//void DrawDebugStateWindow(); + +// Executes the SPU, generating any pending samples. +void GeneratePendingSamples(); + +///// Returns true if currently dumping audio. +//bool IsDumpingAudio(); +// +///// Starts dumping audio to file. +//bool StartDumpingAudio(const char* filename); +// +///// Stops dumping audio to file, if started. +//bool StopDumpingAudio(); + +/// Access to SPU RAM. +const std::array& GetRAM(); +std::array& GetWritableRAM(); + +/// Change output stream - used for runahead. +// TODO: Make it use system "running ahead" flag +bool IsAudioOutputMuted(); +void SetAudioOutputMuted(bool muted); + +AudioStream* GetOutputStream(); +void RecreateOutputStream(); + +}; // namespace SPU \ No newline at end of file diff --git a/Source/AliveLibCommon/audio/mixer/SPUEMU.cpp b/Source/AliveLibCommon/audio/mixer/SPUEMU.cpp new file mode 100644 index 000000000..e97ecc61f --- /dev/null +++ b/Source/AliveLibCommon/audio/mixer/SPUEMU.cpp @@ -0,0 +1,1053 @@ +// Heavly modify version of duckstation spu +#pragma once + +#include "SPUEMU.hpp" +#include +#include "AudioStream.hpp" + +// countof macro +#ifndef countof + #ifdef _countof + #define countof _countof + #else +template +char (&__countof_ArraySizeHelper(T (&array)[N]))[N]; + #define countof(array) (sizeof(__countof_ArraySizeHelper(array))) + #endif +#endif + +u32 mask(u32 num) +{ + u32 res = 0; + while (num-- > 0) + { + res = (res << 1) | 1; + } + return res; +} + +static const u32 NUM_VOICES = 24; +static const u32 NUM_REVERB_REGS = 32; +static const u32 NUM_SAMPLES_PER_ADPCM_BLOCK = 28; +static const u32 NUM_VOICE_REGISTERS = 8; +static const u32 NUM_SAMPLES_FROM_LAST_ADPCM_BLOCK = 3, + +static const s16 ENVELOPE_MIN_VOLUME = 0; +static const s16 ENVELOPE_MAX_VOLUME = 0x7FFF; + +union ADSRRegister +{ + u32 bits; + struct + { + u16 bits_low; + u16 bits_high; + }; + + // adsr.attackRate = (adsr1 >> 8) & mask(7); + BitField sustain_level; + BitField decay_rate_shr2; + BitField attack_rate; + BitField attack_exponential; + + BitField release_rate_shr2; + BitField release_exponential; + BitField sustain_rate; + BitField sustain_direction_decrease; + BitField sustain_exponential; +}; + +union VoiceCounter +{ + // promoted to u32 because of overflow + u32 bits; + + BitField interpolation_index; + BitField sample_index; +}; + +union VoiceRegisters +{ + u16 index[NUM_VOICE_REGISTERS]; + + struct + { + VolumeRegister volume_left; + VolumeRegister volume_right; + + u16 adpcm_sample_rate; // VxPitch + u16 adpcm_start_address; // multiply by 8 + + ADSRRegister adsr; + s16 adsr_volume; + + u16 adpcm_repeat_address; // multiply by 8 + }; +}; + +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]; + }; + + u16 rev[NUM_REVERB_REGS]; + }; +}; + +struct VolumeEnvelope +{ + s32 counter; + u8 rate; + bool decreasing; + bool exponential; + + void Reset(u8 rate_, bool decreasing_, bool exponential_); + s16 Tick(s16 current_level); +}; + +union VolumeRegister +{ + u16 bits; + + BitField sweep_mode; + BitField fixed_volume_shr1; // divided by 2 + + BitField sweep_exponential; + BitField sweep_direction_decrease; + BitField sweep_phase_negative; + BitField sweep_rate; +}; + +struct VolumeSweep +{ + VolumeEnvelope envelope; + bool envelope_active; + s16 current_level; + + void Reset(VolumeRegister reg); + void Tick(); +}; + +union SPUCNT +{ + u16 bits; + + BitField enable; + BitField mute_n; + BitField noise_clock; + BitField reverb_master_enable; + BitField irq9_enable; + BitField ram_transfer_mode; + BitField external_audio_reverb; + BitField cd_audio_reverb; + BitField external_audio_enable; + BitField cd_audio_enable; + + BitField mode; +}; + +union ADPCMFlags +{ + u8 bits; + + //BitField loop_end; + //BitField loop_repeat; + //BitField loop_start; + + u8 loopEnd() + { + return (bits >> 0) & mask(1); + } + + u8 loopRepeat() + { + return (bits >> 1) & mask(1); + } + + u8 loopStart() + { + return (bits >> 2) & mask(1); + } +}; + +struct ADPCMBlock +{ + union + { + u8 bits; + + //BitField shift; + //BitField filter; + + u8 shift() + { + return (bits >> 0) & mask(4); + } + + u8 filter() + { + return (bits >> 4) & mask(3); + } + + } shift_filter; + ADPCMFlags flags; + u8 data[NUM_SAMPLES_PER_ADPCM_BLOCK / 2]; + + // For both 4bit and 8bit ADPCM, reserved shift values 13..15 will act same as shift=9). + u8 GetShift() const + { + const u8 shift = shift_filter.shift(); + return (shift > 12) ? 9 : shift; + } + + u8 GetFilter() const + { + return std::min(shift_filter.filter(), 4); + } + + u8 GetNibble(u32 index) const + { + return (data[index / 2] >> ((index % 2) * 4)) & 0x0F; + } +}; + +template +constexpr u16 Truncate16(TValue value) +{ + return static_cast(static_cast::type>(value)); +} + +template +bool ConvertToBoolUnchecked(TValue value) +{ + bool ret; + std::memcpy(&ret, &value, sizeof(bool)); + return ret; +} + +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); +} + +static constexpr s32 Clamp16(s32 value) +{ + return (value < -0x8000) ? -0x8000 : (value > 0x7FFF) ? 0x7FFF + : value; +} + +template +constexpr u32 ZeroExtend32(TValue value) +{ + return ZeroExtend(value); +} + +template +constexpr u16 ZeroExtend16(TValue value) +{ + return ZeroExtend(value); +} + +static AudioStream* s_audio_stream = new AudioStream(); +static std::array s_voices{}; +static SPUCNT s_SPUCNT = {}; + +// 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; + +static u32 s_key_on_register = 0; +static u32 s_key_off_register = 0; +static u32 s_endx_register = 0; +static u32 s_pitch_modulation_enable_register = 0; + +static u32 s_noise_mode_register = 0; +static u32 s_noise_count = 0; +static u32 s_noise_level = 0; + +static VolumeRegister s_main_volume_left_reg = {}; +static VolumeRegister s_main_volume_right_reg = {}; +static VolumeSweep s_main_volume_left = {}; +static VolumeSweep s_main_volume_right = {}; + +struct Voice +{ + u16 current_address; + VoiceRegisters regs; + VoiceCounter counter; + ADPCMFlags current_block_flags; + bool is_first_block; + std::array current_block_samples; + std::array adpcm_last_samples; + s32 last_volume; + + VolumeSweep left_volume; + VolumeSweep right_volume; + + VolumeEnvelope adsr_envelope; + ADSRPhase adsr_phase; + s16 adsr_target; + bool has_samples; + bool ignore_loop_address; + + bool IsOn() const + { + return adsr_phase != ADSRPhase::Off; + } + + void KeyOn(); + void KeyOff(); + void ForceOff(); + + void DecodeBlock(const ADPCMBlock& block); + s32 Interpolate() const; + + // Switches to the specified phase, filling in target. + void UpdateADSREnvelope(); + ADSRPhase GetNextADSRPhase(ADSRPhase phase); + + // Updates the ADSR volume/phase. + void TickADSR(); +}; + +void SPU::Execute(u32 remaining_frames) +{ + AudioStream* output_stream = s_audio_stream; + + + // I NEED + // 1. sample interpolation + // 2. reverb + // 3. adsr + + + while (remaining_frames > 0) + { + s16* output_frame_start; + u32 output_frame_space = remaining_frames; + output_stream->BeginWrite(&output_frame_start, &output_frame_space); + + s16* output_frame = output_frame_start; + const u32 frames_in_this_batch = std::min(remaining_frames, output_frame_space); + for (u32 i = 0; i < frames_in_this_batch; i++) + { + s32 left_sum = 0; + s32 right_sum = 0; + s32 reverb_in_left = 0; + s32 reverb_in_right = 0; + + u32 reverb_on_register = s_reverb_on_register; + for (u32 voice = 0; voice < NUM_VOICES; voice++) + { + const auto [left, right] = SampleVoice(voice); + left_sum += left; + right_sum += right; + + if (reverb_on_register & 1u) + { + reverb_in_left += left; + reverb_in_right += right; + } + reverb_on_register >>= 1; + } + + // What's this? + //if (!s_SPUCNT.mute_n) + //{ + // left_sum = 0; + // right_sum = 0; + //} + + // Update noise once per frame. + //UpdateNoise(); + + // Compute reverb. + s32 reverb_out_left, reverb_out_right; + ProcessReverb(static_cast(Clamp16(reverb_in_left)), static_cast(Clamp16(reverb_in_right)), + &reverb_out_left, &reverb_out_right); + + // Mix in reverb. + left_sum += reverb_out_left; + right_sum += reverb_out_right; + + // Apply main volume after clamping. A maximum volume should not overflow here because both are 16-bit values. + *(output_frame++) = static_cast(ApplyVolume(Clamp16(left_sum), s_main_volume_left.current_level)); + *(output_frame++) = static_cast(ApplyVolume(Clamp16(right_sum), s_main_volume_right.current_level)); + s_main_volume_left.Tick(); + s_main_volume_right.Tick(); + + // Write to capture buffers. + //WriteToCaptureBuffer(0, cd_audio_left); + //WriteToCaptureBuffer(1, cd_audio_right); + // + // UNSURE IF THESE ARE NEEDED? + //WriteToCaptureBuffer(2, static_cast(Clamp16(s_voices[1].last_volume))); + //WriteToCaptureBuffer(3, static_cast(Clamp16(s_voices[3].last_volume))); + //IncrementCaptureBufferPosition(); + + // Key off/on voices after the first frame. + if (i == 0 && (s_key_off_register != 0 || s_key_on_register != 0)) + { + u32 key_off_register = s_key_off_register; + s_key_off_register = 0; + + u32 key_on_register = s_key_on_register; + s_key_on_register = 0; + + for (u32 voice = 0; voice < NUM_VOICES; voice++) + { + if (key_off_register & 1u) + s_voices[voice].KeyOff(); + key_off_register >>= 1; + + if (key_on_register & 1u) + { + s_endx_register &= ~(1u << voice); + s_voices[voice].KeyOn(); + } + key_on_register >>= 1; + } + } + } + + output_stream->EndWrite(frames_in_this_batch); + remaining_frames -= frames_in_this_batch; + } +} + + + + +bool SPU::IsVoiceNoiseEnabled(u32 i) +{ + // TODO - another mask? + return ConvertToBoolUnchecked((s_noise_mode_register >> i) & u32(1)); +} + +s16 SPU::GetVoiceNoiseLevel() +{ + return static_cast(static_cast(s_noise_level)); +} + +bool SPU::IsPitchModulationEnabled(u32 i) +{ + return ((i > 0) && ConvertToBoolUnchecked((s_pitch_modulation_enable_register >> i) & u32(1))); +} + +static constexpr s32 ApplyVolume(s32 sample, s16 volume) +{ + return (sample * s32(volume)) >> 15; +} + +std::tuple SPU::SampleVoice(u32 voice_index) +{ + Voice& voice = s_voices[voice_index]; + + + // What's this? + if (!voice.IsOn() && !s_SPUCNT.irq9_enable) + { + voice.last_volume = 0; + return {}; + } + + if (!voice.IsOn()) + { + voice.last_volume = 0; + } + + // No need to read from block, we will keep a buffer - TODO + if (!voice.has_samples) + { + ADPCMBlock block; + ReadADPCMBlock(voice.current_address, &block); + voice.DecodeBlock(block); + voice.has_samples = true; + + if (voice.current_block_flags.loopStart() && !voice.ignore_loop_address) + { + // Log_TracePrintf("Voice %u loop start @ 0x%08X", voice_index, ZeroExtend32(voice.current_address)); + voice.regs.adpcm_repeat_address = voice.current_address; + } + } + + // skip interpolation when the volume is muted anyway + s32 volume; + if (voice.regs.adsr_volume != 0) + { + // interpolate/sample and apply ADSR volume + s32 sample; + if (IsVoiceNoiseEnabled(voice_index)) + sample = GetVoiceNoiseLevel(); + else + sample = voice.Interpolate(); + + volume = ApplyVolume(sample, voice.regs.adsr_volume); + } + else + { + volume = 0; + } + + voice.last_volume = volume; + + if (voice.adsr_phase != ADSRPhase::Off) + voice.TickADSR(); + + // Pitch modulation + u16 step = voice.regs.adpcm_sample_rate; + if (IsPitchModulationEnabled(voice_index)) + { + const s32 factor = std::clamp(s_voices[voice_index - 1].last_volume, -0x8000, 0x7FFF) + 0x8000; + step = Truncate16(static_cast((SignExtend32(step) * factor) >> 15)); + } + step = std::min(step, 0x3FFF); + + // Shouldn't ever overflow because if sample_index == 27, step == 0x4000 there won't be a carry out from the + // interpolation index. If there is a carry out, bit 12 will never be 1, so it'll never add more than 4 to + // sample_index, which should never be >27. + //DebugAssert(voice.counter.sample_index < NUM_SAMPLES_PER_ADPCM_BLOCK); + voice.counter.bits += step; + + if (voice.counter.sample_index >= NUM_SAMPLES_PER_ADPCM_BLOCK) + { + // next block + voice.counter.sample_index -= NUM_SAMPLES_PER_ADPCM_BLOCK; + voice.has_samples = false; + voice.is_first_block = false; + voice.current_address += 2; + + // handle flags + if (voice.current_block_flags.loopEnd()) + { + s_endx_register |= (u32(1) << voice_index); + voice.current_address = voice.regs.adpcm_repeat_address & ~u16(1); + + if (!voice.current_block_flags.loopRepeat()) + { + //Log_TracePrintf("Voice %u loop end+mute @ 0x%08X", voice_index, ZeroExtend32(voice.current_address)); + voice.ForceOff(); + } + else + { + //Log_TracePrintf("Voice %u loop end+repeat @ 0x%08X", voice_index, ZeroExtend32(voice.current_address)); + } + } + } + + // apply per-channel volume + const s32 left = ApplyVolume(volume, voice.left_volume.current_level); + const s32 right = ApplyVolume(volume, voice.right_volume.current_level); + voice.left_volume.Tick(); + voice.right_volume.Tick(); + + //#ifdef SPU_DUMP_ALL_VOICES + // if (s_voice_dump_writers[voice_index]) + // { + // const s16 dump_samples[2] = {static_cast(Clamp16(left)), static_cast(Clamp16(right))}; + // s_voice_dump_writers[voice_index]->WriteFrames(dump_samples, 1); + // } + //#endif + + return std::make_tuple(left, right); +} + +//void SPU::UpdateNoise() +//{ +// // Dr Hell's noise waveform, implementation borrowed from pcsx-r. +// static constexpr std::array noise_wave_add = { +// {1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, +// 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1}}; +// static constexpr std::array noise_freq_add = {{0, 84, 140, 180, 210}}; +// +// const u32 noise_clock = s_SPUCNT.noise_clock; +// const u32 level = (0x8000u >> (noise_clock >> 2)) << 16; +// +// s_noise_count += 0x10000u + noise_freq_add[noise_clock & 3u]; +// if ((s_noise_count & 0xFFFFu) >= noise_freq_add[4]) +// { +// s_noise_count += 0x10000; +// s_noise_count -= noise_freq_add[noise_clock & 3u]; +// } +// +// if (s_noise_count < level) +// return; +// +// s_noise_count %= level; +// s_noise_level = (s_noise_level << 1) | noise_wave_add[(s_noise_level >> 10) & 63u]; +//} + + +//void SPU::ReadADPCMBlock(u16 address, ADPCMBlock* block) +//{ +// u32 ram_address = (ZeroExtend32(address) * 8) & RAM_MASK; +// if (IsRAMIRQTriggerable() && (CheckRAMIRQ(ram_address) || CheckRAMIRQ((ram_address + 8) & RAM_MASK))) +// { +// Log_DebugPrintf("Trigger IRQ @ %08X %04X from ADPCM reader", ram_address, ram_address / 8); +// TriggerRAMIRQ(); +// } +// +// // fast path - no wrap-around +// if ((ram_address + sizeof(ADPCMBlock)) <= RAM_SIZE) +// { +// std::memcpy(block, &s_ram[ram_address], sizeof(ADPCMBlock)); +// return; +// } +// +// block->shift_filter.bits = s_ram[ram_address]; +// ram_address = (ram_address + 1) & RAM_MASK; +// block->flags.bits = s_ram[ram_address]; +// ram_address = (ram_address + 1) & RAM_MASK; +// for (u32 i = 0; i < 14; i++) +// { +// block->data[i] = s_ram[ram_address]; +// ram_address = (ram_address + 1) & RAM_MASK; +// } +//} + +//////////////////////////// +// VOICE +//////////////////////////// + +void Voice::DecodeBlock(const ADPCMBlock& block) +{ + static constexpr std::array filter_table_pos = {{0, 60, 115, 98, 122}}; + static constexpr std::array filter_table_neg = {{0, 0, -52, -55, -60}}; + + // store samples needed for interpolation + current_block_samples[2] = current_block_samples[NUM_SAMPLES_FROM_LAST_ADPCM_BLOCK + NUM_SAMPLES_PER_ADPCM_BLOCK - 1]; + current_block_samples[1] = current_block_samples[NUM_SAMPLES_FROM_LAST_ADPCM_BLOCK + NUM_SAMPLES_PER_ADPCM_BLOCK - 2]; + current_block_samples[0] = current_block_samples[NUM_SAMPLES_FROM_LAST_ADPCM_BLOCK + NUM_SAMPLES_PER_ADPCM_BLOCK - 3]; + + // pre-lookup + const u8 shift = block.GetShift(); + const u8 filter_index = block.GetFilter(); + const s32 filter_pos = filter_table_pos[filter_index]; + const s32 filter_neg = filter_table_neg[filter_index]; + s16 last_samples[2] = {adpcm_last_samples[0], adpcm_last_samples[1]}; + + // samples + for (u32 i = 0; i < NUM_SAMPLES_PER_ADPCM_BLOCK; i++) + { + // extend 4-bit to 16-bit, apply shift from header and mix in previous samples + s32 sample = s32(static_cast(ZeroExtend16(block.GetNibble(i)) << 12) >> shift); + sample += (last_samples[0] * filter_pos) >> 6; + sample += (last_samples[1] * filter_neg) >> 6; + + last_samples[1] = last_samples[0]; + current_block_samples[NUM_SAMPLES_FROM_LAST_ADPCM_BLOCK + i] = last_samples[0] = static_cast(Clamp16(sample)); + } + + std::copy(last_samples, last_samples + countof(last_samples), adpcm_last_samples.begin()); + current_block_flags.bits = block.flags.bits; +} + +void Voice::TickADSR() +{ + regs.adsr_volume = adsr_envelope.Tick(regs.adsr_volume); + + if (adsr_phase != ADSRPhase::Sustain) + { + const bool reached_target = adsr_envelope.decreasing ? (regs.adsr_volume <= adsr_target) : (regs.adsr_volume >= adsr_target); + if (reached_target) + { + adsr_phase = GetNextADSRPhase(adsr_phase); + UpdateADSREnvelope(); + } + } +} + +ADSRPhase Voice::GetNextADSRPhase(ADSRPhase phase) +{ + switch (phase) + { + case ADSRPhase::Attack: + // attack -> decay + return ADSRPhase::Decay; + + case ADSRPhase::Decay: + // decay -> sustain + return ADSRPhase::Sustain; + + case ADSRPhase::Sustain: + // sustain stays in sustain until key off + return ADSRPhase::Sustain; + + default: + case ADSRPhase::Release: + // end of release disables the voice + return ADSRPhase::Off; + } +} + +void Voice::UpdateADSREnvelope() +{ + switch (adsr_phase) + { + case ADSRPhase::Off: + adsr_target = 0; + adsr_envelope.Reset(0, false, false); + return; + + case ADSRPhase::Attack: + adsr_target = 32767; // 0 -> max + adsr_envelope.Reset(regs.adsr.attack_rate, false, regs.adsr.attack_exponential); + break; + + case ADSRPhase::Decay: + adsr_target = static_cast(std::min((u32(regs.adsr.sustain_level.GetValue()) + 1) * 0x800, + ENVELOPE_MAX_VOLUME)); // max -> sustain level + adsr_envelope.Reset(regs.adsr.decay_rate_shr2 << 2, true, true); + break; + + case ADSRPhase::Sustain: + adsr_target = 0; + adsr_envelope.Reset(regs.adsr.sustain_rate, regs.adsr.sustain_direction_decrease, regs.adsr.sustain_exponential); + break; + + case ADSRPhase::Release: + adsr_target = 0; + adsr_envelope.Reset(regs.adsr.release_rate_shr2 << 2, true, regs.adsr.release_exponential); + break; + + default: + break; + } +} + +s32 Voice::Interpolate() const +{ + 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 // + }}; + + // the interpolation index is based on the source files sample rate + const u8 i = counter.interpolation_index; + const u32 s = NUM_SAMPLES_FROM_LAST_ADPCM_BLOCK + ZeroExtend32(counter.sample_index.GetValue()); + + // interpolate on 4 most recent samples + s32 out = s32(gauss[0x0FF - i]) * s32(current_block_samples[s - 3]); // oldest + out += s32(gauss[0x1FF - i]) * s32(current_block_samples[s - 2]); // older + out += s32(gauss[0x100 + i]) * s32(current_block_samples[s - 1]); // old + out += s32(gauss[0x000 + i]) * s32(current_block_samples[s - 0]); // new + return out >> 15; +} + +//////////////////////////// +// REVERB +//////////////////////////// +// 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 SPU::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 SPU::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 SPU::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)); +} + +void SPU::ProcessReverb(s16 left_in, s16 right_in, s32* left_out, s32* right_out) +{ + 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; + + s32 out[2]; + if (s_reverb_resample_buffer_position & 1u) + { + 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.reverb_master_enable) + { + const s16 IIR_INPUT_A = ReverbSat((((ReverbRead(s_reverb_registers.IIR_SRC_A[lr ^ 0]) * s_reverb_registers.IIR_COEF) >> 14) + ((downsampled[lr] * s_reverb_registers.IN_COEF[lr]) >> 14)) >> 1); + const s16 IIR_INPUT_B = ReverbSat((((ReverbRead(s_reverb_registers.IIR_SRC_B[lr ^ 1]) * s_reverb_registers.IIR_COEF) >> 14) + ((downsampled[lr] * s_reverb_registers.IN_COEF[lr]) >> 14)) >> 1); + const s16 IIR_A = ReverbSat((((IIR_INPUT_A * s_reverb_registers.IIR_ALPHA) >> 14) + (IIASM(s_reverb_registers.IIR_ALPHA, ReverbRead(s_reverb_registers.IIR_DEST_A[lr], -1)) >> 14)) >> 1); + const s16 IIR_B = ReverbSat((((IIR_INPUT_B * s_reverb_registers.IIR_ALPHA) >> 14) + (IIASM(s_reverb_registers.IIR_ALPHA, ReverbRead(s_reverb_registers.IIR_DEST_B[lr], -1)) >> 14)) >> 1); + + ReverbWrite(s_reverb_registers.IIR_DEST_A[lr], IIR_A); + ReverbWrite(s_reverb_registers.IIR_DEST_B[lr], IIR_B); + } + + const s32 ACC = ((ReverbRead(s_reverb_registers.ACC_SRC_A[lr]) * s_reverb_registers.ACC_COEF_A) >> 14) + ((ReverbRead(s_reverb_registers.ACC_SRC_B[lr]) * s_reverb_registers.ACC_COEF_B) >> 14) + ((ReverbRead(s_reverb_registers.ACC_SRC_C[lr]) * s_reverb_registers.ACC_COEF_C) >> 14) + ((ReverbRead(s_reverb_registers.ACC_SRC_D[lr]) * s_reverb_registers.ACC_COEF_D) >> 14); + + const s16 FB_A = ReverbRead(s_reverb_registers.MIX_DEST_A[lr] - s_reverb_registers.FB_SRC_A); + const s16 FB_B = ReverbRead(s_reverb_registers.MIX_DEST_B[lr] - s_reverb_registers.FB_SRC_B); + const s16 MDA = ReverbSat((ACC + ((FB_A * ReverbNeg(s_reverb_registers.FB_ALPHA)) >> 14)) >> 1); + const s16 MDB = ReverbSat( + FB_A + ((((MDA * s_reverb_registers.FB_ALPHA) >> 14) + ((FB_B * ReverbNeg(s_reverb_registers.FB_X)) >> 14)) >> 1)); + const s16 IVB = ReverbSat(FB_B + ((MDB * s_reverb_registers.FB_X) >> 15)); + + if (s_SPUCNT.reverb_master_enable) + { + ReverbWrite(s_reverb_registers.MIX_DEST_A[lr], MDA); + ReverbWrite(s_reverb_registers.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 + { + 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; + + 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); + +#ifdef SPU_DUMP_ALL_VOICES + if (s_voice_dump_writers[NUM_VOICES]) + { + const s16 dump_samples[2] = {static_cast(Clamp16(s_last_reverb_output[0])), + static_cast(Clamp16(s_last_reverb_output[1]))}; + s_voice_dump_writers[NUM_VOICES]->WriteFrames(dump_samples, 1); + } +#endif +} + + + +//void SPU::WriteToCaptureBuffer(u32 index, s16 value) +//{ +// const u32 ram_address = (index * CAPTURE_BUFFER_SIZE_PER_CHANNEL) | ZeroExtend16(s_capture_buffer_position); +// // Log_DebugPrintf("write to capture buffer %u (0x%08X) <- 0x%04X", index, ram_address, u16(value)); +// std::memcpy(&s_ram[ram_address], &value, sizeof(value)); +// if (IsRAMIRQTriggerable() && CheckRAMIRQ(ram_address)) +// { +// // Log_DebugPrintf("Trigger IRQ @ %08X %04X from capture buffer", ram_address, ram_address / 8); +// TriggerRAMIRQ(); +// } +//} +// +//void SPU::IncrementCaptureBufferPosition() +//{ +// s_capture_buffer_position += sizeof(s16); +// s_capture_buffer_position %= CAPTURE_BUFFER_SIZE_PER_CHANNEL; +// s_SPUSTAT.second_half_capture_buffer = s_capture_buffer_position >= (CAPTURE_BUFFER_SIZE_PER_CHANNEL / 2); +//} \ No newline at end of file diff --git a/Source/AliveLibCommon/audio/mixer/SPUEMU.hpp b/Source/AliveLibCommon/audio/mixer/SPUEMU.hpp new file mode 100644 index 000000000..0abf3e344 --- /dev/null +++ b/Source/AliveLibCommon/audio/mixer/SPUEMU.hpp @@ -0,0 +1,34 @@ +#pragma once + +enum class ADSRPhase : u8 +{ + Off = 0, + Attack = 1, + Decay = 2, + Sustain = 3, + Release = 4 +}; + +class SPU +{ +public: + + // should be called at 44100hz + void Execute(u32 remaining_frames); + + s16 GetVoiceNoiseLevel(); + bool IsVoiceNoiseEnabled(u32 i); + bool IsPitchModulationEnabled(u32 i); + + ADSRPhase GetNextADSRPhase(ADSRPhase phase); + + std::tuple SampleVoice(u32 voice_index); + void UpdateNoise(); + void ProcessReverb(s16 left_in, s16 right_in, s32* left_out, s32* right_out); + void WriteToCaptureBuffer(u32 index, s16 value); + void IncrementCaptureBufferPosition(); + + u32 ReverbMemoryAddress(u32 address); + s16 ReverbRead(u32 address, s32 offset = 0); + void ReverbWrite(u32 address, s16 data); +}; \ No newline at end of file From 6e23095726d8237294500944b42bfb69995894eb Mon Sep 17 00:00:00 2001 From: Sean Horton <3359434+sean-horton@users.noreply.github.com> Date: Thu, 30 Mar 2023 20:48:45 -0400 Subject: [PATCH 26/82] working gauss table? --- Source/AliveLibAE/Sound/Midi.cpp | 2 +- Source/AliveLibCommon/CMakeLists.txt | 5 - Source/AliveLibCommon/audio/MidiPlayer.cpp | 5 +- Source/AliveLibCommon/audio/Sequencer.cpp | 654 ++++++++++--------- Source/AliveLibCommon/audio/Sequencer.hpp | 111 ++-- Source/AliveLibCommon/audio/Soundbank.hpp | 3 +- Source/AliveLibCommon/audio/mixer/SPUEMU.cpp | 2 +- Source/AliveLibCommon/audio/mixer/Voice.cpp | 1 + 8 files changed, 415 insertions(+), 368 deletions(-) diff --git a/Source/AliveLibAE/Sound/Midi.cpp b/Source/AliveLibAE/Sound/Midi.cpp index 3112ebdc0..902d49b14 100644 --- a/Source/AliveLibAE/Sound/Midi.cpp +++ b/Source/AliveLibAE/Sound/Midi.cpp @@ -977,7 +977,7 @@ class AESoundSampleParser : public psx::SoundSampleParser memcpy(data, &soundData[offset], size); psx::Sample* sample = new psx::Sample(); - sample->m_SampleBuffer = reinterpret_cast(data); + sample->m_SampleBuffer = reinterpret_cast(data); sample->i_SampleSize = size / 2; sample->sampleRate = 22050; samples.push_back(sample); diff --git a/Source/AliveLibCommon/CMakeLists.txt b/Source/AliveLibCommon/CMakeLists.txt index aacda2e37..b40343dc4 100644 --- a/Source/AliveLibCommon/CMakeLists.txt +++ b/Source/AliveLibCommon/CMakeLists.txt @@ -54,11 +54,6 @@ SET(AliveLibSrcCommon audio/MidiPlayer.cpp audio/mixer/biquad.hpp audio/mixer/biquad.cpp - audio/mixer/psxadsr.hpp - audio/mixer/psxadsr.cpp - audio/mixer/BitField.hpp - audio/mixer/SPUEMU.hpp - audio/mixer/SPUEMU.cpp audio/mixer/AudioStream.hpp audio/mixer/AudioStream.cpp ) diff --git a/Source/AliveLibCommon/audio/MidiPlayer.cpp b/Source/AliveLibCommon/audio/MidiPlayer.cpp index c328804cc..eabcb0749 100644 --- a/Source/AliveLibCommon/audio/MidiPlayer.cpp +++ b/Source/AliveLibCommon/audio/MidiPlayer.cpp @@ -5,7 +5,6 @@ #include "mixer/ADSR.hpp" #include "mixer/AliveAudio.hpp" #include "SequencePlayer.hpp" -#include "mixer/psxadsr.hpp" namespace psx { @@ -34,7 +33,7 @@ class DefaultSoundSampleParser : public SoundSampleParser pos += size; Sample* sample = new Sample(); - sample->m_SampleBuffer = reinterpret_cast(data); + sample->m_SampleBuffer = reinterpret_cast(data); sample->i_SampleSize = size / 2; sample->sampleRate = sampleRate; samples.push_back(sample); @@ -118,7 +117,7 @@ void MidiPlayer::SND_Load_VABS(SoundBlockInfo* pSoundBlockInfo, s32 reverb) sean::ADSR adsr = sean::parseADSR(ADSR1, ADSR2); sean::Patch* patch = sequencer->createPatch(vagAttr->field_14_prog); Sample* s = samples.at(vagAttr->field_16_vag - 1); - sean::Sample* sample = new sean::Sample(s->m_SampleBuffer, s->i_SampleSize * 2, 22050); // TODO sample-sampleRate? + sean::Sample* sample = new sean::Sample(s->m_SampleBuffer, s->i_SampleSize, 44100); // TODO s->sampleRate? patch->samples[x] = sample; sample->adsr = adsr; diff --git a/Source/AliveLibCommon/audio/Sequencer.cpp b/Source/AliveLibCommon/audio/Sequencer.cpp index 964cdd4a3..43b52c4fc 100644 --- a/Source/AliveLibCommon/audio/Sequencer.cpp +++ b/Source/AliveLibCommon/audio/Sequencer.cpp @@ -2,11 +2,15 @@ #include "Sequencer.hpp" #include +#include "SDL.h" namespace sean { ///////////////////////////////// /// DuckStation + +static const u32 NUM_SAMPLES_PER_ADPCM_BLOCK = 28; + u32 mask(u32 num) { u32 res = 0; @@ -17,6 +21,38 @@ u32 mask(u32 num) return res; } +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); +} + ADSR parseADSR(u16 adsr1, u16 adsr2) { ADSR adsr; @@ -76,73 +112,127 @@ static constexpr ADSRTableEntries ComputeADSRTableEntries() static constexpr ADSRTableEntries s_adsr_table = ComputeADSRTableEntries(); -//struct Voice -//{ -// u16 current_address; -// VoiceRegisters regs; -// VoiceCounter counter; -// ADPCMFlags current_block_flags; -// bool is_first_block; -// std::array current_block_samples; -// std::array adpcm_last_samples; -// s32 last_volume; -// -// VolumeSweep left_volume; -// VolumeSweep right_volume; -// -// VolumeEnvelope adsr_envelope; -// ADSRPhase adsr_phase; -// s16 adsr_target; -// bool has_samples; -// bool ignore_loop_address; -// -// bool IsOn() const -// { -// return adsr_phase != ADSRPhase::Off; -// } -// -// void KeyOn(); -// void KeyOff(); -// void ForceOff(); -// -// void DecodeBlock(const ADPCMBlock& block); -// s32 Interpolate() const; -// -// // Switches to the specified phase, filling in target. -// void UpdateADSREnvelope(); -// -// // Updates the ADSR volume/phase. -// void TickADSR(); -//}; +s32 Voice::interpolate() +{ + 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 // + }}; + + // the interpolation index is based on the source files sample rate + const u8 i = (u8)vounter.interpolation_index(); + const u32 s = ZeroExtend32(vounter.sample_index()); + + // interpolate on 4 most recent samples + s32 out = 0; + out += s32(gauss[0x0FF - i]) * s32(sample->buffer[(int) (f_SampleOffset + s) - 3]); // oldest s16 - but should it be s8? + out += s32(gauss[0x1FF - i]) * s32(sample->buffer[(int) (f_SampleOffset + s) - 2]); // older + out += s32(gauss[0x100 + i]) * s32(sample->buffer[(int) (f_SampleOffset + s) - 1]); // old + out += s32(gauss[0x000 + i]) * s32(sample->buffer[(int) (f_SampleOffset + s) - 0]); // new + + //out += s32(gauss[0x0FF - i]) * sOldest; // oldest s16 - but should it be s8? + //out += s32(gauss[0x1FF - i]) * sOlder; // older + //out += s32(gauss[0x100 + i]) * sOld; // old + //out += s32(gauss[0x000 + i]) * sNew; // new + + return out >> 15; +} + /// DuckStation END ///////////////////////////////// Sequencer::Sequencer() { - // Open the AL device - ALenum error; - alGetError(); // clear out error state - device = alcOpenDevice(NULL); - ctx = alcCreateContext(device, NULL); - alcMakeContextCurrent(ctx); - if ((error = alGetError()) != AL_NO_ERROR) + gseq = this; + + // 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) { - return; + fprintf(stderr, "Failed to initialize audio: %s\n", SDL_GetError()); + exit(-1); } + SDL_PauseAudio(0); + int id = 1; for (int i = 0; i < voiceCount; i++) { voices[i] = new Voice(); voices[i]->id = id; id = id << 1; - - // Prepare sources (these are streams buffers can be played in) - alGenSources(1, &(voices[i])->alSourceId); - if ((error = alGetError()) != AL_NO_ERROR) - { - return; - } } for (int i = 0; i < patchCount; i++) @@ -150,34 +240,15 @@ Sequencer::Sequencer() patches[i] = NULL; } - // Prepare some known effects we will use - LOAD_EFFECT_FUNCTIONS(); - EFXEAXREVERBPROPERTIES reverb = EFX_REVERB_PRESET_CASTLE_MEDIUMROOM; - efxReverb0 = PREPARE_REVERB(&reverb); - alGenAuxiliaryEffectSlots(1, &efxSlot0); - alAuxiliaryEffectSloti(efxSlot0, AL_EFFECTSLOT_EFFECT, (ALint) efxReverb0); - - running = true; - thread = new std::thread(&Sequencer::loop, this); + //running = true; + //thread = new std::thread(&Sequencer::loop, this); } Sequencer::~Sequencer() { - running = false; - thread->join(); - delete thread; - - // Delete sources - //alDeleteSources(sourceCount, source); - - // Delete effects - alDeleteAuxiliaryEffectSlots(1, &efxSlot0); - alDeleteEffects(1, &efxReverb0); - - // Close AL device - alcMakeContextCurrent(NULL); - alcDestroyContext(ctx); - alcCloseDevice(device); + //running = false; + //thread->join(); + //delete thread; // Delete buffers (by killing the instrument) for (Patch* patch : patches) @@ -223,26 +294,81 @@ void Sequencer::loop() } } +void SDLCallback(void* udata, Uint8* stream, int len) +{ + udata; + stream; + len; + + // This runs at 44100hz + // 1. tick voices + // a. interpolate + // b. adsr + // c. pitch (this seems like it should be before interpolation though...) + // 2. run reverb + + gseq->tickSequence(); + + float* AudioStream = (float*) stream; + int StreamLength = len / sizeof(float); + float leftSample = 0; + float rightSample = 0; + for (int i = 0; i < StreamLength; i += 2) + { + // set silence if no voice is played + AudioStream[i] = 0; + AudioStream[i + 1] = 0; + + // mix voices + for (Voice* v : gseq->voices) + { + if (!v->sample || v->complete) + { + continue; + } + + std::tuple s = v->tick(); + // Tick voice + leftSample = (float) std::get<0>(s) / 32767.0f; // * v->leftPan; + rightSample = (float) std::get<1>(s) / 32767.0f; // * v->rightPan; + + // 32767.0f is max of signed 16 bit integer + //float add = v->sample->SampleRate / 44100.0f; + //leftSample = (float) (v->sample->buffer[(int) v->f_SampleOffset]) / 32767.0f; + //v->f_SampleOffset = v->f_SampleOffset + add; + ////rightSample = (float) (v->sample->buffer[(int) v->f_SampleOffset++]) / 32767.0f; + //rightSample = leftSample; + + // Run reverb + // TODO + + // Set against SDL + SDL_MixAudioFormat((Uint8*) (AudioStream + i), (const Uint8*) &leftSample, AUDIO_F32, sizeof(float), 37); // Left Channel + SDL_MixAudioFormat((Uint8*) (AudioStream + i + 1), (const Uint8*) &rightSample, AUDIO_F32, sizeof(float), 37); // Right Channel + } + } +} + ////////////////////////// // PRIVATE void Sequencer::reset() { stopAll(); - for (s16 i = 0; i < patchCount; i++) - { - // must be deleted separately from sources - // since deleting a patch may delete a buffer - // attached to a source - delete patches[i]; - patches[i] = NULL; - } - - for (Sequence* seq : sequences) - { - delete seq; - } - sequences.clear(); + //for (s16 i = 0; i < patchCount; i++) + //{ + // // must be deleted separately from sources + // // since deleting a patch may delete a buffer + // // attached to a source + // delete patches[i]; + // patches[i] = NULL; + //} + // + //for (Sequence* seq : sequences) + //{ + // delete seq; + //} + //sequences.clear(); } void Sequencer::stopAll() @@ -292,7 +418,6 @@ void Sequencer::stopSeq(s32 seqId) void Sequencer::tickSequence() { u64 now = timeSinceEpochMillisec(); - ALenum error; // Tick sequences for (Sequence* seq : sequences) @@ -338,21 +463,6 @@ void Sequencer::tickSequence() continue; } - alSourcei(v->alSourceId, AL_BUFFER, sample->alBuffer); - if ((error = alGetError()) != AL_NO_ERROR) - { - releaseVoice(v); - continue; - } - - alSourcef(v->alSourceId, AL_GAIN, (ALfloat) 0.0f); - alSourcePlay(v->alSourceId); - if ((error = alGetError()) != AL_NO_ERROR) - { - releaseVoice(v); - continue; - } - v->sequence = seq; v->patchId = seq->channels[message->channelId]->patch->id; v->channelId = message->channelId; @@ -413,16 +523,12 @@ void Sequencer::tickVoice() continue; } - voice->adsrCurrentLevel = voice->tick(); + voice->tick(); } } void Sequencer::syncVoice() { - // pause openal changes to perform bulk update - more performant. - // unpause is called at the end. - alcSuspendContext(ctx); - for (int c = 0; c < voiceCount; c++) { Voice* voice = voices[c]; @@ -437,30 +543,28 @@ void Sequencer::syncVoice() } // Update the play source paramters - ALenum state; Sample* sample = voice->sample; - ALuint id = voice->alSourceId; // If we are past the duration of the notes playback - alGetSourcei(id, AL_SOURCE_STATE, &state); - if (state != AL_PLAYING) - { - releaseVoice(voice); - continue; - } - - u8 note = voice->note; - s32 notePitch = voice->pitch < voice->pitchMin ? voice->pitchMin : voice->pitch; // or sample? - u8 rootNote = sample->rootNote; - u8 rootPitch = sample->rootNotePitchShift; + //alGetSourcei(id, AL_SOURCE_STATE, &state); + //if (state != AL_PLAYING) + //{ + // releaseVoice(voice); + // continue; + //} + + //u8 note = voice->note; + //s32 notePitch = voice->pitch < voice->pitchMin ? voice->pitchMin : voice->pitch; // or sample? + //u8 rootNote = sample->rootNote; + //u8 rootPitch = sample->rootNotePitchShift; // This figures out the frequency of midi notes (ex. 60 is middle C and 261.63 hz) // noteFreq is the note we want to play // rootFreq is the samples root note // We then divide the two to figure out how to pitch shift the sample to match the // the desired note. For some reason we have to multiply it by 2. don't know why. - float noteFreq = float(pow(2.0, float(note + (notePitch / 127.0f)) / 12.0f)); - float rootFreq = float(pow(2.0, float(rootNote + (rootPitch / 127.0f)) / 12.0f)); - float freq = noteFreq / rootFreq * 2.0f; + //float noteFreq = float(pow(2.0, float(note + (notePitch / 127.0f)) / 12.0f)); + //float rootFreq = float(pow(2.0, float(rootNote + (rootPitch / 127.0f)) / 12.0f)); + //float freq = noteFreq / rootFreq * 2.0f; float pan = voice->pan; if (sample->pan) @@ -475,51 +579,100 @@ void Sequencer::syncVoice() } float gain = float(voice->adsrCurrentLevel) / 32767.0f * voice->velocity * voice->sample->volume* seqVolume; - alSource3f(id, AL_POSITION, pan, 0, -sqrtf(1.0f - pan * pan)); - alSourcef(id, AL_PITCH, (ALfloat) freq); - alSourcef(id, AL_GAIN, (ALfloat) gain); - alSourcei(id, AL_LOOPING, voice->loop); + //alSource3f(id, AL_POSITION, pan, 0, -sqrtf(1.0f - pan * pan)); + //alSourcef(id, AL_PITCH, (ALfloat) freq); + //alSourcef(id, AL_GAIN, (ALfloat) gain); + //alSourcei(id, AL_LOOPING, voice->loop); // 0 Off // 1 Vibrate // 2 Portamento // 3 1 & 2(Portamento and Vibrate on) // 4 Reverb - if (sample->reverb != 0) - { - alSource3i(id, AL_AUXILIARY_SEND_FILTER, (ALint) efxSlot0, 0, AL_FILTER_NULL); - } - else - { - alSource3i(id, AL_AUXILIARY_SEND_FILTER, AL_EFFECTSLOT_NULL, 0, NULL); - } + if (gain < 0.001f && voice->offTime > 0) { releaseVoice(voice); } } +} - alcProcessContext(ctx); +static constexpr s32 ApplyVolume(s32 sample, s16 volume) +{ + return (sample * s32(volume)) >> 15; } +void Voice::pushSample(s32 s) +{ + sOldest = sOlder; + sOlder = sOld; + sOld = sNew; + sNew = s; +} -s16 Voice::tick() +std::tuple Voice::tick() { if (!sample) { - return 0; + return std::make_tuple(0, 0); } - + + // INTERPOLATION + s32 notePitch = pitch < pitchMin ? pitchMin : pitch; // or sample? + u8 rootNote = sample->rootNote; + u8 rootPitch = sample->rootNotePitchShift; + + // frequencies are stable for duration of note? - move elsewhere to cut down on maths? + float noteFreq = float(pow(2.0, float(note + (notePitch / 127.0f)) / 12.0f)); + float rootFreq = float(pow(2.0, float(rootNote + (rootPitch / 127.0f)) / 12.0f)); + //float freq = (float) ((noteFreq / rootFreq) * (float(sample->SampleRate) / 44100.0f)); + //f_SampleOffset += freq; + + + if (vounter.sample_index() >= NUM_SAMPLES_PER_ADPCM_BLOCK) + { + // shift << 12 as sample_index is shifted over 12 + vounter.bits -= (NUM_SAMPLES_PER_ADPCM_BLOCK << 12); + f_SampleOffset += NUM_SAMPLES_PER_ADPCM_BLOCK; + } + + + if (f_SampleOffset + vounter.sample_index() >= sample->len - 1 && !loop) + { + complete = true; + return std::make_tuple(0, 0); + } + + f_SampleOffset = f_SampleOffset + vounter.sample_index() >= sample->len - 1 ? 3 : f_SampleOffset; + pushSample(sample->buffer[(int) f_SampleOffset]); + + s32 sampleData; + sampleData = interpolate(); + + // duckstation uses this to get the actual sample rate in hz + // (float(v.regs.adpcm_sample_rate) / 4096.0f) * 44100.0f) + + u16 step = (u16) (((float(sample->SampleRate) * (noteFreq / rootFreq)) / 44100.0f) * 4096.0f); + //const s32 factor = std::clamp(sampleData, -0x8000, 0x7FFF) + 0x8000; + //step = Truncate16(static_cast((SignExtend32(step) * factor) >> 15)); + step = std::min(step, 0x3FFF); + vounter.bits += step; + + //s32 volume; + //volume = ApplyVolume(sampleData, 1); // voice.regs.adsr_volume + // UPDATE ADSR STATE - this probably doesn't need to be done every tick? if (adsrPhase == NONE) { + std::cout << sample->adsr.attackRate << " " << velocity << std::endl; 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) { @@ -547,49 +700,72 @@ s16 Voice::tick() adsrExponential = sample->adsr.releaseExponential; adsrTargetLevel = 0; adsrCounter = s_adsr_table[adsrDecreasing][adsrRate].ticks; + //adsrCurrentLevel = MIN_VOLUME; } // UPDATE TICK STATE adsrCounter--; - if (adsrCounter > 0) + if (adsrCounter <= 0) { - return adsrCurrentLevel; - } - - const ADSRTableEntry& table_entry = s_adsr_table[adsrDecreasing][adsrRate]; - s32 this_step = table_entry.step; - adsrCounter = table_entry.ticks; + 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 (adsrExponential) { - if (adsrCurrentLevel >= 0x6000) + if (adsrDecreasing) { - if (adsrRate < 40) - { - this_step >>= 2; - } - else if (adsrRate >= 44) - { - adsrCounter >>= 2; - } - else + this_step = (this_step * adsrCurrentLevel) >> 15; + } + else + { + if (adsrCurrentLevel >= 0x6000) { - this_step >>= 1; - adsrCounter >>= 1; + 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 static_cast( - std::clamp(static_cast(adsrCurrentLevel) + this_step, MIN_VOLUME, MAX_VOLUME)); + float centerPan = pan == 0 ? sample->pan : pan; + float leftPan = 1.0f; + float rightPan = 1.0f; + if (centerPan > 0) + { + leftPan = 1.0f - abs(centerPan); + } + if (centerPan < 0) + { + rightPan = 1.0f - abs(centerPan); + } + + float add = 1; + if (sequence) + { + add = sequence->volume; + } + const float tmp = float(sampleData) * float(adsrCurrentLevel) / float(MAX_VOLUME) * velocity * add; + const s32 left = s32(tmp * leftPan); // ApplyVolume(volume, voice.left_volume.current_level); + const s32 right = s32(tmp * rightPan); //ApplyVolume(volume, voice.right_volume.current_level); + //voice.left_volume.Tick(); + //voice.right_volume.Tick(); + return std::make_tuple(left, right); } Voice* Sequencer::obtainVoice() @@ -598,6 +774,11 @@ Voice* Sequencer::obtainVoice() for (int i = 0; i < voiceCount; i++) { Voice* v = voices[i]; + if (v->complete) + { + releaseVoice(v); + } + if (v->inUse.compare_exchange_weak(expected, true)) { return v; @@ -608,7 +789,6 @@ Voice* Sequencer::obtainVoice() void Sequencer::releaseVoice(Voice* v) { - alSourceStop(v->alSourceId); v->offTime = 0; v->pitch = 0; v->adsrPhase = NONE; @@ -618,6 +798,9 @@ void Sequencer::releaseVoice(Voice* v) v->sequence = NULL; v->loop = false; v->pan = 0; + v->f_SampleOffset = 0; + v->vounter.bits = 0; + v->complete = false; v->inUse = false; } @@ -690,9 +873,6 @@ s32 Sequencer::playNote(s32 patchId, u8 note, float velocity, float pan, u8 pitc v->pitchMax = pitchMax; v->sample = s; ids |= v->id; - alSourcei(v->alSourceId, AL_BUFFER, s->alBuffer); - alSourcef(v->alSourceId, AL_GAIN, (ALfloat) 0.0f); - alSourcePlay(v->alSourceId); } return ids; @@ -768,126 +948,4 @@ Event* EventRing::pop() return &events[(tail++)]; } - -////////////////////////// -// OPENAL BS -ALuint PREPARE_REVERB(const EFXEAXREVERBPROPERTIES* reverb) -{ - ALuint effect = 0; - ALenum err; - - /* Create the effect object and check if we can do EAX reverb. */ - alGenEffects(1, &effect); - if (alGetEnumValue("AL_EFFECT_EAXREVERB") != 0) - { - printf("Using EAX Reverb\n"); - - /* EAX Reverb is available. Set the EAX effect type then load the - * reverb properties. */ - alEffecti(effect, AL_EFFECT_TYPE, AL_EFFECT_EAXREVERB); - alEffectf(effect, AL_EAXREVERB_DENSITY, reverb->flDensity); - alEffectf(effect, AL_EAXREVERB_DIFFUSION, reverb->flDiffusion); - alEffectf(effect, AL_EAXREVERB_GAIN, reverb->flGain); - alEffectf(effect, AL_EAXREVERB_GAINHF, reverb->flGainHF); - alEffectf(effect, AL_EAXREVERB_GAINLF, reverb->flGainLF); - alEffectf(effect, AL_EAXREVERB_DECAY_TIME, reverb->flDecayTime); - alEffectf(effect, AL_EAXREVERB_DECAY_HFRATIO, reverb->flDecayHFRatio); - alEffectf(effect, AL_EAXREVERB_DECAY_LFRATIO, reverb->flDecayLFRatio); - alEffectf(effect, AL_EAXREVERB_REFLECTIONS_GAIN, reverb->flReflectionsGain); - alEffectf(effect, AL_EAXREVERB_REFLECTIONS_DELAY, reverb->flReflectionsDelay); - alEffectfv(effect, AL_EAXREVERB_REFLECTIONS_PAN, reverb->flReflectionsPan); - alEffectf(effect, AL_EAXREVERB_LATE_REVERB_GAIN, reverb->flLateReverbGain); - alEffectf(effect, AL_EAXREVERB_LATE_REVERB_DELAY, reverb->flLateReverbDelay); - alEffectfv(effect, AL_EAXREVERB_LATE_REVERB_PAN, reverb->flLateReverbPan); - alEffectf(effect, AL_EAXREVERB_ECHO_TIME, reverb->flEchoTime); - alEffectf(effect, AL_EAXREVERB_ECHO_DEPTH, reverb->flEchoDepth); - alEffectf(effect, AL_EAXREVERB_MODULATION_TIME, reverb->flModulationTime); - alEffectf(effect, AL_EAXREVERB_MODULATION_DEPTH, reverb->flModulationDepth); - alEffectf(effect, AL_EAXREVERB_AIR_ABSORPTION_GAINHF, reverb->flAirAbsorptionGainHF); - alEffectf(effect, AL_EAXREVERB_HFREFERENCE, reverb->flHFReference); - alEffectf(effect, AL_EAXREVERB_LFREFERENCE, reverb->flLFReference); - alEffectf(effect, AL_EAXREVERB_ROOM_ROLLOFF_FACTOR, reverb->flRoomRolloffFactor); - alEffecti(effect, AL_EAXREVERB_DECAY_HFLIMIT, reverb->iDecayHFLimit); - } - else - { - printf("Using Standard Reverb\n"); - - /* No EAX Reverb. Set the standard reverb effect type then load the - * available reverb properties. */ - alEffecti(effect, AL_EFFECT_TYPE, AL_EFFECT_REVERB); - - alEffectf(effect, AL_REVERB_DENSITY, reverb->flDensity); - alEffectf(effect, AL_REVERB_DIFFUSION, reverb->flDiffusion); - alEffectf(effect, AL_REVERB_GAIN, reverb->flGain); - alEffectf(effect, AL_REVERB_GAINHF, reverb->flGainHF); - alEffectf(effect, AL_REVERB_DECAY_TIME, reverb->flDecayTime); - alEffectf(effect, AL_REVERB_DECAY_HFRATIO, reverb->flDecayHFRatio); - alEffectf(effect, AL_REVERB_REFLECTIONS_GAIN, reverb->flReflectionsGain); - alEffectf(effect, AL_REVERB_REFLECTIONS_DELAY, reverb->flReflectionsDelay); - alEffectf(effect, AL_REVERB_LATE_REVERB_GAIN, reverb->flLateReverbGain); - alEffectf(effect, AL_REVERB_LATE_REVERB_DELAY, reverb->flLateReverbDelay); - alEffectf(effect, AL_REVERB_AIR_ABSORPTION_GAINHF, reverb->flAirAbsorptionGainHF); - alEffectf(effect, AL_REVERB_ROOM_ROLLOFF_FACTOR, reverb->flRoomRolloffFactor); - alEffecti(effect, AL_REVERB_DECAY_HFLIMIT, reverb->iDecayHFLimit); - } - - /* Check if an error occured, and clean up if so. */ - err = alGetError(); - if (err != AL_NO_ERROR) - { - fprintf(stderr, "OpenAL error: %s\n", alGetString(err)); - if (alIsEffect(effect)) - alDeleteEffects(1, &effect); - return 0; - } - - return effect; -} - -void LOAD_EFFECT_FUNCTIONS() -{ -#if __STDC_VERSION__ >= 199901L - #define FUNCTION_CAST(T, ptr) (union \ - { \ - void* p; \ - T f; \ - }){ptr} \ - .f -#elif defined(__cplusplus) - #define FUNCTION_CAST(T, ptr) reinterpret_cast(ptr) -#else - #define FUNCTION_CAST(T, ptr) (T)(ptr) -#endif - -#define LOAD_PROC(T, x) ((x) = FUNCTION_CAST(T, alGetProcAddress(#x))) - LOAD_PROC(LPALGENEFFECTS, alGenEffects); - LOAD_PROC(LPALDELETEEFFECTS, alDeleteEffects); - LOAD_PROC(LPALISEFFECT, alIsEffect); - LOAD_PROC(LPALEFFECTI, alEffecti); - LOAD_PROC(LPALEFFECTIV, alEffectiv); - LOAD_PROC(LPALEFFECTF, alEffectf); - LOAD_PROC(LPALEFFECTFV, alEffectfv); - LOAD_PROC(LPALGETEFFECTI, alGetEffecti); - LOAD_PROC(LPALGETEFFECTIV, alGetEffectiv); - LOAD_PROC(LPALGETEFFECTF, alGetEffectf); - LOAD_PROC(LPALGETEFFECTFV, alGetEffectfv); - - LOAD_PROC(LPALFILTERF, alFilterf); - LOAD_PROC(LPALFILTERI, alFilteri); - - LOAD_PROC(LPALGENAUXILIARYEFFECTSLOTS, alGenAuxiliaryEffectSlots); - LOAD_PROC(LPALDELETEAUXILIARYEFFECTSLOTS, alDeleteAuxiliaryEffectSlots); - LOAD_PROC(LPALISAUXILIARYEFFECTSLOT, alIsAuxiliaryEffectSlot); - LOAD_PROC(LPALAUXILIARYEFFECTSLOTI, alAuxiliaryEffectSloti); - LOAD_PROC(LPALAUXILIARYEFFECTSLOTIV, alAuxiliaryEffectSlotiv); - LOAD_PROC(LPALAUXILIARYEFFECTSLOTF, alAuxiliaryEffectSlotf); - LOAD_PROC(LPALAUXILIARYEFFECTSLOTFV, alAuxiliaryEffectSlotfv); - LOAD_PROC(LPALGETAUXILIARYEFFECTSLOTI, alGetAuxiliaryEffectSloti); - LOAD_PROC(LPALGETAUXILIARYEFFECTSLOTIV, alGetAuxiliaryEffectSlotiv); - LOAD_PROC(LPALGETAUXILIARYEFFECTSLOTF, alGetAuxiliaryEffectSlotf); - LOAD_PROC(LPALGETAUXILIARYEFFECTSLOTFV, alGetAuxiliaryEffectSlotfv); -#undef LOAD_PROC -} - } // namespace \ No newline at end of file diff --git a/Source/AliveLibCommon/audio/Sequencer.hpp b/Source/AliveLibCommon/audio/Sequencer.hpp index 9f61cfe89..5531b10b9 100644 --- a/Source/AliveLibCommon/audio/Sequencer.hpp +++ b/Source/AliveLibCommon/audio/Sequencer.hpp @@ -1,20 +1,17 @@ #pragma once +#include "SDL.h" #include #include #include -#include "AL/al.h" -#include "AL/alc.h" -#include "AL/alext.h" -#include "AL/efx.h" -#include "AL/efx-presets.h" -#include "BitField.hpp" namespace sean { const s16 MIN_VOLUME = 0; const s16 MAX_VOLUME = 32767; +u32 mask(u32 num); + struct ASDR { // adsr1 @@ -39,14 +36,15 @@ ADSR parseADSR(u16 adsr1, u16 adsr2); class Sample { public: - Sample(ALvoid* buffer, ALsizei size, ALsizei sampleRate) + Sample(s16* buf, u32 size, u32 sampleRate) { - alGenBuffers(1, &alBuffer); - alBufferData(alBuffer, AL_FORMAT_MONO16, buffer, size, sampleRate); + buffer = buf; + len = size; + SampleRate = sampleRate; } ~Sample() { - alDeleteBuffers(1, &alBuffer); + delete[] buffer; } float volume; @@ -65,7 +63,9 @@ class Sample ADSR adsr; - ALuint alBuffer; + s16* buffer; + u32 len; + u32 SampleRate; }; @@ -185,11 +185,30 @@ enum ADSRPhase RELEASE }; +union VoiceCounter +{ + // promoted to u32 because of overflow + u32 bits = 0; + + //BitField interpolation_index; + u32 interpolation_index() + { + return (bits >> 4) & mask(8); + } + + //BitField sample_index; + u32 sample_index() + { + return (bits >> 12) & mask(5); + } +}; + class Voice { public: s32 id; + double f_SampleOffset = 3; Sequence* sequence = NULL; u8 patchId; u8 channelId; @@ -201,9 +220,19 @@ class Voice float pan = 0.0f; std::atomic_bool inUse = false; + s32 sOldest = 0; + s32 sOlder = 0; + s32 sOld = 0; + s32 sNew = 0; + + void pushSample(s32 s); + + bool complete = false; bool loop = false; u64 offTime = 0; // when the note was released + VoiceCounter vounter; + ADSRPhase adsrPhase = NONE; s32 adsrCounter = 0; // decremented each midi tick u32 adsrRate; @@ -212,10 +241,12 @@ class Voice s16 adsrCurrentLevel = 0; s16 adsrTargetLevel = MAX_VOLUME; // attack we want to reach max - ALuint alSourceId; Sample* sample; - s16 tick(); + std::tuple tick(); + +private: + s32 interpolate(); }; @@ -265,6 +296,12 @@ class Sequencer void playSeq(s32 seqId); void stopSeq(s32 seqId); + static const int voiceCount = 256; + Voice* voices[voiceCount]; + + void tickSequence(); + + private: std::thread* thread; bool running; @@ -277,61 +314,17 @@ class Sequencer Voice* obtainVoice(); void releaseVoice(Voice* v); - void tickSequence(); void tickVoice(); void syncVoice(); - ALCdevice* device; - ALCcontext* ctx; - std::vector sequences; - static const int voiceCount = 256; - Voice* voices[voiceCount]; static const int patchCount = 128; Patch* patches[patchCount]; - - ALuint efxReverb0; - ALuint efxSlot0; }; - -/* -* BELOW IS ALL OPENAL EFFECTS LOADING -* Not sure where else to put this... -*/ -/* Define a macro to help load the function pointers. - https://github.com/kcat/openal-soft/blob/master/examples/alreverb.c */ -/* Effect object functions */ -static LPALGENEFFECTS alGenEffects; -static LPALDELETEEFFECTS alDeleteEffects; -static LPALISEFFECT alIsEffect; -static LPALEFFECTI alEffecti; -static LPALEFFECTIV alEffectiv; -static LPALEFFECTF alEffectf; -static LPALEFFECTFV alEffectfv; -static LPALGETEFFECTI alGetEffecti; -static LPALGETEFFECTIV alGetEffectiv; -static LPALGETEFFECTF alGetEffectf; -static LPALGETEFFECTFV alGetEffectfv; -static LPALFILTERF alFilterf; -static LPALFILTERI alFilteri; - -/* Auxiliary Effect Slot object functions */ -static LPALGENAUXILIARYEFFECTSLOTS alGenAuxiliaryEffectSlots; -static LPALDELETEAUXILIARYEFFECTSLOTS alDeleteAuxiliaryEffectSlots; -static LPALISAUXILIARYEFFECTSLOT alIsAuxiliaryEffectSlot; -static LPALAUXILIARYEFFECTSLOTI alAuxiliaryEffectSloti; -static LPALAUXILIARYEFFECTSLOTIV alAuxiliaryEffectSlotiv; -static LPALAUXILIARYEFFECTSLOTF alAuxiliaryEffectSlotf; -static LPALAUXILIARYEFFECTSLOTFV alAuxiliaryEffectSlotfv; -static LPALGETAUXILIARYEFFECTSLOTI alGetAuxiliaryEffectSloti; -static LPALGETAUXILIARYEFFECTSLOTIV alGetAuxiliaryEffectSlotiv; -static LPALGETAUXILIARYEFFECTSLOTF alGetAuxiliaryEffectSlotf; -static LPALGETAUXILIARYEFFECTSLOTFV alGetAuxiliaryEffectSlotfv; - -ALuint PREPARE_REVERB(const EFXEAXREVERBPROPERTIES* reverb); -void LOAD_EFFECT_FUNCTIONS(); +static Sequencer* gseq; +void SDLCallback(void* udata, Uint8* stream, int len); } // namespace \ No newline at end of file diff --git a/Source/AliveLibCommon/audio/Soundbank.hpp b/Source/AliveLibCommon/audio/Soundbank.hpp index 28f87ae18..b7a250424 100644 --- a/Source/AliveLibCommon/audio/Soundbank.hpp +++ b/Source/AliveLibCommon/audio/Soundbank.hpp @@ -26,8 +26,9 @@ class AliveAudioHelper class Sample { public: - u16* m_SampleBuffer; + s16* m_SampleBuffer; u32 i_SampleSize; + u32 sampleRate; float GetSample(float sampleOffset); }; diff --git a/Source/AliveLibCommon/audio/mixer/SPUEMU.cpp b/Source/AliveLibCommon/audio/mixer/SPUEMU.cpp index e97ecc61f..e17db849c 100644 --- a/Source/AliveLibCommon/audio/mixer/SPUEMU.cpp +++ b/Source/AliveLibCommon/audio/mixer/SPUEMU.cpp @@ -320,7 +320,7 @@ struct Voice VoiceCounter counter; ADPCMFlags current_block_flags; bool is_first_block; - std::array current_block_samples; + std::array current_block_samples; // 3 + 28 std::array adpcm_last_samples; s32 last_volume; diff --git a/Source/AliveLibCommon/audio/mixer/Voice.cpp b/Source/AliveLibCommon/audio/mixer/Voice.cpp index a384e4b8c..553472ab6 100644 --- a/Source/AliveLibCommon/audio/mixer/Voice.cpp +++ b/Source/AliveLibCommon/audio/mixer/Voice.cpp @@ -47,6 +47,7 @@ float Voice::GetSample() } } + // From midi.cpp -- auto freq = pow(1.059463094359, (f64) (note - v29) * 0.00390625); double sampleFrameRate = pow(1.059463, i_Note - m_Tone->c_Center + m_Tone->Pitch + f_Pitch) * (44100.0 / AliveAudioSampleRate); f_SampleOffset += (sampleFrameRate); From f325d3acd7aecf948a5cd5f9494697051646b65f Mon Sep 17 00:00:00 2001 From: Sean Horton <3359434+sean-horton@users.noreply.github.com> Date: Thu, 30 Mar 2023 20:56:39 -0400 Subject: [PATCH 27/82] cleanup before reverb --- Source/AliveLibCommon/audio/Sequencer.cpp | 16 +--------------- Source/AliveLibCommon/audio/Sequencer.hpp | 2 -- 2 files changed, 1 insertion(+), 17 deletions(-) diff --git a/Source/AliveLibCommon/audio/Sequencer.cpp b/Source/AliveLibCommon/audio/Sequencer.cpp index 43b52c4fc..24cd13b97 100644 --- a/Source/AliveLibCommon/audio/Sequencer.cpp +++ b/Source/AliveLibCommon/audio/Sequencer.cpp @@ -192,11 +192,6 @@ s32 Voice::interpolate() out += s32(gauss[0x100 + i]) * s32(sample->buffer[(int) (f_SampleOffset + s) - 1]); // old out += s32(gauss[0x000 + i]) * s32(sample->buffer[(int) (f_SampleOffset + s) - 0]); // new - //out += s32(gauss[0x0FF - i]) * sOldest; // oldest s16 - but should it be s8? - //out += s32(gauss[0x1FF - i]) * sOlder; // older - //out += s32(gauss[0x100 + i]) * sOld; // old - //out += s32(gauss[0x000 + i]) * sNew; // new - return out >> 15; } @@ -603,14 +598,6 @@ static constexpr s32 ApplyVolume(s32 sample, s16 volume) return (sample * s32(volume)) >> 15; } -void Voice::pushSample(s32 s) -{ - sOldest = sOlder; - sOlder = sOld; - sOld = sNew; - sNew = s; -} - std::tuple Voice::tick() { if (!sample) @@ -644,8 +631,7 @@ std::tuple Voice::tick() return std::make_tuple(0, 0); } - f_SampleOffset = f_SampleOffset + vounter.sample_index() >= sample->len - 1 ? 3 : f_SampleOffset; - pushSample(sample->buffer[(int) f_SampleOffset]); + f_SampleOffset = f_SampleOffset + vounter.sample_index() >= sample->len ? 3 : f_SampleOffset; s32 sampleData; sampleData = interpolate(); diff --git a/Source/AliveLibCommon/audio/Sequencer.hpp b/Source/AliveLibCommon/audio/Sequencer.hpp index 5531b10b9..9d4096bd2 100644 --- a/Source/AliveLibCommon/audio/Sequencer.hpp +++ b/Source/AliveLibCommon/audio/Sequencer.hpp @@ -225,8 +225,6 @@ class Voice s32 sOld = 0; s32 sNew = 0; - void pushSample(s32 s); - bool complete = false; bool loop = false; u64 offTime = 0; // when the note was released From c54e49b0e51e906836c56cd8b3a62ff10a33e874 Mon Sep 17 00:00:00 2001 From: Sean Horton <3359434+sean-horton@users.noreply.github.com> Date: Fri, 31 Mar 2023 07:13:35 -0400 Subject: [PATCH 28/82] reverb checkpoint --- Source/AliveLibCommon/audio/Sequencer.cpp | 319 +++++++++++++++++++++- 1 file changed, 309 insertions(+), 10 deletions(-) diff --git a/Source/AliveLibCommon/audio/Sequencer.cpp b/Source/AliveLibCommon/audio/Sequencer.cpp index 24cd13b97..a1bee2f58 100644 --- a/Source/AliveLibCommon/audio/Sequencer.cpp +++ b/Source/AliveLibCommon/audio/Sequencer.cpp @@ -9,6 +9,9 @@ namespace sean { ///////////////////////////////// /// DuckStation +static void ProcessReverb(s16 left_in, s16 right_in, s32* left_out, s32* right_out); +static s16 ReverbRead(u32 address, s32 offset = 0); + static const u32 NUM_SAMPLES_PER_ADPCM_BLOCK = 28; u32 mask(u32 num) @@ -53,6 +56,12 @@ constexpr u32 ZeroExtend32(TValue value) return ZeroExtend(value); } +static constexpr s32 Clamp16(s32 value) +{ + return (value < -0x8000) ? -0x8000 : (value > 0x7FFF) ? 0x7FFF + : value; +} + ADSR parseADSR(u16 adsr1, u16 adsr2) { ADSR adsr; @@ -308,6 +317,8 @@ void SDLCallback(void* udata, Uint8* stream, int len) int StreamLength = len / sizeof(float); float leftSample = 0; float rightSample = 0; + s32 reverb_in_left = 0; + s32 reverb_in_right = 0; for (int i = 0; i < StreamLength; i += 2) { // set silence if no voice is played @@ -324,15 +335,22 @@ void SDLCallback(void* udata, Uint8* stream, int len) std::tuple s = v->tick(); // Tick voice - leftSample = (float) std::get<0>(s) / 32767.0f; // * v->leftPan; - rightSample = (float) std::get<1>(s) / 32767.0f; // * v->rightPan; + leftSample = (float) std::get<0>(s); // * v->leftPan; + rightSample = (float) std::get<1>(s); // * v->rightPan; + + // 0 Off + // 1 Vibrate + // 2 Portamento + // 3 1 & 2(Portamento and Vibrate on) + // 4 Reverb + if (v->sample->reverb == 4) + { + reverb_in_left += (s32) leftSample; + reverb_in_right += (s32) rightSample; + } - // 32767.0f is max of signed 16 bit integer - //float add = v->sample->SampleRate / 44100.0f; - //leftSample = (float) (v->sample->buffer[(int) v->f_SampleOffset]) / 32767.0f; - //v->f_SampleOffset = v->f_SampleOffset + add; - ////rightSample = (float) (v->sample->buffer[(int) v->f_SampleOffset++]) / 32767.0f; - //rightSample = leftSample; + leftSample = leftSample / 32767.0f; + rightSample = rightSample / 32767.0f; // Run reverb // TODO @@ -341,6 +359,16 @@ void SDLCallback(void* udata, Uint8* stream, int len) SDL_MixAudioFormat((Uint8*) (AudioStream + i), (const Uint8*) &leftSample, AUDIO_F32, sizeof(float), 37); // Left Channel SDL_MixAudioFormat((Uint8*) (AudioStream + i + 1), (const Uint8*) &rightSample, AUDIO_F32, sizeof(float), 37); // Right Channel } + + s32 reverb_out_left = 0, reverb_out_right = 0; + ProcessReverb(static_cast(Clamp16(reverb_in_left)), static_cast(Clamp16(reverb_in_right)), + &reverb_out_left, &reverb_out_right); + + leftSample = float(reverb_out_left); + rightSample = float(reverb_out_right); + + SDL_MixAudioFormat((Uint8*) (AudioStream + i), (const Uint8*) &leftSample, AUDIO_F32, sizeof(float), 37); // Left Channel + SDL_MixAudioFormat((Uint8*) (AudioStream + i + 1), (const Uint8*) &rightSample, AUDIO_F32, sizeof(float), 37); // Right Channel } } @@ -651,7 +679,7 @@ std::tuple Voice::tick() // UPDATE ADSR STATE - this probably doesn't need to be done every tick? if (adsrPhase == NONE) { - std::cout << sample->adsr.attackRate << " " << velocity << std::endl; + //std::cout << sample->adsr.attackRate << " " << velocity << std::endl; adsrPhase = ATTACK; adsrDecreasing = false; adsrRate = sample->adsr.attackRate; @@ -688,7 +716,11 @@ std::tuple Voice::tick() adsrCounter = s_adsr_table[adsrDecreasing][adsrRate].ticks; //adsrCurrentLevel = MIN_VOLUME; } - + else if (adsrPhase == RELEASE && adsrCurrentLevel <= adsrTargetLevel) + { + complete = true; + return std::make_tuple(0, 0); + } // UPDATE TICK STATE adsrCounter--; @@ -918,6 +950,273 @@ MIDIMessage* Sequence::next(u64 now) } +//////////////////////////// +// REVERB +//////////////////////////// +//union SPUCNT +//{ +// u16 bits; +// +// BitField enable; +// BitField mute_n; +// BitField noise_clock; +// BitField reverb_master_enable; +// BitField irq9_enable; +// BitField ram_transfer_mode; +// BitField external_audio_reverb; +// BitField cd_audio_reverb; +// BitField external_audio_enable; +// BitField cd_audio_enable; +// +// BitField mode; +//}; +//static SPUCNT s_SPUCNT = {}; + +static const u32 NUM_REVERB_REGS = 32; +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)); +} + +void ProcessReverb(s16 left_in, s16 right_in, s32* left_out, s32* right_out) +{ + 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; + test = s_reverb_downsample_buffer; + //std::cout << "L:" << left_in << " R:" << right_in << 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; + + 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); + +#ifdef SPU_DUMP_ALL_VOICES + if (s_voice_dump_writers[NUM_VOICES]) + { + const s16 dump_samples[2] = {static_cast(Clamp16(s_last_reverb_output[0])), + static_cast(Clamp16(s_last_reverb_output[1]))}; + s_voice_dump_writers[NUM_VOICES]->WriteFrames(dump_samples, 1); + } +#endif +} + + + ////////////////////////// // Event Ring void EventRing::push(Event event) From acf6ad8f3a28a656acf937459cebd81007ab91bd Mon Sep 17 00:00:00 2001 From: Sean Horton <3359434+sean-horton@users.noreply.github.com> Date: Fri, 31 Mar 2023 18:13:19 -0400 Subject: [PATCH 29/82] working reverb --- Source/AliveLibCommon/audio/Sequencer.cpp | 123 +++++++++++++++------- Source/AliveLibCommon/audio/Sequencer.hpp | 4 +- 2 files changed, 86 insertions(+), 41 deletions(-) diff --git a/Source/AliveLibCommon/audio/Sequencer.cpp b/Source/AliveLibCommon/audio/Sequencer.cpp index a1bee2f58..fc9927530 100644 --- a/Source/AliveLibCommon/audio/Sequencer.cpp +++ b/Source/AliveLibCommon/audio/Sequencer.cpp @@ -62,6 +62,11 @@ static constexpr s32 Clamp16(s32 value) : value; } +static constexpr s32 ApplyVolume(s32 sample, s16 volume) +{ + return (sample * s32(volume)) >> 15; +} + ADSR parseADSR(u16 adsr1, u16 adsr2) { ADSR adsr; @@ -308,24 +313,30 @@ void SDLCallback(void* udata, Uint8* stream, int len) // 1. tick voices // a. interpolate // b. adsr - // c. pitch (this seems like it should be before interpolation though...) + // c. pitch // 2. run reverb + // start/stop any sequence notes gseq->tickSequence(); float* AudioStream = (float*) stream; int StreamLength = len / sizeof(float); - float leftSample = 0; - float rightSample = 0; - s32 reverb_in_left = 0; - s32 reverb_in_right = 0; + + s32 reverb_out_left = 0; + s32 reverb_out_right = 0; + for (int i = 0; i < StreamLength; i += 2) { - // set silence if no voice is played + // set silence incase no voices are played AudioStream[i] = 0; AudioStream[i + 1] = 0; - // mix voices + float leftSample = 0; + float rightSample = 0; + s32 reverb_in_left = 0; + s32 reverb_in_right = 0; + + // 1. Prepare all voices with reverb for (Voice* v : gseq->voices) { if (!v->sample || v->complete) @@ -334,9 +345,8 @@ void SDLCallback(void* udata, Uint8* stream, int len) } std::tuple s = v->tick(); - // Tick voice - leftSample = (float) std::get<0>(s); // * v->leftPan; - rightSample = (float) std::get<1>(s); // * v->rightPan; + leftSample += std::get<0>(s); + rightSample += std::get<1>(s); // 0 Off // 1 Vibrate @@ -345,30 +355,27 @@ void SDLCallback(void* udata, Uint8* stream, int len) // 4 Reverb if (v->sample->reverb == 4) { - reverb_in_left += (s32) leftSample; - reverb_in_right += (s32) rightSample; + reverb_in_left += std::get<0>(s); + reverb_in_right += std::get<1>(s); } - - leftSample = leftSample / 32767.0f; - rightSample = rightSample / 32767.0f; - - // Run reverb - // TODO - - // Set against SDL - SDL_MixAudioFormat((Uint8*) (AudioStream + i), (const Uint8*) &leftSample, AUDIO_F32, sizeof(float), 37); // Left Channel - SDL_MixAudioFormat((Uint8*) (AudioStream + i + 1), (const Uint8*) &rightSample, AUDIO_F32, sizeof(float), 37); // Right Channel } - s32 reverb_out_left = 0, reverb_out_right = 0; - ProcessReverb(static_cast(Clamp16(reverb_in_left)), static_cast(Clamp16(reverb_in_right)), - &reverb_out_left, &reverb_out_right); - - leftSample = float(reverb_out_left); - rightSample = float(reverb_out_right); - - SDL_MixAudioFormat((Uint8*) (AudioStream + i), (const Uint8*) &leftSample, AUDIO_F32, sizeof(float), 37); // Left Channel - SDL_MixAudioFormat((Uint8*) (AudioStream + i + 1), (const Uint8*) &rightSample, AUDIO_F32, sizeof(float), 37); // Right Channel + // 2. Process and mix in the reverb + ProcessReverb( + static_cast(Clamp16((s32) (reverb_in_left))), + static_cast(Clamp16((s32) (reverb_in_right))), + &reverb_out_left, + &reverb_out_right + ); + + leftSample += reverb_out_left; + rightSample += reverb_out_right; + + // make value usable by SDL + leftSample = leftSample / 32767.0f; + rightSample = rightSample / 32767.0f; + SDL_MixAudioFormat((Uint8*) (AudioStream + i), (const Uint8*) &leftSample, AUDIO_F32, sizeof(float), 100); + SDL_MixAudioFormat((Uint8*) (AudioStream + i + 1), (const Uint8*) &rightSample, AUDIO_F32, sizeof(float), 100); } } @@ -493,7 +500,7 @@ void Sequencer::tickSequence() v->velocity = message->velocity / 127.0f; v->note = message->note; v->sample = sample; - v->pan = 0; + v->pan = 0; // divide by 63 for sample v->loop = sample->adsr.attackRate > 0; // attack is greater than sample length? sample->adsr.attackRate / 1000 > 1; } @@ -621,11 +628,6 @@ void Sequencer::syncVoice() } } -static constexpr s32 ApplyVolume(s32 sample, s16 volume) -{ - return (sample * s32(volume)) >> 15; -} - std::tuple Voice::tick() { if (!sample) @@ -653,13 +655,13 @@ std::tuple Voice::tick() } - if (f_SampleOffset + vounter.sample_index() >= sample->len - 1 && !loop) + if (!loop && f_SampleOffset + vounter.sample_index() >= sample->len - 1) { complete = true; return std::make_tuple(0, 0); } - f_SampleOffset = f_SampleOffset + vounter.sample_index() >= sample->len ? 3 : f_SampleOffset; + f_SampleOffset = f_SampleOffset + vounter.sample_index() >= sample->len ? 3.0f : f_SampleOffset; s32 sampleData; sampleData = interpolate(); @@ -778,7 +780,8 @@ std::tuple Voice::tick() { add = sequence->volume; } - const float tmp = float(sampleData) * float(adsrCurrentLevel) / float(MAX_VOLUME) * velocity * add; + + const float tmp = float(ApplyVolume(sampleData, adsrCurrentLevel)) * velocity * add; const s32 left = s32(tmp * leftPan); // ApplyVolume(volume, voice.left_volume.current_level); const s32 right = s32(tmp * rightPan); //ApplyVolume(volume, voice.right_volume.current_level); //voice.left_volume.Tick(); @@ -1145,8 +1148,48 @@ void ProcessReverb(s16 left_in, s16 right_in, s32* left_out, s32* right_out) 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 + 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) diff --git a/Source/AliveLibCommon/audio/Sequencer.hpp b/Source/AliveLibCommon/audio/Sequencer.hpp index 9d4096bd2..62e2adb5d 100644 --- a/Source/AliveLibCommon/audio/Sequencer.hpp +++ b/Source/AliveLibCommon/audio/Sequencer.hpp @@ -218,6 +218,8 @@ class Voice s32 pitchMax = 127; float velocity = 1.0f; float pan = 0.0f; + s16 voll = 0; + s16 volr = 0; std::atomic_bool inUse = false; s32 sOldest = 0; @@ -294,7 +296,7 @@ class Sequencer void playSeq(s32 seqId); void stopSeq(s32 seqId); - static const int voiceCount = 256; + static const int voiceCount = 24; Voice* voices[voiceCount]; void tickSequence(); From 59e87225645a0128f43e769195d431ef859e77d4 Mon Sep 17 00:00:00 2001 From: Sean Horton <3359434+sean-horton@users.noreply.github.com> Date: Sat, 1 Apr 2023 10:07:30 -0400 Subject: [PATCH 30/82] change volume calculations --- Source/AliveLibCommon/audio/MidiPlayer.cpp | 43 +-- Source/AliveLibCommon/audio/Sequencer.cpp | 310 ++++++++------------- Source/AliveLibCommon/audio/Sequencer.hpp | 55 +--- 3 files changed, 152 insertions(+), 256 deletions(-) diff --git a/Source/AliveLibCommon/audio/MidiPlayer.cpp b/Source/AliveLibCommon/audio/MidiPlayer.cpp index eabcb0749..e96915722 100644 --- a/Source/AliveLibCommon/audio/MidiPlayer.cpp +++ b/Source/AliveLibCommon/audio/MidiPlayer.cpp @@ -121,8 +121,8 @@ void MidiPlayer::SND_Load_VABS(SoundBlockInfo* pSoundBlockInfo, s32 reverb) patch->samples[x] = sample; sample->adsr = adsr; - sample->volume = vagAttr->field_2_vol / 127.0f; - sample->pan = (vagAttr->field_3_pan / 64.0f) - 1.0f; + sample->volume = 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; @@ -231,7 +231,8 @@ s16 MidiPlayer::SND_SEQ_PlaySeq(u16 idx, s32 repeatCount, s16 bDontStop) { return 1; } - sequencer->getSequence(idx)->volume = 1.0f; + sequencer->getSequence(idx)->voll = 127; + sequencer->getSequence(idx)->volr = 127; sequencer->getSequence(idx)->repeatLimit = repeatCount; sequencer->playSeq(idx); return 1; @@ -286,13 +287,14 @@ void MidiPlayer::sanitizePitch(s32* src, s16 defaultPitch) void MidiPlayer::SND_SEQ_SetVol(s32 idx, s32 volLeft, s32 volRight) { - sanitizeVolume(&volLeft, 10, 127); - sanitizeVolume(&volRight, 10, 127); + sanitizeVolume(&volLeft, 0, 127); + sanitizeVolume(&volRight, 0, 127); sean::Sequence* seq = sequencer->getSequence((s32) idx); if (seq) { - seq->volume = std::min(volLeft, volRight) / 127.0f; + seq->voll = (s16) volLeft; + seq->volr = (s16) volRight; } } @@ -302,7 +304,8 @@ s16 MidiPlayer::SND_SEQ_Play(u16 idx, s32 repeatCount, s16 volLeft, s16 volRight if (seq) { seq->repeatLimit = repeatCount; - seq->volume = std::min(volLeft, volRight) / 127.0f; + seq->voll = volLeft; + seq->volr = volRight; } sequencer->playSeq(idx); return 1; @@ -327,17 +330,17 @@ s32 MidiPlayer::SFX_SfxDefinition_Play(SfxDefinition* sfxDef, s32 volLeft, s32 v sanitizeVolume(&volLeft, 10, 127); sanitizeVolume(&volRight, 10, 127); // TODO - I don't think these pans and volumes are quite right - float volume = std::max(volLeft, volRight) / 127.0f; - float pan; - if (volLeft < volRight) - { - pan = 1.0f - (float(volLeft) / float(volRight)); - } - else - { - pan = (float(volRight) / float(volLeft)) - 1.0f; - } - return sequencer->playNote(sfxDef->program, sfxDef->note, volume, pan, (u8) std::max(pitch_min, pitch_max), pitch_min, pitch_max); + //float volume = std::max(volLeft, volRight) / 127.0f; + //float pan; + //if (volLeft < volRight) + //{ + // pan = 1.0f - (float(volLeft) / float(volRight)); + //} + //else + //{ + // pan = (float(volRight) / float(volLeft)) - 1.0f; + //} + return sequencer->playNote(sfxDef->program, sfxDef->note, (s16) volLeft, (s16) volRight, (u8) std::max(pitch_min, pitch_max), pitch_min, pitch_max, false); } s32 MidiPlayer::SFX_SfxDefinition_Play(SfxDefinition* sfxDef, s32 volume, s32 pitch_min, s32 pitch_max) @@ -351,13 +354,13 @@ s32 MidiPlayer::SFX_SfxDefinition_Play(SfxDefinition* sfxDef, s32 volume, s32 pi sanitizePitch(&pitch_max, sfxDef->pitch_max); sanitizeVolume(&volume, 1, 127); - return sequencer->playNote(sfxDef->program, sfxDef->note, volume / 127.0f, 0, (u8) std::max(pitch_min, pitch_max), pitch_min, pitch_max); + return sequencer->playNote(sfxDef->program, sfxDef->note, (s16) volume, (s16) volume, (u8) std::max(pitch_min, pitch_max), pitch_min, pitch_max, true); } s32 MidiPlayer::SND(s32 program, s32 vabId, s32 note, s16 vol, s16 min, s16 max) { vabId; // TODO - why is this not used? - return sequencer->playNote(program, (u8) note, vol / 127.0f, 0, 0, min, max); + return sequencer->playNote(program, (u8) note, vol, vol, 0, min, max, true); } void MidiPlayer::SsUtAllKeyOff(s32 mode) diff --git a/Source/AliveLibCommon/audio/Sequencer.cpp b/Source/AliveLibCommon/audio/Sequencer.cpp index fc9927530..0370f4bd9 100644 --- a/Source/AliveLibCommon/audio/Sequencer.cpp +++ b/Source/AliveLibCommon/audio/Sequencer.cpp @@ -272,42 +272,12 @@ u64 timeSinceEpochMillisec() return duration_cast(system_clock::now().time_since_epoch()).count(); } -void Sequencer::loop() -{ - u64 start = timeSinceEpochMillisec(); - u64 now = 0; - u64 currentTicks = 0; - u64 expectedTicks = 0; - while (running) - { - now = timeSinceEpochMillisec(); - expectedTicks = u64(float(now - start) / 1000.0f * 44100); - - // tick sequence - i.e. new notes to play/stop? - tickSequence(); - - while (currentTicks++ < expectedTicks) - { - // tick voices - just math calculations. - // this is ticked 44100 times per second. Possibly this - // can be done with a fast math calculation instead of - // a loop, but that's for another time... - tickVoice(); - } - - // sync voices with openal - syncVoice(); - - // defer this thread some amount of time - std::this_thread::sleep_for(std::chrono::milliseconds(1)); - } -} - void SDLCallback(void* udata, Uint8* stream, int len) { udata; stream; len; + gseq->mutex.lock(); // This runs at 44100hz // 1. tick voices @@ -339,7 +309,7 @@ void SDLCallback(void* udata, Uint8* stream, int len) // 1. Prepare all voices with reverb for (Voice* v : gseq->voices) { - if (!v->sample || v->complete) + if (!v->sample || v->complete || !v->inUse) { continue; } @@ -377,28 +347,29 @@ void SDLCallback(void* udata, Uint8* stream, int len) SDL_MixAudioFormat((Uint8*) (AudioStream + i), (const Uint8*) &leftSample, AUDIO_F32, sizeof(float), 100); SDL_MixAudioFormat((Uint8*) (AudioStream + i + 1), (const Uint8*) &rightSample, AUDIO_F32, sizeof(float), 100); } + + gseq->mutex.unlock(); } ////////////////////////// // PRIVATE void Sequencer::reset() { + mutex.lock(); stopAll(); - //for (s16 i = 0; i < patchCount; i++) - //{ - // // must be deleted separately from sources - // // since deleting a patch may delete a buffer - // // attached to a source - // delete patches[i]; - // patches[i] = NULL; - //} - // - //for (Sequence* seq : sequences) - //{ - // delete seq; - //} - //sequences.clear(); + for (s16 i = 0; i < patchCount; i++) + { + + patches[i] = NULL; + } + + for (Sequence* seq : sequences) + { + delete seq; + } + sequences.clear(); + mutex.unlock(); } void Sequencer::stopAll() @@ -416,6 +387,7 @@ void Sequencer::stopAll() void Sequencer::playSeq(s32 seqId) { + mutex.lock(); for (Sequence* seq : sequences) { if (seq->id == seqId) @@ -424,10 +396,18 @@ void Sequencer::playSeq(s32 seqId) seq->play = true; } } + mutex.unlock(); } void Sequencer::stopSeq(s32 seqId) +{ + mutex.lock(); + stopSeqSafe(seqId); + mutex.unlock(); +} + +void Sequencer::stopSeqSafe(s32 seqId) { for (Sequence* seq : sequences) { @@ -459,7 +439,7 @@ void Sequencer::tickSequence() if (seq->repeats > seq->repeatLimit) { - stopSeq(seq->id); + stopSeqSafe(seq->id); continue; } @@ -487,19 +467,34 @@ void Sequencer::tickSequence() continue; } - Voice* v = obtainVoice(); + Voice* v = obtainVoice(message->note, message->patchId); if (!v) { continue; } + s16 right = (s16) (sample->volume); + s16 left = right; + s16 progPan = (s16) sample->pan; + if (progPan < 64) + { + right = (right * progPan) / 63; + } + else + { + left = (left * (127 - progPan)) / 63; + } + v->sequence = seq; v->patchId = seq->channels[message->channelId]->patch->id; v->channelId = message->channelId; v->pitchMin = 0; - v->velocity = message->velocity / 127.0f; + v->velocity = message->velocity; v->note = message->note; v->sample = sample; + v->voll = left; + v->reuse = true; + v->volr = right; v->pan = 0; // divide by 63 for sample v->loop = sample->adsr.attackRate > 0; // attack is greater than sample length? sample->adsr.attackRate / 1000 > 1; } @@ -557,77 +552,6 @@ void Sequencer::tickVoice() } } -void Sequencer::syncVoice() -{ - for (int c = 0; c < voiceCount; c++) - { - Voice* voice = voices[c]; - if (!voice->inUse) - { - continue; - } - - if (!voice->sample) - { - continue; - } - - // Update the play source paramters - Sample* sample = voice->sample; - - // If we are past the duration of the notes playback - //alGetSourcei(id, AL_SOURCE_STATE, &state); - //if (state != AL_PLAYING) - //{ - // releaseVoice(voice); - // continue; - //} - - //u8 note = voice->note; - //s32 notePitch = voice->pitch < voice->pitchMin ? voice->pitchMin : voice->pitch; // or sample? - //u8 rootNote = sample->rootNote; - //u8 rootPitch = sample->rootNotePitchShift; - // This figures out the frequency of midi notes (ex. 60 is middle C and 261.63 hz) - // noteFreq is the note we want to play - // rootFreq is the samples root note - // We then divide the two to figure out how to pitch shift the sample to match the - // the desired note. For some reason we have to multiply it by 2. don't know why. - //float noteFreq = float(pow(2.0, float(note + (notePitch / 127.0f)) / 12.0f)); - //float rootFreq = float(pow(2.0, float(rootNote + (rootPitch / 127.0f)) / 12.0f)); - //float freq = noteFreq / rootFreq * 2.0f; - float pan = voice->pan; - - if (sample->pan) - { - pan = sample->pan; - } - - float seqVolume = 1.0f; - if (voice->sequence) - { - seqVolume = voice->sequence->volume; - } - float gain = float(voice->adsrCurrentLevel) / 32767.0f * voice->velocity * voice->sample->volume* seqVolume; - - //alSource3f(id, AL_POSITION, pan, 0, -sqrtf(1.0f - pan * pan)); - //alSourcef(id, AL_PITCH, (ALfloat) freq); - //alSourcef(id, AL_GAIN, (ALfloat) gain); - //alSourcei(id, AL_LOOPING, voice->loop); - - // 0 Off - // 1 Vibrate - // 2 Portamento - // 3 1 & 2(Portamento and Vibrate on) - // 4 Reverb - - - if (gain < 0.001f && voice->offTime > 0) - { - releaseVoice(voice); - } - } -} - std::tuple Voice::tick() { if (!sample) @@ -655,13 +579,18 @@ std::tuple Voice::tick() } - if (!loop && f_SampleOffset + vounter.sample_index() >= sample->len - 1) + if ((!loop || !reuse) && f_SampleOffset + vounter.sample_index() >= sample->len - 1) { complete = true; return std::make_tuple(0, 0); } - - f_SampleOffset = f_SampleOffset + vounter.sample_index() >= sample->len ? 3.0f : f_SampleOffset; + if (f_SampleOffset + vounter.sample_index() >= sample->len) + { + // TODO - conver this into 28 sample adpcm blocks - should be closer to duckstation + //vounter.bits = 0; + f_SampleOffset = (((int)f_SampleOffset) + vounter.sample_index()) % sample->len; + f_SampleOffset += 3; + } s32 sampleData; sampleData = interpolate(); @@ -763,45 +692,53 @@ std::tuple Voice::tick() std::clamp(static_cast(adsrCurrentLevel) + this_step, MIN_VOLUME, MAX_VOLUME)); } - float centerPan = pan == 0 ? sample->pan : pan; - float leftPan = 1.0f; - float rightPan = 1.0f; - if (centerPan > 0) - { - leftPan = 1.0f - abs(centerPan); - } - if (centerPan < 0) - { - rightPan = 1.0f - abs(centerPan); - } + s32 vol = ApplyVolume(sampleData, adsrCurrentLevel); + vol = ApplyVolume(vol, (s16) (sample->volume * 129 * 2)); + vol = ApplyVolume(vol, (s16) (velocity * 129 * 2)); + + s32 left = ApplyVolume(vol, voll * 129 * 2); + s32 right = ApplyVolume(vol, volr * 129 * 2); - float add = 1; if (sequence) { - add = sequence->volume; + left = ApplyVolume(left, sequence->voll * 129 * 2); + right = ApplyVolume(right, sequence->volr * 129 * 2); } - const float tmp = float(ApplyVolume(sampleData, adsrCurrentLevel)) * velocity * add; - const s32 left = s32(tmp * leftPan); // ApplyVolume(volume, voice.left_volume.current_level); - const s32 right = s32(tmp * rightPan); //ApplyVolume(volume, voice.right_volume.current_level); - //voice.left_volume.Tick(); - //voice.right_volume.Tick(); + // TODO - apply volume sweeps.tick() + return std::make_tuple(left, right); } -Voice* Sequencer::obtainVoice() +Voice* Sequencer::obtainVoice(u8 note, u8 patchId) { - bool expected = false; + note; + patchId; + + Voice* available; + available = NULL; for (int i = 0; i < voiceCount; i++) { Voice* v = voices[i]; + if (v->reuse && v->note == note && v->patchId == patchId) + { + std::cout << "got prev " << (int) note << " " << v->sample << std::endl; + v->f_SampleOffset = 3; + v->vounter.bits = 0; + v->inUse = true; + return v; + //mutex.unlock(); + } + if (v->complete) { releaseVoice(v); } - if (v->inUse.compare_exchange_weak(expected, true)) + if (!v->inUse) { + v->inUse = true; + std::cout << "return " << (int) note << " " << (int) patchId << std::endl; return v; } } @@ -819,9 +756,13 @@ void Sequencer::releaseVoice(Voice* v) v->sequence = NULL; v->loop = false; v->pan = 0; - v->f_SampleOffset = 0; + v->f_SampleOffset = 3; v->vounter.bits = 0; v->complete = false; + v->velocity = 127; + v->voll = 127; + v->volr = 127; + v->complete = false; v->inUse = false; } @@ -841,28 +782,43 @@ Patch* Sequencer::createPatch(s16 id) Sequence* Sequencer::createSequence() { + mutex.lock(); Sequence* seq = new Sequence(); sequences.push_back(seq); + mutex.unlock(); return seq; } Sequence* Sequencer::getSequence(s32 id) { + //mutex.lock(); + Sequence* s; + s = NULL; for (Sequence* seq : sequences) { if (seq->id == id) { - return seq; + s = seq; + break; } } - return NULL; + //mutex.unlock(); + return s; } -s32 Sequencer::playNote(s32 patchId, u8 note, float velocity, float pan, u8 pitch, s32 pitchMin, s32 pitchMax) +s32 Sequencer::playNote(s32 patchId, u8 note, s16 voll, s16 volr, u8 pitch, s32 pitchMin, s32 pitchMax, bool reuse) { + + // TODO - + // - 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) + + mutex.lock(); Patch* patch = patches[patchId]; if (!patch) { + mutex.unlock(); return 0; } @@ -879,16 +835,20 @@ s32 Sequencer::playNote(s32 patchId, u8 note, float velocity, float pan, u8 pitc continue; } - Voice* v = obtainVoice(); + Voice* v = obtainVoice(note, (u8) patchId); if (!v) { + mutex.unlock(); return 0; } v->patchId = (u8) patchId; v->note = note; - v->velocity = velocity; - v->pan = pan; + v->velocity = 127; + //v->pan = pan; + v->voll = voll; + v->volr = volr; + v->reuse = reuse; v->pitch = pitch; v->pitchMin = pitchMin; v->pitchMax = pitchMax; @@ -896,11 +856,13 @@ s32 Sequencer::playNote(s32 patchId, u8 note, float velocity, float pan, u8 pitc ids |= v->id; } + mutex.unlock(); return ids; } void Sequencer::stopNote(s32 mask) { + mutex.lock(); for (int i = 0; i < voiceCount; i++) { if ((voices[i]->id & mask) != 0) @@ -910,6 +872,7 @@ void Sequencer::stopNote(s32 mask) releaseVoice(voices[i]); } } + mutex.unlock(); } ////////////////////////// @@ -956,24 +919,6 @@ MIDIMessage* Sequence::next(u64 now) //////////////////////////// // REVERB //////////////////////////// -//union SPUCNT -//{ -// u16 bits; -// -// BitField enable; -// BitField mute_n; -// BitField noise_clock; -// BitField reverb_master_enable; -// BitField irq9_enable; -// BitField ram_transfer_mode; -// BitField external_audio_reverb; -// BitField cd_audio_reverb; -// BitField external_audio_enable; -// BitField cd_audio_enable; -// -// BitField mode; -//}; -//static SPUCNT s_SPUCNT = {}; static const u32 NUM_REVERB_REGS = 32; struct ReverbRegisters @@ -1247,33 +1192,6 @@ void ProcessReverb(s16 left_in, s16 right_in, 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); - -#ifdef SPU_DUMP_ALL_VOICES - if (s_voice_dump_writers[NUM_VOICES]) - { - const s16 dump_samples[2] = {static_cast(Clamp16(s_last_reverb_output[0])), - static_cast(Clamp16(s_last_reverb_output[1]))}; - s_voice_dump_writers[NUM_VOICES]->WriteFrames(dump_samples, 1); - } -#endif -} - - - -////////////////////////// -// Event Ring -void EventRing::push(Event event) -{ - events[(head++) % size] = event; -} - -Event* EventRing::pop() -{ - if (tail == head) - { - return NULL; - } - return &events[(tail++)]; } } // namespace \ No newline at end of file diff --git a/Source/AliveLibCommon/audio/Sequencer.hpp b/Source/AliveLibCommon/audio/Sequencer.hpp index 62e2adb5d..732a3808b 100644 --- a/Source/AliveLibCommon/audio/Sequencer.hpp +++ b/Source/AliveLibCommon/audio/Sequencer.hpp @@ -159,7 +159,8 @@ class Sequence s32 repeatLimit = 1; std::atomic_bool play = false; - float volume = 1; + s16 voll = 127; + s16 volr = 127; float pan = 0; float tempoUs; // BPM - defined in in microseconds (us) float ticksPerBeat; @@ -216,16 +217,18 @@ class Voice s32 pitch; s32 pitchMin = 0; s32 pitchMax = 127; - float velocity = 1.0f; + float velocity = 127; float pan = 0.0f; s16 voll = 0; s16 volr = 0; - std::atomic_bool inUse = false; + bool inUse = false; - s32 sOldest = 0; - s32 sOlder = 0; - s32 sOld = 0; - s32 sNew = 0; + bool reuse = false; + + s16 sOldest = 0; + s16 sOlder = 0; + s16 sOld = 0; + s16 sNew = 0; bool complete = false; bool loop = false; @@ -250,29 +253,6 @@ class Voice }; -/* -* Non blocking event ring for -* cross thread actions (oddworld to midi player) -*/ -class Event -{ - void (*func)(); -}; - -class EventRing -{ -public: - void push(Event event); - Event* pop(); - -private: - static const int size = 256; - Event events[size]; - std::atomic tail = 0; - std::atomic head = 0; -}; - - /* * Can play MIDI */ @@ -290,7 +270,7 @@ class Sequencer Sequence* createSequence(); Sequence* getSequence(s32 id); - s32 playNote(s32 patchId, u8 note, float velocity, float pan, u8 pitch, s32 pitchMin, s32 pitchMax); + s32 playNote(s32 patchId, u8 note, s16 voll, s16 volr, u8 pitch, s32 pitchMin, s32 pitchMax, bool reuse); void stopNote(s32 mask); void playSeq(s32 seqId); @@ -300,25 +280,20 @@ class Sequencer Voice* voices[voiceCount]; void tickSequence(); + std::mutex mutex; private: std::thread* thread; bool running; - void loop(); - EventRing eventRing; - - s32 uuid = 1; - s32 nextUuid(); - Voice* obtainVoice(); + Voice* obtainVoice(u8 note, u8 patchId); void releaseVoice(Voice* v); - void tickVoice(); - void syncVoice(); - std::vector sequences; + void stopSeqSafe(s32 seqId); + std::vector sequences; static const int patchCount = 128; Patch* patches[patchCount]; From 707c9e822c591307720837e8abaabd7ffc3873b3 Mon Sep 17 00:00:00 2001 From: Sean Horton <3359434+sean-horton@users.noreply.github.com> Date: Sat, 1 Apr 2023 13:10:03 -0400 Subject: [PATCH 31/82] fix loop popping? --- Source/AliveLibCommon/audio/Sequencer.cpp | 63 +++++++++++++++++------ Source/AliveLibCommon/audio/Sequencer.hpp | 23 +++++++-- 2 files changed, 68 insertions(+), 18 deletions(-) diff --git a/Source/AliveLibCommon/audio/Sequencer.cpp b/Source/AliveLibCommon/audio/Sequencer.cpp index 0370f4bd9..ff9219000 100644 --- a/Source/AliveLibCommon/audio/Sequencer.cpp +++ b/Source/AliveLibCommon/audio/Sequencer.cpp @@ -12,8 +12,6 @@ namespace sean { static void ProcessReverb(s16 left_in, s16 right_in, s32* left_out, s32* right_out); static s16 ReverbRead(u32 address, s32 offset = 0); -static const u32 NUM_SAMPLES_PER_ADPCM_BLOCK = 28; - u32 mask(u32 num) { u32 res = 0; @@ -197,14 +195,49 @@ s32 Voice::interpolate() // the interpolation index is based on the source files sample rate const u8 i = (u8)vounter.interpolation_index(); - const u32 s = ZeroExtend32(vounter.sample_index()); + const u32 s = ((u32)f_SampleOffset) + ZeroExtend32(vounter.sample_index()); // interpolate on 4 most recent samples s32 out = 0; - out += s32(gauss[0x0FF - i]) * s32(sample->buffer[(int) (f_SampleOffset + s) - 3]); // oldest s16 - but should it be s8? - out += s32(gauss[0x1FF - i]) * s32(sample->buffer[(int) (f_SampleOffset + s) - 2]); // older - out += s32(gauss[0x100 + i]) * s32(sample->buffer[(int) (f_SampleOffset + s) - 1]); // old - out += s32(gauss[0x000 + i]) * s32(sample->buffer[(int) (f_SampleOffset + s) - 0]); // new + if (s == 0) + { + if (!isFirstBlock) + { + out += s32(gauss[0x0FF - i]) * s32(sample->buffer[sample->len - 3]); // oldest s16 - but should it be s8? + out += s32(gauss[0x1FF - i]) * s32(sample->buffer[sample->len - 2]); // older + out += s32(gauss[0x100 + i]) * s32(sample->buffer[sample->len - 1]); // old + } + out += s32(gauss[0x000 + i]) * s32(sample->buffer[s - 0]); // new + + } + else if (s == 1) + { + if (!isFirstBlock) + { + out += s32(gauss[0x0FF - i]) * s32(sample->buffer[sample->len - 2]); // oldest s16 - but should it be s8? + out += s32(gauss[0x1FF - i]) * s32(sample->buffer[sample->len - 1]); // older + } + out += s32(gauss[0x100 + i]) * s32(sample->buffer[s - 1]); // old + out += s32(gauss[0x000 + i]) * s32(sample->buffer[s - 0]); // new + } + else if (s == 2) + { + if (!isFirstBlock) + { + out += s32(gauss[0x0FF - i]) * s32(sample->buffer[sample->len - 1]); // oldest s16 - but should it be s8? + } + out += s32(gauss[0x1FF - i]) * s32(sample->buffer[s - 2]); // older + out += s32(gauss[0x100 + i]) * s32(sample->buffer[s - 1]); // old + out += s32(gauss[0x000 + i]) * s32(sample->buffer[s - 0]); // new + } + else + { + out += s32(gauss[0x0FF - i]) * s32(sample->buffer[(int) (s ) - 3]); // oldest s16 - but should it be s8? + out += s32(gauss[0x1FF - i]) * s32(sample->buffer[(int) (s ) - 2]); // older + out += s32(gauss[0x100 + i]) * s32(sample->buffer[(int) (s ) - 1]); // old + out += s32(gauss[0x000 + i]) * s32(sample->buffer[(int) (s ) - 0]); // new + } + return out >> 15; } @@ -576,20 +609,19 @@ std::tuple Voice::tick() // shift << 12 as sample_index is shifted over 12 vounter.bits -= (NUM_SAMPLES_PER_ADPCM_BLOCK << 12); f_SampleOffset += NUM_SAMPLES_PER_ADPCM_BLOCK; + isFirstBlock = false; } - if ((!loop || !reuse) && f_SampleOffset + vounter.sample_index() >= sample->len - 1) + if ((!loop || !reuse) && f_SampleOffset + NUM_SAMPLES_PER_ADPCM_BLOCK >= sample->len - 1) { complete = true; return std::make_tuple(0, 0); } - if (f_SampleOffset + vounter.sample_index() >= sample->len) + if (f_SampleOffset + NUM_SAMPLES_PER_ADPCM_BLOCK >= sample->len) { - // TODO - conver this into 28 sample adpcm blocks - should be closer to duckstation - //vounter.bits = 0; - f_SampleOffset = (((int)f_SampleOffset) + vounter.sample_index()) % sample->len; - f_SampleOffset += 3; + // NOTE: This seems like it might be wrong, but it fixes audio popping on loop + f_SampleOffset = (((int) f_SampleOffset) + NUM_SAMPLES_PER_ADPCM_BLOCK) % sample->len; } s32 sampleData; @@ -723,7 +755,7 @@ Voice* Sequencer::obtainVoice(u8 note, u8 patchId) if (v->reuse && v->note == note && v->patchId == patchId) { std::cout << "got prev " << (int) note << " " << v->sample << std::endl; - v->f_SampleOffset = 3; + v->f_SampleOffset = 0; v->vounter.bits = 0; v->inUse = true; return v; @@ -756,13 +788,14 @@ void Sequencer::releaseVoice(Voice* v) v->sequence = NULL; v->loop = false; v->pan = 0; - v->f_SampleOffset = 3; + v->f_SampleOffset = 0; v->vounter.bits = 0; v->complete = false; v->velocity = 127; v->voll = 127; v->volr = 127; v->complete = false; + v->isFirstBlock = true; v->inUse = false; } diff --git a/Source/AliveLibCommon/audio/Sequencer.hpp b/Source/AliveLibCommon/audio/Sequencer.hpp index 732a3808b..af1933b79 100644 --- a/Source/AliveLibCommon/audio/Sequencer.hpp +++ b/Source/AliveLibCommon/audio/Sequencer.hpp @@ -4,9 +4,13 @@ #include #include #include +#include namespace sean { +const u32 NUM_SAMPLES_FROM_LAST_ADPCM_BLOCK = 3; +const u32 NUM_SAMPLES_PER_ADPCM_BLOCK = 28; + const s16 MIN_VOLUME = 0; const s16 MAX_VOLUME = 32767; @@ -204,12 +208,25 @@ union VoiceCounter } }; +class VolumeEnvelope +{ +public: + s16 current_level; + s16 target_level; + s32 counter; + bool decreasing; + void Tick(); +}; + class Voice { public: s32 id; - double f_SampleOffset = 3; + + bool isFirstBlock = true; + + double f_SampleOffset = 0; Sequence* sequence = NULL; u8 patchId; u8 channelId; @@ -219,8 +236,8 @@ class Voice s32 pitchMax = 127; float velocity = 127; float pan = 0.0f; - s16 voll = 0; - s16 volr = 0; + s16 voll; + s16 volr; bool inUse = false; bool reuse = false; From dec841ddef55b27ad287f47bc667811a93bfb03e Mon Sep 17 00:00:00 2001 From: Sean Horton <3359434+sean-horton@users.noreply.github.com> Date: Sat, 1 Apr 2023 19:58:59 -0400 Subject: [PATCH 32/82] start of some cleanup --- Source/AliveLibCommon/audio/MidiPlayer.cpp | 89 +++++++------ Source/AliveLibCommon/audio/Sequencer.cpp | 141 +++++++++++---------- Source/AliveLibCommon/audio/Sequencer.hpp | 15 +-- 3 files changed, 122 insertions(+), 123 deletions(-) diff --git a/Source/AliveLibCommon/audio/MidiPlayer.cpp b/Source/AliveLibCommon/audio/MidiPlayer.cpp index e96915722..9749ca965 100644 --- a/Source/AliveLibCommon/audio/MidiPlayer.cpp +++ b/Source/AliveLibCommon/audio/MidiPlayer.cpp @@ -69,6 +69,41 @@ void MidiPlayer::SND_Shutdown() delete sequencer; } + +// One - shot VAGs will be created with an additional 16 - byte block +// attached to the end.The block is used to prevent unnecessary SPU +// interrupts or SPU free - run.The block reads as follows : +// “00077777 77777777 77777777 77777777” or +// “00070000 00000000 00000000 00000000.” Looping VAGs do not contain +// this block. +// +// https://psx.arthus.net/sdk/Psy-Q/DOCS/LibOver47.pdf +// +// Doesn't seem to work for PC samples, maybe check PSX samples +// in the future. +bool isLoop(sean::Sample* sample) +{ + s16* b = sample->buffer; + u32 l = sample->len; + + bool oneshot = false; + oneshot |= b[l - 16] == 0x00 && b[l - 15] == 0x07 && b[l - 14] == 0x77 + && b[l - 13] == 0x77 && b[l - 12] == 0x77 && b[l - 11] == 0x77 + && b[l - 10] == 0x77 && b[l - 9] == 0x77 && b[l - 8] == 0x77 + && b[l - 7] == 0x77 && b[l - 6] == 0x77 && b[l - 5] == 0x77 + && b[l - 4] == 0x77 && b[l - 3] == 0x77 && b[l - 2] == 0x77 + && b[l - 1] == 0x77; + + oneshot |= b[l - 16] == 0x00 && b[l - 15] == 0x07 && b[l - 14] == 0x00 + && b[l - 13] == 0x00 && b[l - 12] == 0x00 && b[l - 11] == 0x00 + && b[l - 10] == 0x00 && b[l - 9] == 0x00 && b[l - 8] == 0x00 + && b[l - 7] == 0x00 && b[l - 6] == 0x00 && b[l - 5] == 0x00 + && b[l - 4] == 0x00 && b[l - 3] == 0x00 && b[l - 2] == 0x00 + && b[l - 1] == 0x00; + + return oneshot; +} + void MidiPlayer::SND_Load_VABS(SoundBlockInfo* pSoundBlockInfo, s32 reverb) { reverb; // TODO - what do we do with this? override the patch/sample? @@ -113,7 +148,6 @@ void MidiPlayer::SND_Load_VABS(SoundBlockInfo* pSoundBlockInfo, s32 reverb) unsigned short ADSR1 = vagAttr->field_10_adsr1; unsigned short ADSR2 = vagAttr->field_12_adsr2; - sean::ADSR adsr = sean::parseADSR(ADSR1, ADSR2); sean::Patch* patch = sequencer->createPatch(vagAttr->field_14_prog); Sample* s = samples.at(vagAttr->field_16_vag - 1); @@ -128,7 +162,14 @@ void MidiPlayer::SND_Load_VABS(SoundBlockInfo* pSoundBlockInfo, s32 reverb) sample->rootNotePitchShift = vagAttr->field_5_shift; sample->minNote = vagAttr->field_6_min; sample->maxNote = vagAttr->field_7_max; - //sample->loop = + + // this "works" to figure out if it's a looping sample, + // don't know why... apparently the SPU expects a specific + // 16 byte block for looping, but not available with PC samples? + REAL_ADSR realADSR; + PSXConvADSR(&realADSR, ADSR1, ADSR2, false); + sample->loop = realADSR.attack_time > 1; + // sample->loop = isLoop(sample); } ++vagAttr; } @@ -180,7 +221,6 @@ void MidiPlayer::SND_Load_Seqs(OpenSeqHandle* pSeqTable, const char_type* bsqFil void MidiPlayer::SND_StopAll() { // called when pause menu is open - std::cout << "stop all" << std::endl; sequencer->stopAll(); } @@ -194,8 +234,8 @@ void MidiPlayer::SND_Reset() void MidiPlayer::SND_Restart() { - std::cout << "restart" << std::endl; // TODO - don't know when called + std::cout << "restart" << std::endl; } void MidiPlayer::SND_Stop_Channels_Mask(u32 bitMask) @@ -236,28 +276,6 @@ s16 MidiPlayer::SND_SEQ_PlaySeq(u16 idx, s32 repeatCount, s16 bDontStop) sequencer->getSequence(idx)->repeatLimit = repeatCount; sequencer->playSeq(idx); return 1; - - - //SequencePlayer* player = GetSequencePlayer(idx); - - //// When chanting starts bDontStop is 1 - //// and then 0 is called every frame until chanting stops. - //// I think we can return if it's 0 - //if (player && bDontStop == 0) - //{ - // return 1; // still playing - //} - - //if (!player) - //{ - // player = new SequencePlayer(); - // mSequencePlayers.push_back(player); - //} - //std::cout << "Play seq " << idx << "\n"; - - //player->LoadSequenceData(mSequences.at(s16(idx)), s32(idx), repeatCount); - //player->PlaySequence(idx); - //return s16(mSequencePlayers.size() - 1); } void MidiPlayer::sanitizeVolume(s32* src, s32 low, s32 high) @@ -329,18 +347,8 @@ s32 MidiPlayer::SFX_SfxDefinition_Play(SfxDefinition* sfxDef, s32 volLeft, s32 v sanitizeVolume(&volLeft, 10, 127); sanitizeVolume(&volRight, 10, 127); - // TODO - I don't think these pans and volumes are quite right - //float volume = std::max(volLeft, volRight) / 127.0f; - //float pan; - //if (volLeft < volRight) - //{ - // pan = 1.0f - (float(volLeft) / float(volRight)); - //} - //else - //{ - // pan = (float(volRight) / float(volLeft)) - 1.0f; - //} - return sequencer->playNote(sfxDef->program, sfxDef->note, (s16) volLeft, (s16) volRight, (u8) std::max(pitch_min, pitch_max), pitch_min, pitch_max, false); + + return sequencer->playNote(sfxDef->program, sfxDef->note, (s16) volLeft, (s16) volRight, (u8) 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) @@ -349,18 +357,17 @@ s32 MidiPlayer::SFX_SfxDefinition_Play(SfxDefinition* sfxDef, s32 volume, s32 pi { volume = sfxDef->volume; } - sanitizePitch(&pitch_min, sfxDef->pitch_min); sanitizePitch(&pitch_max, sfxDef->pitch_max); sanitizeVolume(&volume, 1, 127); - return sequencer->playNote(sfxDef->program, sfxDef->note, (s16) volume, (s16) volume, (u8) std::max(pitch_min, pitch_max), pitch_min, pitch_max, true); + return sequencer->playNote(sfxDef->program, sfxDef->note, (s16) volume, (s16) volume, (u8) 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 sequencer->playNote(program, (u8) note, vol, vol, 0, min, max, true); + return sequencer->playNote(program, (u8) note, vol, vol, 0, min, max); } void MidiPlayer::SsUtAllKeyOff(s32 mode) diff --git a/Source/AliveLibCommon/audio/Sequencer.cpp b/Source/AliveLibCommon/audio/Sequencer.cpp index ff9219000..aa19bb85e 100644 --- a/Source/AliveLibCommon/audio/Sequencer.cpp +++ b/Source/AliveLibCommon/audio/Sequencer.cpp @@ -197,13 +197,16 @@ s32 Voice::interpolate() const u8 i = (u8)vounter.interpolation_index(); const u32 s = ((u32)f_SampleOffset) + ZeroExtend32(vounter.sample_index()); - // interpolate on 4 most recent samples + // interpolate on the 4 most recent samples from current position + // The below `if` statements are in case we loop + // we gauss the end of the sample with the beginning. + // Probably a better way to do it, but w/e for now... s32 out = 0; if (s == 0) { if (!isFirstBlock) { - out += s32(gauss[0x0FF - i]) * s32(sample->buffer[sample->len - 3]); // oldest s16 - but should it be s8? + out += s32(gauss[0x0FF - i]) * s32(sample->buffer[sample->len - 3]); // oldest out += s32(gauss[0x1FF - i]) * s32(sample->buffer[sample->len - 2]); // older out += s32(gauss[0x100 + i]) * s32(sample->buffer[sample->len - 1]); // old } @@ -214,25 +217,25 @@ s32 Voice::interpolate() { if (!isFirstBlock) { - out += s32(gauss[0x0FF - i]) * s32(sample->buffer[sample->len - 2]); // oldest s16 - but should it be s8? + out += s32(gauss[0x0FF - i]) * s32(sample->buffer[sample->len - 2]); // oldest out += s32(gauss[0x1FF - i]) * s32(sample->buffer[sample->len - 1]); // older } out += s32(gauss[0x100 + i]) * s32(sample->buffer[s - 1]); // old - out += s32(gauss[0x000 + i]) * s32(sample->buffer[s - 0]); // new + out += s32(gauss[0x000 + i]) * s32(sample->buffer[s - 0]); // new } else if (s == 2) { if (!isFirstBlock) { - out += s32(gauss[0x0FF - i]) * s32(sample->buffer[sample->len - 1]); // oldest s16 - but should it be s8? + out += s32(gauss[0x0FF - i]) * s32(sample->buffer[sample->len - 1]); // oldest } out += s32(gauss[0x1FF - i]) * s32(sample->buffer[s - 2]); // older - out += s32(gauss[0x100 + i]) * s32(sample->buffer[s - 1]); // old - out += s32(gauss[0x000 + i]) * s32(sample->buffer[s - 0]); // new + out += s32(gauss[0x100 + i]) * s32(sample->buffer[s - 1]); // old + out += s32(gauss[0x000 + i]) * s32(sample->buffer[s - 0]); // new } else { - out += s32(gauss[0x0FF - i]) * s32(sample->buffer[(int) (s ) - 3]); // oldest s16 - but should it be s8? + out += s32(gauss[0x0FF - i]) * s32(sample->buffer[(int) (s ) - 3]); // oldest out += s32(gauss[0x1FF - i]) * s32(sample->buffer[(int) (s ) - 2]); // older out += s32(gauss[0x100 + i]) * s32(sample->buffer[(int) (s ) - 1]); // old out += s32(gauss[0x000 + i]) * s32(sample->buffer[(int) (s ) - 0]); // new @@ -281,17 +284,10 @@ Sequencer::Sequencer() { patches[i] = NULL; } - - //running = true; - //thread = new std::thread(&Sequencer::loop, this); } Sequencer::~Sequencer() { - //running = false; - //thread->join(); - //delete thread; - // Delete buffers (by killing the instrument) for (Patch* patch : patches) { @@ -308,8 +304,6 @@ u64 timeSinceEpochMillisec() void SDLCallback(void* udata, Uint8* stream, int len) { udata; - stream; - len; gseq->mutex.lock(); // This runs at 44100hz @@ -328,6 +322,7 @@ void SDLCallback(void* udata, Uint8* stream, int len) s32 reverb_out_left = 0; s32 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 @@ -460,6 +455,8 @@ void Sequencer::stopSeqSafe(s32 seqId) void Sequencer::tickSequence() { + // TODO - convert this to be based on 44100hz instead of ms timestamp + u64 now = timeSinceEpochMillisec(); // Tick sequences @@ -470,7 +467,7 @@ void Sequencer::tickSequence() continue; } - if (seq->repeats > seq->repeatLimit) + if (seq->repeats >= seq->repeatLimit && seq->repeatLimit > 0) { stopSeqSafe(seq->id); continue; @@ -511,11 +508,11 @@ void Sequencer::tickSequence() s16 progPan = (s16) sample->pan; if (progPan < 64) { - right = (right * progPan) / 63; +right = (right * progPan) / 63; } else { - left = (left * (127 - progPan)) / 63; + left = (left * (127 - progPan)) / 63; } v->sequence = seq; @@ -526,10 +523,8 @@ void Sequencer::tickSequence() v->note = message->note; v->sample = sample; v->voll = left; - v->reuse = true; v->volr = right; - v->pan = 0; // divide by 63 for sample - v->loop = sample->adsr.attackRate > 0; // attack is greater than sample length? sample->adsr.attackRate / 1000 > 1; + v->loop = sample->loop; // // attack is greater than sample length? sample->adsr.attackRate / 1000 > 1; } break; @@ -552,7 +547,7 @@ void Sequencer::tickSequence() } case END_TRACK: { - // repeats are handled in the sequence when messages starts at position 0 again + // repeats are handled in the sequence when messages restart at position 0 break; } case PITCH_BEND: @@ -581,7 +576,7 @@ void Sequencer::tickVoice() continue; } - voice->tick(); + voice->tick(); } } @@ -596,53 +591,52 @@ std::tuple Voice::tick() s32 notePitch = pitch < pitchMin ? pitchMin : pitch; // or sample? u8 rootNote = sample->rootNote; u8 rootPitch = sample->rootNotePitchShift; - - // frequencies are stable for duration of note? - move elsewhere to cut down on maths? float noteFreq = float(pow(2.0, float(note + (notePitch / 127.0f)) / 12.0f)); float rootFreq = float(pow(2.0, float(rootNote + (rootPitch / 127.0f)) / 12.0f)); - //float freq = (float) ((noteFreq / rootFreq) * (float(sample->SampleRate) / 44100.0f)); - //f_SampleOffset += freq; - + float noteMultiple = noteFreq / rootFreq; + // vounter (gauss interpolation table) runs at 28 byte blocks. + // move the sample offset forward every 28 bytes if (vounter.sample_index() >= NUM_SAMPLES_PER_ADPCM_BLOCK) { - // shift << 12 as sample_index is shifted over 12 + // vounter holds sample position shifted << 12 vounter.bits -= (NUM_SAMPLES_PER_ADPCM_BLOCK << 12); f_SampleOffset += NUM_SAMPLES_PER_ADPCM_BLOCK; isFirstBlock = false; } - - if ((!loop || !reuse) && f_SampleOffset + NUM_SAMPLES_PER_ADPCM_BLOCK >= sample->len - 1) + // Are we at the end of the sample? + if (f_SampleOffset + vounter.sample_index() >= sample->len) { - complete = true; - return std::make_tuple(0, 0); - } - if (f_SampleOffset + NUM_SAMPLES_PER_ADPCM_BLOCK >= sample->len) - { - // NOTE: This seems like it might be wrong, but it fixes audio popping on loop - f_SampleOffset = (((int) f_SampleOffset) + NUM_SAMPLES_PER_ADPCM_BLOCK) % sample->len; + if (!loop) + { + complete = true; + return std::make_tuple(0, 0); + } + + // we can loop this sample + vounter.bits = 0; + f_SampleOffset = 0; } s32 sampleData; sampleData = interpolate(); - // duckstation uses this to get the actual sample rate in hz - // (float(v.regs.adpcm_sample_rate) / 4096.0f) * 44100.0f) - - u16 step = (u16) (((float(sample->SampleRate) * (noteFreq / rootFreq)) / 44100.0f) * 4096.0f); - //const s32 factor = std::clamp(sampleData, -0x8000, 0x7FFF) + 0x8000; - //step = Truncate16(static_cast((SignExtend32(step) * factor) >> 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 + u16 step = (u16) (((float(sample->SampleRate) * noteMultiple) / 44100.0f) * 4096.0f); + // if (v->isPitchModulated) + // { + // const s32 factor = std::clamp(sampleData, -0x8000, 0x7FFF) + 0x8000; + // step = Truncate16(static_cast((SignExtend32(step) * factor) >> 15)); + // } step = std::min(step, 0x3FFF); vounter.bits += step; - //s32 volume; - //volume = ApplyVolume(sampleData, 1); // voice.regs.adsr_volume - - // UPDATE ADSR STATE - this probably doesn't need to be done every tick? + // UPDATE ADSR STATE - done at 441000hz if (adsrPhase == NONE) { - //std::cout << sample->adsr.attackRate << " " << velocity << std::endl; adsrPhase = ATTACK; adsrDecreasing = false; adsrRate = sample->adsr.attackRate; @@ -685,7 +679,7 @@ std::tuple Voice::tick() return std::make_tuple(0, 0); } - // UPDATE TICK STATE + // UPDATE ADSR TICK STATE adsrCounter--; if (adsrCounter <= 0) { @@ -724,10 +718,14 @@ std::tuple Voice::tick() std::clamp(static_cast(adsrCurrentLevel) + this_step, MIN_VOLUME, MAX_VOLUME)); } + // Set the volume of the sample s32 vol = ApplyVolume(sampleData, adsrCurrentLevel); vol = ApplyVolume(vol, (s16) (sample->volume * 129 * 2)); vol = ApplyVolume(vol, (s16) (velocity * 129 * 2)); + // 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 s32 left = ApplyVolume(vol, voll * 129 * 2); s32 right = ApplyVolume(vol, volr * 129 * 2); @@ -737,29 +735,27 @@ std::tuple Voice::tick() right = ApplyVolume(right, sequence->volr * 129 * 2); } - // TODO - apply volume sweeps.tick() - return std::make_tuple(left, right); } Voice* Sequencer::obtainVoice(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; available = NULL; for (int i = 0; i < voiceCount; i++) { Voice* v = voices[i]; - if (v->reuse && v->note == note && v->patchId == patchId) + if (v->note == note && v->patchId == patchId) { - std::cout << "got prev " << (int) note << " " << v->sample << std::endl; - v->f_SampleOffset = 0; - v->vounter.bits = 0; - v->inUse = true; - return v; - //mutex.unlock(); + if (!available || available->f_SampleOffset > v->f_SampleOffset) + { + available = v; + } } if (v->complete) @@ -770,15 +766,24 @@ Voice* Sequencer::obtainVoice(u8 note, u8 patchId) if (!v->inUse) { v->inUse = true; - std::cout << "return " << (int) note << " " << (int) patchId << std::endl; return v; } } - return NULL; + + if (!available) + { + return NULL; + } + + // this is voice in use that we can reuse + releaseVoice(available); + available->inUse = true; + return available; } void Sequencer::releaseVoice(Voice* v) { + v->channelId = 0xFF; v->offTime = 0; v->pitch = 0; v->adsrPhase = NONE; @@ -787,7 +792,6 @@ void Sequencer::releaseVoice(Voice* v) v->adsrTargetLevel = MAX_VOLUME; v->sequence = NULL; v->loop = false; - v->pan = 0; v->f_SampleOffset = 0; v->vounter.bits = 0; v->complete = false; @@ -839,7 +843,7 @@ Sequence* Sequencer::getSequence(s32 id) return s; } -s32 Sequencer::playNote(s32 patchId, u8 note, s16 voll, s16 volr, u8 pitch, s32 pitchMin, s32 pitchMax, bool reuse) +s32 Sequencer::playNote(s32 patchId, u8 note, s16 voll, s16 volr, u8 pitch, s32 pitchMin, s32 pitchMax) { // TODO - @@ -878,13 +882,12 @@ s32 Sequencer::playNote(s32 patchId, u8 note, s16 voll, s16 volr, u8 pitch, s32 v->patchId = (u8) patchId; v->note = note; v->velocity = 127; - //v->pan = pan; v->voll = voll; v->volr = volr; - v->reuse = reuse; v->pitch = pitch; v->pitchMin = pitchMin; v->pitchMax = pitchMax; + v->loop = s->loop; v->sample = s; ids |= v->id; } diff --git a/Source/AliveLibCommon/audio/Sequencer.hpp b/Source/AliveLibCommon/audio/Sequencer.hpp index af1933b79..587e3c623 100644 --- a/Source/AliveLibCommon/audio/Sequencer.hpp +++ b/Source/AliveLibCommon/audio/Sequencer.hpp @@ -56,6 +56,7 @@ class Sample // reverb style - 0 is none s8 reverb; + bool loop; // Root Key u8 rootNote; @@ -223,7 +224,6 @@ class Voice public: s32 id; - bool isFirstBlock = true; double f_SampleOffset = 0; @@ -235,18 +235,10 @@ class Voice s32 pitchMin = 0; s32 pitchMax = 127; float velocity = 127; - float pan = 0.0f; s16 voll; s16 volr; bool inUse = false; - bool reuse = false; - - s16 sOldest = 0; - s16 sOlder = 0; - s16 sOld = 0; - s16 sNew = 0; - bool complete = false; bool loop = false; u64 offTime = 0; // when the note was released @@ -287,7 +279,7 @@ class Sequencer Sequence* createSequence(); Sequence* getSequence(s32 id); - s32 playNote(s32 patchId, u8 note, s16 voll, s16 volr, u8 pitch, s32 pitchMin, s32 pitchMax, bool reuse); + s32 playNote(s32 patchId, u8 note, s16 voll, s16 volr, u8 pitch, s32 pitchMin, s32 pitchMax); void stopNote(s32 mask); void playSeq(s32 seqId); @@ -301,9 +293,6 @@ class Sequencer private: - std::thread* thread; - bool running; - Voice* obtainVoice(u8 note, u8 patchId); void releaseVoice(Voice* v); void tickVoice(); From 0ff1daf668ca48cf9de91fb13507e5408c9538bc Mon Sep 17 00:00:00 2001 From: Sean Horton <3359434+sean-horton@users.noreply.github.com> Date: Sat, 1 Apr 2023 22:02:11 -0400 Subject: [PATCH 33/82] formatting --- Source/AliveLibCommon/audio/Sequencer.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/AliveLibCommon/audio/Sequencer.cpp b/Source/AliveLibCommon/audio/Sequencer.cpp index aa19bb85e..d080ad813 100644 --- a/Source/AliveLibCommon/audio/Sequencer.cpp +++ b/Source/AliveLibCommon/audio/Sequencer.cpp @@ -508,11 +508,11 @@ void Sequencer::tickSequence() s16 progPan = (s16) sample->pan; if (progPan < 64) { -right = (right * progPan) / 63; + right = (right * progPan) / 63; } else { - left = (left * (127 - progPan)) / 63; + left = (left * (127 - progPan)) / 63; } v->sequence = seq; From 3a357e78273473bcbdc1d587bf6f9017b3261b30 Mon Sep 17 00:00:00 2001 From: Sean Horton <3359434+sean-horton@users.noreply.github.com> Date: Sat, 1 Apr 2023 22:16:55 -0400 Subject: [PATCH 34/82] lower mix volume --- Source/AliveLibCommon/audio/Sequencer.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/AliveLibCommon/audio/Sequencer.cpp b/Source/AliveLibCommon/audio/Sequencer.cpp index d080ad813..64faea87a 100644 --- a/Source/AliveLibCommon/audio/Sequencer.cpp +++ b/Source/AliveLibCommon/audio/Sequencer.cpp @@ -372,8 +372,8 @@ void SDLCallback(void* udata, Uint8* stream, int len) // make value usable by SDL leftSample = leftSample / 32767.0f; rightSample = rightSample / 32767.0f; - SDL_MixAudioFormat((Uint8*) (AudioStream + i), (const Uint8*) &leftSample, AUDIO_F32, sizeof(float), 100); - SDL_MixAudioFormat((Uint8*) (AudioStream + i + 1), (const Uint8*) &rightSample, AUDIO_F32, sizeof(float), 100); + SDL_MixAudioFormat((Uint8*) (AudioStream + i), (const Uint8*) &leftSample, AUDIO_F32, sizeof(float), 70); + SDL_MixAudioFormat((Uint8*) (AudioStream + i + 1), (const Uint8*) &rightSample, AUDIO_F32, sizeof(float), 70); } gseq->mutex.unlock(); From 263b10604e233b266d71970f06025f853db4d5b8 Mon Sep 17 00:00:00 2001 From: Sean Horton <3359434+sean-horton@users.noreply.github.com> Date: Sun, 2 Apr 2023 07:43:29 -0400 Subject: [PATCH 35/82] wrong voice may pitch bend from sequence --- Source/AliveLibCommon/audio/Sequencer.cpp | 13 +++++++------ Source/AliveLibCommon/audio/Sequencer.hpp | 2 +- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/Source/AliveLibCommon/audio/Sequencer.cpp b/Source/AliveLibCommon/audio/Sequencer.cpp index 64faea87a..59c5a72da 100644 --- a/Source/AliveLibCommon/audio/Sequencer.cpp +++ b/Source/AliveLibCommon/audio/Sequencer.cpp @@ -204,7 +204,7 @@ s32 Voice::interpolate() s32 out = 0; if (s == 0) { - if (!isFirstBlock) + if (hasLooped) { out += s32(gauss[0x0FF - i]) * s32(sample->buffer[sample->len - 3]); // oldest out += s32(gauss[0x1FF - i]) * s32(sample->buffer[sample->len - 2]); // older @@ -215,7 +215,7 @@ s32 Voice::interpolate() } else if (s == 1) { - if (!isFirstBlock) + if (hasLooped) { out += s32(gauss[0x0FF - i]) * s32(sample->buffer[sample->len - 2]); // oldest out += s32(gauss[0x1FF - i]) * s32(sample->buffer[sample->len - 1]); // older @@ -225,7 +225,7 @@ s32 Voice::interpolate() } else if (s == 2) { - if (!isFirstBlock) + if (hasLooped) { out += s32(gauss[0x0FF - i]) * s32(sample->buffer[sample->len - 1]); // oldest } @@ -554,7 +554,8 @@ void Sequencer::tickSequence() { for (int i = 0; i < voiceCount; i++) { - if (voices[i]->inUse && voices[i]->channelId == message->channelId) + if (voices[i]->inUse && voices[i]->sequence == seq + && voices[i]->channelId == message->channelId) { voices[i]->pitch = message->bend; } @@ -602,7 +603,6 @@ std::tuple Voice::tick() // vounter holds sample position shifted << 12 vounter.bits -= (NUM_SAMPLES_PER_ADPCM_BLOCK << 12); f_SampleOffset += NUM_SAMPLES_PER_ADPCM_BLOCK; - isFirstBlock = false; } // Are we at the end of the sample? @@ -615,6 +615,7 @@ std::tuple Voice::tick() } // we can loop this sample + hasLooped = true; vounter.bits = 0; f_SampleOffset = 0; } @@ -799,7 +800,7 @@ void Sequencer::releaseVoice(Voice* v) v->voll = 127; v->volr = 127; v->complete = false; - v->isFirstBlock = true; + v->hasLooped = false; v->inUse = false; } diff --git a/Source/AliveLibCommon/audio/Sequencer.hpp b/Source/AliveLibCommon/audio/Sequencer.hpp index 587e3c623..2a707f02e 100644 --- a/Source/AliveLibCommon/audio/Sequencer.hpp +++ b/Source/AliveLibCommon/audio/Sequencer.hpp @@ -224,7 +224,7 @@ class Voice public: s32 id; - bool isFirstBlock = true; + bool hasLooped = false; double f_SampleOffset = 0; Sequence* sequence = NULL; From ffb94d397752efc5e04ba0eeb7efe15cfebfdf3d Mon Sep 17 00:00:00 2001 From: Sean Horton <3359434+sean-horton@users.noreply.github.com> Date: Sun, 2 Apr 2023 08:50:40 -0400 Subject: [PATCH 36/82] remove open-al from project --- .gitmodules | 3 - Source/AliveLibCommon/CMakeLists.txt | 5 - .../AliveLibCommon/audio/mixer/AliveAudio.cpp | 256 +----------------- Source/AliveLibCommon/openal-soft | 1 - 4 files changed, 3 insertions(+), 262 deletions(-) delete mode 160000 Source/AliveLibCommon/openal-soft diff --git a/.gitmodules b/.gitmodules index ea1e14646..c681d75bf 100644 --- a/.gitmodules +++ b/.gitmodules @@ -31,6 +31,3 @@ [submodule "3rdParty/json"] path = 3rdParty/json url = https://github.com/nlohmann/json.git -[submodule "Source/AliveLibCommon/openal-soft"] - path = Source/AliveLibCommon/openal-soft - url = https://github.com/kcat/openal-soft.git diff --git a/Source/AliveLibCommon/CMakeLists.txt b/Source/AliveLibCommon/CMakeLists.txt index b40343dc4..c58f51edf 100644 --- a/Source/AliveLibCommon/CMakeLists.txt +++ b/Source/AliveLibCommon/CMakeLists.txt @@ -118,9 +118,4 @@ target_link_libraries(AliveLibCommon project_warnings ) -add_subdirectory(openal-soft) -target_include_directories(AliveLibCommon PUBLIC openal_soft) -target_link_directories(AliveLibCommon PRIVATE openal_soft) -target_link_libraries(AliveLibCommon OpenAL) - export(TARGETS AliveLibCommon FILE AliveLibCommon.cmake) \ No newline at end of file diff --git a/Source/AliveLibCommon/audio/mixer/AliveAudio.cpp b/Source/AliveLibCommon/audio/mixer/AliveAudio.cpp index e3a336642..220ecc1a3 100644 --- a/Source/AliveLibCommon/audio/mixer/AliveAudio.cpp +++ b/Source/AliveLibCommon/audio/mixer/AliveAudio.cpp @@ -3,41 +3,6 @@ #include #include "AliveAudio.hpp" - -#include "AL/alc.h" -#include "AL/al.h" -#include "AL/alext.h" -#include "AL/efx.h" -#include "AL/efx-presets.h" - -/* Effect object functions */ -static LPALGENEFFECTS alGenEffects; -static LPALDELETEEFFECTS alDeleteEffects; -static LPALISEFFECT alIsEffect; -static LPALEFFECTI alEffecti; -static LPALEFFECTIV alEffectiv; -static LPALEFFECTF alEffectf; -static LPALEFFECTFV alEffectfv; -static LPALGETEFFECTI alGetEffecti; -static LPALGETEFFECTIV alGetEffectiv; -static LPALGETEFFECTF alGetEffectf; -static LPALGETEFFECTFV alGetEffectfv; -static LPALFILTERF alFilterf; -static LPALFILTERI alFilteri; - -/* Auxiliary Effect Slot object functions */ -static LPALGENAUXILIARYEFFECTSLOTS alGenAuxiliaryEffectSlots; -static LPALDELETEAUXILIARYEFFECTSLOTS alDeleteAuxiliaryEffectSlots; -static LPALISAUXILIARYEFFECTSLOT alIsAuxiliaryEffectSlot; -static LPALAUXILIARYEFFECTSLOTI alAuxiliaryEffectSloti; -static LPALAUXILIARYEFFECTSLOTIV alAuxiliaryEffectSlotiv; -static LPALAUXILIARYEFFECTSLOTF alAuxiliaryEffectSlotf; -static LPALAUXILIARYEFFECTSLOTFV alAuxiliaryEffectSlotfv; -static LPALGETAUXILIARYEFFECTSLOTI alGetAuxiliaryEffectSloti; -static LPALGETAUXILIARYEFFECTSLOTIV alGetAuxiliaryEffectSlotiv; -static LPALGETAUXILIARYEFFECTSLOTF alGetAuxiliaryEffectSlotf; -static LPALGETAUXILIARYEFFECTSLOTFV alGetAuxiliaryEffectSlotfv; - namespace psx { Soundbank* AliveAudio::m_CurrentSoundbank = nullptr; @@ -46,94 +11,10 @@ std::vector AliveAudio::m_Voices; long long AliveAudio::currentSampleIndex = 20; biquad* AliveAudio::AliveAudioEQBiQuad = nullptr; -ALCdevice* device; -ALCcontext* ctx; -ALuint effectG; -ALuint slot = 0; unsigned int alSource[64]; -ALenum error; unsigned int alSampleSet[64]; Sample* sampleId[64]; -ALuint LoadEffect(const EFXEAXREVERBPROPERTIES* reverb) -{ - ALuint effect = 0; - ALenum err; - - /* Create the effect object and check if we can do EAX reverb. */ - alGenEffects(1, &effect); - //alFilteri(effect, AL_FILTER_TYPE, AL_FILTER_LOWPASS); - - if (alGetEnumValue("AL_EFFECT_EAXREVERB") != 0) - { - printf("Using EAX Reverb\n"); - - /* EAX Reverb is available. Set the EAX effect type then load the - * reverb properties. */ - alEffecti(effect, AL_EFFECT_TYPE, AL_EFFECT_EAXREVERB); - - alEffectf(effect, AL_EAXREVERB_DENSITY, reverb->flDensity); - alEffectf(effect, AL_EAXREVERB_DIFFUSION, reverb->flDiffusion); - alEffectf(effect, AL_EAXREVERB_GAIN, reverb->flGain); - alEffectf(effect, AL_EAXREVERB_GAINHF, reverb->flGainHF); - alEffectf(effect, AL_EAXREVERB_GAINLF, reverb->flGainLF); - alEffectf(effect, AL_EAXREVERB_DECAY_TIME, reverb->flDecayTime); - alEffectf(effect, AL_EAXREVERB_DECAY_HFRATIO, reverb->flDecayHFRatio); - alEffectf(effect, AL_EAXREVERB_DECAY_LFRATIO, reverb->flDecayLFRatio); - alEffectf(effect, AL_EAXREVERB_REFLECTIONS_GAIN, reverb->flReflectionsGain); - alEffectf(effect, AL_EAXREVERB_REFLECTIONS_DELAY, reverb->flReflectionsDelay); - alEffectfv(effect, AL_EAXREVERB_REFLECTIONS_PAN, reverb->flReflectionsPan); - alEffectf(effect, AL_EAXREVERB_LATE_REVERB_GAIN, reverb->flLateReverbGain); - alEffectf(effect, AL_EAXREVERB_LATE_REVERB_DELAY, reverb->flLateReverbDelay); - alEffectfv(effect, AL_EAXREVERB_LATE_REVERB_PAN, reverb->flLateReverbPan); - alEffectf(effect, AL_EAXREVERB_ECHO_TIME, reverb->flEchoTime); - alEffectf(effect, AL_EAXREVERB_ECHO_DEPTH, reverb->flEchoDepth); - alEffectf(effect, AL_EAXREVERB_MODULATION_TIME, reverb->flModulationTime); - alEffectf(effect, AL_EAXREVERB_MODULATION_DEPTH, reverb->flModulationDepth); - alEffectf(effect, AL_EAXREVERB_AIR_ABSORPTION_GAINHF, reverb->flAirAbsorptionGainHF); - alEffectf(effect, AL_EAXREVERB_HFREFERENCE, reverb->flHFReference); - alEffectf(effect, AL_EAXREVERB_LFREFERENCE, reverb->flLFReference); - alEffectf(effect, AL_EAXREVERB_ROOM_ROLLOFF_FACTOR, reverb->flRoomRolloffFactor); - alEffecti(effect, AL_EAXREVERB_DECAY_HFLIMIT, reverb->iDecayHFLimit); - } - else - { - printf("Using Standard Reverb\n"); - - /* No EAX Reverb. Set the standard reverb effect type then load the - * available reverb properties. */ - alEffecti(effect, AL_EFFECT_TYPE, AL_EFFECT_REVERB); - - alEffectf(effect, AL_REVERB_DENSITY, reverb->flDensity); - alEffectf(effect, AL_REVERB_DIFFUSION, reverb->flDiffusion); - alEffectf(effect, AL_REVERB_GAIN, reverb->flGain); - alEffectf(effect, AL_REVERB_GAINHF, reverb->flGainHF); - alEffectf(effect, AL_REVERB_DECAY_TIME, reverb->flDecayTime); - alEffectf(effect, AL_REVERB_DECAY_HFRATIO, reverb->flDecayHFRatio); - alEffectf(effect, AL_REVERB_REFLECTIONS_GAIN, reverb->flReflectionsGain); - alEffectf(effect, AL_REVERB_REFLECTIONS_DELAY, reverb->flReflectionsDelay); - alEffectf(effect, AL_REVERB_LATE_REVERB_GAIN, reverb->flLateReverbGain); - alEffectf(effect, AL_REVERB_LATE_REVERB_DELAY, reverb->flLateReverbDelay); - alEffectf(effect, AL_REVERB_AIR_ABSORPTION_GAINHF, reverb->flAirAbsorptionGainHF); - alEffectf(effect, AL_REVERB_ROOM_ROLLOFF_FACTOR, reverb->flRoomRolloffFactor); - alEffecti(effect, AL_REVERB_DECAY_HFLIMIT, reverb->iDecayHFLimit); - } - - - - /* Check if an error occured, and clean up if so. */ - err = alGetError(); - if (err != AL_NO_ERROR) - { - fprintf(stderr, "OpenAL error: %s\n", alGetString(err)); - if (alIsEffect(effect)) - alDeleteEffects(1, &effect); - return 0; - } - - return effect; -} - void AliveInitAudio() { SDL_Init(SDL_INIT_AUDIO); @@ -154,91 +35,11 @@ void AliveInitAudio() } SDL_PauseAudio(0); - - - device = alcOpenDevice(NULL); - ctx = alcCreateContext(device, NULL); - alcMakeContextCurrent(ctx); - - /* Define a macro to help load the function pointers. - https://github.com/kcat/openal-soft/blob/master/examples/alreverb.c */ - -#if __STDC_VERSION__ >= 199901L - #define FUNCTION_CAST(T, ptr) (union \ - { \ - void* p; \ - T f; \ - }){ptr} \ - .f -#elif defined(__cplusplus) - #define FUNCTION_CAST(T, ptr) reinterpret_cast(ptr) -#else - #define FUNCTION_CAST(T, ptr) (T)(ptr) -#endif - -#define LOAD_PROC(T, x) ((x) = FUNCTION_CAST(T, alGetProcAddress(#x))) - LOAD_PROC(LPALGENEFFECTS, alGenEffects); - LOAD_PROC(LPALDELETEEFFECTS, alDeleteEffects); - LOAD_PROC(LPALISEFFECT, alIsEffect); - LOAD_PROC(LPALEFFECTI, alEffecti); - LOAD_PROC(LPALEFFECTIV, alEffectiv); - LOAD_PROC(LPALEFFECTF, alEffectf); - LOAD_PROC(LPALEFFECTFV, alEffectfv); - LOAD_PROC(LPALGETEFFECTI, alGetEffecti); - LOAD_PROC(LPALGETEFFECTIV, alGetEffectiv); - LOAD_PROC(LPALGETEFFECTF, alGetEffectf); - LOAD_PROC(LPALGETEFFECTFV, alGetEffectfv); - - LOAD_PROC(LPALFILTERF, alFilterf); - LOAD_PROC(LPALFILTERI, alFilteri); - - LOAD_PROC(LPALGENAUXILIARYEFFECTSLOTS, alGenAuxiliaryEffectSlots); - LOAD_PROC(LPALDELETEAUXILIARYEFFECTSLOTS, alDeleteAuxiliaryEffectSlots); - LOAD_PROC(LPALISAUXILIARYEFFECTSLOT, alIsAuxiliaryEffectSlot); - LOAD_PROC(LPALAUXILIARYEFFECTSLOTI, alAuxiliaryEffectSloti); - LOAD_PROC(LPALAUXILIARYEFFECTSLOTIV, alAuxiliaryEffectSlotiv); - LOAD_PROC(LPALAUXILIARYEFFECTSLOTF, alAuxiliaryEffectSlotf); - LOAD_PROC(LPALAUXILIARYEFFECTSLOTFV, alAuxiliaryEffectSlotfv); - LOAD_PROC(LPALGETAUXILIARYEFFECTSLOTI, alGetAuxiliaryEffectSloti); - LOAD_PROC(LPALGETAUXILIARYEFFECTSLOTIV, alGetAuxiliaryEffectSlotiv); - LOAD_PROC(LPALGETAUXILIARYEFFECTSLOTF, alGetAuxiliaryEffectSlotf); - LOAD_PROC(LPALGETAUXILIARYEFFECTSLOTFV, alGetAuxiliaryEffectSlotfv); -#undef LOAD_PROC - - //EFXEAXREVERBPROPERTIES reverb = EFX_REVERB_PRESET_FACTORY_MEDIUMROOM; - EFXEAXREVERBPROPERTIES reverb = EFX_REVERB_PRESET_AUDITORIUM; - - effectG = LoadEffect(&reverb); - alGenAuxiliaryEffectSlots(1, &slot); - alAuxiliaryEffectSloti(slot, AL_EFFECTSLOT_EFFECT, (ALint) effectG); - - - alGenBuffers(64, alSampleSet); - if ((error = alGetError()) != AL_NO_ERROR) - { - return; - } - - alGenSources(64, alSource); - if ((error = alGetError()) != AL_NO_ERROR) - { - return; - } } int getSource() { - ALenum state; - int i; - for (i = 0; i < 64; i++) - { - alGetSourcei(alSource[i], AL_SOURCE_STATE, &state); - if (state != AL_PLAYING) - { - alSourceStop(alSource[i]); - return i; - } - } + return -1; } @@ -271,7 +72,6 @@ void AliveAudio::PlayOneShot(s32 playId, int programId, int note, s32 volLeft, s voice->f_Pitch = pitch == 0 ? pitch_min / 127 : pitch / 127; //m_Voices.push_back(voice); - alGetError(); // clear error code Sample* s = voice->m_Tone->m_Sample; int i = 0; @@ -290,15 +90,7 @@ void AliveAudio::PlayOneShot(s32 playId, int programId, int note, s32 volLeft, s return; } - if (!sampleId[pos]) - { - alBufferData(alSampleSet[pos], AL_FORMAT_MONO16, s->m_SampleBuffer, s->i_SampleSize * 2, 22050); - if ((error = alGetError()) != AL_NO_ERROR) - { - return; - } - sampleId[pos] = s; - } + //float center = float(tone->c_Center); //float pitch1 = float(tone->Pitch); @@ -317,24 +109,7 @@ void AliveAudio::PlayOneShot(s32 playId, int programId, int note, s32 volLeft, s float sampleRootFreq = float(pow(2.0, float(tone->c_Center + (tone->c_Shift / 127.0)) / 12.0)); freq = noteFreq / sampleRootFreq * 2.0f; - ALfloat pan; - pan = voice->f_Pan; - alSource3f(alSource[a], AL_POSITION, pan, 0, -sqrtf(1.0f - pan * pan)); - alSourcef(alSource[a], AL_PITCH, (ALfloat) freq); - alSourcef(alSource[a], AL_GAIN, (ALfloat) voice->f_Velocity); - - alSourcei(alSource[a], AL_BUFFER, alSampleSet[pos]); - if (tone->mode != 0) - alSource3i(alSource[a], AL_AUXILIARY_SEND_FILTER, (ALint) slot, 0, AL_FILTER_NULL); - else - alSource3i(alSource[a], AL_AUXILIARY_SEND_FILTER, AL_EFFECTSLOT_NULL, 0, NULL); - if ((error = alGetError()) != AL_NO_ERROR) - { - return; - } - - alSourcePlay(alSource[a]); //ALfloat adsr = ALfloat(0.1); //while (adsr < 2) @@ -369,6 +144,7 @@ void AliveAudio::PlayOneShot(s32 playId, int programId, int note, s32 volume, fl void AliveAudio::NoteOn(int programId, int note, char velocity, float pitch , int trackID , float trackDelay, float masterVolMulti) { + masterVolMulti; for (auto program : m_CurrentSoundbank->m_Programs) { if (program->prog_id != programId) @@ -389,7 +165,6 @@ void AliveAudio::NoteOn(int programId, int note, char velocity, float pitch , in voice->f_TrackDelay = trackDelay; //m_Voices.push_back(voice); - alGetError(); // clear error code Sample* s = voice->m_Tone->m_Sample; int i = 0; @@ -408,15 +183,6 @@ void AliveAudio::NoteOn(int programId, int note, char velocity, float pitch , in return; } - if (!sampleId[pos]) - { - alBufferData(alSampleSet[pos], AL_FORMAT_MONO16, s->m_SampleBuffer, s->i_SampleSize * 2, 22050); - if ((error = alGetError()) != AL_NO_ERROR) - { - return; - } - sampleId[pos] = s; - } // Centre: This one is the most important. This is the root note, or, the note that your sample is in. // If you got your sample from a Sample CD, often it will have what note it is somewhere. @@ -455,23 +221,7 @@ void AliveAudio::NoteOn(int programId, int note, char velocity, float pitch , in //freq = freq + 0.5; //freq = float(note) / float(float(tone->c_Center)); - ALfloat pan; - pan = voice->f_Pan == 0 ? voice->m_Tone->f_Pan : voice->f_Pan; - alSource3f(alSource[a], AL_POSITION, pan, 0, -sqrtf(1.0f - pan * pan)); - alSourcef(alSource[a], AL_PITCH, (ALfloat) freq); - alSourcef(alSource[a], AL_GAIN, (ALfloat) voice->f_Velocity * masterVolMulti); - alSourcei(alSource[a], AL_BUFFER, alSampleSet[pos]); - if (tone->mode != 0) - alSource3i(alSource[a], AL_AUXILIARY_SEND_FILTER, (ALint) slot, 0, AL_FILTER_NULL); - else - alSource3i(alSource[a], AL_AUXILIARY_SEND_FILTER, AL_EFFECTSLOT_NULL, 0, NULL); - - if ((error = alGetError()) != AL_NO_ERROR) - { - return; - } - alSourcePlay(alSource[a]); AliveAudio::currentSampleIndex++; } } diff --git a/Source/AliveLibCommon/openal-soft b/Source/AliveLibCommon/openal-soft deleted file mode 160000 index 7e00d4a00..000000000 --- a/Source/AliveLibCommon/openal-soft +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 7e00d4a00243e417ff59d3be001e1180ff55b72b From 639af8d361c762d33ee7e1d76a4766f1e96b44d1 Mon Sep 17 00:00:00 2001 From: Sean Horton <3359434+sean-horton@users.noreply.github.com> Date: Sun, 2 Apr 2023 19:54:34 -0400 Subject: [PATCH 37/82] big refactor for mutex locks on spu --- Source/AliveLibAE/Sound/Midi.cpp | 2 +- Source/AliveLibAE/Sound/Midi.hpp | 2 +- Source/AliveLibAO/Midi.cpp | 2 +- Source/AliveLibCommon/audio/MidiPlayer.cpp | 129 +- Source/AliveLibCommon/audio/MidiPlayer.hpp | 7 +- .../AliveLibCommon/audio/SequencePlayer.cpp | 1 + Source/AliveLibCommon/audio/Sequencer.cpp | 1190 +++++++++-------- Source/AliveLibCommon/audio/Sequencer.hpp | 95 +- 8 files changed, 736 insertions(+), 692 deletions(-) diff --git a/Source/AliveLibAE/Sound/Midi.cpp b/Source/AliveLibAE/Sound/Midi.cpp index 902d49b14..c04beff4b 100644 --- a/Source/AliveLibAE/Sound/Midi.cpp +++ b/Source/AliveLibAE/Sound/Midi.cpp @@ -19,7 +19,7 @@ #include "PsxSpuApi.hpp" #include "AmbientSound.hpp" -#if !ALTERNATE_AUDIO +#if !AUDIO_SPU_EMULATION EXPORT void CC SFX_SetPitch_4CA510(const SfxDefinition* pSfx, s32 channelsBits, s16 pitch); const s32 kSeqTableSizeAE = 144; diff --git a/Source/AliveLibAE/Sound/Midi.hpp b/Source/AliveLibAE/Sound/Midi.hpp index 783b98511..d6b7a7248 100644 --- a/Source/AliveLibAE/Sound/Midi.hpp +++ b/Source/AliveLibAE/Sound/Midi.hpp @@ -39,7 +39,7 @@ class IMidiVars virtual s16 LoadResourceFile(const char_type* pFileName, Camera* pCamera) = 0; }; -#if !ALTERNATE_AUDIO +#if !AUDIO_SPU_EMULATION EXPORT IMidiVars* GetMidiVars(); EXPORT void SetMidiApiVars(IMidiVars* pVars); diff --git a/Source/AliveLibAO/Midi.cpp b/Source/AliveLibAO/Midi.cpp index ab001e45f..a9f1a8907 100644 --- a/Source/AliveLibAO/Midi.cpp +++ b/Source/AliveLibAO/Midi.cpp @@ -23,7 +23,7 @@ #include "../AliveLibAE/Sound/Sound.hpp" #include "../AliveLibAE/PathData.hpp" -#if !ALTERNATE_AUDIO +#if !AUDIO_SPU_EMULATION namespace AO { const s32 kSeqTableSizeAO = 164; diff --git a/Source/AliveLibCommon/audio/MidiPlayer.cpp b/Source/AliveLibCommon/audio/MidiPlayer.cpp index 9749ca965..ec459c255 100644 --- a/Source/AliveLibCommon/audio/MidiPlayer.cpp +++ b/Source/AliveLibCommon/audio/MidiPlayer.cpp @@ -57,16 +57,12 @@ MidiPlayer::MidiPlayer(ResourceProvider* provider, SoundSampleParser* samplePars void MidiPlayer::SND_Init() { - if (sequencer) - { - delete sequencer; - } - sequencer = new sean::Sequencer(); + SPU::Init(); } void MidiPlayer::SND_Shutdown() { - delete sequencer; + SPU::DeInit(); } @@ -81,7 +77,7 @@ void MidiPlayer::SND_Shutdown() // // Doesn't seem to work for PC samples, maybe check PSX samples // in the future. -bool isLoop(sean::Sample* sample) +bool isLoop(SPU::Sample* sample) { s16* b = sample->buffer; u32 l = sample->len; @@ -108,7 +104,8 @@ void MidiPlayer::SND_Load_VABS(SoundBlockInfo* pSoundBlockInfo, s32 reverb) { reverb; // TODO - what do we do with this? override the patch/sample? - sequencer->reset(); + SPU::Reset(); + while (1) { if (!pSoundBlockInfo->header_name) @@ -136,6 +133,9 @@ void MidiPlayer::SND_Load_VABS(SoundBlockInfo* pSoundBlockInfo, s32 reverb) for (s32 i = 0; i < vabHeader->field_12_num_progs; i++) { + SPU::Patch* patch = new SPU::Patch((u8) vagAttr->field_14_prog); + SPU::PatchAdd(patch); + /////////// // PATCH (Instruments) for (s32 x = 0; x < 16; x++) @@ -143,15 +143,14 @@ void MidiPlayer::SND_Load_VABS(SoundBlockInfo* pSoundBlockInfo, s32 reverb) /////////// // SAMPLE - if (vagAttr->field_2_vol > 0) + 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; - - sean::ADSR adsr = sean::parseADSR(ADSR1, ADSR2); - sean::Patch* patch = sequencer->createPatch(vagAttr->field_14_prog); Sample* s = samples.at(vagAttr->field_16_vag - 1); - sean::Sample* sample = new sean::Sample(s->m_SampleBuffer, s->i_SampleSize, 44100); // TODO s->sampleRate? + + SPU::ADSR adsr = SPU::parseADSR(ADSR1, ADSR2); + SPU::Sample* sample = new SPU::Sample(s->m_SampleBuffer, s->i_SampleSize, 44100); // TODO s->sampleRate? patch->samples[x] = sample; sample->adsr = adsr; @@ -205,15 +204,9 @@ void MidiPlayer::SND_Load_Seqs(OpenSeqHandle* pSeqTable, const char_type* bsqFil } // SEQUENCE STREAM - sean::Sequence* sequence = sequencer->createSequence(); + SPU::Sequence* sequence = new SPU::Sequence(); parseMidiStream(sequence, vec, i); - mSequences.push_back(vec); - } - else - { - seq[i].ppSeq_Data = nullptr; - std::vector test; - mSequences.push_back(test); + SPU::SeqAdd(sequence); } } } @@ -221,15 +214,13 @@ void MidiPlayer::SND_Load_Seqs(OpenSeqHandle* pSeqTable, const char_type* bsqFil void MidiPlayer::SND_StopAll() { // called when pause menu is open - std::cout << "stop all" << std::endl; - sequencer->stopAll(); + SPU::StopAll(); } void MidiPlayer::SND_Reset() { // called when quiting to main menu or going into a new level - std::cout << "reset" << std::endl; - sequencer->reset(); + SPU::Reset(); } void MidiPlayer::SND_Restart() @@ -238,16 +229,14 @@ void MidiPlayer::SND_Restart() std::cout << "restart" << std::endl; } -void MidiPlayer::SND_Stop_Channels_Mask(u32 bitMask) +void MidiPlayer::SND_Stop_Channels_Mask(u32 mask) { - bitMask; - std::cout << "bitmask " << bitMask << "\n"; - sequencer->stopNote(bitMask); + SPU::OneShotStop(mask); } void MidiPlayer::SND_SEQ_Stop(u16 idx) { - sequencer->stopSeq(idx); + SPU::SeqStop(idx); } s8 MidiPlayer::SND_Seq_Table_Valid() @@ -257,25 +246,10 @@ s8 MidiPlayer::SND_Seq_Table_Valid() s16 MidiPlayer::SND_SEQ_PlaySeq(u16 idx, s32 repeatCount, s16 bDontStop) { - bDontStop; // TODO + bDontStop; // TODO - does this matter? repeatCount; idx; - // TODO - still broken for chanting - revist below commented code - - sean::Sequence* seq = sequencer->getSequence(idx); - if (!seq) - { - return 0; - } - if (seq->play) - { - return 1; - } - sequencer->getSequence(idx)->voll = 127; - sequencer->getSequence(idx)->volr = 127; - sequencer->getSequence(idx)->repeatLimit = repeatCount; - sequencer->playSeq(idx); - return 1; + return SPU::SeqPlay(idx, repeatCount) ? 1 : 0; } void MidiPlayer::sanitizeVolume(s32* src, s32 low, s32 high) @@ -307,37 +281,17 @@ void MidiPlayer::SND_SEQ_SetVol(s32 idx, s32 volLeft, s32 volRight) { sanitizeVolume(&volLeft, 0, 127); sanitizeVolume(&volRight, 0, 127); - - sean::Sequence* seq = sequencer->getSequence((s32) idx); - if (seq) - { - seq->voll = (s16) volLeft; - seq->volr = (s16) volRight; - } + SPU::SeqSetVolume(idx, (s16) volLeft, (s16) volRight); } s16 MidiPlayer::SND_SEQ_Play(u16 idx, s32 repeatCount, s16 volLeft, s16 volRight) { - sean::Sequence* seq = sequencer->getSequence((s32) idx); - if (seq) - { - seq->repeatLimit = repeatCount; - seq->voll = volLeft; - seq->volr = volRight; - } - sequencer->playSeq(idx); - return 1; + return SPU::SeqPlay(idx, repeatCount, volLeft, volRight) ? 1 : 0; } s16 MidiPlayer::SND_SsIsEos_DeInlined(u16 idx) { - s16 res = 0; - sean::Sequence* seq = sequencer->getSequence((s32) idx); - if (seq) - { - res = seq->repeats < seq->repeatLimit ? 1 : 0; - } - return res; + return SPU::SeqIsDone(idx) ? 0 : 1; } s32 MidiPlayer::SFX_SfxDefinition_Play(SfxDefinition* sfxDef, s32 volLeft, s32 volRight, s32 pitch_min, s32 pitch_max) @@ -348,7 +302,7 @@ s32 MidiPlayer::SFX_SfxDefinition_Play(SfxDefinition* sfxDef, s32 volLeft, s32 v sanitizeVolume(&volLeft, 10, 127); sanitizeVolume(&volRight, 10, 127); - return sequencer->playNote(sfxDef->program, sfxDef->note, (s16) volLeft, (s16) volRight, (u8) std::max(pitch_min, pitch_max), pitch_min, pitch_max); + return SPU::OneShotPlay(sfxDef->program, sfxDef->note, (s16) volLeft, (s16) volRight, (u8) 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) @@ -361,18 +315,20 @@ s32 MidiPlayer::SFX_SfxDefinition_Play(SfxDefinition* sfxDef, s32 volume, s32 pi sanitizePitch(&pitch_max, sfxDef->pitch_max); sanitizeVolume(&volume, 1, 127); - return sequencer->playNote(sfxDef->program, sfxDef->note, (s16) volume, (s16) volume, (u8) std::max(pitch_min, pitch_max), pitch_min, pitch_max); + return SPU::OneShotPlay(sfxDef->program, sfxDef->note, (s16) volume, (s16) volume, (u8) 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 sequencer->playNote(program, (u8) note, vol, vol, 0, min, max); + return SPU::OneShotPlay(program, (u8) note, vol, vol, 0, min, max); } void MidiPlayer::SsUtAllKeyOff(s32 mode) { - mode; // TODO + // TODO - don't know when this is called + mode; + // SPU::StopAll(); } @@ -401,7 +357,7 @@ static Uint32 _MidiReadVarLen(Stream& stream) return ret; } -void parseMidiStream(sean::Sequence* seq, std::vector seqData, s32 trackId) +void parseMidiStream(SPU::Sequence* seq, std::vector seqData, s32 trackId) { Stream stream(std::move(seqData)); seq->id = trackId; @@ -441,6 +397,7 @@ void parseMidiStream(sean::Sequence* seq, std::vector seqData, s32 trackI unsigned int deltaTime = 0; const size_t midiDataStart = stream.Pos(); + midiDataStart; // Context state SeqInfo gSeqInfo = {}; @@ -485,8 +442,8 @@ void parseMidiStream(sean::Sequence* seq, std::vector seqData, s32 trackI { case 0x2f: { - sean::MIDIMessage* msg = seq->createMIDIMessage(); - msg->type = sean::END_TRACK; + SPU::MIDIMessage* msg = seq->createMIDIMessage(); + msg->type = SPU::END_TRACK; msg->tick = ticksPerBeat; msg->tick = deltaTime; return; @@ -533,18 +490,18 @@ void parseMidiStream(sean::Sequence* seq, std::vector seqData, s32 trackI Uint8 velocity = 0; stream.ReadUInt8(velocity); - sean::MIDIMessage* msg = seq->createMIDIMessage(); + 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 = sean::NOTE_OFF; + msg->type = SPU::NOTE_OFF; } else { - msg->type = sean::NOTE_ON; + msg->type = SPU::NOTE_ON; } } break; @@ -555,8 +512,8 @@ void parseMidiStream(sean::Sequence* seq, std::vector seqData, s32 trackI Uint8 velocity = 0; stream.ReadUInt8(velocity); - sean::MIDIMessage* msg = seq->createMIDIMessage(); - msg->type = sean::NOTE_OFF; + SPU::MIDIMessage* msg = seq->createMIDIMessage(); + msg->type = SPU::NOTE_OFF; msg->tick = deltaTime; msg->channelId = channel; msg->note = note; @@ -568,8 +525,8 @@ void parseMidiStream(sean::Sequence* seq, std::vector seqData, s32 trackI Uint8 prog = 0; stream.ReadUInt8(prog); - sean::MIDIMessage* msg = seq->createMIDIMessage(); - msg->type = sean::PATCH_CHANGE; + SPU::MIDIMessage* msg = seq->createMIDIMessage(); + msg->type = SPU::PATCH_CHANGE; msg->tick = deltaTime; msg->channelId = channel; msg->patchId = prog; @@ -612,8 +569,8 @@ void parseMidiStream(sean::Sequence* seq, std::vector seqData, s32 trackI 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? - sean::MIDIMessage* msg = seq->createMIDIMessage(); - msg->type = sean::PITCH_BEND; + SPU::MIDIMessage* msg = seq->createMIDIMessage(); + msg->type = SPU::PITCH_BEND; msg->bend = (s16) multi; msg->channelId = channel; msg->tick = deltaTime; diff --git a/Source/AliveLibCommon/audio/MidiPlayer.hpp b/Source/AliveLibCommon/audio/MidiPlayer.hpp index c44f93560..174f83986 100644 --- a/Source/AliveLibCommon/audio/MidiPlayer.hpp +++ b/Source/AliveLibCommon/audio/MidiPlayer.hpp @@ -6,7 +6,7 @@ namespace psx { - void parseMidiStream(sean::Sequence* seq, std::vector seqData, s32 trackId); + void parseMidiStream(SPU::Sequence* seq, std::vector seqData, s32 trackId); struct VagAtr final { @@ -208,11 +208,6 @@ namespace psx { ResourceProvider* mResourceProvider; SoundSampleParser* mSoundSampleParser; - sean::Sequencer* sequencer = NULL; - Soundbank* mSoundbank = NULL; - std::vector> mSequences; - std::vector mSequencePlayers; - void sanitizePitch(s32* src, s16 defaultPitch); void sanitizeVolume(s32* src, s32 low, s32 high); }; diff --git a/Source/AliveLibCommon/audio/SequencePlayer.cpp b/Source/AliveLibCommon/audio/SequencePlayer.cpp index 4a0aad16e..fc7344ae3 100644 --- a/Source/AliveLibCommon/audio/SequencePlayer.cpp +++ b/Source/AliveLibCommon/audio/SequencePlayer.cpp @@ -230,6 +230,7 @@ int SequencePlayer::LoadSequenceStream(Stream& stream) unsigned int deltaTime = 0; const size_t midiDataStart = stream.Pos(); + midiDataStart; // Context state SeqInfo gSeqInfo = {}; diff --git a/Source/AliveLibCommon/audio/Sequencer.cpp b/Source/AliveLibCommon/audio/Sequencer.cpp index 59c5a72da..a5046d519 100644 --- a/Source/AliveLibCommon/audio/Sequencer.cpp +++ b/Source/AliveLibCommon/audio/Sequencer.cpp @@ -1,27 +1,214 @@ #pragma once #include "Sequencer.hpp" -#include #include "SDL.h" -namespace sean { +namespace SPU { -///////////////////////////////// -/// DuckStation +////////////////////////// +// SPU state +std::mutex mutex; + +const int VOICE_SIZE_LIMIT = 24; +std::array voices; + +const int SEQUENCE_SIZE_LIMIT = 256; +std::vector sequences; + +const int PATCH_SIZE_LIMIT = 128; +std::array patches; + + +////////////////////////// +// SPU Internal mangement methods +void SPUInit(); +void SPUStopAll(); +void SPUReset(); -static void ProcessReverb(s16 left_in, s16 right_in, s32* left_out, s32* right_out); +Voice* SPUObtainVoice(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, u8 pitch, s32 pitchMin, s32 pitchMax); +void SPUOneShotStop(s32 mask); + +void SPUTick(void* udata, Uint8* stream, int len); +void SPUTickSequences(); + +void ProcessReverb(s16 left_in, s16 right_in, s32* left_out, s32* right_out); static s16 ReverbRead(u32 address, s32 offset = 0); -u32 mask(u32 num) + +////////////////////////// +// Public SPU methods +// Basically just a wrapper for mutex lock +void SPU::Init() +{ + mutex.lock(); + SPUInit(); + mutex.unlock(); +} + +void SPU::DeInit() +{ + mutex.lock(); + // TODO - does it matter? + mutex.unlock(); +} + +void SPU::Reset() +{ + mutex.lock(); + SPUReset(); + mutex.unlock(); +} + +void SPU::StopAll() { - u32 res = 0; - while (num-- > 0) + mutex.lock(); + SPUStopAll(); + mutex.unlock(); +} + +void PatchAdd(Patch* patch) +{ + if (patch->_id >= PATCH_SIZE_LIMIT) { - res = (res << 1) | 1; + 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) +{ + mutex.lock(); + 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, u8 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; +} + +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) { @@ -65,246 +252,279 @@ static constexpr s32 ApplyVolume(s32 sample, s16 volume) return (sample * s32(volume)) >> 15; } -ADSR parseADSR(u16 adsr1, u16 adsr2) + +///////////////////////// +// SPU management +u64 timeSinceEpochMillisec() { - 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); + using namespace std::chrono; + return duration_cast(system_clock::now().time_since_epoch()).count(); +} - 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; +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(); + voices[i]->id = id; + id = id << 1; + } + + for (int i = 0; i < PATCH_SIZE_LIMIT; i++) + { + patches[i] = nullptr; + } } -struct ADSRTableEntry +void SPUReset() { - s32 ticks; - s32 step; -}; -enum : u32 + 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() { - NUM_ADSR_TABLE_ENTRIES = 128, - NUM_ADSR_DIRECTIONS = 2 // increasing, decreasing -}; -using ADSRTableEntries = std::array, NUM_ADSR_DIRECTIONS>; + for (Sequence* seq : sequences) + { + seq->play = false; + } -static constexpr ADSRTableEntries ComputeADSRTableEntries() + for (s16 i = 0; i < VOICE_SIZE_LIMIT; i++) + { + SPUReleaseVoice(voices[i]); + } +} + +Voice* SPUObtainVoice(u8 note, u8 patchId) { - ADSRTableEntries entries = {}; - for (u32 decreasing = 0; decreasing < 2; decreasing++) + // 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; + available = NULL; + for (int i = 0; i < VOICE_SIZE_LIMIT; i++) { - for (u32 rate = 0; rate < NUM_ADSR_TABLE_ENTRIES; rate++) + Voice* v = voices[i]; + if (v->note == note && v->patchId == patchId) { - 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 + if (!available || available->f_SampleOffset > v->f_SampleOffset) { - 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)); + available = v; } } + + if (v->complete) + { + SPUReleaseVoice(v); + } + + if (!v->inUse) + { + v->inUse = true; + return v; + } } - return entries; + if (!available) + { + return NULL; + } + + // this is voice in use that we can reuse + SPUReleaseVoice(available); + available->inUse = true; + return available; } -static constexpr ADSRTableEntries s_adsr_table = ComputeADSRTableEntries(); +void SPUReleaseVoice(Voice* v) +{ + v->channelId = 0xFF; + v->offTime = 0; + v->pitch = 0; + v->adsrPhase = NONE; + v->adsrCounter = 0; + v->adsrCurrentLevel = 0; + v->adsrTargetLevel = MAX_VOLUME; + v->sequence = NULL; + v->f_SampleOffset = 0; + v->vounter.bits = 0; + v->complete = false; + v->velocity = 127; + v->voll = 127; + v->volr = 127; + v->complete = false; + v->hasLooped = false; + v->inUse = false; +} -s32 Voice::interpolate() +void SPUPatchAdd(Patch* patch) { - 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 // - }}; + if (patches[patch->_id]) + { + //delete patches[patch->_id]; + } + patches[patch->_id] = patch; +} - // the interpolation index is based on the source files sample rate - const u8 i = (u8)vounter.interpolation_index(); - const u32 s = ((u32)f_SampleOffset) + ZeroExtend32(vounter.sample_index()); +void SPUSeqAdd(Sequence* seq) +{ + sequences.push_back(seq); +} - // interpolate on the 4 most recent samples from current position - // The below `if` statements are in case we loop - // we gauss the end of the sample with the beginning. - // Probably a better way to do it, but w/e for now... - s32 out = 0; - if (s == 0) +bool SPUSeqPlay(s32 seqId, s32 repeats) +{ + bool res = false; + for (Sequence* seq : sequences) { - if (hasLooped) + if (seq->id == seqId) { - out += s32(gauss[0x0FF - i]) * s32(sample->buffer[sample->len - 3]); // oldest - out += s32(gauss[0x1FF - i]) * s32(sample->buffer[sample->len - 2]); // older - out += s32(gauss[0x100 + i]) * s32(sample->buffer[sample->len - 1]); // old + seq->repeatLimit = repeats; + seq->play = true; + res |= true; } - out += s32(gauss[0x000 + i]) * s32(sample->buffer[s - 0]); // new - } - else if (s == 1) + return res; +} + +void SPUSeqStop(s32 seqId) +{ + for (Sequence* seq : sequences) { - if (hasLooped) + if (seq->id == seqId) { - out += s32(gauss[0x0FF - i]) * s32(sample->buffer[sample->len - 2]); // oldest - out += s32(gauss[0x1FF - i]) * s32(sample->buffer[sample->len - 1]); // older + seq->Reset(); + for (Voice* v : voices) + { + if (v && v->sequence == seq) + { + v->offTime = timeSinceEpochMillisec(); + } + } } - out += s32(gauss[0x100 + i]) * s32(sample->buffer[s - 1]); // old - out += s32(gauss[0x000 + i]) * s32(sample->buffer[s - 0]); // new } - else if (s == 2) +} + +void SPUSeqSetVolume(s32 seqId, s16 voll, s16 volr) +{ + for (Sequence* seq : sequences) { - if (hasLooped) + if (seq->id == seqId) { - out += s32(gauss[0x0FF - i]) * s32(sample->buffer[sample->len - 1]); // oldest + seq->voll = voll; + seq->volr = volr; } - out += s32(gauss[0x1FF - i]) * s32(sample->buffer[s - 2]); // older - out += s32(gauss[0x100 + i]) * s32(sample->buffer[s - 1]); // old - out += s32(gauss[0x000 + i]) * s32(sample->buffer[s - 0]); // new } - else +} + +bool SPUSeqIsDone(s32 seqId) +{ + bool res = false; + for (Sequence* seq : sequences) { - out += s32(gauss[0x0FF - i]) * s32(sample->buffer[(int) (s ) - 3]); // oldest - out += s32(gauss[0x1FF - i]) * s32(sample->buffer[(int) (s ) - 2]); // older - out += s32(gauss[0x100 + i]) * s32(sample->buffer[(int) (s ) - 1]); // old - out += s32(gauss[0x000 + i]) * s32(sample->buffer[(int) (s ) - 0]); // new + if (seq->id == seqId) + { + res |= seq->repeats >= seq->repeatLimit; + } } - - - return out >> 15; + return res; } -/// DuckStation END -///////////////////////////////// - -Sequencer::Sequencer() +s32 SPUOneShotPlay(s32 patchId, u8 note, s16 voll, s16 volr, u8 pitch, s32 pitchMin, s32 pitchMax) { - gseq = this; - - // 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; + // TODO - + // - 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) - /* Open the audio device */ - if (SDL_OpenAudio(&waveSpec, NULL) < 0) + Patch* patch = patches[patchId]; + if (!patch) { - fprintf(stderr, "Failed to initialize audio: %s\n", SDL_GetError()); - exit(-1); + return 0; } - SDL_PauseAudio(0); - - int id = 1; - for (int i = 0; i < voiceCount; i++) + int ids = 0; + for (Sample* s : patch->samples) { - voices[i] = new Voice(); - voices[i]->id = id; - id = id << 1; - } + if (!s) + { + continue; + } + + if (note > s->maxNote || note < s->minNote) + { + continue; + } + + Voice* v = SPUObtainVoice(note, (u8) patchId); + if (!v) + { + return 0; + } - for (int i = 0; i < patchCount; i++) - { - patches[i] = NULL; + v->patchId = (u8) patchId; + v->note = note; + v->velocity = 127; + v->voll = voll; + v->volr = volr; + v->pitch = pitch; + v->pitchMin = pitchMin; + v->pitchMax = pitchMax; + v->sample = s; + ids |= v->id; } + + return ids; } -Sequencer::~Sequencer() +void SPUOneShotStop(s32 mask) { - // Delete buffers (by killing the instrument) - for (Patch* patch : patches) + for (int i = 0; i < VOICE_SIZE_LIMIT; i++) { - delete patch; + if ((voices[i]->id & mask) != 0) + { + // TODO - not sure if this is right. + // Maybe it should trigger a release? + SPUReleaseVoice(voices[i]); + } } -} - -u64 timeSinceEpochMillisec() -{ - using namespace std::chrono; - return duration_cast(system_clock::now().time_since_epoch()).count(); -} +} -void SDLCallback(void* udata, Uint8* stream, int len) +void SPUTick(void* udata, Uint8* stream, int len) { udata; - gseq->mutex.lock(); // This runs at 44100hz // 1. tick voices @@ -314,7 +534,7 @@ void SDLCallback(void* udata, Uint8* stream, int len) // 2. run reverb // start/stop any sequence notes - gseq->tickSequence(); + SPUTickSequences(); float* AudioStream = (float*) stream; int StreamLength = len / sizeof(float); @@ -335,7 +555,7 @@ void SDLCallback(void* udata, Uint8* stream, int len) s32 reverb_in_right = 0; // 1. Prepare all voices with reverb - for (Voice* v : gseq->voices) + for (Voice* v : voices) { if (!v->sample || v->complete || !v->inUse) { @@ -363,8 +583,7 @@ void SDLCallback(void* udata, Uint8* stream, int len) static_cast(Clamp16((s32) (reverb_in_left))), static_cast(Clamp16((s32) (reverb_in_right))), &reverb_out_left, - &reverb_out_right - ); + &reverb_out_right); leftSample += reverb_out_left; rightSample += reverb_out_right; @@ -372,88 +591,12 @@ void SDLCallback(void* udata, Uint8* stream, int len) // make value usable by SDL leftSample = leftSample / 32767.0f; rightSample = rightSample / 32767.0f; - SDL_MixAudioFormat((Uint8*) (AudioStream + i), (const Uint8*) &leftSample, AUDIO_F32, sizeof(float), 70); + SDL_MixAudioFormat((Uint8*) (AudioStream + i), (const Uint8*) &leftSample, AUDIO_F32, sizeof(float), 70); SDL_MixAudioFormat((Uint8*) (AudioStream + i + 1), (const Uint8*) &rightSample, AUDIO_F32, sizeof(float), 70); } - - gseq->mutex.unlock(); -} - -////////////////////////// -// PRIVATE -void Sequencer::reset() -{ - mutex.lock(); - stopAll(); - - for (s16 i = 0; i < patchCount; i++) - { - - patches[i] = NULL; - } - - for (Sequence* seq : sequences) - { - delete seq; - } - sequences.clear(); - mutex.unlock(); -} - -void Sequencer::stopAll() -{ - for (Sequence* seq : sequences) - { - seq->play = false; - } - - for (s16 i = 0; i < voiceCount; i++) - { - releaseVoice(voices[i]); - } -} - -void Sequencer::playSeq(s32 seqId) -{ - mutex.lock(); - for (Sequence* seq : sequences) - { - if (seq->id == seqId) - { - seq->repeats = 0; - seq->play = true; - } - } - mutex.unlock(); -} - - -void Sequencer::stopSeq(s32 seqId) -{ - mutex.lock(); - stopSeqSafe(seqId); - mutex.unlock(); -} - -void Sequencer::stopSeqSafe(s32 seqId) -{ - for (Sequence* seq : sequences) - { - if (seq->id == seqId) - { - seq->play = false; - for (Voice* v : voices) - { - if (v && v->sequence == seq) - { - v->offTime = timeSinceEpochMillisec(); - } - } - } - } } -void Sequencer::tickSequence() +void SPUTickSequences() { // TODO - convert this to be based on 44100hz instead of ms timestamp @@ -469,7 +612,7 @@ void Sequencer::tickSequence() if (seq->repeats >= seq->repeatLimit && seq->repeatLimit > 0) { - stopSeqSafe(seq->id); + SPUSeqStop(seq->id); continue; } @@ -497,7 +640,7 @@ void Sequencer::tickSequence() continue; } - Voice* v = obtainVoice(message->note, message->patchId); + Voice* v = SPUObtainVoice(message->note, message->patchId); if (!v) { continue; @@ -516,7 +659,7 @@ void Sequencer::tickSequence() } v->sequence = seq; - v->patchId = seq->channels[message->channelId]->patch->id; + v->patchId = seq->channels[message->channelId]->patch->_id; v->channelId = message->channelId; v->pitchMin = 0; v->velocity = message->velocity; @@ -524,61 +667,231 @@ void Sequencer::tickSequence() v->sample = sample; v->voll = left; v->volr = right; - v->loop = sample->loop; // // attack is greater than sample length? sample->adsr.attackRate / 1000 > 1; } - break; - } - case NOTE_OFF: - { - for (Voice* v : voices) - { - if (v && v->sequence == seq && v->note == 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 (int i = 0; i < voiceCount; i++) - { - if (voices[i]->inUse && voices[i]->sequence == seq - && voices[i]->channelId == message->channelId) - { - voices[i]->pitch = message->bend; - } - } - break; - } - } + break; + } + case NOTE_OFF: + { + for (Voice* v : voices) + { + if (v && v->sequence == seq && v->note == 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 (int i = 0; i < VOICE_SIZE_LIMIT; i++) + { + if (voices[i]->inUse && voices[i]->sequence == seq + && voices[i]->channelId == message->channelId) + { + voices[i]->pitch = 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 +s32 Voice::interpolate() +{ + 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 // + }}; + + // the interpolation index is based on the source files sample rate + const u8 i = (u8) vounter.interpolation_index(); + const u32 s = ((u32) f_SampleOffset) + ZeroExtend32(vounter.sample_index()); + + // interpolate on the 4 most recent samples from current position + // The below `if` statements are in case we loop + // we gauss the end of the sample with the beginning. + // Probably a better way to do it, but w/e for now... + s32 out = 0; + if (s == 0) + { + if (hasLooped) + { + out += s32(gauss[0x0FF - i]) * s32(sample->buffer[sample->len - 3]); // oldest + out += s32(gauss[0x1FF - i]) * s32(sample->buffer[sample->len - 2]); // older + out += s32(gauss[0x100 + i]) * s32(sample->buffer[sample->len - 1]); // old } + out += s32(gauss[0x000 + i]) * s32(sample->buffer[s - 0]); // new } -} - -void Sequencer::tickVoice() -{ - for (int c = 0; c < voiceCount; c++) + else if (s == 1) { - Voice* voice = voices[c]; - if (!voice->inUse) + if (hasLooped) { - continue; + out += s32(gauss[0x0FF - i]) * s32(sample->buffer[sample->len - 2]); // oldest + out += s32(gauss[0x1FF - i]) * s32(sample->buffer[sample->len - 1]); // older } - - voice->tick(); + out += s32(gauss[0x100 + i]) * s32(sample->buffer[s - 1]); // old + out += s32(gauss[0x000 + i]) * s32(sample->buffer[s - 0]); // new } + else if (s == 2) + { + if (hasLooped) + { + out += s32(gauss[0x0FF - i]) * s32(sample->buffer[sample->len - 1]); // oldest + } + out += s32(gauss[0x1FF - i]) * s32(sample->buffer[s - 2]); // older + out += s32(gauss[0x100 + i]) * s32(sample->buffer[s - 1]); // old + out += s32(gauss[0x000 + i]) * s32(sample->buffer[s - 0]); // new + } + else + { + out += s32(gauss[0x0FF - i]) * s32(sample->buffer[(int) (s) -3]); // oldest + out += s32(gauss[0x1FF - i]) * s32(sample->buffer[(int) (s) -2]); // older + out += s32(gauss[0x100 + i]) * s32(sample->buffer[(int) (s) -1]); // old + out += s32(gauss[0x000 + i]) * s32(sample->buffer[(int) (s) -0]); // new + } + + + return out >> 15; } std::tuple Voice::tick() @@ -608,7 +921,7 @@ std::tuple Voice::tick() // Are we at the end of the sample? if (f_SampleOffset + vounter.sample_index() >= sample->len) { - if (!loop) + if (!sample->loop) { complete = true; return std::make_tuple(0, 0); @@ -739,224 +1052,9 @@ std::tuple Voice::tick() return std::make_tuple(left, right); } -Voice* Sequencer::obtainVoice(u8 note, u8 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; - available = NULL; - for (int i = 0; i < voiceCount; i++) - { - Voice* v = voices[i]; - if (v->note == note && v->patchId == patchId) - { - if (!available || available->f_SampleOffset > v->f_SampleOffset) - { - available = v; - } - } - - if (v->complete) - { - releaseVoice(v); - } - - if (!v->inUse) - { - v->inUse = true; - return v; - } - } - - if (!available) - { - return NULL; - } - - // this is voice in use that we can reuse - releaseVoice(available); - available->inUse = true; - return available; -} - -void Sequencer::releaseVoice(Voice* v) -{ - v->channelId = 0xFF; - v->offTime = 0; - v->pitch = 0; - v->adsrPhase = NONE; - v->adsrCounter = 0; - v->adsrCurrentLevel = 0; - v->adsrTargetLevel = MAX_VOLUME; - v->sequence = NULL; - v->loop = false; - v->f_SampleOffset = 0; - v->vounter.bits = 0; - v->complete = false; - v->velocity = 127; - v->voll = 127; - v->volr = 127; - v->complete = false; - v->hasLooped = false; - v->inUse = false; -} - -////////////////////////// -// PUBLIC -Patch* Sequencer::createPatch(s16 id) -{ - if (patches[id]) - { - return patches[id]; - } - - patches[id] = new Patch(); - patches[id]->id = (u8)id; - return patches[id]; -} - -Sequence* Sequencer::createSequence() -{ - mutex.lock(); - Sequence* seq = new Sequence(); - sequences.push_back(seq); - mutex.unlock(); - return seq; -} - -Sequence* Sequencer::getSequence(s32 id) -{ - //mutex.lock(); - Sequence* s; - s = NULL; - for (Sequence* seq : sequences) - { - if (seq->id == id) - { - s = seq; - break; - } - } - //mutex.unlock(); - return s; -} - -s32 Sequencer::playNote(s32 patchId, u8 note, s16 voll, s16 volr, u8 pitch, s32 pitchMin, s32 pitchMax) -{ - - // TODO - - // - 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) - - mutex.lock(); - Patch* patch = patches[patchId]; - if (!patch) - { - mutex.unlock(); - return 0; - } - - int ids = 0; - for (Sample* s : patch->samples) - { - if (!s) - { - continue; - } - - if (note > s->maxNote || note < s->minNote) - { - continue; - } - - Voice* v = obtainVoice(note, (u8) patchId); - if (!v) - { - mutex.unlock(); - return 0; - } - - v->patchId = (u8) patchId; - v->note = note; - v->velocity = 127; - v->voll = voll; - v->volr = volr; - v->pitch = pitch; - v->pitchMin = pitchMin; - v->pitchMax = pitchMax; - v->loop = s->loop; - v->sample = s; - ids |= v->id; - } - - mutex.unlock(); - return ids; -} - -void Sequencer::stopNote(s32 mask) -{ - mutex.lock(); - for (int i = 0; i < voiceCount; i++) - { - if ((voices[i]->id & mask) != 0) - { - // TODO - not sure if this is right. - // Maybe it should trigger a release? - releaseVoice(voices[i]); - } - } - mutex.unlock(); -} ////////////////////////// -// SEQUENCE -MIDIMessage* Sequence::createMIDIMessage() -{ - MIDIMessage* msg = new MIDIMessage(); - messages.push_back(msg); - return msg; -} - -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; -} - - -//////////////////////////// -// REVERB -//////////////////////////// - +// REVERB - duckstation static const u32 NUM_REVERB_REGS = 32; struct ReverbRegisters { @@ -1131,7 +1229,7 @@ void ProcessReverb(s16 left_in, s16 right_in, s32* left_out, s32* right_out) 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 + // 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; diff --git a/Source/AliveLibCommon/audio/Sequencer.hpp b/Source/AliveLibCommon/audio/Sequencer.hpp index 2a707f02e..15998a837 100644 --- a/Source/AliveLibCommon/audio/Sequencer.hpp +++ b/Source/AliveLibCommon/audio/Sequencer.hpp @@ -6,7 +6,7 @@ #include #include -namespace sean { +namespace SPU { const u32 NUM_SAMPLES_FROM_LAST_ADPCM_BLOCK = 3; const u32 NUM_SAMPLES_PER_ADPCM_BLOCK = 28; @@ -14,7 +14,15 @@ const u32 NUM_SAMPLES_PER_ADPCM_BLOCK = 28; const s16 MIN_VOLUME = 0; const s16 MAX_VOLUME = 32767; -u32 mask(u32 num); +static u32 mask(u32 num) +{ + u32 res = 0; + while (num-- > 0) + { + res = (res << 1) | 1; + } + return res; +} struct ASDR { @@ -48,7 +56,7 @@ class Sample } ~Sample() { - delete[] buffer; + } float volume; @@ -82,23 +90,27 @@ class Sample class Patch { public: - Patch() + Patch(const u8 id) + : _id(id) { - for (int i = 0; i < 128; i++) + for (int i = 0; i < SAMPLE_SIZE_LIMIT; i++) { - samples[i] = NULL; + samples[i] = nullptr; } } ~Patch() { - for (Sample* sample : samples) + for (int i = 0; i < SAMPLE_SIZE_LIMIT; i++) { - delete sample; + delete samples[i]; + samples[i] = nullptr; } } - u8 id; - Sample* samples[128]; + const u8 _id; + + static const int SAMPLE_SIZE_LIMIT = 128; + std::array samples; }; @@ -172,6 +184,7 @@ class Sequence s32 repeats = 0; + void Reset(); MIDIMessage* createMIDIMessage(); MIDIMessage* next(u64 now); Channel* channels[16]; @@ -240,7 +253,6 @@ class Voice bool inUse = false; bool complete = false; - bool loop = false; u64 offTime = 0; // when the note was released VoiceCounter vounter; @@ -262,50 +274,31 @@ class Voice }; -/* -* Can play MIDI -*/ -class Sequencer -{ -public: - Sequencer(); - ~Sequencer(); - - void reset(); - void stopAll(); +void Init(); +void DeInit(); - Patch* createPatch(s16 id); +void Reset(); +void StopAll(); - Sequence* createSequence(); - Sequence* getSequence(s32 id); +// 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, u8 pitch, s32 pitchMin, s32 pitchMax); +void OneShotStop(s32 mask); - s32 playNote(s32 patchId, u8 note, s16 voll, s16 volr, u8 pitch, s32 pitchMin, s32 pitchMax); - void stopNote(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); - void playSeq(s32 seqId); - void stopSeq(s32 seqId); - - static const int voiceCount = 24; - Voice* voices[voiceCount]; - - void tickSequence(); - std::mutex mutex; - - -private: - Voice* obtainVoice(u8 note, u8 patchId); - void releaseVoice(Voice* v); - void tickVoice(); - - void stopSeqSafe(s32 seqId); - - std::vector sequences; - - static const int patchCount = 128; - Patch* patches[patchCount]; -}; +// 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 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); -static Sequencer* gseq; -void SDLCallback(void* udata, Uint8* stream, int len); } // namespace \ No newline at end of file From 47558997dd8a5ec3a990c3072747c5b9c4cae672 Mon Sep 17 00:00:00 2001 From: Sean Horton <3359434+sean-horton@users.noreply.github.com> Date: Sun, 2 Apr 2023 19:55:09 -0400 Subject: [PATCH 38/82] missed config option --- Source/relive_config.h.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/relive_config.h.in b/Source/relive_config.h.in index 4022eb546..4cd177e65 100644 --- a/Source/relive_config.h.in +++ b/Source/relive_config.h.in @@ -17,6 +17,6 @@ #cmakedefine01 USE_SDL2_SOUND #cmakedefine01 USE_SDL2_IO #cmakedefine01 RENDERER_OPENGL -#cmakedefine01 ALTERNATE_AUDIO +#cmakedefine01 AUDIO_SPU_EMULATION #cmakedefine BUILD_NUMBER @BUILD_NUMBER@ #cmakedefine CI_PROVIDER "@CI_PROVIDER@" From eaccbfd4a9d6742877470248a6f151fe38c7f950 Mon Sep 17 00:00:00 2001 From: Sean Horton <3359434+sean-horton@users.noreply.github.com> Date: Sun, 2 Apr 2023 19:55:47 -0400 Subject: [PATCH 39/82] missed cmake option --- options.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/options.cmake b/options.cmake index a9161128d..3f2a3a28e 100644 --- a/options.cmake +++ b/options.cmake @@ -17,5 +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(ALTERNATE_AUDIO "Use alternate audio processing." ON) +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) From d31fdc0c788eb9cdd51353b034f455606ae207f4 Mon Sep 17 00:00:00 2001 From: Sean Horton <3359434+sean-horton@users.noreply.github.com> Date: Sun, 2 Apr 2023 20:22:06 -0400 Subject: [PATCH 40/82] always try and get a voice --- Source/AliveLibCommon/audio/Sequencer.cpp | 48 ++++++++++++++++------- 1 file changed, 34 insertions(+), 14 deletions(-) diff --git a/Source/AliveLibCommon/audio/Sequencer.cpp b/Source/AliveLibCommon/audio/Sequencer.cpp index a5046d519..7323260bc 100644 --- a/Source/AliveLibCommon/audio/Sequencer.cpp +++ b/Source/AliveLibCommon/audio/Sequencer.cpp @@ -329,23 +329,18 @@ void SPUStopAll() Voice* SPUObtainVoice(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; - available = NULL; + Voice* pref = NULL; + Voice* available = NULL; for (int i = 0; i < VOICE_SIZE_LIMIT; i++) { Voice* v = voices[i]; - if (v->note == note && v->patchId == patchId) - { - if (!available || available->f_SampleOffset > v->f_SampleOffset) - { - available = v; - } - } - if (v->complete) { SPUReleaseVoice(v); @@ -356,17 +351,42 @@ Voice* SPUObtainVoice(u8 note, u8 patchId) v->inUse = true; return v; } + + if (note == v->note && patchId == v->patchId) + { + if (!pref || pref->f_SampleOffset > v->f_SampleOffset) + { + pref = v; + } + } + else if (!v->sample->loop) + { + if (!available || available->sample->len - available->f_SampleOffset > v->sample->len - v->f_SampleOffset) + { + // find the most played through sample + available = v; + } + } } - if (!available) + Voice* use = NULL; + if (pref) + { + use = pref; + } + else if (available) + { + use = available; + } + else { return NULL; } // this is voice in use that we can reuse - SPUReleaseVoice(available); - available->inUse = true; - return available; + SPUReleaseVoice(use); + use->inUse = true; + return use; } void SPUReleaseVoice(Voice* v) From c6126ecf499caba91a6145f3d9a61255e9d8471d Mon Sep 17 00:00:00 2001 From: Sean Horton <3359434+sean-horton@users.noreply.github.com> Date: Sun, 2 Apr 2023 20:37:38 -0400 Subject: [PATCH 41/82] cleanup project files --- Source/AliveLibCommon/CMakeLists.txt | 16 +- .../AliveLibCommon/audio/{mixer => }/ADSR.cpp | 0 .../AliveLibCommon/audio/{mixer => }/ADSR.hpp | 0 Source/AliveLibCommon/audio/MidiPlayer.cpp | 5 +- Source/AliveLibCommon/audio/MidiPlayer.hpp | 77 +- .../AliveLibCommon/audio/SequencePlayer.cpp | 432 --- .../AliveLibCommon/audio/SequencePlayer.hpp | 113 - Source/AliveLibCommon/audio/Soundbank.cpp | 28 - Source/AliveLibCommon/audio/Soundbank.hpp | 96 - .../AliveLibCommon/audio/mixer/AliveAudio.cpp | 462 --- .../AliveLibCommon/audio/mixer/AliveAudio.hpp | 56 - .../audio/mixer/AudioStream.cpp | 19 - .../audio/mixer/AudioStream.hpp | 8 - Source/AliveLibCommon/audio/mixer/SPU.cpp | 2582 ----------------- Source/AliveLibCommon/audio/mixer/SPU.hpp | 60 - Source/AliveLibCommon/audio/mixer/SPUEMU.cpp | 1053 ------- Source/AliveLibCommon/audio/mixer/SPUEMU.hpp | 34 - Source/AliveLibCommon/audio/mixer/Voice.cpp | 57 - Source/AliveLibCommon/audio/mixer/Voice.hpp | 35 - Source/AliveLibCommon/audio/mixer/biquad.cpp | 144 - Source/AliveLibCommon/audio/mixer/biquad.hpp | 66 - 21 files changed, 78 insertions(+), 5265 deletions(-) rename Source/AliveLibCommon/audio/{mixer => }/ADSR.cpp (100%) rename Source/AliveLibCommon/audio/{mixer => }/ADSR.hpp (100%) delete mode 100644 Source/AliveLibCommon/audio/SequencePlayer.cpp delete mode 100644 Source/AliveLibCommon/audio/SequencePlayer.hpp delete mode 100644 Source/AliveLibCommon/audio/Soundbank.cpp delete mode 100644 Source/AliveLibCommon/audio/Soundbank.hpp delete mode 100644 Source/AliveLibCommon/audio/mixer/AliveAudio.cpp delete mode 100644 Source/AliveLibCommon/audio/mixer/AliveAudio.hpp delete mode 100644 Source/AliveLibCommon/audio/mixer/AudioStream.cpp delete mode 100644 Source/AliveLibCommon/audio/mixer/AudioStream.hpp delete mode 100644 Source/AliveLibCommon/audio/mixer/SPU.cpp delete mode 100644 Source/AliveLibCommon/audio/mixer/SPU.hpp delete mode 100644 Source/AliveLibCommon/audio/mixer/SPUEMU.cpp delete mode 100644 Source/AliveLibCommon/audio/mixer/SPUEMU.hpp delete mode 100644 Source/AliveLibCommon/audio/mixer/Voice.cpp delete mode 100644 Source/AliveLibCommon/audio/mixer/Voice.hpp delete mode 100644 Source/AliveLibCommon/audio/mixer/biquad.cpp delete mode 100644 Source/AliveLibCommon/audio/mixer/biquad.hpp diff --git a/Source/AliveLibCommon/CMakeLists.txt b/Source/AliveLibCommon/CMakeLists.txt index c58f51edf..712465b5b 100644 --- a/Source/AliveLibCommon/CMakeLists.txt +++ b/Source/AliveLibCommon/CMakeLists.txt @@ -37,25 +37,13 @@ SET(AliveLibSrcCommon W32CrashHandler.hpp audio/Sequencer.hpp audio/Sequencer.cpp - audio/SequencePlayer.hpp - audio/SequencePlayer.cpp - audio/mixer/AliveAudio.hpp - audio/mixer/AliveAudio.cpp - audio/mixer/Voice.hpp - audio/mixer/Voice.cpp - audio/mixer/ADSR.hpp - audio/mixer/ADSR.cpp + audio/ADSR.hpp + audio/ADSR.cpp audio/Exceptions.hpp audio/Stream.hpp audio/Stream.cpp - audio/Soundbank.hpp - audio/Soundbank.cpp audio/MidiPlayer.hpp audio/MidiPlayer.cpp - audio/mixer/biquad.hpp - audio/mixer/biquad.cpp - audio/mixer/AudioStream.hpp - audio/mixer/AudioStream.cpp ) add_library(AliveLibCommon ${AliveLibSrcCommon}) diff --git a/Source/AliveLibCommon/audio/mixer/ADSR.cpp b/Source/AliveLibCommon/audio/ADSR.cpp similarity index 100% rename from Source/AliveLibCommon/audio/mixer/ADSR.cpp rename to Source/AliveLibCommon/audio/ADSR.cpp diff --git a/Source/AliveLibCommon/audio/mixer/ADSR.hpp b/Source/AliveLibCommon/audio/ADSR.hpp similarity index 100% rename from Source/AliveLibCommon/audio/mixer/ADSR.hpp rename to Source/AliveLibCommon/audio/ADSR.hpp diff --git a/Source/AliveLibCommon/audio/MidiPlayer.cpp b/Source/AliveLibCommon/audio/MidiPlayer.cpp index ec459c255..382ec1d23 100644 --- a/Source/AliveLibCommon/audio/MidiPlayer.cpp +++ b/Source/AliveLibCommon/audio/MidiPlayer.cpp @@ -1,10 +1,7 @@ #pragma once #include "MidiPlayer.hpp" -#include "Soundbank.hpp" -#include "mixer/ADSR.hpp" -#include "mixer/AliveAudio.hpp" -#include "SequencePlayer.hpp" +#include "ADSR.hpp" namespace psx { diff --git a/Source/AliveLibCommon/audio/MidiPlayer.hpp b/Source/AliveLibCommon/audio/MidiPlayer.hpp index 174f83986..6b62dd756 100644 --- a/Source/AliveLibCommon/audio/MidiPlayer.hpp +++ b/Source/AliveLibCommon/audio/MidiPlayer.hpp @@ -1,13 +1,69 @@ #pragma once -#include "Soundbank.hpp" -#include "SequencePlayer.hpp" +#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: + s16* m_SampleBuffer; + u32 i_SampleSize; + u32 sampleRate; + 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; @@ -78,6 +134,23 @@ namespace psx { 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; diff --git a/Source/AliveLibCommon/audio/SequencePlayer.cpp b/Source/AliveLibCommon/audio/SequencePlayer.cpp deleted file mode 100644 index fc7344ae3..000000000 --- a/Source/AliveLibCommon/audio/SequencePlayer.cpp +++ /dev/null @@ -1,432 +0,0 @@ -#include "SequencePlayer.hpp" - -namespace psx { - -SequencePlayer::SequencePlayer() -{ - // Start the Sequencer thread. - m_SequenceThread = new std::thread(&SequencePlayer::m_PlayerThreadFunction, this); - m_QuarterCallback = nullptr; -} - -SequencePlayer::~SequencePlayer() -{ - m_KillThread = true; - m_SequenceThread->join(); - delete m_SequenceThread; -} - -// 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; -} - -float SequencePlayer::MidiTimeToSample(int time) -{ - // This may, or may not be correct. // TODO: Revise - // Oct 7, 2022 - added (x1.041f). For some reason this seems to match AE playback speed... - // AO might be better with just times 1.000? - return ((60.0f * float(time)) / float(m_SongTempo)) * (float(AliveAudioSampleRate) / 500.0f) * 1000.0f; -} - -s32 SequencePlayer::completedRepeats() -{ - return mCompletedRepeats.load(); -} - -u64 timeSinceEpochMillisec() -{ - using namespace std::chrono; - return duration_cast(system_clock::now().time_since_epoch()).count(); -} - -void SequencePlayer::m_PlayerThreadFunction() -{ - int channels[16]; - for (int i = 0; i < 16; i++) - { - channels[i] = 0; - } - - u64 ms_tick = timeSinceEpochMillisec(); - int trackPosition = 0; - while (!m_KillThread) - { - - m_PlayerStateMutex.lock(); - AliveAudio::LockNotes(); - float multi = float(std::min(mVolLeft, mVolRight)) / 127.0f; - - if ((int) m_MessageList.size() > 0) - { - if (trackPosition >= (int) m_MessageList.size()) - { - trackPosition = 0; - ms_tick = timeSinceEpochMillisec(); - } - - u64 ms_now = timeSinceEpochMillisec() - ms_tick; - float tickDurationUs = m_RawTempo / m_ticksPerBeat; - u64 diffUs = ms_now * 1000; - u64 track_tick = u64(diffUs / tickDurationUs); - - - while (trackPosition < (int) m_MessageList.size()) - { - AliveAudioMidiMessage m = m_MessageList[trackPosition]; - if (m.TimeOffset > track_tick) - { - break; - } - - trackPosition++; - switch (m.Type) - { - case ALIVE_MIDI_NOTE_ON: - AliveAudio::NoteOn(channels[m.Channel], m.Note, (char) m.Velocity, m_PlayId, MidiTimeToSample(m.TimeOffset), multi); - break; - case ALIVE_MIDI_NOTE_OFF: - AliveAudio::NoteOffDelay(channels[m.Channel], m.Note, m_PlayId, MidiTimeToSample(m.TimeOffset)); // Fix this. Make note off's have an offset in the voice timeline. - break; - case ALIVE_MIDI_PROGRAM_CHANGE: - channels[m.Channel] = m.Special; - break; - //break; - case ALIVE_MIDI_ENDTRACK: - m_PlayerState = ALIVE_SEQUENCER_PLAYING; - m_SongFinishSample = ((int) (AliveAudio::currentSampleIndex + MidiTimeToSample(m.TimeOffset))); - mCompletedRepeats.store(mCompletedRepeats.load() + 1); - m_PlayerState = ALIVE_SEQUENCER_FINISHED; - break; - default: - { - } - // Nothin - } - } - } - - AliveAudio::UnlockNotes(); - m_PlayerStateMutex.unlock(); - - // THIS IS RIGHT! - // int t = (int) (m_RawTempo * m.TimeOffset / m_ticksPerBeat); - //std::this_thread::sleep_for(std::chrono::microseconds((int) t)); - - std::this_thread::sleep_for(std::chrono::milliseconds(10)); - } - - -} - -int SequencePlayer::GetPlaybackPositionSample() -{ - return ((int) (AliveAudio::currentSampleIndex - m_SongBeginSample)); -} - -void SequencePlayer::SetVolume(s32 volLeft, s32 volRight) -{ - m_PlayerStateMutex.lock(); - mVolLeft = volLeft; - //AliveAudio::LockNotes(); - //AliveAudio::SetVolume(m_TrackID, mVolLeft, mVolRight); - //AliveAudio::UnlockNotes(); - mVolRight = volRight; - m_PlayerStateMutex.unlock(); -} - -void SequencePlayer::StopSequence() -{ - m_PlayerStateMutex.lock(); - AliveAudio::LockNotes(); - AliveAudio::ClearAllTrackVoices(m_PlayId, false); - m_PlayerState = ALIVE_SEQUENCER_STOPPED; - mCompletedRepeats.store(1); - m_PrevBar = 0; - AliveAudio::UnlockNotes(); - m_PlayerStateMutex.unlock(); -} - -void SequencePlayer::PlaySequence(s32 playId) -{ - m_PlayerStateMutex.lock(); - m_PlayId = playId; - if (m_PlayerState == ALIVE_SEQUENCER_STOPPED || m_PlayerState == ALIVE_SEQUENCER_FINISHED) - { - m_PrevBar = 0; - mCompletedRepeats.store(0); - m_PlayerState = ALIVE_SEQUENCER_INIT_VOICES; - } - m_PlayerStateMutex.unlock(); -} - -int SequencePlayer::LoadSequenceData(std::vector seqData, s32 trackId, s32 repeatCount) -{ - m_TrackID = trackId; - mRepeatCount = repeatCount; - Stream stream(std::move(seqData)); - - return LoadSequenceStream(stream); -} - -int SequencePlayer::LoadSequenceStream(Stream& stream) -{ - StopSequence(); - m_MessageList.clear(); - - SeqHeader seqHeader; - - // Read the header - if (stream.Size() == 0) - { - std::cout << "no stream!?\n"; - return 1; - } - - 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 q = 0; - for (int i = 0; i < 2; i++) - { - q += seqHeader.mResolutionOfQuaterNote[1 - i] << (8 * i); - } - - m_TimeSignatureBars = seqHeader.mTimeSignatureBars; - - m_SongTempo = ((float) (60000000.0 / tempoValue)); - m_RawTempo = (float)tempoValue; // tempo (length of quarter note in microseconds) 0.000001us/s vs 0.001ms/s - m_ticksPerBeat = float(q); - - unsigned int prevDeltaTime = 0; - 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 0; // 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: - { - //std::cout << "end of track" << std::endl; - - // This may not be right, but I've found it lines up new sections well. - // If not, pass deltaTime instead of nextQuarter - unsigned int quarterDur = int(m_SongTempo); - unsigned int nextQuarter = 0; - - while (nextQuarter < prevDeltaTime) - { - nextQuarter += quarterDur; - } - //nextQuarter -= quarterDur; - - if (nextQuarter - quarterDur <= 0) - { - nextQuarter = deltaTime; - } - - m_MessageList.push_back(AliveAudioMidiMessage(ALIVE_MIDI_ENDTRACK, 480, 0, 0, 0)); - return 0; - //Sint32 loopCount = gSeqInfo.iNumTimesToLoop; // v1 some hard coded data?? or just a local static? - //if (loopCount) // If zero then loop forever - //{ - // --loopCount; - - // //char buf[256]; - // //sprintf(buf, "EOT: %d loops left\n", loopCount); - // // OutputDebugString(buf); - - // gSeqInfo.iNumTimesToLoop = loopCount; //v1 - // if (loopCount <= 0) - // { - // //getNext_q(aSeqIndex); // Done playing? Ptr not reset to start - // return 1; - // } - //} - - ////OutputDebugString("EOT: Loop forever\n"); - //// Must be a loop back to the start? - //stream.Seek(midiDataStart); - } - - 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); - if (velocity == 0) // If velocity is 0, then the sequence means to do "Note Off" - { - m_MessageList.push_back(AliveAudioMidiMessage(ALIVE_MIDI_NOTE_OFF, deltaTime, channel, note, velocity)); - } - else - { - prevDeltaTime = deltaTime; - m_MessageList.push_back(AliveAudioMidiMessage(ALIVE_MIDI_NOTE_ON, deltaTime, channel, note, velocity)); - } - } - break; - case 0x8: // Note Off - { - Uint8 note = 0; - stream.ReadUInt8(note); - Uint8 velocity = 0; - stream.ReadUInt8(velocity); - - m_MessageList.push_back(AliveAudioMidiMessage(ALIVE_MIDI_NOTE_OFF, deltaTime, channel, note, velocity)); - } - break; - case 0xc: // Program Change - { - Uint8 prog = 0; - stream.ReadUInt8(prog); - //prevDeltaTime = deltaTime; - m_MessageList.push_back(AliveAudioMidiMessage(ALIVE_MIDI_PROGRAM_CHANGE, deltaTime, channel, 0, 0, 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 - { - Uint16 bend = 0; - stream.ReadUInt16(bend); - } - break; - case 0xf: // Sysex len - { - const Uint32 length = _MidiReadVarLen(stream); - _SndMidiSkipLength(stream, length); - } - break; - default: - throw std::runtime_error("Unknown MIDI command"); - } - } - } -} - -} // namespace psx \ No newline at end of file diff --git a/Source/AliveLibCommon/audio/SequencePlayer.hpp b/Source/AliveLibCommon/audio/SequencePlayer.hpp deleted file mode 100644 index b11d00a5d..000000000 --- a/Source/AliveLibCommon/audio/SequencePlayer.hpp +++ /dev/null @@ -1,113 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include "Stream.hpp" -#include "mixer/AliveAudio.hpp" - -namespace psx { - -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; -}; - -// The types of Midi Messages the sequencer will play. -enum AliveAudioMidiMessageType -{ - ALIVE_MIDI_NOTE_ON = 1, - ALIVE_MIDI_NOTE_OFF = 2, - ALIVE_MIDI_PROGRAM_CHANGE = 3, - ALIVE_MIDI_ENDTRACK = 4, -}; - -// The current state of a SequencePlayer. -enum AliveAudioSequencerState -{ - ALIVE_SEQUENCER_STOPPED = 1, - ALIVE_SEQUENCER_PLAYING = 3, - ALIVE_SEQUENCER_FINISHED = 4, - ALIVE_SEQUENCER_INIT_VOICES = 5, -}; - -struct AliveAudioMidiMessage -{ - AliveAudioMidiMessage(AliveAudioMidiMessageType type, int timeOffset, int channel, int note, int velocity, int special = 0) - { - Type = type; - Channel = channel; - Note = note; - Velocity = velocity; - TimeOffset = timeOffset; - Special = special; - } - AliveAudioMidiMessageType Type; - int Channel; - int Note; - int Velocity; - int TimeOffset; - int Special = 0; -}; - -// Gets called every time the play position is at 1/4 of the song. -// Useful for changing sequences but keeping the time signature in sync. -typedef void (*AliveAudioQuarterCallback)(); - -class SequencePlayer -{ -public: - SequencePlayer(); - ~SequencePlayer(); - - s32 mRepeatCount; - s32 m_PlayId; - - int LoadSequenceData(std::vector seqData, s32 trackId, s32 repeatCount); - int LoadSequenceStream(Stream& stream); - void PlaySequence(s32 playId); - void StopSequence(); - int completedRepeats(); - - void SetVolume(s32 volLeft, s32 volRight); - float MidiTimeToSample(int time); - int GetPlaybackPositionSample(); - - int m_TrackID = 1; // The track ID. Use this to seperate SoundFX from Music. - AliveAudioSequencerState m_PlayerState = ALIVE_SEQUENCER_STOPPED; - AliveAudioQuarterCallback m_QuarterCallback; - -private: - s32 mVolLeft = 64; - s32 mVolRight = 64; - std::atomic mCompletedRepeats; - int m_KillThread = false; // If true, loop thread will exit. - int m_SongFinishSample = 0; // Not relative. - int m_SongBeginSample = 0; // Not relative. - int m_PrevBar = 0; - int m_TimeSignatureBars; - float m_SongTempo; - float m_RawTempo; - float m_ticksPerBeat; - int m_Tick = 0; - void m_PlayerThreadFunction(); - std::vector m_MessageList; - std::thread* m_SequenceThread; - std::mutex m_MessageListMutex; - std::mutex m_PlayerStateMutex; -}; - -} // namespace psx \ No newline at end of file diff --git a/Source/AliveLibCommon/audio/Soundbank.cpp b/Source/AliveLibCommon/audio/Soundbank.cpp deleted file mode 100644 index 2820a6912..000000000 --- a/Source/AliveLibCommon/audio/Soundbank.cpp +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once - -#include "Soundbank.hpp" - -namespace psx { - -float Sample::GetSample(float sampleOffset) -{ - AliveAudioHelper helper; - - //if (!AliveAudio::Interpolation) - return helper.SampleSint16ToFloat(m_SampleBuffer[(int) sampleOffset]); // No interpolation. Faster but sounds jaggy. - - //int roundedOffset = (int) floor(sampleOffset); - //return helper.SampleSint16ToFloat(s16(helper.Lerp(m_SampleBuffer[roundedOffset], m_SampleBuffer[roundedOffset + 1], float(int(sampleOffset) - roundedOffset)))); -} - -Soundbank::Soundbank(std::vector samples, std::vector programs) -{ - m_Samples = samples; - m_Programs = programs; -} -Soundbank::~Soundbank() -{ - -} - -} // namespace psx \ No newline at end of file diff --git a/Source/AliveLibCommon/audio/Soundbank.hpp b/Source/AliveLibCommon/audio/Soundbank.hpp deleted file mode 100644 index b7a250424..000000000 --- a/Source/AliveLibCommon/audio/Soundbank.hpp +++ /dev/null @@ -1,96 +0,0 @@ -#pragma once - -namespace psx { - -class AliveAudioHelper -{ -public: - float Lerp(u16 from, u16 to, float t) - { - return from + ((to - from) * t); - } - float SampleSint16ToFloat(s16 v) - { - return (v / 32767.0f); - } - - float RandFloat(float a, float b) - { - return ((b - a) * ((float) rand() / RAND_MAX)) + a; - } -}; - -/* -* Raw audio data. An audio sample. -*/ -class Sample -{ -public: - s16* m_SampleBuffer; - u32 i_SampleSize; - u32 sampleRate; - 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; -}; - - -/* -* Maintains a collection of Samples (raw audio) and -* Programs (how to play samples with ADSR and pitch) -*/ -class Soundbank -{ -public: - ~Soundbank(); - Soundbank(std::vector samples, std::vector programs); - - std::vector m_Samples; - std::vector m_Programs; -}; - -} // namespace psx diff --git a/Source/AliveLibCommon/audio/mixer/AliveAudio.cpp b/Source/AliveLibCommon/audio/mixer/AliveAudio.cpp deleted file mode 100644 index 220ecc1a3..000000000 --- a/Source/AliveLibCommon/audio/mixer/AliveAudio.cpp +++ /dev/null @@ -1,462 +0,0 @@ -#pragma once - -#include -#include "AliveAudio.hpp" - -namespace psx { - -Soundbank* AliveAudio::m_CurrentSoundbank = nullptr; -std::mutex AliveAudio::voiceListMutex; -std::vector AliveAudio::m_Voices; -long long AliveAudio::currentSampleIndex = 20; -biquad* AliveAudio::AliveAudioEQBiQuad = nullptr; - -unsigned int alSource[64]; -unsigned int alSampleSet[64]; -Sample* sampleId[64]; - -void AliveInitAudio() -{ - SDL_Init(SDL_INIT_AUDIO); - - SDL_AudioSpec waveSpec; - waveSpec.callback = AliveAudioSDLCallback; - waveSpec.userdata = nullptr; - waveSpec.channels = 2; - waveSpec.freq = AliveAudioSampleRate; - 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 getSource() -{ - - return -1; -} - -void AliveAudio::PlayOneShot(s32 playId, int programId, int note, s32 volLeft, s32 volRight, float pitch, s32 pitch_min, s32 pitch_max) -{ - for (auto program : m_CurrentSoundbank->m_Programs) - { - if (program->prog_id != programId) - { - continue; - } - - pitch; - pitch_min; - pitch_max; - for (auto tone : program->m_Tones) - { - if (note >= tone->Min && note <= tone->Max) - { - Voice* voice = new Voice(); - voice->i_TrackID = playId; - voice->i_Note = note; - voice->f_Velocity = float(std::min(volLeft, volRight)) / 127; - voice->m_Tone = tone; - voice->f_Pan = (float(volRight) / float(volLeft)) - 1; - - // TODO - something more is probably suppose to happen with pitch. - // From the looks of things pitch_min and pitch_max are always equal. - // and if pitch is 0, try using pitch_min instead. - voice->f_Pitch = pitch == 0 ? pitch_min / 127 : pitch / 127; - //m_Voices.push_back(voice); - - Sample* s = voice->m_Tone->m_Sample; - - int i = 0; - int pos = -1; - for (i = 0; i < 64; ++i) - { - if (sampleId[i] == s || !sampleId[i]) - { - pos = i; - } - } - - int a = getSource(); - if (a == -1) - { - return; - } - - - - //float center = float(tone->c_Center); - //float pitch1 = float(tone->Pitch); - //float shift = float(tone->c_Shift); - //float min = tone->Min; - //float max = tone->Max; - float freq = float(pow(1.059463094359, (f64) (note - tone->c_Shift) * 0.00390625)); - - freq = (float(note) / float(tone->c_Shift)); - freq = tone->c_Shift == 0 ? 1 : freq; - freq = (note / float(tone->c_Shift / 256.0)); - freq = tone->c_Shift == 0 ? 1 : freq; - - - float noteFreq = float(pow(2.0, float(note) / 12.0)); - float sampleRootFreq = float(pow(2.0, float(tone->c_Center + (tone->c_Shift / 127.0)) / 12.0)); - freq = noteFreq / sampleRootFreq * 2.0f; - - - - //ALfloat adsr = ALfloat(0.1); - //while (adsr < 2) - //{ - // alSourcef(alSource[pos], AL_PITCH, (ALfloat) adsr); - // adsr += ALfloat(0.1); - // std::this_thread::sleep_for(std::chrono::milliseconds(100)); - //} - - //ALenum state; - //do - //{ - // // al_nssleep(10000000); - // alGetSourcei(alSource, AL_SOURCE_STATE, &state); - //} - //while (alGetError() == AL_NO_ERROR && state == AL_PLAYING); - - //alDeleteSources(1, &alSource); - //alDeleteAuxiliaryEffectSlots(1, &slot); - //alDeleteEffects(1, &effect); - //alDeleteBuffers(1, alSampleSet); - } - } - } -} - -void AliveAudio::PlayOneShot(s32 playId, int programId, int note, s32 volume, float pitch, s32 pitch_min, s32 pitch_max) -{ - AliveAudio::PlayOneShot(playId, programId, note, volume, volume, pitch, pitch_min, pitch_max); -} - - -void AliveAudio::NoteOn(int programId, int note, char velocity, float pitch , int trackID , float trackDelay, float masterVolMulti) -{ - masterVolMulti; - for (auto program : m_CurrentSoundbank->m_Programs) - { - if (program->prog_id != programId) - { - continue; - } - for (auto tone : program->m_Tones) - { - if (note >= tone->Min && note <= tone->Max) // this is funny `if (tone->f_Volume > 0)` - { - Voice* voice = new Voice(); - voice->i_Note = note; - voice->m_Tone = tone; - voice->i_Program = programId; - voice->f_Velocity = velocity == 0 ? tone->f_Volume : velocity / 127.0f; - voice->i_TrackID = trackID; - voice->f_Pitch = pitch; - voice->f_TrackDelay = trackDelay; - //m_Voices.push_back(voice); - - Sample* s = voice->m_Tone->m_Sample; - - int i = 0; - int pos = -1; - for (i = 0; i < 64; ++i) - { - if (sampleId[i] == s || !sampleId[i]) - { - pos = i; - } - } - - int a = getSource(); - if (a == -1) - { - return; - } - - - // Centre: This one is the most important. This is the root note, or, the note that your sample is in. - // If you got your sample from a Sample CD, often it will have what note it is somewhere. - // If you don't know what note your sample is in, LoopAuditioneer can find it out for you. - - // Pitch: This is the pitch fine-tuning, it might be needed depending on the sample you are using. - - // Note: The VAB tool can't preview pitch fine-tuning if you are not using a dev board. - - // MinNote: The lowest note in which this tone will be played. - - //float center = float(tone->c_Center); - /* float pitch1 = float(tone->Pitch); - - float ii = center + (center * pitch1); - ii;*/ - - //float shift_cent = float( (tone->c_Shift + (tone->c_Center << 7))); - float freq = float(pow(1.059463094359, (f64) ((tone->c_Shift / 256.0)) * 0.00390625)); - //freq = (pow(1.059463094359, (f64) ((note / 256.0) - (tone->c_Shift / 256.0)))) * 40; - //freq = (note / float(tone->c_Shift / 256.0)); - //freq = tone->c_Shift == 0 ? 1 : freq; - - //freq = (float(note) / float(tone->c_Center)); - //freq = (float(note) / float(tone->c_Shift / 256.0)); - //freq = tone->c_Shift == 0 ? 1 : freq; - freq = float(note) / (float(pow(tone->c_Center, 2) / (pow(tone->c_Shift, 2)))); - freq = float(note) / float(2 * (tone->c_Shift + (tone->c_Center << 7))); - - float noteFreq = float(pow(2.0, float(note) / 12.0)); - float sampleRootFreq = float(pow(2.0, float(tone->c_Center + (tone->c_Shift / 127.0)) / 12.0)); - freq = noteFreq / sampleRootFreq * 2.0f; - //freq = note / float(float(tone->c_Center) + float(tone->c_Shift / 127.0)); - //freq = note / float(float(tone->c_Center) + float(tone->c_Shift / 127.0)); - - //freq = freq + 0.5; - //freq = float(note) / float(float(tone->c_Center)); - - - AliveAudio::currentSampleIndex++; - } - } - } -} -void AliveAudio::NoteOn(int program, int note, char velocity, int trackID , float trackDelay, float masterValMulti) -{ - NoteOn(program, note, velocity, 0, trackID, trackDelay, masterValMulti); -} - -// sets the volume and pan for all voices on a given track. -// since we are probably updating the volume of a sequence -// we will set a volume multiplier that will be used on the -// sequences voices volume. -void AliveAudio::SetVolume(int trackID, s32 volLeft, s32 volRight) -{ - for (auto voice : m_Voices) - { - if (voice->i_TrackID == trackID) - { - voice->f_VelocityMulti = double(std::min(volLeft, volRight)) / double(127); - voice->f_Pan = (float(volRight) / float(volLeft)) - 1; - } - } -} - -void AliveAudio::NoteOff(int program, int note, int trackID ) -{ - for (auto voice : m_Voices) - { - if (voice->i_Note == note && voice->i_Program == program && voice->i_TrackID == trackID) - { - voice->b_NoteOn = false; - } - } -} -void AliveAudio::NoteOffDelay(int program, int note, int trackID , float trackDelay) -{ - for (auto voice : m_Voices) - { - if (voice->i_Note == note && voice->i_Program == program && voice->i_TrackID == trackID && voice->f_TrackDelay < trackDelay && voice->f_NoteOffDelay <= 0) - { - voice->m_UsesNoteOffDelay = true; - voice->f_NoteOffDelay = trackDelay; - } - } -} -void AliveAudio::DebugPlayFirstToneSample(int program, int tone) -{ - program; - tone; -} -void AliveAudio::LockNotes() -{ - voiceListMutex.lock(); -} -void AliveAudio::UnlockNotes() -{ - voiceListMutex.unlock(); -} - -void AliveAudio::ClearAllVoices(bool forceKill) -{ - std::vector deadVoices; - - for (auto voice : AliveAudio::m_Voices) - { - if (forceKill) - { - deadVoices.push_back(voice); - } - else - { - voice->b_NoteOn = false; // Send a note off to all of the notes though. - if (voice->f_SampleOffset == 0) // Let the voices that are CURRENTLY playing play. - { - deadVoices.push_back(voice); - } - } - } - - for (auto obj : deadVoices) - { - delete obj; - - AliveAudio::m_Voices.erase(std::remove(AliveAudio::m_Voices.begin(), AliveAudio::m_Voices.end(), obj), AliveAudio::m_Voices.end()); - } -} - -void AliveAudio::ClearAllTrackVoices(int trackID, bool forceKill) -{ - std::vector deadVoices; - - for (auto voice : AliveAudio::m_Voices) - { - if (forceKill) - { - //voice->b_NoteOn = false; - //voice->ActiveReleaseLevel = 1; - if (voice->i_TrackID == trackID) // Kill the voices no matter what. Cuts of any sounds = Ugly sound - { - deadVoices.push_back(voice); - } - } - else if (voice->i_TrackID == trackID) - { - voice->b_NoteOn = false; // Send a note off to all of the notes though. - if (voice->f_SampleOffset == 0) // Let the voices that are CURRENTLY playing play. - { - deadVoices.push_back(voice); - } - } - } - - for (auto obj : deadVoices) - { - delete obj; - AliveAudio::m_Voices.erase(std::remove(AliveAudio::m_Voices.begin(), AliveAudio::m_Voices.end(), obj), AliveAudio::m_Voices.end()); - } -} - -void CleanVoices() -{ - std::vector deadVoices; - - for (auto voice : AliveAudio::m_Voices) - { - if (voice->b_Dead) - { - deadVoices.push_back(voice); - } - } - - for (auto obj : deadVoices) - { - delete obj; - - AliveAudio::m_Voices.erase(std::remove(AliveAudio::m_Voices.begin(), AliveAudio::m_Voices.end(), obj), AliveAudio::m_Voices.end()); - } -} - -void AliveRenderAudio(float* AudioStream, int StreamLength) -{ - AudioStream; - StreamLength; - - static float tick = 0; - static int note = 0; - - int voiceCount = AliveAudio::m_Voices.size(); - Voice** rawPointer = AliveAudio::m_Voices.data(); // Real nice speed boost here. - - for (int i = 0; i < StreamLength; i += 2) - { - for (int v = 0; v < voiceCount; v++) - { - Voice* voice = rawPointer[v]; // Raw pointer skips all that vector bottleneck crap - - voice->f_TrackDelay--; - - if (voice->m_UsesNoteOffDelay) - { - voice->f_NoteOffDelay--; - } - - if (voice->m_UsesNoteOffDelay && voice->f_NoteOffDelay <= 0 && voice->b_NoteOn == true) - { - voice->b_NoteOn = false; - } - - if (voice->b_Dead || voice->f_TrackDelay > 0) { - continue; - } - - float centerPan = voice->f_Pan == 0 ? voice->m_Tone->f_Pan : voice->f_Pan; - float leftPan = 1.0f; - float rightPan = 1.0f; - - if (centerPan > 0) - { - leftPan = 1.0f - abs(centerPan); - } - if (centerPan < 0) - { - rightPan = 1.0f - abs(centerPan); - } - float s = voice->GetSample(); - - float leftSample = s * leftPan; - float rightSample = s * rightPan; - - SDL_MixAudioFormat((Uint8*) (AudioStream + i), (const Uint8*) &leftSample, AUDIO_F32, sizeof(float), 37); // Left Channel - SDL_MixAudioFormat((Uint8*) (AudioStream + i + 1), (const Uint8*) &rightSample, AUDIO_F32, sizeof(float), 37); // Right Channel - } - - AliveAudio::currentSampleIndex++; - } - - CleanVoices(); -} - - -void AliveAudioSetEQ(float cutoff) -{ - if (AliveAudio::AliveAudioEQBiQuad != nullptr) - delete AliveAudio::AliveAudioEQBiQuad; - - AliveAudio::AliveAudioEQBiQuad = BiQuad_new(PEQ, 8, cutoff, AliveAudioSampleRate, 1); -} - -void AliveEQEffect(float* stream, int len) -{ - if (AliveAudio::AliveAudioEQBiQuad == nullptr) - { - AliveAudioSetEQ(20500); - } - - for (int i = 0; i < len; i++) - { - stream[i] = BiQuad(stream[i], AliveAudio::AliveAudioEQBiQuad); - } -} - -void AliveAudioSDLCallback(void* udata, Uint8* stream, int len) -{ - udata; - memset(stream, 0, len); - - AliveAudio::LockNotes(); - AliveRenderAudio((float*) stream, len / sizeof(float)); - - //if (AliveAudio::EQEnabled) - AliveEQEffect((float*) stream, len / sizeof(float)); - - AliveAudio::UnlockNotes(); -} - -} // namespace psx \ No newline at end of file diff --git a/Source/AliveLibCommon/audio/mixer/AliveAudio.hpp b/Source/AliveLibCommon/audio/mixer/AliveAudio.hpp deleted file mode 100644 index bbf9dac0d..000000000 --- a/Source/AliveLibCommon/audio/mixer/AliveAudio.hpp +++ /dev/null @@ -1,56 +0,0 @@ -#pragma once - -#include "SDL.h" - -#include -#include -#include -#include -#include -#include "../Soundbank.hpp" -#include "Voice.hpp" -#include "biquad.hpp" - -namespace psx { - -void AliveInitAudio(); -void AliveAudioSDLCallback(void* udata, Uint8* stream, int len); - -const int AliveAudioSampleRate = 44100; - -class AliveAudio -{ -public: - static Soundbank* m_CurrentSoundbank; - static std::mutex voiceListMutex; - static std::vector m_Voices; - static bool AliveAudio::voiceListLocked; - - static void PlayOneShot(s32 playId, s32 program, s32 note, s32 volume, float pitch, s32 pitch_min, s32 pitch_max); - static void PlayOneShot(s32 playId, s32 program, s32 note, s32 volLeft, s32 volRight, float pitch, s32 pitch_min, s32 pitch_max); - - static void NoteOn(int program, int note, char velocity, float pitch = 0, int trackID = 0, float trackDelay = 0, float masterVolMulti = 1); - static void NoteOn(int program, int note, char velocity, int trackID = 0, float trackDelay = 0, float masterValMulti = 1); - - static void NoteOff(int program, int note, int trackID = 0); - static void NoteOffDelay(int program, int note, int trackID = 0, float trackDelay = 0); - static void SetVolume(int trackID, s32 volLeft, s32 volRight); - - static void DebugPlayFirstToneSample(int program, int tone); - - static void LockNotes(); - static void UnlockNotes(); - - static void CleanVoices(); - static void ClearAllVoices(bool forceKill = true); - static void ClearAllTrackVoices(int trackID, bool forceKill = false); - - static long long currentSampleIndex; - static biquad* AliveAudioEQBiQuad; - -private: - static void AliveAudioSetEQ(float cutoff); - static void AliveEQEffect(float* stream, int len); -}; - -} // namespace psx \ No newline at end of file diff --git a/Source/AliveLibCommon/audio/mixer/AudioStream.cpp b/Source/AliveLibCommon/audio/mixer/AudioStream.cpp deleted file mode 100644 index b2fb5b0a1..000000000 --- a/Source/AliveLibCommon/audio/mixer/AudioStream.cpp +++ /dev/null @@ -1,19 +0,0 @@ -#pragma once - -#include "AudioStream.hpp" - -// Constructor will prepare SDL and SDL callback -// Will need to maintain a buffer that gets copied to SDL -// Will probably have to be some 20ms behind so SDL -// will always have samples available - -void AudioStream::BeginWrite(s16** buffer_ptr, u32* num_frames) -{ - buffer_ptr; - num_frames; -} - -void AudioStream::EndWrite(u32 num_frames) -{ - num_frames; -} \ No newline at end of file diff --git a/Source/AliveLibCommon/audio/mixer/AudioStream.hpp b/Source/AliveLibCommon/audio/mixer/AudioStream.hpp deleted file mode 100644 index 1b50a8f7f..000000000 --- a/Source/AliveLibCommon/audio/mixer/AudioStream.hpp +++ /dev/null @@ -1,8 +0,0 @@ -#pragma once - -class AudioStream -{ -public: - void BeginWrite(s16** buffer_ptr, u32* num_frames); - void EndWrite(u32 num_frames); -}; \ No newline at end of file diff --git a/Source/AliveLibCommon/audio/mixer/SPU.cpp b/Source/AliveLibCommon/audio/mixer/SPU.cpp deleted file mode 100644 index 2776f63ec..000000000 --- a/Source/AliveLibCommon/audio/mixer/SPU.cpp +++ /dev/null @@ -1,2582 +0,0 @@ -// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin -// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) - -#include "spu.hpp" -#include "cdrom.h" -#include "common/bitfield.h" -#include "common/fifo_queue.h" -#include "common/file_system.h" -#include "common/log.h" -#include "dma.h" -#include "host.h" -#include "imgui.h" -#include "interrupt_controller.h" -#include "system.h" -#include "util/audio_stream.h" -#include "util/state_wrapper.h" -#include "util/wav_writer.h" -#include -Log_SetChannel(SPU); - -// Enable to dump all voices of the SPU audio individually. -// #define SPU_DUMP_ALL_VOICES 1 - -ALWAYS_INLINE static constexpr s32 Clamp16(s32 value) -{ - return (value < -0x8000) ? -0x8000 : (value > 0x7FFF) ? 0x7FFF - : value; -} - -ALWAYS_INLINE static constexpr s32 ApplyVolume(s32 sample, s16 volume) -{ - return (sample * s32(volume)) >> 15; -} - -namespace SPU { -enum : u32 -{ - SPU_BASE = 0x1F801C00, - NUM_CHANNELS = 2, - NUM_VOICES = 24, - NUM_VOICE_REGISTERS = 8, - VOICE_ADDRESS_SHIFT = 3, - NUM_SAMPLES_PER_ADPCM_BLOCK = 28, - NUM_SAMPLES_FROM_LAST_ADPCM_BLOCK = 3, - SYSCLK_TICKS_PER_SPU_TICK = System::MASTER_CLOCK / SAMPLE_RATE, // 0x300 - CAPTURE_BUFFER_SIZE_PER_CHANNEL = 0x400, - MINIMUM_TICKS_BETWEEN_KEY_ON_OFF = 2, - NUM_REVERB_REGS = 32, - FIFO_SIZE_IN_HALFWORDS = 32 -}; -enum : s16 -{ - ENVELOPE_MIN_VOLUME = 0, - ENVELOPE_MAX_VOLUME = 0x7FFF -}; -enum : TickCount -{ - TRANSFER_TICKS_PER_HALFWORD = 16 -}; - -enum class RAMTransferMode : u8 -{ - Stopped = 0, - ManualWrite = 1, - DMAWrite = 2, - DMARead = 3 -}; - -union SPUCNT -{ - u16 bits; - - BitField enable; - BitField mute_n; - BitField noise_clock; - BitField reverb_master_enable; - BitField irq9_enable; - BitField ram_transfer_mode; - BitField external_audio_reverb; - BitField cd_audio_reverb; - BitField external_audio_enable; - BitField cd_audio_enable; - - BitField mode; -}; - -union SPUSTAT -{ - u16 bits; - - BitField second_half_capture_buffer; - BitField transfer_busy; - BitField dma_write_request; - BitField dma_read_request; - BitField dma_request; - BitField irq9_flag; - BitField mode; -}; - -union TransferControl -{ - u16 bits; - - BitField mode; -}; - -union ADSRRegister -{ - u32 bits; - struct - { - u16 bits_low; - u16 bits_high; - }; - - BitField sustain_level; - BitField decay_rate_shr2; - BitField attack_rate; - BitField attack_exponential; - - BitField release_rate_shr2; - BitField release_exponential; - BitField sustain_rate; - BitField sustain_direction_decrease; - BitField sustain_exponential; -}; - -union VolumeRegister -{ - u16 bits; - - BitField sweep_mode; - BitField fixed_volume_shr1; // divided by 2 - - BitField sweep_exponential; - BitField sweep_direction_decrease; - BitField sweep_phase_negative; - BitField sweep_rate; -}; - -// organized so we can replace this with a u16 array in the future -union VoiceRegisters -{ - u16 index[NUM_VOICE_REGISTERS]; - - struct - { - VolumeRegister volume_left; - VolumeRegister volume_right; - - u16 adpcm_sample_rate; // VxPitch - u16 adpcm_start_address; // multiply by 8 - - ADSRRegister adsr; - s16 adsr_volume; - - u16 adpcm_repeat_address; // multiply by 8 - }; -}; - -union VoiceCounter -{ - // promoted to u32 because of overflow - u32 bits; - - BitField interpolation_index; - BitField sample_index; -}; - -union ADPCMFlags -{ - u8 bits; - - BitField loop_end; - BitField loop_repeat; - BitField loop_start; -}; - -struct ADPCMBlock -{ - union - { - u8 bits; - - BitField shift; - BitField filter; - } shift_filter; - ADPCMFlags flags; - u8 data[NUM_SAMPLES_PER_ADPCM_BLOCK / 2]; - - // For both 4bit and 8bit ADPCM, reserved shift values 13..15 will act same as shift=9). - u8 GetShift() const - { - const u8 shift = shift_filter.shift; - return (shift > 12) ? 9 : shift; - } - - u8 GetFilter() const - { - return std::min(shift_filter.filter, 4); - } - - u8 GetNibble(u32 index) const - { - return (data[index / 2] >> ((index % 2) * 4)) & 0x0F; - } -}; - -struct VolumeEnvelope -{ - s32 counter; - u8 rate; - bool decreasing; - bool exponential; - - void Reset(u8 rate_, bool decreasing_, bool exponential_); - s16 Tick(s16 current_level); -}; - -struct VolumeSweep -{ - VolumeEnvelope envelope; - bool envelope_active; - s16 current_level; - - void Reset(VolumeRegister reg); - void Tick(); -}; - -enum class ADSRPhase : u8 -{ - Off = 0, - Attack = 1, - Decay = 2, - Sustain = 3, - Release = 4 -}; - -struct Voice -{ - u16 current_address; - VoiceRegisters regs; - VoiceCounter counter; - ADPCMFlags current_block_flags; - bool is_first_block; - std::array current_block_samples; - std::array adpcm_last_samples; - s32 last_volume; - - VolumeSweep left_volume; - VolumeSweep right_volume; - - VolumeEnvelope adsr_envelope; - ADSRPhase adsr_phase; - s16 adsr_target; - bool has_samples; - bool ignore_loop_address; - - bool IsOn() const - { - return adsr_phase != ADSRPhase::Off; - } - - void KeyOn(); - void KeyOff(); - void ForceOff(); - - void DecodeBlock(const ADPCMBlock& block); - s32 Interpolate() const; - - // Switches to the specified phase, filling in target. - void UpdateADSREnvelope(); - - // Updates the ADSR volume/phase. - void TickADSR(); -}; - -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]; - }; - - u16 rev[NUM_REVERB_REGS]; - }; -}; - -static ADSRPhase GetNextADSRPhase(ADSRPhase phase); - -bool IsVoiceReverbEnabled(u32 i); -bool IsVoiceNoiseEnabled(u32 i); -bool IsPitchModulationEnabled(u32 i); -s16 GetVoiceNoiseLevel(); - -u16 ReadVoiceRegister(u32 offset); -void WriteVoiceRegister(u32 offset, u16 value); - -static bool IsRAMIRQTriggerable(); -static bool CheckRAMIRQ(u32 address); -static void TriggerRAMIRQ(); -static void CheckForLateRAMIRQs(); - -static void WriteToCaptureBuffer(u32 index, s16 value); -static void IncrementCaptureBufferPosition(); - -static void ReadADPCMBlock(u16 address, ADPCMBlock* block); -static std::tuple SampleVoice(u32 voice_index); - -static void UpdateNoise(); - -static u32 ReverbMemoryAddress(u32 address); -static s16 ReverbRead(u32 address, s32 offset = 0); -static void ReverbWrite(u32 address, s16 data); -static void ProcessReverb(s16 left_in, s16 right_in, s32* left_out, s32* right_out); - -static void Execute(void* param, TickCount ticks, TickCount ticks_late); -static void UpdateEventInterval(); - -static void ExecuteFIFOWriteToRAM(TickCount& ticks); -static void ExecuteFIFOReadFromRAM(TickCount& ticks); -static void ExecuteTransfer(void* param, TickCount ticks, TickCount ticks_late); -static void ManualTransferWrite(u16 value); -static void UpdateTransferEvent(); -static void UpdateDMARequest(); - -static void CreateOutputStream(); - -static std::unique_ptr s_tick_event; -static std::unique_ptr s_transfer_event; -static std::unique_ptr s_dump_writer; -static std::unique_ptr s_audio_stream; -static std::unique_ptr s_null_audio_stream; -static bool s_audio_output_muted = false; - -static TickCount s_ticks_carry = 0; -static TickCount s_cpu_ticks_per_spu_tick = 0; -static TickCount s_cpu_tick_divider = 0; - -static SPUCNT s_SPUCNT = {}; -static SPUSTAT s_SPUSTAT = {}; - -static TransferControl s_transfer_control = {}; -static u16 s_transfer_address_reg = 0; -static u32 s_transfer_address = 0; - -static u16 s_irq_address = 0; -static u16 s_capture_buffer_position = 0; - -static VolumeRegister s_main_volume_left_reg = {}; -static VolumeRegister s_main_volume_right_reg = {}; -static VolumeSweep s_main_volume_left = {}; -static VolumeSweep s_main_volume_right = {}; - -static s16 s_cd_audio_volume_left = 0; -static s16 s_cd_audio_volume_right = 0; - -static s16 s_external_volume_left = 0; -static s16 s_external_volume_right = 0; - -static u32 s_key_on_register = 0; -static u32 s_key_off_register = 0; -static u32 s_endx_register = 0; -static u32 s_pitch_modulation_enable_register = 0; - -static u32 s_noise_mode_register = 0; -static u32 s_noise_count = 0; -static u32 s_noise_level = 0; - -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; - -static std::array s_voices{}; - -static InlineFIFOQueue s_transfer_fifo; - -static std::array s_ram{}; - -#ifdef SPU_DUMP_ALL_VOICES -// +1 for reverb output -static std::array, NUM_VOICES + 1> s_voice_dump_writers; -#endif -} // namespace SPU - -void SPU::Initialize() -{ - // (X * D) / N / 768 -> (X * D) / (N * 768) - s_cpu_ticks_per_spu_tick = System::ScaleTicksToOverclock(SYSCLK_TICKS_PER_SPU_TICK); - s_cpu_tick_divider = static_cast(g_settings.cpu_overclock_numerator * SYSCLK_TICKS_PER_SPU_TICK); - s_tick_event = TimingEvents::CreateTimingEvent("SPU Sample", s_cpu_ticks_per_spu_tick, s_cpu_ticks_per_spu_tick, - &SPU::Execute, nullptr, false); - s_transfer_event = TimingEvents::CreateTimingEvent( - "SPU Transfer", TRANSFER_TICKS_PER_HALFWORD, TRANSFER_TICKS_PER_HALFWORD, &SPU::ExecuteTransfer, nullptr, false); - s_null_audio_stream = AudioStream::CreateNullStream(SAMPLE_RATE, NUM_CHANNELS, g_settings.audio_buffer_ms); - - CreateOutputStream(); - Reset(); -} - -void SPU::CreateOutputStream() -{ - Log_InfoPrintf( - "Creating '%s' audio stream, sample rate = %u, channels = %u, buffer = %u, latency = %u, stretching = %s", - Settings::GetAudioBackendName(g_settings.audio_backend), SAMPLE_RATE, NUM_CHANNELS, g_settings.audio_buffer_ms, - g_settings.audio_output_latency_ms, AudioStream::GetStretchModeName(g_settings.audio_stretch_mode)); - - s_audio_stream = Host::CreateAudioStream(g_settings.audio_backend, SAMPLE_RATE, NUM_CHANNELS, g_settings.audio_buffer_ms, - g_settings.audio_output_latency_ms, g_settings.audio_stretch_mode); - if (!s_audio_stream) - { - Host::ReportErrorAsync("Error", "Failed to create or configure audio stream, falling back to null output."); - s_audio_stream.reset(); - s_audio_stream = AudioStream::CreateNullStream(SAMPLE_RATE, NUM_CHANNELS, g_settings.audio_buffer_ms); - } - - s_audio_stream->SetOutputVolume(System::GetAudioOutputVolume()); - s_audio_stream->SetPaused(System::IsPaused()); -} - -void SPU::RecreateOutputStream() -{ - s_audio_stream.reset(); - CreateOutputStream(); -} - -void SPU::CPUClockChanged() -{ - // (X * D) / N / 768 -> (X * D) / (N * 768) - s_cpu_ticks_per_spu_tick = System::ScaleTicksToOverclock(SYSCLK_TICKS_PER_SPU_TICK); - s_cpu_tick_divider = static_cast(g_settings.cpu_overclock_numerator * SYSCLK_TICKS_PER_SPU_TICK); - s_ticks_carry = 0; - UpdateEventInterval(); -} - -void SPU::Shutdown() -{ - StopDumpingAudio(); - s_tick_event.reset(); - s_transfer_event.reset(); - s_audio_stream.reset(); -} - -void SPU::Reset() -{ - s_ticks_carry = 0; - - s_SPUCNT.bits = 0; - s_SPUSTAT.bits = 0; - s_transfer_address = 0; - s_transfer_address_reg = 0; - s_irq_address = 0; - s_capture_buffer_position = 0; - s_main_volume_left_reg.bits = 0; - s_main_volume_right_reg.bits = 0; - s_main_volume_left = {}; - s_main_volume_right = {}; - s_cd_audio_volume_left = 0; - s_cd_audio_volume_right = 0; - s_external_volume_left = 0; - s_external_volume_right = 0; - s_key_on_register = 0; - s_key_off_register = 0; - s_endx_register = 0; - s_pitch_modulation_enable_register = 0; - - s_noise_mode_register = 0; - s_noise_count = 0; - s_noise_level = 1; - - s_reverb_on_register = 0; - s_reverb_registers = {}; - s_reverb_registers.mBASE = 0; - s_reverb_base_address = s_reverb_current_address = ZeroExtend32(s_reverb_registers.mBASE) << 2; - s_reverb_downsample_buffer = {}; - s_reverb_upsample_buffer = {}; - s_reverb_resample_buffer_position = 0; - - for (u32 i = 0; i < NUM_VOICES; i++) - { - Voice& v = s_voices[i]; - v.current_address = 0; - std::fill_n(v.regs.index, NUM_VOICE_REGISTERS, u16(0)); - v.counter.bits = 0; - v.current_block_flags.bits = 0; - v.is_first_block = 0; - v.current_block_samples.fill(s16(0)); - v.adpcm_last_samples.fill(s32(0)); - v.adsr_envelope.Reset(0, false, false); - v.adsr_phase = ADSRPhase::Off; - v.adsr_target = 0; - v.has_samples = false; - v.ignore_loop_address = false; - } - - s_transfer_fifo.Clear(); - s_transfer_event->Deactivate(); - s_ram.fill(0); - UpdateEventInterval(); -} - -bool SPU::DoState(StateWrapper& sw) -{ - sw.Do(&s_ticks_carry); - sw.Do(&s_SPUCNT.bits); - sw.Do(&s_SPUSTAT.bits); - sw.Do(&s_transfer_control.bits); - sw.Do(&s_transfer_address); - sw.Do(&s_transfer_address_reg); - sw.Do(&s_irq_address); - sw.Do(&s_capture_buffer_position); - sw.Do(&s_main_volume_left_reg.bits); - sw.Do(&s_main_volume_right_reg.bits); - sw.DoPOD(&s_main_volume_left); - sw.DoPOD(&s_main_volume_right); - sw.Do(&s_cd_audio_volume_left); - sw.Do(&s_cd_audio_volume_right); - sw.Do(&s_external_volume_left); - sw.Do(&s_external_volume_right); - sw.Do(&s_key_on_register); - sw.Do(&s_key_off_register); - sw.Do(&s_endx_register); - sw.Do(&s_pitch_modulation_enable_register); - sw.Do(&s_noise_mode_register); - sw.Do(&s_noise_count); - sw.Do(&s_noise_level); - sw.Do(&s_reverb_on_register); - sw.Do(&s_reverb_base_address); - sw.Do(&s_reverb_current_address); - sw.Do(&s_reverb_registers.vLOUT); - sw.Do(&s_reverb_registers.vROUT); - sw.Do(&s_reverb_registers.mBASE); - sw.DoArray(s_reverb_registers.rev, NUM_REVERB_REGS); - for (u32 i = 0; i < 2; i++) - sw.DoArray(s_reverb_downsample_buffer.data(), s_reverb_downsample_buffer.size()); - for (u32 i = 0; i < 2; i++) - sw.DoArray(s_reverb_upsample_buffer.data(), s_reverb_upsample_buffer.size()); - sw.Do(&s_reverb_resample_buffer_position); - for (u32 i = 0; i < NUM_VOICES; i++) - { - Voice& v = s_voices[i]; - sw.Do(&v.current_address); - sw.DoArray(v.regs.index, NUM_VOICE_REGISTERS); - sw.Do(&v.counter.bits); - sw.Do(&v.current_block_flags.bits); - sw.DoEx(&v.is_first_block, 47, false); - sw.DoArray(&v.current_block_samples[NUM_SAMPLES_FROM_LAST_ADPCM_BLOCK], NUM_SAMPLES_PER_ADPCM_BLOCK); - sw.DoArray(&v.current_block_samples[0], NUM_SAMPLES_FROM_LAST_ADPCM_BLOCK); - sw.Do(&v.adpcm_last_samples); - sw.Do(&v.last_volume); - sw.DoPOD(&v.left_volume); - sw.DoPOD(&v.right_volume); - sw.DoPOD(&v.adsr_envelope); - sw.Do(&v.adsr_phase); - sw.Do(&v.adsr_target); - sw.Do(&v.has_samples); - sw.Do(&v.ignore_loop_address); - } - - sw.Do(&s_transfer_fifo); - sw.DoBytes(s_ram.data(), RAM_SIZE); - - if (sw.IsReading()) - { - UpdateEventInterval(); - UpdateTransferEvent(); - } - - return !sw.HasError(); -} - -u16 SPU::ReadRegister(u32 offset) -{ - switch (offset) - { - case 0x1F801D80 - SPU_BASE: - return s_main_volume_left_reg.bits; - - case 0x1F801D82 - SPU_BASE: - return s_main_volume_right_reg.bits; - - case 0x1F801D84 - SPU_BASE: - return s_reverb_registers.vLOUT; - - case 0x1F801D86 - SPU_BASE: - return s_reverb_registers.vROUT; - - case 0x1F801D88 - SPU_BASE: - return Truncate16(s_key_on_register); - - case 0x1F801D8A - SPU_BASE: - return Truncate16(s_key_on_register >> 16); - - case 0x1F801D8C - SPU_BASE: - return Truncate16(s_key_off_register); - - case 0x1F801D8E - SPU_BASE: - return Truncate16(s_key_off_register >> 16); - - case 0x1F801D90 - SPU_BASE: - return Truncate16(s_pitch_modulation_enable_register); - - case 0x1F801D92 - SPU_BASE: - return Truncate16(s_pitch_modulation_enable_register >> 16); - - case 0x1F801D94 - SPU_BASE: - return Truncate16(s_noise_mode_register); - - case 0x1F801D96 - SPU_BASE: - return Truncate16(s_noise_mode_register >> 16); - - case 0x1F801D98 - SPU_BASE: - return Truncate16(s_reverb_on_register); - - case 0x1F801D9A - SPU_BASE: - return Truncate16(s_reverb_on_register >> 16); - - case 0x1F801D9C - SPU_BASE: - return Truncate16(s_endx_register); - - case 0x1F801D9E - SPU_BASE: - return Truncate16(s_endx_register >> 16); - - case 0x1F801DA2 - SPU_BASE: - return s_reverb_registers.mBASE; - - case 0x1F801DA4 - SPU_BASE: - Log_TracePrintf("SPU IRQ address -> 0x%04X", ZeroExtend32(s_irq_address)); - return s_irq_address; - - case 0x1F801DA6 - SPU_BASE: - Log_TracePrintf("SPU transfer address register -> 0x%04X", ZeroExtend32(s_transfer_address_reg)); - return s_transfer_address_reg; - - case 0x1F801DA8 - SPU_BASE: - Log_TracePrintf("SPU transfer data register read"); - return UINT16_C(0xFFFF); - - case 0x1F801DAA - SPU_BASE: - Log_TracePrintf("SPU control register -> 0x%04X", ZeroExtend32(s_SPUCNT.bits)); - return s_SPUCNT.bits; - - case 0x1F801DAC - SPU_BASE: - Log_TracePrintf("SPU transfer control register -> 0x%04X", ZeroExtend32(s_transfer_control.bits)); - return s_transfer_control.bits; - - case 0x1F801DAE - SPU_BASE: - GeneratePendingSamples(); - Log_TracePrintf("SPU status register -> 0x%04X", ZeroExtend32(s_SPUCNT.bits)); - return s_SPUSTAT.bits; - - case 0x1F801DB0 - SPU_BASE: - return s_cd_audio_volume_left; - - case 0x1F801DB2 - SPU_BASE: - return s_cd_audio_volume_right; - - case 0x1F801DB4 - SPU_BASE: - return s_external_volume_left; - - case 0x1F801DB6 - SPU_BASE: - return s_external_volume_right; - - case 0x1F801DB8 - SPU_BASE: - GeneratePendingSamples(); - return s_main_volume_left.current_level; - - case 0x1F801DBA - SPU_BASE: - GeneratePendingSamples(); - return s_main_volume_right.current_level; - - default: - { - if (offset < (0x1F801D80 - SPU_BASE)) - return ReadVoiceRegister(offset); - - if (offset >= (0x1F801DC0 - SPU_BASE) && offset < (0x1F801E00 - SPU_BASE)) - return s_reverb_registers.rev[(offset - (0x1F801DC0 - SPU_BASE)) / 2]; - - if (offset >= (0x1F801E00 - SPU_BASE) && offset < (0x1F801E60 - SPU_BASE)) - { - const u32 voice_index = (offset - (0x1F801E00 - SPU_BASE)) / 4; - GeneratePendingSamples(); - if (offset & 0x02) - return s_voices[voice_index].left_volume.current_level; - else - return s_voices[voice_index].right_volume.current_level; - } - - Log_DevPrintf("Unknown SPU register read: offset 0x%X (address 0x%08X)", offset, offset | SPU_BASE); - return UINT16_C(0xFFFF); - } - } -} - -void SPU::WriteRegister(u32 offset, u16 value) -{ - switch (offset) - { - case 0x1F801D80 - SPU_BASE: - { - Log_DebugPrintf("SPU main volume left <- 0x%04X", ZeroExtend32(value)); - GeneratePendingSamples(); - s_main_volume_left_reg.bits = value; - s_main_volume_left.Reset(s_main_volume_left_reg); - return; - } - - case 0x1F801D82 - SPU_BASE: - { - Log_DebugPrintf("SPU main volume right <- 0x%04X", ZeroExtend32(value)); - GeneratePendingSamples(); - s_main_volume_right_reg.bits = value; - s_main_volume_right.Reset(s_main_volume_right_reg); - return; - } - - case 0x1F801D84 - SPU_BASE: - { - Log_DebugPrintf("SPU reverb output volume left <- 0x%04X", ZeroExtend32(value)); - GeneratePendingSamples(); - s_reverb_registers.vLOUT = value; - return; - } - - case 0x1F801D86 - SPU_BASE: - { - Log_DebugPrintf("SPU reverb output volume right <- 0x%04X", ZeroExtend32(value)); - GeneratePendingSamples(); - s_reverb_registers.vROUT = value; - return; - } - - case 0x1F801D88 - SPU_BASE: - { - Log_DebugPrintf("SPU key on low <- 0x%04X", ZeroExtend32(value)); - GeneratePendingSamples(); - s_key_on_register = (s_key_on_register & 0xFFFF0000) | ZeroExtend32(value); - } - break; - - case 0x1F801D8A - SPU_BASE: - { - Log_DebugPrintf("SPU key on high <- 0x%04X", ZeroExtend32(value)); - GeneratePendingSamples(); - s_key_on_register = (s_key_on_register & 0x0000FFFF) | (ZeroExtend32(value) << 16); - } - break; - - case 0x1F801D8C - SPU_BASE: - { - Log_DebugPrintf("SPU key off low <- 0x%04X", ZeroExtend32(value)); - GeneratePendingSamples(); - s_key_off_register = (s_key_off_register & 0xFFFF0000) | ZeroExtend32(value); - } - break; - - case 0x1F801D8E - SPU_BASE: - { - Log_DebugPrintf("SPU key off high <- 0x%04X", ZeroExtend32(value)); - GeneratePendingSamples(); - s_key_off_register = (s_key_off_register & 0x0000FFFF) | (ZeroExtend32(value) << 16); - } - break; - - case 0x1F801D90 - SPU_BASE: - { - GeneratePendingSamples(); - s_pitch_modulation_enable_register = (s_pitch_modulation_enable_register & 0xFFFF0000) | ZeroExtend32(value); - Log_DebugPrintf("SPU pitch modulation enable register <- 0x%08X", s_pitch_modulation_enable_register); - } - break; - - case 0x1F801D92 - SPU_BASE: - { - GeneratePendingSamples(); - s_pitch_modulation_enable_register = (s_pitch_modulation_enable_register & 0x0000FFFF) | (ZeroExtend32(value) << 16); - Log_DebugPrintf("SPU pitch modulation enable register <- 0x%08X", s_pitch_modulation_enable_register); - } - break; - - case 0x1F801D94 - SPU_BASE: - { - Log_DebugPrintf("SPU noise mode register <- 0x%04X", ZeroExtend32(value)); - GeneratePendingSamples(); - s_noise_mode_register = (s_noise_mode_register & 0xFFFF0000) | ZeroExtend32(value); - } - break; - - case 0x1F801D96 - SPU_BASE: - { - Log_DebugPrintf("SPU noise mode register <- 0x%04X", ZeroExtend32(value)); - GeneratePendingSamples(); - s_noise_mode_register = (s_noise_mode_register & 0x0000FFFF) | (ZeroExtend32(value) << 16); - } - break; - - case 0x1F801D98 - SPU_BASE: - { - Log_DebugPrintf("SPU reverb on register <- 0x%04X", ZeroExtend32(value)); - GeneratePendingSamples(); - s_reverb_on_register = (s_reverb_on_register & 0xFFFF0000) | ZeroExtend32(value); - } - break; - - case 0x1F801D9A - SPU_BASE: - { - Log_DebugPrintf("SPU reverb on register <- 0x%04X", ZeroExtend32(value)); - GeneratePendingSamples(); - s_reverb_on_register = (s_reverb_on_register & 0x0000FFFF) | (ZeroExtend32(value) << 16); - } - break; - - case 0x1F801DA2 - SPU_BASE: - { - Log_DebugPrintf("SPU reverb base address < 0x%04X", ZeroExtend32(value)); - GeneratePendingSamples(); - s_reverb_registers.mBASE = value; - s_reverb_base_address = ZeroExtend32(value << 2) & 0x3FFFFu; - s_reverb_current_address = s_reverb_base_address; - } - break; - - case 0x1F801DA4 - SPU_BASE: - { - Log_DebugPrintf("SPU IRQ address register <- 0x%04X", ZeroExtend32(value)); - GeneratePendingSamples(); - s_irq_address = value; - - if (IsRAMIRQTriggerable()) - CheckForLateRAMIRQs(); - - return; - } - - case 0x1F801DA6 - SPU_BASE: - { - Log_DebugPrintf("SPU transfer address register <- 0x%04X", ZeroExtend32(value)); - s_transfer_event->InvokeEarly(); - s_transfer_address_reg = value; - s_transfer_address = ZeroExtend32(value) * 8; - if (IsRAMIRQTriggerable() && CheckRAMIRQ(s_transfer_address)) - { - Log_DebugPrintf("Trigger IRQ @ %08X %04X from transfer address reg set", s_transfer_address, - s_transfer_address / 8); - TriggerRAMIRQ(); - } - return; - } - - case 0x1F801DA8 - SPU_BASE: - { - Log_TracePrintf("SPU transfer data register <- 0x%04X (RAM offset 0x%08X)", ZeroExtend32(value), - s_transfer_address); - - ManualTransferWrite(value); - return; - } - - case 0x1F801DAA - SPU_BASE: - { - Log_DebugPrintf("SPU control register <- 0x%04X", ZeroExtend32(value)); - GeneratePendingSamples(); - - const SPUCNT new_value{value}; - if (new_value.ram_transfer_mode != s_SPUCNT.ram_transfer_mode && new_value.ram_transfer_mode == RAMTransferMode::Stopped) - { - // clear the fifo here? - if (!s_transfer_fifo.IsEmpty()) - { - if (s_SPUCNT.ram_transfer_mode == RAMTransferMode::DMAWrite) - { - // I would guess on the console it would gradually write the FIFO out. Hopefully nothing relies on this - // level of timing granularity if we force it all out here. - Log_WarningPrintf("Draining write SPU transfer FIFO with %u bytes left", s_transfer_fifo.GetSize()); - TickCount ticks = std::numeric_limits::max(); - ExecuteFIFOWriteToRAM(ticks); - DebugAssert(s_transfer_fifo.IsEmpty()); - } - else - { - Log_DebugPrintf("Clearing read SPU transfer FIFO with %u bytes left", s_transfer_fifo.GetSize()); - s_transfer_fifo.Clear(); - } - } - } - - if (!new_value.enable && s_SPUCNT.enable) - { - // Mute all voices. - // Interestingly, hardware tests found this seems to happen immediately, not on the next 44100hz cycle. - for (u32 i = 0; i < NUM_VOICES; i++) - s_voices[i].ForceOff(); - } - - s_SPUCNT.bits = new_value.bits; - s_SPUSTAT.mode = s_SPUCNT.mode.GetValue(); - - if (!s_SPUCNT.irq9_enable) - s_SPUSTAT.irq9_flag = false; - else if (IsRAMIRQTriggerable()) - CheckForLateRAMIRQs(); - - UpdateEventInterval(); - UpdateDMARequest(); - UpdateTransferEvent(); - return; - } - - case 0x1F801DAC - SPU_BASE: - { - Log_DebugPrintf("SPU transfer control register <- 0x%04X", ZeroExtend32(value)); - s_transfer_control.bits = value; - return; - } - - case 0x1F801DB0 - SPU_BASE: - { - Log_DebugPrintf("SPU left cd audio register <- 0x%04X", ZeroExtend32(value)); - GeneratePendingSamples(); - s_cd_audio_volume_left = value; - } - break; - - case 0x1F801DB2 - SPU_BASE: - { - Log_DebugPrintf("SPU right cd audio register <- 0x%04X", ZeroExtend32(value)); - GeneratePendingSamples(); - s_cd_audio_volume_right = value; - } - break; - - case 0x1F801DB4 - SPU_BASE: - { - // External volumes aren't used, so don't bother syncing. - Log_DebugPrintf("SPU left external volume register <- 0x%04X", ZeroExtend32(value)); - s_external_volume_left = value; - } - break; - - case 0x1F801DB6 - SPU_BASE: - { - // External volumes aren't used, so don't bother syncing. - Log_DebugPrintf("SPU right external volume register <- 0x%04X", ZeroExtend32(value)); - s_external_volume_right = value; - } - break; - - // read-only registers - case 0x1F801DAE - SPU_BASE: - { - return; - } - - default: - { - if (offset < (0x1F801D80 - SPU_BASE)) - { - WriteVoiceRegister(offset, value); - return; - } - - if (offset >= (0x1F801DC0 - SPU_BASE) && offset < (0x1F801E00 - SPU_BASE)) - { - const u32 reg = (offset - (0x1F801DC0 - SPU_BASE)) / 2; - Log_DebugPrintf("SPU reverb register %u <- 0x%04X", reg, value); - GeneratePendingSamples(); - s_reverb_registers.rev[reg] = value; - return; - } - - Log_DevPrintf("Unknown SPU register write: offset 0x%X (address 0x%08X) value 0x%04X", offset, offset | SPU_BASE, - ZeroExtend32(value)); - return; - } - } -} - -u16 SPU::ReadVoiceRegister(u32 offset) -{ - const u32 reg_index = (offset % 0x10) / 2; //(offset & 0x0F) / 2; - const u32 voice_index = (offset / 0x10); //((offset >> 4) & 0x1F); - Assert(voice_index < 24); - - // ADSR volume needs to be updated when reading. A voice might be off as well, but key on is pending. - const Voice& voice = s_voices[voice_index]; - if (reg_index >= 6 && (voice.IsOn() || s_key_on_register & (1u << voice_index))) - GeneratePendingSamples(); - - Log_TracePrintf("Read voice %u register %u -> 0x%02X", voice_index, reg_index, voice.regs.index[reg_index]); - return voice.regs.index[reg_index]; -} - -void SPU::WriteVoiceRegister(u32 offset, u16 value) -{ - // per-voice registers - const u32 reg_index = (offset % 0x10); - const u32 voice_index = (offset / 0x10); - DebugAssert(voice_index < 24); - - Voice& voice = s_voices[voice_index]; - if (voice.IsOn() || s_key_on_register & (1u << voice_index)) - GeneratePendingSamples(); - - switch (reg_index) - { - case 0x00: // volume left - { - Log_DebugPrintf("SPU voice %u volume left <- 0x%04X", voice_index, value); - voice.regs.volume_left.bits = value; - voice.left_volume.Reset(voice.regs.volume_left); - } - break; - - case 0x02: // volume right - { - Log_DebugPrintf("SPU voice %u volume right <- 0x%04X", voice_index, value); - voice.regs.volume_right.bits = value; - voice.right_volume.Reset(voice.regs.volume_right); - } - break; - - case 0x04: // sample rate - { - Log_DebugPrintf("SPU voice %u ADPCM sample rate <- 0x%04X", voice_index, value); - voice.regs.adpcm_sample_rate = value; - } - break; - - case 0x06: // start address - { - Log_DebugPrintf("SPU voice %u ADPCM start address <- 0x%04X", voice_index, value); - voice.regs.adpcm_start_address = value; - } - break; - - case 0x08: // adsr low - { - Log_DebugPrintf("SPU voice %u ADSR low <- 0x%04X (was 0x%04X)", voice_index, value, voice.regs.adsr.bits_low); - voice.regs.adsr.bits_low = value; - if (voice.IsOn()) - voice.UpdateADSREnvelope(); - } - break; - - case 0x0A: // adsr high - { - Log_DebugPrintf("SPU voice %u ADSR high <- 0x%04X (was 0x%04X)", voice_index, value, voice.regs.adsr.bits_low); - voice.regs.adsr.bits_high = value; - if (voice.IsOn()) - voice.UpdateADSREnvelope(); - } - break; - - case 0x0C: // adsr volume - { - Log_DebugPrintf("SPU voice %u ADSR volume <- 0x%04X (was 0x%04X)", voice_index, value, voice.regs.adsr_volume); - voice.regs.adsr_volume = value; - } - break; - - case 0x0E: // repeat address - { - // There is a short window of time here between the voice being keyed on and the first block finishing decoding - // where setting the repeat address will *NOT* ignore the block/loop start flag. Games sensitive to this are: - // - The Misadventures of Tron Bonne - // - Re-Loaded - The Hardcore Sequel - // - Valkyrie Profile - - const bool ignore_loop_address = voice.IsOn() && !voice.is_first_block; - Log_DebugPrintf("SPU voice %u ADPCM repeat address <- 0x%04X", voice_index, value); - voice.regs.adpcm_repeat_address = value; - voice.ignore_loop_address |= ignore_loop_address; - - if (!ignore_loop_address) - { - Log_DevPrintf("Not ignoring loop address, the ADPCM repeat address of 0x%04X for voice %u will be overwritten", - value, voice_index); - } - } - break; - - default: - { - Log_ErrorPrintf("Unknown SPU voice %u register write: offset 0x%X (address 0x%08X) value 0x%04X", offset, - voice_index, offset | SPU_BASE, ZeroExtend32(value)); - } - break; - } -} - -bool SPU::IsVoiceReverbEnabled(u32 i) -{ - return ConvertToBoolUnchecked((s_reverb_on_register >> i) & u32(1)); -} - -bool SPU::IsVoiceNoiseEnabled(u32 i) -{ - return ConvertToBoolUnchecked((s_noise_mode_register >> i) & u32(1)); -} - -bool SPU::IsPitchModulationEnabled(u32 i) -{ - return ((i > 0) && ConvertToBoolUnchecked((s_pitch_modulation_enable_register >> i) & u32(1))); -} - -s16 SPU::GetVoiceNoiseLevel() -{ - return static_cast(static_cast(s_noise_level)); -} - -bool SPU::IsRAMIRQTriggerable() -{ - return s_SPUCNT.irq9_enable && !s_SPUSTAT.irq9_flag; -} - -bool SPU::CheckRAMIRQ(u32 address) -{ - return ((ZeroExtend32(s_irq_address) * 8) == address); -} - -void SPU::TriggerRAMIRQ() -{ - DebugAssert(IsRAMIRQTriggerable()); - s_SPUSTAT.irq9_flag = true; - InterruptController::InterruptRequest(InterruptController::IRQ::SPU); -} - -void SPU::CheckForLateRAMIRQs() -{ - if (CheckRAMIRQ(s_transfer_address)) - { - Log_DebugPrintf("Trigger IRQ @ %08X %04X from late transfer", s_transfer_address, s_transfer_address / 8); - TriggerRAMIRQ(); - return; - } - - for (u32 i = 0; i < NUM_VOICES; i++) - { - // we skip voices which haven't started this block yet - because they'll check - // the next time they're sampled, and the delay might be important. - const Voice& v = s_voices[i]; - if (!v.has_samples) - continue; - - const u32 address = v.current_address * 8; - if (CheckRAMIRQ(address) || CheckRAMIRQ((address + 8) & RAM_MASK)) - { - Log_DebugPrintf("Trigger IRQ @ %08X %04X from late", address, address / 8); - TriggerRAMIRQ(); - return; - } - } -} - -void SPU::WriteToCaptureBuffer(u32 index, s16 value) -{ - const u32 ram_address = (index * CAPTURE_BUFFER_SIZE_PER_CHANNEL) | ZeroExtend16(s_capture_buffer_position); - // Log_DebugPrintf("write to capture buffer %u (0x%08X) <- 0x%04X", index, ram_address, u16(value)); - std::memcpy(&s_ram[ram_address], &value, sizeof(value)); - if (IsRAMIRQTriggerable() && CheckRAMIRQ(ram_address)) - { - Log_DebugPrintf("Trigger IRQ @ %08X %04X from capture buffer", ram_address, ram_address / 8); - TriggerRAMIRQ(); - } -} - -void SPU::IncrementCaptureBufferPosition() -{ - s_capture_buffer_position += sizeof(s16); - s_capture_buffer_position %= CAPTURE_BUFFER_SIZE_PER_CHANNEL; - s_SPUSTAT.second_half_capture_buffer = s_capture_buffer_position >= (CAPTURE_BUFFER_SIZE_PER_CHANNEL / 2); -} - -ALWAYS_INLINE_RELEASE void SPU::ExecuteFIFOReadFromRAM(TickCount& ticks) -{ - while (ticks > 0 && !s_transfer_fifo.IsFull()) - { - u16 value; - std::memcpy(&value, &s_ram[s_transfer_address], sizeof(u16)); - s_transfer_address = (s_transfer_address + sizeof(u16)) & RAM_MASK; - s_transfer_fifo.Push(value); - ticks -= TRANSFER_TICKS_PER_HALFWORD; - - if (IsRAMIRQTriggerable() && CheckRAMIRQ(s_transfer_address)) - { - Log_DebugPrintf("Trigger IRQ @ %08X %04X from transfer read", s_transfer_address, s_transfer_address / 8); - TriggerRAMIRQ(); - } - } -} - -ALWAYS_INLINE_RELEASE void SPU::ExecuteFIFOWriteToRAM(TickCount& ticks) -{ - while (ticks > 0 && !s_transfer_fifo.IsEmpty()) - { - u16 value = s_transfer_fifo.Pop(); - std::memcpy(&s_ram[s_transfer_address], &value, sizeof(u16)); - s_transfer_address = (s_transfer_address + sizeof(u16)) & RAM_MASK; - ticks -= TRANSFER_TICKS_PER_HALFWORD; - - if (IsRAMIRQTriggerable() && CheckRAMIRQ(s_transfer_address)) - { - Log_DebugPrintf("Trigger IRQ @ %08X %04X from transfer write", s_transfer_address, s_transfer_address / 8); - TriggerRAMIRQ(); - } - } -} - -void SPU::ExecuteTransfer(void* param, TickCount ticks, TickCount ticks_late) -{ - const RAMTransferMode mode = s_SPUCNT.ram_transfer_mode; - DebugAssert(mode != RAMTransferMode::Stopped); - - if (mode == RAMTransferMode::DMARead) - { - while (ticks > 0 && !s_transfer_fifo.IsFull()) - { - ExecuteFIFOReadFromRAM(ticks); - - // this can result in the FIFO being emptied, hence double the while loop - UpdateDMARequest(); - } - - // we're done if we have no more data to read - if (s_transfer_fifo.IsFull()) - { - s_SPUSTAT.transfer_busy = false; - s_transfer_event->Deactivate(); - return; - } - - s_SPUSTAT.transfer_busy = true; - const TickCount ticks_until_complete = TickCount(s_transfer_fifo.GetSpace() * u32(TRANSFER_TICKS_PER_HALFWORD)) + ((ticks < 0) ? -ticks : 0); - s_transfer_event->Schedule(ticks_until_complete); - } - else - { - // write the fifo to ram, request dma again when empty - while (ticks > 0 && !s_transfer_fifo.IsEmpty()) - { - ExecuteFIFOWriteToRAM(ticks); - - // similar deal here, the FIFO can be written out in a long slice - UpdateDMARequest(); - } - - // we're done if we have no more data to write - if (s_transfer_fifo.IsEmpty()) - { - s_SPUSTAT.transfer_busy = false; - s_transfer_event->Deactivate(); - return; - } - - s_SPUSTAT.transfer_busy = true; - const TickCount ticks_until_complete = TickCount(s_transfer_fifo.GetSize() * u32(TRANSFER_TICKS_PER_HALFWORD)) + ((ticks < 0) ? -ticks : 0); - s_transfer_event->Schedule(ticks_until_complete); - } -} - -void SPU::ManualTransferWrite(u16 value) -{ - if (!s_transfer_fifo.IsEmpty() && s_SPUCNT.ram_transfer_mode != RAMTransferMode::DMARead) - { - Log_WarningPrintf("FIFO not empty on manual SPU write, draining to hopefully avoid corruption. Game is silly."); - if (s_SPUCNT.ram_transfer_mode != RAMTransferMode::Stopped) - ExecuteTransfer(nullptr, std::numeric_limits::max(), 0); - } - - std::memcpy(&s_ram[s_transfer_address], &value, sizeof(u16)); - s_transfer_address = (s_transfer_address + sizeof(u16)) & RAM_MASK; - - if (IsRAMIRQTriggerable() && CheckRAMIRQ(s_transfer_address)) - { - Log_DebugPrintf("Trigger IRQ @ %08X %04X from manual write", s_transfer_address, s_transfer_address / 8); - TriggerRAMIRQ(); - } -} - -void SPU::UpdateTransferEvent() -{ - const RAMTransferMode mode = s_SPUCNT.ram_transfer_mode; - if (mode == RAMTransferMode::Stopped) - { - s_transfer_event->Deactivate(); - } - else if (mode == RAMTransferMode::DMARead) - { - // transfer event fills the fifo - if (s_transfer_fifo.IsFull()) - s_transfer_event->Deactivate(); - else if (!s_transfer_event->IsActive()) - s_transfer_event->Schedule(TickCount(s_transfer_fifo.GetSpace() * u32(TRANSFER_TICKS_PER_HALFWORD))); - } - else - { - // transfer event copies from fifo to ram - if (s_transfer_fifo.IsEmpty()) - s_transfer_event->Deactivate(); - else if (!s_transfer_event->IsActive()) - s_transfer_event->Schedule(TickCount(s_transfer_fifo.GetSize() * u32(TRANSFER_TICKS_PER_HALFWORD))); - } - - s_SPUSTAT.transfer_busy = s_transfer_event->IsActive(); -} - -void SPU::UpdateDMARequest() -{ - switch (s_SPUCNT.ram_transfer_mode) - { - case RAMTransferMode::DMARead: - s_SPUSTAT.dma_read_request = s_transfer_fifo.IsFull(); - s_SPUSTAT.dma_write_request = false; - s_SPUSTAT.dma_request = s_SPUSTAT.dma_read_request; - break; - - case RAMTransferMode::DMAWrite: - s_SPUSTAT.dma_read_request = false; - s_SPUSTAT.dma_write_request = s_transfer_fifo.IsEmpty(); - s_SPUSTAT.dma_request = s_SPUSTAT.dma_write_request; - break; - - case RAMTransferMode::Stopped: - case RAMTransferMode::ManualWrite: - default: - s_SPUSTAT.dma_read_request = false; - s_SPUSTAT.dma_write_request = false; - s_SPUSTAT.dma_request = false; - break; - } - - // This might call us back directly. - DMA::SetRequest(DMA::Channel::SPU, s_SPUSTAT.dma_request); -} - -void SPU::DMARead(u32* words, u32 word_count) -{ - /* - From @JaCzekanski - behavior when block size is larger than the FIFO size - for blocks <= 0x16 - all data is transferred correctly - using block size 0x20 transfer behaves strange: - % Writing 524288 bytes to SPU RAM to 0x00000000 using DMA... ok - % Reading 256 bytes from SPU RAM from 0x00001000 using DMA... ok - % 0x00001000: 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f ................ - % 0x00001010: 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f ................ - % 0x00001020: 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f !"#$%&'()*+,-./ - % 0x00001030: 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f 0123456789:;<=>? - % 0x00001040: 3e 3f 3e 3f 3e 3f 3e 3f 3e 3f 3e 3f 3e 3f 3e 3f >?>?>?>?>?>?>?>? - % 0x00001050: 3e 3f 3e 3f 3e 3f 3e 3f 3e 3f 3e 3f 3e 3f 3e 3f >?>?>?>?>?>?>?>? - % 0x00001060: 3e 3f 3e 3f 3e 3f 3e 3f 3e 3f 3e 3f 3e 3f 3e 3f >?>?>?>?>?>?>?>? - % 0x00001070: 3e 3f 3e 3f 3e 3f 3e 3f 3e 3f 3e 3f 3e 3f 3e 3f >?>?>?>?>?>?>?>? - % 0x00001080: 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f @ABCDEFGHIJKLMNO - % 0x00001090: 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f PQRSTUVWXYZ[\]^_ - % 0x000010a0: 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f `abcdefghijklmno - % 0x000010b0: 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f pqrstuvwxyz{|}~. - % 0x000010c0: 7e 7f 7e 7f 7e 7f 7e 7f 7e 7f 7e 7f 7e 7f 7e 7f ~.~.~.~.~.~.~.~. - % 0x000010d0: 7e 7f 7e 7f 7e 7f 7e 7f 7e 7f 7e 7f 7e 7f 7e 7f ~.~.~.~.~.~.~.~. - % 0x000010e0: 7e 7f 7e 7f 7e 7f 7e 7f 7e 7f 7e 7f 7e 7f 7e 7f ~.~.~.~.~.~.~.~. - % 0x000010f0: 7e 7f 7e 7f 7e 7f 7e 7f 7e 7f 7e 7f 7e 7f 7e 7f ~.~.~.~.~.~.~.~. - Using Block size = 0x10 (correct data) - % Reading 256 bytes from SPU RAM from 0x00001000 using DMA... ok - % 0x00001000: 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f ................ - % 0x00001010: 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f ................ - % 0x00001020: 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f !"#$%&'()*+,-./ - % 0x00001030: 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f 0123456789:;<=>? - % 0x00001040: 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f @ABCDEFGHIJKLMNO - % 0x00001050: 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f PQRSTUVWXYZ[\]^_ - % 0x00001060: 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f `abcdefghijklmno - % 0x00001070: 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f pqrstuvwxyz{|}~. - % 0x00001080: 80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f ................ - % 0x00001090: 90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f ................ - % 0x000010a0: a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af ................ - % 0x000010b0: b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf ................ - % 0x000010c0: c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf ................ - % 0x000010d0: d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df ................ - % 0x000010e0: e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef ................ - % 0x000010f0: f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe ff ................ - */ - - u16* halfwords = reinterpret_cast(words); - u32 halfword_count = word_count * 2; - - const u32 size = s_transfer_fifo.GetSize(); - if (word_count > size) - { - u16 fill_value = 0; - if (size > 0) - { - s_transfer_fifo.PopRange(halfwords, size); - fill_value = halfwords[size - 1]; - } - - Log_WarningPrintf("Transfer FIFO underflow, filling with 0x%04X", fill_value); - std::fill_n(&halfwords[size], halfword_count - size, fill_value); - } - else - { - s_transfer_fifo.PopRange(halfwords, halfword_count); - } - - UpdateDMARequest(); - UpdateTransferEvent(); -} - -void SPU::DMAWrite(const u32* words, u32 word_count) -{ - const u16* halfwords = reinterpret_cast(words); - u32 halfword_count = word_count * 2; - - const u32 words_to_transfer = std::min(s_transfer_fifo.GetSpace(), halfword_count); - s_transfer_fifo.PushRange(halfwords, words_to_transfer); - - if (words_to_transfer != halfword_count) - Log_WarningPrintf("Transfer FIFO overflow, dropping %u halfwords", halfword_count - words_to_transfer); - - UpdateDMARequest(); - UpdateTransferEvent(); -} - -void SPU::GeneratePendingSamples() -{ - if (s_transfer_event->IsActive()) - s_transfer_event->InvokeEarly(); - - const TickCount ticks_pending = s_tick_event->GetTicksSinceLastExecution(); - TickCount frames_to_execute; - if (g_settings.cpu_overclock_active) - { - frames_to_execute = static_cast((static_cast(ticks_pending) * g_settings.cpu_overclock_denominator) + static_cast(s_ticks_carry)) / static_cast(s_cpu_tick_divider); - } - else - { - frames_to_execute = (s_tick_event->GetTicksSinceLastExecution() + s_ticks_carry) / SYSCLK_TICKS_PER_SPU_TICK; - } - - const bool force_exec = (frames_to_execute > 0); - s_tick_event->InvokeEarly(force_exec); -} - -//bool SPU::IsDumpingAudio() -//{ -// return static_cast(s_dump_writer); -//} -// -//bool SPU::StartDumpingAudio(const char* filename) -//{ -// s_dump_writer.reset(); -// s_dump_writer = std::make_unique(); -// if (!s_dump_writer->Open(filename, SAMPLE_RATE, 2)) -// { -// Log_ErrorPrintf("Failed to open '%s'", filename); -// s_dump_writer.reset(); -// return false; -// } -// -//#ifdef SPU_DUMP_ALL_VOICES -// for (size_t i = 0; i < s_voice_dump_writers.size(); i++) -// { -// s_voice_dump_writers[i].reset(); -// s_voice_dump_writers[i] = std::make_unique(); -// -// TinyString new_suffix; -// if (i == NUM_VOICES) -// new_suffix.Assign("reverb.wav"); -// else -// new_suffix.Format("voice%u.wav", i); -// -// std::string voice_filename(FileSystem::ReplaceExtension(filename, new_suffix)); -// if (!s_voice_dump_writers[i]->Open(voice_filename.c_str(), SAMPLE_RATE, 2)) -// { -// Log_ErrorPrintf("Failed to open voice dump filename '%s'", voice_filename.c_str()); -// s_voice_dump_writers[i].reset(); -// } -// } -//#endif -// -// return true; -//} -// -//bool SPU::StopDumpingAudio() -//{ -// if (!s_dump_writer) -// return false; -// -// s_dump_writer.reset(); -// -//#ifdef SPU_DUMP_ALL_VOICES -// for (size_t i = 0; i < s_voice_dump_writers.size(); i++) -// s_voice_dump_writers[i].reset(); -//#endif -// -// return true; -//} - -const std::array& SPU::GetRAM() -{ - return s_ram; -} - -std::array& SPU::GetWritableRAM() -{ - return s_ram; -} - -bool SPU::IsAudioOutputMuted() -{ - return s_audio_output_muted; -} - -void SPU::SetAudioOutputMuted(bool muted) -{ - s_audio_output_muted = muted; -} - -AudioStream* SPU::GetOutputStream() -{ - return s_audio_stream.get(); -} - -void SPU::Voice::KeyOn() -{ - current_address = regs.adpcm_start_address & ~u16(1); - counter.bits = 0; - regs.adsr_volume = 0; - adpcm_last_samples.fill(0); - - // Samples from the previous block for interpolation should be zero. Fixes clicks in audio in Breath of Fire III. - std::fill_n(¤t_block_samples[NUM_SAMPLES_PER_ADPCM_BLOCK], NUM_SAMPLES_FROM_LAST_ADPCM_BLOCK, - static_cast(0)); - - has_samples = false; - is_first_block = true; - ignore_loop_address = false; - adsr_phase = ADSRPhase::Attack; - UpdateADSREnvelope(); -} - -void SPU::Voice::KeyOff() -{ - if (adsr_phase == ADSRPhase::Off || adsr_phase == ADSRPhase::Release) - return; - - adsr_phase = ADSRPhase::Release; - UpdateADSREnvelope(); -} - -void SPU::Voice::ForceOff() -{ - if (adsr_phase == ADSRPhase::Off) - return; - - regs.adsr_volume = 0; - adsr_phase = ADSRPhase::Off; -} - -SPU::ADSRPhase SPU::GetNextADSRPhase(ADSRPhase phase) -{ - switch (phase) - { - case ADSRPhase::Attack: - // attack -> decay - return ADSRPhase::Decay; - - case ADSRPhase::Decay: - // decay -> sustain - return ADSRPhase::Sustain; - - case ADSRPhase::Sustain: - // sustain stays in sustain until key off - return ADSRPhase::Sustain; - - default: - case ADSRPhase::Release: - // end of release disables the voice - return ADSRPhase::Off; - } -} - -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(); - -void SPU::VolumeEnvelope::Reset(u8 rate_, bool decreasing_, bool exponential_) -{ - rate = rate_; - decreasing = decreasing_; - exponential = exponential_; - - const ADSRTableEntry& table_entry = s_adsr_table[BoolToUInt8(decreasing)][rate]; - counter = table_entry.ticks; -} - -s16 SPU::VolumeEnvelope::Tick(s16 current_level) -{ - counter--; - if (counter > 0) - return current_level; - - const ADSRTableEntry& table_entry = s_adsr_table[BoolToUInt8(decreasing)][rate]; - s32 this_step = table_entry.step; - counter = table_entry.ticks; - - if (exponential) - { - if (decreasing) - { - this_step = (this_step * current_level) >> 15; - } - else - { - if (current_level >= 0x6000) - { - if (rate < 40) - { - this_step >>= 2; - } - else if (rate >= 44) - { - counter >>= 2; - } - else - { - this_step >>= 1; - counter >>= 1; - } - } - } - } - - return static_cast( - std::clamp(static_cast(current_level) + this_step, ENVELOPE_MIN_VOLUME, ENVELOPE_MAX_VOLUME)); -} - -void SPU::VolumeSweep::Reset(VolumeRegister reg) -{ - if (!reg.sweep_mode) - { - current_level = reg.fixed_volume_shr1 * 2; - envelope_active = false; - return; - } - - envelope.Reset(reg.sweep_rate, reg.sweep_direction_decrease, reg.sweep_exponential); - envelope_active = true; -} - -void SPU::VolumeSweep::Tick() -{ - if (!envelope_active) - return; - - current_level = envelope.Tick(current_level); - envelope_active = (envelope.decreasing ? (current_level > ENVELOPE_MIN_VOLUME) : (current_level < ENVELOPE_MAX_VOLUME)); -} - -void SPU::Voice::UpdateADSREnvelope() -{ - switch (adsr_phase) - { - case ADSRPhase::Off: - adsr_target = 0; - adsr_envelope.Reset(0, false, false); - return; - - case ADSRPhase::Attack: - adsr_target = 32767; // 0 -> max - adsr_envelope.Reset(regs.adsr.attack_rate, false, regs.adsr.attack_exponential); - break; - - case ADSRPhase::Decay: - adsr_target = static_cast(std::min((u32(regs.adsr.sustain_level.GetValue()) + 1) * 0x800, - ENVELOPE_MAX_VOLUME)); // max -> sustain level - adsr_envelope.Reset(regs.adsr.decay_rate_shr2 << 2, true, true); - break; - - case ADSRPhase::Sustain: - adsr_target = 0; - adsr_envelope.Reset(regs.adsr.sustain_rate, regs.adsr.sustain_direction_decrease, regs.adsr.sustain_exponential); - break; - - case ADSRPhase::Release: - adsr_target = 0; - adsr_envelope.Reset(regs.adsr.release_rate_shr2 << 2, true, regs.adsr.release_exponential); - break; - - default: - break; - } -} - -void SPU::Voice::TickADSR() -{ - regs.adsr_volume = adsr_envelope.Tick(regs.adsr_volume); - - if (adsr_phase != ADSRPhase::Sustain) - { - const bool reached_target = adsr_envelope.decreasing ? (regs.adsr_volume <= adsr_target) : (regs.adsr_volume >= adsr_target); - if (reached_target) - { - adsr_phase = GetNextADSRPhase(adsr_phase); - UpdateADSREnvelope(); - } - } -} - -void SPU::Voice::DecodeBlock(const ADPCMBlock& block) -{ - static constexpr std::array filter_table_pos = {{0, 60, 115, 98, 122}}; - static constexpr std::array filter_table_neg = {{0, 0, -52, -55, -60}}; - - // store samples needed for interpolation - current_block_samples[2] = current_block_samples[NUM_SAMPLES_FROM_LAST_ADPCM_BLOCK + NUM_SAMPLES_PER_ADPCM_BLOCK - 1]; - current_block_samples[1] = current_block_samples[NUM_SAMPLES_FROM_LAST_ADPCM_BLOCK + NUM_SAMPLES_PER_ADPCM_BLOCK - 2]; - current_block_samples[0] = current_block_samples[NUM_SAMPLES_FROM_LAST_ADPCM_BLOCK + NUM_SAMPLES_PER_ADPCM_BLOCK - 3]; - - // pre-lookup - const u8 shift = block.GetShift(); - const u8 filter_index = block.GetFilter(); - const s32 filter_pos = filter_table_pos[filter_index]; - const s32 filter_neg = filter_table_neg[filter_index]; - s16 last_samples[2] = {adpcm_last_samples[0], adpcm_last_samples[1]}; - - // samples - for (u32 i = 0; i < NUM_SAMPLES_PER_ADPCM_BLOCK; i++) - { - // extend 4-bit to 16-bit, apply shift from header and mix in previous samples - s32 sample = s32(static_cast(ZeroExtend16(block.GetNibble(i)) << 12) >> shift); - sample += (last_samples[0] * filter_pos) >> 6; - sample += (last_samples[1] * filter_neg) >> 6; - - last_samples[1] = last_samples[0]; - current_block_samples[NUM_SAMPLES_FROM_LAST_ADPCM_BLOCK + i] = last_samples[0] = static_cast(Clamp16(sample)); - } - - std::copy(last_samples, last_samples + countof(last_samples), adpcm_last_samples.begin()); - current_block_flags.bits = block.flags.bits; -} - -s32 SPU::Voice::Interpolate() const -{ - 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 // - }}; - - const u8 i = counter.interpolation_index; - const u32 s = NUM_SAMPLES_FROM_LAST_ADPCM_BLOCK + ZeroExtend32(counter.sample_index.GetValue()); - - s32 out = s32(gauss[0x0FF - i]) * s32(current_block_samples[s - 3]); - out += s32(gauss[0x1FF - i]) * s32(current_block_samples[s - 2]); - out += s32(gauss[0x100 + i]) * s32(current_block_samples[s - 1]); - out += s32(gauss[0x000 + i]) * s32(current_block_samples[s - 0]); - return out >> 15; -} - -void SPU::ReadADPCMBlock(u16 address, ADPCMBlock* block) -{ - u32 ram_address = (ZeroExtend32(address) * 8) & RAM_MASK; - if (IsRAMIRQTriggerable() && (CheckRAMIRQ(ram_address) || CheckRAMIRQ((ram_address + 8) & RAM_MASK))) - { - Log_DebugPrintf("Trigger IRQ @ %08X %04X from ADPCM reader", ram_address, ram_address / 8); - TriggerRAMIRQ(); - } - - // fast path - no wrap-around - if ((ram_address + sizeof(ADPCMBlock)) <= RAM_SIZE) - { - std::memcpy(block, &s_ram[ram_address], sizeof(ADPCMBlock)); - return; - } - - block->shift_filter.bits = s_ram[ram_address]; - ram_address = (ram_address + 1) & RAM_MASK; - block->flags.bits = s_ram[ram_address]; - ram_address = (ram_address + 1) & RAM_MASK; - for (u32 i = 0; i < 14; i++) - { - block->data[i] = s_ram[ram_address]; - ram_address = (ram_address + 1) & RAM_MASK; - } -} - -ALWAYS_INLINE_RELEASE std::tuple SPU::SampleVoice(u32 voice_index) -{ - Voice& voice = s_voices[voice_index]; - if (!voice.IsOn() && !s_SPUCNT.irq9_enable) - { - voice.last_volume = 0; - -#ifdef SPU_DUMP_ALL_VOICES - if (s_voice_dump_writers[voice_index]) - { - const s16 dump_samples[2] = {0, 0}; - s_voice_dump_writers[voice_index]->WriteFrames(dump_samples, 1); - } -#endif - - return {}; - } - - if (!voice.has_samples) - { - ADPCMBlock block; - ReadADPCMBlock(voice.current_address, &block); - voice.DecodeBlock(block); - voice.has_samples = true; - - if (voice.current_block_flags.loop_start && !voice.ignore_loop_address) - { - Log_TracePrintf("Voice %u loop start @ 0x%08X", voice_index, ZeroExtend32(voice.current_address)); - voice.regs.adpcm_repeat_address = voice.current_address; - } - } - - // skip interpolation when the volume is muted anyway - s32 volume; - if (voice.regs.adsr_volume != 0) - { - // interpolate/sample and apply ADSR volume - s32 sample; - if (IsVoiceNoiseEnabled(voice_index)) - sample = GetVoiceNoiseLevel(); - else - sample = voice.Interpolate(); - - volume = ApplyVolume(sample, voice.regs.adsr_volume); - } - else - { - volume = 0; - } - - voice.last_volume = volume; - - if (voice.adsr_phase != ADSRPhase::Off) - voice.TickADSR(); - - // Pitch modulation - u16 step = voice.regs.adpcm_sample_rate; - if (IsPitchModulationEnabled(voice_index)) - { - const s32 factor = std::clamp(s_voices[voice_index - 1].last_volume, -0x8000, 0x7FFF) + 0x8000; - step = Truncate16(static_cast((SignExtend32(step) * factor) >> 15)); - } - step = std::min(step, 0x3FFF); - - // Shouldn't ever overflow because if sample_index == 27, step == 0x4000 there won't be a carry out from the - // interpolation index. If there is a carry out, bit 12 will never be 1, so it'll never add more than 4 to - // sample_index, which should never be >27. - DebugAssert(voice.counter.sample_index < NUM_SAMPLES_PER_ADPCM_BLOCK); - voice.counter.bits += step; - - if (voice.counter.sample_index >= NUM_SAMPLES_PER_ADPCM_BLOCK) - { - // next block - voice.counter.sample_index -= NUM_SAMPLES_PER_ADPCM_BLOCK; - voice.has_samples = false; - voice.is_first_block = false; - voice.current_address += 2; - - // handle flags - if (voice.current_block_flags.loop_end) - { - s_endx_register |= (u32(1) << voice_index); - voice.current_address = voice.regs.adpcm_repeat_address & ~u16(1); - - if (!voice.current_block_flags.loop_repeat) - { - Log_TracePrintf("Voice %u loop end+mute @ 0x%08X", voice_index, ZeroExtend32(voice.current_address)); - voice.ForceOff(); - } - else - { - Log_TracePrintf("Voice %u loop end+repeat @ 0x%08X", voice_index, ZeroExtend32(voice.current_address)); - } - } - } - - // apply per-channel volume - const s32 left = ApplyVolume(volume, voice.left_volume.current_level); - const s32 right = ApplyVolume(volume, voice.right_volume.current_level); - voice.left_volume.Tick(); - voice.right_volume.Tick(); - -#ifdef SPU_DUMP_ALL_VOICES - if (s_voice_dump_writers[voice_index]) - { - const s16 dump_samples[2] = {static_cast(Clamp16(left)), static_cast(Clamp16(right))}; - s_voice_dump_writers[voice_index]->WriteFrames(dump_samples, 1); - } -#endif - - return std::make_tuple(left, right); -} - -void SPU::UpdateNoise() -{ - // Dr Hell's noise waveform, implementation borrowed from pcsx-r. - static constexpr std::array noise_wave_add = { - {1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, - 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1}}; - static constexpr std::array noise_freq_add = {{0, 84, 140, 180, 210}}; - - const u32 noise_clock = s_SPUCNT.noise_clock; - const u32 level = (0x8000u >> (noise_clock >> 2)) << 16; - - s_noise_count += 0x10000u + noise_freq_add[noise_clock & 3u]; - if ((s_noise_count & 0xFFFFu) >= noise_freq_add[4]) - { - s_noise_count += 0x10000; - s_noise_count -= noise_freq_add[noise_clock & 3u]; - } - - if (s_noise_count < level) - return; - - s_noise_count %= level; - s_noise_level = (s_noise_level << 1) | noise_wave_add[(s_noise_level >> 10) & 63u]; -} - -/************************************************************************/ -/* Reverb algorithm from Mednafen-PSX */ -/************************************************************************/ - -u32 SPU::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 SPU::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 SPU::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)); -} - -// 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]; - -ALWAYS_INLINE 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 -ALWAYS_INLINE 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; -} - -ALWAYS_INLINE static s16 ReverbSat(s32 val) -{ - return static_cast(std::clamp(val, -0x8000, 0x7FFF)); -} - -ALWAYS_INLINE static s16 ReverbNeg(s16 samp) -{ - if (samp == -32768) - return 0x7FFF; - - return -samp; -} - -ALWAYS_INLINE 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); -} - -void SPU::ProcessReverb(s16 left_in, s16 right_in, s32* left_out, s32* right_out) -{ - 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; - - s32 out[2]; - if (s_reverb_resample_buffer_position & 1u) - { - 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.reverb_master_enable) - { - const s16 IIR_INPUT_A = ReverbSat((((ReverbRead(s_reverb_registers.IIR_SRC_A[lr ^ 0]) * s_reverb_registers.IIR_COEF) >> 14) + ((downsampled[lr] * s_reverb_registers.IN_COEF[lr]) >> 14)) >> 1); - const s16 IIR_INPUT_B = ReverbSat((((ReverbRead(s_reverb_registers.IIR_SRC_B[lr ^ 1]) * s_reverb_registers.IIR_COEF) >> 14) + ((downsampled[lr] * s_reverb_registers.IN_COEF[lr]) >> 14)) >> 1); - const s16 IIR_A = ReverbSat((((IIR_INPUT_A * s_reverb_registers.IIR_ALPHA) >> 14) + (IIASM(s_reverb_registers.IIR_ALPHA, ReverbRead(s_reverb_registers.IIR_DEST_A[lr], -1)) >> 14)) >> 1); - const s16 IIR_B = ReverbSat((((IIR_INPUT_B * s_reverb_registers.IIR_ALPHA) >> 14) + (IIASM(s_reverb_registers.IIR_ALPHA, ReverbRead(s_reverb_registers.IIR_DEST_B[lr], -1)) >> 14)) >> 1); - - ReverbWrite(s_reverb_registers.IIR_DEST_A[lr], IIR_A); - ReverbWrite(s_reverb_registers.IIR_DEST_B[lr], IIR_B); - } - - const s32 ACC = ((ReverbRead(s_reverb_registers.ACC_SRC_A[lr]) * s_reverb_registers.ACC_COEF_A) >> 14) + ((ReverbRead(s_reverb_registers.ACC_SRC_B[lr]) * s_reverb_registers.ACC_COEF_B) >> 14) + ((ReverbRead(s_reverb_registers.ACC_SRC_C[lr]) * s_reverb_registers.ACC_COEF_C) >> 14) + ((ReverbRead(s_reverb_registers.ACC_SRC_D[lr]) * s_reverb_registers.ACC_COEF_D) >> 14); - - const s16 FB_A = ReverbRead(s_reverb_registers.MIX_DEST_A[lr] - s_reverb_registers.FB_SRC_A); - const s16 FB_B = ReverbRead(s_reverb_registers.MIX_DEST_B[lr] - s_reverb_registers.FB_SRC_B); - const s16 MDA = ReverbSat((ACC + ((FB_A * ReverbNeg(s_reverb_registers.FB_ALPHA)) >> 14)) >> 1); - const s16 MDB = ReverbSat( - FB_A + ((((MDA * s_reverb_registers.FB_ALPHA) >> 14) + ((FB_B * ReverbNeg(s_reverb_registers.FB_X)) >> 14)) >> 1)); - const s16 IVB = ReverbSat(FB_B + ((MDB * s_reverb_registers.FB_X) >> 15)); - - if (s_SPUCNT.reverb_master_enable) - { - ReverbWrite(s_reverb_registers.MIX_DEST_A[lr], MDA); - ReverbWrite(s_reverb_registers.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 - { - 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; - - 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); - -#ifdef SPU_DUMP_ALL_VOICES - if (s_voice_dump_writers[NUM_VOICES]) - { - const s16 dump_samples[2] = {static_cast(Clamp16(s_last_reverb_output[0])), - static_cast(Clamp16(s_last_reverb_output[1]))}; - s_voice_dump_writers[NUM_VOICES]->WriteFrames(dump_samples, 1); - } -#endif -} - -void SPU::Execute(void* param, TickCount ticks, TickCount ticks_late) -{ - u32 remaining_frames; - if (g_settings.cpu_overclock_active) - { - // (X * D) / N / 768 -> (X * D) / (N * 768) - const u64 num = (static_cast(ticks) * g_settings.cpu_overclock_denominator) + static_cast(s_ticks_carry); - remaining_frames = static_cast(num / s_cpu_tick_divider); - s_ticks_carry = static_cast(num % s_cpu_tick_divider); - } - else - { - remaining_frames = static_cast((ticks + s_ticks_carry) / SYSCLK_TICKS_PER_SPU_TICK); - s_ticks_carry = (ticks + s_ticks_carry) % SYSCLK_TICKS_PER_SPU_TICK; - } - - AudioStream* output_stream = s_audio_output_muted ? s_null_audio_stream.get() : s_audio_stream.get(); - - while (remaining_frames > 0) - { - s16* output_frame_start; - u32 output_frame_space = remaining_frames; - output_stream->BeginWrite(&output_frame_start, &output_frame_space); - - s16* output_frame = output_frame_start; - const u32 frames_in_this_batch = std::min(remaining_frames, output_frame_space); - for (u32 i = 0; i < frames_in_this_batch; i++) - { - s32 left_sum = 0; - s32 right_sum = 0; - s32 reverb_in_left = 0; - s32 reverb_in_right = 0; - - u32 reverb_on_register = s_reverb_on_register; - - for (u32 voice = 0; voice < NUM_VOICES; voice++) - { - const auto [left, right] = SampleVoice(voice); - left_sum += left; - right_sum += right; - - if (reverb_on_register & 1u) - { - reverb_in_left += left; - reverb_in_right += right; - } - reverb_on_register >>= 1; - } - - if (!s_SPUCNT.mute_n) - { - left_sum = 0; - right_sum = 0; - } - - // Update noise once per frame. - UpdateNoise(); - - // Mix in CD audio. - const auto [cd_audio_left, cd_audio_right] = CDROM::GetAudioFrame(); - if (s_SPUCNT.cd_audio_enable) - { - const s32 cd_audio_volume_left = ApplyVolume(s32(cd_audio_left), s_cd_audio_volume_left); - const s32 cd_audio_volume_right = ApplyVolume(s32(cd_audio_right), s_cd_audio_volume_right); - - left_sum += cd_audio_volume_left; - right_sum += cd_audio_volume_right; - - if (s_SPUCNT.cd_audio_reverb) - { - reverb_in_left += cd_audio_volume_left; - reverb_in_right += cd_audio_volume_right; - } - } - - // Compute reverb. - s32 reverb_out_left, reverb_out_right; - ProcessReverb(static_cast(Clamp16(reverb_in_left)), static_cast(Clamp16(reverb_in_right)), - &reverb_out_left, &reverb_out_right); - - // Mix in reverb. - left_sum += reverb_out_left; - right_sum += reverb_out_right; - - // Apply main volume after clamping. A maximum volume should not overflow here because both are 16-bit values. - *(output_frame++) = static_cast(ApplyVolume(Clamp16(left_sum), s_main_volume_left.current_level)); - *(output_frame++) = static_cast(ApplyVolume(Clamp16(right_sum), s_main_volume_right.current_level)); - s_main_volume_left.Tick(); - s_main_volume_right.Tick(); - - // Write to capture buffers. - WriteToCaptureBuffer(0, cd_audio_left); - WriteToCaptureBuffer(1, cd_audio_right); - WriteToCaptureBuffer(2, static_cast(Clamp16(s_voices[1].last_volume))); - WriteToCaptureBuffer(3, static_cast(Clamp16(s_voices[3].last_volume))); - IncrementCaptureBufferPosition(); - - // Key off/on voices after the first frame. - if (i == 0 && (s_key_off_register != 0 || s_key_on_register != 0)) - { - u32 key_off_register = s_key_off_register; - s_key_off_register = 0; - - u32 key_on_register = s_key_on_register; - s_key_on_register = 0; - - for (u32 voice = 0; voice < NUM_VOICES; voice++) - { - if (key_off_register & 1u) - s_voices[voice].KeyOff(); - key_off_register >>= 1; - - if (key_on_register & 1u) - { - s_endx_register &= ~(1u << voice); - s_voices[voice].KeyOn(); - } - key_on_register >>= 1; - } - } - } - - if (s_dump_writer) - s_dump_writer->WriteFrames(output_frame_start, frames_in_this_batch); - - output_stream->EndWrite(frames_in_this_batch); - remaining_frames -= frames_in_this_batch; - } -} - -void SPU::UpdateEventInterval() -{ - // Don't generate more than the audio buffer since in a single slice, otherwise we'll both overflow the buffers when - // we do write it, and the audio thread will underflow since it won't have enough data it the game isn't messing with - // the SPU state. - const u32 max_slice_frames = s_audio_stream->GetBufferSize(); - - // TODO: Make this predict how long until the interrupt will be hit instead... - const u32 interval = (s_SPUCNT.enable && s_SPUCNT.irq9_enable) ? 1 : max_slice_frames; - const TickCount interval_ticks = static_cast(interval) * s_cpu_ticks_per_spu_tick; - if (s_tick_event->IsActive() && s_tick_event->GetInterval() == interval_ticks) - return; - - // Ensure all pending ticks have been executed, since we won't get them back after rescheduling. - s_tick_event->InvokeEarly(true); - s_tick_event->SetInterval(interval_ticks); - - TickCount downcount = interval_ticks; - if (!g_settings.cpu_overclock_active) - downcount -= s_ticks_carry; - - s_tick_event->Schedule(downcount); -} - -//void SPU::DrawDebugStateWindow() -//{ -// static const ImVec4 active_color{1.0f, 1.0f, 1.0f, 1.0f}; -// static const ImVec4 inactive_color{0.4f, 0.4f, 0.4f, 1.0f}; -// const float framebuffer_scale = Host::GetOSDScale(); -// -// ImGui::SetNextWindowSize(ImVec2(800.0f * framebuffer_scale, 800.0f * framebuffer_scale), ImGuiCond_FirstUseEver); -// if (!ImGui::Begin("SPU State", nullptr)) -// { -// ImGui::End(); -// return; -// } -// -// // status -// if (ImGui::CollapsingHeader("Status", ImGuiTreeNodeFlags_DefaultOpen)) -// { -// static constexpr std::array transfer_modes = { -// {"Transfer Stopped", "Manual Write", "DMA Write", "DMA Read"}}; -// const std::array offsets = {{100.0f * framebuffer_scale, 200.0f * framebuffer_scale, -// 300.0f * framebuffer_scale, 420.0f * framebuffer_scale, -// 500.0f * framebuffer_scale, 600.0f * framebuffer_scale}}; -// -// ImGui::Text("Control: "); -// ImGui::SameLine(offsets[0]); -// ImGui::TextColored(s_SPUCNT.enable ? active_color : inactive_color, "SPU Enable"); -// ImGui::SameLine(offsets[1]); -// ImGui::TextColored(s_SPUCNT.mute_n ? inactive_color : active_color, "Mute SPU"); -// ImGui::SameLine(offsets[2]); -// ImGui::TextColored(s_SPUCNT.external_audio_enable ? active_color : inactive_color, "External Audio"); -// ImGui::SameLine(offsets[3]); -// ImGui::TextColored(s_SPUCNT.ram_transfer_mode != RAMTransferMode::Stopped ? active_color : inactive_color, "%s", -// transfer_modes[static_cast(s_SPUCNT.ram_transfer_mode.GetValue())]); -// -// ImGui::Text("Status: "); -// ImGui::SameLine(offsets[0]); -// ImGui::TextColored(s_SPUSTAT.irq9_flag ? active_color : inactive_color, "IRQ9"); -// ImGui::SameLine(offsets[1]); -// ImGui::TextColored(s_SPUSTAT.dma_request ? active_color : inactive_color, "DMA Request"); -// ImGui::SameLine(offsets[2]); -// ImGui::TextColored(s_SPUSTAT.dma_read_request ? active_color : inactive_color, "DMA Read"); -// ImGui::SameLine(offsets[3]); -// ImGui::TextColored(s_SPUSTAT.dma_write_request ? active_color : inactive_color, "DMA Write"); -// ImGui::SameLine(offsets[4]); -// ImGui::TextColored(s_SPUSTAT.transfer_busy ? active_color : inactive_color, "Transfer Busy"); -// ImGui::SameLine(offsets[5]); -// ImGui::TextColored(s_SPUSTAT.second_half_capture_buffer ? active_color : inactive_color, "Second Capture Buffer"); -// -// ImGui::Text("Interrupt: "); -// ImGui::SameLine(offsets[0]); -// ImGui::TextColored(s_SPUCNT.irq9_enable ? active_color : inactive_color, -// s_SPUCNT.irq9_enable ? "Enabled @ 0x%04X (actual 0x%08X)" : "Disabled @ 0x%04X (actual 0x%08X)", -// s_irq_address, (ZeroExtend32(s_irq_address) * 8) & RAM_MASK); -// -// ImGui::Text("Volume: "); -// ImGui::SameLine(offsets[0]); -// ImGui::Text("Left: %d%%", ApplyVolume(100, s_main_volume_left.current_level)); -// ImGui::SameLine(offsets[1]); -// ImGui::Text("Right: %d%%", ApplyVolume(100, s_main_volume_right.current_level)); -// -// ImGui::Text("CD Audio: "); -// ImGui::SameLine(offsets[0]); -// ImGui::TextColored(s_SPUCNT.cd_audio_enable ? active_color : inactive_color, -// s_SPUCNT.cd_audio_enable ? "Enabled" : "Disabled"); -// ImGui::SameLine(offsets[1]); -// ImGui::TextColored(s_SPUCNT.cd_audio_enable ? active_color : inactive_color, "Left Volume: %d%%", -// ApplyVolume(100, s_cd_audio_volume_left)); -// ImGui::SameLine(offsets[3]); -// ImGui::TextColored(s_SPUCNT.cd_audio_enable ? active_color : inactive_color, "Right Volume: %d%%", -// ApplyVolume(100, s_cd_audio_volume_left)); -// -// ImGui::Text("Transfer FIFO: "); -// ImGui::SameLine(offsets[0]); -// ImGui::TextColored(s_transfer_event->IsActive() ? active_color : inactive_color, "%u halfwords (%u bytes)", -// s_transfer_fifo.GetSize(), s_transfer_fifo.GetSize() * 2); -// } -// -// // draw voice states -// if (ImGui::CollapsingHeader("Voice State", ImGuiTreeNodeFlags_DefaultOpen)) -// { -// static constexpr u32 NUM_COLUMNS = 12; -// -// ImGui::Columns(NUM_COLUMNS); -// -// // headers -// static constexpr std::array column_titles = { -// {"#", "InterpIndex", "SampleIndex", "CurAddr", "StartAddr", "RepeatAddr", "SampleRate", "VolLeft", "VolRight", -// "ADSRPhase", "ADSRVol", "ADSRTicks"}}; -// static constexpr std::array adsr_phases = {{"Off", "Attack", "Decay", "Sustain", "Release"}}; -// for (u32 i = 0; i < NUM_COLUMNS; i++) -// { -// ImGui::TextUnformatted(column_titles[i]); -// ImGui::NextColumn(); -// } -// -// // states -// for (u32 voice_index = 0; voice_index < NUM_VOICES; voice_index++) -// { -// const Voice& v = s_voices[voice_index]; -// ImVec4 color = v.IsOn() ? ImVec4(1.0f, 1.0f, 1.0f, 1.0f) : ImVec4(0.5f, 0.5f, 0.5f, 1.0f); -// ImGui::TextColored(color, "%u", ZeroExtend32(voice_index)); -// ImGui::NextColumn(); -// if (IsVoiceNoiseEnabled(voice_index)) -// ImGui::TextColored(color, "NOISE"); -// else -// ImGui::TextColored(color, "%u", ZeroExtend32(v.counter.interpolation_index.GetValue())); -// ImGui::NextColumn(); -// ImGui::TextColored(color, "%u", ZeroExtend32(v.counter.sample_index.GetValue())); -// ImGui::NextColumn(); -// ImGui::TextColored(color, "%04X", ZeroExtend32(v.current_address)); -// ImGui::NextColumn(); -// ImGui::TextColored(color, "%04X", ZeroExtend32(v.regs.adpcm_start_address)); -// ImGui::NextColumn(); -// ImGui::TextColored(color, "%04X", ZeroExtend32(v.regs.adpcm_repeat_address)); -// ImGui::NextColumn(); -// ImGui::TextColored(color, "%.2f", (float(v.regs.adpcm_sample_rate) / 4096.0f) * 44100.0f); -// ImGui::NextColumn(); -// ImGui::TextColored(color, "%d%%", ApplyVolume(100, v.left_volume.current_level)); -// ImGui::NextColumn(); -// ImGui::TextColored(color, "%d%%", ApplyVolume(100, v.right_volume.current_level)); -// ImGui::NextColumn(); -// ImGui::TextColored(color, "%s", adsr_phases[static_cast(v.adsr_phase)]); -// ImGui::NextColumn(); -// ImGui::TextColored(color, "%d%%", ApplyVolume(100, v.regs.adsr_volume)); -// ImGui::NextColumn(); -// ImGui::TextColored(color, "%d", v.adsr_envelope.counter); -// ImGui::NextColumn(); -// } -// -// ImGui::Columns(1); -// } -// -// if (ImGui::CollapsingHeader("Reverb", ImGuiTreeNodeFlags_DefaultOpen)) -// { -// ImGui::TextColored(s_SPUCNT.reverb_master_enable ? active_color : inactive_color, "Master Enable: %s", -// s_SPUCNT.reverb_master_enable ? "Yes" : "No"); -// ImGui::Text("Voices Enabled: "); -// -// for (u32 i = 0; i < NUM_VOICES; i++) -// { -// ImGui::SameLine(0.0f, 16.0f); -// -// const bool active = IsVoiceReverbEnabled(i); -// ImGui::TextColored(active ? active_color : inactive_color, "%u", i); -// } -// -// ImGui::TextColored(s_SPUCNT.cd_audio_reverb ? active_color : inactive_color, "CD Audio Enable: %s", -// s_SPUCNT.cd_audio_reverb ? "Yes" : "No"); -// -// ImGui::TextColored(s_SPUCNT.external_audio_reverb ? active_color : inactive_color, "External Audio Enable: %s", -// s_SPUCNT.external_audio_reverb ? "Yes" : "No"); -// -// ImGui::Text("Base Address: 0x%08X (%04X)", s_reverb_base_address, s_reverb_registers.mBASE); -// ImGui::Text("Current Address: 0x%08X", s_reverb_current_address); -// ImGui::Text("Current Amplitude: Input (%d, %d) Output (%d, %d)", s_last_reverb_input[0], s_last_reverb_input[1], -// s_last_reverb_output[0], s_last_reverb_output[1]); -// ImGui::Text("Output Volume: Left %d%% Right %d%%", ApplyVolume(100, s_reverb_registers.vLOUT), -// ApplyVolume(100, s_reverb_registers.vROUT)); -// -// ImGui::Text("Pitch Modulation: "); -// for (u32 i = 1; i < NUM_VOICES; i++) -// { -// ImGui::SameLine(0.0f, 16.0f); -// -// const bool active = IsPitchModulationEnabled(i); -// ImGui::TextColored(active ? active_color : inactive_color, "%u", i); -// } -// } -// -// if (ImGui::CollapsingHeader("Hacks", ImGuiTreeNodeFlags_DefaultOpen)) -// { -// if (ImGui::Button("Key Off All Voices")) -// { -// for (u32 i = 0; i < NUM_VOICES; i++) -// { -// s_voices[i].KeyOff(); -// s_voices[i].adsr_envelope.counter = 0; -// s_voices[i].regs.adsr_volume = 0; -// } -// } -// } -// -// ImGui::End(); -//} \ No newline at end of file diff --git a/Source/AliveLibCommon/audio/mixer/SPU.hpp b/Source/AliveLibCommon/audio/mixer/SPU.hpp deleted file mode 100644 index d14f674c1..000000000 --- a/Source/AliveLibCommon/audio/mixer/SPU.hpp +++ /dev/null @@ -1,60 +0,0 @@ -// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin -// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) - -#pragma once -#include "types.h" -#include - -class StateWrapper; - -class AudioStream; - -namespace SPU { - -enum : u32 -{ - RAM_SIZE = 512 * 1024, - RAM_MASK = RAM_SIZE - 1, - SAMPLE_RATE = 44100, -}; - -void Initialize(); -void CPUClockChanged(); -void Shutdown(); -void Reset(); -bool DoState(StateWrapper& sw); - -u16 ReadRegister(u32 offset); -void WriteRegister(u32 offset, u16 value); - -void DMARead(u32* words, u32 word_count); -void DMAWrite(const u32* words, u32 word_count); - -//// Render statistics debug window. -//void DrawDebugStateWindow(); - -// Executes the SPU, generating any pending samples. -void GeneratePendingSamples(); - -///// Returns true if currently dumping audio. -//bool IsDumpingAudio(); -// -///// Starts dumping audio to file. -//bool StartDumpingAudio(const char* filename); -// -///// Stops dumping audio to file, if started. -//bool StopDumpingAudio(); - -/// Access to SPU RAM. -const std::array& GetRAM(); -std::array& GetWritableRAM(); - -/// Change output stream - used for runahead. -// TODO: Make it use system "running ahead" flag -bool IsAudioOutputMuted(); -void SetAudioOutputMuted(bool muted); - -AudioStream* GetOutputStream(); -void RecreateOutputStream(); - -}; // namespace SPU \ No newline at end of file diff --git a/Source/AliveLibCommon/audio/mixer/SPUEMU.cpp b/Source/AliveLibCommon/audio/mixer/SPUEMU.cpp deleted file mode 100644 index e17db849c..000000000 --- a/Source/AliveLibCommon/audio/mixer/SPUEMU.cpp +++ /dev/null @@ -1,1053 +0,0 @@ -// Heavly modify version of duckstation spu -#pragma once - -#include "SPUEMU.hpp" -#include -#include "AudioStream.hpp" - -// countof macro -#ifndef countof - #ifdef _countof - #define countof _countof - #else -template -char (&__countof_ArraySizeHelper(T (&array)[N]))[N]; - #define countof(array) (sizeof(__countof_ArraySizeHelper(array))) - #endif -#endif - -u32 mask(u32 num) -{ - u32 res = 0; - while (num-- > 0) - { - res = (res << 1) | 1; - } - return res; -} - -static const u32 NUM_VOICES = 24; -static const u32 NUM_REVERB_REGS = 32; -static const u32 NUM_SAMPLES_PER_ADPCM_BLOCK = 28; -static const u32 NUM_VOICE_REGISTERS = 8; -static const u32 NUM_SAMPLES_FROM_LAST_ADPCM_BLOCK = 3, - -static const s16 ENVELOPE_MIN_VOLUME = 0; -static const s16 ENVELOPE_MAX_VOLUME = 0x7FFF; - -union ADSRRegister -{ - u32 bits; - struct - { - u16 bits_low; - u16 bits_high; - }; - - // adsr.attackRate = (adsr1 >> 8) & mask(7); - BitField sustain_level; - BitField decay_rate_shr2; - BitField attack_rate; - BitField attack_exponential; - - BitField release_rate_shr2; - BitField release_exponential; - BitField sustain_rate; - BitField sustain_direction_decrease; - BitField sustain_exponential; -}; - -union VoiceCounter -{ - // promoted to u32 because of overflow - u32 bits; - - BitField interpolation_index; - BitField sample_index; -}; - -union VoiceRegisters -{ - u16 index[NUM_VOICE_REGISTERS]; - - struct - { - VolumeRegister volume_left; - VolumeRegister volume_right; - - u16 adpcm_sample_rate; // VxPitch - u16 adpcm_start_address; // multiply by 8 - - ADSRRegister adsr; - s16 adsr_volume; - - u16 adpcm_repeat_address; // multiply by 8 - }; -}; - -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]; - }; - - u16 rev[NUM_REVERB_REGS]; - }; -}; - -struct VolumeEnvelope -{ - s32 counter; - u8 rate; - bool decreasing; - bool exponential; - - void Reset(u8 rate_, bool decreasing_, bool exponential_); - s16 Tick(s16 current_level); -}; - -union VolumeRegister -{ - u16 bits; - - BitField sweep_mode; - BitField fixed_volume_shr1; // divided by 2 - - BitField sweep_exponential; - BitField sweep_direction_decrease; - BitField sweep_phase_negative; - BitField sweep_rate; -}; - -struct VolumeSweep -{ - VolumeEnvelope envelope; - bool envelope_active; - s16 current_level; - - void Reset(VolumeRegister reg); - void Tick(); -}; - -union SPUCNT -{ - u16 bits; - - BitField enable; - BitField mute_n; - BitField noise_clock; - BitField reverb_master_enable; - BitField irq9_enable; - BitField ram_transfer_mode; - BitField external_audio_reverb; - BitField cd_audio_reverb; - BitField external_audio_enable; - BitField cd_audio_enable; - - BitField mode; -}; - -union ADPCMFlags -{ - u8 bits; - - //BitField loop_end; - //BitField loop_repeat; - //BitField loop_start; - - u8 loopEnd() - { - return (bits >> 0) & mask(1); - } - - u8 loopRepeat() - { - return (bits >> 1) & mask(1); - } - - u8 loopStart() - { - return (bits >> 2) & mask(1); - } -}; - -struct ADPCMBlock -{ - union - { - u8 bits; - - //BitField shift; - //BitField filter; - - u8 shift() - { - return (bits >> 0) & mask(4); - } - - u8 filter() - { - return (bits >> 4) & mask(3); - } - - } shift_filter; - ADPCMFlags flags; - u8 data[NUM_SAMPLES_PER_ADPCM_BLOCK / 2]; - - // For both 4bit and 8bit ADPCM, reserved shift values 13..15 will act same as shift=9). - u8 GetShift() const - { - const u8 shift = shift_filter.shift(); - return (shift > 12) ? 9 : shift; - } - - u8 GetFilter() const - { - return std::min(shift_filter.filter(), 4); - } - - u8 GetNibble(u32 index) const - { - return (data[index / 2] >> ((index % 2) * 4)) & 0x0F; - } -}; - -template -constexpr u16 Truncate16(TValue value) -{ - return static_cast(static_cast::type>(value)); -} - -template -bool ConvertToBoolUnchecked(TValue value) -{ - bool ret; - std::memcpy(&ret, &value, sizeof(bool)); - return ret; -} - -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); -} - -static constexpr s32 Clamp16(s32 value) -{ - return (value < -0x8000) ? -0x8000 : (value > 0x7FFF) ? 0x7FFF - : value; -} - -template -constexpr u32 ZeroExtend32(TValue value) -{ - return ZeroExtend(value); -} - -template -constexpr u16 ZeroExtend16(TValue value) -{ - return ZeroExtend(value); -} - -static AudioStream* s_audio_stream = new AudioStream(); -static std::array s_voices{}; -static SPUCNT s_SPUCNT = {}; - -// 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; - -static u32 s_key_on_register = 0; -static u32 s_key_off_register = 0; -static u32 s_endx_register = 0; -static u32 s_pitch_modulation_enable_register = 0; - -static u32 s_noise_mode_register = 0; -static u32 s_noise_count = 0; -static u32 s_noise_level = 0; - -static VolumeRegister s_main_volume_left_reg = {}; -static VolumeRegister s_main_volume_right_reg = {}; -static VolumeSweep s_main_volume_left = {}; -static VolumeSweep s_main_volume_right = {}; - -struct Voice -{ - u16 current_address; - VoiceRegisters regs; - VoiceCounter counter; - ADPCMFlags current_block_flags; - bool is_first_block; - std::array current_block_samples; // 3 + 28 - std::array adpcm_last_samples; - s32 last_volume; - - VolumeSweep left_volume; - VolumeSweep right_volume; - - VolumeEnvelope adsr_envelope; - ADSRPhase adsr_phase; - s16 adsr_target; - bool has_samples; - bool ignore_loop_address; - - bool IsOn() const - { - return adsr_phase != ADSRPhase::Off; - } - - void KeyOn(); - void KeyOff(); - void ForceOff(); - - void DecodeBlock(const ADPCMBlock& block); - s32 Interpolate() const; - - // Switches to the specified phase, filling in target. - void UpdateADSREnvelope(); - ADSRPhase GetNextADSRPhase(ADSRPhase phase); - - // Updates the ADSR volume/phase. - void TickADSR(); -}; - -void SPU::Execute(u32 remaining_frames) -{ - AudioStream* output_stream = s_audio_stream; - - - // I NEED - // 1. sample interpolation - // 2. reverb - // 3. adsr - - - while (remaining_frames > 0) - { - s16* output_frame_start; - u32 output_frame_space = remaining_frames; - output_stream->BeginWrite(&output_frame_start, &output_frame_space); - - s16* output_frame = output_frame_start; - const u32 frames_in_this_batch = std::min(remaining_frames, output_frame_space); - for (u32 i = 0; i < frames_in_this_batch; i++) - { - s32 left_sum = 0; - s32 right_sum = 0; - s32 reverb_in_left = 0; - s32 reverb_in_right = 0; - - u32 reverb_on_register = s_reverb_on_register; - for (u32 voice = 0; voice < NUM_VOICES; voice++) - { - const auto [left, right] = SampleVoice(voice); - left_sum += left; - right_sum += right; - - if (reverb_on_register & 1u) - { - reverb_in_left += left; - reverb_in_right += right; - } - reverb_on_register >>= 1; - } - - // What's this? - //if (!s_SPUCNT.mute_n) - //{ - // left_sum = 0; - // right_sum = 0; - //} - - // Update noise once per frame. - //UpdateNoise(); - - // Compute reverb. - s32 reverb_out_left, reverb_out_right; - ProcessReverb(static_cast(Clamp16(reverb_in_left)), static_cast(Clamp16(reverb_in_right)), - &reverb_out_left, &reverb_out_right); - - // Mix in reverb. - left_sum += reverb_out_left; - right_sum += reverb_out_right; - - // Apply main volume after clamping. A maximum volume should not overflow here because both are 16-bit values. - *(output_frame++) = static_cast(ApplyVolume(Clamp16(left_sum), s_main_volume_left.current_level)); - *(output_frame++) = static_cast(ApplyVolume(Clamp16(right_sum), s_main_volume_right.current_level)); - s_main_volume_left.Tick(); - s_main_volume_right.Tick(); - - // Write to capture buffers. - //WriteToCaptureBuffer(0, cd_audio_left); - //WriteToCaptureBuffer(1, cd_audio_right); - // - // UNSURE IF THESE ARE NEEDED? - //WriteToCaptureBuffer(2, static_cast(Clamp16(s_voices[1].last_volume))); - //WriteToCaptureBuffer(3, static_cast(Clamp16(s_voices[3].last_volume))); - //IncrementCaptureBufferPosition(); - - // Key off/on voices after the first frame. - if (i == 0 && (s_key_off_register != 0 || s_key_on_register != 0)) - { - u32 key_off_register = s_key_off_register; - s_key_off_register = 0; - - u32 key_on_register = s_key_on_register; - s_key_on_register = 0; - - for (u32 voice = 0; voice < NUM_VOICES; voice++) - { - if (key_off_register & 1u) - s_voices[voice].KeyOff(); - key_off_register >>= 1; - - if (key_on_register & 1u) - { - s_endx_register &= ~(1u << voice); - s_voices[voice].KeyOn(); - } - key_on_register >>= 1; - } - } - } - - output_stream->EndWrite(frames_in_this_batch); - remaining_frames -= frames_in_this_batch; - } -} - - - - -bool SPU::IsVoiceNoiseEnabled(u32 i) -{ - // TODO - another mask? - return ConvertToBoolUnchecked((s_noise_mode_register >> i) & u32(1)); -} - -s16 SPU::GetVoiceNoiseLevel() -{ - return static_cast(static_cast(s_noise_level)); -} - -bool SPU::IsPitchModulationEnabled(u32 i) -{ - return ((i > 0) && ConvertToBoolUnchecked((s_pitch_modulation_enable_register >> i) & u32(1))); -} - -static constexpr s32 ApplyVolume(s32 sample, s16 volume) -{ - return (sample * s32(volume)) >> 15; -} - -std::tuple SPU::SampleVoice(u32 voice_index) -{ - Voice& voice = s_voices[voice_index]; - - - // What's this? - if (!voice.IsOn() && !s_SPUCNT.irq9_enable) - { - voice.last_volume = 0; - return {}; - } - - if (!voice.IsOn()) - { - voice.last_volume = 0; - } - - // No need to read from block, we will keep a buffer - TODO - if (!voice.has_samples) - { - ADPCMBlock block; - ReadADPCMBlock(voice.current_address, &block); - voice.DecodeBlock(block); - voice.has_samples = true; - - if (voice.current_block_flags.loopStart() && !voice.ignore_loop_address) - { - // Log_TracePrintf("Voice %u loop start @ 0x%08X", voice_index, ZeroExtend32(voice.current_address)); - voice.regs.adpcm_repeat_address = voice.current_address; - } - } - - // skip interpolation when the volume is muted anyway - s32 volume; - if (voice.regs.adsr_volume != 0) - { - // interpolate/sample and apply ADSR volume - s32 sample; - if (IsVoiceNoiseEnabled(voice_index)) - sample = GetVoiceNoiseLevel(); - else - sample = voice.Interpolate(); - - volume = ApplyVolume(sample, voice.regs.adsr_volume); - } - else - { - volume = 0; - } - - voice.last_volume = volume; - - if (voice.adsr_phase != ADSRPhase::Off) - voice.TickADSR(); - - // Pitch modulation - u16 step = voice.regs.adpcm_sample_rate; - if (IsPitchModulationEnabled(voice_index)) - { - const s32 factor = std::clamp(s_voices[voice_index - 1].last_volume, -0x8000, 0x7FFF) + 0x8000; - step = Truncate16(static_cast((SignExtend32(step) * factor) >> 15)); - } - step = std::min(step, 0x3FFF); - - // Shouldn't ever overflow because if sample_index == 27, step == 0x4000 there won't be a carry out from the - // interpolation index. If there is a carry out, bit 12 will never be 1, so it'll never add more than 4 to - // sample_index, which should never be >27. - //DebugAssert(voice.counter.sample_index < NUM_SAMPLES_PER_ADPCM_BLOCK); - voice.counter.bits += step; - - if (voice.counter.sample_index >= NUM_SAMPLES_PER_ADPCM_BLOCK) - { - // next block - voice.counter.sample_index -= NUM_SAMPLES_PER_ADPCM_BLOCK; - voice.has_samples = false; - voice.is_first_block = false; - voice.current_address += 2; - - // handle flags - if (voice.current_block_flags.loopEnd()) - { - s_endx_register |= (u32(1) << voice_index); - voice.current_address = voice.regs.adpcm_repeat_address & ~u16(1); - - if (!voice.current_block_flags.loopRepeat()) - { - //Log_TracePrintf("Voice %u loop end+mute @ 0x%08X", voice_index, ZeroExtend32(voice.current_address)); - voice.ForceOff(); - } - else - { - //Log_TracePrintf("Voice %u loop end+repeat @ 0x%08X", voice_index, ZeroExtend32(voice.current_address)); - } - } - } - - // apply per-channel volume - const s32 left = ApplyVolume(volume, voice.left_volume.current_level); - const s32 right = ApplyVolume(volume, voice.right_volume.current_level); - voice.left_volume.Tick(); - voice.right_volume.Tick(); - - //#ifdef SPU_DUMP_ALL_VOICES - // if (s_voice_dump_writers[voice_index]) - // { - // const s16 dump_samples[2] = {static_cast(Clamp16(left)), static_cast(Clamp16(right))}; - // s_voice_dump_writers[voice_index]->WriteFrames(dump_samples, 1); - // } - //#endif - - return std::make_tuple(left, right); -} - -//void SPU::UpdateNoise() -//{ -// // Dr Hell's noise waveform, implementation borrowed from pcsx-r. -// static constexpr std::array noise_wave_add = { -// {1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, -// 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1}}; -// static constexpr std::array noise_freq_add = {{0, 84, 140, 180, 210}}; -// -// const u32 noise_clock = s_SPUCNT.noise_clock; -// const u32 level = (0x8000u >> (noise_clock >> 2)) << 16; -// -// s_noise_count += 0x10000u + noise_freq_add[noise_clock & 3u]; -// if ((s_noise_count & 0xFFFFu) >= noise_freq_add[4]) -// { -// s_noise_count += 0x10000; -// s_noise_count -= noise_freq_add[noise_clock & 3u]; -// } -// -// if (s_noise_count < level) -// return; -// -// s_noise_count %= level; -// s_noise_level = (s_noise_level << 1) | noise_wave_add[(s_noise_level >> 10) & 63u]; -//} - - -//void SPU::ReadADPCMBlock(u16 address, ADPCMBlock* block) -//{ -// u32 ram_address = (ZeroExtend32(address) * 8) & RAM_MASK; -// if (IsRAMIRQTriggerable() && (CheckRAMIRQ(ram_address) || CheckRAMIRQ((ram_address + 8) & RAM_MASK))) -// { -// Log_DebugPrintf("Trigger IRQ @ %08X %04X from ADPCM reader", ram_address, ram_address / 8); -// TriggerRAMIRQ(); -// } -// -// // fast path - no wrap-around -// if ((ram_address + sizeof(ADPCMBlock)) <= RAM_SIZE) -// { -// std::memcpy(block, &s_ram[ram_address], sizeof(ADPCMBlock)); -// return; -// } -// -// block->shift_filter.bits = s_ram[ram_address]; -// ram_address = (ram_address + 1) & RAM_MASK; -// block->flags.bits = s_ram[ram_address]; -// ram_address = (ram_address + 1) & RAM_MASK; -// for (u32 i = 0; i < 14; i++) -// { -// block->data[i] = s_ram[ram_address]; -// ram_address = (ram_address + 1) & RAM_MASK; -// } -//} - -//////////////////////////// -// VOICE -//////////////////////////// - -void Voice::DecodeBlock(const ADPCMBlock& block) -{ - static constexpr std::array filter_table_pos = {{0, 60, 115, 98, 122}}; - static constexpr std::array filter_table_neg = {{0, 0, -52, -55, -60}}; - - // store samples needed for interpolation - current_block_samples[2] = current_block_samples[NUM_SAMPLES_FROM_LAST_ADPCM_BLOCK + NUM_SAMPLES_PER_ADPCM_BLOCK - 1]; - current_block_samples[1] = current_block_samples[NUM_SAMPLES_FROM_LAST_ADPCM_BLOCK + NUM_SAMPLES_PER_ADPCM_BLOCK - 2]; - current_block_samples[0] = current_block_samples[NUM_SAMPLES_FROM_LAST_ADPCM_BLOCK + NUM_SAMPLES_PER_ADPCM_BLOCK - 3]; - - // pre-lookup - const u8 shift = block.GetShift(); - const u8 filter_index = block.GetFilter(); - const s32 filter_pos = filter_table_pos[filter_index]; - const s32 filter_neg = filter_table_neg[filter_index]; - s16 last_samples[2] = {adpcm_last_samples[0], adpcm_last_samples[1]}; - - // samples - for (u32 i = 0; i < NUM_SAMPLES_PER_ADPCM_BLOCK; i++) - { - // extend 4-bit to 16-bit, apply shift from header and mix in previous samples - s32 sample = s32(static_cast(ZeroExtend16(block.GetNibble(i)) << 12) >> shift); - sample += (last_samples[0] * filter_pos) >> 6; - sample += (last_samples[1] * filter_neg) >> 6; - - last_samples[1] = last_samples[0]; - current_block_samples[NUM_SAMPLES_FROM_LAST_ADPCM_BLOCK + i] = last_samples[0] = static_cast(Clamp16(sample)); - } - - std::copy(last_samples, last_samples + countof(last_samples), adpcm_last_samples.begin()); - current_block_flags.bits = block.flags.bits; -} - -void Voice::TickADSR() -{ - regs.adsr_volume = adsr_envelope.Tick(regs.adsr_volume); - - if (adsr_phase != ADSRPhase::Sustain) - { - const bool reached_target = adsr_envelope.decreasing ? (regs.adsr_volume <= adsr_target) : (regs.adsr_volume >= adsr_target); - if (reached_target) - { - adsr_phase = GetNextADSRPhase(adsr_phase); - UpdateADSREnvelope(); - } - } -} - -ADSRPhase Voice::GetNextADSRPhase(ADSRPhase phase) -{ - switch (phase) - { - case ADSRPhase::Attack: - // attack -> decay - return ADSRPhase::Decay; - - case ADSRPhase::Decay: - // decay -> sustain - return ADSRPhase::Sustain; - - case ADSRPhase::Sustain: - // sustain stays in sustain until key off - return ADSRPhase::Sustain; - - default: - case ADSRPhase::Release: - // end of release disables the voice - return ADSRPhase::Off; - } -} - -void Voice::UpdateADSREnvelope() -{ - switch (adsr_phase) - { - case ADSRPhase::Off: - adsr_target = 0; - adsr_envelope.Reset(0, false, false); - return; - - case ADSRPhase::Attack: - adsr_target = 32767; // 0 -> max - adsr_envelope.Reset(regs.adsr.attack_rate, false, regs.adsr.attack_exponential); - break; - - case ADSRPhase::Decay: - adsr_target = static_cast(std::min((u32(regs.adsr.sustain_level.GetValue()) + 1) * 0x800, - ENVELOPE_MAX_VOLUME)); // max -> sustain level - adsr_envelope.Reset(regs.adsr.decay_rate_shr2 << 2, true, true); - break; - - case ADSRPhase::Sustain: - adsr_target = 0; - adsr_envelope.Reset(regs.adsr.sustain_rate, regs.adsr.sustain_direction_decrease, regs.adsr.sustain_exponential); - break; - - case ADSRPhase::Release: - adsr_target = 0; - adsr_envelope.Reset(regs.adsr.release_rate_shr2 << 2, true, regs.adsr.release_exponential); - break; - - default: - break; - } -} - -s32 Voice::Interpolate() const -{ - 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 // - }}; - - // the interpolation index is based on the source files sample rate - const u8 i = counter.interpolation_index; - const u32 s = NUM_SAMPLES_FROM_LAST_ADPCM_BLOCK + ZeroExtend32(counter.sample_index.GetValue()); - - // interpolate on 4 most recent samples - s32 out = s32(gauss[0x0FF - i]) * s32(current_block_samples[s - 3]); // oldest - out += s32(gauss[0x1FF - i]) * s32(current_block_samples[s - 2]); // older - out += s32(gauss[0x100 + i]) * s32(current_block_samples[s - 1]); // old - out += s32(gauss[0x000 + i]) * s32(current_block_samples[s - 0]); // new - return out >> 15; -} - -//////////////////////////// -// REVERB -//////////////////////////// -// 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 SPU::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 SPU::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 SPU::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)); -} - -void SPU::ProcessReverb(s16 left_in, s16 right_in, s32* left_out, s32* right_out) -{ - 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; - - s32 out[2]; - if (s_reverb_resample_buffer_position & 1u) - { - 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.reverb_master_enable) - { - const s16 IIR_INPUT_A = ReverbSat((((ReverbRead(s_reverb_registers.IIR_SRC_A[lr ^ 0]) * s_reverb_registers.IIR_COEF) >> 14) + ((downsampled[lr] * s_reverb_registers.IN_COEF[lr]) >> 14)) >> 1); - const s16 IIR_INPUT_B = ReverbSat((((ReverbRead(s_reverb_registers.IIR_SRC_B[lr ^ 1]) * s_reverb_registers.IIR_COEF) >> 14) + ((downsampled[lr] * s_reverb_registers.IN_COEF[lr]) >> 14)) >> 1); - const s16 IIR_A = ReverbSat((((IIR_INPUT_A * s_reverb_registers.IIR_ALPHA) >> 14) + (IIASM(s_reverb_registers.IIR_ALPHA, ReverbRead(s_reverb_registers.IIR_DEST_A[lr], -1)) >> 14)) >> 1); - const s16 IIR_B = ReverbSat((((IIR_INPUT_B * s_reverb_registers.IIR_ALPHA) >> 14) + (IIASM(s_reverb_registers.IIR_ALPHA, ReverbRead(s_reverb_registers.IIR_DEST_B[lr], -1)) >> 14)) >> 1); - - ReverbWrite(s_reverb_registers.IIR_DEST_A[lr], IIR_A); - ReverbWrite(s_reverb_registers.IIR_DEST_B[lr], IIR_B); - } - - const s32 ACC = ((ReverbRead(s_reverb_registers.ACC_SRC_A[lr]) * s_reverb_registers.ACC_COEF_A) >> 14) + ((ReverbRead(s_reverb_registers.ACC_SRC_B[lr]) * s_reverb_registers.ACC_COEF_B) >> 14) + ((ReverbRead(s_reverb_registers.ACC_SRC_C[lr]) * s_reverb_registers.ACC_COEF_C) >> 14) + ((ReverbRead(s_reverb_registers.ACC_SRC_D[lr]) * s_reverb_registers.ACC_COEF_D) >> 14); - - const s16 FB_A = ReverbRead(s_reverb_registers.MIX_DEST_A[lr] - s_reverb_registers.FB_SRC_A); - const s16 FB_B = ReverbRead(s_reverb_registers.MIX_DEST_B[lr] - s_reverb_registers.FB_SRC_B); - const s16 MDA = ReverbSat((ACC + ((FB_A * ReverbNeg(s_reverb_registers.FB_ALPHA)) >> 14)) >> 1); - const s16 MDB = ReverbSat( - FB_A + ((((MDA * s_reverb_registers.FB_ALPHA) >> 14) + ((FB_B * ReverbNeg(s_reverb_registers.FB_X)) >> 14)) >> 1)); - const s16 IVB = ReverbSat(FB_B + ((MDB * s_reverb_registers.FB_X) >> 15)); - - if (s_SPUCNT.reverb_master_enable) - { - ReverbWrite(s_reverb_registers.MIX_DEST_A[lr], MDA); - ReverbWrite(s_reverb_registers.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 - { - 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; - - 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); - -#ifdef SPU_DUMP_ALL_VOICES - if (s_voice_dump_writers[NUM_VOICES]) - { - const s16 dump_samples[2] = {static_cast(Clamp16(s_last_reverb_output[0])), - static_cast(Clamp16(s_last_reverb_output[1]))}; - s_voice_dump_writers[NUM_VOICES]->WriteFrames(dump_samples, 1); - } -#endif -} - - - -//void SPU::WriteToCaptureBuffer(u32 index, s16 value) -//{ -// const u32 ram_address = (index * CAPTURE_BUFFER_SIZE_PER_CHANNEL) | ZeroExtend16(s_capture_buffer_position); -// // Log_DebugPrintf("write to capture buffer %u (0x%08X) <- 0x%04X", index, ram_address, u16(value)); -// std::memcpy(&s_ram[ram_address], &value, sizeof(value)); -// if (IsRAMIRQTriggerable() && CheckRAMIRQ(ram_address)) -// { -// // Log_DebugPrintf("Trigger IRQ @ %08X %04X from capture buffer", ram_address, ram_address / 8); -// TriggerRAMIRQ(); -// } -//} -// -//void SPU::IncrementCaptureBufferPosition() -//{ -// s_capture_buffer_position += sizeof(s16); -// s_capture_buffer_position %= CAPTURE_BUFFER_SIZE_PER_CHANNEL; -// s_SPUSTAT.second_half_capture_buffer = s_capture_buffer_position >= (CAPTURE_BUFFER_SIZE_PER_CHANNEL / 2); -//} \ No newline at end of file diff --git a/Source/AliveLibCommon/audio/mixer/SPUEMU.hpp b/Source/AliveLibCommon/audio/mixer/SPUEMU.hpp deleted file mode 100644 index 0abf3e344..000000000 --- a/Source/AliveLibCommon/audio/mixer/SPUEMU.hpp +++ /dev/null @@ -1,34 +0,0 @@ -#pragma once - -enum class ADSRPhase : u8 -{ - Off = 0, - Attack = 1, - Decay = 2, - Sustain = 3, - Release = 4 -}; - -class SPU -{ -public: - - // should be called at 44100hz - void Execute(u32 remaining_frames); - - s16 GetVoiceNoiseLevel(); - bool IsVoiceNoiseEnabled(u32 i); - bool IsPitchModulationEnabled(u32 i); - - ADSRPhase GetNextADSRPhase(ADSRPhase phase); - - std::tuple SampleVoice(u32 voice_index); - void UpdateNoise(); - void ProcessReverb(s16 left_in, s16 right_in, s32* left_out, s32* right_out); - void WriteToCaptureBuffer(u32 index, s16 value); - void IncrementCaptureBufferPosition(); - - u32 ReverbMemoryAddress(u32 address); - s16 ReverbRead(u32 address, s32 offset = 0); - void ReverbWrite(u32 address, s16 data); -}; \ No newline at end of file diff --git a/Source/AliveLibCommon/audio/mixer/Voice.cpp b/Source/AliveLibCommon/audio/mixer/Voice.cpp deleted file mode 100644 index 553472ab6..000000000 --- a/Source/AliveLibCommon/audio/mixer/Voice.cpp +++ /dev/null @@ -1,57 +0,0 @@ -#pragma once -#include "Voice.hpp" -#include "AliveAudio.hpp" - -namespace psx { - -float Voice::GetSample() -{ - if (b_Dead) // Dont return anything if dead. This voice should now be removed. - return 0; - - // ActiveDecayLevel -= ((1.0 / AliveAudioSampleRate) / m_Tone->DecayTime); - - if (ActiveDecayLevel < 0) - ActiveDecayLevel = 0; - - if (!b_NoteOn) - { - ActiveReleaseLevel -= ((1.0 / AliveAudioSampleRate) / m_Tone->ReleaseTime); - - ActiveReleaseLevel -= ((1.0 / AliveAudioSampleRate) / m_Tone->DecayTime); // Hacks?!?! -------------------- - - if (ActiveReleaseLevel <= 0) // Release is done. So the voice is done. - { - b_Dead = true; - ActiveReleaseLevel = 0; - } - } - else - { - ActiveAttackLevel += ((1.0 / AliveAudioSampleRate) / m_Tone->AttackTime); - - if (ActiveAttackLevel > 1) - ActiveAttackLevel = 1; - } - - // For some reason, for samples that dont loop, they need to be cut off 1 sample earlier. - // Todo: Revise this. Maybe its the loop flag at the end of the sample!? - if ((m_Tone->Loop) ? f_SampleOffset >= m_Tone->m_Sample->i_SampleSize : (f_SampleOffset >= m_Tone->m_Sample->i_SampleSize - 1)) - { - if (m_Tone->Loop) - f_SampleOffset = 0; - else - { - b_Dead = true; - return 0; // Voice is dead now, so don't return anything. - } - } - - // From midi.cpp -- auto freq = pow(1.059463094359, (f64) (note - v29) * 0.00390625); - double sampleFrameRate = pow(1.059463, i_Note - m_Tone->c_Center + m_Tone->Pitch + f_Pitch) * (44100.0 / AliveAudioSampleRate); - f_SampleOffset += (sampleFrameRate); - - return ((float) (m_Tone->m_Sample->GetSample(float(f_SampleOffset)) * ActiveAttackLevel * ActiveDecayLevel * ((b_NoteOn) ? ActiveSustainLevel : ActiveReleaseLevel) * (f_Velocity * f_VelocityMulti))); -} - -} // namespace psx \ No newline at end of file diff --git a/Source/AliveLibCommon/audio/mixer/Voice.hpp b/Source/AliveLibCommon/audio/mixer/Voice.hpp deleted file mode 100644 index 7b617047c..000000000 --- a/Source/AliveLibCommon/audio/mixer/Voice.hpp +++ /dev/null @@ -1,35 +0,0 @@ -#pragma once -#include "../Soundbank.hpp" - -namespace psx { - -class Voice -{ -public: - Tone* m_Tone; - int i_Program; - int i_Note = 0; - bool b_Dead = false; - double f_SampleOffset = 0; - bool b_NoteOn = true; - double f_Velocity = 1.0f; - double f_VelocityMulti = 1; - double f_Pitch = 0.0f; - float f_Pan = 0.0f; - - int i_TrackID = 0; // This is used to distinguish between sounds fx and music - double f_TrackDelay = 0; // Used by the sequencer for perfect timing - bool m_UsesNoteOffDelay = false; - double f_NoteOffDelay = 0; - - // Active ADSR Levels - - double ActiveAttackLevel = 0; - double ActiveReleaseLevel = 1; - double ActiveDecayLevel = 1; - double ActiveSustainLevel = 1; - - float GetSample(); -}; - -} // namespace psx \ No newline at end of file diff --git a/Source/AliveLibCommon/audio/mixer/biquad.cpp b/Source/AliveLibCommon/audio/mixer/biquad.cpp deleted file mode 100644 index d4d9ffec4..000000000 --- a/Source/AliveLibCommon/audio/mixer/biquad.cpp +++ /dev/null @@ -1,144 +0,0 @@ -/* Simple implementation of Biquad filters -- Tom St Denis - * - * Based on the work - -Cookbook formulae for audio EQ biquad filter coefficients ---------------------------------------------------------- -by Robert Bristow-Johnson, pbjrbj@viconet.com a.k.a. robert@audioheads.com - - * Available on the web at - -http://www.smartelectronix.com/musicdsp/text/filters005.txt - - * Enjoy. - * - * This work is hereby placed in the public domain for all purposes, whether - * commercial, free [as in speech] or educational, etc. Use the code and please - * give me credit if you wish. - * - * Tom St Denis -- http://tomstdenis.home.dhs.org -*/ - -#include "biquad.hpp" - -namespace psx { - -/* Below this would be biquad.c */ -/* Computes a BiQuad filter on a sample */ -smp_type BiQuad(smp_type sample, biquad* b) -{ - smp_type result; - - /* compute result */ - result = b->a0 * sample + b->a1 * b->x1 + b->a2 * b->x2 - b->a3 * b->y1 - b->a4 * b->y2; - - /* shift x1 to x2, sample to x1 */ - b->x2 = b->x1; - b->x1 = sample; - - /* shift y1 to y2, result to y1 */ - b->y2 = b->y1; - b->y1 = result; - - return result; -} - -/* sets up a BiQuad Filter */ -biquad* BiQuad_new(int type, smp_type dbGain, smp_type freq, - smp_type srate, smp_type bandwidth) -{ - biquad* b; - smp_type A, omega, sn, cs, alpha, beta; - smp_type a0, a1, a2, b0, b1, b2; - - b = (biquad*) malloc(sizeof(biquad)); - if (b == NULL) - return NULL; - - /* setup variables */ - A = smp_type(pow(10, dbGain / 40)); - omega = smp_type(2 * M_PI * freq / srate); - sn = sin(omega); - cs = cos(omega); - alpha = smp_type(sn * sinh(M_LN2 / 2 * bandwidth * omega / sn)); - beta = sqrt(A + A); - - switch (type) - { - case LPF: - b0 = (1 - cs) / 2; - b1 = 1 - cs; - b2 = (1 - cs) / 2; - a0 = 1 + alpha; - a1 = -2 * cs; - a2 = 1 - alpha; - break; - case HPF: - b0 = (1 + cs) / 2; - b1 = -(1 + cs); - b2 = (1 + cs) / 2; - a0 = 1 + alpha; - a1 = -2 * cs; - a2 = 1 - alpha; - break; - case BPF: - b0 = alpha; - b1 = 0; - b2 = -alpha; - a0 = 1 + alpha; - a1 = -2 * cs; - a2 = 1 - alpha; - break; - case NOTCH: - b0 = 1; - b1 = -2 * cs; - b2 = 1; - a0 = 1 + alpha; - a1 = -2 * cs; - a2 = 1 - alpha; - break; - case PEQ: - b0 = 1 + (alpha * A); - b1 = -2 * cs; - b2 = 1 - (alpha * A); - a0 = 1 + (alpha / A); - a1 = -2 * cs; - a2 = 1 - (alpha / A); - break; - case LSH: - b0 = A * ((A + 1) - (A - 1) * cs + beta * sn); - b1 = 2 * A * ((A - 1) - (A + 1) * cs); - b2 = A * ((A + 1) - (A - 1) * cs - beta * sn); - a0 = (A + 1) + (A - 1) * cs + beta * sn; - a1 = -2 * ((A - 1) + (A + 1) * cs); - a2 = (A + 1) + (A - 1) * cs - beta * sn; - break; - case HSH: - b0 = A * ((A + 1) + (A - 1) * cs + beta * sn); - b1 = -2 * A * ((A - 1) + (A + 1) * cs); - b2 = A * ((A + 1) + (A - 1) * cs - beta * sn); - a0 = (A + 1) - (A - 1) * cs + beta * sn; - a1 = 2 * ((A - 1) - (A + 1) * cs); - a2 = (A + 1) - (A - 1) * cs - beta * sn; - break; - default: - free(b); - return NULL; - } - - /* precompute the coefficients */ - b->a0 = b0 / a0; - b->a1 = b1 / a0; - b->a2 = b2 / a0; - b->a3 = a1 / a0; - b->a4 = a2 / a0; - - /* zero initial samples */ - b->x1 = b->x2 = 0; - b->y1 = b->y2 = 0; - - return b; -} -/* crc==3062280887, version==4, Sat Jul 7 00:03:23 2001 */ - -} // namespace psx \ No newline at end of file diff --git a/Source/AliveLibCommon/audio/mixer/biquad.hpp b/Source/AliveLibCommon/audio/mixer/biquad.hpp deleted file mode 100644 index 5e61c3cd4..000000000 --- a/Source/AliveLibCommon/audio/mixer/biquad.hpp +++ /dev/null @@ -1,66 +0,0 @@ -/* Simple implementation of Biquad filters -- Tom St Denis - * - * Based on the work - -Cookbook formulae for audio EQ biquad filter coefficients ---------------------------------------------------------- -by Robert Bristow-Johnson, pbjrbj@viconet.com a.k.a. robert@audioheads.com - - * Available on the web at - -http://www.smartelectronix.com/musicdsp/text/filters005.txt - - * Enjoy. - * - * This work is hereby placed in the public domain for all purposes, whether - * commercial, free [as in speech] or educational, etc. Use the code and please - * give me credit if you wish. - * - * Tom St Denis -- http://tomstdenis.home.dhs.org -*/ - -#pragma once - -namespace psx { - -/* this would be biquad.h */ -#include -#include - -#ifndef M_LN2 - #define M_LN2 0.69314718055994530942 -#endif - -#ifndef M_PI - #define M_PI 3.14159265358979323846 -#endif - -/* whatever sample type you want */ -typedef float smp_type; - -/* this holds the data required to update samples thru a filter */ -typedef struct -{ - smp_type a0, a1, a2, a3, a4; - smp_type x1, x2, y1, y2; -} biquad; - -/* filter types */ -enum -{ - LPF, /* low pass filter */ - HPF, /* High pass filter */ - BPF, /* band pass filter */ - NOTCH, /* Notch Filter */ - PEQ, /* Peaking band EQ filter */ - LSH, /* Low shelf filter */ - HSH /* High shelf filter */ -}; - -smp_type BiQuad(smp_type sample, biquad* b); -biquad* BiQuad_new(int type, smp_type dbGain, /* gain of filter */ - smp_type freq, /* center frequency */ - smp_type srate, /* sampling rate */ - smp_type bandwidth); /* bandwidth in octaves */ - -} // namespace psx \ No newline at end of file From a8af51ef4beca44c6c91c829d350a94365f1508a Mon Sep 17 00:00:00 2001 From: Sean Horton <3359434+sean-horton@users.noreply.github.com> Date: Sun, 2 Apr 2023 21:06:05 -0400 Subject: [PATCH 42/82] sequences did not say they ended --- Source/AliveLibCommon/audio/Sequencer.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Source/AliveLibCommon/audio/Sequencer.cpp b/Source/AliveLibCommon/audio/Sequencer.cpp index 7323260bc..4d899fcec 100644 --- a/Source/AliveLibCommon/audio/Sequencer.cpp +++ b/Source/AliveLibCommon/audio/Sequencer.cpp @@ -9,7 +9,7 @@ namespace SPU { // SPU state std::mutex mutex; -const int VOICE_SIZE_LIMIT = 24; +const int VOICE_SIZE_LIMIT = 32; // really should be 24, but voices overlap... std::array voices; const int SEQUENCE_SIZE_LIMIT = 256; @@ -173,6 +173,8 @@ void Sequence::Reset() trackStartTime = 0; voll = 127; volr = 127; + repeats = 0; + repeatLimit = 1; } MIDIMessage* Sequence::next(u64 now) @@ -476,7 +478,7 @@ bool SPUSeqIsDone(s32 seqId) { if (seq->id == seqId) { - res |= seq->repeats >= seq->repeatLimit; + res |= seq->repeats >= seq->repeatLimit || !seq->play; } } return res; From e8e23a9ab36f875d39208e77b6e3d3d213bc1695 Mon Sep 17 00:00:00 2001 From: Sean Horton <3359434+sean-horton@users.noreply.github.com> Date: Mon, 3 Apr 2023 07:56:20 -0400 Subject: [PATCH 43/82] save some CPU cycles --- Source/AliveLibCommon/audio/Sequencer.cpp | 35 ++++++++++++++++------- Source/AliveLibCommon/audio/Sequencer.hpp | 7 +++-- 2 files changed, 29 insertions(+), 13 deletions(-) diff --git a/Source/AliveLibCommon/audio/Sequencer.cpp b/Source/AliveLibCommon/audio/Sequencer.cpp index 4d899fcec..a0a79e22a 100644 --- a/Source/AliveLibCommon/audio/Sequencer.cpp +++ b/Source/AliveLibCommon/audio/Sequencer.cpp @@ -525,6 +525,7 @@ s32 SPUOneShotPlay(s32 patchId, u8 note, s16 voll, s16 volr, u8 pitch, s32 pitch v->pitchMin = pitchMin; v->pitchMax = pitchMax; v->sample = s; + v->RefreshNoteStep(); ids |= v->id; } @@ -584,7 +585,7 @@ void SPUTick(void* udata, Uint8* stream, int len) continue; } - std::tuple s = v->tick(); + std::tuple s = v->Tick(); leftSample += std::get<0>(s); rightSample += std::get<1>(s); @@ -689,6 +690,7 @@ void SPUTickSequences() v->sample = sample; v->voll = left; v->volr = right; + v->RefreshNoteStep(); } break; @@ -722,6 +724,7 @@ void SPUTickSequences() && voices[i]->channelId == message->channelId) { voices[i]->pitch = message->bend; + voices[i]->RefreshNoteStep(); } } break; @@ -796,7 +799,7 @@ static constexpr ADSRTableEntries s_adsr_table = ComputeADSRTableEntries(); ////////////////////////// // Voice -s32 Voice::interpolate() +s32 Voice::Interpolate() { static constexpr std::array gauss = {{ -0x001, -0x001, -0x001, -0x001, -0x001, -0x001, -0x001, -0x001, // @@ -916,20 +919,29 @@ s32 Voice::interpolate() return out >> 15; } -std::tuple Voice::tick() +void Voice::RefreshNoteStep() { - if (!sample) - { - return std::make_tuple(0, 0); - } - - // INTERPOLATION + // These are expensive math operations. Previously they were called + // for each sample (44100hz). Just call refresh when the note changes + // to save CPU cycles s32 notePitch = pitch < pitchMin ? pitchMin : pitch; // or sample? u8 rootNote = sample->rootNote; u8 rootPitch = sample->rootNotePitchShift; float noteFreq = float(pow(2.0, float(note + (notePitch / 127.0f)) / 12.0f)); float rootFreq = float(pow(2.0, float(rootNote + (rootPitch / 127.0f)) / 12.0f)); float noteMultiple = noteFreq / rootFreq; + noteStep = (u16)(((float(sample->SampleRate) * noteMultiple) / 44100.0f) * 4096.0f); +} + +std::tuple Voice::Tick() +{ + if (!sample) + { + return std::make_tuple(0, 0); + } + + // INTERPOLATION + // vounter (gauss interpolation table) runs at 28 byte blocks. // move the sample offset forward every 28 bytes @@ -956,12 +968,13 @@ std::tuple Voice::tick() } s32 sampleData; - sampleData = interpolate(); + sampleData = Interpolate(); // vounter.bits has two purposes // 1. change how samples are skipped to change the samples note // 2. maintain gauss table positioning for correct interpolation - u16 step = (u16) (((float(sample->SampleRate) * noteMultiple) / 44100.0f) * 4096.0f); + // RefreshNoteStep(); - called elsewhere to save CPU cycles + u16 step = noteStep; // if (v->isPitchModulated) // { // const s32 factor = std::clamp(sampleData, -0x8000, 0x7FFF) + 0x8000; diff --git a/Source/AliveLibCommon/audio/Sequencer.hpp b/Source/AliveLibCommon/audio/Sequencer.hpp index 15998a837..9f06b8794 100644 --- a/Source/AliveLibCommon/audio/Sequencer.hpp +++ b/Source/AliveLibCommon/audio/Sequencer.hpp @@ -267,10 +267,13 @@ class Voice Sample* sample; - std::tuple tick(); + std::tuple Tick(); + + u16 noteStep; + void RefreshNoteStep(); private: - s32 interpolate(); + s32 Interpolate(); }; From df9d454be122d866739e4080bcf95e1584ed64d8 Mon Sep 17 00:00:00 2001 From: Sean Horton <3359434+sean-horton@users.noreply.github.com> Date: Mon, 3 Apr 2023 17:59:51 -0400 Subject: [PATCH 44/82] cleanup + fix seq note off --- Source/AliveLibCommon/CMakeLists.txt | 3 - Source/AliveLibCommon/audio/ADSR.cpp | 336 --------------------- Source/AliveLibCommon/audio/ADSR.hpp | 77 ----- Source/AliveLibCommon/audio/Exceptions.hpp | 22 -- Source/AliveLibCommon/audio/MidiPlayer.cpp | 20 +- Source/AliveLibCommon/audio/MidiPlayer.hpp | 1 + Source/AliveLibCommon/audio/Sequencer.cpp | 21 +- Source/AliveLibCommon/audio/Sequencer.hpp | 4 +- Source/AliveLibCommon/audio/Stream.cpp | 1 - Source/AliveLibCommon/audio/Stream.hpp | 17 ++ 10 files changed, 42 insertions(+), 460 deletions(-) delete mode 100644 Source/AliveLibCommon/audio/ADSR.cpp delete mode 100644 Source/AliveLibCommon/audio/ADSR.hpp delete mode 100644 Source/AliveLibCommon/audio/Exceptions.hpp diff --git a/Source/AliveLibCommon/CMakeLists.txt b/Source/AliveLibCommon/CMakeLists.txt index 712465b5b..724323fdc 100644 --- a/Source/AliveLibCommon/CMakeLists.txt +++ b/Source/AliveLibCommon/CMakeLists.txt @@ -37,9 +37,6 @@ SET(AliveLibSrcCommon W32CrashHandler.hpp audio/Sequencer.hpp audio/Sequencer.cpp - audio/ADSR.hpp - audio/ADSR.cpp - audio/Exceptions.hpp audio/Stream.hpp audio/Stream.cpp audio/MidiPlayer.hpp diff --git a/Source/AliveLibCommon/audio/ADSR.cpp b/Source/AliveLibCommon/audio/ADSR.cpp deleted file mode 100644 index 35fbca765..000000000 --- a/Source/AliveLibCommon/audio/ADSR.cpp +++ /dev/null @@ -1,336 +0,0 @@ -#pragma once - -#include "ADSR.hpp" - -// All of the ADSR calculations herein (except where inaccurate) are derived from Neill Corlett's work in -// reverse-engineering the Playstation 1/2 SPU unit. - -//************************************************************************************************** -// Type Redefinitions - -// This next function converts seconds to full attenuation in a linear amplitude decay scale -// and approximates the time to full attenuation in a linear DB decay scale. -double LinAmpDecayTimeToLinDBDecayTime(double secondsToFullAtten, int linearVolumeRange) -{ - double expMinDecibel = -100.0; - double linearMinDecibel = log10(1.0 / linearVolumeRange) * 20.0; - double linearToExpScale = log(linearMinDecibel - expMinDecibel) / log(2.0); - return secondsToFullAtten * linearToExpScale; -} - -//InitADSR is shamelessly ripped from P.E.Op.S. cause I'm lazy -static void InitADSR(void) // INIT ADSR -{ - unsigned long r, rs, rd; - int i; - - memset(RateTable, 0, sizeof(unsigned long) * 160); // build the rate table according to Neill's rules - - r = 3; - rs = 1; - rd = 0; - - for (i = 32; i < 160; i++) // we start at pos 32 with the real values... everything before is 0 - { - if (r < 0x3FFFFFFF) - { - r += rs; - rd++; - if (rd == 5) - { - rd = 1; - rs *= 2; - } - } - if (r > 0x3FFFFFFF) - r = 0x3FFFFFFF; - - RateTable[i] = r; - } -} - -void PSXConvADSR(REAL_ADSR* realADSR, unsigned short ADSR1, unsigned short ADSR2, bool bPS2) -{ - uint8_t Am = (ADSR1 & 0x8000) >> 15; // if 1, then Exponential, else linear - uint8_t Ar = (ADSR1 & 0x7F00) >> 8; - uint8_t Dr = (ADSR1 & 0x00F0) >> 4; - uint8_t Sl = ADSR1 & 0x000F; - uint8_t Rm = (ADSR2 & 0x0020) >> 5; - uint8_t Rr = ADSR2 & 0x001F; - - // The following are unimplemented in conversion (because DLS does not support Sustain Rate) - uint8_t Sm = (ADSR2 & 0x8000) >> 15; - uint8_t Sd = (ADSR2 & 0x4000) >> 14; - uint8_t Sr = (ADSR2 >> 6) & 0x7F; - - PSXConvADSR(realADSR, Am, Ar, Dr, Sl, Sm, Sd, Sr, Rm, Rr, bPS2); -} - - -void PSXConvADSR(REAL_ADSR* realADSR, - uint8_t Am, uint8_t Ar, uint8_t Dr, uint8_t Sl, - uint8_t Sm, uint8_t Sd, uint8_t Sr, uint8_t Rm, uint8_t Rr, bool bPS2) -{ - // Make sure all the ADSR values are within the valid ranges - if (((Am & ~0x01) != 0) || ((Ar & ~0x7F) != 0) || ((Dr & ~0x0F) != 0) || ((Sl & ~0x0F) != 0) || ((Rm & ~0x01) != 0) || ((Rr & ~0x1F) != 0) || ((Sm & ~0x01) != 0) || ((Sd & ~0x01) != 0) || ((Sr & ~0x7F) != 0)) - { - return; - } - - // PS1 games use 44k, PS2 uses 48k - double sampleRate = bPS2 ? 48000 : 44100; - - - int rateIncTable[8] = {0, 4, 6, 8, 9, 10, 11, 12}; - long envelope_level; - //long sustain_envelope_level; - double samples = 0; - unsigned long rate; - unsigned long remainder; - double timeInSecs; - //double theRate; - int l; - - if (!bRateTableInitialized) - { - InitADSR(); - bRateTableInitialized = true; - } - - //to get the dls 32 bit time cents, take log base 2 of number of seconds * 1200 * 65536 (dls1v11a.pdf p25). - - // if (RateTable[(Ar^0x7F)-0x10 + 32] == 0) - // realADSR->attack_time = 0; - // else - // { - if ((Ar ^ 0x7F) < 0x10) - Ar = 0; - if (Am == 0) //if linear Ar Mode - { - rate = RateTable[RoundToZero((Ar ^ 0x7F) - 0x10) + 32]; - samples = ceil(0x7FFFFFFF / (double) rate); - } - else if (Am == 1) - { - rate = RateTable[RoundToZero((Ar ^ 0x7F) - 0x10) + 32]; - samples = 0x60000000 / rate; - remainder = 0x60000000 % rate; - rate = RateTable[RoundToZero((Ar ^ 0x7F) - 0x18) + 32]; - samples += ceil(fmax(0, 0x1FFFFFFF - remainder) / (double) rate); - } - timeInSecs = samples / sampleRate; - realADSR->attack_time = timeInSecs; - // } - - - //Decay Time - - envelope_level = 0x7FFFFFFF; - - bool bSustainLevFound = false; - uint32_t realSustainLevel = 0; - for (l = 0; envelope_level > 0; l++) //DLS decay rate value is to -96db (silence) not the sustain level - { - if (4 * (Dr ^ 0x1F) < 0x18) - Dr = 0; - switch ((envelope_level >> 28) & 0x7) - { - case 0: - envelope_level -= RateTable[RoundToZero((4 * (Dr ^ 0x1F)) - 0x18 + 0) + 32]; - break; - case 1: - envelope_level -= RateTable[RoundToZero((4 * (Dr ^ 0x1F)) - 0x18 + 4) + 32]; - break; - case 2: - envelope_level -= RateTable[RoundToZero((4 * (Dr ^ 0x1F)) - 0x18 + 6) + 32]; - break; - case 3: - envelope_level -= RateTable[RoundToZero((4 * (Dr ^ 0x1F)) - 0x18 + 8) + 32]; - break; - case 4: - envelope_level -= RateTable[RoundToZero((4 * (Dr ^ 0x1F)) - 0x18 + 9) + 32]; - break; - case 5: - envelope_level -= RateTable[RoundToZero((4 * (Dr ^ 0x1F)) - 0x18 + 10) + 32]; - break; - case 6: - envelope_level -= RateTable[RoundToZero((4 * (Dr ^ 0x1F)) - 0x18 + 11) + 32]; - break; - case 7: - envelope_level -= RateTable[RoundToZero((4 * (Dr ^ 0x1F)) - 0x18 + 12) + 32]; - break; - } - if (!bSustainLevFound && ((envelope_level >> 27) & 0xF) <= Sl) - { - realSustainLevel = envelope_level; - bSustainLevFound = true; - } - } - samples = l; - timeInSecs = samples / sampleRate; - realADSR->decay_time = timeInSecs; - - // Sustain Rate - - envelope_level = 0x7FFFFFFF; - if (Sd == 0) // increasing... we won't even bother - { - realADSR->sustain_time = -1; - } - else - { - if (Sr == 0x7F) - realADSR->sustain_time = -1; // this is actually infinite - else - { - if (Sm == 0) // linear - { - rate = RateTable[RoundToZero((Sr ^ 0x7F) - 0x0F) + 32]; - samples = ceil(0x7FFFFFFF / (double) rate); - } - else - { - l = 0; - while (envelope_level > 0) //DLS decay rate value is to -96db (silence) not the sustain level - { - long envelope_level_diff = 0; - long envelope_level_target = 0; - - switch ((envelope_level >> 28) & 0x7) - { - case 0: - envelope_level_target = 0x00000000; - envelope_level_diff = RateTable[RoundToZero((Sr ^ 0x7F) - 0x1B + 0) + 32]; - break; - case 1: - envelope_level_target = 0x0fffffff; - envelope_level_diff = RateTable[RoundToZero((Sr ^ 0x7F) - 0x1B + 4) + 32]; - break; - case 2: - envelope_level_target = 0x1fffffff; - envelope_level_diff = RateTable[RoundToZero((Sr ^ 0x7F) - 0x1B + 6) + 32]; - break; - case 3: - envelope_level_target = 0x2fffffff; - envelope_level_diff = RateTable[RoundToZero((Sr ^ 0x7F) - 0x1B + 8) + 32]; - break; - case 4: - envelope_level_target = 0x3fffffff; - envelope_level_diff = RateTable[RoundToZero((Sr ^ 0x7F) - 0x1B + 9) + 32]; - break; - case 5: - envelope_level_target = 0x4fffffff; - envelope_level_diff = RateTable[RoundToZero((Sr ^ 0x7F) - 0x1B + 10) + 32]; - break; - case 6: - envelope_level_target = 0x5fffffff; - envelope_level_diff = RateTable[RoundToZero((Sr ^ 0x7F) - 0x1B + 11) + 32]; - break; - case 7: - envelope_level_target = 0x6fffffff; - envelope_level_diff = RateTable[RoundToZero((Sr ^ 0x7F) - 0x1B + 12) + 32]; - break; - } - - long steps = (envelope_level - envelope_level_target + (envelope_level_diff - 1)) / envelope_level_diff; - envelope_level -= (envelope_level_diff * steps); - l += steps; - } - samples = l; - } - timeInSecs = samples / sampleRate; - realADSR->sustain_time = /*Sm ? timeInSecs : */ LinAmpDecayTimeToLinDBDecayTime(timeInSecs, 0x800); - } - } - - //Sustain Level - //realADSR->sustain_level = (double)envelope_level/(double)0x7FFFFFFF;//(long)ceil((double)envelope_level * 0.030517578139210854); //in DLS, sustain level is measured as a percentage - if (Sl == 0) - realSustainLevel = 0x07FFFFFF; - realADSR->sustain_level = realSustainLevel / (double) 0x7FFFFFFF; - - - // If decay is going unused, and there's a sustain rate with sustain level close to max... - // we'll put the sustain_rate in place of the decay rate. - if ((realADSR->decay_time < 2 || (Dr == 0x0F && Sl >= 0x0C)) && Sr < 0x7E && Sd == 1) - { - realADSR->sustain_level = 0; - realADSR->decay_time = realADSR->sustain_time; - //realADSR->decay_time = 0.5; - } - - //Release Time - - //sustain_envelope_level = envelope_level; - - envelope_level = 0x7FFFFFFF; //We do this because we measure release time from max volume to 0, not from sustain level to 0 - - if (Rm == 0) //if linear Rr Mode - { - rate = RateTable[RoundToZero((4 * (Rr ^ 0x1F)) - 0x0C) + 32]; - - if (rate != 0) - samples = ceil((double) envelope_level / (double) rate); - else - samples = 0; - } - else if (Rm == 1) - { - if ((Rr ^ 0x1F) * 4 < 0x18) - Rr = 0; - for (l = 0; envelope_level > 0; l++) - { - switch ((envelope_level >> 28) & 0x7) - { - case 0: - envelope_level -= RateTable[RoundToZero((4 * (Rr ^ 0x1F)) - 0x18 + 0) + 32]; - break; - case 1: - envelope_level -= RateTable[RoundToZero((4 * (Rr ^ 0x1F)) - 0x18 + 4) + 32]; - break; - case 2: - envelope_level -= RateTable[RoundToZero((4 * (Rr ^ 0x1F)) - 0x18 + 6) + 32]; - break; - case 3: - envelope_level -= RateTable[RoundToZero((4 * (Rr ^ 0x1F)) - 0x18 + 8) + 32]; - break; - case 4: - envelope_level -= RateTable[RoundToZero((4 * (Rr ^ 0x1F)) - 0x18 + 9) + 32]; - break; - case 5: - envelope_level -= RateTable[RoundToZero((4 * (Rr ^ 0x1F)) - 0x18 + 10) + 32]; - break; - case 6: - envelope_level -= RateTable[RoundToZero((4 * (Rr ^ 0x1F)) - 0x18 + 11) + 32]; - break; - case 7: - envelope_level -= RateTable[RoundToZero((4 * (Rr ^ 0x1F)) - 0x18 + 12) + 32]; - break; - } - } - samples = l; - } - timeInSecs = samples / sampleRate; - - //theRate = timeInSecs / sustain_envelope_level; - //timeInSecs = 0x7FFFFFFF * theRate; //the release time value is more like a rate. It is the time from max value to 0, not from sustain level. - //if (Rm == 0) // if it's linear - // timeInSecs *= LINEAR_RELEASE_COMPENSATION; - - realADSR->release_time = /*Rm ? timeInSecs : */ LinAmpDecayTimeToLinDBDecayTime(timeInSecs, 0x800); - - // We need to compensate the decay and release times to represent them as the time from full vol to -100db - // where the drop in db is a fixed amount per time unit (SoundFont2 spec for vol envelopes, pg44.) - // We assume the psx envelope is using a linear scale wherein envelope_level / 2 == half loudness. - // For a linear release mode (Rm == 0), the time to reach half volume is simply half the time to reach 0. - // Half perceived loudness is -10db. Therefore, time_to_half_vol * 10 == full_time * 5 == the correct SF2 time - //realADSR->decay_time = LinAmpDecayTimeToLinDBDecayTime(realADSR->decay_time, 0x800); - //realADSR->sustain_time = LinAmpDecayTimeToLinDBDecayTime(realADSR->sustain_time, 0x800); - //realADSR->release_time = LinAmpDecayTimeToLinDBDecayTime(realADSR->release_time, 0x800); - - - - //Calculations are done, so now add the articulation data - //artic->AddADSR(attack_time, Am, decay_time, sustain_lev, release_time, 0); - return; -} \ No newline at end of file diff --git a/Source/AliveLibCommon/audio/ADSR.hpp b/Source/AliveLibCommon/audio/ADSR.hpp deleted file mode 100644 index a1c72f817..000000000 --- a/Source/AliveLibCommon/audio/ADSR.hpp +++ /dev/null @@ -1,77 +0,0 @@ -#pragma once - -#include -#include "SDL_Types.h" - -// All of the ADSR calculations herein (except where inaccurate) are derived from Neill Corlett's work in -// reverse-engineering the Playstation 1/2 SPU unit. - -//************************************************************************************************** -// Type Redefinitions - - -class DLSArt; - -static unsigned long RateTable[160]; -static bool bRateTableInitialized = 0; - -// This next function converts seconds to full attenuation in a linear amplitude decay scale -// and approximates the time to full attenuation in a linear DB decay scale. -double LinAmpDecayTimeToLinDBDecayTime(double secondsToFullAtten, int linearVolumeRange); - -typedef struct _REAL_ADSR -{ - double attack_time; - double decay_time; - double sustain_level; - double sustain_time; - double release_time; -} REAL_ADSR; - -//VAG format ----------------------------------- -typedef struct _VAGHdr //File Header -{ - unsigned int id; //ID - "VAGp" - unsigned int ver; //Version - 0x20 - unsigned int __r1; - unsigned int len; //Length of data - unsigned int rate; //Sample rate - unsigned int __r2[3]; - char title[32]; -} VAGHdr; - - -typedef struct _VAGBlk //Sample Block -{ - struct - { - unsigned char range : 4; - unsigned char filter : 4; - } other; - - struct - { - unsigned char end : 1; //End block - unsigned char looping : 1; //VAG loops - unsigned char loop : 1; //Loop start point - } flag; - - char brr[14]; //Compressed samples -} VAGBlk; - - -//InitADSR is shamelessly ripped from P.E.Op.S. cause I'm lazy -static void InitADSR(void); - -inline int RoundToZero(int val) -{ - if (val < 0) - val = 0; - return val; -} - -void PSXConvADSR(REAL_ADSR* realADSR, unsigned short ADSR1, unsigned short ADSR2, bool bPS2); - -void PSXConvADSR(REAL_ADSR* realADSR, - uint8_t Am, uint8_t Ar, uint8_t Dr, uint8_t Sl, - uint8_t Sm, uint8_t Sd, uint8_t Sr, uint8_t Rm, uint8_t Rr, bool bPS2); \ No newline at end of file diff --git a/Source/AliveLibCommon/audio/Exceptions.hpp b/Source/AliveLibCommon/audio/Exceptions.hpp deleted file mode 100644 index 886c4deff..000000000 --- a/Source/AliveLibCommon/audio/Exceptions.hpp +++ /dev/null @@ -1,22 +0,0 @@ -#include - -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; -}; - -} // namespace psx \ No newline at end of file diff --git a/Source/AliveLibCommon/audio/MidiPlayer.cpp b/Source/AliveLibCommon/audio/MidiPlayer.cpp index 382ec1d23..fd2fee700 100644 --- a/Source/AliveLibCommon/audio/MidiPlayer.cpp +++ b/Source/AliveLibCommon/audio/MidiPlayer.cpp @@ -1,7 +1,6 @@ #pragma once #include "MidiPlayer.hpp" -#include "ADSR.hpp" namespace psx { @@ -32,7 +31,8 @@ class DefaultSoundSampleParser : public SoundSampleParser Sample* sample = new Sample(); sample->m_SampleBuffer = reinterpret_cast(data); sample->i_SampleSize = size / 2; - sample->sampleRate = sampleRate; + sample->sampleRate = 44100; // non standard? Doesn't use sampleRate field? + sample->loop = sampleRate > 44100; // non-standard? samples.push_back(sample); } @@ -147,7 +147,7 @@ void MidiPlayer::SND_Load_VABS(SoundBlockInfo* pSoundBlockInfo, s32 reverb) 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, 44100); // TODO s->sampleRate? + SPU::Sample* sample = new SPU::Sample(s->m_SampleBuffer, s->i_SampleSize, s->sampleRate); patch->samples[x] = sample; sample->adsr = adsr; @@ -158,14 +158,7 @@ void MidiPlayer::SND_Load_VABS(SoundBlockInfo* pSoundBlockInfo, s32 reverb) sample->rootNotePitchShift = vagAttr->field_5_shift; sample->minNote = vagAttr->field_6_min; sample->maxNote = vagAttr->field_7_max; - - // this "works" to figure out if it's a looping sample, - // don't know why... apparently the SPU expects a specific - // 16 byte block for looping, but not available with PC samples? - REAL_ADSR realADSR; - PSXConvADSR(&realADSR, ADSR1, ADSR2, false); - sample->loop = realADSR.attack_time > 1; - // sample->loop = isLoop(sample); + sample->loop = s->loop; } ++vagAttr; } @@ -354,6 +347,8 @@ static Uint32 _MidiReadVarLen(Stream& stream) 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)); @@ -402,7 +397,8 @@ void parseMidiStream(SPU::Sequence* seq, std::vector seqData, s32 trackId for (;;) { // Read event delta time - deltaTime += _MidiReadVarLen(stream); + Uint32 delta = _MidiReadVarLen(stream); + deltaTime += delta; //std::cout << "Delta: " << delta << " over all " << deltaTime << std::endl; // Obtain the event/status byte diff --git a/Source/AliveLibCommon/audio/MidiPlayer.hpp b/Source/AliveLibCommon/audio/MidiPlayer.hpp index 6b62dd756..1f04c6658 100644 --- a/Source/AliveLibCommon/audio/MidiPlayer.hpp +++ b/Source/AliveLibCommon/audio/MidiPlayer.hpp @@ -16,6 +16,7 @@ namespace psx { s16* m_SampleBuffer; u32 i_SampleSize; u32 sampleRate; + bool loop; float GetSample(float sampleOffset); }; diff --git a/Source/AliveLibCommon/audio/Sequencer.cpp b/Source/AliveLibCommon/audio/Sequencer.cpp index a0a79e22a..05922a9e9 100644 --- a/Source/AliveLibCommon/audio/Sequencer.cpp +++ b/Source/AliveLibCommon/audio/Sequencer.cpp @@ -5,6 +5,10 @@ namespace SPU { +// modified version of: +// https://github.com/stenzek/duckstation/blob/master/src/core/spu.cpp + + ////////////////////////// // SPU state std::mutex mutex; @@ -699,7 +703,9 @@ void SPUTickSequences() { for (Voice* v : voices) { - if (v && v->sequence == seq && v->note == message->note) + if (v->inUse && v->sequence == seq + && v->channelId == message->channelId + && v->note == message->note) { v->offTime = now; } @@ -718,13 +724,13 @@ void SPUTickSequences() } case PITCH_BEND: { - for (int i = 0; i < VOICE_SIZE_LIMIT; i++) + for (Voice* v : voices) { - if (voices[i]->inUse && voices[i]->sequence == seq - && voices[i]->channelId == message->channelId) + if (v->inUse && v->sequence == seq + && v->channelId == message->channelId) { - voices[i]->pitch = message->bend; - voices[i]->RefreshNoteStep(); + v->pitch = message->bend; + v->RefreshNoteStep(); } } break; @@ -1068,9 +1074,10 @@ std::tuple Voice::Tick() } // Set the volume of the sample - s32 vol = ApplyVolume(sampleData, adsrCurrentLevel); + s32 vol = sampleData; vol = ApplyVolume(vol, (s16) (sample->volume * 129 * 2)); vol = ApplyVolume(vol, (s16) (velocity * 129 * 2)); + vol = ApplyVolume(vol, adsrCurrentLevel); // TODO - apply voll and volr as sweeps.tick()? (VolumeEnvelope) // it would be similar to the ADSR tick diff --git a/Source/AliveLibCommon/audio/Sequencer.hpp b/Source/AliveLibCommon/audio/Sequencer.hpp index 9f06b8794..ce69ecea4 100644 --- a/Source/AliveLibCommon/audio/Sequencer.hpp +++ b/Source/AliveLibCommon/audio/Sequencer.hpp @@ -173,15 +173,15 @@ class Sequence } s32 id; - s32 repeatLimit = 1; std::atomic_bool play = false; s16 voll = 127; s16 volr = 127; - float pan = 0; + float tempoUs; // BPM - defined in in microseconds (us) float ticksPerBeat; + s32 repeatLimit = 1; s32 repeats = 0; void Reset(); diff --git a/Source/AliveLibCommon/audio/Stream.cpp b/Source/AliveLibCommon/audio/Stream.cpp index c2382ab38..16bb3750c 100644 --- a/Source/AliveLibCommon/audio/Stream.cpp +++ b/Source/AliveLibCommon/audio/Stream.cpp @@ -4,7 +4,6 @@ #include #include "logger.hpp" #include "Stream.hpp" -#include "Exceptions.hpp" namespace psx { diff --git a/Source/AliveLibCommon/audio/Stream.hpp b/Source/AliveLibCommon/audio/Stream.hpp index cf3103af1..c9b602c85 100644 --- a/Source/AliveLibCommon/audio/Stream.hpp +++ b/Source/AliveLibCommon/audio/Stream.hpp @@ -7,6 +7,23 @@ 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: From 006cf9e259dbc0fbd881d70b0cdfc4e09c1d1d07 Mon Sep 17 00:00:00 2001 From: Sean Horton <3359434+sean-horton@users.noreply.github.com> Date: Mon, 3 Apr 2023 18:00:38 -0400 Subject: [PATCH 45/82] AE sample looping --- Source/AliveLibAE/Sound/Midi.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Source/AliveLibAE/Sound/Midi.cpp b/Source/AliveLibAE/Sound/Midi.cpp index c04beff4b..66489e2d3 100644 --- a/Source/AliveLibAE/Sound/Midi.cpp +++ b/Source/AliveLibAE/Sound/Midi.cpp @@ -964,11 +964,9 @@ class AESoundSampleParser : public psx::SoundSampleParser u32 sampleRate = *reinterpret_cast(&ppVabBody[pos]); pos += sizeof(u32); - sampleRate; // In AO the body is at this part. // IN AE instead it's an offset in sounds.dat - // Not sure of the reason. u32 offset = *reinterpret_cast(&ppVabBody[pos]); pos += sizeof(u32); @@ -979,7 +977,8 @@ class AESoundSampleParser : public psx::SoundSampleParser psx::Sample* sample = new psx::Sample(); sample->m_SampleBuffer = reinterpret_cast(data); sample->i_SampleSize = size / 2; - sample->sampleRate = 22050; + sample->sampleRate = 44100; // non standard? Doesn't use sampleRate field? + sample->loop = sampleRate > 44100; // non-standard? samples.push_back(sample); } From 50472c4163e4863649b0522df67c36c55b2243d6 Mon Sep 17 00:00:00 2001 From: Sean Horton <3359434+sean-horton@users.noreply.github.com> Date: Mon, 3 Apr 2023 20:08:53 -0400 Subject: [PATCH 46/82] store sequence volume on voice - seq could reset --- Source/AliveLibCommon/audio/Sequencer.cpp | 18 +++++++++++------- Source/AliveLibCommon/audio/Sequencer.hpp | 12 ++++++++---- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/Source/AliveLibCommon/audio/Sequencer.cpp b/Source/AliveLibCommon/audio/Sequencer.cpp index 05922a9e9..6aaed4bdc 100644 --- a/Source/AliveLibCommon/audio/Sequencer.cpp +++ b/Source/AliveLibCommon/audio/Sequencer.cpp @@ -411,6 +411,7 @@ void SPUReleaseVoice(Voice* v) v->velocity = 127; v->voll = 127; v->volr = 127; + v->hasSeqVol = false; v->complete = false; v->hasLooped = false; v->inUse = false; @@ -673,9 +674,9 @@ void SPUTickSequences() continue; } - s16 right = (s16) (sample->volume); + s16 right = sample->volume; s16 left = right; - s16 progPan = (s16) sample->pan; + s16 progPan = sample->pan; if (progPan < 64) { right = (right * progPan) / 63; @@ -694,6 +695,9 @@ void SPUTickSequences() v->sample = sample; v->voll = left; v->volr = right; + v->hasSeqVol = true; + v->vollSeq = v->sequence->voll; + v->volrSeq = v->sequence->volr; v->RefreshNoteStep(); } @@ -1075,8 +1079,8 @@ std::tuple Voice::Tick() // Set the volume of the sample s32 vol = sampleData; - vol = ApplyVolume(vol, (s16) (sample->volume * 129 * 2)); - vol = ApplyVolume(vol, (s16) (velocity * 129 * 2)); + vol = ApplyVolume(vol, sample->volume * 129 * 2); + vol = ApplyVolume(vol, velocity * 129 * 2); vol = ApplyVolume(vol, adsrCurrentLevel); // TODO - apply voll and volr as sweeps.tick()? (VolumeEnvelope) @@ -1085,10 +1089,10 @@ std::tuple Voice::Tick() s32 left = ApplyVolume(vol, voll * 129 * 2); s32 right = ApplyVolume(vol, volr * 129 * 2); - if (sequence) + if (hasSeqVol) { - left = ApplyVolume(left, sequence->voll * 129 * 2); - right = ApplyVolume(right, sequence->volr * 129 * 2); + left = ApplyVolume(left, vollSeq * 129 * 2); + right = ApplyVolume(right, volrSeq * 129 * 2); } return std::make_tuple(left, right); diff --git a/Source/AliveLibCommon/audio/Sequencer.hpp b/Source/AliveLibCommon/audio/Sequencer.hpp index ce69ecea4..da38ce12a 100644 --- a/Source/AliveLibCommon/audio/Sequencer.hpp +++ b/Source/AliveLibCommon/audio/Sequencer.hpp @@ -59,8 +59,8 @@ class Sample } - float volume; - float pan; + s16 volume; + s16 pan; // reverb style - 0 is none s8 reverb; @@ -239,7 +239,7 @@ class Voice bool hasLooped = false; - double f_SampleOffset = 0; + u32 f_SampleOffset = 0; Sequence* sequence = NULL; u8 patchId; u8 channelId; @@ -247,11 +247,15 @@ class Voice s32 pitch; s32 pitchMin = 0; s32 pitchMax = 127; - float velocity = 127; + s16 velocity = 127; s16 voll; s16 volr; bool inUse = false; + bool hasSeqVol = false; + s16 vollSeq; + s16 volrSeq; + bool complete = false; u64 offTime = 0; // when the note was released From d787222cd9fbd5220a2ae69445a25b1cc65cd901 Mon Sep 17 00:00:00 2001 From: Sean Horton <3359434+sean-horton@users.noreply.github.com> Date: Mon, 3 Apr 2023 21:52:56 -0400 Subject: [PATCH 47/82] change voice volumes if seq volume changes --- Source/AliveLibCommon/audio/Sequencer.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Source/AliveLibCommon/audio/Sequencer.cpp b/Source/AliveLibCommon/audio/Sequencer.cpp index 6aaed4bdc..aaf779ed2 100644 --- a/Source/AliveLibCommon/audio/Sequencer.cpp +++ b/Source/AliveLibCommon/audio/Sequencer.cpp @@ -474,6 +474,14 @@ void SPUSeqSetVolume(s32 seqId, s16 voll, s16 volr) seq->volr = volr; } } + for (Voice* v : voices) + { + if (v->sequence && v->sequence->id == seqId) + { + v->vollSeq = voll; + v->volrSeq = volr; + } + } } bool SPUSeqIsDone(s32 seqId) From 38536740061eab59403bb2a699b83e437ee688df Mon Sep 17 00:00:00 2001 From: Sean Horton <3359434+sean-horton@users.noreply.github.com> Date: Tue, 4 Apr 2023 18:45:35 -0400 Subject: [PATCH 48/82] Make sure END_TRACK occurs on beat --- Source/AliveLibCommon/audio/MidiPlayer.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/Source/AliveLibCommon/audio/MidiPlayer.cpp b/Source/AliveLibCommon/audio/MidiPlayer.cpp index fd2fee700..420f49406 100644 --- a/Source/AliveLibCommon/audio/MidiPlayer.cpp +++ b/Source/AliveLibCommon/audio/MidiPlayer.cpp @@ -435,10 +435,20 @@ void parseMidiStream(SPU::Sequence* seq, std::vector seqData, s32 trackId { 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; + msg->tick = deltaTime + (onTime - deltaTime); return; } From 562b5b25af3b57f7a11e66f9bbf43ea66b428ea9 Mon Sep 17 00:00:00 2001 From: Sean Horton <3359434+sean-horton@users.noreply.github.com> Date: Wed, 5 Apr 2023 22:41:27 -0400 Subject: [PATCH 49/82] proper volume - but samples mesed up? --- Source/AliveLibCommon/audio/MidiPlayer.cpp | 4 +- Source/AliveLibCommon/audio/Sequencer.cpp | 148 ++++++++++++++++----- Source/AliveLibCommon/audio/Sequencer.hpp | 9 +- 3 files changed, 120 insertions(+), 41 deletions(-) diff --git a/Source/AliveLibCommon/audio/MidiPlayer.cpp b/Source/AliveLibCommon/audio/MidiPlayer.cpp index 420f49406..0f5c41f83 100644 --- a/Source/AliveLibCommon/audio/MidiPlayer.cpp +++ b/Source/AliveLibCommon/audio/MidiPlayer.cpp @@ -289,8 +289,8 @@ s32 MidiPlayer::SFX_SfxDefinition_Play(SfxDefinition* sfxDef, s32 volLeft, s32 v sanitizePitch(&pitch_min, sfxDef->pitch_min); sanitizePitch(&pitch_max, sfxDef->pitch_max); - sanitizeVolume(&volLeft, 10, 127); - sanitizeVolume(&volRight, 10, 127); + sanitizeVolume(&volLeft, 0, 127); + sanitizeVolume(&volRight, 0, 127); return SPU::OneShotPlay(sfxDef->program, sfxDef->note, (s16) volLeft, (s16) volRight, (u8) std::max(pitch_min, pitch_max), pitch_min, pitch_max); } diff --git a/Source/AliveLibCommon/audio/Sequencer.cpp b/Source/AliveLibCommon/audio/Sequencer.cpp index aaf779ed2..1f29d4481 100644 --- a/Source/AliveLibCommon/audio/Sequencer.cpp +++ b/Source/AliveLibCommon/audio/Sequencer.cpp @@ -175,8 +175,8 @@ void Sequence::Reset() play = false; actionPos = 0; trackStartTime = 0; - voll = 127; - volr = 127; + //voll = 127; + //volr = 127; repeats = 0; repeatLimit = 1; } @@ -409,8 +409,8 @@ void SPUReleaseVoice(Voice* v) v->vounter.bits = 0; v->complete = false; v->velocity = 127; - v->voll = 127; - v->volr = 127; + //v->voll = 127; + //v->volr = 127; v->hasSeqVol = false; v->complete = false; v->hasLooped = false; @@ -499,6 +499,8 @@ bool SPUSeqIsDone(s32 seqId) s32 SPUOneShotPlay(s32 patchId, u8 note, s16 voll, s16 volr, u8 pitch, s32 pitchMin, s32 pitchMax) { + volr; + voll; // TODO - // - SoundEfx: apparently use sweeps and reuse the voice register (slig walking offscreen) // - OneShots: do not reuse voice regester (slig turning) @@ -510,6 +512,29 @@ s32 SPUOneShotPlay(s32 patchId, u8 note, s16 voll, s16 volr, u8 pitch, s32 pitch return 0; } + char pan; + unsigned int volr_; + unsigned int voll_; + + voll_ = voll; + volr_ = volr; + if (voll_ == volr_) + { + pan = 64; + } + else + { + if (volr_ < voll_) + { + pan = (char) ((volr_ << 6) / voll_); + } + else + { + pan = 127 - (char) ((voll_ << 6) / volr_); + voll = volr; + } + } + int ids = 0; for (Sample* s : patch->samples) { @@ -531,14 +556,14 @@ s32 SPUOneShotPlay(s32 patchId, u8 note, s16 voll, s16 volr, u8 pitch, s32 pitch v->patchId = (u8) patchId; v->note = note; - v->velocity = 127; - v->voll = voll; - v->volr = volr; + v->velocity = voll; + v->pan = pan; v->pitch = pitch; v->pitchMin = pitchMin; v->pitchMax = pitchMax; v->sample = s; v->RefreshNoteStep(); + v->RefreshVolume(); ids |= v->id; } @@ -627,8 +652,8 @@ void SPUTick(void* udata, Uint8* stream, int len) // make value usable by SDL leftSample = leftSample / 32767.0f; rightSample = rightSample / 32767.0f; - SDL_MixAudioFormat((Uint8*) (AudioStream + i), (const Uint8*) &leftSample, AUDIO_F32, sizeof(float), 70); - SDL_MixAudioFormat((Uint8*) (AudioStream + i + 1), (const Uint8*) &rightSample, AUDIO_F32, sizeof(float), 70); + SDL_MixAudioFormat((Uint8*) (AudioStream + i), (const Uint8*) &leftSample, AUDIO_F32, sizeof(float), SDL_MIX_MAXVOLUME); + SDL_MixAudioFormat((Uint8*) (AudioStream + i + 1), (const Uint8*) &rightSample, AUDIO_F32, sizeof(float), SDL_MIX_MAXVOLUME); } } @@ -682,18 +707,6 @@ void SPUTickSequences() continue; } - s16 right = sample->volume; - s16 left = right; - s16 progPan = sample->pan; - if (progPan < 64) - { - right = (right * progPan) / 63; - } - else - { - left = (left * (127 - progPan)) / 63; - } - v->sequence = seq; v->patchId = seq->channels[message->channelId]->patch->_id; v->channelId = message->channelId; @@ -701,12 +714,13 @@ void SPUTickSequences() v->velocity = message->velocity; v->note = message->note; v->sample = sample; - v->voll = left; - v->volr = right; + v->velocity = message->velocity; + v->pan = sample->pan; v->hasSeqVol = true; - v->vollSeq = v->sequence->voll; - v->volrSeq = v->sequence->volr; + v->vollSeq = seq->voll; + v->volrSeq = seq->volr; v->RefreshNoteStep(); + v->RefreshVolume(); } break; @@ -951,6 +965,75 @@ void Voice::RefreshNoteStep() noteStep = (u16)(((float(sample->SampleRate) * noteMultiple) / 44100.0f) * 4096.0f); } + +void Voice::RefreshVolume() +{ + // TODO - this logic may not be correct - duck station produces different + // values. Example for first organ in open theme (first note with velocity == 84) + // velocity=84 sampleVol=70 seqVol=70 + // Duckstation calculates=2176 | below logic calculates=661 + // Possibly the PC sample data is bad? The left right synth does + // produce the correct 4977 value (same in PC as Duckstation...) + // Possibly just need to extract ps1 samples... + // + // all volume types are + // velocity - sampleVol - (patchvol) - (masterVol=127) - seqVol + // PC version is missing patch and master? I think they are always 127 + // The PSX version has an "instrument" (patch) at 109 - but may be unrelated + s32 uVar1 = (((velocity * 127 * 0x3fff) / 0x3f01) * 127 * sample->volume) / 0x3f01; + + s32 left = uVar1; + if (hasSeqVol) + { + left = (uVar1 * vollSeq) / 127; + uVar1 = (uVar1 * volrSeq) / 127; + } + + if (sample->pan < 64) + { + uVar1 = (uVar1 * sample->pan) / 63; + } + else + { + left = (left * (127 - sample->pan)) / 63; + } + + //if (_svm_cur.field_B_patch_pan < 64) + //{ + // uVar1 = (uVar1 * _svm_cur.field_B_patch_pan) / 63; + //} + //else + //{ + left = (left * (127 - 64)) / 63; + //} + + if (pan < 64) + { + uVar1 = (uVar1 * pan) / 63; + } + else + { + left = (left * (127 - 64)) / 63; + } + + + //if (_svm_cur.field_14_seq_sep_no != 0x21) + //{ + s32 right = uVar1; + right; + left = (left * left) / 0x3fff; + right = (right * right) / 0x3fff; + + leftReg = left; + rightReg = right; + //if (velocity == 84) + //{ + // leftR = 2176; + // rightR = 2176; + //} + //std::cout << left << " " << right << std::endl; +} + std::tuple Voice::Tick() { if (!sample) @@ -1087,23 +1170,16 @@ std::tuple Voice::Tick() // Set the volume of the sample s32 vol = sampleData; - vol = ApplyVolume(vol, sample->volume * 129 * 2); - vol = ApplyVolume(vol, velocity * 129 * 2); vol = ApplyVolume(vol, adsrCurrentLevel); + // 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 - s32 left = ApplyVolume(vol, voll * 129 * 2); - s32 right = ApplyVolume(vol, volr * 129 * 2); - - if (hasSeqVol) - { - left = ApplyVolume(left, vollSeq * 129 * 2); - right = ApplyVolume(right, volrSeq * 129 * 2); - } + s32 leftA = ApplyVolume(vol, (s16) leftReg); + s32 rightA = ApplyVolume(vol, (s16) rightReg); - return std::make_tuple(left, right); + return std::make_tuple(leftA, rightA); } diff --git a/Source/AliveLibCommon/audio/Sequencer.hpp b/Source/AliveLibCommon/audio/Sequencer.hpp index da38ce12a..ccda4934d 100644 --- a/Source/AliveLibCommon/audio/Sequencer.hpp +++ b/Source/AliveLibCommon/audio/Sequencer.hpp @@ -247,9 +247,8 @@ class Voice s32 pitch; s32 pitchMin = 0; s32 pitchMax = 127; - s16 velocity = 127; - s16 voll; - s16 volr; + s32 velocity = 127; + s16 pan = 64; bool inUse = false; bool hasSeqVol = false; @@ -276,6 +275,10 @@ class Voice u16 noteStep; void RefreshNoteStep(); + s32 leftReg; + s32 rightReg; + void RefreshVolume(); + private: s32 Interpolate(); }; From 59ccb46e6bcb2cf215a53bfd8772e71ec0ac6b10 Mon Sep 17 00:00:00 2001 From: Sean Horton <3359434+sean-horton@users.noreply.github.com> Date: Thu, 6 Apr 2023 20:01:29 -0400 Subject: [PATCH 50/82] clamp audio before SDL - add comments --- Source/AliveLibCommon/audio/Sequencer.cpp | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/Source/AliveLibCommon/audio/Sequencer.cpp b/Source/AliveLibCommon/audio/Sequencer.cpp index 1f29d4481..5f6b83c59 100644 --- a/Source/AliveLibCommon/audio/Sequencer.cpp +++ b/Source/AliveLibCommon/audio/Sequencer.cpp @@ -649,6 +649,9 @@ void SPUTick(void* udata, Uint8* stream, int len) leftSample += reverb_out_left; rightSample += reverb_out_right; + leftSample = (float) Clamp16((s32) leftSample); + rightSample = (float) Clamp16((s32) rightSample); + // make value usable by SDL leftSample = leftSample / 32767.0f; rightSample = rightSample / 32767.0f; @@ -974,13 +977,16 @@ void Voice::RefreshVolume() // Duckstation calculates=2176 | below logic calculates=661 // Possibly the PC sample data is bad? The left right synth does // produce the correct 4977 value (same in PC as Duckstation...) - // Possibly just need to extract ps1 samples... + // Possibly just need to extract ps1 sample data... + // Interestingly, if the sampleVol is set to 127 for the first organ in theme, + // this calculates the same 2176 value as duckstation... // // all volume types are // velocity - sampleVol - (patchvol) - (masterVol=127) - seqVol // PC version is missing patch and master? I think they are always 127 // The PSX version has an "instrument" (patch) at 109 - but may be unrelated - s32 uVar1 = (((velocity * 127 * 0x3fff) / 0x3f01) * 127 * sample->volume) / 0x3f01; + s32 sampleVol = sample->volume; // sample->volume - UPDATE: Setting this to 127 fixes it? + s32 uVar1 = (((velocity * 127 * 0x3fff) / 0x3f01) * 127 * sampleVol) / 0x3f01; s32 left = uVar1; if (hasSeqVol) @@ -1004,7 +1010,7 @@ void Voice::RefreshVolume() //} //else //{ - left = (left * (127 - 64)) / 63; + // left = (left * (127 - 64)) / 63; //} if (pan < 64) @@ -1028,8 +1034,8 @@ void Voice::RefreshVolume() rightReg = right; //if (velocity == 84) //{ - // leftR = 2176; - // rightR = 2176; + // leftReg = 2176; + // rightReg = 2176; //} //std::cout << left << " " << right << std::endl; } From d3ecafd19645ef1303212523d9572a9caf6ae7a1 Mon Sep 17 00:00:00 2001 From: Sean Horton <3359434+sean-horton@users.noreply.github.com> Date: Thu, 6 Apr 2023 20:33:41 -0400 Subject: [PATCH 51/82] Missed a case when playing sequences --- Source/AliveLibCommon/audio/MidiPlayer.cpp | 11 ++++++----- Source/AliveLibCommon/audio/Sequencer.cpp | 6 +++++- Source/AliveLibCommon/audio/Sequencer.hpp | 2 +- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/Source/AliveLibCommon/audio/MidiPlayer.cpp b/Source/AliveLibCommon/audio/MidiPlayer.cpp index 0f5c41f83..623f46b3b 100644 --- a/Source/AliveLibCommon/audio/MidiPlayer.cpp +++ b/Source/AliveLibCommon/audio/MidiPlayer.cpp @@ -234,12 +234,13 @@ s8 MidiPlayer::SND_Seq_Table_Valid() return 1; } -s16 MidiPlayer::SND_SEQ_PlaySeq(u16 idx, s32 repeatCount, s16 bDontStop) +s16 MidiPlayer::SND_SEQ_PlaySeq(u16 idx, s32 repeatCount, s16 stopDuplicateSeq) { - bDontStop; // TODO - does this matter? - repeatCount; - idx; - return SPU::SeqPlay(idx, repeatCount) ? 1 : 0; + // 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) diff --git a/Source/AliveLibCommon/audio/Sequencer.cpp b/Source/AliveLibCommon/audio/Sequencer.cpp index 5f6b83c59..206af6d88 100644 --- a/Source/AliveLibCommon/audio/Sequencer.cpp +++ b/Source/AliveLibCommon/audio/Sequencer.cpp @@ -99,9 +99,13 @@ void SPU::SeqAdd(Sequence* seq) mutex.unlock(); } -bool SeqPlay(s32 seqId, s32 repeats) +bool SeqPlay(s32 seqId, s32 repeats, bool stopDuplicateSeq) { mutex.lock(); + if (stopDuplicateSeq) + { + SPUSeqStop(seqId); + } bool res = SPUSeqPlay(seqId, repeats); mutex.unlock(); return res; diff --git a/Source/AliveLibCommon/audio/Sequencer.hpp b/Source/AliveLibCommon/audio/Sequencer.hpp index ccda4934d..03ae2932a 100644 --- a/Source/AliveLibCommon/audio/Sequencer.hpp +++ b/Source/AliveLibCommon/audio/Sequencer.hpp @@ -304,7 +304,7 @@ void PatchAdd(Patch* patch); // 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 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); From 6498697fd4b0abc02f2bf3a7506db2a5b5d07ab0 Mon Sep 17 00:00:00 2001 From: Sean Horton <3359434+sean-horton@users.noreply.github.com> Date: Thu, 6 Apr 2023 21:13:55 -0400 Subject: [PATCH 52/82] allocate voices based on priority --- Source/AliveLibCommon/audio/MidiPlayer.cpp | 1 + Source/AliveLibCommon/audio/Sequencer.cpp | 45 +++++++--------------- Source/AliveLibCommon/audio/Sequencer.hpp | 1 + 3 files changed, 15 insertions(+), 32 deletions(-) diff --git a/Source/AliveLibCommon/audio/MidiPlayer.cpp b/Source/AliveLibCommon/audio/MidiPlayer.cpp index 623f46b3b..7763b763d 100644 --- a/Source/AliveLibCommon/audio/MidiPlayer.cpp +++ b/Source/AliveLibCommon/audio/MidiPlayer.cpp @@ -159,6 +159,7 @@ void MidiPlayer::SND_Load_VABS(SoundBlockInfo* pSoundBlockInfo, s32 reverb) sample->minNote = vagAttr->field_6_min; sample->maxNote = vagAttr->field_7_max; sample->loop = s->loop; + sample->priority = vagAttr->field_0_priority; } ++vagAttr; } diff --git a/Source/AliveLibCommon/audio/Sequencer.cpp b/Source/AliveLibCommon/audio/Sequencer.cpp index 206af6d88..aeb6f33f0 100644 --- a/Source/AliveLibCommon/audio/Sequencer.cpp +++ b/Source/AliveLibCommon/audio/Sequencer.cpp @@ -13,7 +13,7 @@ namespace SPU { // SPU state std::mutex mutex; -const int VOICE_SIZE_LIMIT = 32; // really should be 24, but voices overlap... +const int VOICE_SIZE_LIMIT = 24; // Can be increased to 32, but 24 is what PSX has std::array voices; const int SEQUENCE_SIZE_LIMIT = 256; @@ -29,7 +29,7 @@ void SPUInit(); void SPUStopAll(); void SPUReset(); -Voice* SPUObtainVoice(u8 note, u8 patchId); +Voice* SPUObtainVoice(s8 priority, u8 note, u8 patchId); void SPUReleaseVoice(Voice* v); void SPUPatchAdd(Patch* patch); @@ -337,7 +337,7 @@ void SPUStopAll() } } -Voice* SPUObtainVoice(u8 note, u8 patchId) +Voice* SPUObtainVoice(s8 priority, u8 note, u8 patchId) { note; patchId; @@ -346,7 +346,6 @@ Voice* SPUObtainVoice(u8 note, u8 patchId) // 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* pref = NULL; Voice* available = NULL; for (int i = 0; i < VOICE_SIZE_LIMIT; i++) { @@ -362,41 +361,23 @@ Voice* SPUObtainVoice(u8 note, u8 patchId) return v; } - if (note == v->note && patchId == v->patchId) + // Overlapping voices are a problem when sligs shoot continously. + // This is a simple implementation to overwrite some lower priority sound. + if (!available || v->sample->priority < priority) { - if (!pref || pref->f_SampleOffset > v->f_SampleOffset) - { - pref = v; - } - } - else if (!v->sample->loop) - { - if (!available || available->sample->len - available->f_SampleOffset > v->sample->len - v->f_SampleOffset) - { - // find the most played through sample - available = v; - } + available = v; } } - Voice* use = NULL; - if (pref) - { - use = pref; - } - else if (available) - { - use = available; - } - else + if (!available) { return NULL; } // this is voice in use that we can reuse - SPUReleaseVoice(use); - use->inUse = true; - return use; + SPUReleaseVoice(available); + available->inUse = true; + return available; } void SPUReleaseVoice(Voice* v) @@ -552,7 +533,7 @@ s32 SPUOneShotPlay(s32 patchId, u8 note, s16 voll, s16 volr, u8 pitch, s32 pitch continue; } - Voice* v = SPUObtainVoice(note, (u8) patchId); + Voice* v = SPUObtainVoice(s->priority, note, (u8) patchId); if (!v) { return 0; @@ -708,7 +689,7 @@ void SPUTickSequences() continue; } - Voice* v = SPUObtainVoice(message->note, message->patchId); + Voice* v = SPUObtainVoice(sample->priority, message->note, message->patchId); if (!v) { continue; diff --git a/Source/AliveLibCommon/audio/Sequencer.hpp b/Source/AliveLibCommon/audio/Sequencer.hpp index 03ae2932a..bd3c4fe04 100644 --- a/Source/AliveLibCommon/audio/Sequencer.hpp +++ b/Source/AliveLibCommon/audio/Sequencer.hpp @@ -59,6 +59,7 @@ class Sample } + s8 priority; s16 volume; s16 pan; From afc3a7c499338c18d5a5e8de5ffe0c9108481ceb Mon Sep 17 00:00:00 2001 From: Sean Horton <3359434+sean-horton@users.noreply.github.com> Date: Thu, 6 Apr 2023 21:41:11 -0400 Subject: [PATCH 53/82] simplify logic --- Source/AliveLibCommon/audio/Sequencer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/AliveLibCommon/audio/Sequencer.cpp b/Source/AliveLibCommon/audio/Sequencer.cpp index aeb6f33f0..ff4a087e5 100644 --- a/Source/AliveLibCommon/audio/Sequencer.cpp +++ b/Source/AliveLibCommon/audio/Sequencer.cpp @@ -363,7 +363,7 @@ Voice* SPUObtainVoice(s8 priority, u8 note, u8 patchId) // Overlapping voices are a problem when sligs shoot continously. // This is a simple implementation to overwrite some lower priority sound. - if (!available || v->sample->priority < priority) + if (v->sample->priority < priority) { available = v; } From 6473937ddf8a174a90fbc2d7dfae9bbdf52dba01 Mon Sep 17 00:00:00 2001 From: Sean Horton <3359434+sean-horton@users.noreply.github.com> Date: Sun, 23 Apr 2023 15:07:20 -0400 Subject: [PATCH 54/82] fix note/pitch frequency calculation --- Source/AliveLibCommon/audio/Sequencer.cpp | 91 ++++++++++++++++++++--- 1 file changed, 79 insertions(+), 12 deletions(-) diff --git a/Source/AliveLibCommon/audio/Sequencer.cpp b/Source/AliveLibCommon/audio/Sequencer.cpp index ff4a087e5..3e2b1ff84 100644 --- a/Source/AliveLibCommon/audio/Sequencer.cpp +++ b/Source/AliveLibCommon/audio/Sequencer.cpp @@ -634,10 +634,12 @@ void SPUTick(void* udata, Uint8* stream, int len) leftSample += reverb_out_left; rightSample += reverb_out_right; - leftSample = (float) Clamp16((s32) leftSample); - rightSample = (float) Clamp16((s32) rightSample); + leftSample = (float) ApplyVolume(Clamp16((s32) leftSample), MAX_VOLUME); // master vol value + rightSample = (float) ApplyVolume(Clamp16((s32) rightSample), MAX_VOLUME); // make value usable by SDL + // It might make sense to increase the mix volume above MIX_MAXVOUME. + // I think psx sounds like it runs a little hot, compressing audio a bit leftSample = leftSample / 32767.0f; rightSample = rightSample / 32767.0f; SDL_MixAudioFormat((Uint8*) (AudioStream + i), (const Uint8*) &leftSample, AUDIO_F32, sizeof(float), SDL_MIX_MAXVOLUME); @@ -939,23 +941,88 @@ s32 Voice::Interpolate() return out >> 15; } +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, + 8192}; + void Voice::RefreshNoteStep() { - // These are expensive math operations. Previously they were called - // for each sample (44100hz). Just call refresh when the note changes - // to save CPU cycles - s32 notePitch = pitch < pitchMin ? pitchMin : pitch; // or sample? - u8 rootNote = sample->rootNote; - u8 rootPitch = sample->rootNotePitchShift; - float noteFreq = float(pow(2.0, float(note + (notePitch / 127.0f)) / 12.0f)); - float rootFreq = float(pow(2.0, float(rootNote + (rootPitch / 127.0f)) / 12.0f)); - float noteMultiple = noteFreq / rootFreq; - noteStep = (u16)(((float(sample->SampleRate) * noteMultiple) / 44100.0f) * 4096.0f); + + // 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" + // https://pastebin.com/aq7wxDdr + + unsigned int pitchA; + SHORT calc, type; + signed int add, sfine; //, ret; + signed int fine = pitch < pitchMin ? pitchMin : (pitch > pitchMax ? pitchMax : pitch); + sfine = fine + sample->rootNotePitchShift; + if (sfine < 0) + sfine += 7; + sfine >>= 3; + + add = 0; + if (sfine > 15) + { + add = 1; + sfine -= 16; + } + + calc = (SHORT) (add + (note - (sample->rootNote - 60))); //((center + 60) - note) + add; + pitchA = _svm_ptable[16 * (calc % 12) + (short) sfine]; + type = calc / 12 - 5; + + // regular shift + s32 ret = pitchA; + if (type > 0) + ret = pitchA << type; + // negative shift + if (type < 0) + ret = pitchA >> -type; + + double multi = ((double(ret) / 4096.0) * 44100.0); + noteStep = (u16) (((((sample->SampleRate) / 44100.0)) * multi)); + // std::cout << ret << " " << (int) note << std::endl; + + // This works if we use pc vag attributes. + //s32 notePitch = pitch < pitchMin ? pitchMin : pitch; // or sample? + //u8 rootNote = sample->rootNote; + //u8 rootPitch = sample->rootNotePitchShift; + //float noteFreq = float(pow(2.0, (float(note) + (notePitch / 127.0f)) / 12.0f)); + //float rootFreq = float(pow(2.0, (float(rootNote) + (rootPitch / 127.0f)) / 12.0f)); + //float noteMultiple = noteFreq / rootFreq; + //noteStep = (u16)(((float(sample->SampleRate) * noteMultiple) / 44100.0f) * 4096.0f); } void Voice::RefreshVolume() { + // UPDATE - Using PSX vag attributes, this seems identical to duckstation + // // TODO - this logic may not be correct - duck station produces different // values. Example for first organ in open theme (first note with velocity == 84) // velocity=84 sampleVol=70 seqVol=70 From aad7e202649cb29f6fa8596d6886ccfbae993cc6 Mon Sep 17 00:00:00 2001 From: Sean Horton <3359434+sean-horton@users.noreply.github.com> Date: Mon, 24 Apr 2023 21:21:11 -0400 Subject: [PATCH 55/82] fix pitch shift --- Source/AliveLibCommon/audio/Sequencer.cpp | 36 +++++++++++++---------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/Source/AliveLibCommon/audio/Sequencer.cpp b/Source/AliveLibCommon/audio/Sequencer.cpp index 3e2b1ff84..d43cf6757 100644 --- a/Source/AliveLibCommon/audio/Sequencer.cpp +++ b/Source/AliveLibCommon/audio/Sequencer.cpp @@ -342,6 +342,9 @@ Voice* SPUObtainVoice(s8 priority, u8 note, u8 patchId) note; patchId; + // TODO - logic needs to be revisited, I think it's wrong + // Go shoot slig in rupture farms non-stop and music stops playing + // // 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 @@ -443,6 +446,10 @@ void SPUSeqStop(s32 seqId) 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); } } } @@ -642,8 +649,8 @@ void SPUTick(void* udata, Uint8* stream, int len) // I think psx sounds like it runs a little hot, compressing audio a bit leftSample = leftSample / 32767.0f; rightSample = rightSample / 32767.0f; - SDL_MixAudioFormat((Uint8*) (AudioStream + i), (const Uint8*) &leftSample, AUDIO_F32, sizeof(float), SDL_MIX_MAXVOLUME); - SDL_MixAudioFormat((Uint8*) (AudioStream + i + 1), (const Uint8*) &rightSample, AUDIO_F32, sizeof(float), SDL_MIX_MAXVOLUME); + SDL_MixAudioFormat((Uint8*) (AudioStream + i), (const Uint8*) &leftSample, AUDIO_F32, sizeof(float), SDL_MIX_MAXVOLUME * 2); + SDL_MixAudioFormat((Uint8*) (AudioStream + i + 1), (const Uint8*) &rightSample, AUDIO_F32, sizeof(float), SDL_MIX_MAXVOLUME * 2); } } @@ -894,6 +901,7 @@ s32 Voice::Interpolate() const u8 i = (u8) vounter.interpolation_index(); const u32 s = ((u32) f_SampleOffset) + ZeroExtend32(vounter.sample_index()); + // TODO - remove these if statements // interpolate on the 4 most recent samples from current position // The below `if` statements are in case we loop // we gauss the end of the sample with the beginning. @@ -979,10 +987,12 @@ void Voice::RefreshNoteStep() unsigned int pitchA; SHORT calc, type; signed int add, sfine; //, ret; - signed int fine = pitch < pitchMin ? pitchMin : (pitch > pitchMax ? pitchMax : pitch); + signed int fine = pitch < pitchMin ? pitchMin : pitch; sfine = fine + sample->rootNotePitchShift; if (sfine < 0) + { sfine += 7; + } sfine >>= 3; add = 0; @@ -999,23 +1009,19 @@ void Voice::RefreshNoteStep() // regular shift s32 ret = pitchA; if (type > 0) + { ret = pitchA << type; + } + // negative shift if (type < 0) + { ret = pitchA >> -type; + } - double multi = ((double(ret) / 4096.0) * 44100.0); - noteStep = (u16) (((((sample->SampleRate) / 44100.0)) * multi)); - // std::cout << ret << " " << (int) note << std::endl; - - // This works if we use pc vag attributes. - //s32 notePitch = pitch < pitchMin ? pitchMin : pitch; // or sample? - //u8 rootNote = sample->rootNote; - //u8 rootPitch = sample->rootNotePitchShift; - //float noteFreq = float(pow(2.0, (float(note) + (notePitch / 127.0f)) / 12.0f)); - //float rootFreq = float(pow(2.0, (float(rootNote) + (rootPitch / 127.0f)) / 12.0f)); - //float noteMultiple = noteFreq / rootFreq; - //noteStep = (u16)(((float(sample->SampleRate) * noteMultiple) / 44100.0f) * 4096.0f); + // step seems to be calculated for 8000hz samples hence the division + noteStep = (u16) ((sample->SampleRate / 8000.0) * double(ret)); + // std::cout << ret << " " << (int) note << " " << noteStep << std::endl; } From 6b2e4e47fabea3d62cffc479e54b3b293c6312ce Mon Sep 17 00:00:00 2001 From: Sean Horton <3359434+sean-horton@users.noreply.github.com> Date: Mon, 24 Apr 2023 21:22:26 -0400 Subject: [PATCH 56/82] add lookup tables to fix notes --- Source/AliveLibCommon/CMakeLists.txt | 2 +- Source/AliveLibCommon/audio/oddio.h | 2101 ++++++++++++++++++++++++++ 2 files changed, 2102 insertions(+), 1 deletion(-) create mode 100644 Source/AliveLibCommon/audio/oddio.h diff --git a/Source/AliveLibCommon/CMakeLists.txt b/Source/AliveLibCommon/CMakeLists.txt index 724323fdc..04b0996ac 100644 --- a/Source/AliveLibCommon/CMakeLists.txt +++ b/Source/AliveLibCommon/CMakeLists.txt @@ -41,7 +41,7 @@ SET(AliveLibSrcCommon audio/Stream.cpp audio/MidiPlayer.hpp audio/MidiPlayer.cpp -) + audio/oddio.h) add_library(AliveLibCommon ${AliveLibSrcCommon}) diff --git a/Source/AliveLibCommon/audio/oddio.h b/Source/AliveLibCommon/audio/oddio.h new file mode 100644 index 000000000..a9fc71553 --- /dev/null +++ b/Source/AliveLibCommon/audio/oddio.h @@ -0,0 +1,2101 @@ +#pragma once + +namespace ODDIO { + +// VH files contain vag attributes +// VB files contain audio data + +// AO SAMPLE RATE LOOKUP TABLES +// [0] == vag id +// [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 + +const std::vector> AO_D1SNDFX_RATE = {{ + {40, 22050}, + {48, 16000}, + {49, 16000}, + {82, 16000}, + {106, 22050}, + {108, 16000}, + {109, 22050}, + {113, 22050}, + {117, 22050}, + {118, 22050}}}; + +const std::vector> AO_F1SNDFX_RATE = {{ + {54, 16000}, + {55, 16000}, + {91, 22050}, + {93, 22050}, + {95, 16000}, + {96, 22050}, + {100, 22050}, + {104, 22050}, + {105, 22050}}}; + +const std::vector> AO_MUNK_RATE = {{}}; + +// S1.LVL - OPTSNDEFX.VH +const std::vector> AO_OPTSNDFX_RATE = {{ + {0, 4000}, + {6, 44100}, + {7, 44100}, + {8, 22050}, + {13, 16000}, + {15, 22050}, + {17, 16000}, + {18, 22050}, + {22, 22050}, + {26, 22050}, + {27, 22050}}}; + + +// R1.LVL - RFSNDEFX.VH +const std::vector> AO_RFSNDFX_RATE = {{ + {43, 22050}, + {53, 16000}, + {54, 16000}, + {99, 22050}, + {101, 16000}, + {102, 22050}, + {106, 22050}, + {110, 22050}, + {111, 22050}}}; + + +// AO 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. + +const std::vector> AO_D1SNDFX_VH = {{ + {0x0, 0x7f047e, 0x3e006a53, 0x0, 0xb2b17f7f, 0xdff080ff, 0x10000, 0xc100c0, 0xc300c2}, + {0x1, 0x1b380400, 0x3e006a5f, 0x0, 0xb2b17f7f, 0xdff080ff, 0x10000, 0xc100c0, 0xc300c2}, + {0x2, 0x64500400, 0x3e006a66, 0x0, 0xb2b17f7f, 0xdff080ff, 0x10000, 0xc100c0, 0xc300c2}, + {0x3, 0x40280400, 0x3e006a72, 0x0, 0xb2b17f7f, 0xdff080ff, 0x10000, 0xc100c0, 0xc300c2}, + {0x4, 0x7f7f047e, 0x3e006a54, 0x0, 0xb2b17f7f, 0xdff080ff, 0x10000, 0xc100c0, 0xc300c2}, + {0x5, 0x407f005b, 0x42420050, 0x0, 0xb2b17f7f, 0xdff080ff, 0x140000, 0xc100c0, 0xc300c2}, + {0x6, 0x407f005b, 0x4343004f, 0x0, 0xb2b17f7f, 0xdff080ff, 0x80000, 0xc100c0, 0xc300c2}, + {0x7, 0x407f047e, 0x3e006a4d, 0x0, 0xb2b17f7f, 0xdff080ff, 0x10000, 0xc100c0, 0xc300c2}, + {0x8, 0x4064007f, 0x4848003c, 0x0, 0xb2b17f7f, 0x5fe380ff, 0x1f0000, 0xc100c0, 0xc300c2}, + {0x9, 0x4064007f, 0x49490049, 0x0, 0xb2b17f7f, 0x5fe380ff, 0x200000, 0xc100c0, 0xc300c2}, + {0x10, 0x407f007f, 0x3a3a0046, 0x0, 0xb2b17f7f, 0x5feb80ff, 0x180001, 0xc100c0, 0xc300c2}, + {0x11, 0x407f007f, 0x3b3b0045, 0x0, 0xb2b17f7f, 0x5feb80ff, 0x30001, 0xc100c0, 0xc300c2}, + {0x12, 0x407f007f, 0x48480060, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x350001, 0xc100c0, 0xc300c2}, + {0x13, 0x407f007f, 0x48480060, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x20001, 0xc100c0, 0xc300c2}, + {0x14, 0x7f007f, 0x48480048, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x20001, 0xc100c0, 0xc300c2}, + {0x15, 0x7f640051, 0x4848006c, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x20001, 0xc100c0, 0xc300c2}, + {0x16, 0x640051, 0x4848326b, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x20001, 0xc100c0, 0xc300c2}, + {0x17, 0x407f007f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x330001, 0xc100c0, 0xc300c2}, + {0x18, 0x407f007f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x340001, 0xc100c0, 0xc300c2}, + {0x19, 0x407f0000, 0x39390045, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x620001, 0xc100c0, 0xc300c2}, + {0x20, 0x40150464, 0x3c3c6a66, 0x0, 0xb2b17f7f, 0xdaeb80ff, 0x10002, 0xc100c0, 0xc300c2}, + {0x21, 0x3f5f0400, 0x3b006a4e, 0x0, 0xb2b17f7f, 0xdfef80ff, 0x10002, 0xc100c0, 0xc300c2}, + {0x22, 0x540400, 0x3b006a54, 0x0, 0xb2b17f7f, 0xdffd80ff, 0x10002, 0xc100c0, 0xc300c2}, + {0x23, 0x7f540400, 0x3b006a56, 0x0, 0xb2b17f7f, 0xdffd80ff, 0x10002, 0xc100c0, 0xc300c2}, + {0x24, 0x7f007f, 0x3c3c003c, 0x0, 0xb2b17f7f, 0xdffd80ff, 0x240002, 0xc100c0, 0xc300c2}, + {0x25, 0x7f7f007f, 0x3c3c003c, 0x0, 0xb2b17f7f, 0xdffd80ff, 0x250002, 0xc100c0, 0xc300c2}, + {0x26, 0x4046007f, 0x3c3c003c, 0x0, 0xb2b17f7f, 0xdffd80ff, 0x260002, 0xc100c0, 0xc300c2}, + {0x27, 0x407f007f, 0x40400040, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x270002, 0xc100c0, 0xc300c2}, + {0x30, 0x407f007f, 0x3b3b0047, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x170003, 0xc100c0, 0xc300c2}, + {0x31, 0x407f007f, 0x3a3a0052, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0xb0003, 0xc100c0, 0xc300c2}, + {0x32, 0x407f007f, 0x43430048, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0xc0003, 0xc100c0, 0xc300c2}, + {0x33, 0x407f007f, 0x48480054, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x1e0003, 0xc100c0, 0xc300c2}, + {0x34, 0x407f007f, 0x39390051, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x280003, 0xc100c0, 0xc300c2}, + {0x40, 0x407f0050, 0x24240030, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0xd0004, 0xc100c0, 0xc300c2}, + {0x41, 0x407f0050, 0x25250031, 0x0, 0xb2b17f7f, 0xdfeba5ff, 0xd0004, 0xc100c0, 0xc300c2}, + {0x42, 0x407f0050, 0x26260032, 0x0, 0xb2b17f7f, 0xdfebaaff, 0xd0004, 0xc100c0, 0xc300c2}, + {0x43, 0x407f007f, 0x3b3b0047, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x160004, 0xc100c0, 0xc300c2}, + {0x44, 0x407f007f, 0x48480060, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x350004, 0xc100c0, 0xc300c2}, + {0x45, 0x407f007f, 0x48480054, 0x0, 0xb2b17f7f, 0xdfebacff, 0x1e0004, 0xc100c0, 0xc300c2}, + {0x50, 0x407f0050, 0x25250030, 0x0, 0xb2b17f7f, 0xdfe580ff, 0x190005, 0xc100c0, 0xc300c2}, + {0x51, 0x407f007f, 0x43434663, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x100005, 0xc100c0, 0xc300c2}, + {0x52, 0x407f007f, 0x4040005a, 0x0, 0xb2b17f7f, 0xdfed80ff, 0x680005, 0xc100c0, 0xc300c2}, + {0x53, 0x407f003c, 0x41410059, 0x0, 0xb2b17f7f, 0xdfed80ff, 0x680005, 0xc100c0, 0xc300c2}, + {0x54, 0x407f003b, 0x42420058, 0x0, 0xb2b17f7f, 0xdfef80ff, 0x680005, 0xc100c0, 0xc300c2}, + {0x55, 0x407f007f, 0x3f3f005a, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x690005, 0xc100c0, 0xc300c2}, + {0x56, 0x407f007f, 0x3d3d4661, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x8c0005, 0xc100c0, 0xc300c2}, + {0x60, 0x4064047f, 0x3c3c0060, 0x0, 0xb2b17f7f, 0x5ff380ff, 0x40006, 0xc100c0, 0xc300c2}, + {0x61, 0x4064047f, 0x3c3c0078, 0x0, 0xb2b17f7f, 0x5ff580ff, 0x50006, 0xc100c0, 0xc300c2}, + {0x62, 0x4053047f, 0x3c3c006c, 0x0, 0xb2b17f7f, 0x51f0bebf, 0x210006, 0xc100c0, 0xc300c2}, + {0x63, 0x406c047f, 0x3535006c, 0x0, 0xb2b17f7f, 0x5ff580ff, 0x50006, 0xc100c0, 0xc300c2}, + {0x64, 0x406c047f, 0x35350054, 0x0, 0xb2b17f7f, 0x5ff380ff, 0x40006, 0xc100c0, 0xc300c2}, + {0x65, 0x406c047f, 0x3636006d, 0x0, 0xb2b17f7f, 0x5ff580ff, 0x50006, 0xc100c0, 0xc300c2}, + {0x66, 0x406c047f, 0x36360055, 0x0, 0xb2b17f7f, 0x5ff380ff, 0x40006, 0xc100c0, 0xc300c2}, + {0x67, 0x4064047f, 0x3030006c, 0x0, 0xb2b17f7f, 0xdff580ff, 0x50006, 0xc100c0, 0xc300c2}, + {0x68, 0x4053047f, 0x30300060, 0x0, 0xb2b17f7f, 0xd170bebf, 0x210006, 0xc100c0, 0xc300c2}, + {0x70, 0x407f007f, 0x40400059, 0x0, 0xb2b17f7f, 0x5fef80ff, 0x1b0007, 0xc100c0, 0xc300c2}, + {0x71, 0x407f047f, 0x22220048, 0x0, 0xb2b10404, 0xdfefc0ff, 0x4c0007, 0xc100c0, 0xc300c2}, + {0x72, 0x407f0402, 0x3030003c, 0x0, 0xb2b10000, 0x5feb80ff, 0x4d0007, 0xc100c0, 0xc300c2}, + {0x73, 0x407f0002, 0x48480060, 0x0, 0xb2b10000, 0x5fee80ff, 0x4e0007, 0xc100c0, 0xc300c2}, + {0x74, 0x407f0002, 0x43430054, 0x0, 0xb2b10000, 0x5fee80ff, 0x4e0007, 0xc100c0, 0xc300c2}, + {0x75, 0x357f0000, 0x3c3c0048, 0x0, 0xb2b17f7f, 0x5fea85ff, 0x660007, 0xc100c0, 0xc300c2}, + {0x76, 0x397f0000, 0x3b3b0044, 0x0, 0xb2b17f7f, 0xdfea82ff, 0x670007, 0xc100c0, 0xc300c2}, + {0x80, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x60008, 0xc100c0, 0xc300c2}, + {0x81, 0x407f047f, 0x39390054, 0x0, 0xb2b17f7f, 0xdfef80ff, 0x90008, 0xc100c0, 0xc300c2}, + {0x82, 0x407f047f, 0x3a3a0053, 0x0, 0xb2b17f7f, 0xdfe3b0ff, 0xa0008, 0xc100c0, 0xc300c2}, + {0x90, 0x4064007f, 0x2727003c, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0xf0009, 0xc100c0, 0xc300c2}, + {0x91, 0x407f007f, 0x2626003e, 0x0, 0xb2b17f7f, 0xdfe680ff, 0xe0009, 0xc100c0, 0xc300c2}, + {0x92, 0x405a007f, 0x27270044, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x1a0009, 0xc100c0, 0xc300c2}, + {0x93, 0x407f007f, 0x28280040, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x150009, 0xc100c0, 0xc300c2}, + {0x94, 0x407f0065, 0x2525003d, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x220009, 0xc100c0, 0xc300c2}, + {0x95, 0x407f007f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x230009, 0xc100c0, 0xc300c2}, + {0x96, 0x407f0000, 0x3d3d465a, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x8e0009, 0xc100c0, 0xc300c2}, + {0x97, 0x407f0000, 0x2424003c, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x220009, 0xc100c0, 0xc300c2}, + {0x98, 0x407f0000, 0x29290041, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x150009, 0xc100c0, 0xc300c2}, + {0xa0, 0x407f007f, 0x3d3d6a4a, 0x0, 0xb2b17f7f, 0xdfef80ff, 0x7000a, 0xc100c0, 0xc300c2}, + {0xa1, 0x407f007f, 0x3c3c0060, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x2d000a, 0xc100c0, 0xc300c2}, + {0xb0, 0x407f0064, 0x3c3c0048, 0x0, 0xb2b17f7f, 0xdff080ff, 0x11000b, 0xc100c0, 0xc300c2}, + {0xb1, 0x407f0064, 0x3f3f004b, 0x0, 0xb2b17f7f, 0xdff080ff, 0x12000b, 0xc100c0, 0xc300c2}, + {0xb2, 0x407f0064, 0x3d3d0055, 0x0, 0xb2b17f7f, 0xdff080ff, 0x36000b, 0xc100c0, 0xc300c2}, + {0xb3, 0x407f0064, 0x3e3e0056, 0x0, 0xb2b17f7f, 0xdff080ff, 0x43000b, 0xc100c0, 0xc300c2}, + {0xb4, 0x407f0064, 0x41410059, 0x0, 0xb2b17f7f, 0xdff080ff, 0x44000b, 0xc100c0, 0xc300c2}, + {0xb5, 0x407f0064, 0x4040465d, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x55000b, 0xc100c0, 0xc300c2}, + {0xb6, 0x407f0064, 0x4242465f, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x56000b, 0xc100c0, 0xc300c2}, + {0xc0, 0x407f0002, 0x26260026, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x37000c, 0xc100c0, 0xc300c2}, + {0xc1, 0x407f007f, 0x27270027, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x38000c, 0xc100c0, 0xc300c2}, + {0xc2, 0x407f0064, 0x28280040, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x39000c, 0xc100c0, 0xc300c2}, + {0xc3, 0x407f0064, 0x29290041, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x3a000c, 0xc100c0, 0xc300c2}, + {0xc4, 0x407f0064, 0x2a2a0042, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x3b000c, 0xc100c0, 0xc300c2}, + {0xc5, 0x407f0064, 0x2c2c0044, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x3c000c, 0xc100c0, 0xc300c2}, + {0xd0, 0x407f0050, 0x24240024, 0x0, 0xb2b17f7f, 0x5fc380ff, 0x8f000e, 0xc100c0, 0xc300c2}, + {0xd1, 0x407f0050, 0x25250025, 0x0, 0xb2b17f7f, 0x5fc380ff, 0x90000e, 0xc100c0, 0xc300c2}, + {0xd2, 0x407f0050, 0x3d3d4659, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x91000e, 0xc100c0, 0xc300c2}, + {0xd3, 0x407f0050, 0x3e3e465a, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x91000e, 0xc100c0, 0xc300c2}, + {0xe0, 0x407f007f, 0x3b3b4659, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x630011, 0xc100c0, 0xc300c2}, + {0xf0, 0x407f0064, 0x2d2d0045, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x3d0012, 0xc100c0, 0xc300c2}, + {0xf1, 0x407f007f, 0x2e2e0046, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x3e0012, 0xc100c0, 0xc300c2}, + {0xf2, 0x407f0032, 0x2f2f0047, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x3f0012, 0xc100c0, 0xc300c2}, + {0xf3, 0x407f0032, 0x30300048, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x400012, 0xc100c0, 0xc300c2}, + {0xf4, 0x407f0032, 0x2424003c, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x410012, 0xc100c0, 0xc300c2}, + {0xf5, 0x407f0032, 0x2525003d, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x420012, 0xc100c0, 0xc300c2}, + {0x100, 0x407f007e, 0x7f006a53, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x10015, 0xc100c0, 0xc300c2}, + {0x110, 0x407f0064, 0x3d3d004e, 0x0, 0xb2b17f7f, 0xdfe380ff, 0xb0016, 0xc100c0, 0xc300c2}, + {0x111, 0x407f0064, 0x3e3e0045, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x170016, 0xc100c0, 0xc300c2}, + {0x112, 0x407f0064, 0x3f3f0046, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x170016, 0xc100c0, 0xc300c2}, + {0x113, 0x407f0064, 0x3f3f0057, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x3a0016, 0xc100c0, 0xc300c2}, + {0x114, 0x3c7f0050, 0x2121462d, 0x0, 0xb2b17f7f, 0xdfe3a4ff, 0xd0016, 0xc100c0, 0xc300c2}, + {0x115, 0x407f0050, 0x2222462d, 0x0, 0xb2b17f7f, 0xdfe3a0ff, 0xd0016, 0xc100c0, 0xc300c2}, + {0x116, 0x3c7f0050, 0x2323462d, 0x0, 0xb2b17f7f, 0xdfe3a7ff, 0xd0016, 0xc100c0, 0xc300c2}, + {0x117, 0x407f0050, 0x2424462d, 0x0, 0xb2b17f7f, 0xdfe3aaff, 0xd0016, 0xc100c0, 0xc300c2}, + {0x118, 0x407f0050, 0x2525002d, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x190016, 0xc100c0, 0xc300c2}, + {0x120, 0x407f047f, 0x3e3e0073, 0x0, 0xb2b17f7f, 0xceab80ff, 0x530018, 0xc100c0, 0xc300c2}, + {0x130, 0x2c7f007f, 0x24240030, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x1d0019, 0xc100c0, 0xc300c2}, + {0x131, 0x407f007f, 0x25250031, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x1c0019, 0xc100c0, 0xc300c2}, + {0x132, 0x547f007f, 0x24246e31, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x1d0019, 0xc100c0, 0xc300c2}, + {0x140, 0x407f0400, 0x7f000054, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x2c001b, 0xc100c0, 0xc300c2}, + {0x150, 0x407f007f, 0x7f004659, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x64001c, 0xc100c0, 0xc300c2}, + {0x160, 0x407f0052, 0x7f000054, 0x0, 0xb2b10000, 0xdfe380ff, 0x52001f, 0xc100c0, 0xc300c2}, + {0x170, 0x407f047f, 0x2424003c, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x530021, 0xc100c0, 0xc300c2}, + {0x180, 0x407f007f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x650024, 0xc100c0, 0xc300c2}, + {0x181, 0x407f007f, 0x3c3c6a50, 0x0, 0xb2b17f7f, 0xc0a380c0, 0x10024, 0xc100c0, 0xc300c2}, + {0x182, 0x407f007f, 0x3c3c0048, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x160024, 0xc100c0, 0xc300c2}, + {0x183, 0x4037007f, 0x3c3c6a64, 0x0, 0xb2b17f7f, 0xc9a380e3, 0x10024, 0xc100c0, 0xc300c2}, + {0x190, 0x407f007f, 0x3c3c0060, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x4f0026, 0xc100c0, 0xc300c2}, + {0x1a0, 0x407f007f, 0x41410456, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x540027, 0xc100c0, 0xc300c2}, + {0x1b0, 0x407f007f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x500028, 0xc100c0, 0xc300c2}, + {0x1b1, 0x407f007f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x510028, 0xc100c0, 0xc300c2}, + {0x1c0, 0x407f0400, 0x2424003c, 0x0, 0xb2b10000, 0xdfe380ff, 0x57002b, 0xc100c0, 0xc300c2}, + {0x1c1, 0x407f0400, 0x2525003d, 0x0, 0xb2b10000, 0xdfe380ff, 0x58002b, 0xc100c0, 0xc300c2}, + {0x1c2, 0x407f0400, 0x2626003e, 0x0, 0xb2b10000, 0xdfe380ff, 0x59002b, 0xc100c0, 0xc300c2}, + {0x1c3, 0x407f0000, 0x2727003f, 0x0, 0xb2b10000, 0xdfe380ff, 0x5a002b, 0xc100c0, 0xc300c2}, + {0x1c4, 0x407f0400, 0x28280040, 0x0, 0xb2b10000, 0xdfe380ff, 0x5b002b, 0xc100c0, 0xc300c2}, + {0x1c5, 0x407f0000, 0x29290041, 0x0, 0xb2b10000, 0xdfe380ff, 0x5c002b, 0xc100c0, 0xc300c2}, + {0x1c6, 0x407f0400, 0x2a2a0042, 0x0, 0xb2b10000, 0xdfe380ff, 0x5d002b, 0xc100c0, 0xc300c2}, + {0x1c7, 0x407f0000, 0x2b2b0043, 0x0, 0xb2b10000, 0xdfe380ff, 0x5e002b, 0xc100c0, 0xc300c2}, + {0x1c8, 0x407f0400, 0x2c2c0044, 0x0, 0xb2b10000, 0xdfe380ff, 0x5f002b, 0xc100c0, 0xc300c2}, + {0x1c9, 0x407f0000, 0x2d2d0045, 0x0, 0xb2b10000, 0xdfe380ff, 0x60002b, 0xc100c0, 0xc300c2}, + {0x1d0, 0x407f047f, 0x3d3d0049, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x61002c, 0xc100c0, 0xc300c2}, + {0x1e0, 0x407f0040, 0x3d3d3c56, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x8d0031, 0xc100c0, 0xc300c2}, + {0x1f0, 0x407f007f, 0x7f000048, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x6a003a, 0xc100c0, 0xc300c2}, + {0x200, 0x407f007f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x6b003b, 0xc100c0, 0xc300c2}, + {0x201, 0x407f007f, 0x3e3e465a, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x6c003b, 0xc100c0, 0xc300c2}, + {0x202, 0x407f007f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x6d003b, 0xc100c0, 0xc300c2}, + {0x203, 0x407f007f, 0x3f3f005b, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x6e003b, 0xc100c0, 0xc300c2}, + {0x210, 0x407f007f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x6f003c, 0xc100c0, 0xc300c2}, + {0x211, 0x407f007f, 0x3f3f465c, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x70003c, 0xc100c0, 0xc300c2}, + {0x220, 0x407f007f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x71003d, 0xc100c0, 0xc300c2}, + {0x221, 0x407f007f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x72003d, 0xc100c0, 0xc300c2}, + {0x222, 0x407f007f, 0x37374653, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x73003d, 0xc100c0, 0xc300c2}, + {0x223, 0x407f007f, 0x38384655, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x74003d, 0xc100c0, 0xc300c2}, + {0x224, 0x407f007f, 0x39394656, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x75003d, 0xc100c0, 0xc300c2}, + {0x225, 0x407f007f, 0x3b3b4658, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x76003d, 0xc100c0, 0xc300c2}, + {0x226, 0x407f007f, 0x3a3a4657, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x77003d, 0xc100c0, 0xc300c2}, + {0x227, 0x407f007f, 0x4040465d, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x78003d, 0xc100c0, 0xc300c2}, + {0x228, 0x407f007f, 0x4141004d, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x79003d, 0xc100c0, 0xc300c2}, + {0x230, 0x407f007f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x7a003e, 0xc100c0, 0xc300c2}, + {0x231, 0x407f007f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x7b003e, 0xc100c0, 0xc300c2}, + {0x232, 0x407f007f, 0x3e3e465b, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x7c003e, 0xc100c0, 0xc300c2}, + {0x233, 0x407f007f, 0x3f3f465c, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x7d003e, 0xc100c0, 0xc300c2}, + {0x240, 0x407f007f, 0x25254642, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x7e003f, 0xc100c0, 0xc300c2}, + {0x241, 0x407f007f, 0x26264643, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x7f003f, 0xc100c0, 0xc300c2}, + {0x242, 0x407f007f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x80003f, 0xc100c0, 0xc300c2}, + {0x243, 0x407f007f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x81003f, 0xc100c0, 0xc300c2}, + {0x250, 0x407f007f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x820040, 0xc100c0, 0xc300c2}, + {0x251, 0x407f007f, 0x3e3e465b, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x830040, 0xc100c0, 0xc300c2}, + {0x252, 0x407f007f, 0x27274644, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x840040, 0xc100c0, 0xc300c2}, + {0x253, 0x407f007f, 0x3f3f465c, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x850040, 0xc100c0, 0xc300c2}, + {0x260, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x860041, 0xc100c0, 0xc300c2}, + {0x261, 0x407f007f, 0x4040465d, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x870041, 0xc100c0, 0xc300c2}, + {0x270, 0x407f047f, 0x7f004667, 0x0, 0xb2b17f7f, 0xdfefaaff, 0x890042, 0xc100c0, 0xc300c2}, + {0x280, 0x2c7f043c, 0x2f000048, 0x0, 0xb2b10000, 0xdff080ff, 0x8a0045, 0xc100c0, 0xc300c2}, + {0x281, 0x547f0450, 0x7f300048, 0x0, 0xb2b10000, 0xdff080ff, 0x8a0045, 0xc100c0, 0xc300c2}, + {0x290, 0x7f0404, 0x53000048, 0x0, 0xb2b10000, 0xdfeb80ff, 0x50046, 0xc100c0, 0xc300c2}, + {0x291, 0x7f7f0404, 0x7f540048, 0x0, 0xb2b10000, 0xdfeb80ff, 0x50046, 0xc100c0, 0xc300c2}, + {0x2a0, 0x407f0457, 0x3f3f004b, 0x0, 0xb2b10000, 0xdfeb80ff, 0x2a004d, 0xc100c0, 0xc300c2}, + {0x2a1, 0x407f005a, 0x3b00003c, 0x0, 0xb2b10000, 0xdfeb80ff, 0x2b004d, 0xc100c0, 0xc300c2}, + {0x2a2, 0x406e0055, 0x3c3c0048, 0x0, 0xb2b10000, 0xdfeb80ff, 0x8b004d, 0xc100c0, 0xc300c2}, + {0x2b0, 0x407f0478, 0x47005a49, 0x0, 0xb2b10000, 0xdff0a0ff, 0x2e004e, 0xc100c0, 0xc300c2}, + {0x2b1, 0x407f0478, 0x7f48005d, 0x0, 0xb2b10000, 0xdfeb80ff, 0x2f004e, 0xc100c0, 0xc300c2}, + {0x2c0, 0x407f0455, 0x7f250048, 0x0, 0xb2b10000, 0xdfeb80ff, 0x30004f, 0xc100c0, 0xc300c2}, + {0x2c1, 0x6464044f, 0x24230048, 0x0, 0xb2b10000, 0xdfeb80ff, 0x30004f, 0xc100c0, 0xc300c2}, + {0x2c2, 0x1b640405, 0x2200003c, 0x0, 0xb2b10000, 0xdfeb80ff, 0x30004f, 0xc100c0, 0xc300c2}, + {0x2d0, 0x6e047f, 0x7f005a48, 0x0, 0xb2b10000, 0xdfe780ff, 0x310050, 0xc100c0, 0xc300c2}, + {0x2d1, 0x7f6e047f, 0x7f006148, 0x0, 0xb2b10000, 0xdfe780ff, 0x310050, 0xc100c0, 0xc300c2}, + {0x2e0, 0x407f047f, 0x7f00465a, 0x0, 0xb2b10000, 0x5febb4ff, 0x290051, 0xc100c0, 0xc300c2}, + {0x2f0, 0x7f047f, 0x7e000454, 0x0, 0xb2b10000, 0x5fefbbff, 0x450052, 0xc100c0, 0xc300c2}, + {0x2f1, 0x7f7f045b, 0x7e000e54, 0x0, 0xb2b10000, 0x5fefbbff, 0x450052, 0xc100c0, 0xc300c2}, + {0x300, 0x327f0454, 0x30000448, 0x0, 0xb2b10000, 0xdfeb80ff, 0x460053, 0xc100c0, 0xc300c2}, + {0x301, 0x327f0453, 0x3c310448, 0x0, 0xb2b10000, 0xdfeb80ff, 0x470053, 0xc100c0, 0xc300c2}, + {0x302, 0x5a7f0452, 0x483d0460, 0x0, 0xb2b10000, 0xdfeb80ff, 0x480053, 0xc100c0, 0xc300c2}, + {0x303, 0x287f0451, 0x7f49046c, 0x0, 0xb2b10000, 0xdfeb80ff, 0x490053, 0xc100c0, 0xc300c2}, + {0x310, 0x7f0464, 0x3c3c003c, 0x0, 0xb2b10000, 0x5feb80ff, 0x4a0054, 0xc100c0, 0xc300c2}, + {0x311, 0x7f7f005a, 0x3d3d003d, 0x0, 0xb2b10000, 0x5feb80ff, 0x4a0054, 0xc100c0, 0xc300c2}, + {0x312, 0x7f005a, 0x3e3e003e, 0x0, 0xb2b10000, 0x5feb80ff, 0x4b0054, 0xc100c0, 0xc300c2}, + {0x313, 0x7f7f0464, 0x3f3f003f, 0x0, 0xb2b10000, 0x5feb80ff, 0x4b0054, 0xc100c0, 0xc300c2}, + {0x320, 0x407f0465, 0x7f000054, 0x0, 0xb2b10000, 0xdfec80ff, 0x450055, 0xc100c0, 0xc300c2}}}; + +const std::vector> AO_D2SNDFX_VH = {{ + {0x0, 0x7f047f, 0x3e006a53, 0x0, 0xb2b17f7f, 0xdffc80ff, 0x10000, 0xc100c0, 0xc300c2}, + {0x1, 0x1b380400, 0x3e006a5f, 0x0, 0xb2b17f7f, 0xdffc80ff, 0x10000, 0xc100c0, 0xc300c2}, + {0x2, 0x64500400, 0x3e006a66, 0x0, 0xb2b17f7f, 0xdffc80ff, 0x10000, 0xc100c0, 0xc300c2}, + {0x3, 0x40280400, 0x3e006a72, 0x0, 0xb2b17f7f, 0xdffc80ff, 0x10000, 0xc100c0, 0xc300c2}, + {0x4, 0x7f7f047f, 0x3e006a54, 0x0, 0xb2b17f7f, 0xdffc80ff, 0x10000, 0xc100c0, 0xc300c2}, + {0x5, 0x407f045b, 0x42420050, 0x0, 0xb2b17f7f, 0xdff080ff, 0x110000, 0xc100c0, 0xc300c2}, + {0x6, 0x407f045b, 0x4343004f, 0x0, 0xb2b17f7f, 0xdff080ff, 0x80000, 0xc100c0, 0xc300c2}, + {0x7, 0x407f0464, 0x3e006a4d, 0x0, 0xb2b17f7f, 0xdffc80ff, 0x10000, 0xc100c0, 0xc300c2}, + {0x8, 0x4064047f, 0x4848003c, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x200000, 0xc100c0, 0xc300c2}, + {0x9, 0x4064047f, 0x49490049, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x210000, 0xc100c0, 0xc300c2}, + {0x10, 0x407f047f, 0x3a3a0046, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x170001, 0xc100c0, 0xc300c2}, + {0x11, 0x407f047f, 0x3b3b0045, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x30001, 0xc100c0, 0xc300c2}, + {0x12, 0x407f047f, 0x48480060, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x430001, 0xc100c0, 0xc300c2}, + {0x13, 0x407f047f, 0x48480060, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x20001, 0xc100c0, 0xc300c2}, + {0x14, 0x7f047f, 0x48480048, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x20001, 0xc100c0, 0xc300c2}, + {0x15, 0x7f640451, 0x4848006c, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x20001, 0xc100c0, 0xc300c2}, + {0x16, 0x640451, 0x4848326b, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x20001, 0xc100c0, 0xc300c2}, + {0x17, 0x407f0464, 0x3c3c4659, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x410001, 0xc100c0, 0xc300c2}, + {0x18, 0x407f0464, 0x3d3d465a, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x420001, 0xc100c0, 0xc300c2}, + {0x19, 0x407f0400, 0x39390045, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x5c0001, 0xc100c0, 0xc300c2}, + {0x20, 0x40150464, 0x3c3c6a66, 0x0, 0xb2b17f7f, 0xdaeb80ff, 0x10002, 0xc100c0, 0xc300c2}, + {0x21, 0x3f5f0400, 0x3b006a4e, 0x0, 0xb2b17f7f, 0x5fef80ff, 0x10002, 0xc100c0, 0xc300c2}, + {0x22, 0x540400, 0x3b006a54, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10002, 0xc100c0, 0xc300c2}, + {0x23, 0x7f540400, 0x3b006a56, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10002, 0xc100c0, 0xc300c2}, + {0x24, 0x7f007f, 0x3c3c003c, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x250002, 0xc100c0, 0xc300c2}, + {0x25, 0x7f7f007f, 0x3c3c003c, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x260002, 0xc100c0, 0xc300c2}, + {0x26, 0x4046047f, 0x3c3c003c, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x270002, 0xc100c0, 0xc300c2}, + {0x27, 0x407f047f, 0x40400040, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x280002, 0xc100c0, 0xc300c2}, + {0x30, 0x407f047f, 0x3b3b0047, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x160003, 0xc100c0, 0xc300c2}, + {0x31, 0x407f047f, 0x3a3a0052, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0xb0003, 0xc100c0, 0xc300c2}, + {0x32, 0x407f047f, 0x43430048, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0xc0003, 0xc100c0, 0xc300c2}, + {0x33, 0x407f047f, 0x48480054, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x1e0003, 0xc100c0, 0xc300c2}, + {0x34, 0x407f047f, 0x39390051, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x290003, 0xc100c0, 0xc300c2}, + {0x40, 0x407f0450, 0x24240030, 0x0, 0xb2b17f7f, 0xdfe680ff, 0xd0004, 0xc100c0, 0xc300c2}, + {0x41, 0x407f0450, 0x25250031, 0x0, 0xb2b17f7f, 0xdfe6a5ff, 0xd0004, 0xc100c0, 0xc300c2}, + {0x42, 0x407f0450, 0x26260032, 0x0, 0xb2b17f7f, 0xdfe6aaff, 0xd0004, 0xc100c0, 0xc300c2}, + {0x43, 0x407f047f, 0x3b3b0047, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x150004, 0xc100c0, 0xc300c2}, + {0x44, 0x407f047f, 0x48480060, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x430004, 0xc100c0, 0xc300c2}, + {0x45, 0x407f047f, 0x48480054, 0x0, 0xb2b17f7f, 0xdfebacff, 0x1e0004, 0xc100c0, 0xc300c2}, + {0x50, 0x4064047f, 0x3c3c0060, 0x0, 0xb2b17f7f, 0x5ff380ff, 0x40006, 0xc100c0, 0xc300c2}, + {0x51, 0x4064047f, 0x3c3c0078, 0x0, 0xb2b17f7f, 0x5ff580ff, 0x50006, 0xc100c0, 0xc300c2}, + {0x52, 0x4053047f, 0x3c3c006c, 0x0, 0xb2b17f7f, 0x5170bebf, 0x220006, 0xc100c0, 0xc300c2}, + {0x53, 0x406c047f, 0x3535006c, 0x0, 0xb2b17f7f, 0x5ff580ff, 0x50006, 0xc100c0, 0xc300c2}, + {0x54, 0x406c047f, 0x35350054, 0x0, 0xb2b17f7f, 0x5ff380ff, 0x40006, 0xc100c0, 0xc300c2}, + {0x55, 0x406c047f, 0x3636006d, 0x0, 0xb2b17f7f, 0x5ff580ff, 0x50006, 0xc100c0, 0xc300c2}, + {0x56, 0x406c047f, 0x36360055, 0x0, 0xb2b17f7f, 0x5ff380ff, 0x40006, 0xc100c0, 0xc300c2}, + {0x57, 0x4064047f, 0x3030006c, 0x0, 0xb2b17f7f, 0xdff580ff, 0x50006, 0xc100c0, 0xc300c2}, + {0x58, 0x4053047f, 0x30300060, 0x0, 0xb2b17f7f, 0xd170bebf, 0x220006, 0xc100c0, 0xc300c2}, + {0x60, 0x407f047f, 0x40400059, 0x0, 0xb2b17f7f, 0xdfef80ff, 0x1a0007, 0xc100c0, 0xc300c2}, + {0x61, 0x407f047f, 0x1f1f0048, 0x0, 0xb2b10404, 0xdfefc0ff, 0x450007, 0xc100c0, 0xc300c2}, + {0x62, 0x407f0402, 0x22226a22, 0x0, 0xb2b10000, 0xdfe380ff, 0x130007, 0xc100c0, 0xc300c2}, + {0x63, 0x407f0402, 0x23236a23, 0x0, 0xb2b10000, 0xdfe380ff, 0x140007, 0xc100c0, 0xc300c2}, + {0x64, 0x407f0402, 0x26260032, 0x0, 0xb2b10000, 0xdfe580ff, 0x1b0007, 0xc100c0, 0xc300c2}, + {0x65, 0x407f0402, 0x3c3c0448, 0x0, 0xb2b10000, 0xdfeb80ff, 0x460007, 0xc100c0, 0xc300c2}, + {0x70, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x60008, 0xc100c0, 0xc300c2}, + {0x71, 0x407f047f, 0x39390054, 0x0, 0xb2b17f7f, 0xdfef80ff, 0x90008, 0xc100c0, 0xc300c2}, + {0x72, 0x407f047f, 0x3a3a0053, 0x0, 0xb2b17f7f, 0xdfe0b0ff, 0xa0008, 0xc100c0, 0xc300c2}, + {0x80, 0x4064047f, 0x2727003c, 0x0, 0xb2b17f7f, 0xdfe680ff, 0xf0009, 0xc100c0, 0xc300c2}, + {0x81, 0x407f047f, 0x2626003e, 0x0, 0xb2b17f7f, 0xdfe680ff, 0xe0009, 0xc100c0, 0xc300c2}, + {0x82, 0x405a047f, 0x27270044, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x180009, 0xc100c0, 0xc300c2}, + {0x83, 0x407f047f, 0x28280040, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x120009, 0xc100c0, 0xc300c2}, + {0x84, 0x407f0465, 0x2525003d, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x230009, 0xc100c0, 0xc300c2}, + {0x85, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x240009, 0xc100c0, 0xc300c2}, + {0x86, 0x407f0400, 0x3d3d465a, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x830009, 0xc100c0, 0xc300c2}, + {0x87, 0x407f0400, 0x2424003c, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x230009, 0xc100c0, 0xc300c2}, + {0x88, 0x407f0400, 0x29290041, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x120009, 0xc100c0, 0xc300c2}, + {0x90, 0x407f047f, 0x3d3d6a4a, 0x0, 0xb2b17f7f, 0xdfef80ff, 0x7000a, 0xc100c0, 0xc300c2}, + {0x91, 0x407f047f, 0x3c3c0060, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x39000a, 0xc100c0, 0xc300c2}, + {0xa0, 0x407f0464, 0x3c3c0048, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x10000b, 0xc100c0, 0xc300c2}, + {0xa1, 0x407f0464, 0x3d3d0055, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x44000b, 0xc100c0, 0xc300c2}, + {0xb0, 0x407f0402, 0x26260026, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x1b000c, 0xc100c0, 0xc300c2}, + {0xb1, 0x407f047f, 0x27270027, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x19000c, 0xc100c0, 0xc300c2}, + {0xb2, 0x407f0464, 0x28280040, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x2a000c, 0xc100c0, 0xc300c2}, + {0xb3, 0x407f0464, 0x29290041, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x2b000c, 0xc100c0, 0xc300c2}, + {0xb4, 0x407f0464, 0x2a2a0042, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x2c000c, 0xc100c0, 0xc300c2}, + {0xb5, 0x407f0464, 0x2c2c0044, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x38000c, 0xc100c0, 0xc300c2}, + {0xc0, 0x407f047f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0xdff080ff, 0x3f000d, 0xc100c0, 0xc300c2}, + {0xd0, 0x407f0450, 0x24240024, 0x0, 0xb2b17f7f, 0x5fe380ff, 0x13000e, 0xc100c0, 0xc300c2}, + {0xd1, 0x407f0450, 0x25250025, 0x0, 0xb2b17f7f, 0x5fe380ff, 0x14000e, 0xc100c0, 0xc300c2}, + {0xd2, 0x407f0450, 0x3d3d4659, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x1f000e, 0xc100c0, 0xc300c2}, + {0xd3, 0x407f0450, 0x3e3e465a, 0x0, 0xb2b17f7f, 0xdfe6aaff, 0x1f000e, 0xc100c0, 0xc300c2}, + {0xe0, 0x407f047f, 0x3b3b4659, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x5e0011, 0xc100c0, 0xc300c2}, + {0xf0, 0x407f0464, 0x2d2d0045, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x2d0012, 0xc100c0, 0xc300c2}, + {0xf1, 0x407f047f, 0x2e2e0046, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x2e0012, 0xc100c0, 0xc300c2}, + {0xf2, 0x407f0432, 0x2f2f464c, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x2f0012, 0xc100c0, 0xc300c2}, + {0xf3, 0x407f0432, 0x3030464d, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x300012, 0xc100c0, 0xc300c2}, + {0xf4, 0x407f0432, 0x2424003c, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x310012, 0xc100c0, 0xc300c2}, + {0xf5, 0x407f0432, 0x2525003d, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x320012, 0xc100c0, 0xc300c2}, + {0x100, 0x407f047f, 0x30300048, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x530013, 0xc100c0, 0xc300c2}, + {0x110, 0x2c7f047f, 0x24240060, 0x0, 0xb2b10000, 0x1fc080ff, 0x5d0014, 0xc100c0, 0xc300c2}, + {0x111, 0x2c41047f, 0x24240454, 0x0, 0xb2b10000, 0x1fc080ff, 0x40014, 0xc100c0, 0xc300c2}, + {0x112, 0x407f047f, 0x2b2b0060, 0x0, 0xb2b10000, 0x1fc080ff, 0x5d0014, 0xc100c0, 0xc300c2}, + {0x113, 0x4041047f, 0x2b2b0454, 0x0, 0xb2b10000, 0x1fc080ff, 0x40014, 0xc100c0, 0xc300c2}, + {0x114, 0x547f047f, 0x30300060, 0x0, 0xb2b10000, 0x1fc080ff, 0x5d0014, 0xc100c0, 0xc300c2}, + {0x115, 0x5441047f, 0x30300454, 0x0, 0xb2b10000, 0x1fc080ff, 0x40014, 0xc100c0, 0xc300c2}, + {0x120, 0x407f007e, 0x7f006a53, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x10015, 0xc100c0, 0xc300c2}, + {0x130, 0x407f0464, 0x3d3d004e, 0x0, 0xb2b17f7f, 0xdfe080ff, 0xb0016, 0xc100c0, 0xc300c2}, + {0x131, 0x407f0464, 0x3e3e0045, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x160016, 0xc100c0, 0xc300c2}, + {0x132, 0x407f0464, 0x3f3f0046, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x160016, 0xc100c0, 0xc300c2}, + {0x133, 0x407f0464, 0x3f3f0050, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x2b0016, 0xc100c0, 0xc300c2}, + {0x134, 0x3c7f044b, 0x2121002d, 0x0, 0xb2b17f7f, 0xdfe0a4ff, 0xd0016, 0xc100c0, 0xc300c2}, + {0x135, 0x407f044b, 0x2222002d, 0x0, 0xb2b17f7f, 0xdfe0a0ff, 0xd0016, 0xc100c0, 0xc300c2}, + {0x136, 0x3c7f044b, 0x2323002d, 0x0, 0xb2b17f7f, 0xdfe0a7ff, 0xd0016, 0xc100c0, 0xc300c2}, + {0x137, 0x407f044b, 0x2424002d, 0x0, 0xb2b17f7f, 0xdfe0aaff, 0xd0016, 0xc100c0, 0xc300c2}, + {0x138, 0x407f044b, 0x2525002d, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x360016, 0xc100c0, 0xc300c2}, + {0x140, 0x407f047f, 0x3e3e0073, 0x0, 0xb2b17f7f, 0xceab80ff, 0x540018, 0xc100c0, 0xc300c2}, + {0x150, 0x2c7f047f, 0x24240030, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x1d0019, 0xc100c0, 0xc300c2}, + {0x151, 0x407f047f, 0x25250031, 0x0, 0xb2b17f7f, 0x5ffd80ff, 0x1c0019, 0xc100c0, 0xc300c2}, + {0x152, 0x547f047f, 0x24246e31, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x1d0019, 0xc100c0, 0xc300c2}, + {0x160, 0x407f0400, 0x7f000054, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x37001b, 0xc100c0, 0xc300c2}, + {0x170, 0x407f047f, 0x7f004659, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x5f001c, 0xc100c0, 0xc300c2}, + {0x180, 0x407f047f, 0x3c3c0048, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x53001e, 0xc100c0, 0xc300c2}, + {0x190, 0x407f0452, 0x3c3c465a, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x50001f, 0xc100c0, 0xc300c2}, + {0x1a0, 0x407f047f, 0x3d3d4666, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x5a0020, 0xc100c0, 0xc300c2}, + {0x1b0, 0x407f047f, 0x2424003c, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x540021, 0xc100c0, 0xc300c2}, + {0x1c0, 0x407f047f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x3e0022, 0xc100c0, 0xc300c2}, + {0x1c1, 0x407f047f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0xdff080ff, 0x3f0022, 0xc100c0, 0xc300c2}, + {0x1c2, 0x407f047f, 0x3e3e006e, 0x0, 0xb2b17f7f, 0xd16db477, 0x220022, 0xc100c0, 0xc300c2}, + {0x1c3, 0x406b047f, 0x3e3e0075, 0x0, 0xb2b17f7f, 0xdff580ff, 0x50022, 0xc100c0, 0xc300c2}, + {0x1c4, 0x4041047f, 0x3e3e005d, 0x0, 0xb2b17f7f, 0xdff380ff, 0x40022, 0xc100c0, 0xc300c2}, + {0x1d0, 0x406e0463, 0x2424003c, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x510023, 0xc100c0, 0xc300c2}, + {0x1d1, 0x406e0463, 0x2525003d, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x520023, 0xc100c0, 0xc300c2}, + {0x1e0, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x400024, 0xc100c0, 0xc300c2}, + {0x1e1, 0x407f047f, 0x3c3c6a50, 0x0, 0xb2b17f7f, 0xc02080c0, 0x10024, 0xc100c0, 0xc300c2}, + {0x1e2, 0x407f047f, 0x3c3c0048, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x150024, 0xc100c0, 0xc300c2}, + {0x1e3, 0x4037047f, 0x3c3c6a64, 0x0, 0xb2b17f7f, 0xc9a080e3, 0x10024, 0xc100c0, 0xc300c2}, + {0x1f0, 0x407f047f, 0x3c3c0060, 0x0, 0xb2b17f7f, 0x5fe080ff, 0x4c0026, 0xc100c0, 0xc300c2}, + {0x200, 0x407f047f, 0x3c3c0448, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x460027, 0xc100c0, 0xc300c2}, + {0x201, 0x407f0451, 0x3d3d0457, 0x0, 0xb2b17f7f, 0x5fcb80ff, 0x470027, 0xc100c0, 0xc300c2}, + {0x202, 0x407f047f, 0x4040044c, 0x0, 0xb2b17f7f, 0x5fcb80ff, 0x480027, 0xc100c0, 0xc300c2}, + {0x203, 0x407f0451, 0x4242045a, 0x0, 0xb2b17f7f, 0x5fcb80ff, 0x490027, 0xc100c0, 0xc300c2}, + {0x204, 0x407f044b, 0x4343044f, 0x0, 0xb2b17f7f, 0x5feb80ff, 0x4a0027, 0xc100c0, 0xc300c2}, + {0x205, 0x407f047f, 0x41410459, 0x0, 0xb2b17f7f, 0x5feb80ff, 0x4b0027, 0xc100c0, 0xc300c2}, + {0x206, 0x4075047f, 0x4444005c, 0x0, 0xb2b17f7f, 0x5fe080ff, 0x4d0027, 0xc100c0, 0xc300c2}, + {0x210, 0x407f047f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0x5fe080ff, 0x4e0028, 0xc100c0, 0xc300c2}, + {0x211, 0x407f047f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x4f0028, 0xc100c0, 0xc300c2}, + {0x220, 0x407f047f, 0x3d3d0049, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x5b002c, 0xc100c0, 0xc300c2}, + {0x221, 0x407f0419, 0x3e3e465b, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x84002c, 0xc100c0, 0xc300c2}, + {0x222, 0x407f0419, 0x3f3f465c, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x85002c, 0xc100c0, 0xc300c2}, + {0x230, 0x407f0440, 0x3d3d3c56, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x820031, 0xc100c0, 0xc300c2}, + {0x240, 0x407f047f, 0x7f000048, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x61003a, 0xc100c0, 0xc300c2}, + {0x250, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x62003b, 0xc100c0, 0xc300c2}, + {0x251, 0x407f047f, 0x3e3e465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x63003b, 0xc100c0, 0xc300c2}, + {0x252, 0x407f047f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x64003b, 0xc100c0, 0xc300c2}, + {0x253, 0x407f047f, 0x3f3f005b, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x65003b, 0xc100c0, 0xc300c2}, + {0x260, 0x407f047f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x66003c, 0xc100c0, 0xc300c2}, + {0x261, 0x407f047f, 0x3f3f465c, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x67003c, 0xc100c0, 0xc300c2}, + {0x270, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x68003d, 0xc100c0, 0xc300c2}, + {0x271, 0x407f047f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x69003d, 0xc100c0, 0xc300c2}, + {0x272, 0x407f047f, 0x37374653, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x6a003d, 0xc100c0, 0xc300c2}, + {0x273, 0x407f047f, 0x38384655, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x6b003d, 0xc100c0, 0xc300c2}, + {0x274, 0x407f047f, 0x39394656, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x6c003d, 0xc100c0, 0xc300c2}, + {0x275, 0x407f047f, 0x3b3b4658, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x6d003d, 0xc100c0, 0xc300c2}, + {0x276, 0x407f047f, 0x3a3a4657, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x6e003d, 0xc100c0, 0xc300c2}, + {0x277, 0x407f047f, 0x4040465d, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x6f003d, 0xc100c0, 0xc300c2}, + {0x278, 0x407f047f, 0x4141004d, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x70003d, 0xc100c0, 0xc300c2}, + {0x280, 0x407f047f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x71003e, 0xc100c0, 0xc300c2}, + {0x281, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x72003e, 0xc100c0, 0xc300c2}, + {0x282, 0x407f047f, 0x3e3e465b, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x73003e, 0xc100c0, 0xc300c2}, + {0x283, 0x407f047f, 0x3f3f465c, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x74003e, 0xc100c0, 0xc300c2}, + {0x290, 0x407f047f, 0x25254642, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x75003f, 0xc100c0, 0xc300c2}, + {0x291, 0x407f047f, 0x26264643, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x76003f, 0xc100c0, 0xc300c2}, + {0x292, 0x407f047f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x77003f, 0xc100c0, 0xc300c2}, + {0x293, 0x407f047f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x78003f, 0xc100c0, 0xc300c2}, + {0x2a0, 0x407f047f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x790040, 0xc100c0, 0xc300c2}, + {0x2a1, 0x407f047f, 0x3e3e465b, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x7a0040, 0xc100c0, 0xc300c2}, + {0x2a2, 0x407f047f, 0x27274644, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x7b0040, 0xc100c0, 0xc300c2}, + {0x2a3, 0x407f047f, 0x3f3f465c, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x7c0040, 0xc100c0, 0xc300c2}, + {0x2b0, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x7d0041, 0xc100c0, 0xc300c2}, + {0x2b1, 0x407f047f, 0x4040465d, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x7e0041, 0xc100c0, 0xc300c2}, + {0x2c0, 0x407f047f, 0x7f004667, 0x0, 0xb2b17f7f, 0xdfefaaff, 0x7f0042, 0xc100c0, 0xc300c2}, + {0x2d0, 0x2c7f043c, 0x2f000048, 0x0, 0xb2b10000, 0xdff080ff, 0x800045, 0xc100c0, 0xc300c2}, + {0x2d1, 0x547f0450, 0x7f300048, 0x0, 0xb2b10000, 0xdff080ff, 0x800045, 0xc100c0, 0xc300c2}, + {0x2e0, 0x7f0404, 0x53000048, 0x0, 0xb2b10000, 0xdfeb80ff, 0x50046, 0xc100c0, 0xc300c2}, + {0x2e1, 0x7f7f0404, 0x7f540048, 0x0, 0xb2b10000, 0xdfeb80ff, 0x50046, 0xc100c0, 0xc300c2}, + {0x2f0, 0x407f0454, 0x3f3f004b, 0x0, 0xb2b10000, 0x5feb80ff, 0x34004d, 0xc100c0, 0xc300c2}, + {0x2f1, 0x407f005a, 0x3b00003c, 0x0, 0xb2b10000, 0x5feb80ff, 0x35004d, 0xc100c0, 0xc300c2}, + {0x2f2, 0x406e0055, 0x3c3c0048, 0x0, 0xb2b10000, 0x5feb80ff, 0x81004d, 0xc100c0, 0xc300c2}, + {0x300, 0x407f0464, 0x47005a49, 0x0, 0xb2b10000, 0xdff0a0ff, 0x3a004e, 0xc100c0, 0xc300c2}, + {0x301, 0x407f0464, 0x7f48005d, 0x0, 0xb2b10000, 0xdfeb80ff, 0x3b004e, 0xc100c0, 0xc300c2}, + {0x310, 0x407f0455, 0x7f250048, 0x0, 0xb2b10000, 0xdfeb80ff, 0x3c004f, 0xc100c0, 0xc300c2}, + {0x311, 0x6464044f, 0x24230048, 0x0, 0xb2b10000, 0xdfeb80ff, 0x3c004f, 0xc100c0, 0xc300c2}, + {0x312, 0x1b640405, 0x2200003c, 0x0, 0xb2b10000, 0xdfeb80ff, 0x3c004f, 0xc100c0, 0xc300c2}, + {0x320, 0x6e047f, 0x7f005a48, 0x0, 0xb2b10000, 0xdfe780ff, 0x3d0050, 0xc100c0, 0xc300c2}, + {0x321, 0x7f6e047f, 0x7f006148, 0x0, 0xb2b10000, 0xdfe780ff, 0x3d0050, 0xc100c0, 0xc300c2}, + {0x330, 0x407f047f, 0x7f00465a, 0x0, 0xb2b10000, 0x5febb4ff, 0x330051, 0xc100c0, 0xc300c2}, + {0x340, 0x407f0465, 0x7f000460, 0x0, 0xb2b10000, 0xdfefbbff, 0x550052, 0xc100c0, 0xc300c2}, + {0x350, 0x327f0464, 0x30000448, 0x0, 0xb2b10000, 0xdff080ff, 0x560053, 0xc100c0, 0xc300c2}, + {0x351, 0x327f0464, 0x3c310448, 0x0, 0xb2b10000, 0xdff080ff, 0x570053, 0xc100c0, 0xc300c2}, + {0x352, 0x5a7f0464, 0x483d0460, 0x0, 0xb2b10000, 0xdff080ff, 0x580053, 0xc100c0, 0xc300c2}, + {0x353, 0x287f0464, 0x7f49046c, 0x0, 0xb2b10000, 0xdfeb80ff, 0x590053, 0xc100c0, 0xc300c2}, + {0x360, 0x407f0465, 0x7f000454, 0x0, 0xb2b10000, 0xdfec80ff, 0x550055, 0xc100c0, 0xc300c2}}}; + +const std::vector> AO_D2ENDER_VH = {{ + {0x0, 0x40150464, 0x3c3c6a66, 0x0, 0xb2b17f7f, 0xdaeb80ff, 0x10002, 0xc100c0, 0xc300c2}, + {0x1, 0x3f5f0400, 0x3b006a4e, 0x0, 0xb2b17f7f, 0x5fef80ff, 0x10002, 0xc100c0, 0xc300c2}, + {0x2, 0x540400, 0x3b006a54, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10002, 0xc100c0, 0xc300c2}, + {0x3, 0x7f540400, 0x3b006a56, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10002, 0xc100c0, 0xc300c2}, + {0x10, 0x407f047f, 0x3b3b0047, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0xd0003, 0xc100c0, 0xc300c2}, + {0x11, 0x407f047f, 0x3a3a0052, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x60003, 0xc100c0, 0xc300c2}, + {0x12, 0x407f047f, 0x43430048, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x70003, 0xc100c0, 0xc300c2}, + {0x13, 0x407f047f, 0x48480054, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x100003, 0xc100c0, 0xc300c2}, + {0x14, 0x407f047f, 0x39390051, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x110003, 0xc100c0, 0xc300c2}, + {0x20, 0x407f0450, 0x24240030, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x80004, 0xc100c0, 0xc300c2}, + {0x21, 0x407f0450, 0x25250031, 0x0, 0xb2b17f7f, 0xdfe6a5ff, 0x80004, 0xc100c0, 0xc300c2}, + {0x22, 0x407f0450, 0x26260032, 0x0, 0xb2b17f7f, 0xdfe6aaff, 0x80004, 0xc100c0, 0xc300c2}, + {0x23, 0x407f047f, 0x3b3b0047, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0xc0004, 0xc100c0, 0xc300c2}, + {0x24, 0x407f047f, 0x48480060, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x1b0004, 0xc100c0, 0xc300c2}, + {0x25, 0x407f047f, 0x48480054, 0x0, 0xb2b17f7f, 0xdfebacff, 0x100004, 0xc100c0, 0xc300c2}, + {0x30, 0x4064047f, 0x3c3c0060, 0x0, 0xb2b17f7f, 0xdff380ff, 0x5a0006, 0xc100c0, 0xc300c2}, + {0x31, 0x4064047f, 0x3c3c0078, 0x0, 0xb2b17f7f, 0xdff580ff, 0x4d0006, 0xc100c0, 0xc300c2}, + {0x32, 0x4053047f, 0x3c3c006c, 0x0, 0xb2b17f7f, 0xd170bebf, 0x5b0006, 0xc100c0, 0xc300c2}, + {0x40, 0x407f047f, 0x40400059, 0x0, 0xb2b17f7f, 0xdfef80ff, 0xe0007, 0xc100c0, 0xc300c2}, + {0x41, 0x407f047f, 0x1f1f0048, 0x0, 0xb2b10404, 0xdfefc0ff, 0x1d0007, 0xc100c0, 0xc300c2}, + {0x42, 0x407f0402, 0x22226a22, 0x0, 0xb2b10000, 0xdfe380ff, 0xa0007, 0xc100c0, 0xc300c2}, + {0x43, 0x407f0402, 0x23236a23, 0x0, 0xb2b10000, 0xdfe380ff, 0xb0007, 0xc100c0, 0xc300c2}, + {0x44, 0x407f0402, 0x26260032, 0x0, 0xb2b10000, 0xdfe580ff, 0xf0007, 0xc100c0, 0xc300c2}, + {0x45, 0x407f0402, 0x3c3c0448, 0x0, 0xb2b10000, 0xdfeb80ff, 0x1e0007, 0xc100c0, 0xc300c2}, + {0x50, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x20008, 0xc100c0, 0xc300c2}, + {0x51, 0x407f047f, 0x39390054, 0x0, 0xb2b17f7f, 0xdfef80ff, 0x40008, 0xc100c0, 0xc300c2}, + {0x52, 0x407f047f, 0x3a3a0053, 0x0, 0xb2b17f7f, 0xdfe0b0ff, 0x50008, 0xc100c0, 0xc300c2}, + {0x60, 0x4064047f, 0x2727003c, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x4f0009, 0xc100c0, 0xc300c2}, + {0x61, 0x407f047f, 0x2626003e, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x500009, 0xc100c0, 0xc300c2}, + {0x62, 0x405a047f, 0x27270044, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x510009, 0xc100c0, 0xc300c2}, + {0x63, 0x407f047f, 0x28280040, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x520009, 0xc100c0, 0xc300c2}, + {0x64, 0x407f0465, 0x2525003d, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x530009, 0xc100c0, 0xc300c2}, + {0x65, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x540009, 0xc100c0, 0xc300c2}, + {0x66, 0x407f0400, 0x3d3d465a, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x550009, 0xc100c0, 0xc300c2}, + {0x67, 0x407f0400, 0x2424003c, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x530009, 0xc100c0, 0xc300c2}, + {0x68, 0x407f0400, 0x29290041, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x520009, 0xc100c0, 0xc300c2}, + {0x70, 0x407f047f, 0x3d3d6a4a, 0x0, 0xb2b17f7f, 0xdfef80ff, 0x3000a, 0xc100c0, 0xc300c2}, + {0x71, 0x407f047f, 0x3c3c0060, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x15000a, 0xc100c0, 0xc300c2}, + {0x80, 0x407f047e, 0x3c3c0048, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x9000b, 0xc100c0, 0xc300c2}, + {0x81, 0x407f047e, 0x3d3d0055, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x1c000b, 0xc100c0, 0xc300c2}, + {0x90, 0x407f047f, 0x3b3b4659, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x6e0011, 0xc100c0, 0xc300c2}, + {0xa0, 0x407f047f, 0x30300048, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x280013, 0xc100c0, 0xc300c2}, + {0xb0, 0x407f007e, 0x7f006a53, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x10015, 0xc100c0, 0xc300c2}, + {0xc0, 0x407f047f, 0x7f004659, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x6f001c, 0xc100c0, 0xc300c2}, + {0xd0, 0x407f047f, 0x3d3d4666, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x2b0020, 0xc100c0, 0xc300c2}, + {0xe0, 0x407f047f, 0x2424003c, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x290021, 0xc100c0, 0xc300c2}, + {0xf0, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x1a0024, 0xc100c0, 0xc300c2}, + {0xf1, 0x407f047f, 0x3c3c6a50, 0x0, 0xb2b17f7f, 0xc02080c0, 0x10024, 0xc100c0, 0xc300c2}, + {0xf2, 0x407f047f, 0x3c3c0048, 0x0, 0xb2b17f7f, 0x1fc080ff, 0xc0024, 0xc100c0, 0xc300c2}, + {0xf3, 0x4037047d, 0x3c3c6a64, 0x0, 0xb2b17f7f, 0xc9a080e3, 0x10024, 0xc100c0, 0xc300c2}, + {0x100, 0x407f047f, 0x3c3c0060, 0x0, 0xb2b17f7f, 0x5fe080ff, 0x240026, 0xc100c0, 0xc300c2}, + {0x110, 0x407f047f, 0x3c3c0448, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x1e0027, 0xc100c0, 0xc300c2}, + {0x111, 0x407f0451, 0x3d3d0457, 0x0, 0xb2b17f7f, 0x5fcb80ff, 0x1f0027, 0xc100c0, 0xc300c2}, + {0x112, 0x407f047f, 0x4040044c, 0x0, 0xb2b17f7f, 0x5fcb80ff, 0x200027, 0xc100c0, 0xc300c2}, + {0x113, 0x407f0451, 0x4242045a, 0x0, 0xb2b17f7f, 0x5fcb80ff, 0x210027, 0xc100c0, 0xc300c2}, + {0x114, 0x407f044b, 0x4343044f, 0x0, 0xb2b17f7f, 0x5feb80ff, 0x220027, 0xc100c0, 0xc300c2}, + {0x115, 0x407f047f, 0x41410459, 0x0, 0xb2b17f7f, 0x5feb80ff, 0x230027, 0xc100c0, 0xc300c2}, + {0x116, 0x4075047f, 0x4444005c, 0x0, 0xb2b17f7f, 0x5fe080ff, 0x250027, 0xc100c0, 0xc300c2}, + {0x120, 0x407f047f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0x5fe080ff, 0x260028, 0xc100c0, 0xc300c2}, + {0x121, 0x407f047f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x270028, 0xc100c0, 0xc300c2}, + {0x130, 0x407f0440, 0x3d3d3c56, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x4e0031, 0xc100c0, 0xc300c2}, + {0x140, 0x407f047f, 0x7f000048, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x2d003a, 0xc100c0, 0xc300c2}, + {0x150, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x2e003b, 0xc100c0, 0xc300c2}, + {0x151, 0x407f047f, 0x3e3e465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x2f003b, 0xc100c0, 0xc300c2}, + {0x152, 0x407f047f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x30003b, 0xc100c0, 0xc300c2}, + {0x153, 0x407f047f, 0x3f3f005b, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x31003b, 0xc100c0, 0xc300c2}, + {0x160, 0x407f047f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x32003c, 0xc100c0, 0xc300c2}, + {0x161, 0x407f047f, 0x3f3f465c, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x33003c, 0xc100c0, 0xc300c2}, + {0x170, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x34003d, 0xc100c0, 0xc300c2}, + {0x171, 0x407f047f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x35003d, 0xc100c0, 0xc300c2}, + {0x172, 0x407f047f, 0x37374653, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x36003d, 0xc100c0, 0xc300c2}, + {0x173, 0x407f047f, 0x38384655, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x37003d, 0xc100c0, 0xc300c2}, + {0x174, 0x407f047f, 0x39394656, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x38003d, 0xc100c0, 0xc300c2}, + {0x175, 0x407f047f, 0x3b3b4658, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x39003d, 0xc100c0, 0xc300c2}, + {0x176, 0x407f047f, 0x3a3a4657, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x3a003d, 0xc100c0, 0xc300c2}, + {0x177, 0x407f047f, 0x4040465d, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x3b003d, 0xc100c0, 0xc300c2}, + {0x178, 0x407f047f, 0x4141004d, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x3c003d, 0xc100c0, 0xc300c2}, + {0x180, 0x407f047f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x3d003e, 0xc100c0, 0xc300c2}, + {0x181, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x3e003e, 0xc100c0, 0xc300c2}, + {0x182, 0x407f047f, 0x3e3e465b, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x3f003e, 0xc100c0, 0xc300c2}, + {0x183, 0x407f047f, 0x3f3f465c, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x40003e, 0xc100c0, 0xc300c2}, + {0x190, 0x407f047f, 0x25254642, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x41003f, 0xc100c0, 0xc300c2}, + {0x191, 0x407f047f, 0x26264643, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x42003f, 0xc100c0, 0xc300c2}, + {0x192, 0x407f047f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x43003f, 0xc100c0, 0xc300c2}, + {0x193, 0x407f047f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x44003f, 0xc100c0, 0xc300c2}, + {0x1a0, 0x407f047f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x450040, 0xc100c0, 0xc300c2}, + {0x1a1, 0x407f047f, 0x3e3e465b, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x460040, 0xc100c0, 0xc300c2}, + {0x1a2, 0x407f047f, 0x27274644, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x470040, 0xc100c0, 0xc300c2}, + {0x1a3, 0x407f047f, 0x3f3f465c, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x480040, 0xc100c0, 0xc300c2}, + {0x1b0, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x490041, 0xc100c0, 0xc300c2}, + {0x1b1, 0x407f047f, 0x4040465d, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x4a0041, 0xc100c0, 0xc300c2}, + {0x1c0, 0x407f047f, 0x7f004667, 0x0, 0xb2b17f7f, 0xdfefaaff, 0x4b0042, 0xc100c0, 0xc300c2}, + {0x1d0, 0x407f0000, 0x7f000048, 0x0, 0xb2b10000, 0x1fc080ff, 0x4d0046, 0xc100c0, 0xc300c2}, + {0x1d1, 0x407f0000, 0x7f000048, 0x0, 0xb2b10000, 0x1fc080ff, 0x4d0046, 0xc100c0, 0xc300c2}, + {0x1e0, 0x407f0453, 0x603c003c, 0x0, 0xb2b10000, 0xdfed80ff, 0x6d0048, 0xc100c0, 0xc300c2}, + {0x1f0, 0x407f0401, 0x7f00003c, 0x0, 0xb2b10000, 0xdfeb80ff, 0x5c0049, 0xc100c0, 0xc300c2}, + {0x200, 0x367f0400, 0x3e3e004a, 0x0, 0xb2b10000, 0xdfe380ff, 0x56004a, 0xc100c0, 0xc300c2}, + {0x201, 0x407f0400, 0x5454006c, 0x0, 0xb2b10000, 0xdfe680ff, 0x57004a, 0xc100c0, 0xc300c2}, + {0x202, 0x4a7f0000, 0x28280040, 0x0, 0xb2b10000, 0xdfe680ff, 0x58004a, 0xc100c0, 0xc300c2}, + {0x203, 0x407f0400, 0x4c4c0058, 0x0, 0xb2b10000, 0xdfeb80ff, 0x59004a, 0xc100c0, 0xc300c2}, + {0x210, 0x7f0000, 0x24240024, 0x0, 0xb2b10000, 0xdfeb80ff, 0x5d004b, 0xc100c0, 0xc300c2}, + {0x211, 0x7f7f0400, 0x25250025, 0x0, 0xb2b10000, 0xdfeb80ff, 0x5e004b, 0xc100c0, 0xc300c2}, + {0x212, 0x7f0000, 0x26260026, 0x0, 0xb2b10000, 0xdfeb80ff, 0x5f004b, 0xc100c0, 0xc300c2}, + {0x213, 0x7f7f0000, 0x27270027, 0x0, 0xb2b10000, 0xdfeb80ff, 0x60004b, 0xc100c0, 0xc300c2}, + {0x214, 0x7f0400, 0x28280028, 0x0, 0xb2b10000, 0xdfeb80ff, 0x61004b, 0xc100c0, 0xc300c2}, + {0x215, 0x7f7f0000, 0x29290029, 0x0, 0xb2b10000, 0xdfeb80ff, 0x62004b, 0xc100c0, 0xc300c2}, + {0x216, 0x7f0000, 0x2a2a002a, 0x0, 0xb2b10000, 0xdfeb80ff, 0x63004b, 0xc100c0, 0xc300c2}, + {0x217, 0x7f7f0400, 0x2b2b002b, 0x0, 0xb2b10000, 0xdfeb80ff, 0x64004b, 0xc100c0, 0xc300c2}, + {0x218, 0x7f0000, 0x2c2c002c, 0x0, 0xb2b10000, 0xdfeb80ff, 0x65004b, 0xc100c0, 0xc300c2}, + {0x219, 0x7f7f0000, 0x2d2d002d, 0x0, 0xb2b10000, 0xdfeb80ff, 0x66004b, 0xc100c0, 0xc300c2}, + {0x21a, 0x7f0400, 0x2e2e002e, 0x0, 0xb2b10000, 0xdfeb80ff, 0x67004b, 0xc100c0, 0xc300c2}, + {0x21b, 0x7f7f0000, 0x2f2f002f, 0x0, 0xb2b10000, 0xdfeb80ff, 0x68004b, 0xc100c0, 0xc300c2}, + {0x21c, 0x7f0000, 0x30300030, 0x0, 0xb2b10000, 0xdfeb80ff, 0x69004b, 0xc100c0, 0xc300c2}, + {0x21d, 0x7f7f0400, 0x31310031, 0x0, 0xb2b10000, 0xdfeb80ff, 0x6a004b, 0xc100c0, 0xc300c2}, + {0x21e, 0x7f0000, 0x32320032, 0x0, 0xb2b10000, 0xdfeb80ff, 0x6b004b, 0xc100c0, 0xc300c2}, + {0x21f, 0x7f7f0000, 0x33330033, 0x0, 0xb2b10000, 0xdfeb80ff, 0x6c004b, 0xc100c0, 0xc300c2}, + {0x220, 0x7f0400, 0x24240029, 0x0, 0xb2b10000, 0xdfeb80ff, 0x5d004c, 0xc100c0, 0xc300c2}, + {0x221, 0x7f7f0000, 0x2525002a, 0x0, 0xb2b10000, 0xdfeb80ff, 0x5e004c, 0xc100c0, 0xc300c2}, + {0x222, 0x7f0000, 0x2626002b, 0x0, 0xb2b10000, 0xdfeb80ff, 0x5f004c, 0xc100c0, 0xc300c2}, + {0x223, 0x7f7f0400, 0x2727002c, 0x0, 0xb2b10000, 0xdfeb80ff, 0x60004c, 0xc100c0, 0xc300c2}, + {0x224, 0x7f0000, 0x2828002d, 0x0, 0xb2b10000, 0xdfeb80ff, 0x61004c, 0xc100c0, 0xc300c2}, + {0x225, 0x7f7f0000, 0x2929002e, 0x0, 0xb2b10000, 0xdfeb80ff, 0x62004c, 0xc100c0, 0xc300c2}, + {0x226, 0x7f0400, 0x2a2a002f, 0x0, 0xb2b10000, 0xdfeb80ff, 0x63004c, 0xc100c0, 0xc300c2}, + {0x227, 0x7f7f0000, 0x2b2b0030, 0x0, 0xb2b10000, 0xdfeb80ff, 0x64004c, 0xc100c0, 0xc300c2}, + {0x228, 0x7f0000, 0x2c2c0031, 0x0, 0xb2b10000, 0xdfeb80ff, 0x65004c, 0xc100c0, 0xc300c2}, + {0x229, 0x7f7f0400, 0x2d2d0032, 0x0, 0xb2b10000, 0xdfeb80ff, 0x66004c, 0xc100c0, 0xc300c2}, + {0x22a, 0x7f0000, 0x2e2e0033, 0x0, 0xb2b10000, 0xdfeb80ff, 0x67004c, 0xc100c0, 0xc300c2}, + {0x22b, 0x7f7f0000, 0x2f2f0034, 0x0, 0xb2b10000, 0xdfeb80ff, 0x68004c, 0xc100c0, 0xc300c2}, + {0x22c, 0x7f0400, 0x30300035, 0x0, 0xb2b10000, 0xdfeb80ff, 0x69004c, 0xc100c0, 0xc300c2}, + {0x22d, 0x7f7f0000, 0x31310036, 0x0, 0xb2b10000, 0xdfeb80ff, 0x6a004c, 0xc100c0, 0xc300c2}, + {0x22e, 0x7f0000, 0x32320037, 0x0, 0xb2b10000, 0xdfeb80ff, 0x6b004c, 0xc100c0, 0xc300c2}, + {0x22f, 0x7f7f0400, 0x33330038, 0x0, 0xb2b10000, 0xdfeb80ff, 0x6c004c, 0xc100c0, 0xc300c2}, + {0x230, 0x407f0454, 0x3f3f004b, 0x0, 0xb2b10000, 0x5feb80ff, 0x13004d, 0xc100c0, 0xc300c2}, + {0x231, 0x407f005a, 0x3b00003c, 0x0, 0xb2b10000, 0x5feb80ff, 0x14004d, 0xc100c0, 0xc300c2}, + {0x232, 0x406e0055, 0x3c3c0048, 0x0, 0xb2b10000, 0x5feb80ff, 0x4c004d, 0xc100c0, 0xc300c2}, + {0x240, 0x407f0478, 0x47005a49, 0x0, 0xb2b10000, 0xdff0a0ff, 0x16004e, 0xc100c0, 0xc300c2}, + {0x241, 0x407f0478, 0x7f48005d, 0x0, 0xb2b10000, 0xdfeb80ff, 0x17004e, 0xc100c0, 0xc300c2}, + {0x250, 0x407f0455, 0x7f250048, 0x0, 0xb2b10000, 0xdfeb80ff, 0x18004f, 0xc100c0, 0xc300c2}, + {0x251, 0x64640451, 0x24230048, 0x0, 0xb2b10000, 0xdfeb80ff, 0x18004f, 0xc100c0, 0xc300c2}, + {0x252, 0x1b640405, 0x2200003c, 0x0, 0xb2b10000, 0xdfeb80ff, 0x18004f, 0xc100c0, 0xc300c2}, + {0x260, 0x6e047f, 0x7f005a48, 0x0, 0xb2b10000, 0xdfe780ff, 0x190050, 0xc100c0, 0xc300c2}, + {0x261, 0x7f6e047f, 0x7f006148, 0x0, 0xb2b10000, 0xdfe780ff, 0x190050, 0xc100c0, 0xc300c2}, + {0x270, 0x407f047f, 0x7f00465a, 0x0, 0xb2b10000, 0x5febb4ff, 0x120051, 0xc100c0, 0xc300c2}, + {0x280, 0x407f0465, 0x7f000460, 0x0, 0xb2b10000, 0xdfefbbff, 0x2a0052, 0xc100c0, 0xc300c2}, + {0x290, 0x407f0465, 0x7f000454, 0x0, 0xb2b10000, 0xdfec80ff, 0x2a0055, 0xc100c0, 0xc300c2}}}; + +const std::vector> AO_E1SNDFX_VH = {{ + {0x0, 0x7f047f, 0x3e006a53, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10000, 0xc100c0, 0xc300c2}, + {0x1, 0x1b380400, 0x3e006a5f, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10000, 0xc100c0, 0xc300c2}, + {0x2, 0x64500400, 0x3e006a66, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10000, 0xc100c0, 0xc300c2}, + {0x3, 0x40280400, 0x3e006a72, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10000, 0xc100c0, 0xc300c2}, + {0x4, 0x7f7f047f, 0x3e006a54, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10000, 0xc100c0, 0xc300c2}, + {0x5, 0x407f005b, 0x4242004e, 0x0, 0xb2b17f7f, 0x5feb80ff, 0xb0000, 0xc100c0, 0xc300c2}, + {0x6, 0x407f005b, 0x4343004f, 0x0, 0xb2b17f7f, 0x5feb80ff, 0x50000, 0xc100c0, 0xc300c2}, + {0x7, 0x407f0464, 0x3e006a4d, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x10000, 0xc100c0, 0xc300c2}, + {0x8, 0x407f0054, 0x4848003c, 0x0, 0xb2b17f7f, 0x5fe380ff, 0x190000, 0xc100c0, 0xc300c2}, + {0x9, 0x407f0054, 0x49490049, 0x0, 0xb2b17f7f, 0x5fe380ff, 0x1a0000, 0xc100c0, 0xc300c2}, + {0x10, 0x407f007f, 0x3a3a0046, 0x0, 0xb2b17f7f, 0x5feb80ff, 0x120001, 0xc100c0, 0xc300c2}, + {0x11, 0x407f007f, 0x3b3b0045, 0x0, 0xb2b17f7f, 0x5feb80ff, 0x30001, 0xc100c0, 0xc300c2}, + {0x12, 0x407f007f, 0x48480060, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x400001, 0xc100c0, 0xc300c2}, + {0x13, 0x407f007f, 0x48480060, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x20001, 0xc100c0, 0xc300c2}, + {0x14, 0x7f007f, 0x48480048, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x20001, 0xc100c0, 0xc300c2}, + {0x15, 0x7f640051, 0x4848006c, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x20001, 0xc100c0, 0xc300c2}, + {0x16, 0x640051, 0x4848326b, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x20001, 0xc100c0, 0xc300c2}, + {0x17, 0x407f0052, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x3e0001, 0xc100c0, 0xc300c2}, + {0x18, 0x407f0052, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x3f0001, 0xc100c0, 0xc300c2}, + {0x19, 0x407f0000, 0x39390045, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x510001, 0xc100c0, 0xc300c2}, + {0x20, 0x40150464, 0x3c3c6a66, 0x0, 0xb2b17f7f, 0xdaeb80ff, 0x10002, 0xc100c0, 0xc300c2}, + {0x21, 0x3f5f0400, 0x3b006a4e, 0x0, 0xb2b17f7f, 0x5fef80ff, 0x10002, 0xc100c0, 0xc300c2}, + {0x22, 0x540400, 0x3b006a54, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10002, 0xc100c0, 0xc300c2}, + {0x23, 0x7f540400, 0x3b006a56, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10002, 0xc100c0, 0xc300c2}, + {0x24, 0x7f007f, 0x3c3c003c, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x1d0002, 0xc100c0, 0xc300c2}, + {0x25, 0x7f7f007f, 0x3c3c003c, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x1e0002, 0xc100c0, 0xc300c2}, + {0x26, 0x4046007f, 0x3c3c003c, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x1f0002, 0xc100c0, 0xc300c2}, + {0x27, 0x407f007f, 0x40400040, 0x0, 0xb2b17f7f, 0x5fe380ff, 0x200002, 0xc100c0, 0xc300c2}, + {0x30, 0x407f007f, 0x3b3b0047, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x110003, 0xc100c0, 0xc300c2}, + {0x31, 0x407f007f, 0x3a3a0052, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x60003, 0xc100c0, 0xc300c2}, + {0x32, 0x407f007f, 0x43430048, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x70003, 0xc100c0, 0xc300c2}, + {0x33, 0x407f007f, 0x48480054, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x1c0003, 0xc100c0, 0xc300c2}, + {0x34, 0x407f007f, 0x39390051, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x2e0003, 0xc100c0, 0xc300c2}, + {0x40, 0x407f0050, 0x24240030, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x80004, 0xc100c0, 0xc300c2}, + {0x41, 0x407f0050, 0x25250031, 0x0, 0xb2b17f7f, 0xdfeba5ff, 0x80004, 0xc100c0, 0xc300c2}, + {0x42, 0x407f0050, 0x26260032, 0x0, 0xb2b17f7f, 0xdfebaaff, 0x80004, 0xc100c0, 0xc300c2}, + {0x43, 0x407f007f, 0x3b3b0047, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x100004, 0xc100c0, 0xc300c2}, + {0x44, 0x407f007f, 0x48480060, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x400004, 0xc100c0, 0xc300c2}, + {0x45, 0x407f007f, 0x48480054, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x1c0004, 0xc100c0, 0xc300c2}, + {0x50, 0x4064047f, 0x3c3c0060, 0x0, 0xb2b17f7f, 0xdff380ff, 0x7c0006, 0xc100c0, 0xc300c2}, + {0x51, 0x4064047f, 0x3c3c0078, 0x0, 0xb2b17f7f, 0xdff580ff, 0x530006, 0xc100c0, 0xc300c2}, + {0x52, 0x4053047f, 0x3c3c006c, 0x0, 0xb2b17f7f, 0xd170bebf, 0x7d0006, 0xc100c0, 0xc300c2}, + {0x60, 0x407f007f, 0x40400059, 0x0, 0xb2b17f7f, 0xdfef80ff, 0x150007, 0xc100c0, 0xc300c2}, + {0x61, 0x407f0002, 0x3c3c0054, 0x0, 0xb2b10000, 0xdfed80ff, 0x420007, 0xc100c0, 0xc300c2}, + {0x62, 0x407f0002, 0x3d3d0055, 0x0, 0xb2b10000, 0xdfe680ff, 0x430007, 0xc100c0, 0xc300c2}, + {0x63, 0x407f0002, 0x3f3f004b, 0x0, 0xb2b10000, 0xdfe680ff, 0x450007, 0xc100c0, 0xc300c2}, + {0x64, 0x407f0002, 0x4141004d, 0x0, 0xb2b10000, 0xdfe680ff, 0x470007, 0xc100c0, 0xc300c2}, + {0x65, 0x407f0002, 0x4343044f, 0x0, 0xb2b10000, 0xdfe680ff, 0x4b0007, 0xc100c0, 0xc300c2}, + {0x66, 0x407f0002, 0x4444005c, 0x0, 0xb2b10000, 0xdfe680ff, 0x440007, 0xc100c0, 0xc300c2}, + {0x67, 0x407f047f, 0x2424003c, 0x0, 0xb2b10000, 0xdfeb80ff, 0x730007, 0xc100c0, 0xc300c2}, + {0x68, 0x327f0002, 0x26260a32, 0x0, 0xb2b10000, 0xdfe380ff, 0x740007, 0xc100c0, 0xc300c2}, + {0x69, 0x367f0002, 0x28280028, 0x0, 0xb2b10000, 0xdfe380ff, 0x750007, 0xc100c0, 0xc300c2}, + {0x6a, 0x207f0002, 0x2a2a0036, 0x0, 0xb2b10000, 0xdfe380ff, 0x760007, 0xc100c0, 0xc300c2}, + {0x6b, 0x407f0002, 0x2b2b0a37, 0x0, 0xb2b10000, 0xdfe380ff, 0x760007, 0xc100c0, 0xc300c2}, + {0x6c, 0x607f0002, 0x2c2c0038, 0x0, 0xb2b10000, 0xdfe380ff, 0x760007, 0xc100c0, 0xc300c2}, + {0x6d, 0x4e7f0002, 0x25250031, 0x0, 0xb2b10000, 0xdfe380ff, 0x740007, 0xc100c0, 0xc300c2}, + {0x6e, 0x4a7f0002, 0x29290a29, 0x0, 0xb2b10000, 0xdfe380ff, 0x750007, 0xc100c0, 0xc300c2}, + {0x70, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x40008, 0xc100c0, 0xc300c2}, + {0x80, 0x3c64007f, 0x2727003c, 0x0, 0xb2b17f7f, 0xdfe380ff, 0xa0009, 0xc100c0, 0xc300c2}, + {0x81, 0x3c7f007f, 0x2626003e, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x90009, 0xc100c0, 0xc300c2}, + {0x82, 0x3c5a007f, 0x27270044, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x130009, 0xc100c0, 0xc300c2}, + {0x83, 0x407f007f, 0x28280040, 0x0, 0xb2b17f7f, 0xdfe380ff, 0xf0009, 0xc100c0, 0xc300c2}, + {0x84, 0x407f0065, 0x2525003d, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x230009, 0xc100c0, 0xc300c2}, + {0x85, 0x407f007f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x250009, 0xc100c0, 0xc300c2}, + {0x86, 0x407f0000, 0x3d3d465a, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x790009, 0xc100c0, 0xc300c2}, + {0x87, 0x407f0000, 0x29290041, 0x0, 0xb2b17f7f, 0xdfe380ff, 0xf0009, 0xc100c0, 0xc300c2}, + {0x90, 0x407f0064, 0x3c3c0048, 0x0, 0xb2b17f7f, 0xdff080ff, 0x24000b, 0xc100c0, 0xc300c2}, + {0x91, 0x407f0064, 0x3d3d0055, 0x0, 0xb2b17f7f, 0xdff080ff, 0x41000b, 0xc100c0, 0xc300c2}, + {0xa0, 0x407f0002, 0x26260026, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x16000c, 0xc100c0, 0xc300c2}, + {0xa1, 0x407f007f, 0x27270027, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x14000c, 0xc100c0, 0xc300c2}, + {0xa2, 0x407f0064, 0x28280040, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x26000c, 0xc100c0, 0xc300c2}, + {0xa3, 0x407f0064, 0x29290041, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x27000c, 0xc100c0, 0xc300c2}, + {0xa4, 0x407f0064, 0x2a2a0042, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x28000c, 0xc100c0, 0xc300c2}, + {0xa5, 0x407f0064, 0x2c2c0044, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x37000c, 0xc100c0, 0xc300c2}, + {0xb0, 0x407f007f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0xdff080ff, 0xc000d, 0xc100c0, 0xc300c2}, + {0xc0, 0x407f0050, 0x24240024, 0x0, 0xb2b17f7f, 0xdfe380ff, 0xd000e, 0xc100c0, 0xc300c2}, + {0xc1, 0x407f0050, 0x25250025, 0x0, 0xb2b17f7f, 0xdfe380ff, 0xe000e, 0xc100c0, 0xc300c2}, + {0xc2, 0x407f0050, 0x3d3d4659, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x1b000e, 0xc100c0, 0xc300c2}, + {0xc3, 0x407f0050, 0x3e3e465a, 0x0, 0xb2b17f7f, 0xdfe6afff, 0x1b000e, 0xc100c0, 0xc300c2}, + {0xd0, 0x407f0479, 0x30300048, 0x0, 0xb2b17f7f, 0xdff080ff, 0x380010, 0xc100c0, 0xc300c2}, + {0xe0, 0x407f007f, 0x3b3b4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x520011, 0xc100c0, 0xc300c2}, + {0xf0, 0x407f0064, 0x2d2d0045, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x300012, 0xc100c0, 0xc300c2}, + {0xf1, 0x407f007f, 0x2e2e0046, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x290012, 0xc100c0, 0xc300c2}, + {0xf2, 0x407f0032, 0x2f2f0047, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x2a0012, 0xc100c0, 0xc300c2}, + {0xf3, 0x407f0032, 0x30300048, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x2b0012, 0xc100c0, 0xc300c2}, + {0xf4, 0x407f0032, 0x2424003c, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x2c0012, 0xc100c0, 0xc300c2}, + {0xf5, 0x407f0032, 0x2525003d, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x2d0012, 0xc100c0, 0xc300c2}, + {0x100, 0x407f007e, 0x7f006a53, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x10015, 0xc100c0, 0xc300c2}, + {0x110, 0x407f0064, 0x3d3d004e, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x60016, 0xc100c0, 0xc300c2}, + {0x111, 0x407f0064, 0x3e3e0045, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x110016, 0xc100c0, 0xc300c2}, + {0x112, 0x407f0064, 0x3f3f0046, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x110016, 0xc100c0, 0xc300c2}, + {0x113, 0x407f0064, 0x3f3f0057, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x270016, 0xc100c0, 0xc300c2}, + {0x114, 0x3c7f004b, 0x2121462d, 0x0, 0xb2b17f7f, 0xdfe0a4ff, 0x80016, 0xc100c0, 0xc300c2}, + {0x115, 0x407f004b, 0x2222462d, 0x0, 0xb2b17f7f, 0xdfe0a0ff, 0x80016, 0xc100c0, 0xc300c2}, + {0x116, 0x3c7f004b, 0x2323462d, 0x0, 0xb2b17f7f, 0xdfe0a7ff, 0x80016, 0xc100c0, 0xc300c2}, + {0x117, 0x407f004b, 0x2424462d, 0x0, 0xb2b17f7f, 0xdfe0aaff, 0x80016, 0xc100c0, 0xc300c2}, + {0x118, 0x407f004a, 0x2525002d, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x360016, 0xc100c0, 0xc300c2}, + {0x120, 0x407f047f, 0x3e3e0073, 0x0, 0xb2b17f7f, 0xceab80ff, 0x3d0018, 0xc100c0, 0xc300c2}, + {0x130, 0x2c7f007f, 0x24240030, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x180019, 0xc100c0, 0xc300c2}, + {0x131, 0x407f007f, 0x25250031, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x170019, 0xc100c0, 0xc300c2}, + {0x132, 0x547f007f, 0x24246e31, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x180019, 0xc100c0, 0xc300c2}, + {0x140, 0x407f0400, 0x7f000054, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x2f001b, 0xc100c0, 0xc300c2}, + {0x150, 0x407f047f, 0x7f004659, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x22001c, 0xc100c0, 0xc300c2}, + {0x160, 0x407f047f, 0x2424003c, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x3d0021, 0xc100c0, 0xc300c2}, + {0x170, 0x407f0046, 0x24240031, 0x0, 0xb2b17f7f, 0xdfebb4ff, 0x2c0025, 0xc100c0, 0xc300c2}, + {0x171, 0x407f0046, 0x25250031, 0x0, 0xb2b17f7f, 0xdfebaaff, 0x2d0025, 0xc100c0, 0xc300c2}, + {0x172, 0x407f0064, 0x3c3c0448, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x420025, 0xc100c0, 0xc300c2}, + {0x173, 0x407f0064, 0x3d3d0449, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x430025, 0xc100c0, 0xc300c2}, + {0x174, 0x407f0046, 0x4040044c, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x440025, 0xc100c0, 0xc300c2}, + {0x175, 0x407f0046, 0x4141044c, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x440025, 0xc100c0, 0xc300c2}, + {0x176, 0x407f0064, 0x4242044e, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x450025, 0xc100c0, 0xc300c2}, + {0x177, 0x407f0064, 0x4343044f, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x460025, 0xc100c0, 0xc300c2}, + {0x180, 0x407f007f, 0x3c3c0448, 0x0, 0xb2b17f7f, 0x5fcb80ff, 0x470027, 0xc100c0, 0xc300c2}, + {0x181, 0x40550051, 0x3d3d0457, 0x0, 0xb2b17f7f, 0x5fcb80ff, 0x480027, 0xc100c0, 0xc300c2}, + {0x182, 0x407f007f, 0x4040044c, 0x0, 0xb2b17f7f, 0x5fcb80ff, 0x490027, 0xc100c0, 0xc300c2}, + {0x183, 0x407f0051, 0x4242045a, 0x0, 0xb2b17f7f, 0x5fcb80ff, 0x4b0027, 0xc100c0, 0xc300c2}, + {0x184, 0x407f004b, 0x4343044f, 0x0, 0xb2b17f7f, 0x5fcb80ff, 0x4c0027, 0xc100c0, 0xc300c2}, + {0x185, 0x407f007f, 0x41410459, 0x0, 0xb2b17f7f, 0x5feb80ff, 0x4d0027, 0xc100c0, 0xc300c2}, + {0x186, 0x4064007f, 0x4444005c, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x4e0027, 0xc100c0, 0xc300c2}, + {0x190, 0x407f007f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0x5fe080ff, 0x4f0028, 0xc100c0, 0xc300c2}, + {0x191, 0x407f007f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x500028, 0xc100c0, 0xc300c2}, + {0x1a0, 0x407f0019, 0x3e3e465b, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x7a002c, 0xc100c0, 0xc300c2}, + {0x1a1, 0x407f0019, 0x3f3f465c, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x7b002c, 0xc100c0, 0xc300c2}, + {0x1b0, 0x407f0040, 0x3d3d3c56, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x780031, 0xc100c0, 0xc300c2}, + {0x1c0, 0x407f007f, 0x7f000048, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x54003a, 0xc100c0, 0xc300c2}, + {0x1d0, 0x407f007f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x55003b, 0xc100c0, 0xc300c2}, + {0x1d1, 0x407f007f, 0x3e3e465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x56003b, 0xc100c0, 0xc300c2}, + {0x1d2, 0x407f007f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x57003b, 0xc100c0, 0xc300c2}, + {0x1d3, 0x407f007f, 0x3f3f005b, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x58003b, 0xc100c0, 0xc300c2}, + {0x1e0, 0x407f007f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x59003c, 0xc100c0, 0xc300c2}, + {0x1e1, 0x407f007f, 0x3f3f465c, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x5a003c, 0xc100c0, 0xc300c2}, + {0x1f0, 0x407f007f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x5b003d, 0xc100c0, 0xc300c2}, + {0x1f1, 0x407f007f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x5c003d, 0xc100c0, 0xc300c2}, + {0x1f2, 0x407f007f, 0x37374653, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x5d003d, 0xc100c0, 0xc300c2}, + {0x1f3, 0x407f007f, 0x38384655, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x5e003d, 0xc100c0, 0xc300c2}, + {0x1f4, 0x407f007f, 0x39394656, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x5f003d, 0xc100c0, 0xc300c2}, + {0x1f5, 0x407f007f, 0x3b3b4658, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x60003d, 0xc100c0, 0xc300c2}, + {0x1f6, 0x407f007f, 0x3a3a4657, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x61003d, 0xc100c0, 0xc300c2}, + {0x1f7, 0x407f007f, 0x4040465d, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x62003d, 0xc100c0, 0xc300c2}, + {0x1f8, 0x407f007f, 0x4141004d, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x63003d, 0xc100c0, 0xc300c2}, + {0x200, 0x407f007f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x64003e, 0xc100c0, 0xc300c2}, + {0x201, 0x407f007f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x65003e, 0xc100c0, 0xc300c2}, + {0x202, 0x407f007f, 0x3e3e465b, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x66003e, 0xc100c0, 0xc300c2}, + {0x203, 0x407f007f, 0x3f3f465c, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x67003e, 0xc100c0, 0xc300c2}, + {0x210, 0x407f007f, 0x25254642, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x68003f, 0xc100c0, 0xc300c2}, + {0x211, 0x407f007f, 0x26264643, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x69003f, 0xc100c0, 0xc300c2}, + {0x212, 0x407f007f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x6a003f, 0xc100c0, 0xc300c2}, + {0x213, 0x407f007f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x6b003f, 0xc100c0, 0xc300c2}, + {0x220, 0x407f007f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x6c0040, 0xc100c0, 0xc300c2}, + {0x221, 0x407f007f, 0x3e3e465b, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x6d0040, 0xc100c0, 0xc300c2}, + {0x222, 0x407f007f, 0x27274644, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x6e0040, 0xc100c0, 0xc300c2}, + {0x223, 0x407f007f, 0x3f3f465c, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x6f0040, 0xc100c0, 0xc300c2}, + {0x230, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x700041, 0xc100c0, 0xc300c2}, + {0x231, 0x407f007f, 0x4040465d, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x710041, 0xc100c0, 0xc300c2}, + {0x240, 0x407f047f, 0x7f004667, 0x0, 0xb2b17f7f, 0xdfefaaff, 0x720042, 0xc100c0, 0xc300c2}, + {0x250, 0x7f0404, 0x53000048, 0x0, 0xb2b10000, 0xdfeb80ff, 0x530046, 0xc100c0, 0xc300c2}, + {0x251, 0x7f7f0404, 0x7f540048, 0x0, 0xb2b10000, 0xdfeb80ff, 0x530046, 0xc100c0, 0xc300c2}, + {0x260, 0x407f0400, 0x7f000066, 0x0, 0xb2b10000, 0xdfec80ff, 0x33004a, 0xc100c0, 0xc300c2}, + {0x270, 0x7f007f, 0x30304659, 0x0, 0xb2b10101, 0xdff080ff, 0x31004b, 0xc100c0, 0xc300c2}, + {0x271, 0x7f7f047f, 0x3c3c4665, 0x0, 0xb2b10101, 0xdff080ff, 0x32004b, 0xc100c0, 0xc300c2}, + {0x272, 0x407f047f, 0x48484671, 0x0, 0xb2b10101, 0xdff080ff, 0x32004b, 0xc100c0, 0xc300c2}, + {0x280, 0x407f044f, 0x7f00465a, 0x0, 0xb2b10101, 0xdff0c0ff, 0x33004c, 0xc100c0, 0xc300c2}, + {0x290, 0x407f0455, 0x3f3f004b, 0x0, 0xb2b10000, 0x5feb80ff, 0x34004d, 0xc100c0, 0xc300c2}, + {0x291, 0x407f005a, 0x3b00003c, 0x0, 0xb2b10000, 0xdfeb80ff, 0x35004d, 0xc100c0, 0xc300c2}, + {0x292, 0x406e0055, 0x3c3c0048, 0x0, 0xb2b10000, 0xdfeb80ff, 0x77004d, 0xc100c0, 0xc300c2}, + {0x2a0, 0x407f0464, 0x47005a49, 0x0, 0xb2b10000, 0xdff0a0ff, 0x39004e, 0xc100c0, 0xc300c2}, + {0x2a1, 0x407f0464, 0x7f48005d, 0x0, 0xb2b10000, 0xdfeb80ff, 0x3a004e, 0xc100c0, 0xc300c2}, + {0x2b0, 0x407f0455, 0x7f250048, 0x0, 0xb2b10000, 0xdfeb80ff, 0x3b004f, 0xc100c0, 0xc300c2}, + {0x2b1, 0x6464044f, 0x24230048, 0x0, 0xb2b10000, 0xdfeb80ff, 0x3b004f, 0xc100c0, 0xc300c2}, + {0x2b2, 0x1b640405, 0x2200003c, 0x0, 0xb2b10000, 0xdfeb80ff, 0x3b004f, 0xc100c0, 0xc300c2}, + {0x2c0, 0x6e047f, 0x7f005a48, 0x0, 0xb2b10000, 0xdfe780ff, 0x3c0050, 0xc100c0, 0xc300c2}, + {0x2c1, 0x7f6e047f, 0x7f006148, 0x0, 0xb2b10000, 0xdfe780ff, 0x3c0050, 0xc100c0, 0xc300c2}, + {0x2d0, 0x407f047f, 0x7f00465a, 0x0, 0xb2b10000, 0x5febb4ff, 0x330051, 0xc100c0, 0xc300c2}}}; + +const std::vector> AO_E2SNDFX_VH = {{ + {0x0, 0x7f047f, 0x3e006a53, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10000, 0xc100c0, 0xc300c2}, + {0x1, 0x1b380400, 0x3e006a5f, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10000, 0xc100c0, 0xc300c2}, + {0x2, 0x64500400, 0x3e006a66, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10000, 0xc100c0, 0xc300c2}, + {0x3, 0x40280400, 0x3e006a72, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10000, 0xc100c0, 0xc300c2}, + {0x4, 0x7f7f047f, 0x3e006a54, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10000, 0xc100c0, 0xc300c2}, + {0x5, 0x407f005b, 0x42420050, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0xc0000, 0xc100c0, 0xc300c2}, + {0x6, 0x407f005b, 0x4343004f, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x50000, 0xc100c0, 0xc300c2}, + {0x7, 0x407f0464, 0x3e006a4d, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x10000, 0xc100c0, 0xc300c2}, + {0x8, 0x407f0054, 0x4848003c, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x1d0000, 0xc100c0, 0xc300c2}, + {0x9, 0x407f0054, 0x49490049, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x1e0000, 0xc100c0, 0xc300c2}, + {0x10, 0x407f007f, 0x3a3a0046, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x150001, 0xc100c0, 0xc300c2}, + {0x11, 0x407f007f, 0x3b3b0045, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x30001, 0xc100c0, 0xc300c2}, + {0x12, 0x407f007f, 0x48480060, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x430001, 0xc100c0, 0xc300c2}, + {0x13, 0x407f007f, 0x48480060, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x20001, 0xc100c0, 0xc300c2}, + {0x14, 0x7f007f, 0x48480048, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x20001, 0xc100c0, 0xc300c2}, + {0x15, 0x7f640051, 0x4848006c, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x20001, 0xc100c0, 0xc300c2}, + {0x16, 0x640051, 0x4848326b, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x20001, 0xc100c0, 0xc300c2}, + {0x17, 0x407f007f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x410001, 0xc100c0, 0xc300c2}, + {0x18, 0x407f007f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x420001, 0xc100c0, 0xc300c2}, + {0x19, 0x407f0000, 0x39390045, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x480001, 0xc100c0, 0xc300c2}, + {0x20, 0x40150464, 0x3c3c6a66, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x10002, 0xc100c0, 0xc300c2}, + {0x21, 0x3f5f0400, 0x3b006a4e, 0x0, 0xb2b17f7f, 0x5fef80ff, 0x10002, 0xc100c0, 0xc300c2}, + {0x22, 0x540400, 0x3b006a54, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10002, 0xc100c0, 0xc300c2}, + {0x23, 0x7f540400, 0x3b006a56, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10002, 0xc100c0, 0xc300c2}, + {0x24, 0x7f007f, 0x3c3c003c, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x240002, 0xc100c0, 0xc300c2}, + {0x25, 0x7f7f007f, 0x3c3c003c, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x250002, 0xc100c0, 0xc300c2}, + {0x26, 0x4046007f, 0x3c3c003c, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x260002, 0xc100c0, 0xc300c2}, + {0x27, 0x407f007f, 0x40400040, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x270002, 0xc100c0, 0xc300c2}, + {0x30, 0x407f007f, 0x3b3b0047, 0x0, 0xb2b17f7f, 0x5fde80ff, 0x140003, 0xc100c0, 0xc300c2}, + {0x31, 0x407f007f, 0x3a3a0052, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x60003, 0xc100c0, 0xc300c2}, + {0x32, 0x407f007f, 0x43430048, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x70003, 0xc100c0, 0xc300c2}, + {0x33, 0x407f007f, 0x48480054, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x1b0003, 0xc100c0, 0xc300c2}, + {0x34, 0x407f007f, 0x39390051, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x280003, 0xc100c0, 0xc300c2}, + {0x40, 0x407f0050, 0x24240030, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x80004, 0xc100c0, 0xc300c2}, + {0x41, 0x407f0050, 0x25250031, 0x0, 0xb2b17f7f, 0xdffea5ff, 0x80004, 0xc100c0, 0xc300c2}, + {0x42, 0x407f0050, 0x26260032, 0x0, 0xb2b17f7f, 0xdffeaaff, 0x80004, 0xc100c0, 0xc300c2}, + {0x43, 0x407f007f, 0x3b3b0047, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x130004, 0xc100c0, 0xc300c2}, + {0x44, 0x407f007f, 0x48480060, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x430004, 0xc100c0, 0xc300c2}, + {0x45, 0x407f007f, 0x48480054, 0x0, 0xb2b17f7f, 0xdffeacff, 0x1b0004, 0xc100c0, 0xc300c2}, + {0x50, 0x4064047f, 0x3c3c0060, 0x0, 0xb2b17f7f, 0xdff380ff, 0x740006, 0xc100c0, 0xc300c2}, + {0x51, 0x4064047f, 0x3c3c0078, 0x0, 0xb2b17f7f, 0xdff580ff, 0x4a0006, 0xc100c0, 0xc300c2}, + {0x52, 0x4053047f, 0x3c3c006c, 0x0, 0xb2b17f7f, 0xd170bebf, 0x750006, 0xc100c0, 0xc300c2}, + {0x60, 0x407f007f, 0x40400059, 0x0, 0xb2b17f7f, 0x5fef80ff, 0x180007, 0xc100c0, 0xc300c2}, + {0x61, 0x407f047f, 0x2424003c, 0x0, 0xb2b10000, 0xdfeb80ff, 0x6b0007, 0xc100c0, 0xc300c2}, + {0x62, 0x4e7f0002, 0x25250031, 0x0, 0xb2b10000, 0xdfe380ff, 0x6c0007, 0xc100c0, 0xc300c2}, + {0x63, 0x327f0002, 0x26260a32, 0x0, 0xb2b10000, 0xdfe380ff, 0x6c0007, 0xc100c0, 0xc300c2}, + {0x64, 0x367f0002, 0x28280028, 0x0, 0xb2b10000, 0xdfe380ff, 0x6d0007, 0xc100c0, 0xc300c2}, + {0x65, 0x4a7f0002, 0x29290a29, 0x0, 0xb2b10000, 0xdfe380ff, 0x6d0007, 0xc100c0, 0xc300c2}, + {0x66, 0x207f0002, 0x2a2a0036, 0x0, 0xb2b10000, 0xdfe380ff, 0x6e0007, 0xc100c0, 0xc300c2}, + {0x67, 0x407f0002, 0x2b2b0a37, 0x0, 0xb2b10000, 0xdfe380ff, 0x6e0007, 0xc100c0, 0xc300c2}, + {0x68, 0x607f0002, 0x2c2c0038, 0x0, 0xb2b10000, 0xdfe380ff, 0x6e0007, 0xc100c0, 0xc300c2}, + {0x70, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x40008, 0xc100c0, 0xc300c2}, + {0x80, 0x4064007f, 0x2727003c, 0x0, 0xb2b17f7f, 0x5ffd80ff, 0xa0009, 0xc100c0, 0xc300c2}, + {0x81, 0x407f007f, 0x2626003e, 0x0, 0xb2b17f7f, 0x5ffd80ff, 0x90009, 0xc100c0, 0xc300c2}, + {0x82, 0x405a007f, 0x27270044, 0x0, 0xb2b17f7f, 0x5ffd80ff, 0x160009, 0xc100c0, 0xc300c2}, + {0x83, 0x407f007f, 0x28280040, 0x0, 0xb2b17f7f, 0x5ffc80ff, 0xd0009, 0xc100c0, 0xc300c2}, + {0x84, 0x407f0065, 0x2525003d, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x220009, 0xc100c0, 0xc300c2}, + {0x85, 0x407f007f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x230009, 0xc100c0, 0xc300c2}, + {0x86, 0x407f0000, 0x3d3d465a, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x710009, 0xc100c0, 0xc300c2}, + {0x87, 0x407f0000, 0x29290041, 0x0, 0xb2b17f7f, 0xdfe380ff, 0xd0009, 0xc100c0, 0xc300c2}, + {0x90, 0x407f0064, 0x3c3c0048, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0xb000b, 0xc100c0, 0xc300c2}, + {0x91, 0x407f0064, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x44000b, 0xc100c0, 0xc300c2}, + {0xa0, 0x407f0002, 0x26260026, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x19000c, 0xc100c0, 0xc300c2}, + {0xa1, 0x407f007f, 0x27270027, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x17000c, 0xc100c0, 0xc300c2}, + {0xa2, 0x407f0064, 0x28280040, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x29000c, 0xc100c0, 0xc300c2}, + {0xa3, 0x407f0064, 0x29290041, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x2a000c, 0xc100c0, 0xc300c2}, + {0xa4, 0x407f0064, 0x2a2a0042, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x2b000c, 0xc100c0, 0xc300c2}, + {0xa5, 0x407f0064, 0x2c2c0044, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x3a000c, 0xc100c0, 0xc300c2}, + {0xb0, 0x187f007f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0x5ff080ff, 0xe000d, 0xc100c0, 0xc300c2}, + {0xc0, 0x407f0050, 0x24240024, 0x0, 0xb2b17f7f, 0xdfe380ff, 0xf000e, 0xc100c0, 0xc300c2}, + {0xc1, 0x407f0050, 0x25250025, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x10000e, 0xc100c0, 0xc300c2}, + {0xc2, 0x407f0050, 0x3d3d4659, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x1c000e, 0xc100c0, 0xc300c2}, + {0xc3, 0x407f0050, 0x3e3e465a, 0x0, 0xb2b17f7f, 0xdfe6afff, 0x1c000e, 0xc100c0, 0xc300c2}, + {0xd0, 0x407f0479, 0x30300048, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x3b0010, 0xc100c0, 0xc300c2}, + {0xe0, 0x2c7f007f, 0x24240030, 0x0, 0xb2b17f7f, 0x5ffc80ff, 0x110011, 0xc100c0, 0xc300c2}, + {0xe1, 0x367f007f, 0x25250031, 0x0, 0xb2b17f7f, 0x5ffd80ff, 0x120011, 0xc100c0, 0xc300c2}, + {0xe2, 0x407f007f, 0x3b3b4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x490011, 0xc100c0, 0xc300c2}, + {0xf0, 0x407f0064, 0x2d2d0045, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x2c0012, 0xc100c0, 0xc300c2}, + {0xf1, 0x407f007f, 0x2e2e0046, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x2d0012, 0xc100c0, 0xc300c2}, + {0xf2, 0x407f0032, 0x2f2f0047, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x2e0012, 0xc100c0, 0xc300c2}, + {0xf3, 0x407f0032, 0x30300048, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x2f0012, 0xc100c0, 0xc300c2}, + {0xf4, 0x407f0032, 0x2424003c, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x300012, 0xc100c0, 0xc300c2}, + {0xf5, 0x407f0032, 0x2525003d, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x310012, 0xc100c0, 0xc300c2}, + {0x100, 0x407f007e, 0x7f006a53, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x10015, 0xc100c0, 0xc300c2}, + {0x110, 0x407f0064, 0x3d3d004e, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x60016, 0xc100c0, 0xc300c2}, + {0x111, 0x407f0064, 0x3e3e0045, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x140016, 0xc100c0, 0xc300c2}, + {0x112, 0x407f0064, 0x3f3f0046, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x140016, 0xc100c0, 0xc300c2}, + {0x113, 0x407f0064, 0x3f3f0050, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x2a0016, 0xc100c0, 0xc300c2}, + {0x114, 0x3c7f0050, 0x2121002d, 0x0, 0xb2b17f7f, 0xdfe0a4ff, 0x80016, 0xc100c0, 0xc300c2}, + {0x115, 0x407f0050, 0x2222002d, 0x0, 0xb2b17f7f, 0xdfe0aaff, 0x80016, 0xc100c0, 0xc300c2}, + {0x116, 0x3c7f0050, 0x2323002d, 0x0, 0xb2b17f7f, 0xdfe0a7ff, 0x80016, 0xc100c0, 0xc300c2}, + {0x117, 0x407f0050, 0x2424002d, 0x0, 0xb2b17f7f, 0xdfe0a9ff, 0x80016, 0xc100c0, 0xc300c2}, + {0x118, 0x407f0050, 0x2525002d, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x370016, 0xc100c0, 0xc300c2}, + {0x120, 0x407f047f, 0x3e3e0073, 0x0, 0xb2b17f7f, 0xceab80ff, 0x400018, 0xc100c0, 0xc300c2}, + {0x130, 0x2c7f007f, 0x24240030, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x1a0019, 0xc100c0, 0xc300c2}, + {0x131, 0x407f007f, 0x25250031, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x120019, 0xc100c0, 0xc300c2}, + {0x132, 0x547f007f, 0x24246e31, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x1a0019, 0xc100c0, 0xc300c2}, + {0x140, 0x407f0400, 0x7f000054, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x38001b, 0xc100c0, 0xc300c2}, + {0x150, 0x407f047f, 0x7f004659, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x39001c, 0xc100c0, 0xc300c2}, + {0x160, 0x407f047f, 0x7f00003c, 0x0, 0xb2b17f7f, 0x5feb80ff, 0x400021, 0xc100c0, 0xc300c2}, + {0x170, 0x407f007f, 0x41410456, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x470027, 0xc100c0, 0xc300c2}, + {0x171, 0x407f047f, 0x3c3c0448, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x6a0027, 0xc100c0, 0xc300c2}, + {0x180, 0x407f007f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x450028, 0xc100c0, 0xc300c2}, + {0x181, 0x407f007f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x460028, 0xc100c0, 0xc300c2}, + {0x190, 0x407f0019, 0x3e3e465b, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x72002c, 0xc100c0, 0xc300c2}, + {0x191, 0x407f0019, 0x3f3f465c, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x73002c, 0xc100c0, 0xc300c2}, + {0x1a0, 0x407f0040, 0x3d3d3c56, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x700031, 0xc100c0, 0xc300c2}, + {0x1b0, 0x407f007f, 0x7f000048, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x4b003a, 0xc100c0, 0xc300c2}, + {0x1c0, 0x407f007f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x4c003b, 0xc100c0, 0xc300c2}, + {0x1c1, 0x407f007f, 0x3e3e465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x4d003b, 0xc100c0, 0xc300c2}, + {0x1c2, 0x407f007f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x4e003b, 0xc100c0, 0xc300c2}, + {0x1c3, 0x407f007f, 0x3f3f005b, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x4f003b, 0xc100c0, 0xc300c2}, + {0x1d0, 0x407f007f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x50003c, 0xc100c0, 0xc300c2}, + {0x1d1, 0x407f007f, 0x3f3f465c, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x51003c, 0xc100c0, 0xc300c2}, + {0x1e0, 0x407f007f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x52003d, 0xc100c0, 0xc300c2}, + {0x1e1, 0x407f007f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x53003d, 0xc100c0, 0xc300c2}, + {0x1e2, 0x407f007f, 0x37374653, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x54003d, 0xc100c0, 0xc300c2}, + {0x1e3, 0x407f007f, 0x38384655, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x55003d, 0xc100c0, 0xc300c2}, + {0x1e4, 0x407f007f, 0x39394656, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x56003d, 0xc100c0, 0xc300c2}, + {0x1e5, 0x407f007f, 0x3b3b4658, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x57003d, 0xc100c0, 0xc300c2}, + {0x1e6, 0x407f007f, 0x3a3a4657, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x58003d, 0xc100c0, 0xc300c2}, + {0x1e7, 0x407f007f, 0x4040465d, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x59003d, 0xc100c0, 0xc300c2}, + {0x1e8, 0x407f007f, 0x4141004d, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x5a003d, 0xc100c0, 0xc300c2}, + {0x1f0, 0x407f007f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x5b003e, 0xc100c0, 0xc300c2}, + {0x1f1, 0x407f007f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x5c003e, 0xc100c0, 0xc300c2}, + {0x1f2, 0x407f007f, 0x3e3e465b, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x5d003e, 0xc100c0, 0xc300c2}, + {0x1f3, 0x407f007f, 0x3f3f465c, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x5e003e, 0xc100c0, 0xc300c2}, + {0x200, 0x407f007f, 0x25254642, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x5f003f, 0xc100c0, 0xc300c2}, + {0x201, 0x407f007f, 0x26264643, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x60003f, 0xc100c0, 0xc300c2}, + {0x202, 0x407f007f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x61003f, 0xc100c0, 0xc300c2}, + {0x203, 0x407f007f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x62003f, 0xc100c0, 0xc300c2}, + {0x210, 0x407f007f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x630040, 0xc100c0, 0xc300c2}, + {0x211, 0x407f007f, 0x3e3e465b, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x640040, 0xc100c0, 0xc300c2}, + {0x212, 0x407f007f, 0x27274644, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x650040, 0xc100c0, 0xc300c2}, + {0x213, 0x407f007f, 0x3f3f465c, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x660040, 0xc100c0, 0xc300c2}, + {0x220, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x670041, 0xc100c0, 0xc300c2}, + {0x221, 0x407f007f, 0x4040465d, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x680041, 0xc100c0, 0xc300c2}, + {0x230, 0x407f047f, 0x7f004667, 0x0, 0xb2b17f7f, 0xdfefaaff, 0x690042, 0xc100c0, 0xc300c2}, + {0x240, 0x7f0404, 0x53000048, 0x0, 0xb2b10000, 0xdfeb80ff, 0x4a0046, 0xc100c0, 0xc300c2}, + {0x241, 0x7f7f0404, 0x7f540048, 0x0, 0xb2b10000, 0xdfeb80ff, 0x4a0046, 0xc100c0, 0xc300c2}, + {0x250, 0x7f0064, 0x30304659, 0x0, 0xb2b10101, 0xdff080ff, 0x32004b, 0xc100c0, 0xc300c2}, + {0x251, 0x7f7f0464, 0x3c3c4665, 0x0, 0xb2b10101, 0xdff080ff, 0x33004b, 0xc100c0, 0xc300c2}, + {0x252, 0x407f0464, 0x48484671, 0x0, 0xb2b10101, 0xdff080ff, 0x33004b, 0xc100c0, 0xc300c2}, + {0x260, 0x407f044f, 0x7f00465a, 0x0, 0xb2b10101, 0x5ff0c0ff, 0x34004c, 0xc100c0, 0xc300c2}, + {0x270, 0x407f0454, 0x3f3f004b, 0x0, 0xb2b10000, 0xdfeb80ff, 0x36004d, 0xc100c0, 0xc300c2}, + {0x271, 0x407f005a, 0x3b00003c, 0x0, 0xb2b10000, 0xdfeb80ff, 0x35004d, 0xc100c0, 0xc300c2}, + {0x272, 0x406e0055, 0x3c3c0048, 0x0, 0xb2b10000, 0xdfeb80ff, 0x6f004d, 0xc100c0, 0xc300c2}, + {0x280, 0x407f0464, 0x47005a49, 0x0, 0xb2b10000, 0xdff0a0ff, 0x3c004e, 0xc100c0, 0xc300c2}, + {0x281, 0x407f0464, 0x7f48005d, 0x0, 0xb2b10000, 0xdfeb80ff, 0x3d004e, 0xc100c0, 0xc300c2}, + {0x290, 0x407f0455, 0x7f250048, 0x0, 0xb2b10000, 0xdfeb80ff, 0x3e004f, 0xc100c0, 0xc300c2}, + {0x291, 0x6464044f, 0x24230048, 0x0, 0xb2b10000, 0xdfeb80ff, 0x3e004f, 0xc100c0, 0xc300c2}, + {0x292, 0x1b640405, 0x2200003c, 0x0, 0xb2b10000, 0xdfeb80ff, 0x3e004f, 0xc100c0, 0xc300c2}, + {0x2a0, 0x6e047f, 0x7f005a48, 0x0, 0xb2b10000, 0xdfe780ff, 0x3f0050, 0xc100c0, 0xc300c2}, + {0x2a1, 0x7f6e047f, 0x7f006148, 0x0, 0xb2b10000, 0xdfe780ff, 0x3f0050, 0xc100c0, 0xc300c2}, + {0x2b0, 0x407f047f, 0x7f00465a, 0x0, 0xb2b10000, 0x5febb4ff, 0x340051, 0xc100c0, 0xc300c2}}}; + +const std::vector> AO_F1SNDFX_VH = {{ + {0x0, 0x7f047f, 0x3e006a53, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10000, 0xc100c0, 0xc300c2}, + {0x1, 0x1b380400, 0x3e006a5f, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10000, 0xc100c0, 0xc300c2}, + {0x2, 0x64500400, 0x3e006a66, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10000, 0xc100c0, 0xc300c2}, + {0x3, 0x40280400, 0x3e006a72, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10000, 0xc100c0, 0xc300c2}, + {0x4, 0x7f7f047f, 0x3e006a54, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10000, 0xc100c0, 0xc300c2}, + {0x5, 0x407f005b, 0x42420050, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x1a0000, 0xc100c0, 0xc300c2}, + {0x6, 0x407f005b, 0x4343004f, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0xd0000, 0xc100c0, 0xc300c2}, + {0x7, 0x407f0464, 0x3e006a4d, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x10000, 0xc100c0, 0xc300c2}, + {0x8, 0x407f007f, 0x4848003c, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x290000, 0xc100c0, 0xc300c2}, + {0x9, 0x407f007f, 0x49490049, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x2a0000, 0xc100c0, 0xc300c2}, + {0x10, 0x407f007f, 0x3a3a0046, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x220001, 0xc100c0, 0xc300c2}, + {0x11, 0x407f007f, 0x3b3b0045, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x30001, 0xc100c0, 0xc300c2}, + {0x12, 0x407f007f, 0x48480060, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x3b0001, 0xc100c0, 0xc300c2}, + {0x13, 0x407f007f, 0x48480060, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x20001, 0xc100c0, 0xc300c2}, + {0x14, 0x7f007f, 0x48480048, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x20001, 0xc100c0, 0xc300c2}, + {0x15, 0x7f640051, 0x4848006c, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x20001, 0xc100c0, 0xc300c2}, + {0x16, 0x640051, 0x4848326b, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x20001, 0xc100c0, 0xc300c2}, + {0x17, 0x407f007f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x390001, 0xc100c0, 0xc300c2}, + {0x18, 0x407f007f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x3a0001, 0xc100c0, 0xc300c2}, + {0x19, 0x407f0000, 0x39390045, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x560001, 0xc100c0, 0xc300c2}, + {0x20, 0x40150464, 0x3c3c6a66, 0x0, 0xb2b17f7f, 0xdaeb80ff, 0x10002, 0xc100c0, 0xc300c2}, + {0x21, 0x3f5f0400, 0x3b006a4e, 0x0, 0xb2b17f7f, 0xdfef80ff, 0x10002, 0xc100c0, 0xc300c2}, + {0x22, 0x540400, 0x3b006a54, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x10002, 0xc100c0, 0xc300c2}, + {0x23, 0x7f540400, 0x3b006a56, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x10002, 0xc100c0, 0xc300c2}, + {0x24, 0x7f007f, 0x3c3c003c, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x2e0002, 0xc100c0, 0xc300c2}, + {0x25, 0x7f7f007f, 0x3c3c003c, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x2f0002, 0xc100c0, 0xc300c2}, + {0x26, 0x4046007f, 0x3c3c003c, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x300002, 0xc100c0, 0xc300c2}, + {0x27, 0x407f007f, 0x40400040, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x310002, 0xc100c0, 0xc300c2}, + {0x30, 0x407f007f, 0x3b3b0047, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x210003, 0xc100c0, 0xc300c2}, + {0x31, 0x407f007f, 0x3a3a0052, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x100003, 0xc100c0, 0xc300c2}, + {0x32, 0x407f007f, 0x43430048, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x110003, 0xc100c0, 0xc300c2}, + {0x33, 0x407f007f, 0x48480054, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x280003, 0xc100c0, 0xc300c2}, + {0x34, 0x407f007f, 0x39390051, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x320003, 0xc100c0, 0xc300c2}, + {0x40, 0x407f0050, 0x24240030, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x120004, 0xc100c0, 0xc300c2}, + {0x41, 0x407f0050, 0x25250031, 0x0, 0xb2b17f7f, 0xdffea5ff, 0x120004, 0xc100c0, 0xc300c2}, + {0x42, 0x407f0050, 0x26260032, 0x0, 0xb2b17f7f, 0xdffeaaff, 0x120004, 0xc100c0, 0xc300c2}, + {0x43, 0x407f007f, 0x3b3b0047, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x200004, 0xc100c0, 0xc300c2}, + {0x44, 0x407f007f, 0x48480060, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x3b0004, 0xc100c0, 0xc300c2}, + {0x45, 0x407f007f, 0x48480054, 0x0, 0xb2b17f7f, 0xdffeacff, 0x280004, 0xc100c0, 0xc300c2}, + {0x50, 0x407f0050, 0x25250030, 0x0, 0xb2b17f7f, 0x5fe580ff, 0x230005, 0xc100c0, 0xc300c2}, + {0x51, 0x407f007f, 0x3f3f005a, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x40005, 0xc100c0, 0xc300c2}, + {0x52, 0x407f007f, 0x4040005a, 0x0, 0xb2b17f7f, 0x5fed80ff, 0x50005, 0xc100c0, 0xc300c2}, + {0x53, 0x407f003c, 0x41410059, 0x0, 0xb2b17f7f, 0x5fef80ff, 0x50005, 0xc100c0, 0xc300c2}, + {0x54, 0x407f003b, 0x42420058, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x50005, 0xc100c0, 0xc300c2}, + {0x55, 0x407f007f, 0x43434663, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x160005, 0xc100c0, 0xc300c2}, + {0x56, 0x407f007f, 0x3d3d4661, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x7e0005, 0xc100c0, 0xc300c2}, + {0x60, 0x4064047f, 0x3c3c0060, 0x0, 0xb2b17f7f, 0xdff380ff, 0x60006, 0xc100c0, 0xc300c2}, + {0x61, 0x4064047f, 0x3c3c0078, 0x0, 0xb2b17f7f, 0xdff580ff, 0x70006, 0xc100c0, 0xc300c2}, + {0x62, 0x4053047f, 0x3c3c006c, 0x0, 0xb2b17f7f, 0x5170bebf, 0x2b0006, 0xc100c0, 0xc300c2}, + {0x63, 0x406c047f, 0x3535006c, 0x0, 0xb2b17f7f, 0x5ff580ff, 0x70006, 0xc100c0, 0xc300c2}, + {0x64, 0x406c047f, 0x35350054, 0x0, 0xb2b17f7f, 0x5ff380ff, 0x60006, 0xc100c0, 0xc300c2}, + {0x65, 0x406c047f, 0x3636006d, 0x0, 0xb2b17f7f, 0x5ff580ff, 0x70006, 0xc100c0, 0xc300c2}, + {0x66, 0x406c047f, 0x36360055, 0x0, 0xb2b17f7f, 0x5ff380ff, 0x60006, 0xc100c0, 0xc300c2}, + {0x67, 0x4064047f, 0x3030006c, 0x0, 0xb2b17f7f, 0x5ff580ff, 0x70006, 0xc100c0, 0xc300c2}, + {0x68, 0x4053047f, 0x30300060, 0x0, 0xb2b17f7f, 0xd170bebf, 0x2b0006, 0xc100c0, 0xc300c2}, + {0x70, 0x407f0002, 0x5a490054, 0x0, 0xb2b17f7f, 0x5fe081ff, 0x1b0007, 0xc100c0, 0xc300c2}, + {0x71, 0x407f0402, 0x48480054, 0x0, 0xb2b17f7f, 0x5fe580ff, 0x80007, 0xc100c0, 0xc300c2}, + {0x72, 0x407f0402, 0x47477853, 0x0, 0xb2b17f7f, 0x5fe580ff, 0x80007, 0xc100c0, 0xc300c2}, + {0x73, 0x357f0000, 0x3f3c0048, 0x0, 0xb2b17f7f, 0x5fea85ff, 0x90007, 0xc100c0, 0xc300c2}, + {0x74, 0x497f0000, 0x3b3a0044, 0x0, 0xb2b17f7f, 0x5fea82ff, 0xa0007, 0xc100c0, 0xc300c2}, + {0x75, 0x407f007f, 0x40400059, 0x0, 0xb2b17f7f, 0x5fef80ff, 0x250007, 0xc100c0, 0xc300c2}, + {0x80, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0xdfe080ff, 0xb0008, 0xc100c0, 0xc300c2}, + {0x81, 0x407f007f, 0x39390054, 0x0, 0xb2b17f7f, 0x5fef80ff, 0xe0008, 0xc100c0, 0xc300c2}, + {0x82, 0x407f007f, 0x3a3a0053, 0x0, 0xb2b17f7f, 0x5fe0b0ff, 0xf0008, 0xc100c0, 0xc300c2}, + {0x83, 0x407f0064, 0x3d3d0059, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x140008, 0xc100c0, 0xc300c2}, + {0x90, 0x4064007f, 0x2727003c, 0x0, 0xb2b17f7f, 0x5ffd80ff, 0x150009, 0xc100c0, 0xc300c2}, + {0x91, 0x407f007f, 0x2626003e, 0x0, 0xb2b17f7f, 0x5ffd80ff, 0x130009, 0xc100c0, 0xc300c2}, + {0x92, 0x405a007f, 0x27270044, 0x0, 0xb2b17f7f, 0x5ffd80ff, 0x240009, 0xc100c0, 0xc300c2}, + {0x93, 0x407f007f, 0x28280040, 0x0, 0xb2b17f7f, 0x5ffc80ff, 0x1c0009, 0xc100c0, 0xc300c2}, + {0x94, 0x407f0065, 0x2525003d, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x2c0009, 0xc100c0, 0xc300c2}, + {0x95, 0x407f007f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x2d0009, 0xc100c0, 0xc300c2}, + {0x96, 0x407f0000, 0x3d3d465a, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x800009, 0xc100c0, 0xc300c2}, + {0x97, 0x407f0000, 0x2424003c, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x2c0009, 0xc100c0, 0xc300c2}, + {0x98, 0x407f0000, 0x29290041, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x1c0009, 0xc100c0, 0xc300c2}, + {0xa0, 0x407f007f, 0x3d3d6a48, 0x0, 0xb2b17f7f, 0x5fcf80ff, 0xc000a, 0xc100c0, 0xc300c2}, + {0xa1, 0x407f007f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x36000a, 0xc100c0, 0xc300c2}, + {0xb0, 0x407f0064, 0x3c3c0048, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x17000b, 0xc100c0, 0xc300c2}, + {0xb1, 0x407f0064, 0x3f3f004b, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x18000b, 0xc100c0, 0xc300c2}, + {0xb2, 0x407f0064, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x3c000b, 0xc100c0, 0xc300c2}, + {0xb3, 0x407f0064, 0x3e3e0056, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x3d000b, 0xc100c0, 0xc300c2}, + {0xb4, 0x407f0064, 0x41410059, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x3e000b, 0xc100c0, 0xc300c2}, + {0xb5, 0x407f0064, 0x4040465d, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x49000b, 0xc100c0, 0xc300c2}, + {0xb6, 0x407f0064, 0x4242465f, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x4a000b, 0xc100c0, 0xc300c2}, + {0xc0, 0x407f007f, 0x3b3b4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x570011, 0xc100c0, 0xc300c2}, + {0xd0, 0x407f007f, 0x30300048, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x480013, 0xc100c0, 0xc300c2}, + {0xe0, 0x407f007e, 0x7f006a53, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x10015, 0xc100c0, 0xc300c2}, + {0xf0, 0x4032007f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x1d0017, 0xc100c0, 0xc300c2}, + {0xf1, 0x4046007f, 0x3f3d0055, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x1e0017, 0xc100c0, 0xc300c2}, + {0xf2, 0x4046001b, 0x40400055, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x1f0017, 0xc100c0, 0xc300c2}, + {0xf3, 0x4032007f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x1d0017, 0xc100c0, 0xc300c2}, + {0xf4, 0x4046007f, 0x3f3d0055, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x1e0017, 0xc100c0, 0xc300c2}, + {0xf5, 0x4046007f, 0x40400055, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x1f0017, 0xc100c0, 0xc300c2}, + {0xf6, 0x4032007f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x1d0017, 0xc100c0, 0xc300c2}, + {0xf7, 0x4032007f, 0x3e3e0056, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x1d0017, 0xc100c0, 0xc300c2}, + {0xf8, 0x4032007f, 0x3f3f0057, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x1d0017, 0xc100c0, 0xc300c2}, + {0xf9, 0x4032007f, 0x40400058, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x1d0017, 0xc100c0, 0xc300c2}, + {0x100, 0x407f047f, 0x3e3e0073, 0x0, 0xb2b17f7f, 0xceab80ff, 0x380018, 0xc100c0, 0xc300c2}, + {0x110, 0x2c7f007f, 0x24240030, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x270019, 0xc100c0, 0xc300c2}, + {0x111, 0x407f007f, 0x25250031, 0x0, 0xb2b17f7f, 0x5ffd80ff, 0x260019, 0xc100c0, 0xc300c2}, + {0x112, 0x547f007f, 0x24246e31, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x270019, 0xc100c0, 0xc300c2}, + {0x120, 0x407f0400, 0x7f000054, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x35001b, 0xc100c0, 0xc300c2}, + {0x130, 0x407f007f, 0x7f004659, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x58001c, 0xc100c0, 0xc300c2}, + {0x140, 0x407f0052, 0x3c3c0054, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x47001f, 0xc100c0, 0xc300c2}, + {0x150, 0x407f047f, 0x2424003c, 0x0, 0xb2b17f7f, 0x5feb80ff, 0x380021, 0xc100c0, 0xc300c2}, + {0x160, 0x407f007f, 0x3c3c0060, 0x0, 0xb2b17f7f, 0x5fe080ff, 0x460026, 0xc100c0, 0xc300c2}, + {0x170, 0x407f007f, 0x41410456, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x4b0027, 0xc100c0, 0xc300c2}, + {0x180, 0x407f0400, 0x2424003c, 0x0, 0xb2b10000, 0x5fc380ff, 0x4c002b, 0xc100c0, 0xc300c2}, + {0x181, 0x407f0400, 0x2525003d, 0x0, 0xb2b10000, 0xdfe380ff, 0x4d002b, 0xc100c0, 0xc300c2}, + {0x182, 0x407f0400, 0x2626003e, 0x0, 0xb2b10000, 0xdfe380ff, 0x4e002b, 0xc100c0, 0xc300c2}, + {0x183, 0x407f0000, 0x2727003f, 0x0, 0xb2b10000, 0xdfe380ff, 0x4f002b, 0xc100c0, 0xc300c2}, + {0x184, 0x407f0400, 0x28280040, 0x0, 0xb2b10000, 0xdfe380ff, 0x50002b, 0xc100c0, 0xc300c2}, + {0x185, 0x407f0000, 0x29290041, 0x0, 0xb2b10000, 0xdfe380ff, 0x51002b, 0xc100c0, 0xc300c2}, + {0x186, 0x407f0400, 0x2a2a0042, 0x0, 0xb2b10000, 0xdfe380ff, 0x52002b, 0xc100c0, 0xc300c2}, + {0x187, 0x407f0000, 0x2b2b0043, 0x0, 0xb2b10000, 0xdfe380ff, 0x53002b, 0xc100c0, 0xc300c2}, + {0x188, 0x407f0400, 0x2c2c0044, 0x0, 0xb2b10000, 0xdfe380ff, 0x54002b, 0xc100c0, 0xc300c2}, + {0x189, 0x407f0000, 0x2d2d0045, 0x0, 0xb2b10000, 0xdfe380ff, 0x55002b, 0xc100c0, 0xc300c2}, + {0x190, 0x407f0040, 0x3d3d3c56, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x7f0031, 0xc100c0, 0xc300c2}, + {0x1a0, 0x407f007f, 0x7f000048, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x5d003a, 0xc100c0, 0xc300c2}, + {0x1b0, 0x407f007f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x5e003b, 0xc100c0, 0xc300c2}, + {0x1b1, 0x407f007f, 0x3e3e465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x5f003b, 0xc100c0, 0xc300c2}, + {0x1b2, 0x407f007f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x60003b, 0xc100c0, 0xc300c2}, + {0x1b3, 0x407f007f, 0x3f3f005b, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x61003b, 0xc100c0, 0xc300c2}, + {0x1c0, 0x407f007f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x62003c, 0xc100c0, 0xc300c2}, + {0x1c1, 0x407f007f, 0x3f3f465c, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x63003c, 0xc100c0, 0xc300c2}, + {0x1d0, 0x407f007f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x64003d, 0xc100c0, 0xc300c2}, + {0x1d1, 0x407f007f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x65003d, 0xc100c0, 0xc300c2}, + {0x1d2, 0x407f007f, 0x37374653, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x66003d, 0xc100c0, 0xc300c2}, + {0x1d3, 0x407f007f, 0x38384655, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x67003d, 0xc100c0, 0xc300c2}, + {0x1d4, 0x407f007f, 0x39394656, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x68003d, 0xc100c0, 0xc300c2}, + {0x1d5, 0x407f007f, 0x3b3b4658, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x69003d, 0xc100c0, 0xc300c2}, + {0x1d6, 0x407f007f, 0x3a3a4657, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x6a003d, 0xc100c0, 0xc300c2}, + {0x1d7, 0x407f007f, 0x4040465d, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x6b003d, 0xc100c0, 0xc300c2}, + {0x1d8, 0x407f007f, 0x4141004d, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x6c003d, 0xc100c0, 0xc300c2}, + {0x1e0, 0x407f007f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x6d003e, 0xc100c0, 0xc300c2}, + {0x1e1, 0x407f007f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x6e003e, 0xc100c0, 0xc300c2}, + {0x1e2, 0x407f007f, 0x3e3e465b, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x6f003e, 0xc100c0, 0xc300c2}, + {0x1e3, 0x407f007f, 0x3f3f465c, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x70003e, 0xc100c0, 0xc300c2}, + {0x1f0, 0x407f007f, 0x25254642, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x71003f, 0xc100c0, 0xc300c2}, + {0x1f1, 0x407f007f, 0x26264643, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x72003f, 0xc100c0, 0xc300c2}, + {0x1f2, 0x407f007f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x73003f, 0xc100c0, 0xc300c2}, + {0x1f3, 0x407f007f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x74003f, 0xc100c0, 0xc300c2}, + {0x200, 0x407f007f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x750040, 0xc100c0, 0xc300c2}, + {0x201, 0x407f007f, 0x3e3e465b, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x760040, 0xc100c0, 0xc300c2}, + {0x202, 0x407f007f, 0x27274644, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x770040, 0xc100c0, 0xc300c2}, + {0x203, 0x407f007f, 0x3f3f465c, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x780040, 0xc100c0, 0xc300c2}, + {0x210, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x790041, 0xc100c0, 0xc300c2}, + {0x211, 0x407f007f, 0x4040465d, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x7a0041, 0xc100c0, 0xc300c2}, + {0x220, 0x407f047f, 0x7f004667, 0x0, 0xb2b17f7f, 0xdfefaaff, 0x7b0042, 0xc100c0, 0xc300c2}, + {0x230, 0x2c7f043c, 0x2f000048, 0x0, 0xb2b10000, 0xdff080ff, 0x7c0045, 0xc100c0, 0xc300c2}, + {0x231, 0x547f0450, 0x7f300048, 0x0, 0xb2b10000, 0xdff080ff, 0x7c0045, 0xc100c0, 0xc300c2}, + {0x240, 0x7f0404, 0x53000048, 0x0, 0xb2b10000, 0xdfeb80ff, 0x70046, 0xc100c0, 0xc300c2}, + {0x241, 0x7f7f0404, 0x7f540048, 0x0, 0xb2b10000, 0xdfeb80ff, 0x70046, 0xc100c0, 0xc300c2}, + {0x250, 0x407f0464, 0x7f000454, 0x0, 0xb2b10000, 0x5ff080ff, 0x440047, 0xc100c0, 0xc300c2}, + {0x260, 0x407f0464, 0x30000440, 0x0, 0xb2b10000, 0x5ff080ff, 0x400048, 0xc100c0, 0xc300c2}, + {0x261, 0x407f0464, 0x40310458, 0x0, 0xb2b10000, 0x5feb80ff, 0x410048, 0xc100c0, 0xc300c2}, + {0x262, 0x407f0464, 0x4e41045f, 0x0, 0xb2b10000, 0x5feb80ff, 0x420048, 0xc100c0, 0xc300c2}, + {0x263, 0x407f0464, 0x7f4f045f, 0x0, 0xb2b10000, 0x5feb80ff, 0x430048, 0xc100c0, 0xc300c2}, + {0x270, 0x407f0464, 0x7f000467, 0x0, 0xb2b10000, 0x5fee80ff, 0x3f0049, 0xc100c0, 0xc300c2}, + {0x280, 0x407f0454, 0x3f3f004b, 0x0, 0xb2b10000, 0xdfeb80ff, 0x33004d, 0xc100c0, 0xc300c2}, + {0x281, 0x407f005a, 0x3b00003c, 0x0, 0xb2b10000, 0xdfeb80ff, 0x34004d, 0xc100c0, 0xc300c2}, + {0x282, 0x406e0055, 0x3c3c0048, 0x0, 0xb2b10000, 0xdfeb80ff, 0x7d004d, 0xc100c0, 0xc300c2}, + {0x290, 0x407f0464, 0x47005a49, 0x0, 0xb2b10000, 0xdff0a0ff, 0x59004e, 0xc100c0, 0xc300c2}, + {0x291, 0x407f0464, 0x7f48005d, 0x0, 0xb2b10000, 0xdfeb80ff, 0x5a004e, 0xc100c0, 0xc300c2}, + {0x2a0, 0x407f0455, 0x7f250048, 0x0, 0xb2b10000, 0xdfeb80ff, 0x5b004f, 0xc100c0, 0xc300c2}, + {0x2a1, 0x6464044f, 0x24230048, 0x0, 0xb2b10000, 0xdfeb80ff, 0x5b004f, 0xc100c0, 0xc300c2}, + {0x2a2, 0x1b640405, 0x2200003c, 0x0, 0xb2b10000, 0xdfeb80ff, 0x5b004f, 0xc100c0, 0xc300c2}, + {0x2b0, 0x6e047f, 0x7f005a48, 0x0, 0xb2b10000, 0xdfe780ff, 0x370050, 0xc100c0, 0xc300c2}, + {0x2b1, 0x7f6e047f, 0x7f006148, 0x0, 0xb2b10000, 0xdfe780ff, 0x370050, 0xc100c0, 0xc300c2}, + {0x2c0, 0x407f047f, 0x7f00465a, 0x0, 0xb2b10000, 0xdfebb4ff, 0x5c0051, 0xc100c0, 0xc300c2}, + {0x2d0, 0x7f0464, 0x3c3c0448, 0x0, 0xb2b10000, 0x5feba5ff, 0x450054, 0xc100c0, 0xc300c2}, + {0x2d1, 0x7f7f0464, 0x3d3d0449, 0x0, 0xb2b10000, 0x5feba5ff, 0x450054, 0xc100c0, 0xc300c2}, + {0x2d2, 0x7f0464, 0x3e3e044a, 0x0, 0xb2b10000, 0x5feb80ff, 0x450054, 0xc100c0, 0xc300c2}, + {0x2d3, 0x7f7f0464, 0x3f3f044b, 0x0, 0xb2b10000, 0x5feb80ff, 0x450054, 0xc100c0, 0xc300c2}}}; + +const std::vector> AO_F2SNDFX_VH = {{ + {0x0, 0x7f047f, 0x3e006a53, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10000, 0xc100c0, 0xc300c2}, + {0x1, 0x1b380400, 0x3e006a5f, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10000, 0xc100c0, 0xc300c2}, + {0x2, 0x64500400, 0x3e006a66, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10000, 0xc100c0, 0xc300c2}, + {0x3, 0x40280400, 0x3e006a72, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10000, 0xc100c0, 0xc300c2}, + {0x4, 0x7f7f047f, 0x3e006a54, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10000, 0xc100c0, 0xc300c2}, + {0x5, 0x407f045b, 0x42420050, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0xf0000, 0xc100c0, 0xc300c2}, + {0x6, 0x407f045b, 0x4343004f, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x70000, 0xc100c0, 0xc300c2}, + {0x7, 0x407f0464, 0x3e006a4d, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x10000, 0xc100c0, 0xc300c2}, + {0x8, 0x407f047f, 0x4848003c, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x200000, 0xc100c0, 0xc300c2}, + {0x9, 0x407f047f, 0x49490049, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x210000, 0xc100c0, 0xc300c2}, + {0x10, 0x407f047f, 0x3a3a0046, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x180001, 0xc100c0, 0xc300c2}, + {0x11, 0x407f047f, 0x3b3b0045, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x30001, 0xc100c0, 0xc300c2}, + {0x12, 0x407f047f, 0x48480060, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x400001, 0xc100c0, 0xc300c2}, + {0x13, 0x407f047f, 0x48480060, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x20001, 0xc100c0, 0xc300c2}, + {0x14, 0x7f047f, 0x48480048, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x20001, 0xc100c0, 0xc300c2}, + {0x15, 0x7f640451, 0x4848006c, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x20001, 0xc100c0, 0xc300c2}, + {0x16, 0x640451, 0x4848326b, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x20001, 0xc100c0, 0xc300c2}, + {0x17, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x3e0001, 0xc100c0, 0xc300c2}, + {0x18, 0x407f047f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x3f0001, 0xc100c0, 0xc300c2}, + {0x19, 0x407f0400, 0x39390045, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x600001, 0xc100c0, 0xc300c2}, + {0x20, 0x40150464, 0x3c3c6a66, 0x0, 0xb2b17f7f, 0xdaeb80ff, 0x10002, 0xc100c0, 0xc300c2}, + {0x21, 0x3f5f0400, 0x3b006a4e, 0x0, 0xb2b17f7f, 0x5fef80ff, 0x10002, 0xc100c0, 0xc300c2}, + {0x22, 0x540400, 0x3b006a54, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10002, 0xc100c0, 0xc300c2}, + {0x23, 0x7f540400, 0x3b006a56, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10002, 0xc100c0, 0xc300c2}, + {0x24, 0x7f007f, 0x3c3c003c, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x240002, 0xc100c0, 0xc300c2}, + {0x25, 0x7f7f007f, 0x3c3c003c, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x250002, 0xc100c0, 0xc300c2}, + {0x26, 0x4046047f, 0x3c3c003c, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x260002, 0xc100c0, 0xc300c2}, + {0x27, 0x407f047f, 0x40400040, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x270002, 0xc100c0, 0xc300c2}, + {0x30, 0x407f047f, 0x3b3b0047, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x150003, 0xc100c0, 0xc300c2}, + {0x31, 0x407f047f, 0x3a3a0052, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0xa0003, 0xc100c0, 0xc300c2}, + {0x32, 0x407f047f, 0x43430048, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0xb0003, 0xc100c0, 0xc300c2}, + {0x33, 0x407f047f, 0x48480054, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x1e0003, 0xc100c0, 0xc300c2}, + {0x34, 0x407f047f, 0x39390051, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x280003, 0xc100c0, 0xc300c2}, + {0x40, 0x407f0450, 0x24240030, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0xc0004, 0xc100c0, 0xc300c2}, + {0x41, 0x407f0450, 0x25250031, 0x0, 0xb2b17f7f, 0xdfeba5ff, 0xc0004, 0xc100c0, 0xc300c2}, + {0x42, 0x407f0450, 0x26260032, 0x0, 0xb2b17f7f, 0xdfebaaff, 0xc0004, 0xc100c0, 0xc300c2}, + {0x43, 0x407f047f, 0x3b3b0047, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x140004, 0xc100c0, 0xc300c2}, + {0x44, 0x407f007f, 0x48480060, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x400004, 0xc100c0, 0xc300c2}, + {0x45, 0x407f047f, 0x48480054, 0x0, 0xb2b17f7f, 0xdffeacff, 0x1e0004, 0xc100c0, 0xc300c2}, + {0x50, 0x4064047f, 0x3c3c0060, 0x0, 0xb2b17f7f, 0x5fd380ff, 0x570006, 0xc100c0, 0xc300c2}, + {0x51, 0x4064047f, 0x3c3c0078, 0x0, 0xb2b17f7f, 0xdff580ff, 0x40006, 0xc100c0, 0xc300c2}, + {0x52, 0x4053047f, 0x3c3c006c, 0x0, 0xb2b17f7f, 0xd170bebf, 0x840006, 0xc100c0, 0xc300c2}, + {0x53, 0x4064047f, 0x3030006c, 0x0, 0xb2b17f7f, 0xdff580ff, 0x40006, 0xc100c0, 0xc300c2}, + {0x54, 0x4053047f, 0x30300060, 0x0, 0xb2b17f7f, 0xd170bebf, 0x840006, 0xc100c0, 0xc300c2}, + {0x60, 0x357f0400, 0x3f3c0048, 0x0, 0xb2b17f7f, 0xdfea85ff, 0x160007, 0xc100c0, 0xc300c2}, + {0x61, 0x497f0400, 0x3b3a0044, 0x0, 0xb2b17f7f, 0x5fea82ff, 0x170007, 0xc100c0, 0xc300c2}, + {0x62, 0x407f047f, 0x4040465f, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x480007, 0xc100c0, 0xc300c2}, + {0x63, 0x407f0402, 0x2b240060, 0x0, 0xb2b10202, 0x5feb80ff, 0x60007, 0xc100c0, 0xc300c2}, + {0x64, 0x407f0402, 0x2d2c0074, 0x0, 0xb2b10202, 0x5feb80ff, 0xa0007, 0xc100c0, 0xc300c2}, + {0x65, 0x407f0402, 0x2e2e0060, 0x0, 0xb2b10202, 0x5ffd80ff, 0xb0007, 0xc100c0, 0xc300c2}, + {0x66, 0x407f0402, 0x2f2f0060, 0x0, 0xb2b10202, 0x5feb80ff, 0x1a0007, 0xc100c0, 0xc300c2}, + {0x67, 0x407f0402, 0x30300060, 0x0, 0xb2b10202, 0x5feb80ff, 0x1b0007, 0xc100c0, 0xc300c2}, + {0x68, 0x407f0402, 0x37310060, 0x0, 0xb2b10202, 0x5feb80ff, 0x280007, 0xc100c0, 0xc300c2}, + {0x69, 0x407f0402, 0x3938006c, 0x0, 0xb2b10202, 0x5feb80ff, 0x310007, 0xc100c0, 0xc300c2}, + {0x6a, 0x407f0402, 0x4341007f, 0x0, 0xb2b10202, 0x5feb80ff, 0x350007, 0xc100c0, 0xc300c2}, + {0x6c, 0x407f0402, 0x4b45007f, 0x0, 0xb2b10202, 0x5feb80ff, 0x3b0007, 0xc100c0, 0xc300c2}, + {0x6d, 0x407f0402, 0x4c4c0058, 0x0, 0xb2b10202, 0xdfeb80ff, 0x460007, 0xc100c0, 0xc300c2}, + {0x6e, 0x407f0402, 0x22220022, 0x0, 0xb2b10202, 0xdfe380ff, 0x110007, 0xc100c0, 0xc300c2}, + {0x6f, 0x407f0402, 0x23230023, 0x0, 0xb2b10202, 0xdfe380ff, 0x120007, 0xc100c0, 0xc300c2}, + {0x70, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x50008, 0xc100c0, 0xc300c2}, + {0x71, 0x407f047f, 0x39390054, 0x0, 0xb2b17f7f, 0xdfef80ff, 0x80008, 0xc100c0, 0xc300c2}, + {0x72, 0x407f047f, 0x3a3a0053, 0x0, 0xb2b17f7f, 0x5fe0b0ff, 0x90008, 0xc100c0, 0xc300c2}, + {0x80, 0x4064047f, 0x2727003c, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0xe0009, 0xc100c0, 0xc300c2}, + {0x81, 0x407f047f, 0x2626003e, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0xd0009, 0xc100c0, 0xc300c2}, + {0x82, 0x405a047f, 0x27270044, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x190009, 0xc100c0, 0xc300c2}, + {0x83, 0x407f047f, 0x28280040, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x100009, 0xc100c0, 0xc300c2}, + {0x84, 0x407f0465, 0x2525003d, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x220009, 0xc100c0, 0xc300c2}, + {0x85, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x230009, 0xc100c0, 0xc300c2}, + {0x86, 0x407f0400, 0x3d3d465a, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x860009, 0xc100c0, 0xc300c2}, + {0x87, 0x407f0400, 0x2424003c, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x220009, 0xc100c0, 0xc300c2}, + {0x88, 0x407f0400, 0x29290041, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x100009, 0xc100c0, 0xc300c2}, + {0x90, 0x407f047f, 0x3d3d6a48, 0x0, 0xb2b17f7f, 0xdfef80ff, 0x6000a, 0xc100c0, 0xc300c2}, + {0x91, 0x407f047f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x35000a, 0xc100c0, 0xc300c2}, + {0xa0, 0x407f0464, 0x3d3d0055, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x41000b, 0xc100c0, 0xc300c2}, + {0xa1, 0x407f0464, 0x3c3c0454, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x4d000b, 0xc100c0, 0xc300c2}, + {0xa2, 0x407f0464, 0x3f3f0457, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x4e000b, 0xc100c0, 0xc300c2}, + {0xb0, 0x407f0400, 0x26260026, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x1b000c, 0xc100c0, 0xc300c2}, + {0xb1, 0x407f047f, 0x27270027, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x1a000c, 0xc100c0, 0xc300c2}, + {0xb2, 0x407f0464, 0x28280040, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x29000c, 0xc100c0, 0xc300c2}, + {0xb3, 0x407f0464, 0x29290041, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x2a000c, 0xc100c0, 0xc300c2}, + {0xb4, 0x407f0464, 0x2a2a0042, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x2b000c, 0xc100c0, 0xc300c2}, + {0xb5, 0x407f0464, 0x2c2c0044, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x33000c, 0xc100c0, 0xc300c2}, + {0xc0, 0x407f047f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0xdff080ff, 0x3c000d, 0xc100c0, 0xc300c2}, + {0xd0, 0x407f0450, 0x24240024, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x11000e, 0xc100c0, 0xc300c2}, + {0xd1, 0x407f0450, 0x25250025, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x12000e, 0xc100c0, 0xc300c2}, + {0xd2, 0x407f0450, 0x3d3d4659, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x1f000e, 0xc100c0, 0xc300c2}, + {0xd3, 0x407f0450, 0x3e3e465a, 0x0, 0xb2b17f7f, 0xdfe6aaff, 0x1f000e, 0xc100c0, 0xc300c2}, + {0xe0, 0x407f047f, 0x3c3c003c, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x13000f, 0xc100c0, 0xc300c2}, + {0xe1, 0x407f047f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x60000f, 0xc100c0, 0xc300c2}, + {0xe2, 0x407f007f, 0x3c3c0048, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x50000f, 0xc100c0, 0xc300c2}, + {0xf0, 0x407f047f, 0x3b3b4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x620011, 0xc100c0, 0xc300c2}, + {0x100, 0x407f0464, 0x2d2d0045, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x2c0012, 0xc100c0, 0xc300c2}, + {0x101, 0x407f047f, 0x2e2e0046, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x2d0012, 0xc100c0, 0xc300c2}, + {0x102, 0x407f0432, 0x2424003c, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x490012, 0xc100c0, 0xc300c2}, + {0x103, 0x407f0432, 0x2525003d, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x4a0012, 0xc100c0, 0xc300c2}, + {0x104, 0x407f0432, 0x2f2f464c, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x4b0012, 0xc100c0, 0xc300c2}, + {0x105, 0x407f0432, 0x3030464d, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x4c0012, 0xc100c0, 0xc300c2}, + {0x110, 0x2c7f047f, 0x24240060, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x610014, 0xc100c0, 0xc300c2}, + {0x111, 0x2c41047f, 0x24240454, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x570014, 0xc100c0, 0xc300c2}, + {0x112, 0x407f047f, 0x2b2b0060, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x610014, 0xc100c0, 0xc300c2}, + {0x113, 0x4041047f, 0x2b2b0454, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x570014, 0xc100c0, 0xc300c2}, + {0x114, 0x547f047f, 0x30300060, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x610014, 0xc100c0, 0xc300c2}, + {0x115, 0x5441047f, 0x30300454, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x570014, 0xc100c0, 0xc300c2}, + {0x120, 0x407f007e, 0x7f006a53, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x10015, 0xc100c0, 0xc300c2}, + {0x130, 0x407f0464, 0x3d3d004e, 0x0, 0xb2b17f7f, 0xdfe080ff, 0xa0016, 0xc100c0, 0xc300c2}, + {0x131, 0x407f0464, 0x3e3e0045, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x150016, 0xc100c0, 0xc300c2}, + {0x132, 0x407f0464, 0x3f3f0046, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x150016, 0xc100c0, 0xc300c2}, + {0x133, 0x407f0464, 0x3f3f0050, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x2a0016, 0xc100c0, 0xc300c2}, + {0x134, 0x3c7f0450, 0x2121002d, 0x0, 0xb2b17f7f, 0xdfe0a4ff, 0xc0016, 0xc100c0, 0xc300c2}, + {0x135, 0x407f0450, 0x2222002d, 0x0, 0xb2b17f7f, 0xdfe0a0ff, 0xc0016, 0xc100c0, 0xc300c2}, + {0x136, 0x3c7f0450, 0x2323002d, 0x0, 0xb2b17f7f, 0xdfe0a7ff, 0xc0016, 0xc100c0, 0xc300c2}, + {0x137, 0x407f0450, 0x2424002d, 0x0, 0xb2b17f7f, 0xdfe0aaff, 0xc0016, 0xc100c0, 0xc300c2}, + {0x138, 0x407f0450, 0x2525002d, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x310016, 0xc100c0, 0xc300c2}, + {0x140, 0x407f047f, 0x3e3e0073, 0x0, 0xb2b17f7f, 0xceab80ff, 0x3a0018, 0xc100c0, 0xc300c2}, + {0x150, 0x2c7f047f, 0x24240030, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x1d0019, 0xc100c0, 0xc300c2}, + {0x151, 0x407f047f, 0x25250031, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x1c0019, 0xc100c0, 0xc300c2}, + {0x152, 0x547f047f, 0x24246e31, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x1d0019, 0xc100c0, 0xc300c2}, + {0x160, 0x407f0400, 0x7f000054, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x32001b, 0xc100c0, 0xc300c2}, + {0x170, 0x407f047f, 0x7f004659, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x63001c, 0xc100c0, 0xc300c2}, + {0x180, 0x407f0464, 0x30300048, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x34001d, 0xc100c0, 0xc300c2}, + {0x190, 0x407f047f, 0x3c3c0048, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x55001e, 0xc100c0, 0xc300c2}, + {0x1a0, 0x407f0452, 0x3c3c465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x53001f, 0xc100c0, 0xc300c2}, + {0x1b0, 0x407f047f, 0x3d3d4666, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x560020, 0xc100c0, 0xc300c2}, + {0x1c0, 0x407f047f, 0x2424003c, 0x0, 0xb2b17f7f, 0x5feb80ff, 0x3a0021, 0xc100c0, 0xc300c2}, + {0x1d0, 0x407f047f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x3b0022, 0xc100c0, 0xc300c2}, + {0x1d1, 0x407f047f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0xdff080ff, 0x3c0022, 0xc100c0, 0xc300c2}, + {0x1d2, 0x407f047f, 0x3e3e006e, 0x0, 0xb2b17f7f, 0xd16db477, 0x540022, 0xc100c0, 0xc300c2}, + {0x1d3, 0x406b047f, 0x3e3e0075, 0x0, 0xb2b17f7f, 0xdff580ff, 0x40022, 0xc100c0, 0xc300c2}, + {0x1d4, 0x4041047f, 0x3e3e005d, 0x0, 0xb2b17f7f, 0xdff380ff, 0x570022, 0xc100c0, 0xc300c2}, + {0x1e0, 0x406e0463, 0x2424003c, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x890023, 0xc100c0, 0xc300c2}, + {0x1e1, 0x406e0463, 0x2525003d, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x8a0023, 0xc100c0, 0xc300c2}, + {0x1f0, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x3d0024, 0xc100c0, 0xc300c2}, + {0x1f1, 0x407f047f, 0x3c3c6a50, 0x0, 0xb2b17f7f, 0xc02080cf, 0x10024, 0xc100c0, 0xc300c2}, + {0x1f2, 0x407f047f, 0x3c3c6a48, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x140024, 0xc100c0, 0xc300c2}, + {0x1f3, 0x4037047f, 0x3c3c6a64, 0x0, 0xb2b17f7f, 0xc9a080e3, 0x10024, 0xc100c0, 0xc300c2}, + {0x200, 0x407f0446, 0x24244636, 0x0, 0xb2b17f7f, 0xdfebb4ff, 0x490025, 0xc100c0, 0xc300c2}, + {0x201, 0x407f0446, 0x25254636, 0x0, 0xb2b17f7f, 0xdfebaaff, 0x4a0025, 0xc100c0, 0xc300c2}, + {0x202, 0x407f0464, 0x3c3c0448, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x420025, 0xc100c0, 0xc300c2}, + {0x203, 0x407f0464, 0x3d3d0449, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x430025, 0xc100c0, 0xc300c2}, + {0x204, 0x407f0464, 0x3e3e044a, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x440025, 0xc100c0, 0xc300c2}, + {0x205, 0x407f0450, 0x4040044c, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x450025, 0xc100c0, 0xc300c2}, + {0x206, 0x407f0450, 0x4141044c, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x450025, 0xc100c0, 0xc300c2}, + {0x207, 0x407f0464, 0x4242044e, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x460025, 0xc100c0, 0xc300c2}, + {0x208, 0x407f0464, 0x4343044f, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x470025, 0xc100c0, 0xc300c2}, + {0x210, 0x407f047f, 0x3c3c0060, 0x0, 0xb2b17f7f, 0x5fe080ff, 0x4f0026, 0xc100c0, 0xc300c2}, + {0x220, 0x407f047f, 0x41410459, 0x0, 0xb2b17f7f, 0x5feb80ff, 0x500027, 0xc100c0, 0xc300c2}, + {0x230, 0x407f047f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0x5fe080ff, 0x510028, 0xc100c0, 0xc300c2}, + {0x231, 0x407f047f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x520028, 0xc100c0, 0xc300c2}, + {0x240, 0x407f047f, 0x3d3d0049, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x5f002c, 0xc100c0, 0xc300c2}, + {0x241, 0x407f0419, 0x3e3e465b, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x87002c, 0xc100c0, 0xc300c2}, + {0x242, 0x407f0419, 0x3f3f465c, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x88002c, 0xc100c0, 0xc300c2}, + {0x250, 0x407f0440, 0x3d3d3c56, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x850031, 0xc100c0, 0xc300c2}, + {0x260, 0x407f047f, 0x7f000048, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x64003a, 0xc100c0, 0xc300c2}, + {0x270, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x65003b, 0xc100c0, 0xc300c2}, + {0x271, 0x407f047f, 0x3e3e465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x66003b, 0xc100c0, 0xc300c2}, + {0x272, 0x407f047f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x67003b, 0xc100c0, 0xc300c2}, + {0x273, 0x407f047f, 0x3f3f005b, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x68003b, 0xc100c0, 0xc300c2}, + {0x280, 0x407f047f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x69003c, 0xc100c0, 0xc300c2}, + {0x281, 0x407f047f, 0x3f3f465c, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x6a003c, 0xc100c0, 0xc300c2}, + {0x290, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x6b003d, 0xc100c0, 0xc300c2}, + {0x291, 0x407f047f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x6c003d, 0xc100c0, 0xc300c2}, + {0x292, 0x407f047f, 0x37374653, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x6d003d, 0xc100c0, 0xc300c2}, + {0x293, 0x407f047f, 0x38384655, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x6e003d, 0xc100c0, 0xc300c2}, + {0x294, 0x407f047f, 0x39394656, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x6f003d, 0xc100c0, 0xc300c2}, + {0x295, 0x407f047f, 0x3b3b4658, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x70003d, 0xc100c0, 0xc300c2}, + {0x296, 0x407f047f, 0x3a3a4657, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x71003d, 0xc100c0, 0xc300c2}, + {0x297, 0x407f047f, 0x4040465d, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x72003d, 0xc100c0, 0xc300c2}, + {0x298, 0x407f047f, 0x4141004d, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x73003d, 0xc100c0, 0xc300c2}, + {0x2a0, 0x407f047f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x74003e, 0xc100c0, 0xc300c2}, + {0x2a1, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x75003e, 0xc100c0, 0xc300c2}, + {0x2a2, 0x407f047f, 0x3e3e465b, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x76003e, 0xc100c0, 0xc300c2}, + {0x2a3, 0x407f047f, 0x3f3f465c, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x77003e, 0xc100c0, 0xc300c2}, + {0x2b0, 0x407f047f, 0x25254642, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x78003f, 0xc100c0, 0xc300c2}, + {0x2b1, 0x407f047f, 0x26264643, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x79003f, 0xc100c0, 0xc300c2}, + {0x2b2, 0x407f047f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x7a003f, 0xc100c0, 0xc300c2}, + {0x2b3, 0x407f047f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x7b003f, 0xc100c0, 0xc300c2}, + {0x2c0, 0x407f047f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x7c0040, 0xc100c0, 0xc300c2}, + {0x2c1, 0x407f047f, 0x3e3e465b, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x7d0040, 0xc100c0, 0xc300c2}, + {0x2c2, 0x407f047f, 0x27274644, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x7e0040, 0xc100c0, 0xc300c2}, + {0x2c3, 0x407f047f, 0x3f3f465c, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x7f0040, 0xc100c0, 0xc300c2}, + {0x2d0, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x800041, 0xc100c0, 0xc300c2}, + {0x2d1, 0x407f047f, 0x4040465d, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x810041, 0xc100c0, 0xc300c2}, + {0x2e0, 0x407f047f, 0x7f004667, 0x0, 0xb2b17f7f, 0xdfefaaff, 0x820042, 0xc100c0, 0xc300c2}, + {0x2f0, 0x7f0404, 0x53000048, 0x0, 0xb2b10000, 0xdfeb80ff, 0x40046, 0xc100c0, 0xc300c2}, + {0x2f1, 0x7f7f0404, 0x7f540048, 0x0, 0xb2b10000, 0xdfeb80ff, 0x40046, 0xc100c0, 0xc300c2}, + {0x300, 0x40730464, 0x7f000460, 0x0, 0xb2b10000, 0xdff080ff, 0x580047, 0xc100c0, 0xc300c2}, + {0x310, 0x407f0464, 0x3000044c, 0x0, 0xb2b10000, 0xdff080ff, 0x590048, 0xc100c0, 0xc300c2}, + {0x311, 0x407f0464, 0x40310464, 0x0, 0xb2b10000, 0xdfeb80ff, 0x5a0048, 0xc100c0, 0xc300c2}, + {0x312, 0x407f0464, 0x4e41046b, 0x0, 0xb2b10000, 0xdfeb80ff, 0x5b0048, 0xc100c0, 0xc300c2}, + {0x313, 0x407f0464, 0x7f4f046b, 0x0, 0xb2b10000, 0xdfeb80ff, 0x5c0048, 0xc100c0, 0xc300c2}, + {0x320, 0x407f0464, 0x7f000473, 0x0, 0xb2b10000, 0xdfee80ff, 0x5d0049, 0xc100c0, 0xc300c2}, + {0x330, 0x407f0454, 0x3f3f004b, 0x0, 0xb2b10000, 0xdfeb80ff, 0x2f004d, 0xc100c0, 0xc300c2}, + {0x331, 0x407f005a, 0x3b00003c, 0x0, 0xb2b10000, 0xdfeb80ff, 0x30004d, 0xc100c0, 0xc300c2}, + {0x332, 0x406e0055, 0x3c3c0048, 0x0, 0xb2b10000, 0xdfeb80ff, 0x83004d, 0xc100c0, 0xc300c2}, + {0x340, 0x407f0464, 0x47005a49, 0x0, 0xb2b10000, 0xdff0a0ff, 0x36004e, 0xc100c0, 0xc300c2}, + {0x341, 0x407f0464, 0x7f48005d, 0x0, 0xb2b10000, 0xdfeb80ff, 0x37004e, 0xc100c0, 0xc300c2}, + {0x350, 0x407f0455, 0x7f250048, 0x0, 0xb2b10000, 0xdfeb80ff, 0x38004f, 0xc100c0, 0xc300c2}, + {0x351, 0x6464044f, 0x24230048, 0x0, 0xb2b10000, 0xdfeb80ff, 0x38004f, 0xc100c0, 0xc300c2}, + {0x352, 0x1b640405, 0x2200003c, 0x0, 0xb2b10000, 0xdfeb80ff, 0x38004f, 0xc100c0, 0xc300c2}, + {0x360, 0x6e047f, 0x7f005a48, 0x0, 0xb2b10000, 0xdfe780ff, 0x390050, 0xc100c0, 0xc300c2}, + {0x361, 0x7f6e047f, 0x7f006148, 0x0, 0xb2b10000, 0xdfe780ff, 0x390050, 0xc100c0, 0xc300c2}, + {0x370, 0x407f047f, 0x7f00465a, 0x0, 0xb2b10000, 0x5febb4ff, 0x2e0051, 0xc100c0, 0xc300c2}, + {0x380, 0x407f0464, 0x7f000454, 0x0, 0xb2b10000, 0xdfeb80ff, 0x5e0054, 0xc100c0, 0xc300c2}}}; + +const std::vector> AO_F2ENDER_VH = {{ + {0x0, 0x40150464, 0x3c3c6a66, 0x0, 0xb2b17f7f, 0xdaeb80ff, 0x620002, 0xc100c0, 0xc300c2}, + {0x1, 0x3f5f0400, 0x3b006a4e, 0x0, 0xb2b17f7f, 0x5fef80ff, 0x620002, 0xc100c0, 0xc300c2}, + {0x2, 0x540400, 0x3b006a54, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x620002, 0xc100c0, 0xc300c2}, + {0x3, 0x7f540400, 0x3b006a56, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x620002, 0xc100c0, 0xc300c2}, + {0x10, 0x407f047f, 0x3b3b0047, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x100003, 0xc100c0, 0xc300c2}, + {0x11, 0x407f047f, 0x3a3a0052, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x60003, 0xc100c0, 0xc300c2}, + {0x12, 0x407f047f, 0x43430048, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x70003, 0xc100c0, 0xc300c2}, + {0x13, 0x407f047f, 0x48480054, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x160003, 0xc100c0, 0xc300c2}, + {0x14, 0x407f047f, 0x39390051, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x1a0003, 0xc100c0, 0xc300c2}, + {0x20, 0x407f0450, 0x24240030, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x80004, 0xc100c0, 0xc300c2}, + {0x21, 0x407f0450, 0x25250031, 0x0, 0xb2b17f7f, 0xdfeba5ff, 0x80004, 0xc100c0, 0xc300c2}, + {0x22, 0x407f0450, 0x26260032, 0x0, 0xb2b17f7f, 0xdfebaaff, 0x80004, 0xc100c0, 0xc300c2}, + {0x23, 0x407f047f, 0x3b3b0047, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0xf0004, 0xc100c0, 0xc300c2}, + {0x24, 0x407f007f, 0x48480060, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x260004, 0xc100c0, 0xc300c2}, + {0x25, 0x407f047f, 0x48480054, 0x0, 0xb2b17f7f, 0xdffeacff, 0x160004, 0xc100c0, 0xc300c2}, + {0x30, 0x4064047f, 0x3c3c0060, 0x0, 0xb2b17f7f, 0xdff380ff, 0x630006, 0xc100c0, 0xc300c2}, + {0x31, 0x4064047f, 0x3c3c0078, 0x0, 0xb2b17f7f, 0xdff080ff, 0x10006, 0xc100c0, 0xc300c2}, + {0x32, 0x4053047f, 0x3c3c006c, 0x0, 0xb2b17f7f, 0xd170bebf, 0x640006, 0xc100c0, 0xc300c2}, + {0x40, 0x357f0400, 0x3f3c0048, 0x0, 0xb2b17f7f, 0xdfea85ff, 0x110007, 0xc100c0, 0xc300c2}, + {0x41, 0x497f0400, 0x3b3a0044, 0x0, 0xb2b17f7f, 0x5fea82ff, 0x120007, 0xc100c0, 0xc300c2}, + {0x42, 0x407f047f, 0x4040465f, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x2e0007, 0xc100c0, 0xc300c2}, + {0x43, 0x407f0402, 0x2b240060, 0x0, 0xb2b10202, 0x5feb80ff, 0x30007, 0xc100c0, 0xc300c2}, + {0x44, 0x407f0402, 0x2d2c0074, 0x0, 0xb2b10202, 0x5feb80ff, 0x60007, 0xc100c0, 0xc300c2}, + {0x45, 0x407f0402, 0x2e2e0060, 0x0, 0xb2b10202, 0x5ffd80ff, 0x70007, 0xc100c0, 0xc300c2}, + {0x46, 0x407f0402, 0x2f2f0060, 0x0, 0xb2b10202, 0x5feb80ff, 0x140007, 0xc100c0, 0xc300c2}, + {0x47, 0x407f0402, 0x30300062, 0x0, 0xb2b10202, 0xdfeb80ff, 0x2a0007, 0xc100c0, 0xc300c2}, + {0x48, 0x407f0402, 0x37310060, 0x0, 0xb2b10202, 0x5feb80ff, 0x1a0007, 0xc100c0, 0xc300c2}, + {0x49, 0x407f0402, 0x3938006c, 0x0, 0xb2b10202, 0x5feb80ff, 0x1e0007, 0xc100c0, 0xc300c2}, + {0x4a, 0x407f0402, 0x4341007f, 0x0, 0xb2b10202, 0x5feb80ff, 0x1f0007, 0xc100c0, 0xc300c2}, + {0x4c, 0x407f0402, 0x4b45007f, 0x0, 0xb2b10202, 0x5feb80ff, 0x250007, 0xc100c0, 0xc300c2}, + {0x4d, 0x407f0402, 0x4c4c0058, 0x0, 0xb2b10202, 0xdfeb80ff, 0x2c0007, 0xc100c0, 0xc300c2}, + {0x4e, 0x407f0402, 0x22220022, 0x0, 0xb2b10202, 0xdfe380ff, 0xc0007, 0xc100c0, 0xc300c2}, + {0x4f, 0x407f0402, 0x23230023, 0x0, 0xb2b10202, 0xdfe380ff, 0xd0007, 0xc100c0, 0xc300c2}, + {0x50, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x20008, 0xc100c0, 0xc300c2}, + {0x51, 0x407f047f, 0x39390054, 0x0, 0xb2b17f7f, 0xdfef80ff, 0x40008, 0xc100c0, 0xc300c2}, + {0x52, 0x407f047f, 0x3a3a0053, 0x0, 0xb2b17f7f, 0x5fe0b0ff, 0x50008, 0xc100c0, 0xc300c2}, + {0x60, 0x4064047f, 0x2727003c, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0xa0009, 0xc100c0, 0xc300c2}, + {0x61, 0x407f047f, 0x2626003e, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x90009, 0xc100c0, 0xc300c2}, + {0x62, 0x405a047f, 0x27270044, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x130009, 0xc100c0, 0xc300c2}, + {0x63, 0x407f047f, 0x28280040, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0xb0009, 0xc100c0, 0xc300c2}, + {0x64, 0x407f0466, 0x2525003d, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x180009, 0xc100c0, 0xc300c2}, + {0x65, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x190009, 0xc100c0, 0xc300c2}, + {0x66, 0x407f0400, 0x3d3d465a, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x610009, 0xc100c0, 0xc300c2}, + {0x67, 0x407f0400, 0x2424003c, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x180009, 0xc100c0, 0xc300c2}, + {0x68, 0x407f0400, 0x29290041, 0x0, 0xb2b17f7f, 0xdfe380ff, 0xb0009, 0xc100c0, 0xc300c2}, + {0x70, 0x407f047f, 0x3d3d6a48, 0x0, 0xb2b17f7f, 0xdfef80ff, 0x3000a, 0xc100c0, 0xc300c2}, + {0x71, 0x407f047f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x1f000a, 0xc100c0, 0xc300c2}, + {0x80, 0x407f0464, 0x3d3d0055, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x27000b, 0xc100c0, 0xc300c2}, + {0x81, 0x407f0464, 0x3c3c0454, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x31000b, 0xc100c0, 0xc300c2}, + {0x82, 0x407f0464, 0x3f3f0457, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x32000b, 0xc100c0, 0xc300c2}, + {0x90, 0x407f047f, 0x3c3c003c, 0x0, 0xb2b17f7f, 0xdfe080ff, 0xe000f, 0xc100c0, 0xc300c2}, + {0x91, 0x407f047f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x3a000f, 0xc100c0, 0xc300c2}, + {0x92, 0x407f007f, 0x3c3c0048, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x34000f, 0xc100c0, 0xc300c2}, + {0xa0, 0x407f047f, 0x3b3b4659, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x770011, 0xc100c0, 0xc300c2}, + {0xb0, 0x407f007f, 0x30300048, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x380013, 0xc100c0, 0xc300c2}, + {0xc0, 0x407f007e, 0x7f006a53, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x620015, 0xc100c0, 0xc300c2}, + {0xd0, 0x407f047f, 0x7f004659, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x78001c, 0xc100c0, 0xc300c2}, + {0xe0, 0x407f0452, 0x3c3c465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x37001f, 0xc100c0, 0xc300c2}, + {0xf0, 0x407f047f, 0x3d3d4666, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x390020, 0xc100c0, 0xc300c2}, + {0x100, 0x407f047f, 0x2424003c, 0x0, 0xb2b17f7f, 0x5feb80ff, 0x240021, 0xc100c0, 0xc300c2}, + {0x110, 0x407f0446, 0x24244636, 0x0, 0xb2b17f7f, 0xdfebb4ff, 0x2f0025, 0xc100c0, 0xc300c2}, + {0x111, 0x407f0446, 0x25254636, 0x0, 0xb2b17f7f, 0xdfebaaff, 0x300025, 0xc100c0, 0xc300c2}, + {0x112, 0x407f0464, 0x3c3c0448, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x280025, 0xc100c0, 0xc300c2}, + {0x113, 0x407f0464, 0x3d3d0449, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x290025, 0xc100c0, 0xc300c2}, + {0x114, 0x407f0464, 0x3e3e044a, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x2a0025, 0xc100c0, 0xc300c2}, + {0x115, 0x407f0451, 0x4040044c, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x2b0025, 0xc100c0, 0xc300c2}, + {0x116, 0x407f0451, 0x4141044c, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x2b0025, 0xc100c0, 0xc300c2}, + {0x117, 0x407f0464, 0x4242044e, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x2c0025, 0xc100c0, 0xc300c2}, + {0x118, 0x407f0464, 0x4343044f, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x2d0025, 0xc100c0, 0xc300c2}, + {0x119, 0x407f0463, 0x30300455, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x2a0025, 0xc100c0, 0xc300c2}, + {0x11a, 0x407f0463, 0x31310056, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x280025, 0xc100c0, 0xc300c2}, + {0x120, 0x407f047f, 0x3c3c0060, 0x0, 0xb2b17f7f, 0x5fe080ff, 0x330026, 0xc100c0, 0xc300c2}, + {0x130, 0x407f047f, 0x41410459, 0x0, 0xb2b17f7f, 0x5feb80ff, 0x340027, 0xc100c0, 0xc300c2}, + {0x140, 0x407f047f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0x5fe080ff, 0x350028, 0xc100c0, 0xc300c2}, + {0x141, 0x407f047f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x360028, 0xc100c0, 0xc300c2}, + {0x150, 0x407f0440, 0x3d3d3c56, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x600031, 0xc100c0, 0xc300c2}, + {0x160, 0x407f047f, 0x7f000048, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x3b003a, 0xc100c0, 0xc300c2}, + {0x170, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x3c003b, 0xc100c0, 0xc300c2}, + {0x171, 0x407f047f, 0x3e3e465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x3d003b, 0xc100c0, 0xc300c2}, + {0x172, 0x407f047f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x3e003b, 0xc100c0, 0xc300c2}, + {0x173, 0x407f047f, 0x3f3f005b, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x3f003b, 0xc100c0, 0xc300c2}, + {0x180, 0x407f047f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x40003c, 0xc100c0, 0xc300c2}, + {0x181, 0x407f047f, 0x3f3f465c, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x41003c, 0xc100c0, 0xc300c2}, + {0x190, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x42003d, 0xc100c0, 0xc300c2}, + {0x191, 0x407f047f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x43003d, 0xc100c0, 0xc300c2}, + {0x192, 0x407f047f, 0x37374653, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x44003d, 0xc100c0, 0xc300c2}, + {0x193, 0x407f047f, 0x38384655, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x45003d, 0xc100c0, 0xc300c2}, + {0x194, 0x407f047f, 0x39394656, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x46003d, 0xc100c0, 0xc300c2}, + {0x195, 0x407f047f, 0x3b3b4658, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x47003d, 0xc100c0, 0xc300c2}, + {0x196, 0x407f047f, 0x3a3a4657, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x48003d, 0xc100c0, 0xc300c2}, + {0x197, 0x407f047f, 0x4040465d, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x49003d, 0xc100c0, 0xc300c2}, + {0x198, 0x407f047f, 0x4141004d, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x4a003d, 0xc100c0, 0xc300c2}, + {0x1a0, 0x407f047f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x4b003e, 0xc100c0, 0xc300c2}, + {0x1a1, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x4c003e, 0xc100c0, 0xc300c2}, + {0x1a2, 0x407f047f, 0x3e3e465b, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x4d003e, 0xc100c0, 0xc300c2}, + {0x1a3, 0x407f047f, 0x3f3f465c, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x4e003e, 0xc100c0, 0xc300c2}, + {0x1b0, 0x407f047f, 0x25254642, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x4f003f, 0xc100c0, 0xc300c2}, + {0x1b1, 0x407f047f, 0x26264643, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x50003f, 0xc100c0, 0xc300c2}, + {0x1b2, 0x407f047f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x51003f, 0xc100c0, 0xc300c2}, + {0x1b3, 0x407f047f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x52003f, 0xc100c0, 0xc300c2}, + {0x1c0, 0x407f047f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x530040, 0xc100c0, 0xc300c2}, + {0x1c1, 0x407f047f, 0x3e3e465b, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x540040, 0xc100c0, 0xc300c2}, + {0x1c2, 0x407f047f, 0x27274644, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x550040, 0xc100c0, 0xc300c2}, + {0x1c3, 0x407f047f, 0x3f3f465c, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x560040, 0xc100c0, 0xc300c2}, + {0x1d0, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x570041, 0xc100c0, 0xc300c2}, + {0x1d1, 0x407f047f, 0x4040465d, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x580041, 0xc100c0, 0xc300c2}, + {0x1e0, 0x407f047f, 0x7f004667, 0x0, 0xb2b17f7f, 0xdfefaaff, 0x590042, 0xc100c0, 0xc300c2}, + {0x1f0, 0x407f0463, 0x7f000048, 0x0, 0xb2b10000, 0xdfe680ff, 0x5c0048, 0xc100c0, 0xc300c2}, + {0x200, 0x407f0401, 0x7f00003c, 0x0, 0xb2b10000, 0xdfeb80ff, 0x760049, 0xc100c0, 0xc300c2}, + {0x210, 0x407f005a, 0x24240024, 0x0, 0xb2b10000, 0xdfe680ff, 0x5b004a, 0xc100c0, 0xc300c2}, + {0x211, 0x407f045b, 0x30300048, 0x0, 0xb2b10000, 0xdfe680ff, 0x5d004a, 0xc100c0, 0xc300c2}, + {0x212, 0x227f0054, 0x27270027, 0x0, 0xb2b10000, 0xdfeb80ff, 0x5e004a, 0xc100c0, 0xc300c2}, + {0x213, 0x407f0000, 0x25250025, 0x0, 0xb2b10000, 0xdfe680ff, 0x5f004a, 0xc100c0, 0xc300c2}, + {0x214, 0x5e7f0054, 0x28280028, 0x0, 0xb2b10000, 0xdfeb80ff, 0x5e004a, 0xc100c0, 0xc300c2}, + {0x215, 0x407f0453, 0x603c003c, 0x0, 0xb2b10000, 0xdfeb80ff, 0x65004a, 0xc100c0, 0xc300c2}, + {0x220, 0x7f0000, 0x24240024, 0x0, 0xb2b10000, 0xdfeb80ff, 0x66004b, 0xc100c0, 0xc300c2}, + {0x221, 0x7f7f0000, 0x25250025, 0x0, 0xb2b10000, 0xdfeb80ff, 0x67004b, 0xc100c0, 0xc300c2}, + {0x222, 0x7f0000, 0x26260026, 0x0, 0xb2b10000, 0xdfeb80ff, 0x68004b, 0xc100c0, 0xc300c2}, + {0x223, 0x7f7f0000, 0x27270027, 0x0, 0xb2b10000, 0xdfeb80ff, 0x69004b, 0xc100c0, 0xc300c2}, + {0x224, 0x7f0000, 0x28280028, 0x0, 0xb2b10000, 0xdfeb80ff, 0x6a004b, 0xc100c0, 0xc300c2}, + {0x225, 0x7f7f0000, 0x29290029, 0x0, 0xb2b10000, 0xdfeb80ff, 0x6b004b, 0xc100c0, 0xc300c2}, + {0x226, 0x7f0000, 0x2a2a002a, 0x0, 0xb2b10000, 0xdfeb80ff, 0x6c004b, 0xc100c0, 0xc300c2}, + {0x227, 0x7f7f0000, 0x2b2b002b, 0x0, 0xb2b10000, 0xdfeb80ff, 0x6d004b, 0xc100c0, 0xc300c2}, + {0x228, 0x7f0000, 0x2c2c002c, 0x0, 0xb2b10000, 0xdfeb80ff, 0x6e004b, 0xc100c0, 0xc300c2}, + {0x229, 0x7f7f0000, 0x2d2d002d, 0x0, 0xb2b10000, 0xdfeb80ff, 0x6f004b, 0xc100c0, 0xc300c2}, + {0x22a, 0x7f0000, 0x2e2e002e, 0x0, 0xb2b10000, 0xdfeb80ff, 0x70004b, 0xc100c0, 0xc300c2}, + {0x22b, 0x7f7f0000, 0x2f2f002f, 0x0, 0xb2b10000, 0xdfeb80ff, 0x71004b, 0xc100c0, 0xc300c2}, + {0x22c, 0x7f0000, 0x30300030, 0x0, 0xb2b10000, 0xdfeb80ff, 0x72004b, 0xc100c0, 0xc300c2}, + {0x22d, 0x7f7f0000, 0x31310031, 0x0, 0xb2b10000, 0xdfeb80ff, 0x73004b, 0xc100c0, 0xc300c2}, + {0x22e, 0x7f0000, 0x32320032, 0x0, 0xb2b10000, 0xdfeb80ff, 0x74004b, 0xc100c0, 0xc300c2}, + {0x22f, 0x7f7f0000, 0x33330033, 0x0, 0xb2b10000, 0xdfeb80ff, 0x75004b, 0xc100c0, 0xc300c2}, + {0x230, 0x7f0000, 0x2424002b, 0x0, 0xb2b10000, 0xdfeb80ff, 0x66004c, 0xc100c0, 0xc300c2}, + {0x231, 0x7f7f0000, 0x2525002c, 0x0, 0xb2b10000, 0xdfeb80ff, 0x67004c, 0xc100c0, 0xc300c2}, + {0x232, 0x7f0000, 0x2626002d, 0x0, 0xb2b10000, 0xdfeb80ff, 0x68004c, 0xc100c0, 0xc300c2}, + {0x233, 0x7f7f0000, 0x2727002e, 0x0, 0xb2b10000, 0xdfeb80ff, 0x69004c, 0xc100c0, 0xc300c2}, + {0x234, 0x7f0000, 0x2828002f, 0x0, 0xb2b10000, 0xdfeb80ff, 0x6a004c, 0xc100c0, 0xc300c2}, + {0x235, 0x7f7f0000, 0x29290030, 0x0, 0xb2b10000, 0xdfeb80ff, 0x6b004c, 0xc100c0, 0xc300c2}, + {0x236, 0x7f0000, 0x2a2a0031, 0x0, 0xb2b10000, 0xdfeb80ff, 0x6c004c, 0xc100c0, 0xc300c2}, + {0x237, 0x7f7f0000, 0x2b2b0032, 0x0, 0xb2b10000, 0xdfeb80ff, 0x6d004c, 0xc100c0, 0xc300c2}, + {0x238, 0x7f0000, 0x2c2c0033, 0x0, 0xb2b10000, 0xdfeb80ff, 0x6e004c, 0xc100c0, 0xc300c2}, + {0x239, 0x7f7f0000, 0x2d2d0034, 0x0, 0xb2b10000, 0xdfeb80ff, 0x6f004c, 0xc100c0, 0xc300c2}, + {0x23a, 0x7f0000, 0x2e2e0035, 0x0, 0xb2b10000, 0xdfeb80ff, 0x70004c, 0xc100c0, 0xc300c2}, + {0x23b, 0x7f7f0000, 0x2f2f0036, 0x0, 0xb2b10000, 0xdfeb80ff, 0x71004c, 0xc100c0, 0xc300c2}, + {0x23c, 0x7f0000, 0x30300037, 0x0, 0xb2b10000, 0xdfeb80ff, 0x72004c, 0xc100c0, 0xc300c2}, + {0x23d, 0x7f7f0000, 0x31310038, 0x0, 0xb2b10000, 0xdfeb80ff, 0x73004c, 0xc100c0, 0xc300c2}, + {0x23e, 0x7f0000, 0x32320039, 0x0, 0xb2b10000, 0xdfeb80ff, 0x74004c, 0xc100c0, 0xc300c2}, + {0x23f, 0x7f7f0000, 0x3333003a, 0x0, 0xb2b10000, 0xdfeb80ff, 0x75004c, 0xc100c0, 0xc300c2}, + {0x240, 0x407f0454, 0x3f3f004b, 0x0, 0xb2b10000, 0xdfeb80ff, 0x1c004d, 0xc100c0, 0xc300c2}, + {0x241, 0x407f005a, 0x3b00003c, 0x0, 0xb2b10000, 0xdfeb80ff, 0x1d004d, 0xc100c0, 0xc300c2}, + {0x242, 0x406e0055, 0x3c3c0048, 0x0, 0xb2b10000, 0xdfeb80ff, 0x5a004d, 0xc100c0, 0xc300c2}, + {0x250, 0x407f0478, 0x47005a49, 0x0, 0xb2b10000, 0xdff0a0ff, 0x20004e, 0xc100c0, 0xc300c2}, + {0x251, 0x407f0478, 0x7f48005d, 0x0, 0xb2b10000, 0xdfeb80ff, 0x21004e, 0xc100c0, 0xc300c2}, + {0x260, 0x407f0455, 0x7f250048, 0x0, 0xb2b10000, 0xdff080ff, 0x22004f, 0xc100c0, 0xc300c2}, + {0x261, 0x64640452, 0x24230048, 0x0, 0xb2b10000, 0xdff080ff, 0x22004f, 0xc100c0, 0xc300c2}, + {0x262, 0x1b640405, 0x2200003c, 0x0, 0xb2b10000, 0xdff080ff, 0x22004f, 0xc100c0, 0xc300c2}, + {0x270, 0x6e047f, 0x7f005a48, 0x0, 0xb2b10000, 0xdfe780ff, 0x230050, 0xc100c0, 0xc300c2}, + {0x271, 0x7f6e047f, 0x7f006148, 0x0, 0xb2b10000, 0xdfe780ff, 0x230050, 0xc100c0, 0xc300c2}, + {0x280, 0x407f047f, 0x7f00465a, 0x0, 0xb2b10000, 0x5febb4ff, 0x1b0051, 0xc100c0, 0xc300c2}}}; + +const std::vector> AO_MLSNDFX_VH = {{ + {0x0, 0x7f047f, 0x3e006a53, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10000, 0xc100c0, 0xc300c2}, + {0x1, 0x1b380400, 0x3e006a5f, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10000, 0xc100c0, 0xc300c2}, + {0x2, 0x64500400, 0x3e006a66, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10000, 0xc100c0, 0xc300c2}, + {0x3, 0x40280400, 0x3e006a72, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10000, 0xc100c0, 0xc300c2}, + {0x4, 0x7f7f047f, 0x3e006a54, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10000, 0xc100c0, 0xc300c2}, + {0x5, 0x407f045b, 0x42420050, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x100000, 0xc100c0, 0xc300c2}, + {0x6, 0x407f045b, 0x4343004f, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x60000, 0xc100c0, 0xc300c2}, + {0x7, 0x407f0464, 0x3e006a4d, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x10000, 0xc100c0, 0xc300c2}, + {0x8, 0x407f047f, 0x4848003c, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x1a0000, 0xc100c0, 0xc300c2}, + {0x9, 0x407f047f, 0x49490049, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x1b0000, 0xc100c0, 0xc300c2}, + {0x10, 0x407f047f, 0x3a3a0046, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x160001, 0xc100c0, 0xc300c2}, + {0x11, 0x407f047f, 0x3b3b0045, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x30001, 0xc100c0, 0xc300c2}, + {0x12, 0x407f047f, 0x48480060, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x2c0001, 0xc100c0, 0xc300c2}, + {0x13, 0x407f047f, 0x48480060, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x20001, 0xc100c0, 0xc300c2}, + {0x14, 0x7f047f, 0x48480048, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x20001, 0xc100c0, 0xc300c2}, + {0x15, 0x7f640451, 0x4848006c, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x20001, 0xc100c0, 0xc300c2}, + {0x16, 0x640451, 0x4848326b, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x20001, 0xc100c0, 0xc300c2}, + {0x17, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x2a0001, 0xc100c0, 0xc300c2}, + {0x18, 0x407f047f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x2b0001, 0xc100c0, 0xc300c2}, + {0x19, 0x407f0400, 0x39390045, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x490001, 0xc100c0, 0xc300c2}, + {0x20, 0x40150464, 0x3c3c6a66, 0x0, 0xb2b17f7f, 0xdaeb80ff, 0x10002, 0xc100c0, 0xc300c2}, + {0x21, 0x3f5f0400, 0x3b006a4e, 0x0, 0xb2b17f7f, 0x5fef80ff, 0x10002, 0xc100c0, 0xc300c2}, + {0x22, 0x540400, 0x3b006a54, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10002, 0xc100c0, 0xc300c2}, + {0x23, 0x7f540400, 0x3b006a56, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10002, 0xc100c0, 0xc300c2}, + {0x24, 0x7f007f, 0x3c3c003c, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x1f0002, 0xc100c0, 0xc300c2}, + {0x25, 0x7f7f007f, 0x3c3c003c, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x200002, 0xc100c0, 0xc300c2}, + {0x26, 0x4046047f, 0x3c3c003c, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x210002, 0xc100c0, 0xc300c2}, + {0x27, 0x407f047f, 0x40400040, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x220002, 0xc100c0, 0xc300c2}, + {0x30, 0x407f047f, 0x3b3b0047, 0x0, 0xb2b17f7f, 0x5fde80ff, 0x130003, 0xc100c0, 0xc300c2}, + {0x31, 0x407f047f, 0x3a3a0052, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x90003, 0xc100c0, 0xc300c2}, + {0x32, 0x407f047f, 0x43430048, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0xa0003, 0xc100c0, 0xc300c2}, + {0x33, 0x407f047f, 0x48480054, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x190003, 0xc100c0, 0xc300c2}, + {0x34, 0x407f047f, 0x39390051, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x230003, 0xc100c0, 0xc300c2}, + {0x40, 0x407f0450, 0x24240030, 0x0, 0xb2b17f7f, 0xdffe80ff, 0xb0004, 0xc100c0, 0xc300c2}, + {0x41, 0x407f0450, 0x25250031, 0x0, 0xb2b17f7f, 0xdffea5ff, 0xb0004, 0xc100c0, 0xc300c2}, + {0x42, 0x407f0450, 0x26260032, 0x0, 0xb2b17f7f, 0xdffeaaff, 0xb0004, 0xc100c0, 0xc300c2}, + {0x43, 0x407f047f, 0x3b3b0047, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x120004, 0xc100c0, 0xc300c2}, + {0x44, 0x407f007f, 0x48480060, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x2c0004, 0xc100c0, 0xc300c2}, + {0x45, 0x407f047f, 0x48480054, 0x0, 0xb2b17f7f, 0xdffeacff, 0x190004, 0xc100c0, 0xc300c2}, + {0x50, 0x407f0450, 0x25250030, 0x0, 0xb2b17f7f, 0x5fe580ff, 0x260005, 0xc100c0, 0xc300c2}, + {0x51, 0x407f007f, 0x43434663, 0x0, 0xb2b17f7f, 0x5fef80ff, 0x300005, 0xc100c0, 0xc300c2}, + {0x52, 0x407f007f, 0x3f3f005a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x8c0005, 0xc100c0, 0xc300c2}, + {0x53, 0x407f047f, 0x4040005a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x8d0005, 0xc100c0, 0xc300c2}, + {0x54, 0x407f043c, 0x41410059, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x8d0005, 0xc100c0, 0xc300c2}, + {0x55, 0x407f043b, 0x42420058, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x8d0005, 0xc100c0, 0xc300c2}, + {0x56, 0x407f007f, 0x3d3d4661, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x900005, 0xc100c0, 0xc300c2}, + {0x60, 0x4064047f, 0x3c3c0060, 0x0, 0xb2b17f7f, 0xdff380ff, 0x420006, 0xc100c0, 0xc300c2}, + {0x61, 0x4064047f, 0x3c3c0078, 0x0, 0xb2b17f7f, 0xdff580ff, 0x430006, 0xc100c0, 0xc300c2}, + {0x62, 0x4053047f, 0x3c3c006c, 0x0, 0xb2b17f7f, 0xd170bebf, 0x1c0006, 0xc100c0, 0xc300c2}, + {0x63, 0x406c047f, 0x3535006c, 0x0, 0xb2b17f7f, 0xdff580ff, 0x430006, 0xc100c0, 0xc300c2}, + {0x64, 0x406c047f, 0x35350054, 0x0, 0xb2b17f7f, 0xdff380ff, 0x420006, 0xc100c0, 0xc300c2}, + {0x65, 0x406c047f, 0x3636006d, 0x0, 0xb2b17f7f, 0xdff580ff, 0x430006, 0xc100c0, 0xc300c2}, + {0x66, 0x406c047f, 0x36360055, 0x0, 0xb2b17f7f, 0xdff380ff, 0x420006, 0xc100c0, 0xc300c2}, + {0x67, 0x4064047f, 0x3030006c, 0x0, 0xb2b17f7f, 0xdff580ff, 0x430006, 0xc100c0, 0xc300c2}, + {0x68, 0x4053047f, 0x30300060, 0x0, 0xb2b17f7f, 0xd170bebf, 0x1c0006, 0xc100c0, 0xc300c2}, + {0x70, 0x407f047f, 0x40400059, 0x0, 0xb2b17f7f, 0x5fef80ff, 0x180007, 0xc100c0, 0xc300c2}, + {0x71, 0x357f0400, 0x3f3c0048, 0x0, 0xb2b17f7f, 0x5fea85ff, 0x140007, 0xc100c0, 0xc300c2}, + {0x72, 0x497f0400, 0x3b3a0044, 0x0, 0xb2b17f7f, 0x5fea82ff, 0x150007, 0xc100c0, 0xc300c2}, + {0x73, 0x407f0402, 0x25240430, 0x0, 0xb2b10202, 0xdfe380ff, 0x440007, 0xc100c0, 0xc300c2}, + {0x74, 0x407f0402, 0x27260032, 0x0, 0xb2b10202, 0x5fc380ff, 0x450007, 0xc100c0, 0xc300c2}, + {0x75, 0x407f0402, 0x2a280034, 0x0, 0xb2b10202, 0x5fc380ff, 0x460007, 0xc100c0, 0xc300c2}, + {0x76, 0x407f0402, 0x2c2b0037, 0x0, 0xb2b10202, 0x5fc380ff, 0x470007, 0xc100c0, 0xc300c2}, + {0x77, 0x407f0401, 0x2d2d0039, 0x0, 0xb2b10000, 0xdfe680ff, 0x4b0007, 0xc100c0, 0xc300c2}, + {0x78, 0x407f0401, 0x2e2e003a, 0x0, 0xb2b10000, 0xdfe680ff, 0x4c0007, 0xc100c0, 0xc300c2}, + {0x79, 0x407f0401, 0x2f2f0047, 0x0, 0xb2b10000, 0xdfe680ff, 0x4e0007, 0xc100c0, 0xc300c2}, + {0x7a, 0x407f0401, 0x3030003c, 0x0, 0xb2b10000, 0xdfe680ff, 0x4d0007, 0xc100c0, 0xc300c2}, + {0x80, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x40008, 0xc100c0, 0xc300c2}, + {0x81, 0x407f047f, 0x39390054, 0x0, 0xb2b17f7f, 0xdfef80ff, 0x70008, 0xc100c0, 0xc300c2}, + {0x82, 0x407f047f, 0x3a3a0053, 0x0, 0xb2b17f7f, 0x5fe0b0ff, 0x80008, 0xc100c0, 0xc300c2}, + {0x83, 0x407f0464, 0x3d3d0059, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x8b0008, 0xc100c0, 0xc300c2}, + {0x90, 0x4064047f, 0x2727003c, 0x0, 0xb2b17f7f, 0x5ffd80ff, 0xd0009, 0xc100c0, 0xc300c2}, + {0x91, 0x407f047f, 0x2626003e, 0x0, 0xb2b17f7f, 0x5ffd80ff, 0xc0009, 0xc100c0, 0xc300c2}, + {0x92, 0x405a047f, 0x27270044, 0x0, 0xb2b17f7f, 0x5ffd80ff, 0x170009, 0xc100c0, 0xc300c2}, + {0x93, 0x407f047f, 0x28280040, 0x0, 0xb2b17f7f, 0x5ffc80ff, 0x110009, 0xc100c0, 0xc300c2}, + {0x94, 0x407f0465, 0x2525003d, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x1d0009, 0xc100c0, 0xc300c2}, + {0x95, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x1e0009, 0xc100c0, 0xc300c2}, + {0x96, 0x407f0400, 0x2424003c, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x1d0009, 0xc100c0, 0xc300c2}, + {0x97, 0x407f0400, 0x29290041, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x110009, 0xc100c0, 0xc300c2}, + {0xa0, 0x407f047f, 0x3d3d6a48, 0x0, 0xb2b17f7f, 0x5fef80ff, 0x5000a, 0xc100c0, 0xc300c2}, + {0xa1, 0x407f047f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x28000a, 0xc100c0, 0xc300c2}, + {0xb0, 0x407f0464, 0x3c3c0048, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0xe000b, 0xc100c0, 0xc300c2}, + {0xb1, 0x407f0464, 0x3f3f004b, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0xf000b, 0xc100c0, 0xc300c2}, + {0xb2, 0x407f0464, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x2d000b, 0xc100c0, 0xc300c2}, + {0xb3, 0x407f0464, 0x3e3e0056, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x2e000b, 0xc100c0, 0xc300c2}, + {0xb4, 0x407f0464, 0x41410059, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x2f000b, 0xc100c0, 0xc300c2}, + {0xb5, 0x407f0464, 0x4040465d, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x35000b, 0xc100c0, 0xc300c2}, + {0xb6, 0x407f0464, 0x4242465f, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x36000b, 0xc100c0, 0xc300c2}, + {0xc0, 0x407f0402, 0x2424003c, 0x0, 0xb2b10000, 0xdfe380ff, 0x4f000c, 0xc100c0, 0xc300c2}, + {0xc1, 0x407f0402, 0x2525003d, 0x0, 0xb2b10000, 0xdfe380ff, 0x52000c, 0xc100c0, 0xc300c2}, + {0xc2, 0x407f0402, 0x2626003e, 0x0, 0xb2b10000, 0xdfe380ff, 0x53000c, 0xc100c0, 0xc300c2}, + {0xc3, 0x407f0402, 0x2727003f, 0x0, 0xb2b10000, 0xdfe380ff, 0x50000c, 0xc100c0, 0xc300c2}, + {0xc4, 0x407f0402, 0x28280040, 0x0, 0xb2b10000, 0xdfe380ff, 0x51000c, 0xc100c0, 0xc300c2}, + {0xc5, 0x407f0402, 0x2b2b0041, 0x0, 0xb2b10000, 0xdfe380ff, 0x54000c, 0xc100c0, 0xc300c2}, + {0xc6, 0x407f0402, 0x3c3c0048, 0x0, 0xb2b10000, 0xdfe380ff, 0x57000c, 0xc100c0, 0xc300c2}, + {0xc7, 0x407f0402, 0x3d3d0049, 0x0, 0xb2b10000, 0xdfe380ff, 0x56000c, 0xc100c0, 0xc300c2}, + {0xc8, 0x407f0402, 0x3e3e004a, 0x0, 0xb2b10000, 0xdfe380ff, 0x58000c, 0xc100c0, 0xc300c2}, + {0xc9, 0x407f0402, 0x3f3f004b, 0x0, 0xb2b10000, 0xdfe380ff, 0x59000c, 0xc100c0, 0xc300c2}, + {0xca, 0x407f0402, 0x4040004c, 0x0, 0xb2b10000, 0xdfe380ff, 0x5a000c, 0xc100c0, 0xc300c2}, + {0xcb, 0x407f0402, 0x4141004d, 0x0, 0xb2b10000, 0xdfe380ff, 0x55000c, 0xc100c0, 0xc300c2}, + {0xd0, 0x407f047f, 0x3b3b4659, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x5b0011, 0xc100c0, 0xc300c2}, + {0xe0, 0x2c7f047f, 0x24240060, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x860014, 0xc100c0, 0xc300c2}, + {0xe1, 0x2c41047f, 0x24240454, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x420014, 0xc100c0, 0xc300c2}, + {0xe2, 0x407f047f, 0x2b2b0060, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x860014, 0xc100c0, 0xc300c2}, + {0xe3, 0x4041047f, 0x2b2b0454, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x420014, 0xc100c0, 0xc300c2}, + {0xe4, 0x547f047f, 0x30300060, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x860014, 0xc100c0, 0xc300c2}, + {0xe5, 0x5441047f, 0x30300454, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x420014, 0xc100c0, 0xc300c2}, + {0xf0, 0x407f007e, 0x7f006a53, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x10015, 0xc100c0, 0xc300c2}, + {0x100, 0x407f047f, 0x3e3e0073, 0x0, 0xb2b17f7f, 0xceab80ff, 0x290018, 0xc100c0, 0xc300c2}, + {0x110, 0x2c7f047f, 0x24240030, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x5d0019, 0xc100c0, 0xc300c2}, + {0x111, 0x407f047f, 0x25250031, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x5e0019, 0xc100c0, 0xc300c2}, + {0x112, 0x547f047f, 0x24246e31, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x5d0019, 0xc100c0, 0xc300c2}, + {0x120, 0x407f0400, 0x7f000054, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x27001b, 0xc100c0, 0xc300c2}, + {0x130, 0x407f047f, 0x7f004659, 0x0, 0xb2b10000, 0xdff080ff, 0x5c001c, 0xc100c0, 0xc300c2}, + {0x140, 0x407f0452, 0x3c3c0054, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x34001f, 0xc100c0, 0xc300c2}, + {0x150, 0x407f047f, 0x3d3d4666, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x920020, 0xc100c0, 0xc300c2}, + {0x160, 0x407f047f, 0x2424003c, 0x0, 0xb2b17f7f, 0x5feb80ff, 0x290021, 0xc100c0, 0xc300c2}, + {0x170, 0x407f047f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x8e0022, 0xc100c0, 0xc300c2}, + {0x171, 0x407f047f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0xdff080ff, 0x8f0022, 0xc100c0, 0xc300c2}, + {0x172, 0x407f047f, 0x3e3e006e, 0x0, 0xb2b17f7f, 0xd16db477, 0x1c0022, 0xc100c0, 0xc300c2}, + {0x173, 0x406b047f, 0x3e3e0075, 0x0, 0xb2b17f7f, 0xdff580ff, 0x430022, 0xc100c0, 0xc300c2}, + {0x174, 0x4041047f, 0x3e3e005d, 0x0, 0xb2b17f7f, 0xdff380ff, 0x420022, 0xc100c0, 0xc300c2}, + {0x180, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x7e0024, 0xc100c0, 0xc300c2}, + {0x181, 0x407f047f, 0x3c3c6a50, 0x0, 0xb2b17f7f, 0xc02080c0, 0x10024, 0xc100c0, 0xc300c2}, + {0x182, 0x407f047f, 0x3c3c0048, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x120024, 0xc100c0, 0xc300c2}, + {0x183, 0x4037047f, 0x3c3c6a64, 0x0, 0xb2b17f7f, 0xc9a080e3, 0x10024, 0xc100c0, 0xc300c2}, + {0x190, 0x407f047f, 0x3c3c0060, 0x0, 0xb2b17f7f, 0x5fe080ff, 0x310026, 0xc100c0, 0xc300c2}, + {0x1a0, 0x407f047f, 0x41410456, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x370027, 0xc100c0, 0xc300c2}, + {0x1a1, 0x4075047f, 0x3c3c0448, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x7f0027, 0xc100c0, 0xc300c2}, + {0x1b0, 0x407f047f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0x5fe080ff, 0x320028, 0xc100c0, 0xc300c2}, + {0x1b1, 0x407f047f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x330028, 0xc100c0, 0xc300c2}, + {0x1c0, 0x407f0400, 0x2424003c, 0x0, 0xb2b10000, 0xdfe380ff, 0x38002b, 0xc100c0, 0xc300c2}, + {0x1c1, 0x407f0400, 0x2525003d, 0x0, 0xb2b10000, 0xdfe380ff, 0x39002b, 0xc100c0, 0xc300c2}, + {0x1c2, 0x407f0400, 0x2626003e, 0x0, 0xb2b10000, 0xdfe380ff, 0x3a002b, 0xc100c0, 0xc300c2}, + {0x1c3, 0x407f0400, 0x2727003f, 0x0, 0xb2b10000, 0xdfe380ff, 0x3b002b, 0xc100c0, 0xc300c2}, + {0x1c4, 0x407f0400, 0x28280040, 0x0, 0xb2b10000, 0xdfe380ff, 0x3c002b, 0xc100c0, 0xc300c2}, + {0x1c5, 0x407f0400, 0x29290041, 0x0, 0xb2b10000, 0xdfe380ff, 0x3d002b, 0xc100c0, 0xc300c2}, + {0x1c6, 0x407f0400, 0x2a2a0042, 0x0, 0xb2b10000, 0xdfe380ff, 0x3e002b, 0xc100c0, 0xc300c2}, + {0x1c7, 0x407f0400, 0x2b2b0043, 0x0, 0xb2b10000, 0xdfe380ff, 0x3f002b, 0xc100c0, 0xc300c2}, + {0x1c8, 0x407f0400, 0x2c2c0044, 0x0, 0xb2b10000, 0xdfe380ff, 0x40002b, 0xc100c0, 0xc300c2}, + {0x1c9, 0x407f0400, 0x2d2d0045, 0x0, 0xb2b10000, 0xdfe380ff, 0x41002b, 0xc100c0, 0xc300c2}, + {0x1d0, 0x407f047f, 0x3d3d0049, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x48002c, 0xc100c0, 0xc300c2}, + {0x1e0, 0x407f0440, 0x3d3d3c56, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x910031, 0xc100c0, 0xc300c2}, + {0x1f0, 0x407f047f, 0x7f000048, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x5f003a, 0xc100c0, 0xc300c2}, + {0x200, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x60003b, 0xc100c0, 0xc300c2}, + {0x201, 0x407f047f, 0x3e3e465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x61003b, 0xc100c0, 0xc300c2}, + {0x202, 0x407f047f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x62003b, 0xc100c0, 0xc300c2}, + {0x203, 0x407f047f, 0x3f3f005b, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x63003b, 0xc100c0, 0xc300c2}, + {0x210, 0x407f047f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x64003c, 0xc100c0, 0xc300c2}, + {0x211, 0x407f047f, 0x3f3f465c, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x65003c, 0xc100c0, 0xc300c2}, + {0x220, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x66003d, 0xc100c0, 0xc300c2}, + {0x221, 0x407f047f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x67003d, 0xc100c0, 0xc300c2}, + {0x222, 0x407f047f, 0x37374653, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x68003d, 0xc100c0, 0xc300c2}, + {0x223, 0x407f047f, 0x38384655, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x69003d, 0xc100c0, 0xc300c2}, + {0x224, 0x407f047f, 0x39394656, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x6a003d, 0xc100c0, 0xc300c2}, + {0x225, 0x407f047f, 0x3b3b4658, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x6b003d, 0xc100c0, 0xc300c2}, + {0x226, 0x407f047f, 0x3a3a4657, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x6c003d, 0xc100c0, 0xc300c2}, + {0x227, 0x407f047f, 0x4040465d, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x6d003d, 0xc100c0, 0xc300c2}, + {0x228, 0x407f047f, 0x4141004d, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x6e003d, 0xc100c0, 0xc300c2}, + {0x230, 0x407f047f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x6f003e, 0xc100c0, 0xc300c2}, + {0x231, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x70003e, 0xc100c0, 0xc300c2}, + {0x232, 0x407f047f, 0x3e3e465b, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x71003e, 0xc100c0, 0xc300c2}, + {0x233, 0x407f047f, 0x3f3f465c, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x72003e, 0xc100c0, 0xc300c2}, + {0x240, 0x407f047f, 0x25254642, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x73003f, 0xc100c0, 0xc300c2}, + {0x241, 0x407f047f, 0x26264643, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x74003f, 0xc100c0, 0xc300c2}, + {0x242, 0x407f047f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x75003f, 0xc100c0, 0xc300c2}, + {0x243, 0x407f047f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x76003f, 0xc100c0, 0xc300c2}, + {0x250, 0x407f047f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x770040, 0xc100c0, 0xc300c2}, + {0x251, 0x407f047f, 0x3e3e465b, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x780040, 0xc100c0, 0xc300c2}, + {0x252, 0x407f047f, 0x27274644, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x790040, 0xc100c0, 0xc300c2}, + {0x253, 0x407f047f, 0x3f3f465c, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x7a0040, 0xc100c0, 0xc300c2}, + {0x260, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x7b0041, 0xc100c0, 0xc300c2}, + {0x261, 0x407f047f, 0x4040465d, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x7c0041, 0xc100c0, 0xc300c2}, + {0x270, 0x407f047f, 0x7f004667, 0x0, 0xb2b17f7f, 0xdfefaaff, 0x7d0042, 0xc100c0, 0xc300c2}, + {0x280, 0x2c7f043c, 0x2f000048, 0x0, 0xb2b10000, 0xdff080ff, 0x880045, 0xc100c0, 0xc300c2}, + {0x281, 0x547f0450, 0x7f300048, 0x0, 0xb2b10000, 0xdff080ff, 0x880045, 0xc100c0, 0xc300c2}, + {0x290, 0x7f0404, 0x53000048, 0x0, 0xb2b10000, 0xdfeb80ff, 0x430046, 0xc100c0, 0xc300c2}, + {0x291, 0x7f7f0404, 0x7f540048, 0x0, 0xb2b10000, 0xdfeb80ff, 0x430046, 0xc100c0, 0xc300c2}, + {0x2a0, 0x3c6e0454, 0x2424003c, 0x0, 0xb2b10000, 0xdfe680ff, 0x80004a, 0xc100c0, 0xc300c2}, + {0x2a1, 0x407f0450, 0x54486578, 0x0, 0xb2b10000, 0xdfeeb0ff, 0x1c004a, 0xc100c0, 0xc300c2}, + {0x2a2, 0x446e0454, 0x2929003c, 0x0, 0xb2b10000, 0xdfe680ff, 0x80004a, 0xc100c0, 0xc300c2}, + {0x2a3, 0x286e0454, 0x3c3c003c, 0x0, 0xb2b10000, 0xdfe680ff, 0x80004a, 0xc100c0, 0xc300c2}, + {0x2a4, 0x586e0454, 0x4747003c, 0x0, 0xb2b10000, 0xdfe680ff, 0x80004a, 0xc100c0, 0xc300c2}, + {0x2b0, 0x40640464, 0x7f004659, 0x0, 0xb2b10000, 0xdfeb80ff, 0x87004c, 0xc100c0, 0xc300c2}, + {0x2c0, 0x407f0454, 0x3f3f004b, 0x0, 0xb2b10000, 0xdfeb80ff, 0x24004d, 0xc100c0, 0xc300c2}, + {0x2c1, 0x407f005a, 0x3b00003c, 0x0, 0xb2b10000, 0xdfeb80ff, 0x25004d, 0xc100c0, 0xc300c2}, + {0x2c2, 0x406e0055, 0x3c3c0048, 0x0, 0xb2b10000, 0xdfeb80ff, 0x8a004d, 0xc100c0, 0xc300c2}, + {0x2d0, 0x407f0464, 0x47005a49, 0x0, 0xb2b10000, 0xdff0a0ff, 0x83004e, 0xc100c0, 0xc300c2}, + {0x2d1, 0x407f0464, 0x7f48005d, 0x0, 0xb2b10000, 0xdfeb80ff, 0x84004e, 0xc100c0, 0xc300c2}, + {0x2e0, 0x407f0455, 0x7f250048, 0x0, 0xb2b10000, 0xdfeb80ff, 0x85004f, 0xc100c0, 0xc300c2}, + {0x2e1, 0x6464044f, 0x24230048, 0x0, 0xb2b10000, 0xdfeb80ff, 0x85004f, 0xc100c0, 0xc300c2}, + {0x2e2, 0x1b640405, 0x2200003c, 0x0, 0xb2b10000, 0xdfeb80ff, 0x85004f, 0xc100c0, 0xc300c2}, + {0x2f0, 0x6e047f, 0x7f005a48, 0x0, 0xb2b10000, 0xdfe780ff, 0x4a0050, 0xc100c0, 0xc300c2}, + {0x2f1, 0x7f6e047f, 0x7f006148, 0x0, 0xb2b10000, 0xdfe780ff, 0x4a0050, 0xc100c0, 0xc300c2}, + {0x300, 0x407f047f, 0x7f00465a, 0x0, 0xb2b10000, 0xdfebb4ff, 0x820051, 0xc100c0, 0xc300c2}, + {0x310, 0x40640464, 0x2400003c, 0x0, 0xb2b10000, 0xdfeb80ff, 0x810053, 0xc100c0, 0xc300c2}, + {0x320, 0x7f0464, 0x3c3c0448, 0x0, 0xb2b10000, 0xdfeba5ff, 0x890054, 0xc100c0, 0xc300c2}, + {0x321, 0x7f7f0464, 0x3d3d0449, 0x0, 0xb2b10000, 0xdfeba5ff, 0x890054, 0xc100c0, 0xc300c2}, + {0x322, 0x7f0464, 0x3e3e044a, 0x0, 0xb2b10000, 0xdfeb80ff, 0x890054, 0xc100c0, 0xc300c2}, + {0x323, 0x7f7f0464, 0x3f3f044b, 0x0, 0xb2b10000, 0xdfeb80ff, 0x890054, 0xc100c0, 0xc300c2}}}; + +const std::vector> AO_MUNK_VH = {{ + {0x0, 0x407f047f, 0x24240054, 0x0, 0xb2b17f7f, 0xdff0bdff, 0x10000, 0xc100c0, 0xc300c2}}}; + +const std::vector> AO_OPTSNDFX_VH = {{ + {0x0, 0x407f0402, 0x48480048, 0x0, 0xb2b17f7f, 0xdffd80ff, 0x10000, 0xc100c0, 0xc300c2}, + {0x1, 0x407f0402, 0x47473247, 0x0, 0xb2b17f7f, 0xdffd80ff, 0x10000, 0xc100c0, 0xc300c2}, + {0x2, 0x407f0400, 0x4c490049, 0x0, 0xb2b17f7f, 0xdffd80ff, 0x20000, 0xc100c0, 0xc300c2}, + {0x3, 0x407f0402, 0x4d4d004d, 0x0, 0xb2b17f7f, 0xdffd80ff, 0x60000, 0xc100c0, 0xc300c2}, + {0x4, 0x407f0402, 0x4e4e644e, 0x0, 0xb2b17f7f, 0xdffd80ff, 0x60000, 0xc100c0, 0xc300c2}, + {0x10, 0x7f007f, 0x30304659, 0x0, 0xb2b10101, 0xdfe680ff, 0x70001, 0xc100c0, 0xc300c2}, + {0x11, 0x7f7f047f, 0x3c3c4665, 0x0, 0xb2b10101, 0xdfe680ff, 0x80001, 0xc100c0, 0xc300c2}, + {0x12, 0x407f047f, 0x48484671, 0x0, 0xb2b10101, 0xdfe680ff, 0x80001, 0xc100c0, 0xc300c2}, + {0x20, 0x407f044f, 0x7f00465a, 0x0, 0xb2b10101, 0xdff0c0ff, 0x90002, 0xc100c0, 0xc300c2}, + {0x30, 0x4028047f, 0x7f000048, 0x0, 0xb2b10000, 0xdffd80ff, 0xc0003, 0xc100c0, 0xc300c2}, + {0x31, 0x4050007f, 0x7f000048, 0x0, 0xb2b10000, 0xdffd80ff, 0xc0003, 0xc100c0, 0xc300c2}, + {0x40, 0x6e047f, 0x7f005a48, 0x0, 0xb2b17f7f, 0xdfe780ff, 0xe0004, 0xc100c0, 0xc300c2}, + {0x41, 0x7f6e047f, 0x7f006148, 0x0, 0xb2b17f7f, 0xdfe780ff, 0xe0004, 0xc100c0, 0xc300c2}, + {0x50, 0x4064047f, 0x3c3c0060, 0x0, 0xb2b17f7f, 0xdff380ff, 0x30006, 0xc100c0, 0xc300c2}, + {0x51, 0x4064047f, 0x3c3c0078, 0x0, 0xb2b17f7f, 0xdff580ff, 0x40006, 0xc100c0, 0xc300c2}, + {0x52, 0x4053047f, 0x3c3c006c, 0x0, 0xb2b17f7f, 0xd170bebf, 0x50006, 0xc100c0, 0xc300c2}, + {0x60, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0xdff580ff, 0xa0008, 0xc100c0, 0xc300c2}, + {0x70, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0xb0009, 0xc100c0, 0xc300c2}, + {0x80, 0x4064007f, 0x7f004659, 0x0, 0xb2b17f7f, 0xdff080ff, 0xd001c, 0xc100c0, 0xc300c2}, + {0x81, 0x4041047f, 0x7f004659, 0x0, 0xb2b17f7f, 0xdff080ff, 0xd001c, 0xc100c0, 0xc300c2}, + {0x90, 0x407f007f, 0x7f000048, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0xf003a, 0xc100c0, 0xc300c2}, + {0xa0, 0x407f007f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0xdffd80ff, 0x10003b, 0xc100c0, 0xc300c2}, + {0xa1, 0x407f007f, 0x3e3e465a, 0x0, 0xb2b17f7f, 0xdffd80ff, 0x11003b, 0xc100c0, 0xc300c2}, + {0xa2, 0x407f007f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0xdffd80ff, 0x12003b, 0xc100c0, 0xc300c2}, + {0xa3, 0x407f007f, 0x3f3f005b, 0x0, 0xb2b17f7f, 0xdffd80ff, 0x13003b, 0xc100c0, 0xc300c2}, + {0xb0, 0x407f007f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0xdffd80ff, 0x14003c, 0xc100c0, 0xc300c2}, + {0xb1, 0x407f007f, 0x3f3f465c, 0x0, 0xb2b17f7f, 0xdffd80ff, 0x15003c, 0xc100c0, 0xc300c2}, + {0xc0, 0x407f007f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0xdffd80ff, 0x16003d, 0xc100c0, 0xc300c2}, + {0xc1, 0x407f007f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0xdffd80ff, 0x17003d, 0xc100c0, 0xc300c2}, + {0xc2, 0x407f007f, 0x37374653, 0x0, 0xb2b17f7f, 0xdffd80ff, 0x18003d, 0xc100c0, 0xc300c2}, + {0xc3, 0x407f007f, 0x38384655, 0x0, 0xb2b17f7f, 0xdffd80ff, 0x19003d, 0xc100c0, 0xc300c2}, + {0xc4, 0x407f007f, 0x39394656, 0x0, 0xb2b17f7f, 0xdffd80ff, 0x1a003d, 0xc100c0, 0xc300c2}, + {0xc5, 0x407f007f, 0x3b3b4658, 0x0, 0xb2b17f7f, 0xdffd80ff, 0x1b003d, 0xc100c0, 0xc300c2}, + {0xc6, 0x407f007f, 0x3a3a4657, 0x0, 0xb2b17f7f, 0xdffd80ff, 0x1c003d, 0xc100c0, 0xc300c2}, + {0xc7, 0x407f007f, 0x4040465d, 0x0, 0xb2b17f7f, 0xdffd80ff, 0x1d003d, 0xc100c0, 0xc300c2}, + {0xc8, 0x407f007f, 0x4141004d, 0x0, 0xb2b17f7f, 0xdffd80ff, 0x1e003d, 0xc100c0, 0xc300c2}, + {0xd0, 0x407f007f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0xdffd80ff, 0x1f003e, 0xc100c0, 0xc300c2}, + {0xd1, 0x407f007f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0xdffd80ff, 0x20003e, 0xc100c0, 0xc300c2}, + {0xd2, 0x407f007f, 0x3e3e465b, 0x0, 0xb2b17f7f, 0xdffd80ff, 0x21003e, 0xc100c0, 0xc300c2}, + {0xd3, 0x407f007f, 0x3f3f465c, 0x0, 0xb2b17f7f, 0xdffd80ff, 0x22003e, 0xc100c0, 0xc300c2}, + {0xe0, 0x407f007f, 0x25254642, 0x0, 0xb2b17f7f, 0xdffd80ff, 0x23003f, 0xc100c0, 0xc300c2}, + {0xe1, 0x407f007f, 0x26264643, 0x0, 0xb2b17f7f, 0xdffd80ff, 0x24003f, 0xc100c0, 0xc300c2}, + {0xe2, 0x407f007f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0xdffd80ff, 0x25003f, 0xc100c0, 0xc300c2}, + {0xe3, 0x407f007f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0xdffd80ff, 0x26003f, 0xc100c0, 0xc300c2}, + {0xf0, 0x407f007f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0xdffd80ff, 0x270040, 0xc100c0, 0xc300c2}, + {0xf1, 0x407f007f, 0x3e3e465b, 0x0, 0xb2b17f7f, 0xdffd80ff, 0x280040, 0xc100c0, 0xc300c2}, + {0xf2, 0x407f007f, 0x27274644, 0x0, 0xb2b17f7f, 0xdffd80ff, 0x290040, 0xc100c0, 0xc300c2}, + {0xf3, 0x407f007f, 0x3f3f465c, 0x0, 0xb2b17f7f, 0xdffd80ff, 0x2a0040, 0xc100c0, 0xc300c2}, + {0x100, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0xdffd80ff, 0x2b0041, 0xc100c0, 0xc300c2}, + {0x101, 0x407f007f, 0x4040465d, 0x0, 0xb2b17f7f, 0xdffd80ff, 0x2c0041, 0xc100c0, 0xc300c2}, + {0x110, 0x407f007f, 0x7f004667, 0x0, 0xb2b17f7f, 0xdfefaaff, 0x2d0042, 0xc100c0, 0xc300c2}}}; + + +const std::vector> AO_RFSNDFX_VH = {{ + {0x0, 0x7f047f, 0x3e006a53, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10000, 0xc100c0, 0xc300c2}, + {0x1, 0x1b380400, 0x3e006a5f, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10000, 0xc100c0, 0xc300c2}, + {0x2, 0x64500400, 0x3e006a66, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10000, 0xc100c0, 0xc300c2}, + {0x3, 0x40280400, 0x3e006a72, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10000, 0xc100c0, 0xc300c2}, + {0x4, 0x7f7f047f, 0x3e006a54, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10000, 0xc100c0, 0xc300c2}, + {0x5, 0x407f045b, 0x42420050, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0xd0000, 0xc100c0, 0xc300c2}, + {0x6, 0x407f045b, 0x4343004f, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x60000, 0xc100c0, 0xc300c2}, + {0x7, 0x407f0464, 0x3e006a4d, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x10000, 0xc100c0, 0xc300c2}, + {0x8, 0x4064047f, 0x4848003c, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x1a0000, 0xc100c0, 0xc300c2}, + {0x9, 0x4064047f, 0x49490049, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x1b0000, 0xc100c0, 0xc300c2}, + {0x10, 0x407f047f, 0x3a3a0046, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x120001, 0xc100c0, 0xc300c2}, + {0x11, 0x407f047f, 0x3b3b0045, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x30001, 0xc100c0, 0xc300c2}, + {0x12, 0x407f047f, 0x48480060, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x3a0001, 0xc100c0, 0xc300c2}, + {0x13, 0x407f047f, 0x48480060, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x20001, 0xc100c0, 0xc300c2}, + {0x14, 0x7f047f, 0x48480048, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x20001, 0xc100c0, 0xc300c2}, + {0x15, 0x7f640451, 0x4848006c, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x20001, 0xc100c0, 0xc300c2}, + {0x16, 0x640451, 0x4848326b, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x20001, 0xc100c0, 0xc300c2}, + {0x17, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x380001, 0xc100c0, 0xc300c2}, + {0x18, 0x407f047f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x390001, 0xc100c0, 0xc300c2}, + {0x19, 0x407f0400, 0x39390045, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x580001, 0xc100c0, 0xc300c2}, + {0x20, 0x40150464, 0x3c3c6a66, 0x0, 0xb2b17f7f, 0xdaeb80ff, 0x10002, 0xc100c0, 0xc300c2}, + {0x21, 0x3f5f0400, 0x3b006a4e, 0x0, 0xb2b17f7f, 0xdfef80ff, 0x10002, 0xc100c0, 0xc300c2}, + {0x22, 0x540400, 0x3b006a54, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x10002, 0xc100c0, 0xc300c2}, + {0x23, 0x7f540400, 0x3b006a56, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10002, 0xc100c0, 0xc300c2}, + {0x24, 0x7f007f, 0x3c3c003c, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x1e0002, 0xc100c0, 0xc300c2}, + {0x25, 0x7f7f007f, 0x3c3c003c, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x1f0002, 0xc100c0, 0xc300c2}, + {0x26, 0x4046047f, 0x3c3c003c, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x200002, 0xc100c0, 0xc300c2}, + {0x27, 0x407f047f, 0x40400040, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x210002, 0xc100c0, 0xc300c2}, + {0x28, 0x7f007e, 0x54480054, 0x0, 0xb2b10000, 0xdfe680ff, 0x1e0002, 0xc100c0, 0xc300c2}, + {0x29, 0x7f7f007e, 0x54480054, 0x0, 0xb2b10000, 0x1fc080ff, 0x1f0002, 0xc100c0, 0xc300c2}, + {0x30, 0x407f047f, 0x3b3b0047, 0x0, 0xb2b17f7f, 0x5fde80ff, 0x110003, 0xc100c0, 0xc300c2}, + {0x31, 0x407f047f, 0x3a3a0052, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x70003, 0xc100c0, 0xc300c2}, + {0x32, 0x407f047f, 0x43430048, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x80003, 0xc100c0, 0xc300c2}, + {0x33, 0x407f047f, 0x48480054, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x190003, 0xc100c0, 0xc300c2}, + {0x34, 0x407f047f, 0x39390051, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x220003, 0xc100c0, 0xc300c2}, + {0x40, 0x407f044f, 0x24240030, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x90004, 0xc100c0, 0xc300c2}, + {0x41, 0x407f044f, 0x25250031, 0x0, 0xb2b17f7f, 0xdffea5ff, 0x90004, 0xc100c0, 0xc300c2}, + {0x42, 0x407f044f, 0x26260032, 0x0, 0xb2b17f7f, 0xdffeaaff, 0x90004, 0xc100c0, 0xc300c2}, + {0x43, 0x407f047f, 0x3b3b0047, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x100004, 0xc100c0, 0xc300c2}, + {0x44, 0x407f007f, 0x48480060, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x3a0004, 0xc100c0, 0xc300c2}, + {0x45, 0x407f047f, 0x48480054, 0x0, 0xb2b17f7f, 0xdffeacff, 0x190004, 0xc100c0, 0xc300c2}, + {0x50, 0x4064047f, 0x3030006c, 0x0, 0xb2b17f7f, 0xdff580ff, 0x840006, 0xc100c0, 0xc300c2}, + {0x51, 0x4053047f, 0x30300060, 0x0, 0xb2b17f7f, 0xd170bebf, 0x850006, 0xc100c0, 0xc300c2}, + {0x52, 0x407f047f, 0x24240054, 0x0, 0xb2b17f7f, 0xdff0bdff, 0x850006, 0xc100c0, 0xc300c2}, + {0x60, 0x407f047f, 0x40400059, 0x0, 0xb2b17f7f, 0x5fef80ff, 0x150007, 0xc100c0, 0xc300c2}, + {0x61, 0x407f0402, 0x30240054, 0x0, 0xb2b10000, 0xdfeb80ff, 0x5e0007, 0xc100c0, 0xc300c2}, + {0x62, 0x407f0402, 0x23236a54, 0x0, 0xb2b10000, 0xdfe080ff, 0x10007, 0xc100c0, 0xc300c2}, + {0x63, 0x407f0402, 0x2222005e, 0x0, 0xb2b10000, 0x1fc080ff, 0x20007, 0xc100c0, 0xc300c2}, + {0x64, 0x407f0402, 0x21210051, 0x0, 0xb2b10000, 0x1fc080ff, 0x30007, 0xc100c0, 0xc300c2}, + {0x65, 0x407f0402, 0x2020004a, 0x0, 0xb2b10000, 0x1fc080ff, 0x70007, 0xc100c0, 0xc300c2}, + {0x66, 0x407f0402, 0x1f1f0049, 0x0, 0xb2b10000, 0x1fc080ff, 0x90007, 0xc100c0, 0xc300c2}, + {0x67, 0x407f0402, 0x1e1e0042, 0x0, 0xb2b10000, 0x1fc080ff, 0xc0007, 0xc100c0, 0xc300c2}, + {0x68, 0x407f0402, 0x33310061, 0x0, 0xb2b10000, 0x1fc080ff, 0x100007, 0xc100c0, 0xc300c2}, + {0x69, 0x407f0402, 0x3f340060, 0x0, 0xb2b10000, 0x1fc080ff, 0x110007, 0xc100c0, 0xc300c2}, + {0x70, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x40008, 0xc100c0, 0xc300c2}, + {0x80, 0x4064047f, 0x2727003c, 0x0, 0xb2b17f7f, 0x5ffd80ff, 0xb0009, 0xc100c0, 0xc300c2}, + {0x81, 0x407f047f, 0x2626003e, 0x0, 0xb2b17f7f, 0x5ffd80ff, 0xa0009, 0xc100c0, 0xc300c2}, + {0x82, 0x405a047f, 0x27270044, 0x0, 0xb2b17f7f, 0x5ffd80ff, 0x130009, 0xc100c0, 0xc300c2}, + {0x83, 0x407f047f, 0x28280040, 0x0, 0xb2b17f7f, 0x5ffc80ff, 0xe0009, 0xc100c0, 0xc300c2}, + {0x84, 0x407f0465, 0x2525003d, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x1c0009, 0xc100c0, 0xc300c2}, + {0x85, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x1d0009, 0xc100c0, 0xc300c2}, + {0x86, 0x407f0400, 0x2424003c, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x1c0009, 0xc100c0, 0xc300c2}, + {0x87, 0x407f0400, 0x29290041, 0x0, 0xb2b17f7f, 0xdfe380ff, 0xe0009, 0xc100c0, 0xc300c2}, + {0x90, 0x407f047f, 0x3d3d6a48, 0x0, 0xb2b17f7f, 0x5fef80ff, 0x5000a, 0xc100c0, 0xc300c2}, + {0x91, 0x407f047f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x32000a, 0xc100c0, 0xc300c2}, + {0xa0, 0x407f0464, 0x3c3c0048, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0xc000b, 0xc100c0, 0xc300c2}, + {0xa1, 0x407f0464, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x3b000b, 0xc100c0, 0xc300c2}, + {0xb0, 0x407f0400, 0x26260026, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x16000c, 0xc100c0, 0xc300c2}, + {0xb1, 0x407f047f, 0x27270027, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x14000c, 0xc100c0, 0xc300c2}, + {0xb2, 0x407f0464, 0x28280040, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x23000c, 0xc100c0, 0xc300c2}, + {0xb3, 0x407f0464, 0x29290041, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x24000c, 0xc100c0, 0xc300c2}, + {0xb4, 0x407f0464, 0x2a2a0042, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x25000c, 0xc100c0, 0xc300c2}, + {0xb5, 0x407f0464, 0x2c2c0044, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x31000c, 0xc100c0, 0xc300c2}, + {0xc0, 0x407f047f, 0x3c3c003c, 0x0, 0xb2b17f7f, 0xdfe080ff, 0xf000f, 0xc100c0, 0xc300c2}, + {0xc1, 0x407f047f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x58000f, 0xc100c0, 0xc300c2}, + {0xc2, 0x407f007f, 0x3c3c0048, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x48000f, 0xc100c0, 0xc300c2}, + {0xd0, 0x407f0479, 0x30300048, 0x0, 0xb2b17f7f, 0xdff080ff, 0x3c0010, 0xc100c0, 0xc300c2}, + {0xe0, 0x2c7f047f, 0x24240030, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x3d0011, 0xc100c0, 0xc300c2}, + {0xe1, 0x367f0451, 0x25250031, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x170011, 0xc100c0, 0xc300c2}, + {0xe2, 0x407f047f, 0x3b3b4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x5d0011, 0xc100c0, 0xc300c2}, + {0xf0, 0x407f0464, 0x2d2d0045, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x260012, 0xc100c0, 0xc300c2}, + {0xf1, 0x407f047f, 0x2e2e0046, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x270012, 0xc100c0, 0xc300c2}, + {0xf2, 0x407f0432, 0x2f2f0047, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x280012, 0xc100c0, 0xc300c2}, + {0xf3, 0x407f0432, 0x30300048, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x290012, 0xc100c0, 0xc300c2}, + {0xf4, 0x407f0432, 0x2424003c, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x2a0012, 0xc100c0, 0xc300c2}, + {0xf5, 0x407f0432, 0x2525003d, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x2b0012, 0xc100c0, 0xc300c2}, + {0x100, 0x407f047f, 0x30300054, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x490013, 0xc100c0, 0xc300c2}, + {0x110, 0x407f007e, 0x7f006a53, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x10015, 0xc100c0, 0xc300c2}, + {0x120, 0x407f0464, 0x3d3d004e, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x70016, 0xc100c0, 0xc300c2}, + {0x121, 0x407f0464, 0x3e3e0045, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x110016, 0xc100c0, 0xc300c2}, + {0x122, 0x407f0464, 0x3f3f0046, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x110016, 0xc100c0, 0xc300c2}, + {0x123, 0x407f0464, 0x3f3f0050, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x240016, 0xc100c0, 0xc300c2}, + {0x124, 0x3c7f044f, 0x2121002d, 0x0, 0xb2b17f7f, 0xdfe0a4ff, 0x90016, 0xc100c0, 0xc300c2}, + {0x125, 0x407f044f, 0x2222002d, 0x0, 0xb2b17f7f, 0xdfe0a0ff, 0x90016, 0xc100c0, 0xc300c2}, + {0x126, 0x3c7f004f, 0x2323002d, 0x0, 0xb2b17f7f, 0xdfe0a7ff, 0x90016, 0xc100c0, 0xc300c2}, + {0x127, 0x407f004f, 0x2424002d, 0x0, 0xb2b17f7f, 0xdfe0aaff, 0x90016, 0xc100c0, 0xc300c2}, + {0x128, 0x407f0450, 0x2525002d, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x2f0016, 0xc100c0, 0xc300c2}, + {0x130, 0x407f047f, 0x3e3e0073, 0x0, 0xb2b17f7f, 0xceab80ff, 0x370018, 0xc100c0, 0xc300c2}, + {0x140, 0x2c7f047f, 0x24240030, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x180019, 0xc100c0, 0xc300c2}, + {0x141, 0x407f047f, 0x25250031, 0x0, 0xb2b17f7f, 0x5ffd80ff, 0x170019, 0xc100c0, 0xc300c2}, + {0x142, 0x547f047f, 0x24246e31, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x180019, 0xc100c0, 0xc300c2}, + {0x150, 0x407f0400, 0x7f000054, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x30001b, 0xc100c0, 0xc300c2}, + {0x160, 0x407f047f, 0x7f004659, 0x0, 0xb2b10000, 0x5ff080ff, 0x3e001c, 0xc100c0, 0xc300c2}, + {0x170, 0x407f047f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x49001e, 0xc100c0, 0xc300c2}, + {0x180, 0x407f0452, 0x3c3c0054, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x4e001f, 0xc100c0, 0xc300c2}, + {0x190, 0x407f047f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x550020, 0xc100c0, 0xc300c2}, + {0x1a0, 0x407f047f, 0x2424003c, 0x0, 0xb2b17f7f, 0x5feb80ff, 0x370021, 0xc100c0, 0xc300c2}, + {0x1b0, 0x407f0446, 0x24240031, 0x0, 0xb2b17f7f, 0xdffeb4ff, 0x2a0025, 0xc100c0, 0xc300c2}, + {0x1b1, 0x407f0446, 0x25250031, 0x0, 0xb2b17f7f, 0xdffeaaff, 0x2b0025, 0xc100c0, 0xc300c2}, + {0x1b2, 0x407f0464, 0x3c3c0448, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x3f0025, 0xc100c0, 0xc300c2}, + {0x1b3, 0x407f0464, 0x3d3d0449, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x400025, 0xc100c0, 0xc300c2}, + {0x1b4, 0x407f0450, 0x4040044c, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x410025, 0xc100c0, 0xc300c2}, + {0x1b5, 0x407f0450, 0x4141044c, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x410025, 0xc100c0, 0xc300c2}, + {0x1b6, 0x407f0464, 0x4242044e, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x420025, 0xc100c0, 0xc300c2}, + {0x1b7, 0x407f0464, 0x4343044f, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x430025, 0xc100c0, 0xc300c2}, + {0x1c0, 0x407f047f, 0x3c3c005a, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x490026, 0xc100c0, 0xc300c2}, + {0x1d0, 0x407f047f, 0x3c3c0448, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x440027, 0xc100c0, 0xc300c2}, + {0x1d1, 0x407f047f, 0x4040044c, 0x0, 0xb2b17f7f, 0x5feb80ff, 0x450027, 0xc100c0, 0xc300c2}, + {0x1d2, 0x407f0451, 0x4242045a, 0x0, 0xb2b17f7f, 0x5feb80ff, 0x460027, 0xc100c0, 0xc300c2}, + {0x1d3, 0x407f044b, 0x4343044f, 0x0, 0xb2b17f7f, 0x5feb80ff, 0x470027, 0xc100c0, 0xc300c2}, + {0x1d4, 0x407f047f, 0x41410459, 0x0, 0xb2b17f7f, 0x5feb80ff, 0x480027, 0xc100c0, 0xc300c2}, + {0x1d5, 0x4075047f, 0x4444005c, 0x0, 0xb2b17f7f, 0x5fe080ff, 0x4a0027, 0xc100c0, 0xc300c2}, + {0x1d6, 0x407f047f, 0x3d3d0457, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x4b0027, 0xc100c0, 0xc300c2}, + {0x1e0, 0x407f047f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0x5fe080ff, 0x4c0028, 0xc100c0, 0xc300c2}, + {0x1e1, 0x407f047f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x4d0028, 0xc100c0, 0xc300c2}, + {0x1f0, 0x407f047f, 0x40404659, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x57002a, 0xc100c0, 0xc300c2}, + {0x200, 0x407f0419, 0x3e3e465b, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x81002c, 0xc100c0, 0xc300c2}, + {0x201, 0x407f0419, 0x3f3f465c, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x82002c, 0xc100c0, 0xc300c2}, + {0x210, 0x407f047f, 0x30304659, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x54002d, 0xc100c0, 0xc300c2}, + {0x220, 0x407f0400, 0x3c3c003c, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x56002e, 0xc100c0, 0xc300c2}, + {0x230, 0x407f047f, 0x7f000054, 0x0, 0xb2b10000, 0x1fc080ff, 0x59002f, 0xc100c0, 0xc300c2}, + {0x231, 0x5450047f, 0x7f000048, 0x0, 0xb2b10000, 0x1fc080ff, 0x59002f, 0xc100c0, 0xc300c2}, + {0x232, 0x4064047f, 0x7f00005a, 0x0, 0xb2b10000, 0x1fc080ff, 0x59002f, 0xc100c0, 0xc300c2}, + {0x233, 0x2c6e047f, 0x7f000060, 0x0, 0xb2b10000, 0x1fc080ff, 0x5a002f, 0xc100c0, 0xc300c2}, + {0x234, 0x2c55047f, 0x7f00006c, 0x0, 0xb2b10000, 0x1fc080ff, 0x5a002f, 0xc100c0, 0xc300c2}, + {0x235, 0x544b047f, 0x7f000072, 0x0, 0xb2b10000, 0x1fc080ff, 0x5a002f, 0xc100c0, 0xc300c2}, + {0x240, 0x407f047f, 0x24240054, 0x0, 0xb2b17f7f, 0x5fcb80ff, 0x5b0030, 0xc100c0, 0xc300c2}, + {0x241, 0x407f047f, 0x2a2a0054, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x5c0030, 0xc100c0, 0xc300c2}, + {0x242, 0x407f047f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x5c0030, 0xc100c0, 0xc300c2}, + {0x243, 0x407f047f, 0x3e3e0056, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x5b0030, 0xc100c0, 0xc300c2}, + {0x244, 0x407f047f, 0x3f3f0057, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x610030, 0xc100c0, 0xc300c2}, + {0x250, 0x407f047f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x5f0031, 0xc100c0, 0xc300c2}, + {0x251, 0x407f0440, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x600031, 0xc100c0, 0xc300c2}, + {0x252, 0x407f047f, 0x3e3e0056, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x600031, 0xc100c0, 0xc300c2}, + {0x260, 0x407f047f, 0x7f000048, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x62003a, 0xc100c0, 0xc300c2}, + {0x270, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x63003b, 0xc100c0, 0xc300c2}, + {0x271, 0x407f047f, 0x3e3e465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x64003b, 0xc100c0, 0xc300c2}, + {0x272, 0x407f047f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x65003b, 0xc100c0, 0xc300c2}, + {0x273, 0x407f047f, 0x3f3f005b, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x66003b, 0xc100c0, 0xc300c2}, + {0x280, 0x407f047f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x67003c, 0xc100c0, 0xc300c2}, + {0x281, 0x407f047f, 0x3f3f465c, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x68003c, 0xc100c0, 0xc300c2}, + {0x290, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x69003d, 0xc100c0, 0xc300c2}, + {0x291, 0x407f047f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x6a003d, 0xc100c0, 0xc300c2}, + {0x292, 0x407f047f, 0x37374653, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x6b003d, 0xc100c0, 0xc300c2}, + {0x293, 0x407f047f, 0x38384655, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x6c003d, 0xc100c0, 0xc300c2}, + {0x294, 0x407f047f, 0x39394656, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x6d003d, 0xc100c0, 0xc300c2}, + {0x295, 0x407f047f, 0x3b3b4658, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x6e003d, 0xc100c0, 0xc300c2}, + {0x296, 0x407f047f, 0x3a3a4657, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x6f003d, 0xc100c0, 0xc300c2}, + {0x297, 0x407f047f, 0x4040465d, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x70003d, 0xc100c0, 0xc300c2}, + {0x298, 0x407f047f, 0x4141004d, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x71003d, 0xc100c0, 0xc300c2}, + {0x2a0, 0x407f047f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x72003e, 0xc100c0, 0xc300c2}, + {0x2a1, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x73003e, 0xc100c0, 0xc300c2}, + {0x2a2, 0x407f047f, 0x3e3e465b, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x74003e, 0xc100c0, 0xc300c2}, + {0x2a3, 0x407f047f, 0x3f3f465c, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x75003e, 0xc100c0, 0xc300c2}, + {0x2b0, 0x407f047f, 0x25254642, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x76003f, 0xc100c0, 0xc300c2}, + {0x2b1, 0x407f047f, 0x26264643, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x77003f, 0xc100c0, 0xc300c2}, + {0x2b2, 0x407f047f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x78003f, 0xc100c0, 0xc300c2}, + {0x2b3, 0x407f047f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x79003f, 0xc100c0, 0xc300c2}, + {0x2c0, 0x407f047f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x7a0040, 0xc100c0, 0xc300c2}, + {0x2c1, 0x407f047f, 0x3e3e465b, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x7b0040, 0xc100c0, 0xc300c2}, + {0x2c2, 0x407f047f, 0x27274644, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x7c0040, 0xc100c0, 0xc300c2}, + {0x2c3, 0x407f047f, 0x3f3f465c, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x7d0040, 0xc100c0, 0xc300c2}, + {0x2d0, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x7e0041, 0xc100c0, 0xc300c2}, + {0x2d1, 0x407f047f, 0x4040465d, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x7f0041, 0xc100c0, 0xc300c2}, + {0x2e0, 0x407f047f, 0x7f004667, 0x0, 0xb2b17f7f, 0xdfefaaff, 0x800042, 0xc100c0, 0xc300c2}, + {0x2f0, 0x7f0404, 0x5050046c, 0x0, 0xb2b10000, 0xdfeb80ff, 0x530046, 0xc100c0, 0xc300c2}, + {0x2f1, 0x7f7f0404, 0x5151046d, 0x0, 0xb2b10000, 0xdfeb80ff, 0x530046, 0xc100c0, 0xc300c2}, + {0x300, 0x407f005a, 0x2f00003c, 0x0, 0xb2b10000, 0xdfe680ff, 0x4f004a, 0xc100c0, 0xc300c2}, + {0x301, 0x407f045a, 0x36300048, 0x0, 0xb2b10000, 0xdfe680ff, 0x50004a, 0xc100c0, 0xc300c2}, + {0x302, 0x407f0456, 0x43370054, 0x0, 0xb2b10000, 0xdfe680ff, 0x51004a, 0xc100c0, 0xc300c2}, + {0x303, 0x407f0456, 0x4f440060, 0x0, 0xb2b10000, 0xdfe680ff, 0x52004a, 0xc100c0, 0xc300c2}, + {0x304, 0x7f0455, 0x5050006c, 0x0, 0xb2b10000, 0xdfe680ff, 0x53004a, 0xc100c0, 0xc300c2}, + {0x305, 0x7f7f0453, 0x5151006d, 0x0, 0xb2b10000, 0xdfe680ff, 0x53004a, 0xc100c0, 0xc300c2}, + {0x310, 0x407f0457, 0x3f3f004b, 0x0, 0xb2b10000, 0x5feb80ff, 0x2d004d, 0xc100c0, 0xc300c2}, + {0x311, 0x407f005a, 0x3b00003c, 0x0, 0xb2b10000, 0xdfeb80ff, 0x2e004d, 0xc100c0, 0xc300c2}, + {0x312, 0x406e0055, 0x3c3c0048, 0x0, 0xb2b10000, 0xdfeb80ff, 0x83004d, 0xc100c0, 0xc300c2}, + {0x320, 0x407f0478, 0x47005a49, 0x0, 0xb2b10000, 0xdff0a0ff, 0x33004e, 0xc100c0, 0xc300c2}, + {0x321, 0x407f0478, 0x7f48005d, 0x0, 0xb2b10000, 0xdfeb80ff, 0x34004e, 0xc100c0, 0xc300c2}, + {0x330, 0x407f0455, 0x7f250048, 0x0, 0xb2b10000, 0xdfeb80ff, 0x35004f, 0xc100c0, 0xc300c2}, + {0x331, 0x6464044f, 0x24230048, 0x0, 0xb2b10000, 0xdfeb80ff, 0x35004f, 0xc100c0, 0xc300c2}, + {0x332, 0x1b640405, 0x2200003c, 0x0, 0xb2b10000, 0xdfeb80ff, 0x35004f, 0xc100c0, 0xc300c2}, + {0x340, 0x6e047f, 0x7f005a48, 0x0, 0xb2b10000, 0xdfe780ff, 0x360050, 0xc100c0, 0xc300c2}, + {0x341, 0x7f6e047f, 0x7f006148, 0x0, 0xb2b10000, 0xdfe780ff, 0x360050, 0xc100c0, 0xc300c2}, + {0x350, 0x407f047f, 0x7f00465a, 0x0, 0xb2b10000, 0x5febb4ff, 0x2c0051, 0xc100c0, 0xc300c2}}}; + +const std::vector> AO_RFENDER_VH = {{ + {0x0, 0x7f047f, 0x3e006a53, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10000, 0xc100c0, 0xc300c2}, + {0x1, 0x1b380400, 0x3e006a5f, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10000, 0xc100c0, 0xc300c2}, + {0x2, 0x64500400, 0x3e006a66, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10000, 0xc100c0, 0xc300c2}, + {0x3, 0x40280400, 0x3e006a72, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10000, 0xc100c0, 0xc300c2}, + {0x4, 0x7f7f047f, 0x3e006a54, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10000, 0xc100c0, 0xc300c2}, + {0x5, 0x407f045b, 0x42420050, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0xb0000, 0xc100c0, 0xc300c2}, + {0x6, 0x407f045b, 0x4343004f, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x60000, 0xc100c0, 0xc300c2}, + {0x7, 0x407f0464, 0x3e006a4d, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x10000, 0xc100c0, 0xc300c2}, + {0x8, 0x4064045a, 0x4848003c, 0x0, 0xb2b17f7f, 0xdffd80ff, 0x140000, 0xc100c0, 0xc300c2}, + {0x9, 0x4064045a, 0x49490049, 0x0, 0xb2b17f7f, 0xdffd80ff, 0x150000, 0xc100c0, 0xc300c2}, + {0x10, 0x407f047f, 0x3a3a0046, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0xe0001, 0xc100c0, 0xc300c2}, + {0x11, 0x407f047f, 0x3b3b0045, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x30001, 0xc100c0, 0xc300c2}, + {0x12, 0x407f047f, 0x48480060, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x310001, 0xc100c0, 0xc300c2}, + {0x13, 0x407f047f, 0x48480060, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x20001, 0xc100c0, 0xc300c2}, + {0x14, 0x7f047f, 0x48480048, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x20001, 0xc100c0, 0xc300c2}, + {0x15, 0x7f640451, 0x4848006c, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x20001, 0xc100c0, 0xc300c2}, + {0x16, 0x640451, 0x4848326b, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x20001, 0xc100c0, 0xc300c2}, + {0x17, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x2f0001, 0xc100c0, 0xc300c2}, + {0x18, 0x407f047f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x300001, 0xc100c0, 0xc300c2}, + {0x19, 0x407f0400, 0x39390045, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x3c0001, 0xc100c0, 0xc300c2}, + {0x20, 0x40150450, 0x3c3c6a66, 0x0, 0xb2b17f7f, 0xdaeb80ff, 0x10002, 0xc100c0, 0xc300c2}, + {0x21, 0x3f5f0400, 0x3b006a4e, 0x0, 0xb2b17f7f, 0x5fef80ff, 0x10002, 0xc100c0, 0xc300c2}, + {0x22, 0x540400, 0x3b006a54, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10002, 0xc100c0, 0xc300c2}, + {0x23, 0x7f540400, 0x3b006a56, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10002, 0xc100c0, 0xc300c2}, + {0x24, 0x7f007f, 0x3c3c003c, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x160002, 0xc100c0, 0xc300c2}, + {0x25, 0x7f7f007f, 0x3c3c003c, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x170002, 0xc100c0, 0xc300c2}, + {0x26, 0x4046047f, 0x3c3c003c, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x180002, 0xc100c0, 0xc300c2}, + {0x27, 0x407f047f, 0x40400040, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x190002, 0xc100c0, 0xc300c2}, + {0x30, 0x407f047f, 0x3b3b0047, 0x0, 0xb2b17f7f, 0x5fde80ff, 0xd0003, 0xc100c0, 0xc300c2}, + {0x31, 0x407f047f, 0x3a3a0052, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x70003, 0xc100c0, 0xc300c2}, + {0x32, 0x407f047f, 0x43430048, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x80003, 0xc100c0, 0xc300c2}, + {0x33, 0x407f047f, 0x48480054, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x130003, 0xc100c0, 0xc300c2}, + {0x34, 0x407f047f, 0x39390051, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x1a0003, 0xc100c0, 0xc300c2}, + {0x40, 0x407f0450, 0x24240030, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x90004, 0xc100c0, 0xc300c2}, + {0x41, 0x407f0450, 0x25250031, 0x0, 0xb2b17f7f, 0xdffea5ff, 0x90004, 0xc100c0, 0xc300c2}, + {0x42, 0x407f0450, 0x26260032, 0x0, 0xb2b17f7f, 0xdffeaaff, 0x90004, 0xc100c0, 0xc300c2}, + {0x43, 0x407f047f, 0x3b3b0047, 0x0, 0xb2b17f7f, 0xdffe80ff, 0xc0004, 0xc100c0, 0xc300c2}, + {0x44, 0x407f007f, 0x48480060, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x310004, 0xc100c0, 0xc300c2}, + {0x45, 0x407f047f, 0x48480054, 0x0, 0xb2b17f7f, 0xdffeacff, 0x130004, 0xc100c0, 0xc300c2}, + {0x50, 0x407f047f, 0x40400059, 0x0, 0xb2b17f7f, 0x5fef80ff, 0x100007, 0xc100c0, 0xc300c2}, + {0x51, 0x407f0402, 0x30240054, 0x0, 0xb2b10000, 0xdfeb80ff, 0x400007, 0xc100c0, 0xc300c2}, + {0x52, 0x407f0402, 0x23236a54, 0x0, 0xb2b10000, 0xdfe080ff, 0x10007, 0xc100c0, 0xc300c2}, + {0x53, 0x407f0402, 0x2222005e, 0x0, 0xb2b10000, 0x1fc080ff, 0x20007, 0xc100c0, 0xc300c2}, + {0x54, 0x407f0402, 0x21210051, 0x0, 0xb2b10000, 0x1fc080ff, 0x30007, 0xc100c0, 0xc300c2}, + {0x55, 0x407f0402, 0x2020004a, 0x0, 0xb2b10000, 0x1fc080ff, 0x70007, 0xc100c0, 0xc300c2}, + {0x56, 0x407f0402, 0x1f1f0049, 0x0, 0xb2b10000, 0x1fc080ff, 0x90007, 0xc100c0, 0xc300c2}, + {0x57, 0x407f0402, 0x1e1e0042, 0x0, 0xb2b10000, 0x1fc080ff, 0xa0007, 0xc100c0, 0xc300c2}, + {0x58, 0x407f0402, 0x33310061, 0x0, 0xb2b10000, 0x1fc080ff, 0xc0007, 0xc100c0, 0xc300c2}, + {0x59, 0x407f0402, 0x3f340060, 0x0, 0xb2b10000, 0x1fc080ff, 0xd0007, 0xc100c0, 0xc300c2}, + {0x60, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x40008, 0xc100c0, 0xc300c2}, + {0x70, 0x407f047f, 0x3d3d6a48, 0x0, 0xb2b17f7f, 0x5fef80ff, 0x5000a, 0xc100c0, 0xc300c2}, + {0x71, 0x407f047f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x29000a, 0xc100c0, 0xc300c2}, + {0x80, 0x407f0464, 0x3c3c0048, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0xa000b, 0xc100c0, 0xc300c2}, + {0x81, 0x407f0464, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x32000b, 0xc100c0, 0xc300c2}, + {0x90, 0x407f0400, 0x26260026, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x11000c, 0xc100c0, 0xc300c2}, + {0x91, 0x407f047f, 0x27270027, 0x0, 0xb2b17f7f, 0xdfe680ff, 0xf000c, 0xc100c0, 0xc300c2}, + {0x92, 0x407f0464, 0x28280040, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x1b000c, 0xc100c0, 0xc300c2}, + {0x93, 0x407f0464, 0x29290041, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x1c000c, 0xc100c0, 0xc300c2}, + {0x94, 0x407f0464, 0x2a2a0042, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x1d000c, 0xc100c0, 0xc300c2}, + {0x95, 0x407f0464, 0x2c2c0044, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x28000c, 0xc100c0, 0xc300c2}, + {0xa0, 0x407f047f, 0x30300048, 0x0, 0xb2b17f7f, 0xdff080ff, 0x330010, 0xc100c0, 0xc300c2}, + {0xb0, 0x2c7f047f, 0x24240030, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x340011, 0xc100c0, 0xc300c2}, + {0xb1, 0x367f0451, 0x25250031, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x120011, 0xc100c0, 0xc300c2}, + {0xb2, 0x407f047f, 0x3b3b4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x3f0011, 0xc100c0, 0xc300c2}, + {0xc0, 0x407f0464, 0x2d2d0045, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x1e0012, 0xc100c0, 0xc300c2}, + {0xc1, 0x407f047f, 0x2e2e0046, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x1f0012, 0xc100c0, 0xc300c2}, + {0xc2, 0x407f0432, 0x2f2f0047, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x200012, 0xc100c0, 0xc300c2}, + {0xc3, 0x407f0432, 0x30300048, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x210012, 0xc100c0, 0xc300c2}, + {0xc4, 0x407f0432, 0x2424003c, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x220012, 0xc100c0, 0xc300c2}, + {0xc5, 0x407f0432, 0x2525003d, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x230012, 0xc100c0, 0xc300c2}, + {0xd0, 0x407f047f, 0x30300054, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x360013, 0xc100c0, 0xc300c2}, + {0xe0, 0x407f007e, 0x7f006a53, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x10015, 0xc100c0, 0xc300c2}, + {0xf0, 0x407f0464, 0x3d3d004e, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x70016, 0xc100c0, 0xc300c2}, + {0xf1, 0x407f0464, 0x3e3e0045, 0x0, 0xb2b17f7f, 0xdfe080ff, 0xd0016, 0xc100c0, 0xc300c2}, + {0xf2, 0x407f0464, 0x3f3f0046, 0x0, 0xb2b17f7f, 0xdfe080ff, 0xd0016, 0xc100c0, 0xc300c2}, + {0xf3, 0x407f0464, 0x3f3f0050, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x1c0016, 0xc100c0, 0xc300c2}, + {0xf4, 0x3c7f0450, 0x2121002d, 0x0, 0xb2b17f7f, 0xdfe0a4ff, 0x90016, 0xc100c0, 0xc300c2}, + {0xf5, 0x407f0450, 0x2222002d, 0x0, 0xb2b17f7f, 0xdfe0a0ff, 0x90016, 0xc100c0, 0xc300c2}, + {0xf6, 0x3c7f0050, 0x2323002d, 0x0, 0xb2b17f7f, 0xdfe0a7ff, 0x90016, 0xc100c0, 0xc300c2}, + {0xf7, 0x407f0050, 0x2424002d, 0x0, 0xb2b17f7f, 0xdfe0aaff, 0x90016, 0xc100c0, 0xc300c2}, + {0xf8, 0x407f0450, 0x2525002d, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x270016, 0xc100c0, 0xc300c2}, + {0x100, 0x407f047f, 0x3e3e0073, 0x0, 0xb2b17f7f, 0xceab80ff, 0x2e0018, 0xc100c0, 0xc300c2}, + {0x110, 0x2c7f047f, 0x24240030, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x6b0019, 0xc100c0, 0xc300c2}, + {0x111, 0x407f047f, 0x25250031, 0x0, 0xb2b17f7f, 0x5ffd80ff, 0x120019, 0xc100c0, 0xc300c2}, + {0x112, 0x547f047f, 0x24246e31, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x6b0019, 0xc100c0, 0xc300c2}, + {0x120, 0x407f047f, 0x7f004659, 0x0, 0xb2b10000, 0x5ff080ff, 0x35001c, 0xc100c0, 0xc300c2}, + {0x130, 0x407f0452, 0x3c3c0054, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x39001f, 0xc100c0, 0xc300c2}, + {0x140, 0x407f047f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x3a0020, 0xc100c0, 0xc300c2}, + {0x150, 0x407f047f, 0x2424003c, 0x0, 0xb2b17f7f, 0x5feb80ff, 0x2e0021, 0xc100c0, 0xc300c2}, + {0x160, 0x407f047f, 0x3c3c005a, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x360026, 0xc100c0, 0xc300c2}, + {0x170, 0x407f047f, 0x3c3c0448, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x6e0027, 0xc100c0, 0xc300c2}, + {0x171, 0x4075047f, 0x4444005c, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x6f0027, 0xc100c0, 0xc300c2}, + {0x180, 0x407f047f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0x5fe080ff, 0x370028, 0xc100c0, 0xc300c2}, + {0x181, 0x407f047f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x380028, 0xc100c0, 0xc300c2}, + {0x190, 0x407f0400, 0x3c3c003c, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x3b002e, 0xc100c0, 0xc300c2}, + {0x1a0, 0x407f047f, 0x24240054, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x3d0030, 0xc100c0, 0xc300c2}, + {0x1a1, 0x407f047f, 0x2a2a0054, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x3e0030, 0xc100c0, 0xc300c2}, + {0x1a2, 0x407f047f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x3e0030, 0xc100c0, 0xc300c2}, + {0x1a3, 0x407f047f, 0x3e3e0056, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x3d0030, 0xc100c0, 0xc300c2}, + {0x1a4, 0x407f047f, 0x3f3f0057, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x430030, 0xc100c0, 0xc300c2}, + {0x1b0, 0x407f047f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x410031, 0xc100c0, 0xc300c2}, + {0x1b1, 0x407f0440, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x420031, 0xc100c0, 0xc300c2}, + {0x1b2, 0x407f047f, 0x3e3e0056, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x420031, 0xc100c0, 0xc300c2}, + {0x1c0, 0x407f047f, 0x24244641, 0x0, 0xb2b17f7f, 0x5fc380ff, 0x640032, 0xc100c0, 0xc300c2}, + {0x1c1, 0x407f047e, 0x3030464d, 0x0, 0xb2b17f7f, 0x5fcb80ff, 0x650032, 0xc100c0, 0xc300c2}, + {0x1c2, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x6a0032, 0xc100c0, 0xc300c2}, + {0x1d0, 0x407f047f, 0x7f000048, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x44003a, 0xc100c0, 0xc300c2}, + {0x1e0, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x45003b, 0xc100c0, 0xc300c2}, + {0x1e1, 0x407f047f, 0x3e3e465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x46003b, 0xc100c0, 0xc300c2}, + {0x1e2, 0x407f047f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x47003b, 0xc100c0, 0xc300c2}, + {0x1e3, 0x407f047f, 0x3f3f005b, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x48003b, 0xc100c0, 0xc300c2}, + {0x1f0, 0x407f047f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x49003c, 0xc100c0, 0xc300c2}, + {0x1f1, 0x407f047f, 0x3f3f465c, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x4a003c, 0xc100c0, 0xc300c2}, + {0x200, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x4b003d, 0xc100c0, 0xc300c2}, + {0x201, 0x407f047f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x4c003d, 0xc100c0, 0xc300c2}, + {0x202, 0x407f047f, 0x37374653, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x4d003d, 0xc100c0, 0xc300c2}, + {0x203, 0x407f047f, 0x38384655, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x4e003d, 0xc100c0, 0xc300c2}, + {0x204, 0x407f047f, 0x39394656, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x4f003d, 0xc100c0, 0xc300c2}, + {0x205, 0x407f047f, 0x3b3b4658, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x50003d, 0xc100c0, 0xc300c2}, + {0x206, 0x407f047f, 0x3a3a4657, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x51003d, 0xc100c0, 0xc300c2}, + {0x207, 0x407f047f, 0x4040465d, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x52003d, 0xc100c0, 0xc300c2}, + {0x208, 0x407f047f, 0x4141004d, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x53003d, 0xc100c0, 0xc300c2}, + {0x210, 0x407f047f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x54003e, 0xc100c0, 0xc300c2}, + {0x211, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x55003e, 0xc100c0, 0xc300c2}, + {0x212, 0x407f047f, 0x3e3e465b, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x56003e, 0xc100c0, 0xc300c2}, + {0x213, 0x407f047f, 0x3f3f465c, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x57003e, 0xc100c0, 0xc300c2}, + {0x220, 0x407f047f, 0x25254642, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x58003f, 0xc100c0, 0xc300c2}, + {0x221, 0x407f047f, 0x26264643, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x59003f, 0xc100c0, 0xc300c2}, + {0x222, 0x407f047f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x5a003f, 0xc100c0, 0xc300c2}, + {0x223, 0x407f047f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x5b003f, 0xc100c0, 0xc300c2}, + {0x230, 0x407f047f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x5c0040, 0xc100c0, 0xc300c2}, + {0x231, 0x407f047f, 0x3e3e465b, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x5d0040, 0xc100c0, 0xc300c2}, + {0x232, 0x407f047f, 0x27274644, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x5e0040, 0xc100c0, 0xc300c2}, + {0x233, 0x407f047f, 0x3f3f465c, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x5f0040, 0xc100c0, 0xc300c2}, + {0x240, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x600041, 0xc100c0, 0xc300c2}, + {0x241, 0x407f047f, 0x4040465d, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x610041, 0xc100c0, 0xc300c2}, + {0x250, 0x407f047f, 0x7f004667, 0x0, 0xb2b17f7f, 0xdfefaaff, 0x620042, 0xc100c0, 0xc300c2}, + {0x260, 0x4064047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x660043, 0xc100c0, 0xc300c2}, + {0x261, 0x4064047f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x670043, 0xc100c0, 0xc300c2}, + {0x262, 0x4064047f, 0x3e3e465b, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x680043, 0xc100c0, 0xc300c2}, + {0x263, 0x4064047f, 0x3f3f465c, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x690043, 0xc100c0, 0xc300c2}, + {0x264, 0x4064047f, 0x40404660, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x6c0043, 0xc100c0, 0xc300c2}, + {0x265, 0x4064047f, 0x41414661, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x6d0043, 0xc100c0, 0xc300c2}, + {0x270, 0x7f0404, 0x5050046c, 0x0, 0xb2b10000, 0xdfeb80ff, 0x700046, 0xc100c0, 0xc300c2}, + {0x271, 0x7f7f0404, 0x5151046d, 0x0, 0xb2b10000, 0xdfeb80ff, 0x700046, 0xc100c0, 0xc300c2}, + {0x280, 0x407f005a, 0x2f00003c, 0x0, 0xb2b10000, 0xdfe680ff, 0x71004a, 0xc100c0, 0xc300c2}, + {0x281, 0x407f045a, 0x36300048, 0x0, 0xb2b10000, 0xdfe680ff, 0x72004a, 0xc100c0, 0xc300c2}, + {0x282, 0x407f0456, 0x43370054, 0x0, 0xb2b10000, 0xdfe680ff, 0x73004a, 0xc100c0, 0xc300c2}, + {0x283, 0x407f0456, 0x4f440060, 0x0, 0xb2b10000, 0xdfe680ff, 0x74004a, 0xc100c0, 0xc300c2}, + {0x284, 0x7f0455, 0x5050006c, 0x0, 0xb2b10000, 0xdfe680ff, 0x70004a, 0xc100c0, 0xc300c2}, + {0x285, 0x7f7f0453, 0x5151006d, 0x0, 0xb2b10000, 0xdfe680ff, 0x70004a, 0xc100c0, 0xc300c2}, + {0x290, 0x407f0454, 0x3f3f004b, 0x0, 0xb2b10000, 0x5feb80ff, 0x25004d, 0xc100c0, 0xc300c2}, + {0x291, 0x407f005a, 0x3b00003c, 0x0, 0xb2b10000, 0xdfeb80ff, 0x26004d, 0xc100c0, 0xc300c2}, + {0x292, 0x406e0055, 0x3c3c0048, 0x0, 0xb2b10000, 0xdfeb80ff, 0x63004d, 0xc100c0, 0xc300c2}, + {0x2a0, 0x407f0478, 0x47005a49, 0x0, 0xb2b10000, 0xdff0a0ff, 0x2a004e, 0xc100c0, 0xc300c2}, + {0x2a1, 0x407f0478, 0x7f48005d, 0x0, 0xb2b10000, 0xdfeb80ff, 0x2b004e, 0xc100c0, 0xc300c2}, + {0x2b0, 0x407f0455, 0x7f250048, 0x0, 0xb2b10000, 0xdfeb80ff, 0x2c004f, 0xc100c0, 0xc300c2}, + {0x2b1, 0x64640452, 0x24230048, 0x0, 0xb2b10000, 0xdfeb80ff, 0x2c004f, 0xc100c0, 0xc300c2}, + {0x2b2, 0x1b640405, 0x2200003c, 0x0, 0xb2b10000, 0xdfeb80ff, 0x2c004f, 0xc100c0, 0xc300c2}, + {0x2c0, 0x6e047f, 0x7f005a48, 0x0, 0xb2b10000, 0xdfe780ff, 0x2d0050, 0xc100c0, 0xc300c2}, + {0x2c1, 0x7f6e047f, 0x7f006148, 0x0, 0xb2b10000, 0xdfe780ff, 0x2d0050, 0xc100c0, 0xc300c2}, + {0x2d0, 0x407f047f, 0x7f00465a, 0x0, 0xb2b10000, 0x5febb4ff, 0x240051, 0xc100c0, 0xc300c2}}}; + +} \ No newline at end of file From 954908d7b58b671ea0429fd4689f9f9d518bc2eb Mon Sep 17 00:00:00 2001 From: Sean Horton <3359434+sean-horton@users.noreply.github.com> Date: Wed, 26 Apr 2023 18:57:29 -0400 Subject: [PATCH 57/82] remove unneeded table entries --- Source/AliveLibCommon/audio/oddio.h | 2487 +++++---------------------- 1 file changed, 431 insertions(+), 2056 deletions(-) diff --git a/Source/AliveLibCommon/audio/oddio.h b/Source/AliveLibCommon/audio/oddio.h index a9fc71553..7961ba3b9 100644 --- a/Source/AliveLibCommon/audio/oddio.h +++ b/Source/AliveLibCommon/audio/oddio.h @@ -2,2100 +2,475 @@ namespace ODDIO { +// All patches in this file are based on Steams US version. + // VH files contain vag attributes // VB files contain audio data -// AO SAMPLE RATE LOOKUP TABLES -// [0] == vag id +// 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 -const std::vector> AO_D1SNDFX_RATE = {{ - {40, 22050}, - {48, 16000}, - {49, 16000}, - {82, 16000}, - {106, 22050}, - {108, 16000}, - {109, 22050}, - {113, 22050}, - {117, 22050}, - {118, 22050}}}; - -const std::vector> AO_F1SNDFX_RATE = {{ - {54, 16000}, - {55, 16000}, - {91, 22050}, - {93, 22050}, - {95, 16000}, - {96, 22050}, - {100, 22050}, - {104, 22050}, - {105, 22050}}}; +// 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. -const std::vector> AO_MUNK_RATE = {{}}; +// First column (vag) number can be correlated between rate and attr tables +// for a given VH/VB pair (Example, D1SNDFX.VH and D1SNDFX.VB) -// S1.LVL - OPTSNDEFX.VH -const std::vector> AO_OPTSNDFX_RATE = {{ - {0, 4000}, - {6, 44100}, - {7, 44100}, - {8, 22050}, - {13, 16000}, - {15, 22050}, - {17, 16000}, - {18, 22050}, - {22, 22050}, - {26, 22050}, - {27, 22050}}}; +// RATE TABLES +// {vag, sampleRate} +// VH TABLES +// {vag, vagOffset, [256 bits of vag data]} -// R1.LVL - RFSNDEFX.VH -const std::vector> AO_RFSNDFX_RATE = {{ - {43, 22050}, - {53, 16000}, - {54, 16000}, - {99, 22050}, - {101, 16000}, - {102, 22050}, - {106, 22050}, - {110, 22050}, - {111, 22050}}}; +// 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] -// AO 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. +//////////////////////////////////////// +// 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 = {{ - {0x0, 0x7f047e, 0x3e006a53, 0x0, 0xb2b17f7f, 0xdff080ff, 0x10000, 0xc100c0, 0xc300c2}, - {0x1, 0x1b380400, 0x3e006a5f, 0x0, 0xb2b17f7f, 0xdff080ff, 0x10000, 0xc100c0, 0xc300c2}, - {0x2, 0x64500400, 0x3e006a66, 0x0, 0xb2b17f7f, 0xdff080ff, 0x10000, 0xc100c0, 0xc300c2}, - {0x3, 0x40280400, 0x3e006a72, 0x0, 0xb2b17f7f, 0xdff080ff, 0x10000, 0xc100c0, 0xc300c2}, - {0x4, 0x7f7f047e, 0x3e006a54, 0x0, 0xb2b17f7f, 0xdff080ff, 0x10000, 0xc100c0, 0xc300c2}, - {0x5, 0x407f005b, 0x42420050, 0x0, 0xb2b17f7f, 0xdff080ff, 0x140000, 0xc100c0, 0xc300c2}, - {0x6, 0x407f005b, 0x4343004f, 0x0, 0xb2b17f7f, 0xdff080ff, 0x80000, 0xc100c0, 0xc300c2}, - {0x7, 0x407f047e, 0x3e006a4d, 0x0, 0xb2b17f7f, 0xdff080ff, 0x10000, 0xc100c0, 0xc300c2}, - {0x8, 0x4064007f, 0x4848003c, 0x0, 0xb2b17f7f, 0x5fe380ff, 0x1f0000, 0xc100c0, 0xc300c2}, - {0x9, 0x4064007f, 0x49490049, 0x0, 0xb2b17f7f, 0x5fe380ff, 0x200000, 0xc100c0, 0xc300c2}, - {0x10, 0x407f007f, 0x3a3a0046, 0x0, 0xb2b17f7f, 0x5feb80ff, 0x180001, 0xc100c0, 0xc300c2}, - {0x11, 0x407f007f, 0x3b3b0045, 0x0, 0xb2b17f7f, 0x5feb80ff, 0x30001, 0xc100c0, 0xc300c2}, - {0x12, 0x407f007f, 0x48480060, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x350001, 0xc100c0, 0xc300c2}, - {0x13, 0x407f007f, 0x48480060, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x20001, 0xc100c0, 0xc300c2}, - {0x14, 0x7f007f, 0x48480048, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x20001, 0xc100c0, 0xc300c2}, - {0x15, 0x7f640051, 0x4848006c, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x20001, 0xc100c0, 0xc300c2}, - {0x16, 0x640051, 0x4848326b, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x20001, 0xc100c0, 0xc300c2}, - {0x17, 0x407f007f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x330001, 0xc100c0, 0xc300c2}, - {0x18, 0x407f007f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x340001, 0xc100c0, 0xc300c2}, - {0x19, 0x407f0000, 0x39390045, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x620001, 0xc100c0, 0xc300c2}, - {0x20, 0x40150464, 0x3c3c6a66, 0x0, 0xb2b17f7f, 0xdaeb80ff, 0x10002, 0xc100c0, 0xc300c2}, - {0x21, 0x3f5f0400, 0x3b006a4e, 0x0, 0xb2b17f7f, 0xdfef80ff, 0x10002, 0xc100c0, 0xc300c2}, - {0x22, 0x540400, 0x3b006a54, 0x0, 0xb2b17f7f, 0xdffd80ff, 0x10002, 0xc100c0, 0xc300c2}, - {0x23, 0x7f540400, 0x3b006a56, 0x0, 0xb2b17f7f, 0xdffd80ff, 0x10002, 0xc100c0, 0xc300c2}, - {0x24, 0x7f007f, 0x3c3c003c, 0x0, 0xb2b17f7f, 0xdffd80ff, 0x240002, 0xc100c0, 0xc300c2}, - {0x25, 0x7f7f007f, 0x3c3c003c, 0x0, 0xb2b17f7f, 0xdffd80ff, 0x250002, 0xc100c0, 0xc300c2}, - {0x26, 0x4046007f, 0x3c3c003c, 0x0, 0xb2b17f7f, 0xdffd80ff, 0x260002, 0xc100c0, 0xc300c2}, - {0x27, 0x407f007f, 0x40400040, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x270002, 0xc100c0, 0xc300c2}, - {0x30, 0x407f007f, 0x3b3b0047, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x170003, 0xc100c0, 0xc300c2}, - {0x31, 0x407f007f, 0x3a3a0052, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0xb0003, 0xc100c0, 0xc300c2}, - {0x32, 0x407f007f, 0x43430048, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0xc0003, 0xc100c0, 0xc300c2}, - {0x33, 0x407f007f, 0x48480054, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x1e0003, 0xc100c0, 0xc300c2}, - {0x34, 0x407f007f, 0x39390051, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x280003, 0xc100c0, 0xc300c2}, - {0x40, 0x407f0050, 0x24240030, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0xd0004, 0xc100c0, 0xc300c2}, - {0x41, 0x407f0050, 0x25250031, 0x0, 0xb2b17f7f, 0xdfeba5ff, 0xd0004, 0xc100c0, 0xc300c2}, - {0x42, 0x407f0050, 0x26260032, 0x0, 0xb2b17f7f, 0xdfebaaff, 0xd0004, 0xc100c0, 0xc300c2}, - {0x43, 0x407f007f, 0x3b3b0047, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x160004, 0xc100c0, 0xc300c2}, - {0x44, 0x407f007f, 0x48480060, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x350004, 0xc100c0, 0xc300c2}, - {0x45, 0x407f007f, 0x48480054, 0x0, 0xb2b17f7f, 0xdfebacff, 0x1e0004, 0xc100c0, 0xc300c2}, - {0x50, 0x407f0050, 0x25250030, 0x0, 0xb2b17f7f, 0xdfe580ff, 0x190005, 0xc100c0, 0xc300c2}, - {0x51, 0x407f007f, 0x43434663, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x100005, 0xc100c0, 0xc300c2}, - {0x52, 0x407f007f, 0x4040005a, 0x0, 0xb2b17f7f, 0xdfed80ff, 0x680005, 0xc100c0, 0xc300c2}, - {0x53, 0x407f003c, 0x41410059, 0x0, 0xb2b17f7f, 0xdfed80ff, 0x680005, 0xc100c0, 0xc300c2}, - {0x54, 0x407f003b, 0x42420058, 0x0, 0xb2b17f7f, 0xdfef80ff, 0x680005, 0xc100c0, 0xc300c2}, - {0x55, 0x407f007f, 0x3f3f005a, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x690005, 0xc100c0, 0xc300c2}, - {0x56, 0x407f007f, 0x3d3d4661, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x8c0005, 0xc100c0, 0xc300c2}, - {0x60, 0x4064047f, 0x3c3c0060, 0x0, 0xb2b17f7f, 0x5ff380ff, 0x40006, 0xc100c0, 0xc300c2}, - {0x61, 0x4064047f, 0x3c3c0078, 0x0, 0xb2b17f7f, 0x5ff580ff, 0x50006, 0xc100c0, 0xc300c2}, - {0x62, 0x4053047f, 0x3c3c006c, 0x0, 0xb2b17f7f, 0x51f0bebf, 0x210006, 0xc100c0, 0xc300c2}, - {0x63, 0x406c047f, 0x3535006c, 0x0, 0xb2b17f7f, 0x5ff580ff, 0x50006, 0xc100c0, 0xc300c2}, - {0x64, 0x406c047f, 0x35350054, 0x0, 0xb2b17f7f, 0x5ff380ff, 0x40006, 0xc100c0, 0xc300c2}, - {0x65, 0x406c047f, 0x3636006d, 0x0, 0xb2b17f7f, 0x5ff580ff, 0x50006, 0xc100c0, 0xc300c2}, - {0x66, 0x406c047f, 0x36360055, 0x0, 0xb2b17f7f, 0x5ff380ff, 0x40006, 0xc100c0, 0xc300c2}, - {0x67, 0x4064047f, 0x3030006c, 0x0, 0xb2b17f7f, 0xdff580ff, 0x50006, 0xc100c0, 0xc300c2}, - {0x68, 0x4053047f, 0x30300060, 0x0, 0xb2b17f7f, 0xd170bebf, 0x210006, 0xc100c0, 0xc300c2}, - {0x70, 0x407f007f, 0x40400059, 0x0, 0xb2b17f7f, 0x5fef80ff, 0x1b0007, 0xc100c0, 0xc300c2}, - {0x71, 0x407f047f, 0x22220048, 0x0, 0xb2b10404, 0xdfefc0ff, 0x4c0007, 0xc100c0, 0xc300c2}, - {0x72, 0x407f0402, 0x3030003c, 0x0, 0xb2b10000, 0x5feb80ff, 0x4d0007, 0xc100c0, 0xc300c2}, - {0x73, 0x407f0002, 0x48480060, 0x0, 0xb2b10000, 0x5fee80ff, 0x4e0007, 0xc100c0, 0xc300c2}, - {0x74, 0x407f0002, 0x43430054, 0x0, 0xb2b10000, 0x5fee80ff, 0x4e0007, 0xc100c0, 0xc300c2}, - {0x75, 0x357f0000, 0x3c3c0048, 0x0, 0xb2b17f7f, 0x5fea85ff, 0x660007, 0xc100c0, 0xc300c2}, - {0x76, 0x397f0000, 0x3b3b0044, 0x0, 0xb2b17f7f, 0xdfea82ff, 0x670007, 0xc100c0, 0xc300c2}, - {0x80, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x60008, 0xc100c0, 0xc300c2}, - {0x81, 0x407f047f, 0x39390054, 0x0, 0xb2b17f7f, 0xdfef80ff, 0x90008, 0xc100c0, 0xc300c2}, - {0x82, 0x407f047f, 0x3a3a0053, 0x0, 0xb2b17f7f, 0xdfe3b0ff, 0xa0008, 0xc100c0, 0xc300c2}, - {0x90, 0x4064007f, 0x2727003c, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0xf0009, 0xc100c0, 0xc300c2}, - {0x91, 0x407f007f, 0x2626003e, 0x0, 0xb2b17f7f, 0xdfe680ff, 0xe0009, 0xc100c0, 0xc300c2}, - {0x92, 0x405a007f, 0x27270044, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x1a0009, 0xc100c0, 0xc300c2}, - {0x93, 0x407f007f, 0x28280040, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x150009, 0xc100c0, 0xc300c2}, - {0x94, 0x407f0065, 0x2525003d, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x220009, 0xc100c0, 0xc300c2}, - {0x95, 0x407f007f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x230009, 0xc100c0, 0xc300c2}, - {0x96, 0x407f0000, 0x3d3d465a, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x8e0009, 0xc100c0, 0xc300c2}, - {0x97, 0x407f0000, 0x2424003c, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x220009, 0xc100c0, 0xc300c2}, - {0x98, 0x407f0000, 0x29290041, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x150009, 0xc100c0, 0xc300c2}, - {0xa0, 0x407f007f, 0x3d3d6a4a, 0x0, 0xb2b17f7f, 0xdfef80ff, 0x7000a, 0xc100c0, 0xc300c2}, - {0xa1, 0x407f007f, 0x3c3c0060, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x2d000a, 0xc100c0, 0xc300c2}, - {0xb0, 0x407f0064, 0x3c3c0048, 0x0, 0xb2b17f7f, 0xdff080ff, 0x11000b, 0xc100c0, 0xc300c2}, - {0xb1, 0x407f0064, 0x3f3f004b, 0x0, 0xb2b17f7f, 0xdff080ff, 0x12000b, 0xc100c0, 0xc300c2}, - {0xb2, 0x407f0064, 0x3d3d0055, 0x0, 0xb2b17f7f, 0xdff080ff, 0x36000b, 0xc100c0, 0xc300c2}, - {0xb3, 0x407f0064, 0x3e3e0056, 0x0, 0xb2b17f7f, 0xdff080ff, 0x43000b, 0xc100c0, 0xc300c2}, - {0xb4, 0x407f0064, 0x41410059, 0x0, 0xb2b17f7f, 0xdff080ff, 0x44000b, 0xc100c0, 0xc300c2}, - {0xb5, 0x407f0064, 0x4040465d, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x55000b, 0xc100c0, 0xc300c2}, - {0xb6, 0x407f0064, 0x4242465f, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x56000b, 0xc100c0, 0xc300c2}, - {0xc0, 0x407f0002, 0x26260026, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x37000c, 0xc100c0, 0xc300c2}, - {0xc1, 0x407f007f, 0x27270027, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x38000c, 0xc100c0, 0xc300c2}, - {0xc2, 0x407f0064, 0x28280040, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x39000c, 0xc100c0, 0xc300c2}, - {0xc3, 0x407f0064, 0x29290041, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x3a000c, 0xc100c0, 0xc300c2}, - {0xc4, 0x407f0064, 0x2a2a0042, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x3b000c, 0xc100c0, 0xc300c2}, - {0xc5, 0x407f0064, 0x2c2c0044, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x3c000c, 0xc100c0, 0xc300c2}, - {0xd0, 0x407f0050, 0x24240024, 0x0, 0xb2b17f7f, 0x5fc380ff, 0x8f000e, 0xc100c0, 0xc300c2}, - {0xd1, 0x407f0050, 0x25250025, 0x0, 0xb2b17f7f, 0x5fc380ff, 0x90000e, 0xc100c0, 0xc300c2}, - {0xd2, 0x407f0050, 0x3d3d4659, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x91000e, 0xc100c0, 0xc300c2}, - {0xd3, 0x407f0050, 0x3e3e465a, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x91000e, 0xc100c0, 0xc300c2}, - {0xe0, 0x407f007f, 0x3b3b4659, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x630011, 0xc100c0, 0xc300c2}, - {0xf0, 0x407f0064, 0x2d2d0045, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x3d0012, 0xc100c0, 0xc300c2}, - {0xf1, 0x407f007f, 0x2e2e0046, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x3e0012, 0xc100c0, 0xc300c2}, - {0xf2, 0x407f0032, 0x2f2f0047, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x3f0012, 0xc100c0, 0xc300c2}, - {0xf3, 0x407f0032, 0x30300048, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x400012, 0xc100c0, 0xc300c2}, - {0xf4, 0x407f0032, 0x2424003c, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x410012, 0xc100c0, 0xc300c2}, - {0xf5, 0x407f0032, 0x2525003d, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x420012, 0xc100c0, 0xc300c2}, - {0x100, 0x407f007e, 0x7f006a53, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x10015, 0xc100c0, 0xc300c2}, - {0x110, 0x407f0064, 0x3d3d004e, 0x0, 0xb2b17f7f, 0xdfe380ff, 0xb0016, 0xc100c0, 0xc300c2}, - {0x111, 0x407f0064, 0x3e3e0045, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x170016, 0xc100c0, 0xc300c2}, - {0x112, 0x407f0064, 0x3f3f0046, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x170016, 0xc100c0, 0xc300c2}, - {0x113, 0x407f0064, 0x3f3f0057, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x3a0016, 0xc100c0, 0xc300c2}, - {0x114, 0x3c7f0050, 0x2121462d, 0x0, 0xb2b17f7f, 0xdfe3a4ff, 0xd0016, 0xc100c0, 0xc300c2}, - {0x115, 0x407f0050, 0x2222462d, 0x0, 0xb2b17f7f, 0xdfe3a0ff, 0xd0016, 0xc100c0, 0xc300c2}, - {0x116, 0x3c7f0050, 0x2323462d, 0x0, 0xb2b17f7f, 0xdfe3a7ff, 0xd0016, 0xc100c0, 0xc300c2}, - {0x117, 0x407f0050, 0x2424462d, 0x0, 0xb2b17f7f, 0xdfe3aaff, 0xd0016, 0xc100c0, 0xc300c2}, - {0x118, 0x407f0050, 0x2525002d, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x190016, 0xc100c0, 0xc300c2}, - {0x120, 0x407f047f, 0x3e3e0073, 0x0, 0xb2b17f7f, 0xceab80ff, 0x530018, 0xc100c0, 0xc300c2}, - {0x130, 0x2c7f007f, 0x24240030, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x1d0019, 0xc100c0, 0xc300c2}, - {0x131, 0x407f007f, 0x25250031, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x1c0019, 0xc100c0, 0xc300c2}, - {0x132, 0x547f007f, 0x24246e31, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x1d0019, 0xc100c0, 0xc300c2}, - {0x140, 0x407f0400, 0x7f000054, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x2c001b, 0xc100c0, 0xc300c2}, - {0x150, 0x407f007f, 0x7f004659, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x64001c, 0xc100c0, 0xc300c2}, - {0x160, 0x407f0052, 0x7f000054, 0x0, 0xb2b10000, 0xdfe380ff, 0x52001f, 0xc100c0, 0xc300c2}, - {0x170, 0x407f047f, 0x2424003c, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x530021, 0xc100c0, 0xc300c2}, - {0x180, 0x407f007f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x650024, 0xc100c0, 0xc300c2}, - {0x181, 0x407f007f, 0x3c3c6a50, 0x0, 0xb2b17f7f, 0xc0a380c0, 0x10024, 0xc100c0, 0xc300c2}, - {0x182, 0x407f007f, 0x3c3c0048, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x160024, 0xc100c0, 0xc300c2}, - {0x183, 0x4037007f, 0x3c3c6a64, 0x0, 0xb2b17f7f, 0xc9a380e3, 0x10024, 0xc100c0, 0xc300c2}, - {0x190, 0x407f007f, 0x3c3c0060, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x4f0026, 0xc100c0, 0xc300c2}, - {0x1a0, 0x407f007f, 0x41410456, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x540027, 0xc100c0, 0xc300c2}, - {0x1b0, 0x407f007f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x500028, 0xc100c0, 0xc300c2}, - {0x1b1, 0x407f007f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x510028, 0xc100c0, 0xc300c2}, - {0x1c0, 0x407f0400, 0x2424003c, 0x0, 0xb2b10000, 0xdfe380ff, 0x57002b, 0xc100c0, 0xc300c2}, - {0x1c1, 0x407f0400, 0x2525003d, 0x0, 0xb2b10000, 0xdfe380ff, 0x58002b, 0xc100c0, 0xc300c2}, - {0x1c2, 0x407f0400, 0x2626003e, 0x0, 0xb2b10000, 0xdfe380ff, 0x59002b, 0xc100c0, 0xc300c2}, - {0x1c3, 0x407f0000, 0x2727003f, 0x0, 0xb2b10000, 0xdfe380ff, 0x5a002b, 0xc100c0, 0xc300c2}, - {0x1c4, 0x407f0400, 0x28280040, 0x0, 0xb2b10000, 0xdfe380ff, 0x5b002b, 0xc100c0, 0xc300c2}, - {0x1c5, 0x407f0000, 0x29290041, 0x0, 0xb2b10000, 0xdfe380ff, 0x5c002b, 0xc100c0, 0xc300c2}, - {0x1c6, 0x407f0400, 0x2a2a0042, 0x0, 0xb2b10000, 0xdfe380ff, 0x5d002b, 0xc100c0, 0xc300c2}, - {0x1c7, 0x407f0000, 0x2b2b0043, 0x0, 0xb2b10000, 0xdfe380ff, 0x5e002b, 0xc100c0, 0xc300c2}, - {0x1c8, 0x407f0400, 0x2c2c0044, 0x0, 0xb2b10000, 0xdfe380ff, 0x5f002b, 0xc100c0, 0xc300c2}, - {0x1c9, 0x407f0000, 0x2d2d0045, 0x0, 0xb2b10000, 0xdfe380ff, 0x60002b, 0xc100c0, 0xc300c2}, - {0x1d0, 0x407f047f, 0x3d3d0049, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x61002c, 0xc100c0, 0xc300c2}, - {0x1e0, 0x407f0040, 0x3d3d3c56, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x8d0031, 0xc100c0, 0xc300c2}, - {0x1f0, 0x407f007f, 0x7f000048, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x6a003a, 0xc100c0, 0xc300c2}, - {0x200, 0x407f007f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x6b003b, 0xc100c0, 0xc300c2}, - {0x201, 0x407f007f, 0x3e3e465a, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x6c003b, 0xc100c0, 0xc300c2}, - {0x202, 0x407f007f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x6d003b, 0xc100c0, 0xc300c2}, - {0x203, 0x407f007f, 0x3f3f005b, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x6e003b, 0xc100c0, 0xc300c2}, - {0x210, 0x407f007f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x6f003c, 0xc100c0, 0xc300c2}, - {0x211, 0x407f007f, 0x3f3f465c, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x70003c, 0xc100c0, 0xc300c2}, - {0x220, 0x407f007f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x71003d, 0xc100c0, 0xc300c2}, - {0x221, 0x407f007f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x72003d, 0xc100c0, 0xc300c2}, - {0x222, 0x407f007f, 0x37374653, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x73003d, 0xc100c0, 0xc300c2}, - {0x223, 0x407f007f, 0x38384655, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x74003d, 0xc100c0, 0xc300c2}, - {0x224, 0x407f007f, 0x39394656, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x75003d, 0xc100c0, 0xc300c2}, - {0x225, 0x407f007f, 0x3b3b4658, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x76003d, 0xc100c0, 0xc300c2}, - {0x226, 0x407f007f, 0x3a3a4657, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x77003d, 0xc100c0, 0xc300c2}, - {0x227, 0x407f007f, 0x4040465d, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x78003d, 0xc100c0, 0xc300c2}, - {0x228, 0x407f007f, 0x4141004d, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x79003d, 0xc100c0, 0xc300c2}, - {0x230, 0x407f007f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x7a003e, 0xc100c0, 0xc300c2}, - {0x231, 0x407f007f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x7b003e, 0xc100c0, 0xc300c2}, - {0x232, 0x407f007f, 0x3e3e465b, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x7c003e, 0xc100c0, 0xc300c2}, - {0x233, 0x407f007f, 0x3f3f465c, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x7d003e, 0xc100c0, 0xc300c2}, - {0x240, 0x407f007f, 0x25254642, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x7e003f, 0xc100c0, 0xc300c2}, - {0x241, 0x407f007f, 0x26264643, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x7f003f, 0xc100c0, 0xc300c2}, - {0x242, 0x407f007f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x80003f, 0xc100c0, 0xc300c2}, - {0x243, 0x407f007f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x81003f, 0xc100c0, 0xc300c2}, - {0x250, 0x407f007f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x820040, 0xc100c0, 0xc300c2}, - {0x251, 0x407f007f, 0x3e3e465b, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x830040, 0xc100c0, 0xc300c2}, - {0x252, 0x407f007f, 0x27274644, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x840040, 0xc100c0, 0xc300c2}, - {0x253, 0x407f007f, 0x3f3f465c, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x850040, 0xc100c0, 0xc300c2}, - {0x260, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x860041, 0xc100c0, 0xc300c2}, - {0x261, 0x407f007f, 0x4040465d, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x870041, 0xc100c0, 0xc300c2}, - {0x270, 0x407f047f, 0x7f004667, 0x0, 0xb2b17f7f, 0xdfefaaff, 0x890042, 0xc100c0, 0xc300c2}, - {0x280, 0x2c7f043c, 0x2f000048, 0x0, 0xb2b10000, 0xdff080ff, 0x8a0045, 0xc100c0, 0xc300c2}, - {0x281, 0x547f0450, 0x7f300048, 0x0, 0xb2b10000, 0xdff080ff, 0x8a0045, 0xc100c0, 0xc300c2}, - {0x290, 0x7f0404, 0x53000048, 0x0, 0xb2b10000, 0xdfeb80ff, 0x50046, 0xc100c0, 0xc300c2}, - {0x291, 0x7f7f0404, 0x7f540048, 0x0, 0xb2b10000, 0xdfeb80ff, 0x50046, 0xc100c0, 0xc300c2}, - {0x2a0, 0x407f0457, 0x3f3f004b, 0x0, 0xb2b10000, 0xdfeb80ff, 0x2a004d, 0xc100c0, 0xc300c2}, - {0x2a1, 0x407f005a, 0x3b00003c, 0x0, 0xb2b10000, 0xdfeb80ff, 0x2b004d, 0xc100c0, 0xc300c2}, - {0x2a2, 0x406e0055, 0x3c3c0048, 0x0, 0xb2b10000, 0xdfeb80ff, 0x8b004d, 0xc100c0, 0xc300c2}, - {0x2b0, 0x407f0478, 0x47005a49, 0x0, 0xb2b10000, 0xdff0a0ff, 0x2e004e, 0xc100c0, 0xc300c2}, - {0x2b1, 0x407f0478, 0x7f48005d, 0x0, 0xb2b10000, 0xdfeb80ff, 0x2f004e, 0xc100c0, 0xc300c2}, - {0x2c0, 0x407f0455, 0x7f250048, 0x0, 0xb2b10000, 0xdfeb80ff, 0x30004f, 0xc100c0, 0xc300c2}, - {0x2c1, 0x6464044f, 0x24230048, 0x0, 0xb2b10000, 0xdfeb80ff, 0x30004f, 0xc100c0, 0xc300c2}, - {0x2c2, 0x1b640405, 0x2200003c, 0x0, 0xb2b10000, 0xdfeb80ff, 0x30004f, 0xc100c0, 0xc300c2}, - {0x2d0, 0x6e047f, 0x7f005a48, 0x0, 0xb2b10000, 0xdfe780ff, 0x310050, 0xc100c0, 0xc300c2}, - {0x2d1, 0x7f6e047f, 0x7f006148, 0x0, 0xb2b10000, 0xdfe780ff, 0x310050, 0xc100c0, 0xc300c2}, - {0x2e0, 0x407f047f, 0x7f00465a, 0x0, 0xb2b10000, 0x5febb4ff, 0x290051, 0xc100c0, 0xc300c2}, - {0x2f0, 0x7f047f, 0x7e000454, 0x0, 0xb2b10000, 0x5fefbbff, 0x450052, 0xc100c0, 0xc300c2}, - {0x2f1, 0x7f7f045b, 0x7e000e54, 0x0, 0xb2b10000, 0x5fefbbff, 0x450052, 0xc100c0, 0xc300c2}, - {0x300, 0x327f0454, 0x30000448, 0x0, 0xb2b10000, 0xdfeb80ff, 0x460053, 0xc100c0, 0xc300c2}, - {0x301, 0x327f0453, 0x3c310448, 0x0, 0xb2b10000, 0xdfeb80ff, 0x470053, 0xc100c0, 0xc300c2}, - {0x302, 0x5a7f0452, 0x483d0460, 0x0, 0xb2b10000, 0xdfeb80ff, 0x480053, 0xc100c0, 0xc300c2}, - {0x303, 0x287f0451, 0x7f49046c, 0x0, 0xb2b10000, 0xdfeb80ff, 0x490053, 0xc100c0, 0xc300c2}, - {0x310, 0x7f0464, 0x3c3c003c, 0x0, 0xb2b10000, 0x5feb80ff, 0x4a0054, 0xc100c0, 0xc300c2}, - {0x311, 0x7f7f005a, 0x3d3d003d, 0x0, 0xb2b10000, 0x5feb80ff, 0x4a0054, 0xc100c0, 0xc300c2}, - {0x312, 0x7f005a, 0x3e3e003e, 0x0, 0xb2b10000, 0x5feb80ff, 0x4b0054, 0xc100c0, 0xc300c2}, - {0x313, 0x7f7f0464, 0x3f3f003f, 0x0, 0xb2b10000, 0x5feb80ff, 0x4b0054, 0xc100c0, 0xc300c2}, - {0x320, 0x407f0465, 0x7f000054, 0x0, 0xb2b10000, 0xdfec80ff, 0x450055, 0xc100c0, 0xc300c2}}}; + {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_VH = {{ - {0x0, 0x7f047f, 0x3e006a53, 0x0, 0xb2b17f7f, 0xdffc80ff, 0x10000, 0xc100c0, 0xc300c2}, - {0x1, 0x1b380400, 0x3e006a5f, 0x0, 0xb2b17f7f, 0xdffc80ff, 0x10000, 0xc100c0, 0xc300c2}, - {0x2, 0x64500400, 0x3e006a66, 0x0, 0xb2b17f7f, 0xdffc80ff, 0x10000, 0xc100c0, 0xc300c2}, - {0x3, 0x40280400, 0x3e006a72, 0x0, 0xb2b17f7f, 0xdffc80ff, 0x10000, 0xc100c0, 0xc300c2}, - {0x4, 0x7f7f047f, 0x3e006a54, 0x0, 0xb2b17f7f, 0xdffc80ff, 0x10000, 0xc100c0, 0xc300c2}, - {0x5, 0x407f045b, 0x42420050, 0x0, 0xb2b17f7f, 0xdff080ff, 0x110000, 0xc100c0, 0xc300c2}, - {0x6, 0x407f045b, 0x4343004f, 0x0, 0xb2b17f7f, 0xdff080ff, 0x80000, 0xc100c0, 0xc300c2}, - {0x7, 0x407f0464, 0x3e006a4d, 0x0, 0xb2b17f7f, 0xdffc80ff, 0x10000, 0xc100c0, 0xc300c2}, - {0x8, 0x4064047f, 0x4848003c, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x200000, 0xc100c0, 0xc300c2}, - {0x9, 0x4064047f, 0x49490049, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x210000, 0xc100c0, 0xc300c2}, - {0x10, 0x407f047f, 0x3a3a0046, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x170001, 0xc100c0, 0xc300c2}, - {0x11, 0x407f047f, 0x3b3b0045, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x30001, 0xc100c0, 0xc300c2}, - {0x12, 0x407f047f, 0x48480060, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x430001, 0xc100c0, 0xc300c2}, - {0x13, 0x407f047f, 0x48480060, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x20001, 0xc100c0, 0xc300c2}, - {0x14, 0x7f047f, 0x48480048, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x20001, 0xc100c0, 0xc300c2}, - {0x15, 0x7f640451, 0x4848006c, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x20001, 0xc100c0, 0xc300c2}, - {0x16, 0x640451, 0x4848326b, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x20001, 0xc100c0, 0xc300c2}, - {0x17, 0x407f0464, 0x3c3c4659, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x410001, 0xc100c0, 0xc300c2}, - {0x18, 0x407f0464, 0x3d3d465a, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x420001, 0xc100c0, 0xc300c2}, - {0x19, 0x407f0400, 0x39390045, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x5c0001, 0xc100c0, 0xc300c2}, - {0x20, 0x40150464, 0x3c3c6a66, 0x0, 0xb2b17f7f, 0xdaeb80ff, 0x10002, 0xc100c0, 0xc300c2}, - {0x21, 0x3f5f0400, 0x3b006a4e, 0x0, 0xb2b17f7f, 0x5fef80ff, 0x10002, 0xc100c0, 0xc300c2}, - {0x22, 0x540400, 0x3b006a54, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10002, 0xc100c0, 0xc300c2}, - {0x23, 0x7f540400, 0x3b006a56, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10002, 0xc100c0, 0xc300c2}, - {0x24, 0x7f007f, 0x3c3c003c, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x250002, 0xc100c0, 0xc300c2}, - {0x25, 0x7f7f007f, 0x3c3c003c, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x260002, 0xc100c0, 0xc300c2}, - {0x26, 0x4046047f, 0x3c3c003c, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x270002, 0xc100c0, 0xc300c2}, - {0x27, 0x407f047f, 0x40400040, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x280002, 0xc100c0, 0xc300c2}, - {0x30, 0x407f047f, 0x3b3b0047, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x160003, 0xc100c0, 0xc300c2}, - {0x31, 0x407f047f, 0x3a3a0052, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0xb0003, 0xc100c0, 0xc300c2}, - {0x32, 0x407f047f, 0x43430048, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0xc0003, 0xc100c0, 0xc300c2}, - {0x33, 0x407f047f, 0x48480054, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x1e0003, 0xc100c0, 0xc300c2}, - {0x34, 0x407f047f, 0x39390051, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x290003, 0xc100c0, 0xc300c2}, - {0x40, 0x407f0450, 0x24240030, 0x0, 0xb2b17f7f, 0xdfe680ff, 0xd0004, 0xc100c0, 0xc300c2}, - {0x41, 0x407f0450, 0x25250031, 0x0, 0xb2b17f7f, 0xdfe6a5ff, 0xd0004, 0xc100c0, 0xc300c2}, - {0x42, 0x407f0450, 0x26260032, 0x0, 0xb2b17f7f, 0xdfe6aaff, 0xd0004, 0xc100c0, 0xc300c2}, - {0x43, 0x407f047f, 0x3b3b0047, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x150004, 0xc100c0, 0xc300c2}, - {0x44, 0x407f047f, 0x48480060, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x430004, 0xc100c0, 0xc300c2}, - {0x45, 0x407f047f, 0x48480054, 0x0, 0xb2b17f7f, 0xdfebacff, 0x1e0004, 0xc100c0, 0xc300c2}, - {0x50, 0x4064047f, 0x3c3c0060, 0x0, 0xb2b17f7f, 0x5ff380ff, 0x40006, 0xc100c0, 0xc300c2}, - {0x51, 0x4064047f, 0x3c3c0078, 0x0, 0xb2b17f7f, 0x5ff580ff, 0x50006, 0xc100c0, 0xc300c2}, - {0x52, 0x4053047f, 0x3c3c006c, 0x0, 0xb2b17f7f, 0x5170bebf, 0x220006, 0xc100c0, 0xc300c2}, - {0x53, 0x406c047f, 0x3535006c, 0x0, 0xb2b17f7f, 0x5ff580ff, 0x50006, 0xc100c0, 0xc300c2}, - {0x54, 0x406c047f, 0x35350054, 0x0, 0xb2b17f7f, 0x5ff380ff, 0x40006, 0xc100c0, 0xc300c2}, - {0x55, 0x406c047f, 0x3636006d, 0x0, 0xb2b17f7f, 0x5ff580ff, 0x50006, 0xc100c0, 0xc300c2}, - {0x56, 0x406c047f, 0x36360055, 0x0, 0xb2b17f7f, 0x5ff380ff, 0x40006, 0xc100c0, 0xc300c2}, - {0x57, 0x4064047f, 0x3030006c, 0x0, 0xb2b17f7f, 0xdff580ff, 0x50006, 0xc100c0, 0xc300c2}, - {0x58, 0x4053047f, 0x30300060, 0x0, 0xb2b17f7f, 0xd170bebf, 0x220006, 0xc100c0, 0xc300c2}, - {0x60, 0x407f047f, 0x40400059, 0x0, 0xb2b17f7f, 0xdfef80ff, 0x1a0007, 0xc100c0, 0xc300c2}, - {0x61, 0x407f047f, 0x1f1f0048, 0x0, 0xb2b10404, 0xdfefc0ff, 0x450007, 0xc100c0, 0xc300c2}, - {0x62, 0x407f0402, 0x22226a22, 0x0, 0xb2b10000, 0xdfe380ff, 0x130007, 0xc100c0, 0xc300c2}, - {0x63, 0x407f0402, 0x23236a23, 0x0, 0xb2b10000, 0xdfe380ff, 0x140007, 0xc100c0, 0xc300c2}, - {0x64, 0x407f0402, 0x26260032, 0x0, 0xb2b10000, 0xdfe580ff, 0x1b0007, 0xc100c0, 0xc300c2}, - {0x65, 0x407f0402, 0x3c3c0448, 0x0, 0xb2b10000, 0xdfeb80ff, 0x460007, 0xc100c0, 0xc300c2}, - {0x70, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x60008, 0xc100c0, 0xc300c2}, - {0x71, 0x407f047f, 0x39390054, 0x0, 0xb2b17f7f, 0xdfef80ff, 0x90008, 0xc100c0, 0xc300c2}, - {0x72, 0x407f047f, 0x3a3a0053, 0x0, 0xb2b17f7f, 0xdfe0b0ff, 0xa0008, 0xc100c0, 0xc300c2}, - {0x80, 0x4064047f, 0x2727003c, 0x0, 0xb2b17f7f, 0xdfe680ff, 0xf0009, 0xc100c0, 0xc300c2}, - {0x81, 0x407f047f, 0x2626003e, 0x0, 0xb2b17f7f, 0xdfe680ff, 0xe0009, 0xc100c0, 0xc300c2}, - {0x82, 0x405a047f, 0x27270044, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x180009, 0xc100c0, 0xc300c2}, - {0x83, 0x407f047f, 0x28280040, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x120009, 0xc100c0, 0xc300c2}, - {0x84, 0x407f0465, 0x2525003d, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x230009, 0xc100c0, 0xc300c2}, - {0x85, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x240009, 0xc100c0, 0xc300c2}, - {0x86, 0x407f0400, 0x3d3d465a, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x830009, 0xc100c0, 0xc300c2}, - {0x87, 0x407f0400, 0x2424003c, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x230009, 0xc100c0, 0xc300c2}, - {0x88, 0x407f0400, 0x29290041, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x120009, 0xc100c0, 0xc300c2}, - {0x90, 0x407f047f, 0x3d3d6a4a, 0x0, 0xb2b17f7f, 0xdfef80ff, 0x7000a, 0xc100c0, 0xc300c2}, - {0x91, 0x407f047f, 0x3c3c0060, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x39000a, 0xc100c0, 0xc300c2}, - {0xa0, 0x407f0464, 0x3c3c0048, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x10000b, 0xc100c0, 0xc300c2}, - {0xa1, 0x407f0464, 0x3d3d0055, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x44000b, 0xc100c0, 0xc300c2}, - {0xb0, 0x407f0402, 0x26260026, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x1b000c, 0xc100c0, 0xc300c2}, - {0xb1, 0x407f047f, 0x27270027, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x19000c, 0xc100c0, 0xc300c2}, - {0xb2, 0x407f0464, 0x28280040, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x2a000c, 0xc100c0, 0xc300c2}, - {0xb3, 0x407f0464, 0x29290041, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x2b000c, 0xc100c0, 0xc300c2}, - {0xb4, 0x407f0464, 0x2a2a0042, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x2c000c, 0xc100c0, 0xc300c2}, - {0xb5, 0x407f0464, 0x2c2c0044, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x38000c, 0xc100c0, 0xc300c2}, - {0xc0, 0x407f047f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0xdff080ff, 0x3f000d, 0xc100c0, 0xc300c2}, - {0xd0, 0x407f0450, 0x24240024, 0x0, 0xb2b17f7f, 0x5fe380ff, 0x13000e, 0xc100c0, 0xc300c2}, - {0xd1, 0x407f0450, 0x25250025, 0x0, 0xb2b17f7f, 0x5fe380ff, 0x14000e, 0xc100c0, 0xc300c2}, - {0xd2, 0x407f0450, 0x3d3d4659, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x1f000e, 0xc100c0, 0xc300c2}, - {0xd3, 0x407f0450, 0x3e3e465a, 0x0, 0xb2b17f7f, 0xdfe6aaff, 0x1f000e, 0xc100c0, 0xc300c2}, - {0xe0, 0x407f047f, 0x3b3b4659, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x5e0011, 0xc100c0, 0xc300c2}, - {0xf0, 0x407f0464, 0x2d2d0045, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x2d0012, 0xc100c0, 0xc300c2}, - {0xf1, 0x407f047f, 0x2e2e0046, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x2e0012, 0xc100c0, 0xc300c2}, - {0xf2, 0x407f0432, 0x2f2f464c, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x2f0012, 0xc100c0, 0xc300c2}, - {0xf3, 0x407f0432, 0x3030464d, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x300012, 0xc100c0, 0xc300c2}, - {0xf4, 0x407f0432, 0x2424003c, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x310012, 0xc100c0, 0xc300c2}, - {0xf5, 0x407f0432, 0x2525003d, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x320012, 0xc100c0, 0xc300c2}, - {0x100, 0x407f047f, 0x30300048, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x530013, 0xc100c0, 0xc300c2}, - {0x110, 0x2c7f047f, 0x24240060, 0x0, 0xb2b10000, 0x1fc080ff, 0x5d0014, 0xc100c0, 0xc300c2}, - {0x111, 0x2c41047f, 0x24240454, 0x0, 0xb2b10000, 0x1fc080ff, 0x40014, 0xc100c0, 0xc300c2}, - {0x112, 0x407f047f, 0x2b2b0060, 0x0, 0xb2b10000, 0x1fc080ff, 0x5d0014, 0xc100c0, 0xc300c2}, - {0x113, 0x4041047f, 0x2b2b0454, 0x0, 0xb2b10000, 0x1fc080ff, 0x40014, 0xc100c0, 0xc300c2}, - {0x114, 0x547f047f, 0x30300060, 0x0, 0xb2b10000, 0x1fc080ff, 0x5d0014, 0xc100c0, 0xc300c2}, - {0x115, 0x5441047f, 0x30300454, 0x0, 0xb2b10000, 0x1fc080ff, 0x40014, 0xc100c0, 0xc300c2}, - {0x120, 0x407f007e, 0x7f006a53, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x10015, 0xc100c0, 0xc300c2}, - {0x130, 0x407f0464, 0x3d3d004e, 0x0, 0xb2b17f7f, 0xdfe080ff, 0xb0016, 0xc100c0, 0xc300c2}, - {0x131, 0x407f0464, 0x3e3e0045, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x160016, 0xc100c0, 0xc300c2}, - {0x132, 0x407f0464, 0x3f3f0046, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x160016, 0xc100c0, 0xc300c2}, - {0x133, 0x407f0464, 0x3f3f0050, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x2b0016, 0xc100c0, 0xc300c2}, - {0x134, 0x3c7f044b, 0x2121002d, 0x0, 0xb2b17f7f, 0xdfe0a4ff, 0xd0016, 0xc100c0, 0xc300c2}, - {0x135, 0x407f044b, 0x2222002d, 0x0, 0xb2b17f7f, 0xdfe0a0ff, 0xd0016, 0xc100c0, 0xc300c2}, - {0x136, 0x3c7f044b, 0x2323002d, 0x0, 0xb2b17f7f, 0xdfe0a7ff, 0xd0016, 0xc100c0, 0xc300c2}, - {0x137, 0x407f044b, 0x2424002d, 0x0, 0xb2b17f7f, 0xdfe0aaff, 0xd0016, 0xc100c0, 0xc300c2}, - {0x138, 0x407f044b, 0x2525002d, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x360016, 0xc100c0, 0xc300c2}, - {0x140, 0x407f047f, 0x3e3e0073, 0x0, 0xb2b17f7f, 0xceab80ff, 0x540018, 0xc100c0, 0xc300c2}, - {0x150, 0x2c7f047f, 0x24240030, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x1d0019, 0xc100c0, 0xc300c2}, - {0x151, 0x407f047f, 0x25250031, 0x0, 0xb2b17f7f, 0x5ffd80ff, 0x1c0019, 0xc100c0, 0xc300c2}, - {0x152, 0x547f047f, 0x24246e31, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x1d0019, 0xc100c0, 0xc300c2}, - {0x160, 0x407f0400, 0x7f000054, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x37001b, 0xc100c0, 0xc300c2}, - {0x170, 0x407f047f, 0x7f004659, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x5f001c, 0xc100c0, 0xc300c2}, - {0x180, 0x407f047f, 0x3c3c0048, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x53001e, 0xc100c0, 0xc300c2}, - {0x190, 0x407f0452, 0x3c3c465a, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x50001f, 0xc100c0, 0xc300c2}, - {0x1a0, 0x407f047f, 0x3d3d4666, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x5a0020, 0xc100c0, 0xc300c2}, - {0x1b0, 0x407f047f, 0x2424003c, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x540021, 0xc100c0, 0xc300c2}, - {0x1c0, 0x407f047f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x3e0022, 0xc100c0, 0xc300c2}, - {0x1c1, 0x407f047f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0xdff080ff, 0x3f0022, 0xc100c0, 0xc300c2}, - {0x1c2, 0x407f047f, 0x3e3e006e, 0x0, 0xb2b17f7f, 0xd16db477, 0x220022, 0xc100c0, 0xc300c2}, - {0x1c3, 0x406b047f, 0x3e3e0075, 0x0, 0xb2b17f7f, 0xdff580ff, 0x50022, 0xc100c0, 0xc300c2}, - {0x1c4, 0x4041047f, 0x3e3e005d, 0x0, 0xb2b17f7f, 0xdff380ff, 0x40022, 0xc100c0, 0xc300c2}, - {0x1d0, 0x406e0463, 0x2424003c, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x510023, 0xc100c0, 0xc300c2}, - {0x1d1, 0x406e0463, 0x2525003d, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x520023, 0xc100c0, 0xc300c2}, - {0x1e0, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x400024, 0xc100c0, 0xc300c2}, - {0x1e1, 0x407f047f, 0x3c3c6a50, 0x0, 0xb2b17f7f, 0xc02080c0, 0x10024, 0xc100c0, 0xc300c2}, - {0x1e2, 0x407f047f, 0x3c3c0048, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x150024, 0xc100c0, 0xc300c2}, - {0x1e3, 0x4037047f, 0x3c3c6a64, 0x0, 0xb2b17f7f, 0xc9a080e3, 0x10024, 0xc100c0, 0xc300c2}, - {0x1f0, 0x407f047f, 0x3c3c0060, 0x0, 0xb2b17f7f, 0x5fe080ff, 0x4c0026, 0xc100c0, 0xc300c2}, - {0x200, 0x407f047f, 0x3c3c0448, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x460027, 0xc100c0, 0xc300c2}, - {0x201, 0x407f0451, 0x3d3d0457, 0x0, 0xb2b17f7f, 0x5fcb80ff, 0x470027, 0xc100c0, 0xc300c2}, - {0x202, 0x407f047f, 0x4040044c, 0x0, 0xb2b17f7f, 0x5fcb80ff, 0x480027, 0xc100c0, 0xc300c2}, - {0x203, 0x407f0451, 0x4242045a, 0x0, 0xb2b17f7f, 0x5fcb80ff, 0x490027, 0xc100c0, 0xc300c2}, - {0x204, 0x407f044b, 0x4343044f, 0x0, 0xb2b17f7f, 0x5feb80ff, 0x4a0027, 0xc100c0, 0xc300c2}, - {0x205, 0x407f047f, 0x41410459, 0x0, 0xb2b17f7f, 0x5feb80ff, 0x4b0027, 0xc100c0, 0xc300c2}, - {0x206, 0x4075047f, 0x4444005c, 0x0, 0xb2b17f7f, 0x5fe080ff, 0x4d0027, 0xc100c0, 0xc300c2}, - {0x210, 0x407f047f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0x5fe080ff, 0x4e0028, 0xc100c0, 0xc300c2}, - {0x211, 0x407f047f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x4f0028, 0xc100c0, 0xc300c2}, - {0x220, 0x407f047f, 0x3d3d0049, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x5b002c, 0xc100c0, 0xc300c2}, - {0x221, 0x407f0419, 0x3e3e465b, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x84002c, 0xc100c0, 0xc300c2}, - {0x222, 0x407f0419, 0x3f3f465c, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x85002c, 0xc100c0, 0xc300c2}, - {0x230, 0x407f0440, 0x3d3d3c56, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x820031, 0xc100c0, 0xc300c2}, - {0x240, 0x407f047f, 0x7f000048, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x61003a, 0xc100c0, 0xc300c2}, - {0x250, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x62003b, 0xc100c0, 0xc300c2}, - {0x251, 0x407f047f, 0x3e3e465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x63003b, 0xc100c0, 0xc300c2}, - {0x252, 0x407f047f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x64003b, 0xc100c0, 0xc300c2}, - {0x253, 0x407f047f, 0x3f3f005b, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x65003b, 0xc100c0, 0xc300c2}, - {0x260, 0x407f047f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x66003c, 0xc100c0, 0xc300c2}, - {0x261, 0x407f047f, 0x3f3f465c, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x67003c, 0xc100c0, 0xc300c2}, - {0x270, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x68003d, 0xc100c0, 0xc300c2}, - {0x271, 0x407f047f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x69003d, 0xc100c0, 0xc300c2}, - {0x272, 0x407f047f, 0x37374653, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x6a003d, 0xc100c0, 0xc300c2}, - {0x273, 0x407f047f, 0x38384655, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x6b003d, 0xc100c0, 0xc300c2}, - {0x274, 0x407f047f, 0x39394656, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x6c003d, 0xc100c0, 0xc300c2}, - {0x275, 0x407f047f, 0x3b3b4658, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x6d003d, 0xc100c0, 0xc300c2}, - {0x276, 0x407f047f, 0x3a3a4657, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x6e003d, 0xc100c0, 0xc300c2}, - {0x277, 0x407f047f, 0x4040465d, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x6f003d, 0xc100c0, 0xc300c2}, - {0x278, 0x407f047f, 0x4141004d, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x70003d, 0xc100c0, 0xc300c2}, - {0x280, 0x407f047f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x71003e, 0xc100c0, 0xc300c2}, - {0x281, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x72003e, 0xc100c0, 0xc300c2}, - {0x282, 0x407f047f, 0x3e3e465b, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x73003e, 0xc100c0, 0xc300c2}, - {0x283, 0x407f047f, 0x3f3f465c, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x74003e, 0xc100c0, 0xc300c2}, - {0x290, 0x407f047f, 0x25254642, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x75003f, 0xc100c0, 0xc300c2}, - {0x291, 0x407f047f, 0x26264643, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x76003f, 0xc100c0, 0xc300c2}, - {0x292, 0x407f047f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x77003f, 0xc100c0, 0xc300c2}, - {0x293, 0x407f047f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x78003f, 0xc100c0, 0xc300c2}, - {0x2a0, 0x407f047f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x790040, 0xc100c0, 0xc300c2}, - {0x2a1, 0x407f047f, 0x3e3e465b, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x7a0040, 0xc100c0, 0xc300c2}, - {0x2a2, 0x407f047f, 0x27274644, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x7b0040, 0xc100c0, 0xc300c2}, - {0x2a3, 0x407f047f, 0x3f3f465c, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x7c0040, 0xc100c0, 0xc300c2}, - {0x2b0, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x7d0041, 0xc100c0, 0xc300c2}, - {0x2b1, 0x407f047f, 0x4040465d, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x7e0041, 0xc100c0, 0xc300c2}, - {0x2c0, 0x407f047f, 0x7f004667, 0x0, 0xb2b17f7f, 0xdfefaaff, 0x7f0042, 0xc100c0, 0xc300c2}, - {0x2d0, 0x2c7f043c, 0x2f000048, 0x0, 0xb2b10000, 0xdff080ff, 0x800045, 0xc100c0, 0xc300c2}, - {0x2d1, 0x547f0450, 0x7f300048, 0x0, 0xb2b10000, 0xdff080ff, 0x800045, 0xc100c0, 0xc300c2}, - {0x2e0, 0x7f0404, 0x53000048, 0x0, 0xb2b10000, 0xdfeb80ff, 0x50046, 0xc100c0, 0xc300c2}, - {0x2e1, 0x7f7f0404, 0x7f540048, 0x0, 0xb2b10000, 0xdfeb80ff, 0x50046, 0xc100c0, 0xc300c2}, - {0x2f0, 0x407f0454, 0x3f3f004b, 0x0, 0xb2b10000, 0x5feb80ff, 0x34004d, 0xc100c0, 0xc300c2}, - {0x2f1, 0x407f005a, 0x3b00003c, 0x0, 0xb2b10000, 0x5feb80ff, 0x35004d, 0xc100c0, 0xc300c2}, - {0x2f2, 0x406e0055, 0x3c3c0048, 0x0, 0xb2b10000, 0x5feb80ff, 0x81004d, 0xc100c0, 0xc300c2}, - {0x300, 0x407f0464, 0x47005a49, 0x0, 0xb2b10000, 0xdff0a0ff, 0x3a004e, 0xc100c0, 0xc300c2}, - {0x301, 0x407f0464, 0x7f48005d, 0x0, 0xb2b10000, 0xdfeb80ff, 0x3b004e, 0xc100c0, 0xc300c2}, - {0x310, 0x407f0455, 0x7f250048, 0x0, 0xb2b10000, 0xdfeb80ff, 0x3c004f, 0xc100c0, 0xc300c2}, - {0x311, 0x6464044f, 0x24230048, 0x0, 0xb2b10000, 0xdfeb80ff, 0x3c004f, 0xc100c0, 0xc300c2}, - {0x312, 0x1b640405, 0x2200003c, 0x0, 0xb2b10000, 0xdfeb80ff, 0x3c004f, 0xc100c0, 0xc300c2}, - {0x320, 0x6e047f, 0x7f005a48, 0x0, 0xb2b10000, 0xdfe780ff, 0x3d0050, 0xc100c0, 0xc300c2}, - {0x321, 0x7f6e047f, 0x7f006148, 0x0, 0xb2b10000, 0xdfe780ff, 0x3d0050, 0xc100c0, 0xc300c2}, - {0x330, 0x407f047f, 0x7f00465a, 0x0, 0xb2b10000, 0x5febb4ff, 0x330051, 0xc100c0, 0xc300c2}, - {0x340, 0x407f0465, 0x7f000460, 0x0, 0xb2b10000, 0xdfefbbff, 0x550052, 0xc100c0, 0xc300c2}, - {0x350, 0x327f0464, 0x30000448, 0x0, 0xb2b10000, 0xdff080ff, 0x560053, 0xc100c0, 0xc300c2}, - {0x351, 0x327f0464, 0x3c310448, 0x0, 0xb2b10000, 0xdff080ff, 0x570053, 0xc100c0, 0xc300c2}, - {0x352, 0x5a7f0464, 0x483d0460, 0x0, 0xb2b10000, 0xdff080ff, 0x580053, 0xc100c0, 0xc300c2}, - {0x353, 0x287f0464, 0x7f49046c, 0x0, 0xb2b10000, 0xdfeb80ff, 0x590053, 0xc100c0, 0xc300c2}, - {0x360, 0x407f0465, 0x7f000454, 0x0, 0xb2b10000, 0xdfec80ff, 0x550055, 0xc100c0, 0xc300c2}}}; + {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}, + {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_VH = {{ - {0x0, 0x40150464, 0x3c3c6a66, 0x0, 0xb2b17f7f, 0xdaeb80ff, 0x10002, 0xc100c0, 0xc300c2}, - {0x1, 0x3f5f0400, 0x3b006a4e, 0x0, 0xb2b17f7f, 0x5fef80ff, 0x10002, 0xc100c0, 0xc300c2}, - {0x2, 0x540400, 0x3b006a54, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10002, 0xc100c0, 0xc300c2}, - {0x3, 0x7f540400, 0x3b006a56, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10002, 0xc100c0, 0xc300c2}, - {0x10, 0x407f047f, 0x3b3b0047, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0xd0003, 0xc100c0, 0xc300c2}, - {0x11, 0x407f047f, 0x3a3a0052, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x60003, 0xc100c0, 0xc300c2}, - {0x12, 0x407f047f, 0x43430048, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x70003, 0xc100c0, 0xc300c2}, - {0x13, 0x407f047f, 0x48480054, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x100003, 0xc100c0, 0xc300c2}, - {0x14, 0x407f047f, 0x39390051, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x110003, 0xc100c0, 0xc300c2}, - {0x20, 0x407f0450, 0x24240030, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x80004, 0xc100c0, 0xc300c2}, - {0x21, 0x407f0450, 0x25250031, 0x0, 0xb2b17f7f, 0xdfe6a5ff, 0x80004, 0xc100c0, 0xc300c2}, - {0x22, 0x407f0450, 0x26260032, 0x0, 0xb2b17f7f, 0xdfe6aaff, 0x80004, 0xc100c0, 0xc300c2}, - {0x23, 0x407f047f, 0x3b3b0047, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0xc0004, 0xc100c0, 0xc300c2}, - {0x24, 0x407f047f, 0x48480060, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x1b0004, 0xc100c0, 0xc300c2}, - {0x25, 0x407f047f, 0x48480054, 0x0, 0xb2b17f7f, 0xdfebacff, 0x100004, 0xc100c0, 0xc300c2}, - {0x30, 0x4064047f, 0x3c3c0060, 0x0, 0xb2b17f7f, 0xdff380ff, 0x5a0006, 0xc100c0, 0xc300c2}, - {0x31, 0x4064047f, 0x3c3c0078, 0x0, 0xb2b17f7f, 0xdff580ff, 0x4d0006, 0xc100c0, 0xc300c2}, - {0x32, 0x4053047f, 0x3c3c006c, 0x0, 0xb2b17f7f, 0xd170bebf, 0x5b0006, 0xc100c0, 0xc300c2}, - {0x40, 0x407f047f, 0x40400059, 0x0, 0xb2b17f7f, 0xdfef80ff, 0xe0007, 0xc100c0, 0xc300c2}, - {0x41, 0x407f047f, 0x1f1f0048, 0x0, 0xb2b10404, 0xdfefc0ff, 0x1d0007, 0xc100c0, 0xc300c2}, - {0x42, 0x407f0402, 0x22226a22, 0x0, 0xb2b10000, 0xdfe380ff, 0xa0007, 0xc100c0, 0xc300c2}, - {0x43, 0x407f0402, 0x23236a23, 0x0, 0xb2b10000, 0xdfe380ff, 0xb0007, 0xc100c0, 0xc300c2}, - {0x44, 0x407f0402, 0x26260032, 0x0, 0xb2b10000, 0xdfe580ff, 0xf0007, 0xc100c0, 0xc300c2}, - {0x45, 0x407f0402, 0x3c3c0448, 0x0, 0xb2b10000, 0xdfeb80ff, 0x1e0007, 0xc100c0, 0xc300c2}, - {0x50, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x20008, 0xc100c0, 0xc300c2}, - {0x51, 0x407f047f, 0x39390054, 0x0, 0xb2b17f7f, 0xdfef80ff, 0x40008, 0xc100c0, 0xc300c2}, - {0x52, 0x407f047f, 0x3a3a0053, 0x0, 0xb2b17f7f, 0xdfe0b0ff, 0x50008, 0xc100c0, 0xc300c2}, - {0x60, 0x4064047f, 0x2727003c, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x4f0009, 0xc100c0, 0xc300c2}, - {0x61, 0x407f047f, 0x2626003e, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x500009, 0xc100c0, 0xc300c2}, - {0x62, 0x405a047f, 0x27270044, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x510009, 0xc100c0, 0xc300c2}, - {0x63, 0x407f047f, 0x28280040, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x520009, 0xc100c0, 0xc300c2}, - {0x64, 0x407f0465, 0x2525003d, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x530009, 0xc100c0, 0xc300c2}, - {0x65, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x540009, 0xc100c0, 0xc300c2}, - {0x66, 0x407f0400, 0x3d3d465a, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x550009, 0xc100c0, 0xc300c2}, - {0x67, 0x407f0400, 0x2424003c, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x530009, 0xc100c0, 0xc300c2}, - {0x68, 0x407f0400, 0x29290041, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x520009, 0xc100c0, 0xc300c2}, - {0x70, 0x407f047f, 0x3d3d6a4a, 0x0, 0xb2b17f7f, 0xdfef80ff, 0x3000a, 0xc100c0, 0xc300c2}, - {0x71, 0x407f047f, 0x3c3c0060, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x15000a, 0xc100c0, 0xc300c2}, - {0x80, 0x407f047e, 0x3c3c0048, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x9000b, 0xc100c0, 0xc300c2}, - {0x81, 0x407f047e, 0x3d3d0055, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x1c000b, 0xc100c0, 0xc300c2}, - {0x90, 0x407f047f, 0x3b3b4659, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x6e0011, 0xc100c0, 0xc300c2}, - {0xa0, 0x407f047f, 0x30300048, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x280013, 0xc100c0, 0xc300c2}, - {0xb0, 0x407f007e, 0x7f006a53, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x10015, 0xc100c0, 0xc300c2}, - {0xc0, 0x407f047f, 0x7f004659, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x6f001c, 0xc100c0, 0xc300c2}, - {0xd0, 0x407f047f, 0x3d3d4666, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x2b0020, 0xc100c0, 0xc300c2}, - {0xe0, 0x407f047f, 0x2424003c, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x290021, 0xc100c0, 0xc300c2}, - {0xf0, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x1a0024, 0xc100c0, 0xc300c2}, - {0xf1, 0x407f047f, 0x3c3c6a50, 0x0, 0xb2b17f7f, 0xc02080c0, 0x10024, 0xc100c0, 0xc300c2}, - {0xf2, 0x407f047f, 0x3c3c0048, 0x0, 0xb2b17f7f, 0x1fc080ff, 0xc0024, 0xc100c0, 0xc300c2}, - {0xf3, 0x4037047d, 0x3c3c6a64, 0x0, 0xb2b17f7f, 0xc9a080e3, 0x10024, 0xc100c0, 0xc300c2}, - {0x100, 0x407f047f, 0x3c3c0060, 0x0, 0xb2b17f7f, 0x5fe080ff, 0x240026, 0xc100c0, 0xc300c2}, - {0x110, 0x407f047f, 0x3c3c0448, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x1e0027, 0xc100c0, 0xc300c2}, - {0x111, 0x407f0451, 0x3d3d0457, 0x0, 0xb2b17f7f, 0x5fcb80ff, 0x1f0027, 0xc100c0, 0xc300c2}, - {0x112, 0x407f047f, 0x4040044c, 0x0, 0xb2b17f7f, 0x5fcb80ff, 0x200027, 0xc100c0, 0xc300c2}, - {0x113, 0x407f0451, 0x4242045a, 0x0, 0xb2b17f7f, 0x5fcb80ff, 0x210027, 0xc100c0, 0xc300c2}, - {0x114, 0x407f044b, 0x4343044f, 0x0, 0xb2b17f7f, 0x5feb80ff, 0x220027, 0xc100c0, 0xc300c2}, - {0x115, 0x407f047f, 0x41410459, 0x0, 0xb2b17f7f, 0x5feb80ff, 0x230027, 0xc100c0, 0xc300c2}, - {0x116, 0x4075047f, 0x4444005c, 0x0, 0xb2b17f7f, 0x5fe080ff, 0x250027, 0xc100c0, 0xc300c2}, - {0x120, 0x407f047f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0x5fe080ff, 0x260028, 0xc100c0, 0xc300c2}, - {0x121, 0x407f047f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x270028, 0xc100c0, 0xc300c2}, - {0x130, 0x407f0440, 0x3d3d3c56, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x4e0031, 0xc100c0, 0xc300c2}, - {0x140, 0x407f047f, 0x7f000048, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x2d003a, 0xc100c0, 0xc300c2}, - {0x150, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x2e003b, 0xc100c0, 0xc300c2}, - {0x151, 0x407f047f, 0x3e3e465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x2f003b, 0xc100c0, 0xc300c2}, - {0x152, 0x407f047f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x30003b, 0xc100c0, 0xc300c2}, - {0x153, 0x407f047f, 0x3f3f005b, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x31003b, 0xc100c0, 0xc300c2}, - {0x160, 0x407f047f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x32003c, 0xc100c0, 0xc300c2}, - {0x161, 0x407f047f, 0x3f3f465c, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x33003c, 0xc100c0, 0xc300c2}, - {0x170, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x34003d, 0xc100c0, 0xc300c2}, - {0x171, 0x407f047f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x35003d, 0xc100c0, 0xc300c2}, - {0x172, 0x407f047f, 0x37374653, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x36003d, 0xc100c0, 0xc300c2}, - {0x173, 0x407f047f, 0x38384655, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x37003d, 0xc100c0, 0xc300c2}, - {0x174, 0x407f047f, 0x39394656, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x38003d, 0xc100c0, 0xc300c2}, - {0x175, 0x407f047f, 0x3b3b4658, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x39003d, 0xc100c0, 0xc300c2}, - {0x176, 0x407f047f, 0x3a3a4657, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x3a003d, 0xc100c0, 0xc300c2}, - {0x177, 0x407f047f, 0x4040465d, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x3b003d, 0xc100c0, 0xc300c2}, - {0x178, 0x407f047f, 0x4141004d, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x3c003d, 0xc100c0, 0xc300c2}, - {0x180, 0x407f047f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x3d003e, 0xc100c0, 0xc300c2}, - {0x181, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x3e003e, 0xc100c0, 0xc300c2}, - {0x182, 0x407f047f, 0x3e3e465b, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x3f003e, 0xc100c0, 0xc300c2}, - {0x183, 0x407f047f, 0x3f3f465c, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x40003e, 0xc100c0, 0xc300c2}, - {0x190, 0x407f047f, 0x25254642, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x41003f, 0xc100c0, 0xc300c2}, - {0x191, 0x407f047f, 0x26264643, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x42003f, 0xc100c0, 0xc300c2}, - {0x192, 0x407f047f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x43003f, 0xc100c0, 0xc300c2}, - {0x193, 0x407f047f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x44003f, 0xc100c0, 0xc300c2}, - {0x1a0, 0x407f047f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x450040, 0xc100c0, 0xc300c2}, - {0x1a1, 0x407f047f, 0x3e3e465b, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x460040, 0xc100c0, 0xc300c2}, - {0x1a2, 0x407f047f, 0x27274644, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x470040, 0xc100c0, 0xc300c2}, - {0x1a3, 0x407f047f, 0x3f3f465c, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x480040, 0xc100c0, 0xc300c2}, - {0x1b0, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x490041, 0xc100c0, 0xc300c2}, - {0x1b1, 0x407f047f, 0x4040465d, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x4a0041, 0xc100c0, 0xc300c2}, - {0x1c0, 0x407f047f, 0x7f004667, 0x0, 0xb2b17f7f, 0xdfefaaff, 0x4b0042, 0xc100c0, 0xc300c2}, - {0x1d0, 0x407f0000, 0x7f000048, 0x0, 0xb2b10000, 0x1fc080ff, 0x4d0046, 0xc100c0, 0xc300c2}, - {0x1d1, 0x407f0000, 0x7f000048, 0x0, 0xb2b10000, 0x1fc080ff, 0x4d0046, 0xc100c0, 0xc300c2}, - {0x1e0, 0x407f0453, 0x603c003c, 0x0, 0xb2b10000, 0xdfed80ff, 0x6d0048, 0xc100c0, 0xc300c2}, - {0x1f0, 0x407f0401, 0x7f00003c, 0x0, 0xb2b10000, 0xdfeb80ff, 0x5c0049, 0xc100c0, 0xc300c2}, - {0x200, 0x367f0400, 0x3e3e004a, 0x0, 0xb2b10000, 0xdfe380ff, 0x56004a, 0xc100c0, 0xc300c2}, - {0x201, 0x407f0400, 0x5454006c, 0x0, 0xb2b10000, 0xdfe680ff, 0x57004a, 0xc100c0, 0xc300c2}, - {0x202, 0x4a7f0000, 0x28280040, 0x0, 0xb2b10000, 0xdfe680ff, 0x58004a, 0xc100c0, 0xc300c2}, - {0x203, 0x407f0400, 0x4c4c0058, 0x0, 0xb2b10000, 0xdfeb80ff, 0x59004a, 0xc100c0, 0xc300c2}, - {0x210, 0x7f0000, 0x24240024, 0x0, 0xb2b10000, 0xdfeb80ff, 0x5d004b, 0xc100c0, 0xc300c2}, - {0x211, 0x7f7f0400, 0x25250025, 0x0, 0xb2b10000, 0xdfeb80ff, 0x5e004b, 0xc100c0, 0xc300c2}, - {0x212, 0x7f0000, 0x26260026, 0x0, 0xb2b10000, 0xdfeb80ff, 0x5f004b, 0xc100c0, 0xc300c2}, - {0x213, 0x7f7f0000, 0x27270027, 0x0, 0xb2b10000, 0xdfeb80ff, 0x60004b, 0xc100c0, 0xc300c2}, - {0x214, 0x7f0400, 0x28280028, 0x0, 0xb2b10000, 0xdfeb80ff, 0x61004b, 0xc100c0, 0xc300c2}, - {0x215, 0x7f7f0000, 0x29290029, 0x0, 0xb2b10000, 0xdfeb80ff, 0x62004b, 0xc100c0, 0xc300c2}, - {0x216, 0x7f0000, 0x2a2a002a, 0x0, 0xb2b10000, 0xdfeb80ff, 0x63004b, 0xc100c0, 0xc300c2}, - {0x217, 0x7f7f0400, 0x2b2b002b, 0x0, 0xb2b10000, 0xdfeb80ff, 0x64004b, 0xc100c0, 0xc300c2}, - {0x218, 0x7f0000, 0x2c2c002c, 0x0, 0xb2b10000, 0xdfeb80ff, 0x65004b, 0xc100c0, 0xc300c2}, - {0x219, 0x7f7f0000, 0x2d2d002d, 0x0, 0xb2b10000, 0xdfeb80ff, 0x66004b, 0xc100c0, 0xc300c2}, - {0x21a, 0x7f0400, 0x2e2e002e, 0x0, 0xb2b10000, 0xdfeb80ff, 0x67004b, 0xc100c0, 0xc300c2}, - {0x21b, 0x7f7f0000, 0x2f2f002f, 0x0, 0xb2b10000, 0xdfeb80ff, 0x68004b, 0xc100c0, 0xc300c2}, - {0x21c, 0x7f0000, 0x30300030, 0x0, 0xb2b10000, 0xdfeb80ff, 0x69004b, 0xc100c0, 0xc300c2}, - {0x21d, 0x7f7f0400, 0x31310031, 0x0, 0xb2b10000, 0xdfeb80ff, 0x6a004b, 0xc100c0, 0xc300c2}, - {0x21e, 0x7f0000, 0x32320032, 0x0, 0xb2b10000, 0xdfeb80ff, 0x6b004b, 0xc100c0, 0xc300c2}, - {0x21f, 0x7f7f0000, 0x33330033, 0x0, 0xb2b10000, 0xdfeb80ff, 0x6c004b, 0xc100c0, 0xc300c2}, - {0x220, 0x7f0400, 0x24240029, 0x0, 0xb2b10000, 0xdfeb80ff, 0x5d004c, 0xc100c0, 0xc300c2}, - {0x221, 0x7f7f0000, 0x2525002a, 0x0, 0xb2b10000, 0xdfeb80ff, 0x5e004c, 0xc100c0, 0xc300c2}, - {0x222, 0x7f0000, 0x2626002b, 0x0, 0xb2b10000, 0xdfeb80ff, 0x5f004c, 0xc100c0, 0xc300c2}, - {0x223, 0x7f7f0400, 0x2727002c, 0x0, 0xb2b10000, 0xdfeb80ff, 0x60004c, 0xc100c0, 0xc300c2}, - {0x224, 0x7f0000, 0x2828002d, 0x0, 0xb2b10000, 0xdfeb80ff, 0x61004c, 0xc100c0, 0xc300c2}, - {0x225, 0x7f7f0000, 0x2929002e, 0x0, 0xb2b10000, 0xdfeb80ff, 0x62004c, 0xc100c0, 0xc300c2}, - {0x226, 0x7f0400, 0x2a2a002f, 0x0, 0xb2b10000, 0xdfeb80ff, 0x63004c, 0xc100c0, 0xc300c2}, - {0x227, 0x7f7f0000, 0x2b2b0030, 0x0, 0xb2b10000, 0xdfeb80ff, 0x64004c, 0xc100c0, 0xc300c2}, - {0x228, 0x7f0000, 0x2c2c0031, 0x0, 0xb2b10000, 0xdfeb80ff, 0x65004c, 0xc100c0, 0xc300c2}, - {0x229, 0x7f7f0400, 0x2d2d0032, 0x0, 0xb2b10000, 0xdfeb80ff, 0x66004c, 0xc100c0, 0xc300c2}, - {0x22a, 0x7f0000, 0x2e2e0033, 0x0, 0xb2b10000, 0xdfeb80ff, 0x67004c, 0xc100c0, 0xc300c2}, - {0x22b, 0x7f7f0000, 0x2f2f0034, 0x0, 0xb2b10000, 0xdfeb80ff, 0x68004c, 0xc100c0, 0xc300c2}, - {0x22c, 0x7f0400, 0x30300035, 0x0, 0xb2b10000, 0xdfeb80ff, 0x69004c, 0xc100c0, 0xc300c2}, - {0x22d, 0x7f7f0000, 0x31310036, 0x0, 0xb2b10000, 0xdfeb80ff, 0x6a004c, 0xc100c0, 0xc300c2}, - {0x22e, 0x7f0000, 0x32320037, 0x0, 0xb2b10000, 0xdfeb80ff, 0x6b004c, 0xc100c0, 0xc300c2}, - {0x22f, 0x7f7f0400, 0x33330038, 0x0, 0xb2b10000, 0xdfeb80ff, 0x6c004c, 0xc100c0, 0xc300c2}, - {0x230, 0x407f0454, 0x3f3f004b, 0x0, 0xb2b10000, 0x5feb80ff, 0x13004d, 0xc100c0, 0xc300c2}, - {0x231, 0x407f005a, 0x3b00003c, 0x0, 0xb2b10000, 0x5feb80ff, 0x14004d, 0xc100c0, 0xc300c2}, - {0x232, 0x406e0055, 0x3c3c0048, 0x0, 0xb2b10000, 0x5feb80ff, 0x4c004d, 0xc100c0, 0xc300c2}, - {0x240, 0x407f0478, 0x47005a49, 0x0, 0xb2b10000, 0xdff0a0ff, 0x16004e, 0xc100c0, 0xc300c2}, - {0x241, 0x407f0478, 0x7f48005d, 0x0, 0xb2b10000, 0xdfeb80ff, 0x17004e, 0xc100c0, 0xc300c2}, - {0x250, 0x407f0455, 0x7f250048, 0x0, 0xb2b10000, 0xdfeb80ff, 0x18004f, 0xc100c0, 0xc300c2}, - {0x251, 0x64640451, 0x24230048, 0x0, 0xb2b10000, 0xdfeb80ff, 0x18004f, 0xc100c0, 0xc300c2}, - {0x252, 0x1b640405, 0x2200003c, 0x0, 0xb2b10000, 0xdfeb80ff, 0x18004f, 0xc100c0, 0xc300c2}, - {0x260, 0x6e047f, 0x7f005a48, 0x0, 0xb2b10000, 0xdfe780ff, 0x190050, 0xc100c0, 0xc300c2}, - {0x261, 0x7f6e047f, 0x7f006148, 0x0, 0xb2b10000, 0xdfe780ff, 0x190050, 0xc100c0, 0xc300c2}, - {0x270, 0x407f047f, 0x7f00465a, 0x0, 0xb2b10000, 0x5febb4ff, 0x120051, 0xc100c0, 0xc300c2}, - {0x280, 0x407f0465, 0x7f000460, 0x0, 0xb2b10000, 0xdfefbbff, 0x2a0052, 0xc100c0, 0xc300c2}, - {0x290, 0x407f0465, 0x7f000454, 0x0, 0xb2b10000, 0xdfec80ff, 0x2a0055, 0xc100c0, 0xc300c2}}}; + {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_VH = {{ - {0x0, 0x7f047f, 0x3e006a53, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10000, 0xc100c0, 0xc300c2}, - {0x1, 0x1b380400, 0x3e006a5f, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10000, 0xc100c0, 0xc300c2}, - {0x2, 0x64500400, 0x3e006a66, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10000, 0xc100c0, 0xc300c2}, - {0x3, 0x40280400, 0x3e006a72, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10000, 0xc100c0, 0xc300c2}, - {0x4, 0x7f7f047f, 0x3e006a54, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10000, 0xc100c0, 0xc300c2}, - {0x5, 0x407f005b, 0x4242004e, 0x0, 0xb2b17f7f, 0x5feb80ff, 0xb0000, 0xc100c0, 0xc300c2}, - {0x6, 0x407f005b, 0x4343004f, 0x0, 0xb2b17f7f, 0x5feb80ff, 0x50000, 0xc100c0, 0xc300c2}, - {0x7, 0x407f0464, 0x3e006a4d, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x10000, 0xc100c0, 0xc300c2}, - {0x8, 0x407f0054, 0x4848003c, 0x0, 0xb2b17f7f, 0x5fe380ff, 0x190000, 0xc100c0, 0xc300c2}, - {0x9, 0x407f0054, 0x49490049, 0x0, 0xb2b17f7f, 0x5fe380ff, 0x1a0000, 0xc100c0, 0xc300c2}, - {0x10, 0x407f007f, 0x3a3a0046, 0x0, 0xb2b17f7f, 0x5feb80ff, 0x120001, 0xc100c0, 0xc300c2}, - {0x11, 0x407f007f, 0x3b3b0045, 0x0, 0xb2b17f7f, 0x5feb80ff, 0x30001, 0xc100c0, 0xc300c2}, - {0x12, 0x407f007f, 0x48480060, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x400001, 0xc100c0, 0xc300c2}, - {0x13, 0x407f007f, 0x48480060, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x20001, 0xc100c0, 0xc300c2}, - {0x14, 0x7f007f, 0x48480048, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x20001, 0xc100c0, 0xc300c2}, - {0x15, 0x7f640051, 0x4848006c, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x20001, 0xc100c0, 0xc300c2}, - {0x16, 0x640051, 0x4848326b, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x20001, 0xc100c0, 0xc300c2}, - {0x17, 0x407f0052, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x3e0001, 0xc100c0, 0xc300c2}, - {0x18, 0x407f0052, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x3f0001, 0xc100c0, 0xc300c2}, - {0x19, 0x407f0000, 0x39390045, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x510001, 0xc100c0, 0xc300c2}, - {0x20, 0x40150464, 0x3c3c6a66, 0x0, 0xb2b17f7f, 0xdaeb80ff, 0x10002, 0xc100c0, 0xc300c2}, - {0x21, 0x3f5f0400, 0x3b006a4e, 0x0, 0xb2b17f7f, 0x5fef80ff, 0x10002, 0xc100c0, 0xc300c2}, - {0x22, 0x540400, 0x3b006a54, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10002, 0xc100c0, 0xc300c2}, - {0x23, 0x7f540400, 0x3b006a56, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10002, 0xc100c0, 0xc300c2}, - {0x24, 0x7f007f, 0x3c3c003c, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x1d0002, 0xc100c0, 0xc300c2}, - {0x25, 0x7f7f007f, 0x3c3c003c, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x1e0002, 0xc100c0, 0xc300c2}, - {0x26, 0x4046007f, 0x3c3c003c, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x1f0002, 0xc100c0, 0xc300c2}, - {0x27, 0x407f007f, 0x40400040, 0x0, 0xb2b17f7f, 0x5fe380ff, 0x200002, 0xc100c0, 0xc300c2}, - {0x30, 0x407f007f, 0x3b3b0047, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x110003, 0xc100c0, 0xc300c2}, - {0x31, 0x407f007f, 0x3a3a0052, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x60003, 0xc100c0, 0xc300c2}, - {0x32, 0x407f007f, 0x43430048, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x70003, 0xc100c0, 0xc300c2}, - {0x33, 0x407f007f, 0x48480054, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x1c0003, 0xc100c0, 0xc300c2}, - {0x34, 0x407f007f, 0x39390051, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x2e0003, 0xc100c0, 0xc300c2}, - {0x40, 0x407f0050, 0x24240030, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x80004, 0xc100c0, 0xc300c2}, - {0x41, 0x407f0050, 0x25250031, 0x0, 0xb2b17f7f, 0xdfeba5ff, 0x80004, 0xc100c0, 0xc300c2}, - {0x42, 0x407f0050, 0x26260032, 0x0, 0xb2b17f7f, 0xdfebaaff, 0x80004, 0xc100c0, 0xc300c2}, - {0x43, 0x407f007f, 0x3b3b0047, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x100004, 0xc100c0, 0xc300c2}, - {0x44, 0x407f007f, 0x48480060, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x400004, 0xc100c0, 0xc300c2}, - {0x45, 0x407f007f, 0x48480054, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x1c0004, 0xc100c0, 0xc300c2}, - {0x50, 0x4064047f, 0x3c3c0060, 0x0, 0xb2b17f7f, 0xdff380ff, 0x7c0006, 0xc100c0, 0xc300c2}, - {0x51, 0x4064047f, 0x3c3c0078, 0x0, 0xb2b17f7f, 0xdff580ff, 0x530006, 0xc100c0, 0xc300c2}, - {0x52, 0x4053047f, 0x3c3c006c, 0x0, 0xb2b17f7f, 0xd170bebf, 0x7d0006, 0xc100c0, 0xc300c2}, - {0x60, 0x407f007f, 0x40400059, 0x0, 0xb2b17f7f, 0xdfef80ff, 0x150007, 0xc100c0, 0xc300c2}, - {0x61, 0x407f0002, 0x3c3c0054, 0x0, 0xb2b10000, 0xdfed80ff, 0x420007, 0xc100c0, 0xc300c2}, - {0x62, 0x407f0002, 0x3d3d0055, 0x0, 0xb2b10000, 0xdfe680ff, 0x430007, 0xc100c0, 0xc300c2}, - {0x63, 0x407f0002, 0x3f3f004b, 0x0, 0xb2b10000, 0xdfe680ff, 0x450007, 0xc100c0, 0xc300c2}, - {0x64, 0x407f0002, 0x4141004d, 0x0, 0xb2b10000, 0xdfe680ff, 0x470007, 0xc100c0, 0xc300c2}, - {0x65, 0x407f0002, 0x4343044f, 0x0, 0xb2b10000, 0xdfe680ff, 0x4b0007, 0xc100c0, 0xc300c2}, - {0x66, 0x407f0002, 0x4444005c, 0x0, 0xb2b10000, 0xdfe680ff, 0x440007, 0xc100c0, 0xc300c2}, - {0x67, 0x407f047f, 0x2424003c, 0x0, 0xb2b10000, 0xdfeb80ff, 0x730007, 0xc100c0, 0xc300c2}, - {0x68, 0x327f0002, 0x26260a32, 0x0, 0xb2b10000, 0xdfe380ff, 0x740007, 0xc100c0, 0xc300c2}, - {0x69, 0x367f0002, 0x28280028, 0x0, 0xb2b10000, 0xdfe380ff, 0x750007, 0xc100c0, 0xc300c2}, - {0x6a, 0x207f0002, 0x2a2a0036, 0x0, 0xb2b10000, 0xdfe380ff, 0x760007, 0xc100c0, 0xc300c2}, - {0x6b, 0x407f0002, 0x2b2b0a37, 0x0, 0xb2b10000, 0xdfe380ff, 0x760007, 0xc100c0, 0xc300c2}, - {0x6c, 0x607f0002, 0x2c2c0038, 0x0, 0xb2b10000, 0xdfe380ff, 0x760007, 0xc100c0, 0xc300c2}, - {0x6d, 0x4e7f0002, 0x25250031, 0x0, 0xb2b10000, 0xdfe380ff, 0x740007, 0xc100c0, 0xc300c2}, - {0x6e, 0x4a7f0002, 0x29290a29, 0x0, 0xb2b10000, 0xdfe380ff, 0x750007, 0xc100c0, 0xc300c2}, - {0x70, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x40008, 0xc100c0, 0xc300c2}, - {0x80, 0x3c64007f, 0x2727003c, 0x0, 0xb2b17f7f, 0xdfe380ff, 0xa0009, 0xc100c0, 0xc300c2}, - {0x81, 0x3c7f007f, 0x2626003e, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x90009, 0xc100c0, 0xc300c2}, - {0x82, 0x3c5a007f, 0x27270044, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x130009, 0xc100c0, 0xc300c2}, - {0x83, 0x407f007f, 0x28280040, 0x0, 0xb2b17f7f, 0xdfe380ff, 0xf0009, 0xc100c0, 0xc300c2}, - {0x84, 0x407f0065, 0x2525003d, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x230009, 0xc100c0, 0xc300c2}, - {0x85, 0x407f007f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x250009, 0xc100c0, 0xc300c2}, - {0x86, 0x407f0000, 0x3d3d465a, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x790009, 0xc100c0, 0xc300c2}, - {0x87, 0x407f0000, 0x29290041, 0x0, 0xb2b17f7f, 0xdfe380ff, 0xf0009, 0xc100c0, 0xc300c2}, - {0x90, 0x407f0064, 0x3c3c0048, 0x0, 0xb2b17f7f, 0xdff080ff, 0x24000b, 0xc100c0, 0xc300c2}, - {0x91, 0x407f0064, 0x3d3d0055, 0x0, 0xb2b17f7f, 0xdff080ff, 0x41000b, 0xc100c0, 0xc300c2}, - {0xa0, 0x407f0002, 0x26260026, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x16000c, 0xc100c0, 0xc300c2}, - {0xa1, 0x407f007f, 0x27270027, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x14000c, 0xc100c0, 0xc300c2}, - {0xa2, 0x407f0064, 0x28280040, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x26000c, 0xc100c0, 0xc300c2}, - {0xa3, 0x407f0064, 0x29290041, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x27000c, 0xc100c0, 0xc300c2}, - {0xa4, 0x407f0064, 0x2a2a0042, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x28000c, 0xc100c0, 0xc300c2}, - {0xa5, 0x407f0064, 0x2c2c0044, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x37000c, 0xc100c0, 0xc300c2}, - {0xb0, 0x407f007f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0xdff080ff, 0xc000d, 0xc100c0, 0xc300c2}, - {0xc0, 0x407f0050, 0x24240024, 0x0, 0xb2b17f7f, 0xdfe380ff, 0xd000e, 0xc100c0, 0xc300c2}, - {0xc1, 0x407f0050, 0x25250025, 0x0, 0xb2b17f7f, 0xdfe380ff, 0xe000e, 0xc100c0, 0xc300c2}, - {0xc2, 0x407f0050, 0x3d3d4659, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x1b000e, 0xc100c0, 0xc300c2}, - {0xc3, 0x407f0050, 0x3e3e465a, 0x0, 0xb2b17f7f, 0xdfe6afff, 0x1b000e, 0xc100c0, 0xc300c2}, - {0xd0, 0x407f0479, 0x30300048, 0x0, 0xb2b17f7f, 0xdff080ff, 0x380010, 0xc100c0, 0xc300c2}, - {0xe0, 0x407f007f, 0x3b3b4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x520011, 0xc100c0, 0xc300c2}, - {0xf0, 0x407f0064, 0x2d2d0045, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x300012, 0xc100c0, 0xc300c2}, - {0xf1, 0x407f007f, 0x2e2e0046, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x290012, 0xc100c0, 0xc300c2}, - {0xf2, 0x407f0032, 0x2f2f0047, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x2a0012, 0xc100c0, 0xc300c2}, - {0xf3, 0x407f0032, 0x30300048, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x2b0012, 0xc100c0, 0xc300c2}, - {0xf4, 0x407f0032, 0x2424003c, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x2c0012, 0xc100c0, 0xc300c2}, - {0xf5, 0x407f0032, 0x2525003d, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x2d0012, 0xc100c0, 0xc300c2}, - {0x100, 0x407f007e, 0x7f006a53, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x10015, 0xc100c0, 0xc300c2}, - {0x110, 0x407f0064, 0x3d3d004e, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x60016, 0xc100c0, 0xc300c2}, - {0x111, 0x407f0064, 0x3e3e0045, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x110016, 0xc100c0, 0xc300c2}, - {0x112, 0x407f0064, 0x3f3f0046, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x110016, 0xc100c0, 0xc300c2}, - {0x113, 0x407f0064, 0x3f3f0057, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x270016, 0xc100c0, 0xc300c2}, - {0x114, 0x3c7f004b, 0x2121462d, 0x0, 0xb2b17f7f, 0xdfe0a4ff, 0x80016, 0xc100c0, 0xc300c2}, - {0x115, 0x407f004b, 0x2222462d, 0x0, 0xb2b17f7f, 0xdfe0a0ff, 0x80016, 0xc100c0, 0xc300c2}, - {0x116, 0x3c7f004b, 0x2323462d, 0x0, 0xb2b17f7f, 0xdfe0a7ff, 0x80016, 0xc100c0, 0xc300c2}, - {0x117, 0x407f004b, 0x2424462d, 0x0, 0xb2b17f7f, 0xdfe0aaff, 0x80016, 0xc100c0, 0xc300c2}, - {0x118, 0x407f004a, 0x2525002d, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x360016, 0xc100c0, 0xc300c2}, - {0x120, 0x407f047f, 0x3e3e0073, 0x0, 0xb2b17f7f, 0xceab80ff, 0x3d0018, 0xc100c0, 0xc300c2}, - {0x130, 0x2c7f007f, 0x24240030, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x180019, 0xc100c0, 0xc300c2}, - {0x131, 0x407f007f, 0x25250031, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x170019, 0xc100c0, 0xc300c2}, - {0x132, 0x547f007f, 0x24246e31, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x180019, 0xc100c0, 0xc300c2}, - {0x140, 0x407f0400, 0x7f000054, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x2f001b, 0xc100c0, 0xc300c2}, - {0x150, 0x407f047f, 0x7f004659, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x22001c, 0xc100c0, 0xc300c2}, - {0x160, 0x407f047f, 0x2424003c, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x3d0021, 0xc100c0, 0xc300c2}, - {0x170, 0x407f0046, 0x24240031, 0x0, 0xb2b17f7f, 0xdfebb4ff, 0x2c0025, 0xc100c0, 0xc300c2}, - {0x171, 0x407f0046, 0x25250031, 0x0, 0xb2b17f7f, 0xdfebaaff, 0x2d0025, 0xc100c0, 0xc300c2}, - {0x172, 0x407f0064, 0x3c3c0448, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x420025, 0xc100c0, 0xc300c2}, - {0x173, 0x407f0064, 0x3d3d0449, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x430025, 0xc100c0, 0xc300c2}, - {0x174, 0x407f0046, 0x4040044c, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x440025, 0xc100c0, 0xc300c2}, - {0x175, 0x407f0046, 0x4141044c, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x440025, 0xc100c0, 0xc300c2}, - {0x176, 0x407f0064, 0x4242044e, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x450025, 0xc100c0, 0xc300c2}, - {0x177, 0x407f0064, 0x4343044f, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x460025, 0xc100c0, 0xc300c2}, - {0x180, 0x407f007f, 0x3c3c0448, 0x0, 0xb2b17f7f, 0x5fcb80ff, 0x470027, 0xc100c0, 0xc300c2}, - {0x181, 0x40550051, 0x3d3d0457, 0x0, 0xb2b17f7f, 0x5fcb80ff, 0x480027, 0xc100c0, 0xc300c2}, - {0x182, 0x407f007f, 0x4040044c, 0x0, 0xb2b17f7f, 0x5fcb80ff, 0x490027, 0xc100c0, 0xc300c2}, - {0x183, 0x407f0051, 0x4242045a, 0x0, 0xb2b17f7f, 0x5fcb80ff, 0x4b0027, 0xc100c0, 0xc300c2}, - {0x184, 0x407f004b, 0x4343044f, 0x0, 0xb2b17f7f, 0x5fcb80ff, 0x4c0027, 0xc100c0, 0xc300c2}, - {0x185, 0x407f007f, 0x41410459, 0x0, 0xb2b17f7f, 0x5feb80ff, 0x4d0027, 0xc100c0, 0xc300c2}, - {0x186, 0x4064007f, 0x4444005c, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x4e0027, 0xc100c0, 0xc300c2}, - {0x190, 0x407f007f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0x5fe080ff, 0x4f0028, 0xc100c0, 0xc300c2}, - {0x191, 0x407f007f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x500028, 0xc100c0, 0xc300c2}, - {0x1a0, 0x407f0019, 0x3e3e465b, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x7a002c, 0xc100c0, 0xc300c2}, - {0x1a1, 0x407f0019, 0x3f3f465c, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x7b002c, 0xc100c0, 0xc300c2}, - {0x1b0, 0x407f0040, 0x3d3d3c56, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x780031, 0xc100c0, 0xc300c2}, - {0x1c0, 0x407f007f, 0x7f000048, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x54003a, 0xc100c0, 0xc300c2}, - {0x1d0, 0x407f007f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x55003b, 0xc100c0, 0xc300c2}, - {0x1d1, 0x407f007f, 0x3e3e465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x56003b, 0xc100c0, 0xc300c2}, - {0x1d2, 0x407f007f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x57003b, 0xc100c0, 0xc300c2}, - {0x1d3, 0x407f007f, 0x3f3f005b, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x58003b, 0xc100c0, 0xc300c2}, - {0x1e0, 0x407f007f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x59003c, 0xc100c0, 0xc300c2}, - {0x1e1, 0x407f007f, 0x3f3f465c, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x5a003c, 0xc100c0, 0xc300c2}, - {0x1f0, 0x407f007f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x5b003d, 0xc100c0, 0xc300c2}, - {0x1f1, 0x407f007f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x5c003d, 0xc100c0, 0xc300c2}, - {0x1f2, 0x407f007f, 0x37374653, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x5d003d, 0xc100c0, 0xc300c2}, - {0x1f3, 0x407f007f, 0x38384655, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x5e003d, 0xc100c0, 0xc300c2}, - {0x1f4, 0x407f007f, 0x39394656, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x5f003d, 0xc100c0, 0xc300c2}, - {0x1f5, 0x407f007f, 0x3b3b4658, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x60003d, 0xc100c0, 0xc300c2}, - {0x1f6, 0x407f007f, 0x3a3a4657, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x61003d, 0xc100c0, 0xc300c2}, - {0x1f7, 0x407f007f, 0x4040465d, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x62003d, 0xc100c0, 0xc300c2}, - {0x1f8, 0x407f007f, 0x4141004d, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x63003d, 0xc100c0, 0xc300c2}, - {0x200, 0x407f007f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x64003e, 0xc100c0, 0xc300c2}, - {0x201, 0x407f007f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x65003e, 0xc100c0, 0xc300c2}, - {0x202, 0x407f007f, 0x3e3e465b, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x66003e, 0xc100c0, 0xc300c2}, - {0x203, 0x407f007f, 0x3f3f465c, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x67003e, 0xc100c0, 0xc300c2}, - {0x210, 0x407f007f, 0x25254642, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x68003f, 0xc100c0, 0xc300c2}, - {0x211, 0x407f007f, 0x26264643, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x69003f, 0xc100c0, 0xc300c2}, - {0x212, 0x407f007f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x6a003f, 0xc100c0, 0xc300c2}, - {0x213, 0x407f007f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x6b003f, 0xc100c0, 0xc300c2}, - {0x220, 0x407f007f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x6c0040, 0xc100c0, 0xc300c2}, - {0x221, 0x407f007f, 0x3e3e465b, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x6d0040, 0xc100c0, 0xc300c2}, - {0x222, 0x407f007f, 0x27274644, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x6e0040, 0xc100c0, 0xc300c2}, - {0x223, 0x407f007f, 0x3f3f465c, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x6f0040, 0xc100c0, 0xc300c2}, - {0x230, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x700041, 0xc100c0, 0xc300c2}, - {0x231, 0x407f007f, 0x4040465d, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x710041, 0xc100c0, 0xc300c2}, - {0x240, 0x407f047f, 0x7f004667, 0x0, 0xb2b17f7f, 0xdfefaaff, 0x720042, 0xc100c0, 0xc300c2}, - {0x250, 0x7f0404, 0x53000048, 0x0, 0xb2b10000, 0xdfeb80ff, 0x530046, 0xc100c0, 0xc300c2}, - {0x251, 0x7f7f0404, 0x7f540048, 0x0, 0xb2b10000, 0xdfeb80ff, 0x530046, 0xc100c0, 0xc300c2}, - {0x260, 0x407f0400, 0x7f000066, 0x0, 0xb2b10000, 0xdfec80ff, 0x33004a, 0xc100c0, 0xc300c2}, - {0x270, 0x7f007f, 0x30304659, 0x0, 0xb2b10101, 0xdff080ff, 0x31004b, 0xc100c0, 0xc300c2}, - {0x271, 0x7f7f047f, 0x3c3c4665, 0x0, 0xb2b10101, 0xdff080ff, 0x32004b, 0xc100c0, 0xc300c2}, - {0x272, 0x407f047f, 0x48484671, 0x0, 0xb2b10101, 0xdff080ff, 0x32004b, 0xc100c0, 0xc300c2}, - {0x280, 0x407f044f, 0x7f00465a, 0x0, 0xb2b10101, 0xdff0c0ff, 0x33004c, 0xc100c0, 0xc300c2}, - {0x290, 0x407f0455, 0x3f3f004b, 0x0, 0xb2b10000, 0x5feb80ff, 0x34004d, 0xc100c0, 0xc300c2}, - {0x291, 0x407f005a, 0x3b00003c, 0x0, 0xb2b10000, 0xdfeb80ff, 0x35004d, 0xc100c0, 0xc300c2}, - {0x292, 0x406e0055, 0x3c3c0048, 0x0, 0xb2b10000, 0xdfeb80ff, 0x77004d, 0xc100c0, 0xc300c2}, - {0x2a0, 0x407f0464, 0x47005a49, 0x0, 0xb2b10000, 0xdff0a0ff, 0x39004e, 0xc100c0, 0xc300c2}, - {0x2a1, 0x407f0464, 0x7f48005d, 0x0, 0xb2b10000, 0xdfeb80ff, 0x3a004e, 0xc100c0, 0xc300c2}, - {0x2b0, 0x407f0455, 0x7f250048, 0x0, 0xb2b10000, 0xdfeb80ff, 0x3b004f, 0xc100c0, 0xc300c2}, - {0x2b1, 0x6464044f, 0x24230048, 0x0, 0xb2b10000, 0xdfeb80ff, 0x3b004f, 0xc100c0, 0xc300c2}, - {0x2b2, 0x1b640405, 0x2200003c, 0x0, 0xb2b10000, 0xdfeb80ff, 0x3b004f, 0xc100c0, 0xc300c2}, - {0x2c0, 0x6e047f, 0x7f005a48, 0x0, 0xb2b10000, 0xdfe780ff, 0x3c0050, 0xc100c0, 0xc300c2}, - {0x2c1, 0x7f6e047f, 0x7f006148, 0x0, 0xb2b10000, 0xdfe780ff, 0x3c0050, 0xc100c0, 0xc300c2}, - {0x2d0, 0x407f047f, 0x7f00465a, 0x0, 0xb2b10000, 0x5febb4ff, 0x330051, 0xc100c0, 0xc300c2}}}; + {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_VH = {{ - {0x0, 0x7f047f, 0x3e006a53, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10000, 0xc100c0, 0xc300c2}, - {0x1, 0x1b380400, 0x3e006a5f, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10000, 0xc100c0, 0xc300c2}, - {0x2, 0x64500400, 0x3e006a66, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10000, 0xc100c0, 0xc300c2}, - {0x3, 0x40280400, 0x3e006a72, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10000, 0xc100c0, 0xc300c2}, - {0x4, 0x7f7f047f, 0x3e006a54, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10000, 0xc100c0, 0xc300c2}, - {0x5, 0x407f005b, 0x42420050, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0xc0000, 0xc100c0, 0xc300c2}, - {0x6, 0x407f005b, 0x4343004f, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x50000, 0xc100c0, 0xc300c2}, - {0x7, 0x407f0464, 0x3e006a4d, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x10000, 0xc100c0, 0xc300c2}, - {0x8, 0x407f0054, 0x4848003c, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x1d0000, 0xc100c0, 0xc300c2}, - {0x9, 0x407f0054, 0x49490049, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x1e0000, 0xc100c0, 0xc300c2}, - {0x10, 0x407f007f, 0x3a3a0046, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x150001, 0xc100c0, 0xc300c2}, - {0x11, 0x407f007f, 0x3b3b0045, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x30001, 0xc100c0, 0xc300c2}, - {0x12, 0x407f007f, 0x48480060, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x430001, 0xc100c0, 0xc300c2}, - {0x13, 0x407f007f, 0x48480060, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x20001, 0xc100c0, 0xc300c2}, - {0x14, 0x7f007f, 0x48480048, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x20001, 0xc100c0, 0xc300c2}, - {0x15, 0x7f640051, 0x4848006c, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x20001, 0xc100c0, 0xc300c2}, - {0x16, 0x640051, 0x4848326b, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x20001, 0xc100c0, 0xc300c2}, - {0x17, 0x407f007f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x410001, 0xc100c0, 0xc300c2}, - {0x18, 0x407f007f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x420001, 0xc100c0, 0xc300c2}, - {0x19, 0x407f0000, 0x39390045, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x480001, 0xc100c0, 0xc300c2}, - {0x20, 0x40150464, 0x3c3c6a66, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x10002, 0xc100c0, 0xc300c2}, - {0x21, 0x3f5f0400, 0x3b006a4e, 0x0, 0xb2b17f7f, 0x5fef80ff, 0x10002, 0xc100c0, 0xc300c2}, - {0x22, 0x540400, 0x3b006a54, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10002, 0xc100c0, 0xc300c2}, - {0x23, 0x7f540400, 0x3b006a56, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10002, 0xc100c0, 0xc300c2}, - {0x24, 0x7f007f, 0x3c3c003c, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x240002, 0xc100c0, 0xc300c2}, - {0x25, 0x7f7f007f, 0x3c3c003c, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x250002, 0xc100c0, 0xc300c2}, - {0x26, 0x4046007f, 0x3c3c003c, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x260002, 0xc100c0, 0xc300c2}, - {0x27, 0x407f007f, 0x40400040, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x270002, 0xc100c0, 0xc300c2}, - {0x30, 0x407f007f, 0x3b3b0047, 0x0, 0xb2b17f7f, 0x5fde80ff, 0x140003, 0xc100c0, 0xc300c2}, - {0x31, 0x407f007f, 0x3a3a0052, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x60003, 0xc100c0, 0xc300c2}, - {0x32, 0x407f007f, 0x43430048, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x70003, 0xc100c0, 0xc300c2}, - {0x33, 0x407f007f, 0x48480054, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x1b0003, 0xc100c0, 0xc300c2}, - {0x34, 0x407f007f, 0x39390051, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x280003, 0xc100c0, 0xc300c2}, - {0x40, 0x407f0050, 0x24240030, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x80004, 0xc100c0, 0xc300c2}, - {0x41, 0x407f0050, 0x25250031, 0x0, 0xb2b17f7f, 0xdffea5ff, 0x80004, 0xc100c0, 0xc300c2}, - {0x42, 0x407f0050, 0x26260032, 0x0, 0xb2b17f7f, 0xdffeaaff, 0x80004, 0xc100c0, 0xc300c2}, - {0x43, 0x407f007f, 0x3b3b0047, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x130004, 0xc100c0, 0xc300c2}, - {0x44, 0x407f007f, 0x48480060, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x430004, 0xc100c0, 0xc300c2}, - {0x45, 0x407f007f, 0x48480054, 0x0, 0xb2b17f7f, 0xdffeacff, 0x1b0004, 0xc100c0, 0xc300c2}, - {0x50, 0x4064047f, 0x3c3c0060, 0x0, 0xb2b17f7f, 0xdff380ff, 0x740006, 0xc100c0, 0xc300c2}, - {0x51, 0x4064047f, 0x3c3c0078, 0x0, 0xb2b17f7f, 0xdff580ff, 0x4a0006, 0xc100c0, 0xc300c2}, - {0x52, 0x4053047f, 0x3c3c006c, 0x0, 0xb2b17f7f, 0xd170bebf, 0x750006, 0xc100c0, 0xc300c2}, - {0x60, 0x407f007f, 0x40400059, 0x0, 0xb2b17f7f, 0x5fef80ff, 0x180007, 0xc100c0, 0xc300c2}, - {0x61, 0x407f047f, 0x2424003c, 0x0, 0xb2b10000, 0xdfeb80ff, 0x6b0007, 0xc100c0, 0xc300c2}, - {0x62, 0x4e7f0002, 0x25250031, 0x0, 0xb2b10000, 0xdfe380ff, 0x6c0007, 0xc100c0, 0xc300c2}, - {0x63, 0x327f0002, 0x26260a32, 0x0, 0xb2b10000, 0xdfe380ff, 0x6c0007, 0xc100c0, 0xc300c2}, - {0x64, 0x367f0002, 0x28280028, 0x0, 0xb2b10000, 0xdfe380ff, 0x6d0007, 0xc100c0, 0xc300c2}, - {0x65, 0x4a7f0002, 0x29290a29, 0x0, 0xb2b10000, 0xdfe380ff, 0x6d0007, 0xc100c0, 0xc300c2}, - {0x66, 0x207f0002, 0x2a2a0036, 0x0, 0xb2b10000, 0xdfe380ff, 0x6e0007, 0xc100c0, 0xc300c2}, - {0x67, 0x407f0002, 0x2b2b0a37, 0x0, 0xb2b10000, 0xdfe380ff, 0x6e0007, 0xc100c0, 0xc300c2}, - {0x68, 0x607f0002, 0x2c2c0038, 0x0, 0xb2b10000, 0xdfe380ff, 0x6e0007, 0xc100c0, 0xc300c2}, - {0x70, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x40008, 0xc100c0, 0xc300c2}, - {0x80, 0x4064007f, 0x2727003c, 0x0, 0xb2b17f7f, 0x5ffd80ff, 0xa0009, 0xc100c0, 0xc300c2}, - {0x81, 0x407f007f, 0x2626003e, 0x0, 0xb2b17f7f, 0x5ffd80ff, 0x90009, 0xc100c0, 0xc300c2}, - {0x82, 0x405a007f, 0x27270044, 0x0, 0xb2b17f7f, 0x5ffd80ff, 0x160009, 0xc100c0, 0xc300c2}, - {0x83, 0x407f007f, 0x28280040, 0x0, 0xb2b17f7f, 0x5ffc80ff, 0xd0009, 0xc100c0, 0xc300c2}, - {0x84, 0x407f0065, 0x2525003d, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x220009, 0xc100c0, 0xc300c2}, - {0x85, 0x407f007f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x230009, 0xc100c0, 0xc300c2}, - {0x86, 0x407f0000, 0x3d3d465a, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x710009, 0xc100c0, 0xc300c2}, - {0x87, 0x407f0000, 0x29290041, 0x0, 0xb2b17f7f, 0xdfe380ff, 0xd0009, 0xc100c0, 0xc300c2}, - {0x90, 0x407f0064, 0x3c3c0048, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0xb000b, 0xc100c0, 0xc300c2}, - {0x91, 0x407f0064, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x44000b, 0xc100c0, 0xc300c2}, - {0xa0, 0x407f0002, 0x26260026, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x19000c, 0xc100c0, 0xc300c2}, - {0xa1, 0x407f007f, 0x27270027, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x17000c, 0xc100c0, 0xc300c2}, - {0xa2, 0x407f0064, 0x28280040, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x29000c, 0xc100c0, 0xc300c2}, - {0xa3, 0x407f0064, 0x29290041, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x2a000c, 0xc100c0, 0xc300c2}, - {0xa4, 0x407f0064, 0x2a2a0042, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x2b000c, 0xc100c0, 0xc300c2}, - {0xa5, 0x407f0064, 0x2c2c0044, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x3a000c, 0xc100c0, 0xc300c2}, - {0xb0, 0x187f007f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0x5ff080ff, 0xe000d, 0xc100c0, 0xc300c2}, - {0xc0, 0x407f0050, 0x24240024, 0x0, 0xb2b17f7f, 0xdfe380ff, 0xf000e, 0xc100c0, 0xc300c2}, - {0xc1, 0x407f0050, 0x25250025, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x10000e, 0xc100c0, 0xc300c2}, - {0xc2, 0x407f0050, 0x3d3d4659, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x1c000e, 0xc100c0, 0xc300c2}, - {0xc3, 0x407f0050, 0x3e3e465a, 0x0, 0xb2b17f7f, 0xdfe6afff, 0x1c000e, 0xc100c0, 0xc300c2}, - {0xd0, 0x407f0479, 0x30300048, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x3b0010, 0xc100c0, 0xc300c2}, - {0xe0, 0x2c7f007f, 0x24240030, 0x0, 0xb2b17f7f, 0x5ffc80ff, 0x110011, 0xc100c0, 0xc300c2}, - {0xe1, 0x367f007f, 0x25250031, 0x0, 0xb2b17f7f, 0x5ffd80ff, 0x120011, 0xc100c0, 0xc300c2}, - {0xe2, 0x407f007f, 0x3b3b4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x490011, 0xc100c0, 0xc300c2}, - {0xf0, 0x407f0064, 0x2d2d0045, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x2c0012, 0xc100c0, 0xc300c2}, - {0xf1, 0x407f007f, 0x2e2e0046, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x2d0012, 0xc100c0, 0xc300c2}, - {0xf2, 0x407f0032, 0x2f2f0047, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x2e0012, 0xc100c0, 0xc300c2}, - {0xf3, 0x407f0032, 0x30300048, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x2f0012, 0xc100c0, 0xc300c2}, - {0xf4, 0x407f0032, 0x2424003c, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x300012, 0xc100c0, 0xc300c2}, - {0xf5, 0x407f0032, 0x2525003d, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x310012, 0xc100c0, 0xc300c2}, - {0x100, 0x407f007e, 0x7f006a53, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x10015, 0xc100c0, 0xc300c2}, - {0x110, 0x407f0064, 0x3d3d004e, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x60016, 0xc100c0, 0xc300c2}, - {0x111, 0x407f0064, 0x3e3e0045, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x140016, 0xc100c0, 0xc300c2}, - {0x112, 0x407f0064, 0x3f3f0046, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x140016, 0xc100c0, 0xc300c2}, - {0x113, 0x407f0064, 0x3f3f0050, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x2a0016, 0xc100c0, 0xc300c2}, - {0x114, 0x3c7f0050, 0x2121002d, 0x0, 0xb2b17f7f, 0xdfe0a4ff, 0x80016, 0xc100c0, 0xc300c2}, - {0x115, 0x407f0050, 0x2222002d, 0x0, 0xb2b17f7f, 0xdfe0aaff, 0x80016, 0xc100c0, 0xc300c2}, - {0x116, 0x3c7f0050, 0x2323002d, 0x0, 0xb2b17f7f, 0xdfe0a7ff, 0x80016, 0xc100c0, 0xc300c2}, - {0x117, 0x407f0050, 0x2424002d, 0x0, 0xb2b17f7f, 0xdfe0a9ff, 0x80016, 0xc100c0, 0xc300c2}, - {0x118, 0x407f0050, 0x2525002d, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x370016, 0xc100c0, 0xc300c2}, - {0x120, 0x407f047f, 0x3e3e0073, 0x0, 0xb2b17f7f, 0xceab80ff, 0x400018, 0xc100c0, 0xc300c2}, - {0x130, 0x2c7f007f, 0x24240030, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x1a0019, 0xc100c0, 0xc300c2}, - {0x131, 0x407f007f, 0x25250031, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x120019, 0xc100c0, 0xc300c2}, - {0x132, 0x547f007f, 0x24246e31, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x1a0019, 0xc100c0, 0xc300c2}, - {0x140, 0x407f0400, 0x7f000054, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x38001b, 0xc100c0, 0xc300c2}, - {0x150, 0x407f047f, 0x7f004659, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x39001c, 0xc100c0, 0xc300c2}, - {0x160, 0x407f047f, 0x7f00003c, 0x0, 0xb2b17f7f, 0x5feb80ff, 0x400021, 0xc100c0, 0xc300c2}, - {0x170, 0x407f007f, 0x41410456, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x470027, 0xc100c0, 0xc300c2}, - {0x171, 0x407f047f, 0x3c3c0448, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x6a0027, 0xc100c0, 0xc300c2}, - {0x180, 0x407f007f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x450028, 0xc100c0, 0xc300c2}, - {0x181, 0x407f007f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x460028, 0xc100c0, 0xc300c2}, - {0x190, 0x407f0019, 0x3e3e465b, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x72002c, 0xc100c0, 0xc300c2}, - {0x191, 0x407f0019, 0x3f3f465c, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x73002c, 0xc100c0, 0xc300c2}, - {0x1a0, 0x407f0040, 0x3d3d3c56, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x700031, 0xc100c0, 0xc300c2}, - {0x1b0, 0x407f007f, 0x7f000048, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x4b003a, 0xc100c0, 0xc300c2}, - {0x1c0, 0x407f007f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x4c003b, 0xc100c0, 0xc300c2}, - {0x1c1, 0x407f007f, 0x3e3e465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x4d003b, 0xc100c0, 0xc300c2}, - {0x1c2, 0x407f007f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x4e003b, 0xc100c0, 0xc300c2}, - {0x1c3, 0x407f007f, 0x3f3f005b, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x4f003b, 0xc100c0, 0xc300c2}, - {0x1d0, 0x407f007f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x50003c, 0xc100c0, 0xc300c2}, - {0x1d1, 0x407f007f, 0x3f3f465c, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x51003c, 0xc100c0, 0xc300c2}, - {0x1e0, 0x407f007f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x52003d, 0xc100c0, 0xc300c2}, - {0x1e1, 0x407f007f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x53003d, 0xc100c0, 0xc300c2}, - {0x1e2, 0x407f007f, 0x37374653, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x54003d, 0xc100c0, 0xc300c2}, - {0x1e3, 0x407f007f, 0x38384655, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x55003d, 0xc100c0, 0xc300c2}, - {0x1e4, 0x407f007f, 0x39394656, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x56003d, 0xc100c0, 0xc300c2}, - {0x1e5, 0x407f007f, 0x3b3b4658, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x57003d, 0xc100c0, 0xc300c2}, - {0x1e6, 0x407f007f, 0x3a3a4657, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x58003d, 0xc100c0, 0xc300c2}, - {0x1e7, 0x407f007f, 0x4040465d, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x59003d, 0xc100c0, 0xc300c2}, - {0x1e8, 0x407f007f, 0x4141004d, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x5a003d, 0xc100c0, 0xc300c2}, - {0x1f0, 0x407f007f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x5b003e, 0xc100c0, 0xc300c2}, - {0x1f1, 0x407f007f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x5c003e, 0xc100c0, 0xc300c2}, - {0x1f2, 0x407f007f, 0x3e3e465b, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x5d003e, 0xc100c0, 0xc300c2}, - {0x1f3, 0x407f007f, 0x3f3f465c, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x5e003e, 0xc100c0, 0xc300c2}, - {0x200, 0x407f007f, 0x25254642, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x5f003f, 0xc100c0, 0xc300c2}, - {0x201, 0x407f007f, 0x26264643, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x60003f, 0xc100c0, 0xc300c2}, - {0x202, 0x407f007f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x61003f, 0xc100c0, 0xc300c2}, - {0x203, 0x407f007f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x62003f, 0xc100c0, 0xc300c2}, - {0x210, 0x407f007f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x630040, 0xc100c0, 0xc300c2}, - {0x211, 0x407f007f, 0x3e3e465b, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x640040, 0xc100c0, 0xc300c2}, - {0x212, 0x407f007f, 0x27274644, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x650040, 0xc100c0, 0xc300c2}, - {0x213, 0x407f007f, 0x3f3f465c, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x660040, 0xc100c0, 0xc300c2}, - {0x220, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x670041, 0xc100c0, 0xc300c2}, - {0x221, 0x407f007f, 0x4040465d, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x680041, 0xc100c0, 0xc300c2}, - {0x230, 0x407f047f, 0x7f004667, 0x0, 0xb2b17f7f, 0xdfefaaff, 0x690042, 0xc100c0, 0xc300c2}, - {0x240, 0x7f0404, 0x53000048, 0x0, 0xb2b10000, 0xdfeb80ff, 0x4a0046, 0xc100c0, 0xc300c2}, - {0x241, 0x7f7f0404, 0x7f540048, 0x0, 0xb2b10000, 0xdfeb80ff, 0x4a0046, 0xc100c0, 0xc300c2}, - {0x250, 0x7f0064, 0x30304659, 0x0, 0xb2b10101, 0xdff080ff, 0x32004b, 0xc100c0, 0xc300c2}, - {0x251, 0x7f7f0464, 0x3c3c4665, 0x0, 0xb2b10101, 0xdff080ff, 0x33004b, 0xc100c0, 0xc300c2}, - {0x252, 0x407f0464, 0x48484671, 0x0, 0xb2b10101, 0xdff080ff, 0x33004b, 0xc100c0, 0xc300c2}, - {0x260, 0x407f044f, 0x7f00465a, 0x0, 0xb2b10101, 0x5ff0c0ff, 0x34004c, 0xc100c0, 0xc300c2}, - {0x270, 0x407f0454, 0x3f3f004b, 0x0, 0xb2b10000, 0xdfeb80ff, 0x36004d, 0xc100c0, 0xc300c2}, - {0x271, 0x407f005a, 0x3b00003c, 0x0, 0xb2b10000, 0xdfeb80ff, 0x35004d, 0xc100c0, 0xc300c2}, - {0x272, 0x406e0055, 0x3c3c0048, 0x0, 0xb2b10000, 0xdfeb80ff, 0x6f004d, 0xc100c0, 0xc300c2}, - {0x280, 0x407f0464, 0x47005a49, 0x0, 0xb2b10000, 0xdff0a0ff, 0x3c004e, 0xc100c0, 0xc300c2}, - {0x281, 0x407f0464, 0x7f48005d, 0x0, 0xb2b10000, 0xdfeb80ff, 0x3d004e, 0xc100c0, 0xc300c2}, - {0x290, 0x407f0455, 0x7f250048, 0x0, 0xb2b10000, 0xdfeb80ff, 0x3e004f, 0xc100c0, 0xc300c2}, - {0x291, 0x6464044f, 0x24230048, 0x0, 0xb2b10000, 0xdfeb80ff, 0x3e004f, 0xc100c0, 0xc300c2}, - {0x292, 0x1b640405, 0x2200003c, 0x0, 0xb2b10000, 0xdfeb80ff, 0x3e004f, 0xc100c0, 0xc300c2}, - {0x2a0, 0x6e047f, 0x7f005a48, 0x0, 0xb2b10000, 0xdfe780ff, 0x3f0050, 0xc100c0, 0xc300c2}, - {0x2a1, 0x7f6e047f, 0x7f006148, 0x0, 0xb2b10000, 0xdfe780ff, 0x3f0050, 0xc100c0, 0xc300c2}, - {0x2b0, 0x407f047f, 0x7f00465a, 0x0, 0xb2b10000, 0x5febb4ff, 0x340051, 0xc100c0, 0xc300c2}}}; + {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 = {{ - {0x0, 0x7f047f, 0x3e006a53, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10000, 0xc100c0, 0xc300c2}, - {0x1, 0x1b380400, 0x3e006a5f, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10000, 0xc100c0, 0xc300c2}, - {0x2, 0x64500400, 0x3e006a66, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10000, 0xc100c0, 0xc300c2}, - {0x3, 0x40280400, 0x3e006a72, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10000, 0xc100c0, 0xc300c2}, - {0x4, 0x7f7f047f, 0x3e006a54, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10000, 0xc100c0, 0xc300c2}, - {0x5, 0x407f005b, 0x42420050, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x1a0000, 0xc100c0, 0xc300c2}, - {0x6, 0x407f005b, 0x4343004f, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0xd0000, 0xc100c0, 0xc300c2}, - {0x7, 0x407f0464, 0x3e006a4d, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x10000, 0xc100c0, 0xc300c2}, - {0x8, 0x407f007f, 0x4848003c, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x290000, 0xc100c0, 0xc300c2}, - {0x9, 0x407f007f, 0x49490049, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x2a0000, 0xc100c0, 0xc300c2}, - {0x10, 0x407f007f, 0x3a3a0046, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x220001, 0xc100c0, 0xc300c2}, - {0x11, 0x407f007f, 0x3b3b0045, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x30001, 0xc100c0, 0xc300c2}, - {0x12, 0x407f007f, 0x48480060, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x3b0001, 0xc100c0, 0xc300c2}, - {0x13, 0x407f007f, 0x48480060, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x20001, 0xc100c0, 0xc300c2}, - {0x14, 0x7f007f, 0x48480048, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x20001, 0xc100c0, 0xc300c2}, - {0x15, 0x7f640051, 0x4848006c, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x20001, 0xc100c0, 0xc300c2}, - {0x16, 0x640051, 0x4848326b, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x20001, 0xc100c0, 0xc300c2}, - {0x17, 0x407f007f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x390001, 0xc100c0, 0xc300c2}, - {0x18, 0x407f007f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x3a0001, 0xc100c0, 0xc300c2}, - {0x19, 0x407f0000, 0x39390045, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x560001, 0xc100c0, 0xc300c2}, - {0x20, 0x40150464, 0x3c3c6a66, 0x0, 0xb2b17f7f, 0xdaeb80ff, 0x10002, 0xc100c0, 0xc300c2}, - {0x21, 0x3f5f0400, 0x3b006a4e, 0x0, 0xb2b17f7f, 0xdfef80ff, 0x10002, 0xc100c0, 0xc300c2}, - {0x22, 0x540400, 0x3b006a54, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x10002, 0xc100c0, 0xc300c2}, - {0x23, 0x7f540400, 0x3b006a56, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x10002, 0xc100c0, 0xc300c2}, - {0x24, 0x7f007f, 0x3c3c003c, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x2e0002, 0xc100c0, 0xc300c2}, - {0x25, 0x7f7f007f, 0x3c3c003c, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x2f0002, 0xc100c0, 0xc300c2}, - {0x26, 0x4046007f, 0x3c3c003c, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x300002, 0xc100c0, 0xc300c2}, - {0x27, 0x407f007f, 0x40400040, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x310002, 0xc100c0, 0xc300c2}, - {0x30, 0x407f007f, 0x3b3b0047, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x210003, 0xc100c0, 0xc300c2}, - {0x31, 0x407f007f, 0x3a3a0052, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x100003, 0xc100c0, 0xc300c2}, - {0x32, 0x407f007f, 0x43430048, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x110003, 0xc100c0, 0xc300c2}, - {0x33, 0x407f007f, 0x48480054, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x280003, 0xc100c0, 0xc300c2}, - {0x34, 0x407f007f, 0x39390051, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x320003, 0xc100c0, 0xc300c2}, - {0x40, 0x407f0050, 0x24240030, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x120004, 0xc100c0, 0xc300c2}, - {0x41, 0x407f0050, 0x25250031, 0x0, 0xb2b17f7f, 0xdffea5ff, 0x120004, 0xc100c0, 0xc300c2}, - {0x42, 0x407f0050, 0x26260032, 0x0, 0xb2b17f7f, 0xdffeaaff, 0x120004, 0xc100c0, 0xc300c2}, - {0x43, 0x407f007f, 0x3b3b0047, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x200004, 0xc100c0, 0xc300c2}, - {0x44, 0x407f007f, 0x48480060, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x3b0004, 0xc100c0, 0xc300c2}, - {0x45, 0x407f007f, 0x48480054, 0x0, 0xb2b17f7f, 0xdffeacff, 0x280004, 0xc100c0, 0xc300c2}, - {0x50, 0x407f0050, 0x25250030, 0x0, 0xb2b17f7f, 0x5fe580ff, 0x230005, 0xc100c0, 0xc300c2}, - {0x51, 0x407f007f, 0x3f3f005a, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x40005, 0xc100c0, 0xc300c2}, - {0x52, 0x407f007f, 0x4040005a, 0x0, 0xb2b17f7f, 0x5fed80ff, 0x50005, 0xc100c0, 0xc300c2}, - {0x53, 0x407f003c, 0x41410059, 0x0, 0xb2b17f7f, 0x5fef80ff, 0x50005, 0xc100c0, 0xc300c2}, - {0x54, 0x407f003b, 0x42420058, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x50005, 0xc100c0, 0xc300c2}, - {0x55, 0x407f007f, 0x43434663, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x160005, 0xc100c0, 0xc300c2}, - {0x56, 0x407f007f, 0x3d3d4661, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x7e0005, 0xc100c0, 0xc300c2}, - {0x60, 0x4064047f, 0x3c3c0060, 0x0, 0xb2b17f7f, 0xdff380ff, 0x60006, 0xc100c0, 0xc300c2}, - {0x61, 0x4064047f, 0x3c3c0078, 0x0, 0xb2b17f7f, 0xdff580ff, 0x70006, 0xc100c0, 0xc300c2}, - {0x62, 0x4053047f, 0x3c3c006c, 0x0, 0xb2b17f7f, 0x5170bebf, 0x2b0006, 0xc100c0, 0xc300c2}, - {0x63, 0x406c047f, 0x3535006c, 0x0, 0xb2b17f7f, 0x5ff580ff, 0x70006, 0xc100c0, 0xc300c2}, - {0x64, 0x406c047f, 0x35350054, 0x0, 0xb2b17f7f, 0x5ff380ff, 0x60006, 0xc100c0, 0xc300c2}, - {0x65, 0x406c047f, 0x3636006d, 0x0, 0xb2b17f7f, 0x5ff580ff, 0x70006, 0xc100c0, 0xc300c2}, - {0x66, 0x406c047f, 0x36360055, 0x0, 0xb2b17f7f, 0x5ff380ff, 0x60006, 0xc100c0, 0xc300c2}, - {0x67, 0x4064047f, 0x3030006c, 0x0, 0xb2b17f7f, 0x5ff580ff, 0x70006, 0xc100c0, 0xc300c2}, - {0x68, 0x4053047f, 0x30300060, 0x0, 0xb2b17f7f, 0xd170bebf, 0x2b0006, 0xc100c0, 0xc300c2}, - {0x70, 0x407f0002, 0x5a490054, 0x0, 0xb2b17f7f, 0x5fe081ff, 0x1b0007, 0xc100c0, 0xc300c2}, - {0x71, 0x407f0402, 0x48480054, 0x0, 0xb2b17f7f, 0x5fe580ff, 0x80007, 0xc100c0, 0xc300c2}, - {0x72, 0x407f0402, 0x47477853, 0x0, 0xb2b17f7f, 0x5fe580ff, 0x80007, 0xc100c0, 0xc300c2}, - {0x73, 0x357f0000, 0x3f3c0048, 0x0, 0xb2b17f7f, 0x5fea85ff, 0x90007, 0xc100c0, 0xc300c2}, - {0x74, 0x497f0000, 0x3b3a0044, 0x0, 0xb2b17f7f, 0x5fea82ff, 0xa0007, 0xc100c0, 0xc300c2}, - {0x75, 0x407f007f, 0x40400059, 0x0, 0xb2b17f7f, 0x5fef80ff, 0x250007, 0xc100c0, 0xc300c2}, - {0x80, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0xdfe080ff, 0xb0008, 0xc100c0, 0xc300c2}, - {0x81, 0x407f007f, 0x39390054, 0x0, 0xb2b17f7f, 0x5fef80ff, 0xe0008, 0xc100c0, 0xc300c2}, - {0x82, 0x407f007f, 0x3a3a0053, 0x0, 0xb2b17f7f, 0x5fe0b0ff, 0xf0008, 0xc100c0, 0xc300c2}, - {0x83, 0x407f0064, 0x3d3d0059, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x140008, 0xc100c0, 0xc300c2}, - {0x90, 0x4064007f, 0x2727003c, 0x0, 0xb2b17f7f, 0x5ffd80ff, 0x150009, 0xc100c0, 0xc300c2}, - {0x91, 0x407f007f, 0x2626003e, 0x0, 0xb2b17f7f, 0x5ffd80ff, 0x130009, 0xc100c0, 0xc300c2}, - {0x92, 0x405a007f, 0x27270044, 0x0, 0xb2b17f7f, 0x5ffd80ff, 0x240009, 0xc100c0, 0xc300c2}, - {0x93, 0x407f007f, 0x28280040, 0x0, 0xb2b17f7f, 0x5ffc80ff, 0x1c0009, 0xc100c0, 0xc300c2}, - {0x94, 0x407f0065, 0x2525003d, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x2c0009, 0xc100c0, 0xc300c2}, - {0x95, 0x407f007f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x2d0009, 0xc100c0, 0xc300c2}, - {0x96, 0x407f0000, 0x3d3d465a, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x800009, 0xc100c0, 0xc300c2}, - {0x97, 0x407f0000, 0x2424003c, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x2c0009, 0xc100c0, 0xc300c2}, - {0x98, 0x407f0000, 0x29290041, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x1c0009, 0xc100c0, 0xc300c2}, - {0xa0, 0x407f007f, 0x3d3d6a48, 0x0, 0xb2b17f7f, 0x5fcf80ff, 0xc000a, 0xc100c0, 0xc300c2}, - {0xa1, 0x407f007f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x36000a, 0xc100c0, 0xc300c2}, - {0xb0, 0x407f0064, 0x3c3c0048, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x17000b, 0xc100c0, 0xc300c2}, - {0xb1, 0x407f0064, 0x3f3f004b, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x18000b, 0xc100c0, 0xc300c2}, - {0xb2, 0x407f0064, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x3c000b, 0xc100c0, 0xc300c2}, - {0xb3, 0x407f0064, 0x3e3e0056, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x3d000b, 0xc100c0, 0xc300c2}, - {0xb4, 0x407f0064, 0x41410059, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x3e000b, 0xc100c0, 0xc300c2}, - {0xb5, 0x407f0064, 0x4040465d, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x49000b, 0xc100c0, 0xc300c2}, - {0xb6, 0x407f0064, 0x4242465f, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x4a000b, 0xc100c0, 0xc300c2}, - {0xc0, 0x407f007f, 0x3b3b4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x570011, 0xc100c0, 0xc300c2}, - {0xd0, 0x407f007f, 0x30300048, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x480013, 0xc100c0, 0xc300c2}, - {0xe0, 0x407f007e, 0x7f006a53, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x10015, 0xc100c0, 0xc300c2}, - {0xf0, 0x4032007f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x1d0017, 0xc100c0, 0xc300c2}, - {0xf1, 0x4046007f, 0x3f3d0055, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x1e0017, 0xc100c0, 0xc300c2}, - {0xf2, 0x4046001b, 0x40400055, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x1f0017, 0xc100c0, 0xc300c2}, - {0xf3, 0x4032007f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x1d0017, 0xc100c0, 0xc300c2}, - {0xf4, 0x4046007f, 0x3f3d0055, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x1e0017, 0xc100c0, 0xc300c2}, - {0xf5, 0x4046007f, 0x40400055, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x1f0017, 0xc100c0, 0xc300c2}, - {0xf6, 0x4032007f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x1d0017, 0xc100c0, 0xc300c2}, - {0xf7, 0x4032007f, 0x3e3e0056, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x1d0017, 0xc100c0, 0xc300c2}, - {0xf8, 0x4032007f, 0x3f3f0057, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x1d0017, 0xc100c0, 0xc300c2}, - {0xf9, 0x4032007f, 0x40400058, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x1d0017, 0xc100c0, 0xc300c2}, - {0x100, 0x407f047f, 0x3e3e0073, 0x0, 0xb2b17f7f, 0xceab80ff, 0x380018, 0xc100c0, 0xc300c2}, - {0x110, 0x2c7f007f, 0x24240030, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x270019, 0xc100c0, 0xc300c2}, - {0x111, 0x407f007f, 0x25250031, 0x0, 0xb2b17f7f, 0x5ffd80ff, 0x260019, 0xc100c0, 0xc300c2}, - {0x112, 0x547f007f, 0x24246e31, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x270019, 0xc100c0, 0xc300c2}, - {0x120, 0x407f0400, 0x7f000054, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x35001b, 0xc100c0, 0xc300c2}, - {0x130, 0x407f007f, 0x7f004659, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x58001c, 0xc100c0, 0xc300c2}, - {0x140, 0x407f0052, 0x3c3c0054, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x47001f, 0xc100c0, 0xc300c2}, - {0x150, 0x407f047f, 0x2424003c, 0x0, 0xb2b17f7f, 0x5feb80ff, 0x380021, 0xc100c0, 0xc300c2}, - {0x160, 0x407f007f, 0x3c3c0060, 0x0, 0xb2b17f7f, 0x5fe080ff, 0x460026, 0xc100c0, 0xc300c2}, - {0x170, 0x407f007f, 0x41410456, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x4b0027, 0xc100c0, 0xc300c2}, - {0x180, 0x407f0400, 0x2424003c, 0x0, 0xb2b10000, 0x5fc380ff, 0x4c002b, 0xc100c0, 0xc300c2}, - {0x181, 0x407f0400, 0x2525003d, 0x0, 0xb2b10000, 0xdfe380ff, 0x4d002b, 0xc100c0, 0xc300c2}, - {0x182, 0x407f0400, 0x2626003e, 0x0, 0xb2b10000, 0xdfe380ff, 0x4e002b, 0xc100c0, 0xc300c2}, - {0x183, 0x407f0000, 0x2727003f, 0x0, 0xb2b10000, 0xdfe380ff, 0x4f002b, 0xc100c0, 0xc300c2}, - {0x184, 0x407f0400, 0x28280040, 0x0, 0xb2b10000, 0xdfe380ff, 0x50002b, 0xc100c0, 0xc300c2}, - {0x185, 0x407f0000, 0x29290041, 0x0, 0xb2b10000, 0xdfe380ff, 0x51002b, 0xc100c0, 0xc300c2}, - {0x186, 0x407f0400, 0x2a2a0042, 0x0, 0xb2b10000, 0xdfe380ff, 0x52002b, 0xc100c0, 0xc300c2}, - {0x187, 0x407f0000, 0x2b2b0043, 0x0, 0xb2b10000, 0xdfe380ff, 0x53002b, 0xc100c0, 0xc300c2}, - {0x188, 0x407f0400, 0x2c2c0044, 0x0, 0xb2b10000, 0xdfe380ff, 0x54002b, 0xc100c0, 0xc300c2}, - {0x189, 0x407f0000, 0x2d2d0045, 0x0, 0xb2b10000, 0xdfe380ff, 0x55002b, 0xc100c0, 0xc300c2}, - {0x190, 0x407f0040, 0x3d3d3c56, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x7f0031, 0xc100c0, 0xc300c2}, - {0x1a0, 0x407f007f, 0x7f000048, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x5d003a, 0xc100c0, 0xc300c2}, - {0x1b0, 0x407f007f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x5e003b, 0xc100c0, 0xc300c2}, - {0x1b1, 0x407f007f, 0x3e3e465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x5f003b, 0xc100c0, 0xc300c2}, - {0x1b2, 0x407f007f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x60003b, 0xc100c0, 0xc300c2}, - {0x1b3, 0x407f007f, 0x3f3f005b, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x61003b, 0xc100c0, 0xc300c2}, - {0x1c0, 0x407f007f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x62003c, 0xc100c0, 0xc300c2}, - {0x1c1, 0x407f007f, 0x3f3f465c, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x63003c, 0xc100c0, 0xc300c2}, - {0x1d0, 0x407f007f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x64003d, 0xc100c0, 0xc300c2}, - {0x1d1, 0x407f007f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x65003d, 0xc100c0, 0xc300c2}, - {0x1d2, 0x407f007f, 0x37374653, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x66003d, 0xc100c0, 0xc300c2}, - {0x1d3, 0x407f007f, 0x38384655, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x67003d, 0xc100c0, 0xc300c2}, - {0x1d4, 0x407f007f, 0x39394656, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x68003d, 0xc100c0, 0xc300c2}, - {0x1d5, 0x407f007f, 0x3b3b4658, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x69003d, 0xc100c0, 0xc300c2}, - {0x1d6, 0x407f007f, 0x3a3a4657, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x6a003d, 0xc100c0, 0xc300c2}, - {0x1d7, 0x407f007f, 0x4040465d, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x6b003d, 0xc100c0, 0xc300c2}, - {0x1d8, 0x407f007f, 0x4141004d, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x6c003d, 0xc100c0, 0xc300c2}, - {0x1e0, 0x407f007f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x6d003e, 0xc100c0, 0xc300c2}, - {0x1e1, 0x407f007f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x6e003e, 0xc100c0, 0xc300c2}, - {0x1e2, 0x407f007f, 0x3e3e465b, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x6f003e, 0xc100c0, 0xc300c2}, - {0x1e3, 0x407f007f, 0x3f3f465c, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x70003e, 0xc100c0, 0xc300c2}, - {0x1f0, 0x407f007f, 0x25254642, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x71003f, 0xc100c0, 0xc300c2}, - {0x1f1, 0x407f007f, 0x26264643, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x72003f, 0xc100c0, 0xc300c2}, - {0x1f2, 0x407f007f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x73003f, 0xc100c0, 0xc300c2}, - {0x1f3, 0x407f007f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x74003f, 0xc100c0, 0xc300c2}, - {0x200, 0x407f007f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x750040, 0xc100c0, 0xc300c2}, - {0x201, 0x407f007f, 0x3e3e465b, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x760040, 0xc100c0, 0xc300c2}, - {0x202, 0x407f007f, 0x27274644, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x770040, 0xc100c0, 0xc300c2}, - {0x203, 0x407f007f, 0x3f3f465c, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x780040, 0xc100c0, 0xc300c2}, - {0x210, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x790041, 0xc100c0, 0xc300c2}, - {0x211, 0x407f007f, 0x4040465d, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x7a0041, 0xc100c0, 0xc300c2}, - {0x220, 0x407f047f, 0x7f004667, 0x0, 0xb2b17f7f, 0xdfefaaff, 0x7b0042, 0xc100c0, 0xc300c2}, - {0x230, 0x2c7f043c, 0x2f000048, 0x0, 0xb2b10000, 0xdff080ff, 0x7c0045, 0xc100c0, 0xc300c2}, - {0x231, 0x547f0450, 0x7f300048, 0x0, 0xb2b10000, 0xdff080ff, 0x7c0045, 0xc100c0, 0xc300c2}, - {0x240, 0x7f0404, 0x53000048, 0x0, 0xb2b10000, 0xdfeb80ff, 0x70046, 0xc100c0, 0xc300c2}, - {0x241, 0x7f7f0404, 0x7f540048, 0x0, 0xb2b10000, 0xdfeb80ff, 0x70046, 0xc100c0, 0xc300c2}, - {0x250, 0x407f0464, 0x7f000454, 0x0, 0xb2b10000, 0x5ff080ff, 0x440047, 0xc100c0, 0xc300c2}, - {0x260, 0x407f0464, 0x30000440, 0x0, 0xb2b10000, 0x5ff080ff, 0x400048, 0xc100c0, 0xc300c2}, - {0x261, 0x407f0464, 0x40310458, 0x0, 0xb2b10000, 0x5feb80ff, 0x410048, 0xc100c0, 0xc300c2}, - {0x262, 0x407f0464, 0x4e41045f, 0x0, 0xb2b10000, 0x5feb80ff, 0x420048, 0xc100c0, 0xc300c2}, - {0x263, 0x407f0464, 0x7f4f045f, 0x0, 0xb2b10000, 0x5feb80ff, 0x430048, 0xc100c0, 0xc300c2}, - {0x270, 0x407f0464, 0x7f000467, 0x0, 0xb2b10000, 0x5fee80ff, 0x3f0049, 0xc100c0, 0xc300c2}, - {0x280, 0x407f0454, 0x3f3f004b, 0x0, 0xb2b10000, 0xdfeb80ff, 0x33004d, 0xc100c0, 0xc300c2}, - {0x281, 0x407f005a, 0x3b00003c, 0x0, 0xb2b10000, 0xdfeb80ff, 0x34004d, 0xc100c0, 0xc300c2}, - {0x282, 0x406e0055, 0x3c3c0048, 0x0, 0xb2b10000, 0xdfeb80ff, 0x7d004d, 0xc100c0, 0xc300c2}, - {0x290, 0x407f0464, 0x47005a49, 0x0, 0xb2b10000, 0xdff0a0ff, 0x59004e, 0xc100c0, 0xc300c2}, - {0x291, 0x407f0464, 0x7f48005d, 0x0, 0xb2b10000, 0xdfeb80ff, 0x5a004e, 0xc100c0, 0xc300c2}, - {0x2a0, 0x407f0455, 0x7f250048, 0x0, 0xb2b10000, 0xdfeb80ff, 0x5b004f, 0xc100c0, 0xc300c2}, - {0x2a1, 0x6464044f, 0x24230048, 0x0, 0xb2b10000, 0xdfeb80ff, 0x5b004f, 0xc100c0, 0xc300c2}, - {0x2a2, 0x1b640405, 0x2200003c, 0x0, 0xb2b10000, 0xdfeb80ff, 0x5b004f, 0xc100c0, 0xc300c2}, - {0x2b0, 0x6e047f, 0x7f005a48, 0x0, 0xb2b10000, 0xdfe780ff, 0x370050, 0xc100c0, 0xc300c2}, - {0x2b1, 0x7f6e047f, 0x7f006148, 0x0, 0xb2b10000, 0xdfe780ff, 0x370050, 0xc100c0, 0xc300c2}, - {0x2c0, 0x407f047f, 0x7f00465a, 0x0, 0xb2b10000, 0xdfebb4ff, 0x5c0051, 0xc100c0, 0xc300c2}, - {0x2d0, 0x7f0464, 0x3c3c0448, 0x0, 0xb2b10000, 0x5feba5ff, 0x450054, 0xc100c0, 0xc300c2}, - {0x2d1, 0x7f7f0464, 0x3d3d0449, 0x0, 0xb2b10000, 0x5feba5ff, 0x450054, 0xc100c0, 0xc300c2}, - {0x2d2, 0x7f0464, 0x3e3e044a, 0x0, 0xb2b10000, 0x5feb80ff, 0x450054, 0xc100c0, 0xc300c2}, - {0x2d3, 0x7f7f0464, 0x3f3f044b, 0x0, 0xb2b10000, 0x5feb80ff, 0x450054, 0xc100c0, 0xc300c2}}}; + {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_VH = {{ - {0x0, 0x7f047f, 0x3e006a53, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10000, 0xc100c0, 0xc300c2}, - {0x1, 0x1b380400, 0x3e006a5f, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10000, 0xc100c0, 0xc300c2}, - {0x2, 0x64500400, 0x3e006a66, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10000, 0xc100c0, 0xc300c2}, - {0x3, 0x40280400, 0x3e006a72, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10000, 0xc100c0, 0xc300c2}, - {0x4, 0x7f7f047f, 0x3e006a54, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10000, 0xc100c0, 0xc300c2}, - {0x5, 0x407f045b, 0x42420050, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0xf0000, 0xc100c0, 0xc300c2}, - {0x6, 0x407f045b, 0x4343004f, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x70000, 0xc100c0, 0xc300c2}, - {0x7, 0x407f0464, 0x3e006a4d, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x10000, 0xc100c0, 0xc300c2}, - {0x8, 0x407f047f, 0x4848003c, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x200000, 0xc100c0, 0xc300c2}, - {0x9, 0x407f047f, 0x49490049, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x210000, 0xc100c0, 0xc300c2}, - {0x10, 0x407f047f, 0x3a3a0046, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x180001, 0xc100c0, 0xc300c2}, - {0x11, 0x407f047f, 0x3b3b0045, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x30001, 0xc100c0, 0xc300c2}, - {0x12, 0x407f047f, 0x48480060, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x400001, 0xc100c0, 0xc300c2}, - {0x13, 0x407f047f, 0x48480060, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x20001, 0xc100c0, 0xc300c2}, - {0x14, 0x7f047f, 0x48480048, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x20001, 0xc100c0, 0xc300c2}, - {0x15, 0x7f640451, 0x4848006c, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x20001, 0xc100c0, 0xc300c2}, - {0x16, 0x640451, 0x4848326b, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x20001, 0xc100c0, 0xc300c2}, - {0x17, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x3e0001, 0xc100c0, 0xc300c2}, - {0x18, 0x407f047f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x3f0001, 0xc100c0, 0xc300c2}, - {0x19, 0x407f0400, 0x39390045, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x600001, 0xc100c0, 0xc300c2}, - {0x20, 0x40150464, 0x3c3c6a66, 0x0, 0xb2b17f7f, 0xdaeb80ff, 0x10002, 0xc100c0, 0xc300c2}, - {0x21, 0x3f5f0400, 0x3b006a4e, 0x0, 0xb2b17f7f, 0x5fef80ff, 0x10002, 0xc100c0, 0xc300c2}, - {0x22, 0x540400, 0x3b006a54, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10002, 0xc100c0, 0xc300c2}, - {0x23, 0x7f540400, 0x3b006a56, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10002, 0xc100c0, 0xc300c2}, - {0x24, 0x7f007f, 0x3c3c003c, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x240002, 0xc100c0, 0xc300c2}, - {0x25, 0x7f7f007f, 0x3c3c003c, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x250002, 0xc100c0, 0xc300c2}, - {0x26, 0x4046047f, 0x3c3c003c, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x260002, 0xc100c0, 0xc300c2}, - {0x27, 0x407f047f, 0x40400040, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x270002, 0xc100c0, 0xc300c2}, - {0x30, 0x407f047f, 0x3b3b0047, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x150003, 0xc100c0, 0xc300c2}, - {0x31, 0x407f047f, 0x3a3a0052, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0xa0003, 0xc100c0, 0xc300c2}, - {0x32, 0x407f047f, 0x43430048, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0xb0003, 0xc100c0, 0xc300c2}, - {0x33, 0x407f047f, 0x48480054, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x1e0003, 0xc100c0, 0xc300c2}, - {0x34, 0x407f047f, 0x39390051, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x280003, 0xc100c0, 0xc300c2}, - {0x40, 0x407f0450, 0x24240030, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0xc0004, 0xc100c0, 0xc300c2}, - {0x41, 0x407f0450, 0x25250031, 0x0, 0xb2b17f7f, 0xdfeba5ff, 0xc0004, 0xc100c0, 0xc300c2}, - {0x42, 0x407f0450, 0x26260032, 0x0, 0xb2b17f7f, 0xdfebaaff, 0xc0004, 0xc100c0, 0xc300c2}, - {0x43, 0x407f047f, 0x3b3b0047, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x140004, 0xc100c0, 0xc300c2}, - {0x44, 0x407f007f, 0x48480060, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x400004, 0xc100c0, 0xc300c2}, - {0x45, 0x407f047f, 0x48480054, 0x0, 0xb2b17f7f, 0xdffeacff, 0x1e0004, 0xc100c0, 0xc300c2}, - {0x50, 0x4064047f, 0x3c3c0060, 0x0, 0xb2b17f7f, 0x5fd380ff, 0x570006, 0xc100c0, 0xc300c2}, - {0x51, 0x4064047f, 0x3c3c0078, 0x0, 0xb2b17f7f, 0xdff580ff, 0x40006, 0xc100c0, 0xc300c2}, - {0x52, 0x4053047f, 0x3c3c006c, 0x0, 0xb2b17f7f, 0xd170bebf, 0x840006, 0xc100c0, 0xc300c2}, - {0x53, 0x4064047f, 0x3030006c, 0x0, 0xb2b17f7f, 0xdff580ff, 0x40006, 0xc100c0, 0xc300c2}, - {0x54, 0x4053047f, 0x30300060, 0x0, 0xb2b17f7f, 0xd170bebf, 0x840006, 0xc100c0, 0xc300c2}, - {0x60, 0x357f0400, 0x3f3c0048, 0x0, 0xb2b17f7f, 0xdfea85ff, 0x160007, 0xc100c0, 0xc300c2}, - {0x61, 0x497f0400, 0x3b3a0044, 0x0, 0xb2b17f7f, 0x5fea82ff, 0x170007, 0xc100c0, 0xc300c2}, - {0x62, 0x407f047f, 0x4040465f, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x480007, 0xc100c0, 0xc300c2}, - {0x63, 0x407f0402, 0x2b240060, 0x0, 0xb2b10202, 0x5feb80ff, 0x60007, 0xc100c0, 0xc300c2}, - {0x64, 0x407f0402, 0x2d2c0074, 0x0, 0xb2b10202, 0x5feb80ff, 0xa0007, 0xc100c0, 0xc300c2}, - {0x65, 0x407f0402, 0x2e2e0060, 0x0, 0xb2b10202, 0x5ffd80ff, 0xb0007, 0xc100c0, 0xc300c2}, - {0x66, 0x407f0402, 0x2f2f0060, 0x0, 0xb2b10202, 0x5feb80ff, 0x1a0007, 0xc100c0, 0xc300c2}, - {0x67, 0x407f0402, 0x30300060, 0x0, 0xb2b10202, 0x5feb80ff, 0x1b0007, 0xc100c0, 0xc300c2}, - {0x68, 0x407f0402, 0x37310060, 0x0, 0xb2b10202, 0x5feb80ff, 0x280007, 0xc100c0, 0xc300c2}, - {0x69, 0x407f0402, 0x3938006c, 0x0, 0xb2b10202, 0x5feb80ff, 0x310007, 0xc100c0, 0xc300c2}, - {0x6a, 0x407f0402, 0x4341007f, 0x0, 0xb2b10202, 0x5feb80ff, 0x350007, 0xc100c0, 0xc300c2}, - {0x6c, 0x407f0402, 0x4b45007f, 0x0, 0xb2b10202, 0x5feb80ff, 0x3b0007, 0xc100c0, 0xc300c2}, - {0x6d, 0x407f0402, 0x4c4c0058, 0x0, 0xb2b10202, 0xdfeb80ff, 0x460007, 0xc100c0, 0xc300c2}, - {0x6e, 0x407f0402, 0x22220022, 0x0, 0xb2b10202, 0xdfe380ff, 0x110007, 0xc100c0, 0xc300c2}, - {0x6f, 0x407f0402, 0x23230023, 0x0, 0xb2b10202, 0xdfe380ff, 0x120007, 0xc100c0, 0xc300c2}, - {0x70, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x50008, 0xc100c0, 0xc300c2}, - {0x71, 0x407f047f, 0x39390054, 0x0, 0xb2b17f7f, 0xdfef80ff, 0x80008, 0xc100c0, 0xc300c2}, - {0x72, 0x407f047f, 0x3a3a0053, 0x0, 0xb2b17f7f, 0x5fe0b0ff, 0x90008, 0xc100c0, 0xc300c2}, - {0x80, 0x4064047f, 0x2727003c, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0xe0009, 0xc100c0, 0xc300c2}, - {0x81, 0x407f047f, 0x2626003e, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0xd0009, 0xc100c0, 0xc300c2}, - {0x82, 0x405a047f, 0x27270044, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x190009, 0xc100c0, 0xc300c2}, - {0x83, 0x407f047f, 0x28280040, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x100009, 0xc100c0, 0xc300c2}, - {0x84, 0x407f0465, 0x2525003d, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x220009, 0xc100c0, 0xc300c2}, - {0x85, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x230009, 0xc100c0, 0xc300c2}, - {0x86, 0x407f0400, 0x3d3d465a, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x860009, 0xc100c0, 0xc300c2}, - {0x87, 0x407f0400, 0x2424003c, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x220009, 0xc100c0, 0xc300c2}, - {0x88, 0x407f0400, 0x29290041, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x100009, 0xc100c0, 0xc300c2}, - {0x90, 0x407f047f, 0x3d3d6a48, 0x0, 0xb2b17f7f, 0xdfef80ff, 0x6000a, 0xc100c0, 0xc300c2}, - {0x91, 0x407f047f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x35000a, 0xc100c0, 0xc300c2}, - {0xa0, 0x407f0464, 0x3d3d0055, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x41000b, 0xc100c0, 0xc300c2}, - {0xa1, 0x407f0464, 0x3c3c0454, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x4d000b, 0xc100c0, 0xc300c2}, - {0xa2, 0x407f0464, 0x3f3f0457, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x4e000b, 0xc100c0, 0xc300c2}, - {0xb0, 0x407f0400, 0x26260026, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x1b000c, 0xc100c0, 0xc300c2}, - {0xb1, 0x407f047f, 0x27270027, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x1a000c, 0xc100c0, 0xc300c2}, - {0xb2, 0x407f0464, 0x28280040, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x29000c, 0xc100c0, 0xc300c2}, - {0xb3, 0x407f0464, 0x29290041, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x2a000c, 0xc100c0, 0xc300c2}, - {0xb4, 0x407f0464, 0x2a2a0042, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x2b000c, 0xc100c0, 0xc300c2}, - {0xb5, 0x407f0464, 0x2c2c0044, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x33000c, 0xc100c0, 0xc300c2}, - {0xc0, 0x407f047f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0xdff080ff, 0x3c000d, 0xc100c0, 0xc300c2}, - {0xd0, 0x407f0450, 0x24240024, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x11000e, 0xc100c0, 0xc300c2}, - {0xd1, 0x407f0450, 0x25250025, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x12000e, 0xc100c0, 0xc300c2}, - {0xd2, 0x407f0450, 0x3d3d4659, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x1f000e, 0xc100c0, 0xc300c2}, - {0xd3, 0x407f0450, 0x3e3e465a, 0x0, 0xb2b17f7f, 0xdfe6aaff, 0x1f000e, 0xc100c0, 0xc300c2}, - {0xe0, 0x407f047f, 0x3c3c003c, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x13000f, 0xc100c0, 0xc300c2}, - {0xe1, 0x407f047f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x60000f, 0xc100c0, 0xc300c2}, - {0xe2, 0x407f007f, 0x3c3c0048, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x50000f, 0xc100c0, 0xc300c2}, - {0xf0, 0x407f047f, 0x3b3b4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x620011, 0xc100c0, 0xc300c2}, - {0x100, 0x407f0464, 0x2d2d0045, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x2c0012, 0xc100c0, 0xc300c2}, - {0x101, 0x407f047f, 0x2e2e0046, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x2d0012, 0xc100c0, 0xc300c2}, - {0x102, 0x407f0432, 0x2424003c, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x490012, 0xc100c0, 0xc300c2}, - {0x103, 0x407f0432, 0x2525003d, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x4a0012, 0xc100c0, 0xc300c2}, - {0x104, 0x407f0432, 0x2f2f464c, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x4b0012, 0xc100c0, 0xc300c2}, - {0x105, 0x407f0432, 0x3030464d, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x4c0012, 0xc100c0, 0xc300c2}, - {0x110, 0x2c7f047f, 0x24240060, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x610014, 0xc100c0, 0xc300c2}, - {0x111, 0x2c41047f, 0x24240454, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x570014, 0xc100c0, 0xc300c2}, - {0x112, 0x407f047f, 0x2b2b0060, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x610014, 0xc100c0, 0xc300c2}, - {0x113, 0x4041047f, 0x2b2b0454, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x570014, 0xc100c0, 0xc300c2}, - {0x114, 0x547f047f, 0x30300060, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x610014, 0xc100c0, 0xc300c2}, - {0x115, 0x5441047f, 0x30300454, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x570014, 0xc100c0, 0xc300c2}, - {0x120, 0x407f007e, 0x7f006a53, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x10015, 0xc100c0, 0xc300c2}, - {0x130, 0x407f0464, 0x3d3d004e, 0x0, 0xb2b17f7f, 0xdfe080ff, 0xa0016, 0xc100c0, 0xc300c2}, - {0x131, 0x407f0464, 0x3e3e0045, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x150016, 0xc100c0, 0xc300c2}, - {0x132, 0x407f0464, 0x3f3f0046, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x150016, 0xc100c0, 0xc300c2}, - {0x133, 0x407f0464, 0x3f3f0050, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x2a0016, 0xc100c0, 0xc300c2}, - {0x134, 0x3c7f0450, 0x2121002d, 0x0, 0xb2b17f7f, 0xdfe0a4ff, 0xc0016, 0xc100c0, 0xc300c2}, - {0x135, 0x407f0450, 0x2222002d, 0x0, 0xb2b17f7f, 0xdfe0a0ff, 0xc0016, 0xc100c0, 0xc300c2}, - {0x136, 0x3c7f0450, 0x2323002d, 0x0, 0xb2b17f7f, 0xdfe0a7ff, 0xc0016, 0xc100c0, 0xc300c2}, - {0x137, 0x407f0450, 0x2424002d, 0x0, 0xb2b17f7f, 0xdfe0aaff, 0xc0016, 0xc100c0, 0xc300c2}, - {0x138, 0x407f0450, 0x2525002d, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x310016, 0xc100c0, 0xc300c2}, - {0x140, 0x407f047f, 0x3e3e0073, 0x0, 0xb2b17f7f, 0xceab80ff, 0x3a0018, 0xc100c0, 0xc300c2}, - {0x150, 0x2c7f047f, 0x24240030, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x1d0019, 0xc100c0, 0xc300c2}, - {0x151, 0x407f047f, 0x25250031, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x1c0019, 0xc100c0, 0xc300c2}, - {0x152, 0x547f047f, 0x24246e31, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x1d0019, 0xc100c0, 0xc300c2}, - {0x160, 0x407f0400, 0x7f000054, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x32001b, 0xc100c0, 0xc300c2}, - {0x170, 0x407f047f, 0x7f004659, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x63001c, 0xc100c0, 0xc300c2}, - {0x180, 0x407f0464, 0x30300048, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x34001d, 0xc100c0, 0xc300c2}, - {0x190, 0x407f047f, 0x3c3c0048, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x55001e, 0xc100c0, 0xc300c2}, - {0x1a0, 0x407f0452, 0x3c3c465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x53001f, 0xc100c0, 0xc300c2}, - {0x1b0, 0x407f047f, 0x3d3d4666, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x560020, 0xc100c0, 0xc300c2}, - {0x1c0, 0x407f047f, 0x2424003c, 0x0, 0xb2b17f7f, 0x5feb80ff, 0x3a0021, 0xc100c0, 0xc300c2}, - {0x1d0, 0x407f047f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x3b0022, 0xc100c0, 0xc300c2}, - {0x1d1, 0x407f047f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0xdff080ff, 0x3c0022, 0xc100c0, 0xc300c2}, - {0x1d2, 0x407f047f, 0x3e3e006e, 0x0, 0xb2b17f7f, 0xd16db477, 0x540022, 0xc100c0, 0xc300c2}, - {0x1d3, 0x406b047f, 0x3e3e0075, 0x0, 0xb2b17f7f, 0xdff580ff, 0x40022, 0xc100c0, 0xc300c2}, - {0x1d4, 0x4041047f, 0x3e3e005d, 0x0, 0xb2b17f7f, 0xdff380ff, 0x570022, 0xc100c0, 0xc300c2}, - {0x1e0, 0x406e0463, 0x2424003c, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x890023, 0xc100c0, 0xc300c2}, - {0x1e1, 0x406e0463, 0x2525003d, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x8a0023, 0xc100c0, 0xc300c2}, - {0x1f0, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x3d0024, 0xc100c0, 0xc300c2}, - {0x1f1, 0x407f047f, 0x3c3c6a50, 0x0, 0xb2b17f7f, 0xc02080cf, 0x10024, 0xc100c0, 0xc300c2}, - {0x1f2, 0x407f047f, 0x3c3c6a48, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x140024, 0xc100c0, 0xc300c2}, - {0x1f3, 0x4037047f, 0x3c3c6a64, 0x0, 0xb2b17f7f, 0xc9a080e3, 0x10024, 0xc100c0, 0xc300c2}, - {0x200, 0x407f0446, 0x24244636, 0x0, 0xb2b17f7f, 0xdfebb4ff, 0x490025, 0xc100c0, 0xc300c2}, - {0x201, 0x407f0446, 0x25254636, 0x0, 0xb2b17f7f, 0xdfebaaff, 0x4a0025, 0xc100c0, 0xc300c2}, - {0x202, 0x407f0464, 0x3c3c0448, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x420025, 0xc100c0, 0xc300c2}, - {0x203, 0x407f0464, 0x3d3d0449, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x430025, 0xc100c0, 0xc300c2}, - {0x204, 0x407f0464, 0x3e3e044a, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x440025, 0xc100c0, 0xc300c2}, - {0x205, 0x407f0450, 0x4040044c, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x450025, 0xc100c0, 0xc300c2}, - {0x206, 0x407f0450, 0x4141044c, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x450025, 0xc100c0, 0xc300c2}, - {0x207, 0x407f0464, 0x4242044e, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x460025, 0xc100c0, 0xc300c2}, - {0x208, 0x407f0464, 0x4343044f, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x470025, 0xc100c0, 0xc300c2}, - {0x210, 0x407f047f, 0x3c3c0060, 0x0, 0xb2b17f7f, 0x5fe080ff, 0x4f0026, 0xc100c0, 0xc300c2}, - {0x220, 0x407f047f, 0x41410459, 0x0, 0xb2b17f7f, 0x5feb80ff, 0x500027, 0xc100c0, 0xc300c2}, - {0x230, 0x407f047f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0x5fe080ff, 0x510028, 0xc100c0, 0xc300c2}, - {0x231, 0x407f047f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x520028, 0xc100c0, 0xc300c2}, - {0x240, 0x407f047f, 0x3d3d0049, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x5f002c, 0xc100c0, 0xc300c2}, - {0x241, 0x407f0419, 0x3e3e465b, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x87002c, 0xc100c0, 0xc300c2}, - {0x242, 0x407f0419, 0x3f3f465c, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x88002c, 0xc100c0, 0xc300c2}, - {0x250, 0x407f0440, 0x3d3d3c56, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x850031, 0xc100c0, 0xc300c2}, - {0x260, 0x407f047f, 0x7f000048, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x64003a, 0xc100c0, 0xc300c2}, - {0x270, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x65003b, 0xc100c0, 0xc300c2}, - {0x271, 0x407f047f, 0x3e3e465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x66003b, 0xc100c0, 0xc300c2}, - {0x272, 0x407f047f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x67003b, 0xc100c0, 0xc300c2}, - {0x273, 0x407f047f, 0x3f3f005b, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x68003b, 0xc100c0, 0xc300c2}, - {0x280, 0x407f047f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x69003c, 0xc100c0, 0xc300c2}, - {0x281, 0x407f047f, 0x3f3f465c, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x6a003c, 0xc100c0, 0xc300c2}, - {0x290, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x6b003d, 0xc100c0, 0xc300c2}, - {0x291, 0x407f047f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x6c003d, 0xc100c0, 0xc300c2}, - {0x292, 0x407f047f, 0x37374653, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x6d003d, 0xc100c0, 0xc300c2}, - {0x293, 0x407f047f, 0x38384655, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x6e003d, 0xc100c0, 0xc300c2}, - {0x294, 0x407f047f, 0x39394656, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x6f003d, 0xc100c0, 0xc300c2}, - {0x295, 0x407f047f, 0x3b3b4658, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x70003d, 0xc100c0, 0xc300c2}, - {0x296, 0x407f047f, 0x3a3a4657, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x71003d, 0xc100c0, 0xc300c2}, - {0x297, 0x407f047f, 0x4040465d, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x72003d, 0xc100c0, 0xc300c2}, - {0x298, 0x407f047f, 0x4141004d, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x73003d, 0xc100c0, 0xc300c2}, - {0x2a0, 0x407f047f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x74003e, 0xc100c0, 0xc300c2}, - {0x2a1, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x75003e, 0xc100c0, 0xc300c2}, - {0x2a2, 0x407f047f, 0x3e3e465b, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x76003e, 0xc100c0, 0xc300c2}, - {0x2a3, 0x407f047f, 0x3f3f465c, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x77003e, 0xc100c0, 0xc300c2}, - {0x2b0, 0x407f047f, 0x25254642, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x78003f, 0xc100c0, 0xc300c2}, - {0x2b1, 0x407f047f, 0x26264643, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x79003f, 0xc100c0, 0xc300c2}, - {0x2b2, 0x407f047f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x7a003f, 0xc100c0, 0xc300c2}, - {0x2b3, 0x407f047f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x7b003f, 0xc100c0, 0xc300c2}, - {0x2c0, 0x407f047f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x7c0040, 0xc100c0, 0xc300c2}, - {0x2c1, 0x407f047f, 0x3e3e465b, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x7d0040, 0xc100c0, 0xc300c2}, - {0x2c2, 0x407f047f, 0x27274644, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x7e0040, 0xc100c0, 0xc300c2}, - {0x2c3, 0x407f047f, 0x3f3f465c, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x7f0040, 0xc100c0, 0xc300c2}, - {0x2d0, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x800041, 0xc100c0, 0xc300c2}, - {0x2d1, 0x407f047f, 0x4040465d, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x810041, 0xc100c0, 0xc300c2}, - {0x2e0, 0x407f047f, 0x7f004667, 0x0, 0xb2b17f7f, 0xdfefaaff, 0x820042, 0xc100c0, 0xc300c2}, - {0x2f0, 0x7f0404, 0x53000048, 0x0, 0xb2b10000, 0xdfeb80ff, 0x40046, 0xc100c0, 0xc300c2}, - {0x2f1, 0x7f7f0404, 0x7f540048, 0x0, 0xb2b10000, 0xdfeb80ff, 0x40046, 0xc100c0, 0xc300c2}, - {0x300, 0x40730464, 0x7f000460, 0x0, 0xb2b10000, 0xdff080ff, 0x580047, 0xc100c0, 0xc300c2}, - {0x310, 0x407f0464, 0x3000044c, 0x0, 0xb2b10000, 0xdff080ff, 0x590048, 0xc100c0, 0xc300c2}, - {0x311, 0x407f0464, 0x40310464, 0x0, 0xb2b10000, 0xdfeb80ff, 0x5a0048, 0xc100c0, 0xc300c2}, - {0x312, 0x407f0464, 0x4e41046b, 0x0, 0xb2b10000, 0xdfeb80ff, 0x5b0048, 0xc100c0, 0xc300c2}, - {0x313, 0x407f0464, 0x7f4f046b, 0x0, 0xb2b10000, 0xdfeb80ff, 0x5c0048, 0xc100c0, 0xc300c2}, - {0x320, 0x407f0464, 0x7f000473, 0x0, 0xb2b10000, 0xdfee80ff, 0x5d0049, 0xc100c0, 0xc300c2}, - {0x330, 0x407f0454, 0x3f3f004b, 0x0, 0xb2b10000, 0xdfeb80ff, 0x2f004d, 0xc100c0, 0xc300c2}, - {0x331, 0x407f005a, 0x3b00003c, 0x0, 0xb2b10000, 0xdfeb80ff, 0x30004d, 0xc100c0, 0xc300c2}, - {0x332, 0x406e0055, 0x3c3c0048, 0x0, 0xb2b10000, 0xdfeb80ff, 0x83004d, 0xc100c0, 0xc300c2}, - {0x340, 0x407f0464, 0x47005a49, 0x0, 0xb2b10000, 0xdff0a0ff, 0x36004e, 0xc100c0, 0xc300c2}, - {0x341, 0x407f0464, 0x7f48005d, 0x0, 0xb2b10000, 0xdfeb80ff, 0x37004e, 0xc100c0, 0xc300c2}, - {0x350, 0x407f0455, 0x7f250048, 0x0, 0xb2b10000, 0xdfeb80ff, 0x38004f, 0xc100c0, 0xc300c2}, - {0x351, 0x6464044f, 0x24230048, 0x0, 0xb2b10000, 0xdfeb80ff, 0x38004f, 0xc100c0, 0xc300c2}, - {0x352, 0x1b640405, 0x2200003c, 0x0, 0xb2b10000, 0xdfeb80ff, 0x38004f, 0xc100c0, 0xc300c2}, - {0x360, 0x6e047f, 0x7f005a48, 0x0, 0xb2b10000, 0xdfe780ff, 0x390050, 0xc100c0, 0xc300c2}, - {0x361, 0x7f6e047f, 0x7f006148, 0x0, 0xb2b10000, 0xdfe780ff, 0x390050, 0xc100c0, 0xc300c2}, - {0x370, 0x407f047f, 0x7f00465a, 0x0, 0xb2b10000, 0x5febb4ff, 0x2e0051, 0xc100c0, 0xc300c2}, - {0x380, 0x407f0464, 0x7f000454, 0x0, 0xb2b10000, 0xdfeb80ff, 0x5e0054, 0xc100c0, 0xc300c2}}}; + {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}, + {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_VH = {{ - {0x0, 0x40150464, 0x3c3c6a66, 0x0, 0xb2b17f7f, 0xdaeb80ff, 0x620002, 0xc100c0, 0xc300c2}, - {0x1, 0x3f5f0400, 0x3b006a4e, 0x0, 0xb2b17f7f, 0x5fef80ff, 0x620002, 0xc100c0, 0xc300c2}, - {0x2, 0x540400, 0x3b006a54, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x620002, 0xc100c0, 0xc300c2}, - {0x3, 0x7f540400, 0x3b006a56, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x620002, 0xc100c0, 0xc300c2}, - {0x10, 0x407f047f, 0x3b3b0047, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x100003, 0xc100c0, 0xc300c2}, - {0x11, 0x407f047f, 0x3a3a0052, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x60003, 0xc100c0, 0xc300c2}, - {0x12, 0x407f047f, 0x43430048, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x70003, 0xc100c0, 0xc300c2}, - {0x13, 0x407f047f, 0x48480054, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x160003, 0xc100c0, 0xc300c2}, - {0x14, 0x407f047f, 0x39390051, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x1a0003, 0xc100c0, 0xc300c2}, - {0x20, 0x407f0450, 0x24240030, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x80004, 0xc100c0, 0xc300c2}, - {0x21, 0x407f0450, 0x25250031, 0x0, 0xb2b17f7f, 0xdfeba5ff, 0x80004, 0xc100c0, 0xc300c2}, - {0x22, 0x407f0450, 0x26260032, 0x0, 0xb2b17f7f, 0xdfebaaff, 0x80004, 0xc100c0, 0xc300c2}, - {0x23, 0x407f047f, 0x3b3b0047, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0xf0004, 0xc100c0, 0xc300c2}, - {0x24, 0x407f007f, 0x48480060, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x260004, 0xc100c0, 0xc300c2}, - {0x25, 0x407f047f, 0x48480054, 0x0, 0xb2b17f7f, 0xdffeacff, 0x160004, 0xc100c0, 0xc300c2}, - {0x30, 0x4064047f, 0x3c3c0060, 0x0, 0xb2b17f7f, 0xdff380ff, 0x630006, 0xc100c0, 0xc300c2}, - {0x31, 0x4064047f, 0x3c3c0078, 0x0, 0xb2b17f7f, 0xdff080ff, 0x10006, 0xc100c0, 0xc300c2}, - {0x32, 0x4053047f, 0x3c3c006c, 0x0, 0xb2b17f7f, 0xd170bebf, 0x640006, 0xc100c0, 0xc300c2}, - {0x40, 0x357f0400, 0x3f3c0048, 0x0, 0xb2b17f7f, 0xdfea85ff, 0x110007, 0xc100c0, 0xc300c2}, - {0x41, 0x497f0400, 0x3b3a0044, 0x0, 0xb2b17f7f, 0x5fea82ff, 0x120007, 0xc100c0, 0xc300c2}, - {0x42, 0x407f047f, 0x4040465f, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x2e0007, 0xc100c0, 0xc300c2}, - {0x43, 0x407f0402, 0x2b240060, 0x0, 0xb2b10202, 0x5feb80ff, 0x30007, 0xc100c0, 0xc300c2}, - {0x44, 0x407f0402, 0x2d2c0074, 0x0, 0xb2b10202, 0x5feb80ff, 0x60007, 0xc100c0, 0xc300c2}, - {0x45, 0x407f0402, 0x2e2e0060, 0x0, 0xb2b10202, 0x5ffd80ff, 0x70007, 0xc100c0, 0xc300c2}, - {0x46, 0x407f0402, 0x2f2f0060, 0x0, 0xb2b10202, 0x5feb80ff, 0x140007, 0xc100c0, 0xc300c2}, - {0x47, 0x407f0402, 0x30300062, 0x0, 0xb2b10202, 0xdfeb80ff, 0x2a0007, 0xc100c0, 0xc300c2}, - {0x48, 0x407f0402, 0x37310060, 0x0, 0xb2b10202, 0x5feb80ff, 0x1a0007, 0xc100c0, 0xc300c2}, - {0x49, 0x407f0402, 0x3938006c, 0x0, 0xb2b10202, 0x5feb80ff, 0x1e0007, 0xc100c0, 0xc300c2}, - {0x4a, 0x407f0402, 0x4341007f, 0x0, 0xb2b10202, 0x5feb80ff, 0x1f0007, 0xc100c0, 0xc300c2}, - {0x4c, 0x407f0402, 0x4b45007f, 0x0, 0xb2b10202, 0x5feb80ff, 0x250007, 0xc100c0, 0xc300c2}, - {0x4d, 0x407f0402, 0x4c4c0058, 0x0, 0xb2b10202, 0xdfeb80ff, 0x2c0007, 0xc100c0, 0xc300c2}, - {0x4e, 0x407f0402, 0x22220022, 0x0, 0xb2b10202, 0xdfe380ff, 0xc0007, 0xc100c0, 0xc300c2}, - {0x4f, 0x407f0402, 0x23230023, 0x0, 0xb2b10202, 0xdfe380ff, 0xd0007, 0xc100c0, 0xc300c2}, - {0x50, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x20008, 0xc100c0, 0xc300c2}, - {0x51, 0x407f047f, 0x39390054, 0x0, 0xb2b17f7f, 0xdfef80ff, 0x40008, 0xc100c0, 0xc300c2}, - {0x52, 0x407f047f, 0x3a3a0053, 0x0, 0xb2b17f7f, 0x5fe0b0ff, 0x50008, 0xc100c0, 0xc300c2}, - {0x60, 0x4064047f, 0x2727003c, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0xa0009, 0xc100c0, 0xc300c2}, - {0x61, 0x407f047f, 0x2626003e, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x90009, 0xc100c0, 0xc300c2}, - {0x62, 0x405a047f, 0x27270044, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x130009, 0xc100c0, 0xc300c2}, - {0x63, 0x407f047f, 0x28280040, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0xb0009, 0xc100c0, 0xc300c2}, - {0x64, 0x407f0466, 0x2525003d, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x180009, 0xc100c0, 0xc300c2}, - {0x65, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x190009, 0xc100c0, 0xc300c2}, - {0x66, 0x407f0400, 0x3d3d465a, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x610009, 0xc100c0, 0xc300c2}, - {0x67, 0x407f0400, 0x2424003c, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x180009, 0xc100c0, 0xc300c2}, - {0x68, 0x407f0400, 0x29290041, 0x0, 0xb2b17f7f, 0xdfe380ff, 0xb0009, 0xc100c0, 0xc300c2}, - {0x70, 0x407f047f, 0x3d3d6a48, 0x0, 0xb2b17f7f, 0xdfef80ff, 0x3000a, 0xc100c0, 0xc300c2}, - {0x71, 0x407f047f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x1f000a, 0xc100c0, 0xc300c2}, - {0x80, 0x407f0464, 0x3d3d0055, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x27000b, 0xc100c0, 0xc300c2}, - {0x81, 0x407f0464, 0x3c3c0454, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x31000b, 0xc100c0, 0xc300c2}, - {0x82, 0x407f0464, 0x3f3f0457, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x32000b, 0xc100c0, 0xc300c2}, - {0x90, 0x407f047f, 0x3c3c003c, 0x0, 0xb2b17f7f, 0xdfe080ff, 0xe000f, 0xc100c0, 0xc300c2}, - {0x91, 0x407f047f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x3a000f, 0xc100c0, 0xc300c2}, - {0x92, 0x407f007f, 0x3c3c0048, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x34000f, 0xc100c0, 0xc300c2}, - {0xa0, 0x407f047f, 0x3b3b4659, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x770011, 0xc100c0, 0xc300c2}, - {0xb0, 0x407f007f, 0x30300048, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x380013, 0xc100c0, 0xc300c2}, - {0xc0, 0x407f007e, 0x7f006a53, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x620015, 0xc100c0, 0xc300c2}, - {0xd0, 0x407f047f, 0x7f004659, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x78001c, 0xc100c0, 0xc300c2}, - {0xe0, 0x407f0452, 0x3c3c465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x37001f, 0xc100c0, 0xc300c2}, - {0xf0, 0x407f047f, 0x3d3d4666, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x390020, 0xc100c0, 0xc300c2}, - {0x100, 0x407f047f, 0x2424003c, 0x0, 0xb2b17f7f, 0x5feb80ff, 0x240021, 0xc100c0, 0xc300c2}, - {0x110, 0x407f0446, 0x24244636, 0x0, 0xb2b17f7f, 0xdfebb4ff, 0x2f0025, 0xc100c0, 0xc300c2}, - {0x111, 0x407f0446, 0x25254636, 0x0, 0xb2b17f7f, 0xdfebaaff, 0x300025, 0xc100c0, 0xc300c2}, - {0x112, 0x407f0464, 0x3c3c0448, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x280025, 0xc100c0, 0xc300c2}, - {0x113, 0x407f0464, 0x3d3d0449, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x290025, 0xc100c0, 0xc300c2}, - {0x114, 0x407f0464, 0x3e3e044a, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x2a0025, 0xc100c0, 0xc300c2}, - {0x115, 0x407f0451, 0x4040044c, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x2b0025, 0xc100c0, 0xc300c2}, - {0x116, 0x407f0451, 0x4141044c, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x2b0025, 0xc100c0, 0xc300c2}, - {0x117, 0x407f0464, 0x4242044e, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x2c0025, 0xc100c0, 0xc300c2}, - {0x118, 0x407f0464, 0x4343044f, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x2d0025, 0xc100c0, 0xc300c2}, - {0x119, 0x407f0463, 0x30300455, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x2a0025, 0xc100c0, 0xc300c2}, - {0x11a, 0x407f0463, 0x31310056, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x280025, 0xc100c0, 0xc300c2}, - {0x120, 0x407f047f, 0x3c3c0060, 0x0, 0xb2b17f7f, 0x5fe080ff, 0x330026, 0xc100c0, 0xc300c2}, - {0x130, 0x407f047f, 0x41410459, 0x0, 0xb2b17f7f, 0x5feb80ff, 0x340027, 0xc100c0, 0xc300c2}, - {0x140, 0x407f047f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0x5fe080ff, 0x350028, 0xc100c0, 0xc300c2}, - {0x141, 0x407f047f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x360028, 0xc100c0, 0xc300c2}, - {0x150, 0x407f0440, 0x3d3d3c56, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x600031, 0xc100c0, 0xc300c2}, - {0x160, 0x407f047f, 0x7f000048, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x3b003a, 0xc100c0, 0xc300c2}, - {0x170, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x3c003b, 0xc100c0, 0xc300c2}, - {0x171, 0x407f047f, 0x3e3e465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x3d003b, 0xc100c0, 0xc300c2}, - {0x172, 0x407f047f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x3e003b, 0xc100c0, 0xc300c2}, - {0x173, 0x407f047f, 0x3f3f005b, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x3f003b, 0xc100c0, 0xc300c2}, - {0x180, 0x407f047f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x40003c, 0xc100c0, 0xc300c2}, - {0x181, 0x407f047f, 0x3f3f465c, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x41003c, 0xc100c0, 0xc300c2}, - {0x190, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x42003d, 0xc100c0, 0xc300c2}, - {0x191, 0x407f047f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x43003d, 0xc100c0, 0xc300c2}, - {0x192, 0x407f047f, 0x37374653, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x44003d, 0xc100c0, 0xc300c2}, - {0x193, 0x407f047f, 0x38384655, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x45003d, 0xc100c0, 0xc300c2}, - {0x194, 0x407f047f, 0x39394656, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x46003d, 0xc100c0, 0xc300c2}, - {0x195, 0x407f047f, 0x3b3b4658, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x47003d, 0xc100c0, 0xc300c2}, - {0x196, 0x407f047f, 0x3a3a4657, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x48003d, 0xc100c0, 0xc300c2}, - {0x197, 0x407f047f, 0x4040465d, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x49003d, 0xc100c0, 0xc300c2}, - {0x198, 0x407f047f, 0x4141004d, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x4a003d, 0xc100c0, 0xc300c2}, - {0x1a0, 0x407f047f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x4b003e, 0xc100c0, 0xc300c2}, - {0x1a1, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x4c003e, 0xc100c0, 0xc300c2}, - {0x1a2, 0x407f047f, 0x3e3e465b, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x4d003e, 0xc100c0, 0xc300c2}, - {0x1a3, 0x407f047f, 0x3f3f465c, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x4e003e, 0xc100c0, 0xc300c2}, - {0x1b0, 0x407f047f, 0x25254642, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x4f003f, 0xc100c0, 0xc300c2}, - {0x1b1, 0x407f047f, 0x26264643, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x50003f, 0xc100c0, 0xc300c2}, - {0x1b2, 0x407f047f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x51003f, 0xc100c0, 0xc300c2}, - {0x1b3, 0x407f047f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x52003f, 0xc100c0, 0xc300c2}, - {0x1c0, 0x407f047f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x530040, 0xc100c0, 0xc300c2}, - {0x1c1, 0x407f047f, 0x3e3e465b, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x540040, 0xc100c0, 0xc300c2}, - {0x1c2, 0x407f047f, 0x27274644, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x550040, 0xc100c0, 0xc300c2}, - {0x1c3, 0x407f047f, 0x3f3f465c, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x560040, 0xc100c0, 0xc300c2}, - {0x1d0, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x570041, 0xc100c0, 0xc300c2}, - {0x1d1, 0x407f047f, 0x4040465d, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x580041, 0xc100c0, 0xc300c2}, - {0x1e0, 0x407f047f, 0x7f004667, 0x0, 0xb2b17f7f, 0xdfefaaff, 0x590042, 0xc100c0, 0xc300c2}, - {0x1f0, 0x407f0463, 0x7f000048, 0x0, 0xb2b10000, 0xdfe680ff, 0x5c0048, 0xc100c0, 0xc300c2}, - {0x200, 0x407f0401, 0x7f00003c, 0x0, 0xb2b10000, 0xdfeb80ff, 0x760049, 0xc100c0, 0xc300c2}, - {0x210, 0x407f005a, 0x24240024, 0x0, 0xb2b10000, 0xdfe680ff, 0x5b004a, 0xc100c0, 0xc300c2}, - {0x211, 0x407f045b, 0x30300048, 0x0, 0xb2b10000, 0xdfe680ff, 0x5d004a, 0xc100c0, 0xc300c2}, - {0x212, 0x227f0054, 0x27270027, 0x0, 0xb2b10000, 0xdfeb80ff, 0x5e004a, 0xc100c0, 0xc300c2}, - {0x213, 0x407f0000, 0x25250025, 0x0, 0xb2b10000, 0xdfe680ff, 0x5f004a, 0xc100c0, 0xc300c2}, - {0x214, 0x5e7f0054, 0x28280028, 0x0, 0xb2b10000, 0xdfeb80ff, 0x5e004a, 0xc100c0, 0xc300c2}, - {0x215, 0x407f0453, 0x603c003c, 0x0, 0xb2b10000, 0xdfeb80ff, 0x65004a, 0xc100c0, 0xc300c2}, - {0x220, 0x7f0000, 0x24240024, 0x0, 0xb2b10000, 0xdfeb80ff, 0x66004b, 0xc100c0, 0xc300c2}, - {0x221, 0x7f7f0000, 0x25250025, 0x0, 0xb2b10000, 0xdfeb80ff, 0x67004b, 0xc100c0, 0xc300c2}, - {0x222, 0x7f0000, 0x26260026, 0x0, 0xb2b10000, 0xdfeb80ff, 0x68004b, 0xc100c0, 0xc300c2}, - {0x223, 0x7f7f0000, 0x27270027, 0x0, 0xb2b10000, 0xdfeb80ff, 0x69004b, 0xc100c0, 0xc300c2}, - {0x224, 0x7f0000, 0x28280028, 0x0, 0xb2b10000, 0xdfeb80ff, 0x6a004b, 0xc100c0, 0xc300c2}, - {0x225, 0x7f7f0000, 0x29290029, 0x0, 0xb2b10000, 0xdfeb80ff, 0x6b004b, 0xc100c0, 0xc300c2}, - {0x226, 0x7f0000, 0x2a2a002a, 0x0, 0xb2b10000, 0xdfeb80ff, 0x6c004b, 0xc100c0, 0xc300c2}, - {0x227, 0x7f7f0000, 0x2b2b002b, 0x0, 0xb2b10000, 0xdfeb80ff, 0x6d004b, 0xc100c0, 0xc300c2}, - {0x228, 0x7f0000, 0x2c2c002c, 0x0, 0xb2b10000, 0xdfeb80ff, 0x6e004b, 0xc100c0, 0xc300c2}, - {0x229, 0x7f7f0000, 0x2d2d002d, 0x0, 0xb2b10000, 0xdfeb80ff, 0x6f004b, 0xc100c0, 0xc300c2}, - {0x22a, 0x7f0000, 0x2e2e002e, 0x0, 0xb2b10000, 0xdfeb80ff, 0x70004b, 0xc100c0, 0xc300c2}, - {0x22b, 0x7f7f0000, 0x2f2f002f, 0x0, 0xb2b10000, 0xdfeb80ff, 0x71004b, 0xc100c0, 0xc300c2}, - {0x22c, 0x7f0000, 0x30300030, 0x0, 0xb2b10000, 0xdfeb80ff, 0x72004b, 0xc100c0, 0xc300c2}, - {0x22d, 0x7f7f0000, 0x31310031, 0x0, 0xb2b10000, 0xdfeb80ff, 0x73004b, 0xc100c0, 0xc300c2}, - {0x22e, 0x7f0000, 0x32320032, 0x0, 0xb2b10000, 0xdfeb80ff, 0x74004b, 0xc100c0, 0xc300c2}, - {0x22f, 0x7f7f0000, 0x33330033, 0x0, 0xb2b10000, 0xdfeb80ff, 0x75004b, 0xc100c0, 0xc300c2}, - {0x230, 0x7f0000, 0x2424002b, 0x0, 0xb2b10000, 0xdfeb80ff, 0x66004c, 0xc100c0, 0xc300c2}, - {0x231, 0x7f7f0000, 0x2525002c, 0x0, 0xb2b10000, 0xdfeb80ff, 0x67004c, 0xc100c0, 0xc300c2}, - {0x232, 0x7f0000, 0x2626002d, 0x0, 0xb2b10000, 0xdfeb80ff, 0x68004c, 0xc100c0, 0xc300c2}, - {0x233, 0x7f7f0000, 0x2727002e, 0x0, 0xb2b10000, 0xdfeb80ff, 0x69004c, 0xc100c0, 0xc300c2}, - {0x234, 0x7f0000, 0x2828002f, 0x0, 0xb2b10000, 0xdfeb80ff, 0x6a004c, 0xc100c0, 0xc300c2}, - {0x235, 0x7f7f0000, 0x29290030, 0x0, 0xb2b10000, 0xdfeb80ff, 0x6b004c, 0xc100c0, 0xc300c2}, - {0x236, 0x7f0000, 0x2a2a0031, 0x0, 0xb2b10000, 0xdfeb80ff, 0x6c004c, 0xc100c0, 0xc300c2}, - {0x237, 0x7f7f0000, 0x2b2b0032, 0x0, 0xb2b10000, 0xdfeb80ff, 0x6d004c, 0xc100c0, 0xc300c2}, - {0x238, 0x7f0000, 0x2c2c0033, 0x0, 0xb2b10000, 0xdfeb80ff, 0x6e004c, 0xc100c0, 0xc300c2}, - {0x239, 0x7f7f0000, 0x2d2d0034, 0x0, 0xb2b10000, 0xdfeb80ff, 0x6f004c, 0xc100c0, 0xc300c2}, - {0x23a, 0x7f0000, 0x2e2e0035, 0x0, 0xb2b10000, 0xdfeb80ff, 0x70004c, 0xc100c0, 0xc300c2}, - {0x23b, 0x7f7f0000, 0x2f2f0036, 0x0, 0xb2b10000, 0xdfeb80ff, 0x71004c, 0xc100c0, 0xc300c2}, - {0x23c, 0x7f0000, 0x30300037, 0x0, 0xb2b10000, 0xdfeb80ff, 0x72004c, 0xc100c0, 0xc300c2}, - {0x23d, 0x7f7f0000, 0x31310038, 0x0, 0xb2b10000, 0xdfeb80ff, 0x73004c, 0xc100c0, 0xc300c2}, - {0x23e, 0x7f0000, 0x32320039, 0x0, 0xb2b10000, 0xdfeb80ff, 0x74004c, 0xc100c0, 0xc300c2}, - {0x23f, 0x7f7f0000, 0x3333003a, 0x0, 0xb2b10000, 0xdfeb80ff, 0x75004c, 0xc100c0, 0xc300c2}, - {0x240, 0x407f0454, 0x3f3f004b, 0x0, 0xb2b10000, 0xdfeb80ff, 0x1c004d, 0xc100c0, 0xc300c2}, - {0x241, 0x407f005a, 0x3b00003c, 0x0, 0xb2b10000, 0xdfeb80ff, 0x1d004d, 0xc100c0, 0xc300c2}, - {0x242, 0x406e0055, 0x3c3c0048, 0x0, 0xb2b10000, 0xdfeb80ff, 0x5a004d, 0xc100c0, 0xc300c2}, - {0x250, 0x407f0478, 0x47005a49, 0x0, 0xb2b10000, 0xdff0a0ff, 0x20004e, 0xc100c0, 0xc300c2}, - {0x251, 0x407f0478, 0x7f48005d, 0x0, 0xb2b10000, 0xdfeb80ff, 0x21004e, 0xc100c0, 0xc300c2}, - {0x260, 0x407f0455, 0x7f250048, 0x0, 0xb2b10000, 0xdff080ff, 0x22004f, 0xc100c0, 0xc300c2}, - {0x261, 0x64640452, 0x24230048, 0x0, 0xb2b10000, 0xdff080ff, 0x22004f, 0xc100c0, 0xc300c2}, - {0x262, 0x1b640405, 0x2200003c, 0x0, 0xb2b10000, 0xdff080ff, 0x22004f, 0xc100c0, 0xc300c2}, - {0x270, 0x6e047f, 0x7f005a48, 0x0, 0xb2b10000, 0xdfe780ff, 0x230050, 0xc100c0, 0xc300c2}, - {0x271, 0x7f6e047f, 0x7f006148, 0x0, 0xb2b10000, 0xdfe780ff, 0x230050, 0xc100c0, 0xc300c2}, - {0x280, 0x407f047f, 0x7f00465a, 0x0, 0xb2b10000, 0x5febb4ff, 0x1b0051, 0xc100c0, 0xc300c2}}}; + {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 +// TODO - this one is messed up... "hello" is 100, but sfx plays 96 and says Hello? +const std::vector> AO_MLSNDFX_RATE = {{ + {44, 22050}, + {54, 16000}, + {55, 16000}, + {100, 22050}, + {102, 16000}, + {103, 22050}, + {107, 22050}, + {111, 22050}, + {112, 22050}}}; const std::vector> AO_MLSNDFX_VH = {{ - {0x0, 0x7f047f, 0x3e006a53, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10000, 0xc100c0, 0xc300c2}, - {0x1, 0x1b380400, 0x3e006a5f, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10000, 0xc100c0, 0xc300c2}, - {0x2, 0x64500400, 0x3e006a66, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10000, 0xc100c0, 0xc300c2}, - {0x3, 0x40280400, 0x3e006a72, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10000, 0xc100c0, 0xc300c2}, - {0x4, 0x7f7f047f, 0x3e006a54, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10000, 0xc100c0, 0xc300c2}, - {0x5, 0x407f045b, 0x42420050, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x100000, 0xc100c0, 0xc300c2}, - {0x6, 0x407f045b, 0x4343004f, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x60000, 0xc100c0, 0xc300c2}, - {0x7, 0x407f0464, 0x3e006a4d, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x10000, 0xc100c0, 0xc300c2}, - {0x8, 0x407f047f, 0x4848003c, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x1a0000, 0xc100c0, 0xc300c2}, - {0x9, 0x407f047f, 0x49490049, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x1b0000, 0xc100c0, 0xc300c2}, - {0x10, 0x407f047f, 0x3a3a0046, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x160001, 0xc100c0, 0xc300c2}, - {0x11, 0x407f047f, 0x3b3b0045, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x30001, 0xc100c0, 0xc300c2}, - {0x12, 0x407f047f, 0x48480060, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x2c0001, 0xc100c0, 0xc300c2}, - {0x13, 0x407f047f, 0x48480060, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x20001, 0xc100c0, 0xc300c2}, - {0x14, 0x7f047f, 0x48480048, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x20001, 0xc100c0, 0xc300c2}, - {0x15, 0x7f640451, 0x4848006c, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x20001, 0xc100c0, 0xc300c2}, - {0x16, 0x640451, 0x4848326b, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x20001, 0xc100c0, 0xc300c2}, - {0x17, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x2a0001, 0xc100c0, 0xc300c2}, - {0x18, 0x407f047f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x2b0001, 0xc100c0, 0xc300c2}, - {0x19, 0x407f0400, 0x39390045, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x490001, 0xc100c0, 0xc300c2}, - {0x20, 0x40150464, 0x3c3c6a66, 0x0, 0xb2b17f7f, 0xdaeb80ff, 0x10002, 0xc100c0, 0xc300c2}, - {0x21, 0x3f5f0400, 0x3b006a4e, 0x0, 0xb2b17f7f, 0x5fef80ff, 0x10002, 0xc100c0, 0xc300c2}, - {0x22, 0x540400, 0x3b006a54, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10002, 0xc100c0, 0xc300c2}, - {0x23, 0x7f540400, 0x3b006a56, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10002, 0xc100c0, 0xc300c2}, - {0x24, 0x7f007f, 0x3c3c003c, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x1f0002, 0xc100c0, 0xc300c2}, - {0x25, 0x7f7f007f, 0x3c3c003c, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x200002, 0xc100c0, 0xc300c2}, - {0x26, 0x4046047f, 0x3c3c003c, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x210002, 0xc100c0, 0xc300c2}, - {0x27, 0x407f047f, 0x40400040, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x220002, 0xc100c0, 0xc300c2}, - {0x30, 0x407f047f, 0x3b3b0047, 0x0, 0xb2b17f7f, 0x5fde80ff, 0x130003, 0xc100c0, 0xc300c2}, - {0x31, 0x407f047f, 0x3a3a0052, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x90003, 0xc100c0, 0xc300c2}, - {0x32, 0x407f047f, 0x43430048, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0xa0003, 0xc100c0, 0xc300c2}, - {0x33, 0x407f047f, 0x48480054, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x190003, 0xc100c0, 0xc300c2}, - {0x34, 0x407f047f, 0x39390051, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x230003, 0xc100c0, 0xc300c2}, - {0x40, 0x407f0450, 0x24240030, 0x0, 0xb2b17f7f, 0xdffe80ff, 0xb0004, 0xc100c0, 0xc300c2}, - {0x41, 0x407f0450, 0x25250031, 0x0, 0xb2b17f7f, 0xdffea5ff, 0xb0004, 0xc100c0, 0xc300c2}, - {0x42, 0x407f0450, 0x26260032, 0x0, 0xb2b17f7f, 0xdffeaaff, 0xb0004, 0xc100c0, 0xc300c2}, - {0x43, 0x407f047f, 0x3b3b0047, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x120004, 0xc100c0, 0xc300c2}, - {0x44, 0x407f007f, 0x48480060, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x2c0004, 0xc100c0, 0xc300c2}, - {0x45, 0x407f047f, 0x48480054, 0x0, 0xb2b17f7f, 0xdffeacff, 0x190004, 0xc100c0, 0xc300c2}, - {0x50, 0x407f0450, 0x25250030, 0x0, 0xb2b17f7f, 0x5fe580ff, 0x260005, 0xc100c0, 0xc300c2}, - {0x51, 0x407f007f, 0x43434663, 0x0, 0xb2b17f7f, 0x5fef80ff, 0x300005, 0xc100c0, 0xc300c2}, - {0x52, 0x407f007f, 0x3f3f005a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x8c0005, 0xc100c0, 0xc300c2}, - {0x53, 0x407f047f, 0x4040005a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x8d0005, 0xc100c0, 0xc300c2}, - {0x54, 0x407f043c, 0x41410059, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x8d0005, 0xc100c0, 0xc300c2}, - {0x55, 0x407f043b, 0x42420058, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x8d0005, 0xc100c0, 0xc300c2}, - {0x56, 0x407f007f, 0x3d3d4661, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x900005, 0xc100c0, 0xc300c2}, - {0x60, 0x4064047f, 0x3c3c0060, 0x0, 0xb2b17f7f, 0xdff380ff, 0x420006, 0xc100c0, 0xc300c2}, - {0x61, 0x4064047f, 0x3c3c0078, 0x0, 0xb2b17f7f, 0xdff580ff, 0x430006, 0xc100c0, 0xc300c2}, - {0x62, 0x4053047f, 0x3c3c006c, 0x0, 0xb2b17f7f, 0xd170bebf, 0x1c0006, 0xc100c0, 0xc300c2}, - {0x63, 0x406c047f, 0x3535006c, 0x0, 0xb2b17f7f, 0xdff580ff, 0x430006, 0xc100c0, 0xc300c2}, - {0x64, 0x406c047f, 0x35350054, 0x0, 0xb2b17f7f, 0xdff380ff, 0x420006, 0xc100c0, 0xc300c2}, - {0x65, 0x406c047f, 0x3636006d, 0x0, 0xb2b17f7f, 0xdff580ff, 0x430006, 0xc100c0, 0xc300c2}, - {0x66, 0x406c047f, 0x36360055, 0x0, 0xb2b17f7f, 0xdff380ff, 0x420006, 0xc100c0, 0xc300c2}, - {0x67, 0x4064047f, 0x3030006c, 0x0, 0xb2b17f7f, 0xdff580ff, 0x430006, 0xc100c0, 0xc300c2}, - {0x68, 0x4053047f, 0x30300060, 0x0, 0xb2b17f7f, 0xd170bebf, 0x1c0006, 0xc100c0, 0xc300c2}, - {0x70, 0x407f047f, 0x40400059, 0x0, 0xb2b17f7f, 0x5fef80ff, 0x180007, 0xc100c0, 0xc300c2}, - {0x71, 0x357f0400, 0x3f3c0048, 0x0, 0xb2b17f7f, 0x5fea85ff, 0x140007, 0xc100c0, 0xc300c2}, - {0x72, 0x497f0400, 0x3b3a0044, 0x0, 0xb2b17f7f, 0x5fea82ff, 0x150007, 0xc100c0, 0xc300c2}, - {0x73, 0x407f0402, 0x25240430, 0x0, 0xb2b10202, 0xdfe380ff, 0x440007, 0xc100c0, 0xc300c2}, - {0x74, 0x407f0402, 0x27260032, 0x0, 0xb2b10202, 0x5fc380ff, 0x450007, 0xc100c0, 0xc300c2}, - {0x75, 0x407f0402, 0x2a280034, 0x0, 0xb2b10202, 0x5fc380ff, 0x460007, 0xc100c0, 0xc300c2}, - {0x76, 0x407f0402, 0x2c2b0037, 0x0, 0xb2b10202, 0x5fc380ff, 0x470007, 0xc100c0, 0xc300c2}, - {0x77, 0x407f0401, 0x2d2d0039, 0x0, 0xb2b10000, 0xdfe680ff, 0x4b0007, 0xc100c0, 0xc300c2}, - {0x78, 0x407f0401, 0x2e2e003a, 0x0, 0xb2b10000, 0xdfe680ff, 0x4c0007, 0xc100c0, 0xc300c2}, - {0x79, 0x407f0401, 0x2f2f0047, 0x0, 0xb2b10000, 0xdfe680ff, 0x4e0007, 0xc100c0, 0xc300c2}, - {0x7a, 0x407f0401, 0x3030003c, 0x0, 0xb2b10000, 0xdfe680ff, 0x4d0007, 0xc100c0, 0xc300c2}, - {0x80, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x40008, 0xc100c0, 0xc300c2}, - {0x81, 0x407f047f, 0x39390054, 0x0, 0xb2b17f7f, 0xdfef80ff, 0x70008, 0xc100c0, 0xc300c2}, - {0x82, 0x407f047f, 0x3a3a0053, 0x0, 0xb2b17f7f, 0x5fe0b0ff, 0x80008, 0xc100c0, 0xc300c2}, - {0x83, 0x407f0464, 0x3d3d0059, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x8b0008, 0xc100c0, 0xc300c2}, - {0x90, 0x4064047f, 0x2727003c, 0x0, 0xb2b17f7f, 0x5ffd80ff, 0xd0009, 0xc100c0, 0xc300c2}, - {0x91, 0x407f047f, 0x2626003e, 0x0, 0xb2b17f7f, 0x5ffd80ff, 0xc0009, 0xc100c0, 0xc300c2}, - {0x92, 0x405a047f, 0x27270044, 0x0, 0xb2b17f7f, 0x5ffd80ff, 0x170009, 0xc100c0, 0xc300c2}, - {0x93, 0x407f047f, 0x28280040, 0x0, 0xb2b17f7f, 0x5ffc80ff, 0x110009, 0xc100c0, 0xc300c2}, - {0x94, 0x407f0465, 0x2525003d, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x1d0009, 0xc100c0, 0xc300c2}, - {0x95, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x1e0009, 0xc100c0, 0xc300c2}, - {0x96, 0x407f0400, 0x2424003c, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x1d0009, 0xc100c0, 0xc300c2}, - {0x97, 0x407f0400, 0x29290041, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x110009, 0xc100c0, 0xc300c2}, - {0xa0, 0x407f047f, 0x3d3d6a48, 0x0, 0xb2b17f7f, 0x5fef80ff, 0x5000a, 0xc100c0, 0xc300c2}, - {0xa1, 0x407f047f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x28000a, 0xc100c0, 0xc300c2}, - {0xb0, 0x407f0464, 0x3c3c0048, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0xe000b, 0xc100c0, 0xc300c2}, - {0xb1, 0x407f0464, 0x3f3f004b, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0xf000b, 0xc100c0, 0xc300c2}, - {0xb2, 0x407f0464, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x2d000b, 0xc100c0, 0xc300c2}, - {0xb3, 0x407f0464, 0x3e3e0056, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x2e000b, 0xc100c0, 0xc300c2}, - {0xb4, 0x407f0464, 0x41410059, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x2f000b, 0xc100c0, 0xc300c2}, - {0xb5, 0x407f0464, 0x4040465d, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x35000b, 0xc100c0, 0xc300c2}, - {0xb6, 0x407f0464, 0x4242465f, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x36000b, 0xc100c0, 0xc300c2}, - {0xc0, 0x407f0402, 0x2424003c, 0x0, 0xb2b10000, 0xdfe380ff, 0x4f000c, 0xc100c0, 0xc300c2}, - {0xc1, 0x407f0402, 0x2525003d, 0x0, 0xb2b10000, 0xdfe380ff, 0x52000c, 0xc100c0, 0xc300c2}, - {0xc2, 0x407f0402, 0x2626003e, 0x0, 0xb2b10000, 0xdfe380ff, 0x53000c, 0xc100c0, 0xc300c2}, - {0xc3, 0x407f0402, 0x2727003f, 0x0, 0xb2b10000, 0xdfe380ff, 0x50000c, 0xc100c0, 0xc300c2}, - {0xc4, 0x407f0402, 0x28280040, 0x0, 0xb2b10000, 0xdfe380ff, 0x51000c, 0xc100c0, 0xc300c2}, - {0xc5, 0x407f0402, 0x2b2b0041, 0x0, 0xb2b10000, 0xdfe380ff, 0x54000c, 0xc100c0, 0xc300c2}, - {0xc6, 0x407f0402, 0x3c3c0048, 0x0, 0xb2b10000, 0xdfe380ff, 0x57000c, 0xc100c0, 0xc300c2}, - {0xc7, 0x407f0402, 0x3d3d0049, 0x0, 0xb2b10000, 0xdfe380ff, 0x56000c, 0xc100c0, 0xc300c2}, - {0xc8, 0x407f0402, 0x3e3e004a, 0x0, 0xb2b10000, 0xdfe380ff, 0x58000c, 0xc100c0, 0xc300c2}, - {0xc9, 0x407f0402, 0x3f3f004b, 0x0, 0xb2b10000, 0xdfe380ff, 0x59000c, 0xc100c0, 0xc300c2}, - {0xca, 0x407f0402, 0x4040004c, 0x0, 0xb2b10000, 0xdfe380ff, 0x5a000c, 0xc100c0, 0xc300c2}, - {0xcb, 0x407f0402, 0x4141004d, 0x0, 0xb2b10000, 0xdfe380ff, 0x55000c, 0xc100c0, 0xc300c2}, - {0xd0, 0x407f047f, 0x3b3b4659, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x5b0011, 0xc100c0, 0xc300c2}, - {0xe0, 0x2c7f047f, 0x24240060, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x860014, 0xc100c0, 0xc300c2}, - {0xe1, 0x2c41047f, 0x24240454, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x420014, 0xc100c0, 0xc300c2}, - {0xe2, 0x407f047f, 0x2b2b0060, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x860014, 0xc100c0, 0xc300c2}, - {0xe3, 0x4041047f, 0x2b2b0454, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x420014, 0xc100c0, 0xc300c2}, - {0xe4, 0x547f047f, 0x30300060, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x860014, 0xc100c0, 0xc300c2}, - {0xe5, 0x5441047f, 0x30300454, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x420014, 0xc100c0, 0xc300c2}, - {0xf0, 0x407f007e, 0x7f006a53, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x10015, 0xc100c0, 0xc300c2}, - {0x100, 0x407f047f, 0x3e3e0073, 0x0, 0xb2b17f7f, 0xceab80ff, 0x290018, 0xc100c0, 0xc300c2}, - {0x110, 0x2c7f047f, 0x24240030, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x5d0019, 0xc100c0, 0xc300c2}, - {0x111, 0x407f047f, 0x25250031, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x5e0019, 0xc100c0, 0xc300c2}, - {0x112, 0x547f047f, 0x24246e31, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x5d0019, 0xc100c0, 0xc300c2}, - {0x120, 0x407f0400, 0x7f000054, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x27001b, 0xc100c0, 0xc300c2}, - {0x130, 0x407f047f, 0x7f004659, 0x0, 0xb2b10000, 0xdff080ff, 0x5c001c, 0xc100c0, 0xc300c2}, - {0x140, 0x407f0452, 0x3c3c0054, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x34001f, 0xc100c0, 0xc300c2}, - {0x150, 0x407f047f, 0x3d3d4666, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x920020, 0xc100c0, 0xc300c2}, - {0x160, 0x407f047f, 0x2424003c, 0x0, 0xb2b17f7f, 0x5feb80ff, 0x290021, 0xc100c0, 0xc300c2}, - {0x170, 0x407f047f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x8e0022, 0xc100c0, 0xc300c2}, - {0x171, 0x407f047f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0xdff080ff, 0x8f0022, 0xc100c0, 0xc300c2}, - {0x172, 0x407f047f, 0x3e3e006e, 0x0, 0xb2b17f7f, 0xd16db477, 0x1c0022, 0xc100c0, 0xc300c2}, - {0x173, 0x406b047f, 0x3e3e0075, 0x0, 0xb2b17f7f, 0xdff580ff, 0x430022, 0xc100c0, 0xc300c2}, - {0x174, 0x4041047f, 0x3e3e005d, 0x0, 0xb2b17f7f, 0xdff380ff, 0x420022, 0xc100c0, 0xc300c2}, - {0x180, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x7e0024, 0xc100c0, 0xc300c2}, - {0x181, 0x407f047f, 0x3c3c6a50, 0x0, 0xb2b17f7f, 0xc02080c0, 0x10024, 0xc100c0, 0xc300c2}, - {0x182, 0x407f047f, 0x3c3c0048, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x120024, 0xc100c0, 0xc300c2}, - {0x183, 0x4037047f, 0x3c3c6a64, 0x0, 0xb2b17f7f, 0xc9a080e3, 0x10024, 0xc100c0, 0xc300c2}, - {0x190, 0x407f047f, 0x3c3c0060, 0x0, 0xb2b17f7f, 0x5fe080ff, 0x310026, 0xc100c0, 0xc300c2}, - {0x1a0, 0x407f047f, 0x41410456, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x370027, 0xc100c0, 0xc300c2}, - {0x1a1, 0x4075047f, 0x3c3c0448, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x7f0027, 0xc100c0, 0xc300c2}, - {0x1b0, 0x407f047f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0x5fe080ff, 0x320028, 0xc100c0, 0xc300c2}, - {0x1b1, 0x407f047f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x330028, 0xc100c0, 0xc300c2}, - {0x1c0, 0x407f0400, 0x2424003c, 0x0, 0xb2b10000, 0xdfe380ff, 0x38002b, 0xc100c0, 0xc300c2}, - {0x1c1, 0x407f0400, 0x2525003d, 0x0, 0xb2b10000, 0xdfe380ff, 0x39002b, 0xc100c0, 0xc300c2}, - {0x1c2, 0x407f0400, 0x2626003e, 0x0, 0xb2b10000, 0xdfe380ff, 0x3a002b, 0xc100c0, 0xc300c2}, - {0x1c3, 0x407f0400, 0x2727003f, 0x0, 0xb2b10000, 0xdfe380ff, 0x3b002b, 0xc100c0, 0xc300c2}, - {0x1c4, 0x407f0400, 0x28280040, 0x0, 0xb2b10000, 0xdfe380ff, 0x3c002b, 0xc100c0, 0xc300c2}, - {0x1c5, 0x407f0400, 0x29290041, 0x0, 0xb2b10000, 0xdfe380ff, 0x3d002b, 0xc100c0, 0xc300c2}, - {0x1c6, 0x407f0400, 0x2a2a0042, 0x0, 0xb2b10000, 0xdfe380ff, 0x3e002b, 0xc100c0, 0xc300c2}, - {0x1c7, 0x407f0400, 0x2b2b0043, 0x0, 0xb2b10000, 0xdfe380ff, 0x3f002b, 0xc100c0, 0xc300c2}, - {0x1c8, 0x407f0400, 0x2c2c0044, 0x0, 0xb2b10000, 0xdfe380ff, 0x40002b, 0xc100c0, 0xc300c2}, - {0x1c9, 0x407f0400, 0x2d2d0045, 0x0, 0xb2b10000, 0xdfe380ff, 0x41002b, 0xc100c0, 0xc300c2}, - {0x1d0, 0x407f047f, 0x3d3d0049, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x48002c, 0xc100c0, 0xc300c2}, - {0x1e0, 0x407f0440, 0x3d3d3c56, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x910031, 0xc100c0, 0xc300c2}, - {0x1f0, 0x407f047f, 0x7f000048, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x5f003a, 0xc100c0, 0xc300c2}, - {0x200, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x60003b, 0xc100c0, 0xc300c2}, - {0x201, 0x407f047f, 0x3e3e465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x61003b, 0xc100c0, 0xc300c2}, - {0x202, 0x407f047f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x62003b, 0xc100c0, 0xc300c2}, - {0x203, 0x407f047f, 0x3f3f005b, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x63003b, 0xc100c0, 0xc300c2}, - {0x210, 0x407f047f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x64003c, 0xc100c0, 0xc300c2}, - {0x211, 0x407f047f, 0x3f3f465c, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x65003c, 0xc100c0, 0xc300c2}, - {0x220, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x66003d, 0xc100c0, 0xc300c2}, - {0x221, 0x407f047f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x67003d, 0xc100c0, 0xc300c2}, - {0x222, 0x407f047f, 0x37374653, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x68003d, 0xc100c0, 0xc300c2}, - {0x223, 0x407f047f, 0x38384655, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x69003d, 0xc100c0, 0xc300c2}, - {0x224, 0x407f047f, 0x39394656, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x6a003d, 0xc100c0, 0xc300c2}, - {0x225, 0x407f047f, 0x3b3b4658, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x6b003d, 0xc100c0, 0xc300c2}, - {0x226, 0x407f047f, 0x3a3a4657, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x6c003d, 0xc100c0, 0xc300c2}, - {0x227, 0x407f047f, 0x4040465d, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x6d003d, 0xc100c0, 0xc300c2}, - {0x228, 0x407f047f, 0x4141004d, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x6e003d, 0xc100c0, 0xc300c2}, - {0x230, 0x407f047f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x6f003e, 0xc100c0, 0xc300c2}, - {0x231, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x70003e, 0xc100c0, 0xc300c2}, - {0x232, 0x407f047f, 0x3e3e465b, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x71003e, 0xc100c0, 0xc300c2}, - {0x233, 0x407f047f, 0x3f3f465c, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x72003e, 0xc100c0, 0xc300c2}, - {0x240, 0x407f047f, 0x25254642, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x73003f, 0xc100c0, 0xc300c2}, - {0x241, 0x407f047f, 0x26264643, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x74003f, 0xc100c0, 0xc300c2}, - {0x242, 0x407f047f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x75003f, 0xc100c0, 0xc300c2}, - {0x243, 0x407f047f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x76003f, 0xc100c0, 0xc300c2}, - {0x250, 0x407f047f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x770040, 0xc100c0, 0xc300c2}, - {0x251, 0x407f047f, 0x3e3e465b, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x780040, 0xc100c0, 0xc300c2}, - {0x252, 0x407f047f, 0x27274644, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x790040, 0xc100c0, 0xc300c2}, - {0x253, 0x407f047f, 0x3f3f465c, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x7a0040, 0xc100c0, 0xc300c2}, - {0x260, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x7b0041, 0xc100c0, 0xc300c2}, - {0x261, 0x407f047f, 0x4040465d, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x7c0041, 0xc100c0, 0xc300c2}, - {0x270, 0x407f047f, 0x7f004667, 0x0, 0xb2b17f7f, 0xdfefaaff, 0x7d0042, 0xc100c0, 0xc300c2}, - {0x280, 0x2c7f043c, 0x2f000048, 0x0, 0xb2b10000, 0xdff080ff, 0x880045, 0xc100c0, 0xc300c2}, - {0x281, 0x547f0450, 0x7f300048, 0x0, 0xb2b10000, 0xdff080ff, 0x880045, 0xc100c0, 0xc300c2}, - {0x290, 0x7f0404, 0x53000048, 0x0, 0xb2b10000, 0xdfeb80ff, 0x430046, 0xc100c0, 0xc300c2}, - {0x291, 0x7f7f0404, 0x7f540048, 0x0, 0xb2b10000, 0xdfeb80ff, 0x430046, 0xc100c0, 0xc300c2}, - {0x2a0, 0x3c6e0454, 0x2424003c, 0x0, 0xb2b10000, 0xdfe680ff, 0x80004a, 0xc100c0, 0xc300c2}, - {0x2a1, 0x407f0450, 0x54486578, 0x0, 0xb2b10000, 0xdfeeb0ff, 0x1c004a, 0xc100c0, 0xc300c2}, - {0x2a2, 0x446e0454, 0x2929003c, 0x0, 0xb2b10000, 0xdfe680ff, 0x80004a, 0xc100c0, 0xc300c2}, - {0x2a3, 0x286e0454, 0x3c3c003c, 0x0, 0xb2b10000, 0xdfe680ff, 0x80004a, 0xc100c0, 0xc300c2}, - {0x2a4, 0x586e0454, 0x4747003c, 0x0, 0xb2b10000, 0xdfe680ff, 0x80004a, 0xc100c0, 0xc300c2}, - {0x2b0, 0x40640464, 0x7f004659, 0x0, 0xb2b10000, 0xdfeb80ff, 0x87004c, 0xc100c0, 0xc300c2}, - {0x2c0, 0x407f0454, 0x3f3f004b, 0x0, 0xb2b10000, 0xdfeb80ff, 0x24004d, 0xc100c0, 0xc300c2}, - {0x2c1, 0x407f005a, 0x3b00003c, 0x0, 0xb2b10000, 0xdfeb80ff, 0x25004d, 0xc100c0, 0xc300c2}, - {0x2c2, 0x406e0055, 0x3c3c0048, 0x0, 0xb2b10000, 0xdfeb80ff, 0x8a004d, 0xc100c0, 0xc300c2}, - {0x2d0, 0x407f0464, 0x47005a49, 0x0, 0xb2b10000, 0xdff0a0ff, 0x83004e, 0xc100c0, 0xc300c2}, - {0x2d1, 0x407f0464, 0x7f48005d, 0x0, 0xb2b10000, 0xdfeb80ff, 0x84004e, 0xc100c0, 0xc300c2}, - {0x2e0, 0x407f0455, 0x7f250048, 0x0, 0xb2b10000, 0xdfeb80ff, 0x85004f, 0xc100c0, 0xc300c2}, - {0x2e1, 0x6464044f, 0x24230048, 0x0, 0xb2b10000, 0xdfeb80ff, 0x85004f, 0xc100c0, 0xc300c2}, - {0x2e2, 0x1b640405, 0x2200003c, 0x0, 0xb2b10000, 0xdfeb80ff, 0x85004f, 0xc100c0, 0xc300c2}, - {0x2f0, 0x6e047f, 0x7f005a48, 0x0, 0xb2b10000, 0xdfe780ff, 0x4a0050, 0xc100c0, 0xc300c2}, - {0x2f1, 0x7f6e047f, 0x7f006148, 0x0, 0xb2b10000, 0xdfe780ff, 0x4a0050, 0xc100c0, 0xc300c2}, - {0x300, 0x407f047f, 0x7f00465a, 0x0, 0xb2b10000, 0xdfebb4ff, 0x820051, 0xc100c0, 0xc300c2}, - {0x310, 0x40640464, 0x2400003c, 0x0, 0xb2b10000, 0xdfeb80ff, 0x810053, 0xc100c0, 0xc300c2}, - {0x320, 0x7f0464, 0x3c3c0448, 0x0, 0xb2b10000, 0xdfeba5ff, 0x890054, 0xc100c0, 0xc300c2}, - {0x321, 0x7f7f0464, 0x3d3d0449, 0x0, 0xb2b10000, 0xdfeba5ff, 0x890054, 0xc100c0, 0xc300c2}, - {0x322, 0x7f0464, 0x3e3e044a, 0x0, 0xb2b10000, 0xdfeb80ff, 0x890054, 0xc100c0, 0xc300c2}, - {0x323, 0x7f7f0464, 0x3f3f044b, 0x0, 0xb2b10000, 0xdfeb80ff, 0x890054, 0xc100c0, 0xc300c2}}}; + {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}, + {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}}}; -const std::vector> AO_MUNK_VH = {{ - {0x0, 0x407f047f, 0x24240054, 0x0, 0xb2b17f7f, 0xdff0bdff, 0x10000, 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 = {{ - {0x0, 0x407f0402, 0x48480048, 0x0, 0xb2b17f7f, 0xdffd80ff, 0x10000, 0xc100c0, 0xc300c2}, - {0x1, 0x407f0402, 0x47473247, 0x0, 0xb2b17f7f, 0xdffd80ff, 0x10000, 0xc100c0, 0xc300c2}, - {0x2, 0x407f0400, 0x4c490049, 0x0, 0xb2b17f7f, 0xdffd80ff, 0x20000, 0xc100c0, 0xc300c2}, - {0x3, 0x407f0402, 0x4d4d004d, 0x0, 0xb2b17f7f, 0xdffd80ff, 0x60000, 0xc100c0, 0xc300c2}, - {0x4, 0x407f0402, 0x4e4e644e, 0x0, 0xb2b17f7f, 0xdffd80ff, 0x60000, 0xc100c0, 0xc300c2}, - {0x10, 0x7f007f, 0x30304659, 0x0, 0xb2b10101, 0xdfe680ff, 0x70001, 0xc100c0, 0xc300c2}, - {0x11, 0x7f7f047f, 0x3c3c4665, 0x0, 0xb2b10101, 0xdfe680ff, 0x80001, 0xc100c0, 0xc300c2}, - {0x12, 0x407f047f, 0x48484671, 0x0, 0xb2b10101, 0xdfe680ff, 0x80001, 0xc100c0, 0xc300c2}, - {0x20, 0x407f044f, 0x7f00465a, 0x0, 0xb2b10101, 0xdff0c0ff, 0x90002, 0xc100c0, 0xc300c2}, - {0x30, 0x4028047f, 0x7f000048, 0x0, 0xb2b10000, 0xdffd80ff, 0xc0003, 0xc100c0, 0xc300c2}, - {0x31, 0x4050007f, 0x7f000048, 0x0, 0xb2b10000, 0xdffd80ff, 0xc0003, 0xc100c0, 0xc300c2}, - {0x40, 0x6e047f, 0x7f005a48, 0x0, 0xb2b17f7f, 0xdfe780ff, 0xe0004, 0xc100c0, 0xc300c2}, - {0x41, 0x7f6e047f, 0x7f006148, 0x0, 0xb2b17f7f, 0xdfe780ff, 0xe0004, 0xc100c0, 0xc300c2}, - {0x50, 0x4064047f, 0x3c3c0060, 0x0, 0xb2b17f7f, 0xdff380ff, 0x30006, 0xc100c0, 0xc300c2}, - {0x51, 0x4064047f, 0x3c3c0078, 0x0, 0xb2b17f7f, 0xdff580ff, 0x40006, 0xc100c0, 0xc300c2}, - {0x52, 0x4053047f, 0x3c3c006c, 0x0, 0xb2b17f7f, 0xd170bebf, 0x50006, 0xc100c0, 0xc300c2}, - {0x60, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0xdff580ff, 0xa0008, 0xc100c0, 0xc300c2}, - {0x70, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0xb0009, 0xc100c0, 0xc300c2}, - {0x80, 0x4064007f, 0x7f004659, 0x0, 0xb2b17f7f, 0xdff080ff, 0xd001c, 0xc100c0, 0xc300c2}, - {0x81, 0x4041047f, 0x7f004659, 0x0, 0xb2b17f7f, 0xdff080ff, 0xd001c, 0xc100c0, 0xc300c2}, - {0x90, 0x407f007f, 0x7f000048, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0xf003a, 0xc100c0, 0xc300c2}, - {0xa0, 0x407f007f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0xdffd80ff, 0x10003b, 0xc100c0, 0xc300c2}, - {0xa1, 0x407f007f, 0x3e3e465a, 0x0, 0xb2b17f7f, 0xdffd80ff, 0x11003b, 0xc100c0, 0xc300c2}, - {0xa2, 0x407f007f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0xdffd80ff, 0x12003b, 0xc100c0, 0xc300c2}, - {0xa3, 0x407f007f, 0x3f3f005b, 0x0, 0xb2b17f7f, 0xdffd80ff, 0x13003b, 0xc100c0, 0xc300c2}, - {0xb0, 0x407f007f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0xdffd80ff, 0x14003c, 0xc100c0, 0xc300c2}, - {0xb1, 0x407f007f, 0x3f3f465c, 0x0, 0xb2b17f7f, 0xdffd80ff, 0x15003c, 0xc100c0, 0xc300c2}, - {0xc0, 0x407f007f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0xdffd80ff, 0x16003d, 0xc100c0, 0xc300c2}, - {0xc1, 0x407f007f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0xdffd80ff, 0x17003d, 0xc100c0, 0xc300c2}, - {0xc2, 0x407f007f, 0x37374653, 0x0, 0xb2b17f7f, 0xdffd80ff, 0x18003d, 0xc100c0, 0xc300c2}, - {0xc3, 0x407f007f, 0x38384655, 0x0, 0xb2b17f7f, 0xdffd80ff, 0x19003d, 0xc100c0, 0xc300c2}, - {0xc4, 0x407f007f, 0x39394656, 0x0, 0xb2b17f7f, 0xdffd80ff, 0x1a003d, 0xc100c0, 0xc300c2}, - {0xc5, 0x407f007f, 0x3b3b4658, 0x0, 0xb2b17f7f, 0xdffd80ff, 0x1b003d, 0xc100c0, 0xc300c2}, - {0xc6, 0x407f007f, 0x3a3a4657, 0x0, 0xb2b17f7f, 0xdffd80ff, 0x1c003d, 0xc100c0, 0xc300c2}, - {0xc7, 0x407f007f, 0x4040465d, 0x0, 0xb2b17f7f, 0xdffd80ff, 0x1d003d, 0xc100c0, 0xc300c2}, - {0xc8, 0x407f007f, 0x4141004d, 0x0, 0xb2b17f7f, 0xdffd80ff, 0x1e003d, 0xc100c0, 0xc300c2}, - {0xd0, 0x407f007f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0xdffd80ff, 0x1f003e, 0xc100c0, 0xc300c2}, - {0xd1, 0x407f007f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0xdffd80ff, 0x20003e, 0xc100c0, 0xc300c2}, - {0xd2, 0x407f007f, 0x3e3e465b, 0x0, 0xb2b17f7f, 0xdffd80ff, 0x21003e, 0xc100c0, 0xc300c2}, - {0xd3, 0x407f007f, 0x3f3f465c, 0x0, 0xb2b17f7f, 0xdffd80ff, 0x22003e, 0xc100c0, 0xc300c2}, - {0xe0, 0x407f007f, 0x25254642, 0x0, 0xb2b17f7f, 0xdffd80ff, 0x23003f, 0xc100c0, 0xc300c2}, - {0xe1, 0x407f007f, 0x26264643, 0x0, 0xb2b17f7f, 0xdffd80ff, 0x24003f, 0xc100c0, 0xc300c2}, - {0xe2, 0x407f007f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0xdffd80ff, 0x25003f, 0xc100c0, 0xc300c2}, - {0xe3, 0x407f007f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0xdffd80ff, 0x26003f, 0xc100c0, 0xc300c2}, - {0xf0, 0x407f007f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0xdffd80ff, 0x270040, 0xc100c0, 0xc300c2}, - {0xf1, 0x407f007f, 0x3e3e465b, 0x0, 0xb2b17f7f, 0xdffd80ff, 0x280040, 0xc100c0, 0xc300c2}, - {0xf2, 0x407f007f, 0x27274644, 0x0, 0xb2b17f7f, 0xdffd80ff, 0x290040, 0xc100c0, 0xc300c2}, - {0xf3, 0x407f007f, 0x3f3f465c, 0x0, 0xb2b17f7f, 0xdffd80ff, 0x2a0040, 0xc100c0, 0xc300c2}, - {0x100, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0xdffd80ff, 0x2b0041, 0xc100c0, 0xc300c2}, - {0x101, 0x407f007f, 0x4040465d, 0x0, 0xb2b17f7f, 0xdffd80ff, 0x2c0041, 0xc100c0, 0xc300c2}, - {0x110, 0x407f007f, 0x7f004667, 0x0, 0xb2b17f7f, 0xdfefaaff, 0x2d0042, 0xc100c0, 0xc300c2}}}; + {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}, + {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 = {{ + // {4, 22050}, // exit menu / posses - This should be 8000 - pause sfx def is wrong in code + {44, 22050}, + {54, 41100}, + {55, 16000}, + {100, 22050}, + {102, 16000}, + {103, 22050}, + {107, 22050}, + {111, 22050}, + {112, 22050}}}; + const std::vector> AO_RFSNDFX_VH = {{ - {0x0, 0x7f047f, 0x3e006a53, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10000, 0xc100c0, 0xc300c2}, - {0x1, 0x1b380400, 0x3e006a5f, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10000, 0xc100c0, 0xc300c2}, - {0x2, 0x64500400, 0x3e006a66, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10000, 0xc100c0, 0xc300c2}, - {0x3, 0x40280400, 0x3e006a72, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10000, 0xc100c0, 0xc300c2}, - {0x4, 0x7f7f047f, 0x3e006a54, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10000, 0xc100c0, 0xc300c2}, - {0x5, 0x407f045b, 0x42420050, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0xd0000, 0xc100c0, 0xc300c2}, - {0x6, 0x407f045b, 0x4343004f, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x60000, 0xc100c0, 0xc300c2}, - {0x7, 0x407f0464, 0x3e006a4d, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x10000, 0xc100c0, 0xc300c2}, - {0x8, 0x4064047f, 0x4848003c, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x1a0000, 0xc100c0, 0xc300c2}, - {0x9, 0x4064047f, 0x49490049, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x1b0000, 0xc100c0, 0xc300c2}, - {0x10, 0x407f047f, 0x3a3a0046, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x120001, 0xc100c0, 0xc300c2}, - {0x11, 0x407f047f, 0x3b3b0045, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x30001, 0xc100c0, 0xc300c2}, - {0x12, 0x407f047f, 0x48480060, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x3a0001, 0xc100c0, 0xc300c2}, - {0x13, 0x407f047f, 0x48480060, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x20001, 0xc100c0, 0xc300c2}, - {0x14, 0x7f047f, 0x48480048, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x20001, 0xc100c0, 0xc300c2}, - {0x15, 0x7f640451, 0x4848006c, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x20001, 0xc100c0, 0xc300c2}, - {0x16, 0x640451, 0x4848326b, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x20001, 0xc100c0, 0xc300c2}, - {0x17, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x380001, 0xc100c0, 0xc300c2}, - {0x18, 0x407f047f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x390001, 0xc100c0, 0xc300c2}, - {0x19, 0x407f0400, 0x39390045, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x580001, 0xc100c0, 0xc300c2}, - {0x20, 0x40150464, 0x3c3c6a66, 0x0, 0xb2b17f7f, 0xdaeb80ff, 0x10002, 0xc100c0, 0xc300c2}, - {0x21, 0x3f5f0400, 0x3b006a4e, 0x0, 0xb2b17f7f, 0xdfef80ff, 0x10002, 0xc100c0, 0xc300c2}, - {0x22, 0x540400, 0x3b006a54, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x10002, 0xc100c0, 0xc300c2}, - {0x23, 0x7f540400, 0x3b006a56, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10002, 0xc100c0, 0xc300c2}, - {0x24, 0x7f007f, 0x3c3c003c, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x1e0002, 0xc100c0, 0xc300c2}, - {0x25, 0x7f7f007f, 0x3c3c003c, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x1f0002, 0xc100c0, 0xc300c2}, - {0x26, 0x4046047f, 0x3c3c003c, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x200002, 0xc100c0, 0xc300c2}, - {0x27, 0x407f047f, 0x40400040, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x210002, 0xc100c0, 0xc300c2}, - {0x28, 0x7f007e, 0x54480054, 0x0, 0xb2b10000, 0xdfe680ff, 0x1e0002, 0xc100c0, 0xc300c2}, - {0x29, 0x7f7f007e, 0x54480054, 0x0, 0xb2b10000, 0x1fc080ff, 0x1f0002, 0xc100c0, 0xc300c2}, - {0x30, 0x407f047f, 0x3b3b0047, 0x0, 0xb2b17f7f, 0x5fde80ff, 0x110003, 0xc100c0, 0xc300c2}, - {0x31, 0x407f047f, 0x3a3a0052, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x70003, 0xc100c0, 0xc300c2}, - {0x32, 0x407f047f, 0x43430048, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x80003, 0xc100c0, 0xc300c2}, - {0x33, 0x407f047f, 0x48480054, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x190003, 0xc100c0, 0xc300c2}, - {0x34, 0x407f047f, 0x39390051, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x220003, 0xc100c0, 0xc300c2}, - {0x40, 0x407f044f, 0x24240030, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x90004, 0xc100c0, 0xc300c2}, - {0x41, 0x407f044f, 0x25250031, 0x0, 0xb2b17f7f, 0xdffea5ff, 0x90004, 0xc100c0, 0xc300c2}, - {0x42, 0x407f044f, 0x26260032, 0x0, 0xb2b17f7f, 0xdffeaaff, 0x90004, 0xc100c0, 0xc300c2}, - {0x43, 0x407f047f, 0x3b3b0047, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x100004, 0xc100c0, 0xc300c2}, - {0x44, 0x407f007f, 0x48480060, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x3a0004, 0xc100c0, 0xc300c2}, - {0x45, 0x407f047f, 0x48480054, 0x0, 0xb2b17f7f, 0xdffeacff, 0x190004, 0xc100c0, 0xc300c2}, - {0x50, 0x4064047f, 0x3030006c, 0x0, 0xb2b17f7f, 0xdff580ff, 0x840006, 0xc100c0, 0xc300c2}, - {0x51, 0x4053047f, 0x30300060, 0x0, 0xb2b17f7f, 0xd170bebf, 0x850006, 0xc100c0, 0xc300c2}, - {0x52, 0x407f047f, 0x24240054, 0x0, 0xb2b17f7f, 0xdff0bdff, 0x850006, 0xc100c0, 0xc300c2}, - {0x60, 0x407f047f, 0x40400059, 0x0, 0xb2b17f7f, 0x5fef80ff, 0x150007, 0xc100c0, 0xc300c2}, - {0x61, 0x407f0402, 0x30240054, 0x0, 0xb2b10000, 0xdfeb80ff, 0x5e0007, 0xc100c0, 0xc300c2}, - {0x62, 0x407f0402, 0x23236a54, 0x0, 0xb2b10000, 0xdfe080ff, 0x10007, 0xc100c0, 0xc300c2}, - {0x63, 0x407f0402, 0x2222005e, 0x0, 0xb2b10000, 0x1fc080ff, 0x20007, 0xc100c0, 0xc300c2}, - {0x64, 0x407f0402, 0x21210051, 0x0, 0xb2b10000, 0x1fc080ff, 0x30007, 0xc100c0, 0xc300c2}, - {0x65, 0x407f0402, 0x2020004a, 0x0, 0xb2b10000, 0x1fc080ff, 0x70007, 0xc100c0, 0xc300c2}, - {0x66, 0x407f0402, 0x1f1f0049, 0x0, 0xb2b10000, 0x1fc080ff, 0x90007, 0xc100c0, 0xc300c2}, - {0x67, 0x407f0402, 0x1e1e0042, 0x0, 0xb2b10000, 0x1fc080ff, 0xc0007, 0xc100c0, 0xc300c2}, - {0x68, 0x407f0402, 0x33310061, 0x0, 0xb2b10000, 0x1fc080ff, 0x100007, 0xc100c0, 0xc300c2}, - {0x69, 0x407f0402, 0x3f340060, 0x0, 0xb2b10000, 0x1fc080ff, 0x110007, 0xc100c0, 0xc300c2}, - {0x70, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x40008, 0xc100c0, 0xc300c2}, - {0x80, 0x4064047f, 0x2727003c, 0x0, 0xb2b17f7f, 0x5ffd80ff, 0xb0009, 0xc100c0, 0xc300c2}, - {0x81, 0x407f047f, 0x2626003e, 0x0, 0xb2b17f7f, 0x5ffd80ff, 0xa0009, 0xc100c0, 0xc300c2}, - {0x82, 0x405a047f, 0x27270044, 0x0, 0xb2b17f7f, 0x5ffd80ff, 0x130009, 0xc100c0, 0xc300c2}, - {0x83, 0x407f047f, 0x28280040, 0x0, 0xb2b17f7f, 0x5ffc80ff, 0xe0009, 0xc100c0, 0xc300c2}, - {0x84, 0x407f0465, 0x2525003d, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x1c0009, 0xc100c0, 0xc300c2}, - {0x85, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x1d0009, 0xc100c0, 0xc300c2}, - {0x86, 0x407f0400, 0x2424003c, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x1c0009, 0xc100c0, 0xc300c2}, - {0x87, 0x407f0400, 0x29290041, 0x0, 0xb2b17f7f, 0xdfe380ff, 0xe0009, 0xc100c0, 0xc300c2}, - {0x90, 0x407f047f, 0x3d3d6a48, 0x0, 0xb2b17f7f, 0x5fef80ff, 0x5000a, 0xc100c0, 0xc300c2}, - {0x91, 0x407f047f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x32000a, 0xc100c0, 0xc300c2}, - {0xa0, 0x407f0464, 0x3c3c0048, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0xc000b, 0xc100c0, 0xc300c2}, - {0xa1, 0x407f0464, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x3b000b, 0xc100c0, 0xc300c2}, - {0xb0, 0x407f0400, 0x26260026, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x16000c, 0xc100c0, 0xc300c2}, - {0xb1, 0x407f047f, 0x27270027, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x14000c, 0xc100c0, 0xc300c2}, - {0xb2, 0x407f0464, 0x28280040, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x23000c, 0xc100c0, 0xc300c2}, - {0xb3, 0x407f0464, 0x29290041, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x24000c, 0xc100c0, 0xc300c2}, - {0xb4, 0x407f0464, 0x2a2a0042, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x25000c, 0xc100c0, 0xc300c2}, - {0xb5, 0x407f0464, 0x2c2c0044, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x31000c, 0xc100c0, 0xc300c2}, - {0xc0, 0x407f047f, 0x3c3c003c, 0x0, 0xb2b17f7f, 0xdfe080ff, 0xf000f, 0xc100c0, 0xc300c2}, - {0xc1, 0x407f047f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x58000f, 0xc100c0, 0xc300c2}, - {0xc2, 0x407f007f, 0x3c3c0048, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x48000f, 0xc100c0, 0xc300c2}, - {0xd0, 0x407f0479, 0x30300048, 0x0, 0xb2b17f7f, 0xdff080ff, 0x3c0010, 0xc100c0, 0xc300c2}, - {0xe0, 0x2c7f047f, 0x24240030, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x3d0011, 0xc100c0, 0xc300c2}, - {0xe1, 0x367f0451, 0x25250031, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x170011, 0xc100c0, 0xc300c2}, - {0xe2, 0x407f047f, 0x3b3b4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x5d0011, 0xc100c0, 0xc300c2}, - {0xf0, 0x407f0464, 0x2d2d0045, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x260012, 0xc100c0, 0xc300c2}, - {0xf1, 0x407f047f, 0x2e2e0046, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x270012, 0xc100c0, 0xc300c2}, - {0xf2, 0x407f0432, 0x2f2f0047, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x280012, 0xc100c0, 0xc300c2}, - {0xf3, 0x407f0432, 0x30300048, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x290012, 0xc100c0, 0xc300c2}, - {0xf4, 0x407f0432, 0x2424003c, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x2a0012, 0xc100c0, 0xc300c2}, - {0xf5, 0x407f0432, 0x2525003d, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x2b0012, 0xc100c0, 0xc300c2}, - {0x100, 0x407f047f, 0x30300054, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x490013, 0xc100c0, 0xc300c2}, - {0x110, 0x407f007e, 0x7f006a53, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x10015, 0xc100c0, 0xc300c2}, - {0x120, 0x407f0464, 0x3d3d004e, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x70016, 0xc100c0, 0xc300c2}, - {0x121, 0x407f0464, 0x3e3e0045, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x110016, 0xc100c0, 0xc300c2}, - {0x122, 0x407f0464, 0x3f3f0046, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x110016, 0xc100c0, 0xc300c2}, - {0x123, 0x407f0464, 0x3f3f0050, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x240016, 0xc100c0, 0xc300c2}, - {0x124, 0x3c7f044f, 0x2121002d, 0x0, 0xb2b17f7f, 0xdfe0a4ff, 0x90016, 0xc100c0, 0xc300c2}, - {0x125, 0x407f044f, 0x2222002d, 0x0, 0xb2b17f7f, 0xdfe0a0ff, 0x90016, 0xc100c0, 0xc300c2}, - {0x126, 0x3c7f004f, 0x2323002d, 0x0, 0xb2b17f7f, 0xdfe0a7ff, 0x90016, 0xc100c0, 0xc300c2}, - {0x127, 0x407f004f, 0x2424002d, 0x0, 0xb2b17f7f, 0xdfe0aaff, 0x90016, 0xc100c0, 0xc300c2}, - {0x128, 0x407f0450, 0x2525002d, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x2f0016, 0xc100c0, 0xc300c2}, - {0x130, 0x407f047f, 0x3e3e0073, 0x0, 0xb2b17f7f, 0xceab80ff, 0x370018, 0xc100c0, 0xc300c2}, - {0x140, 0x2c7f047f, 0x24240030, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x180019, 0xc100c0, 0xc300c2}, - {0x141, 0x407f047f, 0x25250031, 0x0, 0xb2b17f7f, 0x5ffd80ff, 0x170019, 0xc100c0, 0xc300c2}, - {0x142, 0x547f047f, 0x24246e31, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x180019, 0xc100c0, 0xc300c2}, - {0x150, 0x407f0400, 0x7f000054, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x30001b, 0xc100c0, 0xc300c2}, - {0x160, 0x407f047f, 0x7f004659, 0x0, 0xb2b10000, 0x5ff080ff, 0x3e001c, 0xc100c0, 0xc300c2}, - {0x170, 0x407f047f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x49001e, 0xc100c0, 0xc300c2}, - {0x180, 0x407f0452, 0x3c3c0054, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x4e001f, 0xc100c0, 0xc300c2}, - {0x190, 0x407f047f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x550020, 0xc100c0, 0xc300c2}, - {0x1a0, 0x407f047f, 0x2424003c, 0x0, 0xb2b17f7f, 0x5feb80ff, 0x370021, 0xc100c0, 0xc300c2}, - {0x1b0, 0x407f0446, 0x24240031, 0x0, 0xb2b17f7f, 0xdffeb4ff, 0x2a0025, 0xc100c0, 0xc300c2}, - {0x1b1, 0x407f0446, 0x25250031, 0x0, 0xb2b17f7f, 0xdffeaaff, 0x2b0025, 0xc100c0, 0xc300c2}, - {0x1b2, 0x407f0464, 0x3c3c0448, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x3f0025, 0xc100c0, 0xc300c2}, - {0x1b3, 0x407f0464, 0x3d3d0449, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x400025, 0xc100c0, 0xc300c2}, - {0x1b4, 0x407f0450, 0x4040044c, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x410025, 0xc100c0, 0xc300c2}, - {0x1b5, 0x407f0450, 0x4141044c, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x410025, 0xc100c0, 0xc300c2}, - {0x1b6, 0x407f0464, 0x4242044e, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x420025, 0xc100c0, 0xc300c2}, - {0x1b7, 0x407f0464, 0x4343044f, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x430025, 0xc100c0, 0xc300c2}, - {0x1c0, 0x407f047f, 0x3c3c005a, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x490026, 0xc100c0, 0xc300c2}, - {0x1d0, 0x407f047f, 0x3c3c0448, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x440027, 0xc100c0, 0xc300c2}, - {0x1d1, 0x407f047f, 0x4040044c, 0x0, 0xb2b17f7f, 0x5feb80ff, 0x450027, 0xc100c0, 0xc300c2}, - {0x1d2, 0x407f0451, 0x4242045a, 0x0, 0xb2b17f7f, 0x5feb80ff, 0x460027, 0xc100c0, 0xc300c2}, - {0x1d3, 0x407f044b, 0x4343044f, 0x0, 0xb2b17f7f, 0x5feb80ff, 0x470027, 0xc100c0, 0xc300c2}, - {0x1d4, 0x407f047f, 0x41410459, 0x0, 0xb2b17f7f, 0x5feb80ff, 0x480027, 0xc100c0, 0xc300c2}, - {0x1d5, 0x4075047f, 0x4444005c, 0x0, 0xb2b17f7f, 0x5fe080ff, 0x4a0027, 0xc100c0, 0xc300c2}, - {0x1d6, 0x407f047f, 0x3d3d0457, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x4b0027, 0xc100c0, 0xc300c2}, - {0x1e0, 0x407f047f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0x5fe080ff, 0x4c0028, 0xc100c0, 0xc300c2}, - {0x1e1, 0x407f047f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x4d0028, 0xc100c0, 0xc300c2}, - {0x1f0, 0x407f047f, 0x40404659, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x57002a, 0xc100c0, 0xc300c2}, - {0x200, 0x407f0419, 0x3e3e465b, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x81002c, 0xc100c0, 0xc300c2}, - {0x201, 0x407f0419, 0x3f3f465c, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x82002c, 0xc100c0, 0xc300c2}, - {0x210, 0x407f047f, 0x30304659, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x54002d, 0xc100c0, 0xc300c2}, - {0x220, 0x407f0400, 0x3c3c003c, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x56002e, 0xc100c0, 0xc300c2}, - {0x230, 0x407f047f, 0x7f000054, 0x0, 0xb2b10000, 0x1fc080ff, 0x59002f, 0xc100c0, 0xc300c2}, - {0x231, 0x5450047f, 0x7f000048, 0x0, 0xb2b10000, 0x1fc080ff, 0x59002f, 0xc100c0, 0xc300c2}, - {0x232, 0x4064047f, 0x7f00005a, 0x0, 0xb2b10000, 0x1fc080ff, 0x59002f, 0xc100c0, 0xc300c2}, - {0x233, 0x2c6e047f, 0x7f000060, 0x0, 0xb2b10000, 0x1fc080ff, 0x5a002f, 0xc100c0, 0xc300c2}, - {0x234, 0x2c55047f, 0x7f00006c, 0x0, 0xb2b10000, 0x1fc080ff, 0x5a002f, 0xc100c0, 0xc300c2}, - {0x235, 0x544b047f, 0x7f000072, 0x0, 0xb2b10000, 0x1fc080ff, 0x5a002f, 0xc100c0, 0xc300c2}, - {0x240, 0x407f047f, 0x24240054, 0x0, 0xb2b17f7f, 0x5fcb80ff, 0x5b0030, 0xc100c0, 0xc300c2}, - {0x241, 0x407f047f, 0x2a2a0054, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x5c0030, 0xc100c0, 0xc300c2}, - {0x242, 0x407f047f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x5c0030, 0xc100c0, 0xc300c2}, - {0x243, 0x407f047f, 0x3e3e0056, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x5b0030, 0xc100c0, 0xc300c2}, - {0x244, 0x407f047f, 0x3f3f0057, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x610030, 0xc100c0, 0xc300c2}, - {0x250, 0x407f047f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x5f0031, 0xc100c0, 0xc300c2}, - {0x251, 0x407f0440, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x600031, 0xc100c0, 0xc300c2}, - {0x252, 0x407f047f, 0x3e3e0056, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x600031, 0xc100c0, 0xc300c2}, - {0x260, 0x407f047f, 0x7f000048, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x62003a, 0xc100c0, 0xc300c2}, - {0x270, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x63003b, 0xc100c0, 0xc300c2}, - {0x271, 0x407f047f, 0x3e3e465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x64003b, 0xc100c0, 0xc300c2}, - {0x272, 0x407f047f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x65003b, 0xc100c0, 0xc300c2}, - {0x273, 0x407f047f, 0x3f3f005b, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x66003b, 0xc100c0, 0xc300c2}, - {0x280, 0x407f047f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x67003c, 0xc100c0, 0xc300c2}, - {0x281, 0x407f047f, 0x3f3f465c, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x68003c, 0xc100c0, 0xc300c2}, - {0x290, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x69003d, 0xc100c0, 0xc300c2}, - {0x291, 0x407f047f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x6a003d, 0xc100c0, 0xc300c2}, - {0x292, 0x407f047f, 0x37374653, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x6b003d, 0xc100c0, 0xc300c2}, - {0x293, 0x407f047f, 0x38384655, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x6c003d, 0xc100c0, 0xc300c2}, - {0x294, 0x407f047f, 0x39394656, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x6d003d, 0xc100c0, 0xc300c2}, - {0x295, 0x407f047f, 0x3b3b4658, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x6e003d, 0xc100c0, 0xc300c2}, - {0x296, 0x407f047f, 0x3a3a4657, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x6f003d, 0xc100c0, 0xc300c2}, - {0x297, 0x407f047f, 0x4040465d, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x70003d, 0xc100c0, 0xc300c2}, - {0x298, 0x407f047f, 0x4141004d, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x71003d, 0xc100c0, 0xc300c2}, - {0x2a0, 0x407f047f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x72003e, 0xc100c0, 0xc300c2}, - {0x2a1, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x73003e, 0xc100c0, 0xc300c2}, - {0x2a2, 0x407f047f, 0x3e3e465b, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x74003e, 0xc100c0, 0xc300c2}, - {0x2a3, 0x407f047f, 0x3f3f465c, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x75003e, 0xc100c0, 0xc300c2}, - {0x2b0, 0x407f047f, 0x25254642, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x76003f, 0xc100c0, 0xc300c2}, - {0x2b1, 0x407f047f, 0x26264643, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x77003f, 0xc100c0, 0xc300c2}, - {0x2b2, 0x407f047f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x78003f, 0xc100c0, 0xc300c2}, - {0x2b3, 0x407f047f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x79003f, 0xc100c0, 0xc300c2}, - {0x2c0, 0x407f047f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x7a0040, 0xc100c0, 0xc300c2}, - {0x2c1, 0x407f047f, 0x3e3e465b, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x7b0040, 0xc100c0, 0xc300c2}, - {0x2c2, 0x407f047f, 0x27274644, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x7c0040, 0xc100c0, 0xc300c2}, - {0x2c3, 0x407f047f, 0x3f3f465c, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x7d0040, 0xc100c0, 0xc300c2}, - {0x2d0, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x7e0041, 0xc100c0, 0xc300c2}, - {0x2d1, 0x407f047f, 0x4040465d, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x7f0041, 0xc100c0, 0xc300c2}, - {0x2e0, 0x407f047f, 0x7f004667, 0x0, 0xb2b17f7f, 0xdfefaaff, 0x800042, 0xc100c0, 0xc300c2}, - {0x2f0, 0x7f0404, 0x5050046c, 0x0, 0xb2b10000, 0xdfeb80ff, 0x530046, 0xc100c0, 0xc300c2}, - {0x2f1, 0x7f7f0404, 0x5151046d, 0x0, 0xb2b10000, 0xdfeb80ff, 0x530046, 0xc100c0, 0xc300c2}, - {0x300, 0x407f005a, 0x2f00003c, 0x0, 0xb2b10000, 0xdfe680ff, 0x4f004a, 0xc100c0, 0xc300c2}, - {0x301, 0x407f045a, 0x36300048, 0x0, 0xb2b10000, 0xdfe680ff, 0x50004a, 0xc100c0, 0xc300c2}, - {0x302, 0x407f0456, 0x43370054, 0x0, 0xb2b10000, 0xdfe680ff, 0x51004a, 0xc100c0, 0xc300c2}, - {0x303, 0x407f0456, 0x4f440060, 0x0, 0xb2b10000, 0xdfe680ff, 0x52004a, 0xc100c0, 0xc300c2}, - {0x304, 0x7f0455, 0x5050006c, 0x0, 0xb2b10000, 0xdfe680ff, 0x53004a, 0xc100c0, 0xc300c2}, - {0x305, 0x7f7f0453, 0x5151006d, 0x0, 0xb2b10000, 0xdfe680ff, 0x53004a, 0xc100c0, 0xc300c2}, - {0x310, 0x407f0457, 0x3f3f004b, 0x0, 0xb2b10000, 0x5feb80ff, 0x2d004d, 0xc100c0, 0xc300c2}, - {0x311, 0x407f005a, 0x3b00003c, 0x0, 0xb2b10000, 0xdfeb80ff, 0x2e004d, 0xc100c0, 0xc300c2}, - {0x312, 0x406e0055, 0x3c3c0048, 0x0, 0xb2b10000, 0xdfeb80ff, 0x83004d, 0xc100c0, 0xc300c2}, - {0x320, 0x407f0478, 0x47005a49, 0x0, 0xb2b10000, 0xdff0a0ff, 0x33004e, 0xc100c0, 0xc300c2}, - {0x321, 0x407f0478, 0x7f48005d, 0x0, 0xb2b10000, 0xdfeb80ff, 0x34004e, 0xc100c0, 0xc300c2}, - {0x330, 0x407f0455, 0x7f250048, 0x0, 0xb2b10000, 0xdfeb80ff, 0x35004f, 0xc100c0, 0xc300c2}, - {0x331, 0x6464044f, 0x24230048, 0x0, 0xb2b10000, 0xdfeb80ff, 0x35004f, 0xc100c0, 0xc300c2}, - {0x332, 0x1b640405, 0x2200003c, 0x0, 0xb2b10000, 0xdfeb80ff, 0x35004f, 0xc100c0, 0xc300c2}, - {0x340, 0x6e047f, 0x7f005a48, 0x0, 0xb2b10000, 0xdfe780ff, 0x360050, 0xc100c0, 0xc300c2}, - {0x341, 0x7f6e047f, 0x7f006148, 0x0, 0xb2b10000, 0xdfe780ff, 0x360050, 0xc100c0, 0xc300c2}, - {0x350, 0x407f047f, 0x7f00465a, 0x0, 0xb2b10000, 0x5febb4ff, 0x2c0051, 0xc100c0, 0xc300c2}}}; + {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}, + {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 const std::vector> AO_RFENDER_VH = {{ - {0x0, 0x7f047f, 0x3e006a53, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10000, 0xc100c0, 0xc300c2}, - {0x1, 0x1b380400, 0x3e006a5f, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10000, 0xc100c0, 0xc300c2}, - {0x2, 0x64500400, 0x3e006a66, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10000, 0xc100c0, 0xc300c2}, - {0x3, 0x40280400, 0x3e006a72, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10000, 0xc100c0, 0xc300c2}, - {0x4, 0x7f7f047f, 0x3e006a54, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10000, 0xc100c0, 0xc300c2}, - {0x5, 0x407f045b, 0x42420050, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0xb0000, 0xc100c0, 0xc300c2}, - {0x6, 0x407f045b, 0x4343004f, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x60000, 0xc100c0, 0xc300c2}, - {0x7, 0x407f0464, 0x3e006a4d, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x10000, 0xc100c0, 0xc300c2}, - {0x8, 0x4064045a, 0x4848003c, 0x0, 0xb2b17f7f, 0xdffd80ff, 0x140000, 0xc100c0, 0xc300c2}, - {0x9, 0x4064045a, 0x49490049, 0x0, 0xb2b17f7f, 0xdffd80ff, 0x150000, 0xc100c0, 0xc300c2}, - {0x10, 0x407f047f, 0x3a3a0046, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0xe0001, 0xc100c0, 0xc300c2}, - {0x11, 0x407f047f, 0x3b3b0045, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x30001, 0xc100c0, 0xc300c2}, - {0x12, 0x407f047f, 0x48480060, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x310001, 0xc100c0, 0xc300c2}, - {0x13, 0x407f047f, 0x48480060, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x20001, 0xc100c0, 0xc300c2}, - {0x14, 0x7f047f, 0x48480048, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x20001, 0xc100c0, 0xc300c2}, - {0x15, 0x7f640451, 0x4848006c, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x20001, 0xc100c0, 0xc300c2}, - {0x16, 0x640451, 0x4848326b, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x20001, 0xc100c0, 0xc300c2}, - {0x17, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x2f0001, 0xc100c0, 0xc300c2}, - {0x18, 0x407f047f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x300001, 0xc100c0, 0xc300c2}, - {0x19, 0x407f0400, 0x39390045, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x3c0001, 0xc100c0, 0xc300c2}, - {0x20, 0x40150450, 0x3c3c6a66, 0x0, 0xb2b17f7f, 0xdaeb80ff, 0x10002, 0xc100c0, 0xc300c2}, - {0x21, 0x3f5f0400, 0x3b006a4e, 0x0, 0xb2b17f7f, 0x5fef80ff, 0x10002, 0xc100c0, 0xc300c2}, - {0x22, 0x540400, 0x3b006a54, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10002, 0xc100c0, 0xc300c2}, - {0x23, 0x7f540400, 0x3b006a56, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x10002, 0xc100c0, 0xc300c2}, - {0x24, 0x7f007f, 0x3c3c003c, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x160002, 0xc100c0, 0xc300c2}, - {0x25, 0x7f7f007f, 0x3c3c003c, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x170002, 0xc100c0, 0xc300c2}, - {0x26, 0x4046047f, 0x3c3c003c, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x180002, 0xc100c0, 0xc300c2}, - {0x27, 0x407f047f, 0x40400040, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x190002, 0xc100c0, 0xc300c2}, - {0x30, 0x407f047f, 0x3b3b0047, 0x0, 0xb2b17f7f, 0x5fde80ff, 0xd0003, 0xc100c0, 0xc300c2}, - {0x31, 0x407f047f, 0x3a3a0052, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x70003, 0xc100c0, 0xc300c2}, - {0x32, 0x407f047f, 0x43430048, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x80003, 0xc100c0, 0xc300c2}, - {0x33, 0x407f047f, 0x48480054, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x130003, 0xc100c0, 0xc300c2}, - {0x34, 0x407f047f, 0x39390051, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x1a0003, 0xc100c0, 0xc300c2}, - {0x40, 0x407f0450, 0x24240030, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x90004, 0xc100c0, 0xc300c2}, - {0x41, 0x407f0450, 0x25250031, 0x0, 0xb2b17f7f, 0xdffea5ff, 0x90004, 0xc100c0, 0xc300c2}, - {0x42, 0x407f0450, 0x26260032, 0x0, 0xb2b17f7f, 0xdffeaaff, 0x90004, 0xc100c0, 0xc300c2}, - {0x43, 0x407f047f, 0x3b3b0047, 0x0, 0xb2b17f7f, 0xdffe80ff, 0xc0004, 0xc100c0, 0xc300c2}, - {0x44, 0x407f007f, 0x48480060, 0x0, 0xb2b17f7f, 0xdffe80ff, 0x310004, 0xc100c0, 0xc300c2}, - {0x45, 0x407f047f, 0x48480054, 0x0, 0xb2b17f7f, 0xdffeacff, 0x130004, 0xc100c0, 0xc300c2}, - {0x50, 0x407f047f, 0x40400059, 0x0, 0xb2b17f7f, 0x5fef80ff, 0x100007, 0xc100c0, 0xc300c2}, - {0x51, 0x407f0402, 0x30240054, 0x0, 0xb2b10000, 0xdfeb80ff, 0x400007, 0xc100c0, 0xc300c2}, - {0x52, 0x407f0402, 0x23236a54, 0x0, 0xb2b10000, 0xdfe080ff, 0x10007, 0xc100c0, 0xc300c2}, - {0x53, 0x407f0402, 0x2222005e, 0x0, 0xb2b10000, 0x1fc080ff, 0x20007, 0xc100c0, 0xc300c2}, - {0x54, 0x407f0402, 0x21210051, 0x0, 0xb2b10000, 0x1fc080ff, 0x30007, 0xc100c0, 0xc300c2}, - {0x55, 0x407f0402, 0x2020004a, 0x0, 0xb2b10000, 0x1fc080ff, 0x70007, 0xc100c0, 0xc300c2}, - {0x56, 0x407f0402, 0x1f1f0049, 0x0, 0xb2b10000, 0x1fc080ff, 0x90007, 0xc100c0, 0xc300c2}, - {0x57, 0x407f0402, 0x1e1e0042, 0x0, 0xb2b10000, 0x1fc080ff, 0xa0007, 0xc100c0, 0xc300c2}, - {0x58, 0x407f0402, 0x33310061, 0x0, 0xb2b10000, 0x1fc080ff, 0xc0007, 0xc100c0, 0xc300c2}, - {0x59, 0x407f0402, 0x3f340060, 0x0, 0xb2b10000, 0x1fc080ff, 0xd0007, 0xc100c0, 0xc300c2}, - {0x60, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x40008, 0xc100c0, 0xc300c2}, - {0x70, 0x407f047f, 0x3d3d6a48, 0x0, 0xb2b17f7f, 0x5fef80ff, 0x5000a, 0xc100c0, 0xc300c2}, - {0x71, 0x407f047f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x29000a, 0xc100c0, 0xc300c2}, - {0x80, 0x407f0464, 0x3c3c0048, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0xa000b, 0xc100c0, 0xc300c2}, - {0x81, 0x407f0464, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x32000b, 0xc100c0, 0xc300c2}, - {0x90, 0x407f0400, 0x26260026, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x11000c, 0xc100c0, 0xc300c2}, - {0x91, 0x407f047f, 0x27270027, 0x0, 0xb2b17f7f, 0xdfe680ff, 0xf000c, 0xc100c0, 0xc300c2}, - {0x92, 0x407f0464, 0x28280040, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x1b000c, 0xc100c0, 0xc300c2}, - {0x93, 0x407f0464, 0x29290041, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x1c000c, 0xc100c0, 0xc300c2}, - {0x94, 0x407f0464, 0x2a2a0042, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x1d000c, 0xc100c0, 0xc300c2}, - {0x95, 0x407f0464, 0x2c2c0044, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x28000c, 0xc100c0, 0xc300c2}, - {0xa0, 0x407f047f, 0x30300048, 0x0, 0xb2b17f7f, 0xdff080ff, 0x330010, 0xc100c0, 0xc300c2}, - {0xb0, 0x2c7f047f, 0x24240030, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x340011, 0xc100c0, 0xc300c2}, - {0xb1, 0x367f0451, 0x25250031, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x120011, 0xc100c0, 0xc300c2}, - {0xb2, 0x407f047f, 0x3b3b4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x3f0011, 0xc100c0, 0xc300c2}, - {0xc0, 0x407f0464, 0x2d2d0045, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x1e0012, 0xc100c0, 0xc300c2}, - {0xc1, 0x407f047f, 0x2e2e0046, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x1f0012, 0xc100c0, 0xc300c2}, - {0xc2, 0x407f0432, 0x2f2f0047, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x200012, 0xc100c0, 0xc300c2}, - {0xc3, 0x407f0432, 0x30300048, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x210012, 0xc100c0, 0xc300c2}, - {0xc4, 0x407f0432, 0x2424003c, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x220012, 0xc100c0, 0xc300c2}, - {0xc5, 0x407f0432, 0x2525003d, 0x0, 0xb2b17f7f, 0xdfe680ff, 0x230012, 0xc100c0, 0xc300c2}, - {0xd0, 0x407f047f, 0x30300054, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x360013, 0xc100c0, 0xc300c2}, - {0xe0, 0x407f007e, 0x7f006a53, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x10015, 0xc100c0, 0xc300c2}, - {0xf0, 0x407f0464, 0x3d3d004e, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x70016, 0xc100c0, 0xc300c2}, - {0xf1, 0x407f0464, 0x3e3e0045, 0x0, 0xb2b17f7f, 0xdfe080ff, 0xd0016, 0xc100c0, 0xc300c2}, - {0xf2, 0x407f0464, 0x3f3f0046, 0x0, 0xb2b17f7f, 0xdfe080ff, 0xd0016, 0xc100c0, 0xc300c2}, - {0xf3, 0x407f0464, 0x3f3f0050, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x1c0016, 0xc100c0, 0xc300c2}, - {0xf4, 0x3c7f0450, 0x2121002d, 0x0, 0xb2b17f7f, 0xdfe0a4ff, 0x90016, 0xc100c0, 0xc300c2}, - {0xf5, 0x407f0450, 0x2222002d, 0x0, 0xb2b17f7f, 0xdfe0a0ff, 0x90016, 0xc100c0, 0xc300c2}, - {0xf6, 0x3c7f0050, 0x2323002d, 0x0, 0xb2b17f7f, 0xdfe0a7ff, 0x90016, 0xc100c0, 0xc300c2}, - {0xf7, 0x407f0050, 0x2424002d, 0x0, 0xb2b17f7f, 0xdfe0aaff, 0x90016, 0xc100c0, 0xc300c2}, - {0xf8, 0x407f0450, 0x2525002d, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x270016, 0xc100c0, 0xc300c2}, - {0x100, 0x407f047f, 0x3e3e0073, 0x0, 0xb2b17f7f, 0xceab80ff, 0x2e0018, 0xc100c0, 0xc300c2}, - {0x110, 0x2c7f047f, 0x24240030, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x6b0019, 0xc100c0, 0xc300c2}, - {0x111, 0x407f047f, 0x25250031, 0x0, 0xb2b17f7f, 0x5ffd80ff, 0x120019, 0xc100c0, 0xc300c2}, - {0x112, 0x547f047f, 0x24246e31, 0x0, 0xb2b17f7f, 0x5ffe80ff, 0x6b0019, 0xc100c0, 0xc300c2}, - {0x120, 0x407f047f, 0x7f004659, 0x0, 0xb2b10000, 0x5ff080ff, 0x35001c, 0xc100c0, 0xc300c2}, - {0x130, 0x407f0452, 0x3c3c0054, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x39001f, 0xc100c0, 0xc300c2}, - {0x140, 0x407f047f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x3a0020, 0xc100c0, 0xc300c2}, - {0x150, 0x407f047f, 0x2424003c, 0x0, 0xb2b17f7f, 0x5feb80ff, 0x2e0021, 0xc100c0, 0xc300c2}, - {0x160, 0x407f047f, 0x3c3c005a, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x360026, 0xc100c0, 0xc300c2}, - {0x170, 0x407f047f, 0x3c3c0448, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x6e0027, 0xc100c0, 0xc300c2}, - {0x171, 0x4075047f, 0x4444005c, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x6f0027, 0xc100c0, 0xc300c2}, - {0x180, 0x407f047f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0x5fe080ff, 0x370028, 0xc100c0, 0xc300c2}, - {0x181, 0x407f047f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x380028, 0xc100c0, 0xc300c2}, - {0x190, 0x407f0400, 0x3c3c003c, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x3b002e, 0xc100c0, 0xc300c2}, - {0x1a0, 0x407f047f, 0x24240054, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x3d0030, 0xc100c0, 0xc300c2}, - {0x1a1, 0x407f047f, 0x2a2a0054, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x3e0030, 0xc100c0, 0xc300c2}, - {0x1a2, 0x407f047f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x3e0030, 0xc100c0, 0xc300c2}, - {0x1a3, 0x407f047f, 0x3e3e0056, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x3d0030, 0xc100c0, 0xc300c2}, - {0x1a4, 0x407f047f, 0x3f3f0057, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x430030, 0xc100c0, 0xc300c2}, - {0x1b0, 0x407f047f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x410031, 0xc100c0, 0xc300c2}, - {0x1b1, 0x407f0440, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x420031, 0xc100c0, 0xc300c2}, - {0x1b2, 0x407f047f, 0x3e3e0056, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x420031, 0xc100c0, 0xc300c2}, - {0x1c0, 0x407f047f, 0x24244641, 0x0, 0xb2b17f7f, 0x5fc380ff, 0x640032, 0xc100c0, 0xc300c2}, - {0x1c1, 0x407f047e, 0x3030464d, 0x0, 0xb2b17f7f, 0x5fcb80ff, 0x650032, 0xc100c0, 0xc300c2}, - {0x1c2, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0xdfe380ff, 0x6a0032, 0xc100c0, 0xc300c2}, - {0x1d0, 0x407f047f, 0x7f000048, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x44003a, 0xc100c0, 0xc300c2}, - {0x1e0, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x45003b, 0xc100c0, 0xc300c2}, - {0x1e1, 0x407f047f, 0x3e3e465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x46003b, 0xc100c0, 0xc300c2}, - {0x1e2, 0x407f047f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x47003b, 0xc100c0, 0xc300c2}, - {0x1e3, 0x407f047f, 0x3f3f005b, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x48003b, 0xc100c0, 0xc300c2}, - {0x1f0, 0x407f047f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x49003c, 0xc100c0, 0xc300c2}, - {0x1f1, 0x407f047f, 0x3f3f465c, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x4a003c, 0xc100c0, 0xc300c2}, - {0x200, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x4b003d, 0xc100c0, 0xc300c2}, - {0x201, 0x407f047f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x4c003d, 0xc100c0, 0xc300c2}, - {0x202, 0x407f047f, 0x37374653, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x4d003d, 0xc100c0, 0xc300c2}, - {0x203, 0x407f047f, 0x38384655, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x4e003d, 0xc100c0, 0xc300c2}, - {0x204, 0x407f047f, 0x39394656, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x4f003d, 0xc100c0, 0xc300c2}, - {0x205, 0x407f047f, 0x3b3b4658, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x50003d, 0xc100c0, 0xc300c2}, - {0x206, 0x407f047f, 0x3a3a4657, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x51003d, 0xc100c0, 0xc300c2}, - {0x207, 0x407f047f, 0x4040465d, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x52003d, 0xc100c0, 0xc300c2}, - {0x208, 0x407f047f, 0x4141004d, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x53003d, 0xc100c0, 0xc300c2}, - {0x210, 0x407f047f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x54003e, 0xc100c0, 0xc300c2}, - {0x211, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x55003e, 0xc100c0, 0xc300c2}, - {0x212, 0x407f047f, 0x3e3e465b, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x56003e, 0xc100c0, 0xc300c2}, - {0x213, 0x407f047f, 0x3f3f465c, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x57003e, 0xc100c0, 0xc300c2}, - {0x220, 0x407f047f, 0x25254642, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x58003f, 0xc100c0, 0xc300c2}, - {0x221, 0x407f047f, 0x26264643, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x59003f, 0xc100c0, 0xc300c2}, - {0x222, 0x407f047f, 0x3c3c0054, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x5a003f, 0xc100c0, 0xc300c2}, - {0x223, 0x407f047f, 0x3d3d0055, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x5b003f, 0xc100c0, 0xc300c2}, - {0x230, 0x407f047f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x5c0040, 0xc100c0, 0xc300c2}, - {0x231, 0x407f047f, 0x3e3e465b, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x5d0040, 0xc100c0, 0xc300c2}, - {0x232, 0x407f047f, 0x27274644, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x5e0040, 0xc100c0, 0xc300c2}, - {0x233, 0x407f047f, 0x3f3f465c, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x5f0040, 0xc100c0, 0xc300c2}, - {0x240, 0x407f047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x600041, 0xc100c0, 0xc300c2}, - {0x241, 0x407f047f, 0x4040465d, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x610041, 0xc100c0, 0xc300c2}, - {0x250, 0x407f047f, 0x7f004667, 0x0, 0xb2b17f7f, 0xdfefaaff, 0x620042, 0xc100c0, 0xc300c2}, - {0x260, 0x4064047f, 0x3c3c4659, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x660043, 0xc100c0, 0xc300c2}, - {0x261, 0x4064047f, 0x3d3d465a, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x670043, 0xc100c0, 0xc300c2}, - {0x262, 0x4064047f, 0x3e3e465b, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x680043, 0xc100c0, 0xc300c2}, - {0x263, 0x4064047f, 0x3f3f465c, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x690043, 0xc100c0, 0xc300c2}, - {0x264, 0x4064047f, 0x40404660, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x6c0043, 0xc100c0, 0xc300c2}, - {0x265, 0x4064047f, 0x41414661, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x6d0043, 0xc100c0, 0xc300c2}, - {0x270, 0x7f0404, 0x5050046c, 0x0, 0xb2b10000, 0xdfeb80ff, 0x700046, 0xc100c0, 0xc300c2}, - {0x271, 0x7f7f0404, 0x5151046d, 0x0, 0xb2b10000, 0xdfeb80ff, 0x700046, 0xc100c0, 0xc300c2}, - {0x280, 0x407f005a, 0x2f00003c, 0x0, 0xb2b10000, 0xdfe680ff, 0x71004a, 0xc100c0, 0xc300c2}, - {0x281, 0x407f045a, 0x36300048, 0x0, 0xb2b10000, 0xdfe680ff, 0x72004a, 0xc100c0, 0xc300c2}, - {0x282, 0x407f0456, 0x43370054, 0x0, 0xb2b10000, 0xdfe680ff, 0x73004a, 0xc100c0, 0xc300c2}, - {0x283, 0x407f0456, 0x4f440060, 0x0, 0xb2b10000, 0xdfe680ff, 0x74004a, 0xc100c0, 0xc300c2}, - {0x284, 0x7f0455, 0x5050006c, 0x0, 0xb2b10000, 0xdfe680ff, 0x70004a, 0xc100c0, 0xc300c2}, - {0x285, 0x7f7f0453, 0x5151006d, 0x0, 0xb2b10000, 0xdfe680ff, 0x70004a, 0xc100c0, 0xc300c2}, - {0x290, 0x407f0454, 0x3f3f004b, 0x0, 0xb2b10000, 0x5feb80ff, 0x25004d, 0xc100c0, 0xc300c2}, - {0x291, 0x407f005a, 0x3b00003c, 0x0, 0xb2b10000, 0xdfeb80ff, 0x26004d, 0xc100c0, 0xc300c2}, - {0x292, 0x406e0055, 0x3c3c0048, 0x0, 0xb2b10000, 0xdfeb80ff, 0x63004d, 0xc100c0, 0xc300c2}, - {0x2a0, 0x407f0478, 0x47005a49, 0x0, 0xb2b10000, 0xdff0a0ff, 0x2a004e, 0xc100c0, 0xc300c2}, - {0x2a1, 0x407f0478, 0x7f48005d, 0x0, 0xb2b10000, 0xdfeb80ff, 0x2b004e, 0xc100c0, 0xc300c2}, - {0x2b0, 0x407f0455, 0x7f250048, 0x0, 0xb2b10000, 0xdfeb80ff, 0x2c004f, 0xc100c0, 0xc300c2}, - {0x2b1, 0x64640452, 0x24230048, 0x0, 0xb2b10000, 0xdfeb80ff, 0x2c004f, 0xc100c0, 0xc300c2}, - {0x2b2, 0x1b640405, 0x2200003c, 0x0, 0xb2b10000, 0xdfeb80ff, 0x2c004f, 0xc100c0, 0xc300c2}, - {0x2c0, 0x6e047f, 0x7f005a48, 0x0, 0xb2b10000, 0xdfe780ff, 0x2d0050, 0xc100c0, 0xc300c2}, - {0x2c1, 0x7f6e047f, 0x7f006148, 0x0, 0xb2b10000, 0xdfe780ff, 0x2d0050, 0xc100c0, 0xc300c2}, - {0x2d0, 0x407f047f, 0x7f00465a, 0x0, 0xb2b10000, 0x5febb4ff, 0x240051, 0xc100c0, 0xc300c2}}}; + {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}, + {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}}}; } \ No newline at end of file From 6acb74449eba6ee7d84ebb4dbc2f661f80a33522 Mon Sep 17 00:00:00 2001 From: Sean Horton <3359434+sean-horton@users.noreply.github.com> Date: Wed, 26 Apr 2023 19:36:30 -0400 Subject: [PATCH 58/82] Was looking at wrong level.... --- Source/AliveLibCommon/audio/oddio.h | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/Source/AliveLibCommon/audio/oddio.h b/Source/AliveLibCommon/audio/oddio.h index 7961ba3b9..9029827fb 100644 --- a/Source/AliveLibCommon/audio/oddio.h +++ b/Source/AliveLibCommon/audio/oddio.h @@ -214,17 +214,16 @@ const std::vector> AO_F2ENDER_VH = {{ //////////////////////////////////////// // L1.LVL - MLSNDFX.VH -// TODO - this one is messed up... "hello" is 100, but sfx plays 96 and says Hello? const std::vector> AO_MLSNDFX_RATE = {{ - {44, 22050}, - {54, 16000}, - {55, 16000}, - {100, 22050}, - {102, 16000}, + {41, 16000}, + {74, 16000}, + {96, 22050}, + {98, 16000}, + {99, 22050}, {103, 22050}, {107, 22050}, - {111, 22050}, - {112, 22050}}}; + {108, 22050}, + {130, 22050}}}; const std::vector> AO_MLSNDFX_VH = {{ {134, 224, 0x2c7f047f, 0x24240060, 0x0, 0xb2b17f7f, 0xdfe080ff, 0x860014, 0xc100c0, 0xc300c2}, @@ -232,7 +231,7 @@ const std::vector> AO_MLSNDFX_VH = {{ {134, 228, 0x547f047f, 0x30300060, 0x0, 0xb2b17f7f, 0x1fc080ff, 0x860014, 0xc100c0, 0xc300c2}, {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}, + {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}, @@ -282,9 +281,8 @@ const std::vector> AO_OPTSNDFX_VH = {{ //////////////////////////////////////// // R1.LVL / R2.LVL - RFSNDEFX.VH const std::vector> AO_RFSNDFX_RATE = {{ - // {4, 22050}, // exit menu / posses - This should be 8000 - pause sfx def is wrong in code {44, 22050}, - {54, 41100}, + {54, 16000}, {55, 16000}, {100, 22050}, {102, 16000}, From 1dd882b2d7766122d7c9dae7bc3fad07a9f21219 Mon Sep 17 00:00:00 2001 From: Sean Horton <3359434+sean-horton@users.noreply.github.com> Date: Wed, 26 Apr 2023 19:43:07 -0400 Subject: [PATCH 59/82] applying audio fixes --- Source/AliveLibAE/Sound/Midi.cpp | 2 +- Source/AliveLibCommon/audio/MidiPlayer.cpp | 153 ++++++++++++++------- Source/AliveLibCommon/audio/MidiPlayer.hpp | 7 + 3 files changed, 115 insertions(+), 47 deletions(-) diff --git a/Source/AliveLibAE/Sound/Midi.cpp b/Source/AliveLibAE/Sound/Midi.cpp index 66489e2d3..bed50f190 100644 --- a/Source/AliveLibAE/Sound/Midi.cpp +++ b/Source/AliveLibAE/Sound/Midi.cpp @@ -977,7 +977,7 @@ class AESoundSampleParser : public psx::SoundSampleParser psx::Sample* sample = new psx::Sample(); sample->m_SampleBuffer = reinterpret_cast(data); sample->i_SampleSize = size / 2; - sample->sampleRate = 44100; // non standard? Doesn't use sampleRate field? + sample->sampleRate = 8000; // non standard? Doesn't use sampleRate field? sample->loop = sampleRate > 44100; // non-standard? samples.push_back(sample); } diff --git a/Source/AliveLibCommon/audio/MidiPlayer.cpp b/Source/AliveLibCommon/audio/MidiPlayer.cpp index 7763b763d..9e22da2bb 100644 --- a/Source/AliveLibCommon/audio/MidiPlayer.cpp +++ b/Source/AliveLibCommon/audio/MidiPlayer.cpp @@ -1,6 +1,7 @@ #pragma once #include "MidiPlayer.hpp" +#include "oddio.h" namespace psx { @@ -31,13 +32,106 @@ class DefaultSoundSampleParser : public SoundSampleParser Sample* sample = new Sample(); sample->m_SampleBuffer = reinterpret_cast(data); sample->i_SampleSize = size / 2; - sample->sampleRate = 44100; // non standard? Doesn't use sampleRate field? + 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_D2SNDFX_RATE; + } + else if (strcmp(headerName, "E1SNDFX.VH") == 0) + { + lookupVag = ODDIO::AO_E1SNDFX_VH; + //lookupRate = ODDIO::AO_D2SNDFX_RATE; + } + else if (strcmp(headerName, "E2SNDFX.VH") == 0) + { + lookupVag = ODDIO::AO_E2SNDFX_VH; + //lookupRate = ODDIO::AO_D2SNDFX_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_F1SNDFX_VH; + //lookupRate = ODDIO::AO_F1SNDFX_RATE; + } + else if (strcmp(headerName, "F2ENDER.VH") == 0) + { + lookupVag = ODDIO::AO_F2ENDER_VH; + //lookupRate = ODDIO::AO_F1SNDFX_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) + { + lookupVag = ODDIO::AO_RFENDER_VH; + //lookupRate = ODDIO::AO_RFSNDFX_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) @@ -62,41 +156,6 @@ void MidiPlayer::SND_Shutdown() SPU::DeInit(); } - -// One - shot VAGs will be created with an additional 16 - byte block -// attached to the end.The block is used to prevent unnecessary SPU -// interrupts or SPU free - run.The block reads as follows : -// “00077777 77777777 77777777 77777777” or -// “00070000 00000000 00000000 00000000.” Looping VAGs do not contain -// this block. -// -// https://psx.arthus.net/sdk/Psy-Q/DOCS/LibOver47.pdf -// -// Doesn't seem to work for PC samples, maybe check PSX samples -// in the future. -bool isLoop(SPU::Sample* sample) -{ - s16* b = sample->buffer; - u32 l = sample->len; - - bool oneshot = false; - oneshot |= b[l - 16] == 0x00 && b[l - 15] == 0x07 && b[l - 14] == 0x77 - && b[l - 13] == 0x77 && b[l - 12] == 0x77 && b[l - 11] == 0x77 - && b[l - 10] == 0x77 && b[l - 9] == 0x77 && b[l - 8] == 0x77 - && b[l - 7] == 0x77 && b[l - 6] == 0x77 && b[l - 5] == 0x77 - && b[l - 4] == 0x77 && b[l - 3] == 0x77 && b[l - 2] == 0x77 - && b[l - 1] == 0x77; - - oneshot |= b[l - 16] == 0x00 && b[l - 15] == 0x07 && b[l - 14] == 0x00 - && b[l - 13] == 0x00 && b[l - 12] == 0x00 && b[l - 11] == 0x00 - && b[l - 10] == 0x00 && b[l - 9] == 0x00 && b[l - 8] == 0x00 - && b[l - 7] == 0x00 && b[l - 6] == 0x00 && b[l - 5] == 0x00 - && b[l - 4] == 0x00 && b[l - 3] == 0x00 && b[l - 2] == 0x00 - && b[l - 1] == 0x00; - - return oneshot; -} - void MidiPlayer::SND_Load_VABS(SoundBlockInfo* pSoundBlockInfo, s32 reverb) { reverb; // TODO - what do we do with this? override the patch/sample? @@ -126,8 +185,8 @@ void MidiPlayer::SND_Load_VABS(SoundBlockInfo* pSoundBlockInfo, s32 reverb) // HEADER VagAtr* vagAttr = (VagAtr*) &vabHeader[1]; std::vector programs; - - for (s32 i = 0; i < vabHeader->field_12_num_progs; i++) + s32 vagOffset = 0; + for (s32 pIdx = 0; pIdx < vabHeader->field_12_num_progs; pIdx++) { SPU::Patch* patch = new SPU::Patch((u8) vagAttr->field_14_prog); @@ -135,9 +194,8 @@ void MidiPlayer::SND_Load_VABS(SoundBlockInfo* pSoundBlockInfo, s32 reverb) /////////// // PATCH (Instruments) - for (s32 x = 0; x < 16; x++) + for (s32 vagIdx = 0; vagIdx < 16; vagIdx++) { - /////////// // SAMPLE if (vagAttr->field_2_vol > 0 && vagAttr->field_16_vag > 0) @@ -148,20 +206,23 @@ void MidiPlayer::SND_Load_VABS(SoundBlockInfo* pSoundBlockInfo, s32 reverb) SPU::ADSR adsr = SPU::parseADSR(ADSR1, ADSR2); SPU::Sample* sample = new SPU::Sample(s->m_SampleBuffer, s->i_SampleSize, s->sampleRate); - patch->samples[x] = sample; + patch->samples[vagIdx] = sample; sample->adsr = adsr; - sample->volume = vagAttr->field_2_vol; + 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->loop = s->loop; sample->priority = vagAttr->field_0_priority; + sample->loop = s->loop; + + mSoundSampleParser->applyFix(pSoundBlockInfo->header_name, vagAttr->field_16_vag, vagOffset, sample); } - ++vagAttr; + vagOffset++; + vagAttr++; } } @@ -293,7 +354,7 @@ s32 MidiPlayer::SFX_SfxDefinition_Play(SfxDefinition* sfxDef, s32 volLeft, s32 v sanitizeVolume(&volLeft, 0, 127); sanitizeVolume(&volRight, 0, 127); - + return SPU::OneShotPlay(sfxDef->program, sfxDef->note, (s16) volLeft, (s16) volRight, (u8) std::max(pitch_min, pitch_max), pitch_min, pitch_max); } diff --git a/Source/AliveLibCommon/audio/MidiPlayer.hpp b/Source/AliveLibCommon/audio/MidiPlayer.hpp index 1f04c6658..97895d381 100644 --- a/Source/AliveLibCommon/audio/MidiPlayer.hpp +++ b/Source/AliveLibCommon/audio/MidiPlayer.hpp @@ -242,6 +242,13 @@ namespace psx { std::vector empty; throw std::invalid_argument("parseSamples is not overridden"); } + virtual void applyFix(char_type* headerName, s32 vag, s32 vagOffset, SPU::Sample* sample) + { + headerName; + vag; + vagOffset; + sample; + } }; /* From 1b2391c74d96d786b941a178d7f28a1af47dc4d1 Mon Sep 17 00:00:00 2001 From: Sean Horton <3359434+sean-horton@users.noreply.github.com> Date: Wed, 26 Apr 2023 20:03:52 -0400 Subject: [PATCH 60/82] and another one --- Source/AliveLibCommon/audio/oddio.h | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Source/AliveLibCommon/audio/oddio.h b/Source/AliveLibCommon/audio/oddio.h index 9029827fb..81e10ce8f 100644 --- a/Source/AliveLibCommon/audio/oddio.h +++ b/Source/AliveLibCommon/audio/oddio.h @@ -66,6 +66,17 @@ const std::vector> AO_D1SNDFX_VH = {{ //////////////////////////////////////// // 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}, From a7680cb96e9bb03ae23496754a08f561b3b3737a Mon Sep 17 00:00:00 2001 From: Sean Horton <3359434+sean-horton@users.noreply.github.com> Date: Thu, 27 Apr 2023 21:21:03 -0400 Subject: [PATCH 61/82] and another one... --- Source/AliveLibCommon/audio/oddio.h | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Source/AliveLibCommon/audio/oddio.h b/Source/AliveLibCommon/audio/oddio.h index 81e10ce8f..890b96b72 100644 --- a/Source/AliveLibCommon/audio/oddio.h +++ b/Source/AliveLibCommon/audio/oddio.h @@ -98,6 +98,17 @@ const std::vector> AO_D2SNDFX_VH = {{ //////////////////////////////////////// // 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 = {{ {45, 320, 0x407f047f, 0x7f000048, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x2d003a, 0xc100c0, 0xc300c2}, {0, 321, 0x0, 0x0, 0x0, 0xb2b10000, 0x5fc080ff, 0x3a, 0xc100c0, 0xc300c2}, From c702c8f74d1d0492ad6ff9ccc9c958d66eca4505 Mon Sep 17 00:00:00 2001 From: Sean Horton <3359434+sean-horton@users.noreply.github.com> Date: Thu, 27 Apr 2023 22:15:16 -0400 Subject: [PATCH 62/82] and another one. --- Source/AliveLibCommon/audio/oddio.h | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Source/AliveLibCommon/audio/oddio.h b/Source/AliveLibCommon/audio/oddio.h index 890b96b72..ba018dd2e 100644 --- a/Source/AliveLibCommon/audio/oddio.h +++ b/Source/AliveLibCommon/audio/oddio.h @@ -127,6 +127,19 @@ const std::vector> AO_D2ENDER_VH = {{ //////////////////////////////////////// // 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 = {{ {84, 448, 0x407f007f, 0x7f000048, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x54003a, 0xc100c0, 0xc300c2}, {0, 449, 0x0, 0x0, 0x0, 0xb2b10000, 0x5fc080ff, 0x3a, 0xc100c0, 0xc300c2}, From b7d88faea3ff6ab913d841d4e28a0e70831de406 Mon Sep 17 00:00:00 2001 From: Sean Horton <3359434+sean-horton@users.noreply.github.com> Date: Thu, 27 Apr 2023 22:35:11 -0400 Subject: [PATCH 63/82] and another one --- Source/AliveLibCommon/audio/oddio.h | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Source/AliveLibCommon/audio/oddio.h b/Source/AliveLibCommon/audio/oddio.h index ba018dd2e..b9e9c1674 100644 --- a/Source/AliveLibCommon/audio/oddio.h +++ b/Source/AliveLibCommon/audio/oddio.h @@ -161,6 +161,19 @@ const std::vector> AO_E1SNDFX_VH = {{ //////////////////////////////////////// // E2.LVL - E2SNDFX.VH +const std::vector> AO_E1SNDFX_RATE = {{ + {50, 44100}, + {51, 44100}, + {52, 22050}, + {63, 16000}, + {64, 16000}, + {76, 22050}, + {78, 16000}, + {79, 16000}, + {83, 22050}, + {87, 22050}, + {88, 22050}}}; + const std::vector> AO_E2SNDFX_VH = {{ {75, 432, 0x407f007f, 0x7f000048, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x4b003a, 0xc100c0, 0xc300c2}, {0, 433, 0x0, 0x0, 0x0, 0xb2b10000, 0x5fc080ff, 0x3a, 0xc100c0, 0xc300c2}, From 070ce3235001732fa669e278b08f1c3d803ceae8 Mon Sep 17 00:00:00 2001 From: Sean Horton <3359434+sean-horton@users.noreply.github.com> Date: Fri, 28 Apr 2023 19:54:23 -0400 Subject: [PATCH 64/82] last one, but some are broken still --- Source/AliveLibCommon/audio/oddio.h | 39 ++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/Source/AliveLibCommon/audio/oddio.h b/Source/AliveLibCommon/audio/oddio.h index b9e9c1674..be0c69e14 100644 --- a/Source/AliveLibCommon/audio/oddio.h +++ b/Source/AliveLibCommon/audio/oddio.h @@ -161,7 +161,7 @@ const std::vector> AO_E1SNDFX_VH = {{ //////////////////////////////////////// // E2.LVL - E2SNDFX.VH -const std::vector> AO_E1SNDFX_RATE = {{ +const std::vector> AO_E2SNDFX_RATE = {{ {50, 44100}, {51, 44100}, {52, 22050}, @@ -218,6 +218,19 @@ const std::vector> AO_F1SNDFX_VH = {{ //////////////////////////////////////// // 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}, @@ -246,6 +259,19 @@ const std::vector> AO_F2SNDFX_VH = {{ //////////////////////////////////////// // 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 = {{ {59, 352, 0x407f047f, 0x7f000048, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x3b003a, 0xc100c0, 0xc300c2}, {0, 353, 0x0, 0x0, 0x0, 0xb2b10000, 0x5fc080ff, 0x3a, 0xc100c0, 0xc300c2}, @@ -436,6 +462,17 @@ const std::vector> AO_RFSNDFX_VH = {{ //////////////////////////////////////// // R6.LVL - RFENDER.VH +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}, From 65233c18fbba152dd819ed1c12de4135b4643a5c Mon Sep 17 00:00:00 2001 From: Sean Horton <3359434+sean-horton@users.noreply.github.com> Date: Fri, 28 Apr 2023 19:55:27 -0400 Subject: [PATCH 65/82] use remaining lookup tables --- Source/AliveLibCommon/audio/MidiPlayer.cpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/Source/AliveLibCommon/audio/MidiPlayer.cpp b/Source/AliveLibCommon/audio/MidiPlayer.cpp index 9e22da2bb..49749aa54 100644 --- a/Source/AliveLibCommon/audio/MidiPlayer.cpp +++ b/Source/AliveLibCommon/audio/MidiPlayer.cpp @@ -52,22 +52,22 @@ class DefaultSoundSampleParser : public SoundSampleParser else if (strcmp(headerName, "D2SNDFX.VH") == 0) { lookupVag = ODDIO::AO_D2SNDFX_VH; - //lookupRate = ODDIO::AO_D2SNDFX_RATE; + lookupRate = ODDIO::AO_D2SNDFX_RATE; } else if (strcmp(headerName, "D2ENDER.VH") == 0) { lookupVag = ODDIO::AO_D2ENDER_VH; - //lookupRate = ODDIO::AO_D2SNDFX_RATE; + lookupRate = ODDIO::AO_D2ENDER_RATE; } else if (strcmp(headerName, "E1SNDFX.VH") == 0) { lookupVag = ODDIO::AO_E1SNDFX_VH; - //lookupRate = ODDIO::AO_D2SNDFX_RATE; + lookupRate = ODDIO::AO_E1SNDFX_RATE; } else if (strcmp(headerName, "E2SNDFX.VH") == 0) { lookupVag = ODDIO::AO_E2SNDFX_VH; - //lookupRate = ODDIO::AO_D2SNDFX_RATE; + lookupRate = ODDIO::AO_E2SNDFX_RATE; } else if (strcmp(headerName, "F1SNDFX.VH") == 0) { @@ -76,13 +76,13 @@ class DefaultSoundSampleParser : public SoundSampleParser } else if (strcmp(headerName, "F2SNDFX.VH") == 0) { - lookupVag = ODDIO::AO_F1SNDFX_VH; - //lookupRate = ODDIO::AO_F1SNDFX_RATE; + 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_F1SNDFX_RATE; + lookupRate = ODDIO::AO_F2ENDER_RATE; } else if (strcmp(headerName, "MLSNDFX.VH") == 0) { @@ -102,7 +102,7 @@ class DefaultSoundSampleParser : public SoundSampleParser else if (strcmp(headerName, "RFENDER.VH") == 0) { lookupVag = ODDIO::AO_RFENDER_VH; - //lookupRate = ODDIO::AO_RFSNDFX_RATE; + lookupRate = ODDIO::AO_RFENDER_RATE; } for (auto entry : lookupVag) @@ -169,6 +169,7 @@ void MidiPlayer::SND_Load_VABS(SoundBlockInfo* pSoundBlockInfo, s32 reverb) break; } + // Read header ResourceData* vabHeaderResource = mResourceProvider->readFile(pSoundBlockInfo->header_name); VabHeader* vabHeader = reinterpret_cast(vabHeaderResource->data); @@ -206,6 +207,7 @@ void MidiPlayer::SND_Load_VABS(SoundBlockInfo* pSoundBlockInfo, s32 reverb) 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; From d00aa57439b5a09185711bdf3c5fc894428c1ee1 Mon Sep 17 00:00:00 2001 From: Sean Horton <3359434+sean-horton@users.noreply.github.com> Date: Fri, 28 Apr 2023 20:30:40 -0400 Subject: [PATCH 66/82] add comment --- Source/AliveLibCommon/audio/oddio.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Source/AliveLibCommon/audio/oddio.h b/Source/AliveLibCommon/audio/oddio.h index be0c69e14..cf305aba6 100644 --- a/Source/AliveLibCommon/audio/oddio.h +++ b/Source/AliveLibCommon/audio/oddio.h @@ -462,6 +462,8 @@ const std::vector> AO_RFSNDFX_VH = {{ //////////////////////////////////////// // 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}, From 63a05f96b747d65f65ef6f15bb72d7c57dc544b1 Mon Sep 17 00:00:00 2001 From: Sean Horton <3359434+sean-horton@users.noreply.github.com> Date: Fri, 28 Apr 2023 20:50:51 -0400 Subject: [PATCH 67/82] RF escape audio is all messed up --- Source/AliveLibCommon/audio/MidiPlayer.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Source/AliveLibCommon/audio/MidiPlayer.cpp b/Source/AliveLibCommon/audio/MidiPlayer.cpp index 49749aa54..9bd312137 100644 --- a/Source/AliveLibCommon/audio/MidiPlayer.cpp +++ b/Source/AliveLibCommon/audio/MidiPlayer.cpp @@ -101,8 +101,9 @@ class DefaultSoundSampleParser : public SoundSampleParser } else if (strcmp(headerName, "RFENDER.VH") == 0) { - lookupVag = ODDIO::AO_RFENDER_VH; - lookupRate = ODDIO::AO_RFENDER_RATE; + // 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) @@ -207,7 +208,7 @@ void MidiPlayer::SND_Load_VABS(SoundBlockInfo* pSoundBlockInfo, s32 reverb) 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; From 16f01332fb91e58cfe412ef3b09d9af51ac20757 Mon Sep 17 00:00:00 2001 From: Sean Horton <3359434+sean-horton@users.noreply.github.com> Date: Fri, 28 Apr 2023 21:49:45 -0400 Subject: [PATCH 68/82] whistle fixes --- Source/AliveLibCommon/audio/oddio.h | 56 ++++++++++++++++------------- 1 file changed, 31 insertions(+), 25 deletions(-) diff --git a/Source/AliveLibCommon/audio/oddio.h b/Source/AliveLibCommon/audio/oddio.h index cf305aba6..26db586a5 100644 --- a/Source/AliveLibCommon/audio/oddio.h +++ b/Source/AliveLibCommon/audio/oddio.h @@ -32,6 +32,12 @@ namespace ODDIO { // 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 @@ -48,8 +54,8 @@ const std::vector> AO_D1SNDFX_RATE = {{ {119, 22050}}}; const std::vector> AO_D1SNDFX_VH = {{ - {106, 496, 0x407f007f, 0x7f000048, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x6a003a, 0xc100c0, 0xc300c2}, - {0, 497, 0x0, 0x0, 0x0, 0xb2b10000, 0x5fc080ff, 0x3a, 0xc100c0, 0xc300c2}, + // 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}, @@ -81,8 +87,8 @@ 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}, - {97, 576, 0x407f047f, 0x7f000048, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x61003a, 0xc100c0, 0xc300c2}, - {0, 577, 0x0, 0x0, 0x0, 0xb2b10000, 0x5fc080ff, 0x3a, 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}, @@ -110,8 +116,8 @@ const std::vector> AO_D2ENDER_RATE = {{ {58, 22050}}}; const std::vector> AO_D2ENDER_VH = {{ - {45, 320, 0x407f047f, 0x7f000048, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x2d003a, 0xc100c0, 0xc300c2}, - {0, 321, 0x0, 0x0, 0x0, 0xb2b10000, 0x5fc080ff, 0x3a, 0xc100c0, 0xc300c2}, + // 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}, @@ -141,8 +147,8 @@ const std::vector> AO_E1SNDFX_RATE = {{ {97, 22050}}}; const std::vector> AO_E1SNDFX_VH = {{ - {84, 448, 0x407f007f, 0x7f000048, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x54003a, 0xc100c0, 0xc300c2}, - {0, 449, 0x0, 0x0, 0x0, 0xb2b10000, 0x5fc080ff, 0x3a, 0xc100c0, 0xc300c2}, + // 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}, @@ -169,14 +175,14 @@ const std::vector> AO_E2SNDFX_RATE = {{ {64, 16000}, {76, 22050}, {78, 16000}, - {79, 16000}, + {79, 22050}, {83, 22050}, {87, 22050}, {88, 22050}}}; const std::vector> AO_E2SNDFX_VH = {{ - {75, 432, 0x407f007f, 0x7f000048, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x4b003a, 0xc100c0, 0xc300c2}, - {0, 433, 0x0, 0x0, 0x0, 0xb2b10000, 0x5fc080ff, 0x3a, 0xc100c0, 0xc300c2}, + // 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}, @@ -203,8 +209,8 @@ const std::vector> AO_F1SNDFX_RATE = {{ {106, 22050}}}; const std::vector> AO_F1SNDFX_VH = {{ - {93, 416, 0x407f007f, 0x7f000048, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x5d003a, 0xc100c0, 0xc300c2}, - {0, 417, 0x0, 0x0, 0x0, 0xb2b10000, 0x5fc080ff, 0x3a, 0xc100c0, 0xc300c2}, + // 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}, @@ -244,8 +250,8 @@ const std::vector> AO_F2SNDFX_VH = {{ {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}, - {100, 608, 0x407f047f, 0x7f000048, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x64003a, 0xc100c0, 0xc300c2}, - {0, 609, 0x0, 0x0, 0x0, 0xb2b10000, 0x5fc080ff, 0x3a, 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}, @@ -273,8 +279,8 @@ const std::vector> AO_F2ENDER_RATE = {{ {93, 3800}}}; const std::vector> AO_F2ENDER_VH = {{ - {59, 352, 0x407f047f, 0x7f000048, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x3b003a, 0xc100c0, 0xc300c2}, - {0, 353, 0x0, 0x0, 0x0, 0xb2b10000, 0x5fc080ff, 0x3a, 0xc100c0, 0xc300c2}, + // 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}, @@ -303,8 +309,8 @@ 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}, - {95, 496, 0x407f047f, 0x7f000048, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x5f003a, 0xc100c0, 0xc300c2}, - {0, 497, 0x0, 0x0, 0x0, 0xb2b10000, 0x5fc080ff, 0x3a, 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}, @@ -342,8 +348,8 @@ const std::vector> AO_OPTSNDFX_VH = {{ {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}, - {15, 144, 0x407f007f, 0x7f000048, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0xf003a, 0xc100c0, 0xc300c2}, - {0, 145, 0x0, 0x0, 0x0, 0xb2b10000, 0x5fc080ff, 0x3a, 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}, @@ -414,8 +420,8 @@ const std::vector> AO_RFSNDFX_VH = {{ {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}, - {98, 608, 0x407f047f, 0x7f000048, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x62003a, 0xc100c0, 0xc300c2}, - {0, 609, 0x0, 0x0, 0x0, 0xb2b10000, 0x5fc080ff, 0x3a, 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}, @@ -500,8 +506,8 @@ const std::vector> AO_RFENDER_VH = {{ {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}, - {68, 464, 0x407f047f, 0x7f000048, 0x0, 0xb2b17f7f, 0xdfeb80ff, 0x44003a, 0xc100c0, 0xc300c2}, - {0, 465, 0x0, 0x0, 0x0, 0xb2b10000, 0x5fc080ff, 0x3a, 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}, From cecffc87b954ca9d13c21fe6d81335952943ee72 Mon Sep 17 00:00:00 2001 From: Sean Horton <3359434+sean-horton@users.noreply.github.com> Date: Sat, 20 May 2023 09:39:13 -0400 Subject: [PATCH 69/82] fix pitches? --- Source/AliveLibCommon/audio/Sequencer.cpp | 55 ++++++++++++++++++----- Source/AliveLibCommon/audio/Sequencer.hpp | 10 +++-- 2 files changed, 49 insertions(+), 16 deletions(-) diff --git a/Source/AliveLibCommon/audio/Sequencer.cpp b/Source/AliveLibCommon/audio/Sequencer.cpp index d43cf6757..e23be7130 100644 --- a/Source/AliveLibCommon/audio/Sequencer.cpp +++ b/Source/AliveLibCommon/audio/Sequencer.cpp @@ -40,7 +40,7 @@ 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, u8 pitch, s32 pitchMin, s32 pitchMax); +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); @@ -106,6 +106,7 @@ bool SeqPlay(s32 seqId, s32 repeats, bool stopDuplicateSeq) { SPUSeqStop(seqId); } + // SPUSeqSetVolume(seqId, 100, 100); bool res = SPUSeqPlay(seqId, repeats); mutex.unlock(); return res; @@ -142,7 +143,7 @@ bool SeqIsDone(s32 seqId) return res; } -s32 SPU::OneShotPlay(s32 patchId, u8 note, s16 voll, s16 volr, u8 pitch, s32 pitchMin, s32 pitchMax) +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); @@ -489,7 +490,7 @@ bool SPUSeqIsDone(s32 seqId) return res; } -s32 SPUOneShotPlay(s32 patchId, u8 note, s16 voll, s16 volr, u8 pitch, s32 pitchMin, s32 pitchMax) +s32 SPUOneShotPlay(s32 patchId, u8 note, s16 voll, s16 volr, s32 pitch, s32 pitchMin, s32 pitchMax) { volr; voll; @@ -973,22 +974,25 @@ USHORT _svm_ptable[] = { 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, - 8192}; + 7958, 7987, 8016, 8045, 8074, 8103, 8133, 8162}; // 192 total (0-191) -void Voice::RefreshNoteStep() +s32 Voice::noteFromVgmTrans() { - // 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" + // 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: This doesn't 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) + unsigned int pitchA; SHORT calc, type; signed int add, sfine; //, ret; signed int fine = pitch < pitchMin ? pitchMin : pitch; - sfine = fine + sample->rootNotePitchShift; + sfine = fine + ((s32) sample->rootNotePitchShift); if (sfine < 0) { sfine += 7; @@ -1002,8 +1006,28 @@ void Voice::RefreshNoteStep() sfine -= 16; } - calc = (SHORT) (add + (note - (sample->rootNote - 60))); //((center + 60) - note) + add; - pitchA = _svm_ptable[16 * (calc % 12) + (short) sfine]; + calc = (SHORT) (add + (note - (((s32) sample->rootNote) - 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[0]; + } + else + { + pitchA = _svm_ptable[pos]; + } + // END: Added + type = calc / 12 - 5; // regular shift @@ -1018,7 +1042,14 @@ void Voice::RefreshNoteStep() { ret = pitchA >> -type; } + return ret; +} + +void Voice::RefreshNoteStep() +{ + s32 ret = noteFromVgmTrans(); + // freq = (ret * 44100) / 4096 // step seems to be calculated for 8000hz samples hence the division noteStep = (u16) ((sample->SampleRate / 8000.0) * double(ret)); // std::cout << ret << " " << (int) note << " " << noteStep << std::endl; diff --git a/Source/AliveLibCommon/audio/Sequencer.hpp b/Source/AliveLibCommon/audio/Sequencer.hpp index bd3c4fe04..0cdc4a440 100644 --- a/Source/AliveLibCommon/audio/Sequencer.hpp +++ b/Source/AliveLibCommon/audio/Sequencer.hpp @@ -59,6 +59,7 @@ class Sample } + s32 vag; s8 priority; s16 volume; s16 pan; @@ -68,8 +69,8 @@ class Sample bool loop; // Root Key - u8 rootNote; - u8 rootNotePitchShift; + s32 rootNote; + s32 rootNotePitchShift; // Key range u8 minNote; @@ -244,7 +245,7 @@ class Voice Sequence* sequence = NULL; u8 patchId; u8 channelId; - u8 note; + s32 note; s32 pitch; s32 pitchMin = 0; s32 pitchMax = 127; @@ -282,6 +283,7 @@ class Voice private: s32 Interpolate(); + s32 noteFromVgmTrans(); }; @@ -293,7 +295,7 @@ 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, u8 pitch, s32 pitchMin, s32 pitchMax); +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. From ad6cef7eee0f7080db5019149e1e811041ccec43 Mon Sep 17 00:00:00 2001 From: Sean Horton <3359434+sean-horton@users.noreply.github.com> Date: Sat, 20 May 2023 09:46:00 -0400 Subject: [PATCH 70/82] add comment for removed item from table --- Source/AliveLibCommon/audio/Sequencer.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/AliveLibCommon/audio/Sequencer.cpp b/Source/AliveLibCommon/audio/Sequencer.cpp index e23be7130..755fbb577 100644 --- a/Source/AliveLibCommon/audio/Sequencer.cpp +++ b/Source/AliveLibCommon/audio/Sequencer.cpp @@ -975,6 +975,7 @@ USHORT _svm_ptable[] = { 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? s32 Voice::noteFromVgmTrans() { From 9016f9ecbcf15f95c7284ec168b531e400cde4f3 Mon Sep 17 00:00:00 2001 From: Sean Horton <3359434+sean-horton@users.noreply.github.com> Date: Sat, 20 May 2023 11:01:27 -0400 Subject: [PATCH 71/82] cleanup --- Source/AliveLibCommon/audio/MidiPlayer.cpp | 6 +++--- Source/AliveLibCommon/audio/Sequencer.hpp | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Source/AliveLibCommon/audio/MidiPlayer.cpp b/Source/AliveLibCommon/audio/MidiPlayer.cpp index 9bd312137..0c2bf99d3 100644 --- a/Source/AliveLibCommon/audio/MidiPlayer.cpp +++ b/Source/AliveLibCommon/audio/MidiPlayer.cpp @@ -101,7 +101,7 @@ class DefaultSoundSampleParser : public SoundSampleParser } else if (strcmp(headerName, "RFENDER.VH") == 0) { - // These can't be used - the vag atr are messed up + // TODO - These can't be used - the vag atr are messed up //lookupVag = ODDIO::AO_RFENDER_VH; //lookupRate = ODDIO::AO_RFENDER_RATE; } @@ -358,7 +358,7 @@ s32 MidiPlayer::SFX_SfxDefinition_Play(SfxDefinition* sfxDef, s32 volLeft, s32 v sanitizeVolume(&volLeft, 0, 127); sanitizeVolume(&volRight, 0, 127); - return SPU::OneShotPlay(sfxDef->program, sfxDef->note, (s16) volLeft, (s16) volRight, (u8) std::max(pitch_min, pitch_max), pitch_min, pitch_max); + 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) @@ -371,7 +371,7 @@ s32 MidiPlayer::SFX_SfxDefinition_Play(SfxDefinition* sfxDef, s32 volume, s32 pi sanitizePitch(&pitch_max, sfxDef->pitch_max); sanitizeVolume(&volume, 1, 127); - return SPU::OneShotPlay(sfxDef->program, sfxDef->note, (s16) volume, (s16) volume, (u8) std::max(pitch_min, pitch_max), pitch_min, pitch_max); + 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) diff --git a/Source/AliveLibCommon/audio/Sequencer.hpp b/Source/AliveLibCommon/audio/Sequencer.hpp index 0cdc4a440..ace42d01f 100644 --- a/Source/AliveLibCommon/audio/Sequencer.hpp +++ b/Source/AliveLibCommon/audio/Sequencer.hpp @@ -59,7 +59,6 @@ class Sample } - s32 vag; s8 priority; s16 volume; s16 pan; From ca27e1e2ff0f10aebbf55d3b60f425c7769bd1ce Mon Sep 17 00:00:00 2001 From: Sean Horton <3359434+sean-horton@users.noreply.github.com> Date: Sat, 20 May 2023 17:32:36 -0400 Subject: [PATCH 72/82] pitch can go lower --- Source/AliveLibCommon/audio/Sequencer.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/AliveLibCommon/audio/Sequencer.cpp b/Source/AliveLibCommon/audio/Sequencer.cpp index 755fbb577..b9902db55 100644 --- a/Source/AliveLibCommon/audio/Sequencer.cpp +++ b/Source/AliveLibCommon/audio/Sequencer.cpp @@ -13,7 +13,7 @@ namespace SPU { // SPU state std::mutex mutex; -const int VOICE_SIZE_LIMIT = 24; // Can be increased to 32, but 24 is what PSX has +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; @@ -1021,7 +1021,7 @@ s32 Voice::noteFromVgmTrans() } else if (pos < 0) { - pitchA = _svm_ptable[0]; + pitchA = _svm_ptable[192 + pos] >> 1; } else { From 26429d9b2d2f613678162c162590761f072ecc32 Mon Sep 17 00:00:00 2001 From: Sean Horton <3359434+sean-horton@users.noreply.github.com> Date: Sun, 21 May 2023 13:47:42 -0400 Subject: [PATCH 73/82] Change volume division --- Source/AliveLibCommon/audio/Sequencer.cpp | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/Source/AliveLibCommon/audio/Sequencer.cpp b/Source/AliveLibCommon/audio/Sequencer.cpp index b9902db55..efc1c6d5c 100644 --- a/Source/AliveLibCommon/audio/Sequencer.cpp +++ b/Source/AliveLibCommon/audio/Sequencer.cpp @@ -648,10 +648,12 @@ void SPUTick(void* udata, Uint8* stream, int len) // make value usable by SDL // It might make sense to increase the mix volume above MIX_MAXVOUME. // I think psx sounds like it runs a little hot, compressing audio a bit - leftSample = leftSample / 32767.0f; - rightSample = rightSample / 32767.0f; - SDL_MixAudioFormat((Uint8*) (AudioStream + i), (const Uint8*) &leftSample, AUDIO_F32, sizeof(float), SDL_MIX_MAXVOLUME * 2); - SDL_MixAudioFormat((Uint8*) (AudioStream + i + 1), (const Uint8*) &rightSample, AUDIO_F32, sizeof(float), SDL_MIX_MAXVOLUME * 2); + leftSample = leftSample / 16383.0f; // 16383.0f 32767.0f + rightSample = rightSample / 16383.0f; + leftSample = leftSample > 1 ? 1 : leftSample; + rightSample = rightSample > 1 ? 1 : rightSample; + SDL_MixAudioFormat((Uint8*) (AudioStream + i), (const Uint8*) &leftSample, AUDIO_F32, sizeof(float), SDL_MIX_MAXVOLUME); + SDL_MixAudioFormat((Uint8*) (AudioStream + i + 1), (const Uint8*) &rightSample, AUDIO_F32, sizeof(float), SDL_MIX_MAXVOLUME); } } @@ -1112,9 +1114,6 @@ void Voice::RefreshVolume() left = (left * (127 - 64)) / 63; } - - //if (_svm_cur.field_14_seq_sep_no != 0x21) - //{ s32 right = uVar1; right; left = (left * left) / 0x3fff; @@ -1122,11 +1121,6 @@ void Voice::RefreshVolume() leftReg = left; rightReg = right; - //if (velocity == 84) - //{ - // leftReg = 2176; - // rightReg = 2176; - //} //std::cout << left << " " << right << std::endl; } From ea16adac3709dba96028a4f86bf10ac70fe4fddd Mon Sep 17 00:00:00 2001 From: Sean Horton <3359434+sean-horton@users.noreply.github.com> Date: Sat, 8 Jun 2024 18:31:19 -0400 Subject: [PATCH 74/82] checkpoint incorporated new panning --- Source/AliveLibCommon/audio/MidiPlayer.cpp | 6 +- Source/AliveLibCommon/audio/MidiPlayer.hpp | 2 +- Source/AliveLibCommon/audio/Sequencer.cpp | 258 ++++++++++++--------- Source/AliveLibCommon/audio/Sequencer.hpp | 20 +- 4 files changed, 161 insertions(+), 125 deletions(-) diff --git a/Source/AliveLibCommon/audio/MidiPlayer.cpp b/Source/AliveLibCommon/audio/MidiPlayer.cpp index 0c2bf99d3..b9b04dcdb 100644 --- a/Source/AliveLibCommon/audio/MidiPlayer.cpp +++ b/Source/AliveLibCommon/audio/MidiPlayer.cpp @@ -190,8 +190,10 @@ void MidiPlayer::SND_Load_VABS(SoundBlockInfo* pSoundBlockInfo, s32 reverb) s32 vagOffset = 0; for (s32 pIdx = 0; pIdx < vabHeader->field_12_num_progs; pIdx++) { - - SPU::Patch* patch = new SPU::Patch((u8) vagAttr->field_14_prog); + ProgAtr progAtr = vabHeader->field_20_progs[pIdx]; + u8 progVol = progAtr.field_1_vol; + progVol = progVol == 0 ? 127 : progVol; + SPU::Patch* patch = new SPU::Patch((u8) vagAttr->field_14_prog, progVol, progAtr.field_4_pan); SPU::PatchAdd(patch); /////////// diff --git a/Source/AliveLibCommon/audio/MidiPlayer.hpp b/Source/AliveLibCommon/audio/MidiPlayer.hpp index 97895d381..a8008deec 100644 --- a/Source/AliveLibCommon/audio/MidiPlayer.hpp +++ b/Source/AliveLibCommon/audio/MidiPlayer.hpp @@ -99,7 +99,7 @@ namespace psx { struct ProgAtr final { u8 field_0_num_tones; - s8 field_1_vol; + u8 field_1_vol; s8 field_2_priority; s8 field_3_mode; s8 field_4_pan; diff --git a/Source/AliveLibCommon/audio/Sequencer.cpp b/Source/AliveLibCommon/audio/Sequencer.cpp index efc1c6d5c..65ba568fe 100644 --- a/Source/AliveLibCommon/audio/Sequencer.cpp +++ b/Source/AliveLibCommon/audio/Sequencer.cpp @@ -492,8 +492,7 @@ bool SPUSeqIsDone(s32 seqId) s32 SPUOneShotPlay(s32 patchId, u8 note, s16 voll, s16 volr, s32 pitch, s32 pitchMin, s32 pitchMax) { - volr; - voll; + // TODO - // - SoundEfx: apparently use sweeps and reuse the voice register (slig walking offscreen) // - OneShots: do not reuse voice regester (slig turning) @@ -505,29 +504,6 @@ s32 SPUOneShotPlay(s32 patchId, u8 note, s16 voll, s16 volr, s32 pitch, s32 pitc return 0; } - char pan; - unsigned int volr_; - unsigned int voll_; - - voll_ = voll; - volr_ = volr; - if (voll_ == volr_) - { - pan = 64; - } - else - { - if (volr_ < voll_) - { - pan = (char) ((volr_ << 6) / voll_); - } - else - { - pan = 127 - (char) ((voll_ << 6) / volr_); - voll = volr; - } - } - int ids = 0; for (Sample* s : patch->samples) { @@ -548,15 +524,16 @@ s32 SPUOneShotPlay(s32 patchId, u8 note, s16 voll, s16 volr, s32 pitch, s32 pitc } v->patchId = (u8) patchId; + v->sample = s; v->note = note; - v->velocity = voll; - v->pan = pan; + v->velocity = (voll + volr) / 2; v->pitch = pitch; v->pitchMin = pitchMin; v->pitchMax = pitchMax; - v->sample = s; v->RefreshNoteStep(); - v->RefreshVolume(); + + v->aVoll = (voll / 128.0f) * (patch->_vol / 128.0f) * (v->sample->volume / 128.0f); + v->aVolr = (volr / 128.0f) * (patch->_vol / 128.0f) * (v->sample->volume / 128.0f); ids |= v->id; } @@ -616,7 +593,7 @@ void SPUTick(void* udata, Uint8* stream, int len) continue; } - std::tuple s = v->Tick(); + std::tuple s = v->Tick(); leftSample += std::get<0>(s); rightSample += std::get<1>(s); @@ -627,8 +604,8 @@ void SPUTick(void* udata, Uint8* stream, int len) // 4 Reverb if (v->sample->reverb == 4) { - reverb_in_left += std::get<0>(s); - reverb_in_right += std::get<1>(s); + reverb_in_left += (s32) std::get<0>(s); + reverb_in_right += (s32) std::get<1>(s); } } @@ -642,14 +619,14 @@ void SPUTick(void* udata, Uint8* stream, int len) leftSample += reverb_out_left; rightSample += reverb_out_right; - leftSample = (float) ApplyVolume(Clamp16((s32) leftSample), MAX_VOLUME); // master vol value - rightSample = (float) ApplyVolume(Clamp16((s32) rightSample), MAX_VOLUME); + //leftSample = (float) ApplyVolume(Clamp16((s32) leftSample), MAX_VOLUME); // master vol value + //rightSample = (float) ApplyVolume(Clamp16((s32) rightSample), MAX_VOLUME); // make value usable by SDL // It might make sense to increase the mix volume above MIX_MAXVOUME. // I think psx sounds like it runs a little hot, compressing audio a bit - leftSample = leftSample / 16383.0f; // 16383.0f 32767.0f - rightSample = rightSample / 16383.0f; + leftSample = leftSample / (32767.0f * 2); // 16383.0f 32767.0f + rightSample = rightSample / (32767.0f * 2); leftSample = leftSample > 1 ? 1 : leftSample; rightSample = rightSample > 1 ? 1 : rightSample; SDL_MixAudioFormat((Uint8*) (AudioStream + i), (const Uint8*) &leftSample, AUDIO_F32, sizeof(float), SDL_MIX_MAXVOLUME); @@ -657,6 +634,77 @@ void SPUTick(void* udata, Uint8* stream, int len) } } + + + +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 USHORT MergePan(USHORT pan1, USHORT pan2) +{ + return panMergeTable[pan1 + pan2]; +} + +static void CalculateVolume(f32* voll, f32* volr, s16 toneVol, s16 tonePan, s16 velocityVol) +{ + f32 panVolumes[0x80]; + + // 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}; + + for (size_t i = 0; i < 0x80; i++) + { + panVolumes[i] = panTable[i] / 128.0f; + } + + //float volume = Channel.AdjustedVolume * Program.Volume * Tone.Volume * VelocityVolume; + float volume = (toneVol / 128.0f) * (velocityVol / 128.0f); + + //(float panLeft, float panRight) = LookupTables.getPanVolumes(Channel.Pan, Program.Pan, Tone.Pan); + // MergePan(channelPan, MergePan(programPan, tonePan)); + USHORT panIndex = MergePan(64, MergePan(64, tonePan)); + + *voll = volume * panVolumes[127 - panIndex]; + *volr = volume * panVolumes[panIndex]; +} + + + + + + void SPUTickSequences() { // TODO - convert this to be based on 44100hz instead of ms timestamp @@ -715,12 +763,17 @@ void SPUTickSequences() v->note = message->note; v->sample = sample; v->velocity = message->velocity; - v->pan = sample->pan; v->hasSeqVol = true; v->vollSeq = seq->voll; v->volrSeq = seq->volr; v->RefreshNoteStep(); - v->RefreshVolume(); + + float voll; + float volr; + u8 patchVol = seq->channels[message->channelId]->patch->_vol; + CalculateVolume(&voll, &volr, sample->volume, sample->pan, message->velocity); + v->aVoll = voll * (seq->voll / 128.0f) * (patchVol / 128.0f) * (sample->volume / 128.0f); + v->aVolr = volr * (seq->volr / 128.0f) * (patchVol / 128.0f) * (sample->volume / 128.0f); } break; @@ -831,7 +884,7 @@ static constexpr ADSRTableEntries s_adsr_table = ComputeADSRTableEntries(); ////////////////////////// // Voice -s32 Voice::Interpolate() +float Voice::Interpolate() { static constexpr std::array gauss = {{ -0x001, -0x001, -0x001, -0x001, -0x001, -0x001, -0x001, -0x001, // @@ -948,10 +1001,50 @@ s32 Voice::Interpolate() out += s32(gauss[0x000 + i]) * s32(sample->buffer[(int) (s) -0]); // new } - - return out >> 15; + return (f32) (out >> 15); } + +//f32 Voice::Interpolate() +//{ +// const s32 InterpolationWindowSize = 1 << InterpolationBitDept; +// const s32 InterpolationWeightCount = 4; +// f32 weights[InterpolationWindowSize][4]; +// for (size_t i = 0; i < InterpolationWeightCount; i++) +// { +// double pow1 = i / (double) InterpolationWindowSize; +// double pow2 = pow1 * pow1; +// double pow3 = pow2 * pow1; +// +// weights[i][0] = static_cast(0.5 * (-pow3 + 2 * pow2 - pow1)); +// weights[i][1] = static_cast(0.5 * (3 * pow3 - 5 * pow2 + 2)); +// weights[i][2] = static_cast(0.5 * (-3 * pow3 + 4 * pow2 + pow1)); +// weights[i][3] = static_cast(0.5 * (pow3 - pow2)); +// } +// +// //if (!this->HasSamples) +// //{ +// // // Copy end of the buffer to the front. +// // memcpy(this->Samples, this->Samples + 28, 3); +// // // TODO copy new samples and handle end of VAG +// // this->HasSamples = true; +// //} +// +// // the interpolation index is based on the source files sample rate +// const u32 InterpolationAnd = (1 << Voice::InterpolationBitDept) - 1; +// const u32 SampleRateBitDepth = 26; +// const u32 InterpolationShift = SampleRateBitDepth - InterpolationBitDept; +// const u8 sampleIndex = ((u32) f_SampleOffset) + ZeroExtend32(vounter.sample_index()); +// const u32 interpolationIndex = (vounter.interpolation_index() >> InterpolationShift) & InterpolationAnd; +// +// +// // this->HasSamples = !this->Counter.NextSampleBlock(this->SampleRate); +// return weights[interpolationIndex][0] * sample->buffer[sampleIndex] +// + weights[interpolationIndex][1] * sample->buffer[sampleIndex + 1] +// + weights[interpolationIndex][2] * sample->buffer[sampleIndex + 2] +// + weights[interpolationIndex][3] * sample->buffer[sampleIndex + 3]; +//} + USHORT _svm_ptable[] = { 4096, 4110, 4125, 4140, 4155, 4170, 4185, 4200, 4216, 4231, 4246, 4261, 4277, 4292, 4308, 4323, @@ -1058,77 +1151,11 @@ void Voice::RefreshNoteStep() // std::cout << ret << " " << (int) note << " " << noteStep << std::endl; } - -void Voice::RefreshVolume() -{ - // UPDATE - Using PSX vag attributes, this seems identical to duckstation - // - // TODO - this logic may not be correct - duck station produces different - // values. Example for first organ in open theme (first note with velocity == 84) - // velocity=84 sampleVol=70 seqVol=70 - // Duckstation calculates=2176 | below logic calculates=661 - // Possibly the PC sample data is bad? The left right synth does - // produce the correct 4977 value (same in PC as Duckstation...) - // Possibly just need to extract ps1 sample data... - // Interestingly, if the sampleVol is set to 127 for the first organ in theme, - // this calculates the same 2176 value as duckstation... - // - // all volume types are - // velocity - sampleVol - (patchvol) - (masterVol=127) - seqVol - // PC version is missing patch and master? I think they are always 127 - // The PSX version has an "instrument" (patch) at 109 - but may be unrelated - s32 sampleVol = sample->volume; // sample->volume - UPDATE: Setting this to 127 fixes it? - s32 uVar1 = (((velocity * 127 * 0x3fff) / 0x3f01) * 127 * sampleVol) / 0x3f01; - - s32 left = uVar1; - if (hasSeqVol) - { - left = (uVar1 * vollSeq) / 127; - uVar1 = (uVar1 * volrSeq) / 127; - } - - if (sample->pan < 64) - { - uVar1 = (uVar1 * sample->pan) / 63; - } - else - { - left = (left * (127 - sample->pan)) / 63; - } - - //if (_svm_cur.field_B_patch_pan < 64) - //{ - // uVar1 = (uVar1 * _svm_cur.field_B_patch_pan) / 63; - //} - //else - //{ - // left = (left * (127 - 64)) / 63; - //} - - if (pan < 64) - { - uVar1 = (uVar1 * pan) / 63; - } - else - { - left = (left * (127 - 64)) / 63; - } - - s32 right = uVar1; - right; - left = (left * left) / 0x3fff; - right = (right * right) / 0x3fff; - - leftReg = left; - rightReg = right; - //std::cout << left << " " << right << std::endl; -} - -std::tuple Voice::Tick() +std::tuple Voice::Tick() { if (!sample) { - return std::make_tuple(0, 0); + return std::make_tuple(0.0f, 0.0f); } // INTERPOLATION @@ -1149,7 +1176,7 @@ std::tuple Voice::Tick() if (!sample->loop) { complete = true; - return std::make_tuple(0, 0); + return std::make_tuple(0.0f, 0.0f); } // we can loop this sample @@ -1159,7 +1186,7 @@ std::tuple Voice::Tick() } s32 sampleData; - sampleData = Interpolate(); + sampleData = (s32) Interpolate(); // vounter.bits has two purposes // 1. change how samples are skipped to change the samples note @@ -1216,7 +1243,7 @@ std::tuple Voice::Tick() else if (adsrPhase == RELEASE && adsrCurrentLevel <= adsrTargetLevel) { complete = true; - return std::make_tuple(0, 0); + return std::make_tuple(0.0f, 0.0f); } // UPDATE ADSR TICK STATE @@ -1266,8 +1293,11 @@ std::tuple Voice::Tick() // 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 - s32 leftA = ApplyVolume(vol, (s16) leftReg); - s32 rightA = ApplyVolume(vol, (s16) rightReg); + f32 leftA = 0.0f; // = ApplyVolume(vol, (s16) aVoll); + f32 rightA = 0.0f; //= ApplyVolume(vol, (s16) aVolr); + // 16383.0f 32767.0f + leftA = (vol) * aVoll; + rightA = (vol) * aVolr; return std::make_tuple(leftA, rightA); } diff --git a/Source/AliveLibCommon/audio/Sequencer.hpp b/Source/AliveLibCommon/audio/Sequencer.hpp index ace42d01f..ba3ca7167 100644 --- a/Source/AliveLibCommon/audio/Sequencer.hpp +++ b/Source/AliveLibCommon/audio/Sequencer.hpp @@ -91,8 +91,10 @@ class Sample class Patch { public: - Patch(const u8 id) + Patch(const u8 id, u8 vol, u8 pan) : _id(id) + , _vol(vol) + , _pan(pan) { for (int i = 0; i < SAMPLE_SIZE_LIMIT; i++) { @@ -109,6 +111,8 @@ class Patch } const u8 _id; + const u8 _vol; + const u8 _pan; static const int SAMPLE_SIZE_LIMIT = 128; std::array samples; @@ -236,6 +240,9 @@ class VolumeEnvelope class Voice { public: + static const u32 InterpolationBitDept = 9; + static const f32 InterpolationWeights[][4]; + s32 id; bool hasLooped = false; @@ -249,8 +256,9 @@ class Voice s32 pitchMin = 0; s32 pitchMax = 127; s32 velocity = 127; - s16 pan = 64; bool inUse = false; + float aVoll; + float aVolr; bool hasSeqVol = false; s16 vollSeq; @@ -271,17 +279,13 @@ class Voice Sample* sample; - std::tuple Tick(); + std::tuple Tick(); u16 noteStep; void RefreshNoteStep(); - s32 leftReg; - s32 rightReg; - void RefreshVolume(); - private: - s32 Interpolate(); + f32 Interpolate(); s32 noteFromVgmTrans(); }; From 5ab35343d4c6f8e67a62fb60536f24410e8e861d Mon Sep 17 00:00:00 2001 From: Sean Horton <3359434+sean-horton@users.noreply.github.com> Date: Sun, 9 Jun 2024 13:10:32 -0400 Subject: [PATCH 75/82] better panning + minor refactor --- Source/AliveLibCommon/audio/MidiPlayer.cpp | 4 +- Source/AliveLibCommon/audio/Sequencer.cpp | 598 +++++++++------------ Source/AliveLibCommon/audio/Sequencer.hpp | 76 +-- 3 files changed, 307 insertions(+), 371 deletions(-) diff --git a/Source/AliveLibCommon/audio/MidiPlayer.cpp b/Source/AliveLibCommon/audio/MidiPlayer.cpp index b9b04dcdb..517fcc002 100644 --- a/Source/AliveLibCommon/audio/MidiPlayer.cpp +++ b/Source/AliveLibCommon/audio/MidiPlayer.cpp @@ -193,7 +193,9 @@ void MidiPlayer::SND_Load_VABS(SoundBlockInfo* pSoundBlockInfo, s32 reverb) ProgAtr progAtr = vabHeader->field_20_progs[pIdx]; u8 progVol = progAtr.field_1_vol; progVol = progVol == 0 ? 127 : progVol; - SPU::Patch* patch = new SPU::Patch((u8) vagAttr->field_14_prog, progVol, progAtr.field_4_pan); + 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); /////////// diff --git a/Source/AliveLibCommon/audio/Sequencer.cpp b/Source/AliveLibCommon/audio/Sequencer.cpp index 65ba568fe..2336bc917 100644 --- a/Source/AliveLibCommon/audio/Sequencer.cpp +++ b/Source/AliveLibCommon/audio/Sequencer.cpp @@ -83,7 +83,7 @@ void SPU::StopAll() void PatchAdd(Patch* patch) { - if (patch->_id >= PATCH_SIZE_LIMIT) + if (patch->id >= PATCH_SIZE_LIMIT) { throw std::runtime_error("PatchId is above PATCH_SIZE_LIMIT"); } @@ -386,33 +386,16 @@ Voice* SPUObtainVoice(s8 priority, u8 note, u8 patchId) void SPUReleaseVoice(Voice* v) { - v->channelId = 0xFF; - v->offTime = 0; - v->pitch = 0; - v->adsrPhase = NONE; - v->adsrCounter = 0; - v->adsrCurrentLevel = 0; - v->adsrTargetLevel = MAX_VOLUME; - v->sequence = NULL; - v->f_SampleOffset = 0; - v->vounter.bits = 0; - v->complete = false; - v->velocity = 127; - //v->voll = 127; - //v->volr = 127; - v->hasSeqVol = false; - v->complete = false; - v->hasLooped = false; - v->inUse = false; + v->Reset(); } void SPUPatchAdd(Patch* patch) { - if (patches[patch->_id]) + if (patches[patch->id]) { //delete patches[patch->_id]; } - patches[patch->_id] = patch; + patches[patch->id] = patch; } void SPUSeqAdd(Sequence* seq) @@ -471,8 +454,7 @@ void SPUSeqSetVolume(s32 seqId, s16 voll, s16 volr) { if (v->sequence && v->sequence->id == seqId) { - v->vollSeq = voll; - v->volrSeq = volr; + v->ConfigureVolume(127, 127); // max volume, just calling configure to calc again } } } @@ -492,12 +474,9 @@ bool SPUSeqIsDone(s32 seqId) s32 SPUOneShotPlay(s32 patchId, u8 note, s16 voll, s16 volr, s32 pitch, s32 pitchMin, s32 pitchMax) { - - // TODO - // - 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) { @@ -505,35 +484,28 @@ s32 SPUOneShotPlay(s32 patchId, u8 note, s16 voll, s16 volr, s32 pitch, s32 pitc } int ids = 0; - for (Sample* s : patch->samples) + for (Sample* sample : patch->samples) { - if (!s) + if (!sample) { continue; } - if (note > s->maxNote || note < s->minNote) + if (note > sample->maxNote || note < sample->minNote) { continue; } - Voice* v = SPUObtainVoice(s->priority, note, (u8) patchId); + Voice* v = SPUObtainVoice(sample->priority, note, (u8) patchId); if (!v) { return 0; } - v->patchId = (u8) patchId; - v->sample = s; - v->note = note; - v->velocity = (voll + volr) / 2; - v->pitch = pitch; - v->pitchMin = pitchMin; - v->pitchMax = pitchMax; - v->RefreshNoteStep(); - - v->aVoll = (voll / 128.0f) * (patch->_vol / 128.0f) * (v->sample->volume / 128.0f); - v->aVolr = (volr / 128.0f) * (patch->_vol / 128.0f) * (v->sample->volume / 128.0f); + pitchMax; // Not needed? + v->Configure(patch, sample); + v->ConfigureNote(note, pitch < pitchMin ? pitchMin : pitch); + v->ConfigureVolume((u8) voll, (u8) volr); ids |= v->id; } @@ -619,92 +591,14 @@ void SPUTick(void* udata, Uint8* stream, int len) leftSample += reverb_out_left; rightSample += reverb_out_right; - //leftSample = (float) ApplyVolume(Clamp16((s32) leftSample), MAX_VOLUME); // master vol value - //rightSample = (float) ApplyVolume(Clamp16((s32) rightSample), MAX_VOLUME); - // make value usable by SDL - // It might make sense to increase the mix volume above MIX_MAXVOUME. - // I think psx sounds like it runs a little hot, compressing audio a bit - leftSample = leftSample / (32767.0f * 2); // 16383.0f 32767.0f - rightSample = rightSample / (32767.0f * 2); - leftSample = leftSample > 1 ? 1 : leftSample; - rightSample = rightSample > 1 ? 1 : rightSample; + leftSample = leftSample / 32767.0f; + rightSample = rightSample / 32767.0f; SDL_MixAudioFormat((Uint8*) (AudioStream + i), (const Uint8*) &leftSample, AUDIO_F32, sizeof(float), SDL_MIX_MAXVOLUME); SDL_MixAudioFormat((Uint8*) (AudioStream + i + 1), (const Uint8*) &rightSample, AUDIO_F32, sizeof(float), SDL_MIX_MAXVOLUME); } } - - - -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 USHORT MergePan(USHORT pan1, USHORT pan2) -{ - return panMergeTable[pan1 + pan2]; -} - -static void CalculateVolume(f32* voll, f32* volr, s16 toneVol, s16 tonePan, s16 velocityVol) -{ - f32 panVolumes[0x80]; - - // 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}; - - for (size_t i = 0; i < 0x80; i++) - { - panVolumes[i] = panTable[i] / 128.0f; - } - - //float volume = Channel.AdjustedVolume * Program.Volume * Tone.Volume * VelocityVolume; - float volume = (toneVol / 128.0f) * (velocityVol / 128.0f); - - //(float panLeft, float panRight) = LookupTables.getPanVolumes(Channel.Pan, Program.Pan, Tone.Pan); - // MergePan(channelPan, MergePan(programPan, tonePan)); - USHORT panIndex = MergePan(64, MergePan(64, tonePan)); - - *voll = volume * panVolumes[127 - panIndex]; - *volr = volume * panVolumes[panIndex]; -} - - - - - - void SPUTickSequences() { // TODO - convert this to be based on 44100hz instead of ms timestamp @@ -737,7 +631,9 @@ void SPUTickSequences() break; } - for (Sample* sample : seq->channels[message->channelId]->patch->samples) + Channel* channel = seq->channels[message->channelId]; + Patch* patch = channel->patch; + for (Sample* sample : patch->samples) { if (!sample) { @@ -755,25 +651,9 @@ void SPUTickSequences() continue; } - v->sequence = seq; - v->patchId = seq->channels[message->channelId]->patch->_id; - v->channelId = message->channelId; - v->pitchMin = 0; - v->velocity = message->velocity; - v->note = message->note; - v->sample = sample; - v->velocity = message->velocity; - v->hasSeqVol = true; - v->vollSeq = seq->voll; - v->volrSeq = seq->volr; - v->RefreshNoteStep(); - - float voll; - float volr; - u8 patchVol = seq->channels[message->channelId]->patch->_vol; - CalculateVolume(&voll, &volr, sample->volume, sample->pan, message->velocity); - v->aVoll = voll * (seq->voll / 128.0f) * (patchVol / 128.0f) * (sample->volume / 128.0f); - v->aVolr = volr * (seq->volr / 128.0f) * (patchVol / 128.0f) * (sample->volume / 128.0f); + 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; @@ -782,9 +662,7 @@ void SPUTickSequences() { for (Voice* v : voices) { - if (v->inUse && v->sequence == seq - && v->channelId == message->channelId - && v->note == message->note) + if (v->inUse && v->IsMatch(seq, message->channelId, message->note)) { v->offTime = now; } @@ -805,11 +683,9 @@ void SPUTickSequences() { for (Voice* v : voices) { - if (v->inUse && v->sequence == seq - && v->channelId == message->channelId) + if (v->inUse && v->IsMatch(seq, message->channelId)) { - v->pitch = message->bend; - v->RefreshNoteStep(); + v->ConfigureNote(v->GetNote(), message->bend); } } break; @@ -884,166 +760,67 @@ static constexpr ADSRTableEntries s_adsr_table = ComputeADSRTableEntries(); ////////////////////////// // Voice -float Voice::Interpolate() -{ - 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 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}; - // the interpolation index is based on the source files sample rate - const u8 i = (u8) vounter.interpolation_index(); - const u32 s = ((u32) f_SampleOffset) + ZeroExtend32(vounter.sample_index()); - // TODO - remove these if statements - // interpolate on the 4 most recent samples from current position - // The below `if` statements are in case we loop - // we gauss the end of the sample with the beginning. - // Probably a better way to do it, but w/e for now... - s32 out = 0; - if (s == 0) - { - if (hasLooped) - { - out += s32(gauss[0x0FF - i]) * s32(sample->buffer[sample->len - 3]); // oldest - out += s32(gauss[0x1FF - i]) * s32(sample->buffer[sample->len - 2]); // older - out += s32(gauss[0x100 + i]) * s32(sample->buffer[sample->len - 1]); // old - } - out += s32(gauss[0x000 + i]) * s32(sample->buffer[s - 0]); // new - } - else if (s == 1) - { - if (hasLooped) - { - out += s32(gauss[0x0FF - i]) * s32(sample->buffer[sample->len - 2]); // oldest - out += s32(gauss[0x1FF - i]) * s32(sample->buffer[sample->len - 1]); // older - } - out += s32(gauss[0x100 + i]) * s32(sample->buffer[s - 1]); // old - out += s32(gauss[0x000 + i]) * s32(sample->buffer[s - 0]); // new - } - else if (s == 2) - { - if (hasLooped) - { - out += s32(gauss[0x0FF - i]) * s32(sample->buffer[sample->len - 1]); // oldest - } - out += s32(gauss[0x1FF - i]) * s32(sample->buffer[s - 2]); // older - out += s32(gauss[0x100 + i]) * s32(sample->buffer[s - 1]); // old - out += s32(gauss[0x000 + i]) * s32(sample->buffer[s - 0]); // new - } - else - { - out += s32(gauss[0x0FF - i]) * s32(sample->buffer[(int) (s) -3]); // oldest - out += s32(gauss[0x1FF - i]) * s32(sample->buffer[(int) (s) -2]); // older - out += s32(gauss[0x100 + i]) * s32(sample->buffer[(int) (s) -1]); // old - out += s32(gauss[0x000 + i]) * s32(sample->buffer[(int) (s) -0]); // new - } +static u16 MergePan(u16 pan1, u16 pan2) +{ + return panMergeTable[pan1 + pan2]; +} - return (f32) (out >> 15); +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; +} -//f32 Voice::Interpolate() -//{ -// const s32 InterpolationWindowSize = 1 << InterpolationBitDept; -// const s32 InterpolationWeightCount = 4; -// f32 weights[InterpolationWindowSize][4]; -// for (size_t i = 0; i < InterpolationWeightCount; i++) -// { -// double pow1 = i / (double) InterpolationWindowSize; -// double pow2 = pow1 * pow1; -// double pow3 = pow2 * pow1; -// -// weights[i][0] = static_cast(0.5 * (-pow3 + 2 * pow2 - pow1)); -// weights[i][1] = static_cast(0.5 * (3 * pow3 - 5 * pow2 + 2)); -// weights[i][2] = static_cast(0.5 * (-3 * pow3 + 4 * pow2 + pow1)); -// weights[i][3] = static_cast(0.5 * (pow3 - pow2)); -// } -// -// //if (!this->HasSamples) -// //{ -// // // Copy end of the buffer to the front. -// // memcpy(this->Samples, this->Samples + 28, 3); -// // // TODO copy new samples and handle end of VAG -// // this->HasSamples = true; -// //} -// -// // the interpolation index is based on the source files sample rate -// const u32 InterpolationAnd = (1 << Voice::InterpolationBitDept) - 1; -// const u32 SampleRateBitDepth = 26; -// const u32 InterpolationShift = SampleRateBitDepth - InterpolationBitDept; -// const u8 sampleIndex = ((u32) f_SampleOffset) + ZeroExtend32(vounter.sample_index()); -// const u32 interpolationIndex = (vounter.interpolation_index() >> InterpolationShift) & InterpolationAnd; -// -// -// // this->HasSamples = !this->Counter.NextSampleBlock(this->SampleRate); -// return weights[interpolationIndex][0] * sample->buffer[sampleIndex] -// + weights[interpolationIndex][1] * sample->buffer[sampleIndex + 1] -// + weights[interpolationIndex][2] * sample->buffer[sampleIndex + 2] -// + weights[interpolationIndex][3] * sample->buffer[sampleIndex + 3]; -//} +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.bits = 0; + complete = false; + complete = false; + hasLooped = false; + inUse = false; +} USHORT _svm_ptable[] = { 4096, 4110, 4125, 4140, 4155, 4170, 4185, 4200, @@ -1072,8 +849,12 @@ USHORT _svm_ptable[] = { 7958, 7987, 8016, 8045, 8074, 8103, 8133, 8162}; // 192 total (0-191) // 8192}; This was originally here. Do we need? -s32 Voice::noteFromVgmTrans() +void Voice::ConfigureNote(u8 _note, u32 _pitch) { + this->note = _note; + this->pitch = _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? @@ -1084,10 +865,10 @@ s32 Voice::noteFromVgmTrans() // 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) - unsigned int pitchA; - SHORT calc, type; - signed int add, sfine; //, ret; - signed int fine = pitch < pitchMin ? pitchMin : pitch; + u32 pitchA; + s16 calc, type; + s32 add, sfine; + s32 fine = pitch; sfine = fine + ((s32) sample->rootNotePitchShift); if (sfine < 0) { @@ -1102,11 +883,11 @@ s32 Voice::noteFromVgmTrans() sfine -= 16; } - calc = (SHORT) (add + (note - (((s32) sample->rootNote) - 60))); //((center + 60) - note) + add; + calc = (s16) (add + (note - (((s32) sample->rootNote) - 60))); //((center + 60) - note) + add; int pos = (16 * (calc % 12) + sfine); // START: ADDED - // Security Orbs, Pause menu, and well leaves had the + // 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. @@ -1138,12 +919,6 @@ s32 Voice::noteFromVgmTrans() { ret = pitchA >> -type; } - return ret; -} - -void Voice::RefreshNoteStep() -{ - s32 ret = noteFromVgmTrans(); // freq = (ret * 44100) / 4096 // step seems to be calculated for 8000hz samples hence the division @@ -1151,15 +926,148 @@ void Voice::RefreshNoteStep() // std::cout << ret << " " << (int) note << " " << noteStep << std::endl; } -std::tuple Voice::Tick() +u8 Voice::GetNote() { - if (!sample) + return note; +} + +void Voice::ConfigureVolume(u8 volLeft, u8 volRight) +{ + f32 panVolumes[0x80]; + + // 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}; + + for (size_t i = 0; i < 0x80; i++) { - return std::make_tuple(0.0f, 0.0f); + panVolumes[i] = panTable[i] / 128.0f; } - // INTERPOLATION + 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() +{ + 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 // + }}; // vounter (gauss interpolation table) runs at 28 byte blocks. // move the sample offset forward every 28 bytes @@ -1176,7 +1084,7 @@ std::tuple Voice::Tick() if (!sample->loop) { complete = true; - return std::make_tuple(0.0f, 0.0f); + return 0; } // we can loop this sample @@ -1185,15 +1093,23 @@ std::tuple Voice::Tick() f_SampleOffset = 0; } - s32 sampleData; - sampleData = (s32) Interpolate(); + // the interpolation index is based on the source files sample rate + const u8 i = (u8) vounter.interpolation_index(); + const u32 s = ((u32) f_SampleOffset) + ZeroExtend32(vounter.sample_index()); + + // interpolate on the 4 most recent samples from current position + s32 out = 0; + out += s32(gauss[0x0FF - i]) * s32(sample->buffer[(int) (s)]); + out += s32(gauss[0x1FF - i]) * s32(sample->buffer[((int) (s) + 1) % sample->len]); + out += s32(gauss[0x100 + i]) * s32(sample->buffer[((int) (s) + 2) % sample->len]); + out += s32(gauss[0x000 + i]) * s32(sample->buffer[((int) (s) + 3) % sample->len]); - // vounter.bits has two purposes + // vounter.bits has two purposes // 1. change how samples are skipped to change the samples note // 2. maintain gauss table positioning for correct interpolation - // RefreshNoteStep(); - called elsewhere to save CPU cycles + // noteStep is called in ConfigureNote() to save CPU cycles as this is called at 44100hz u16 step = noteStep; - // if (v->isPitchModulated) + // if (v->isPitchModulated) // { // const s32 factor = std::clamp(sampleData, -0x8000, 0x7FFF) + 0x8000; // step = Truncate16(static_cast((SignExtend32(step) * factor) >> 15)); @@ -1201,6 +1117,11 @@ std::tuple Voice::Tick() step = std::min(step, 0x3FFF); vounter.bits += step; + return (f32) (out >> 15); +} + +s16 Voice::TickAdsr() +{ // UPDATE ADSR STATE - done at 441000hz if (adsrPhase == NONE) { @@ -1243,7 +1164,7 @@ std::tuple Voice::Tick() else if (adsrPhase == RELEASE && adsrCurrentLevel <= adsrTargetLevel) { complete = true; - return std::make_tuple(0.0f, 0.0f); + return 0; } // UPDATE ADSR TICK STATE @@ -1285,20 +1206,27 @@ std::tuple Voice::Tick() std::clamp(static_cast(adsrCurrentLevel) + this_step, MIN_VOLUME, MAX_VOLUME)); } - // Set the volume of the sample - s32 vol = sampleData; - vol = ApplyVolume(vol, adsrCurrentLevel); + 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 = 0.0f; // = ApplyVolume(vol, (s16) aVoll); - f32 rightA = 0.0f; //= ApplyVolume(vol, (s16) aVolr); - // 16383.0f 32767.0f - leftA = (vol) * aVoll; - rightA = (vol) * aVolr; - + f32 leftA = vol * volumeLeftModifier; + f32 rightA = vol * volumeRightModifier; return std::make_tuple(leftA, rightA); } diff --git a/Source/AliveLibCommon/audio/Sequencer.hpp b/Source/AliveLibCommon/audio/Sequencer.hpp index ba3ca7167..edd25c8ab 100644 --- a/Source/AliveLibCommon/audio/Sequencer.hpp +++ b/Source/AliveLibCommon/audio/Sequencer.hpp @@ -59,9 +59,9 @@ class Sample } - s8 priority; - s16 volume; - s16 pan; + u8 priority; + u8 volume; + u8 pan; // reverb style - 0 is none s8 reverb; @@ -91,10 +91,10 @@ class Sample class Patch { public: - Patch(const u8 id, u8 vol, u8 pan) - : _id(id) - , _vol(vol) - , _pan(pan) + Patch(const u8 _id, u8 _vol, u8 _pan) + : id(_id) + , vol(_vol) + , pan(_pan) { for (int i = 0; i < SAMPLE_SIZE_LIMIT; i++) { @@ -110,9 +110,9 @@ class Patch } } - const u8 _id; - const u8 _vol; - const u8 _pan; + const u8 id; + const u8 vol; + const u8 pan; static const int SAMPLE_SIZE_LIMIT = 128; std::array samples; @@ -149,6 +149,8 @@ struct Channel { // channels may have volums and pans too, but not implemented Patch* patch; + u8 vol = 127; + u8 pan = 64; }; /* @@ -245,30 +247,33 @@ class Voice s32 id; - bool hasLooped = false; + Sequence* sequence = nullptr; + Channel* channel = nullptr; + Patch* patch = nullptr; + Sample* sample = nullptr; + MIDIMessage* message = nullptr; - u32 f_SampleOffset = 0; - Sequence* sequence = NULL; - u8 patchId; - u8 channelId; - s32 note; - s32 pitch; - s32 pitchMin = 0; - s32 pitchMax = 127; - s32 velocity = 127; bool inUse = false; - float aVoll; - float aVolr; + bool complete = false; + u64 offTime = 0; // when the note was released - bool hasSeqVol = false; - s16 vollSeq; - s16 volrSeq; + 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 complete = false; - u64 offTime = 0; // when the note was released + bool IsMatch(Sequence* seq, u8 channelId); + bool IsMatch(Sequence* seq, u8 channelId, u8 note); - VoiceCounter vounter; +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; @@ -277,16 +282,17 @@ class Voice s16 adsrCurrentLevel = 0; s16 adsrTargetLevel = MAX_VOLUME; // attack we want to reach max - Sample* sample; - - std::tuple Tick(); - + // Interpolation/pitch Calculations + VoiceCounter vounter; + u8 note; + s32 pitch; u16 noteStep; - void RefreshNoteStep(); + bool hasLooped = false; + u32 f_SampleOffset = 0; -private: + std::tuple CalculateVolume(); f32 Interpolate(); - s32 noteFromVgmTrans(); + s16 TickAdsr(); }; From 211847da8b7e018960ff0c7abaf790297d55ad2c Mon Sep 17 00:00:00 2001 From: Sean Horton <3359434+sean-horton@users.noreply.github.com> Date: Sun, 9 Jun 2024 13:51:34 -0400 Subject: [PATCH 76/82] Code review points --- Source/AliveLibAE/Sound/Midi.cpp | 21 +++++++----- Source/AliveLibCommon/CMakeLists.txt | 5 +-- Source/AliveLibCommon/audio/MidiPlayer.cpp | 2 +- Source/AliveLibCommon/audio/MidiPlayer.hpp | 39 +++++----------------- Source/AliveLibCommon/audio/Sequencer.cpp | 12 +++---- 5 files changed, 31 insertions(+), 48 deletions(-) diff --git a/Source/AliveLibAE/Sound/Midi.cpp b/Source/AliveLibAE/Sound/Midi.cpp index bed50f190..dab780874 100644 --- a/Source/AliveLibAE/Sound/Midi.cpp +++ b/Source/AliveLibAE/Sound/Midi.cpp @@ -938,8 +938,6 @@ class AESoundSampleParser : public psx::SoundSampleParser 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()) @@ -951,9 +949,9 @@ class AESoundSampleParser : public psx::SoundSampleParser 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)) + if (!soundDatFile.read(soundData, fileSize)) { - // Tada! + throw std::invalid_argument("Could not read sounds.dat"); } int pos = 0; @@ -986,6 +984,16 @@ class AESoundSampleParser : public psx::SoundSampleParser 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()); @@ -1009,7 +1017,6 @@ EXPORT void CC SND_Shutdown_4CA280() EXPORT void CC SND_Stop_Channels_Mask_4CA810(u32 bitMask) { - bitMask; player->SND_Stop_Channels_Mask(bitMask); } @@ -1020,8 +1027,6 @@ EXPORT void SND_Reset_4C9FB0() EXPORT void CC SND_Load_VABS_4CA350(SoundBlockInfo* pSoundBlockInfo, s32 reverb) { - pSoundBlockInfo; - reverb; player->SND_Load_VABS(reinterpret_cast(pSoundBlockInfo), reverb); } @@ -1083,4 +1088,4 @@ EXPORT s32 CC SFX_SfxDefinition_Play_4CA420(const SfxDefinition* sfxDef, s16 vol return player->SFX_SfxDefinition_Play(reinterpret_cast(const_cast(sfxDef)), volume, pitch_min, pitch_max); } -#endif \ No newline at end of file +#endif diff --git a/Source/AliveLibCommon/CMakeLists.txt b/Source/AliveLibCommon/CMakeLists.txt index 04b0996ac..12e48ae11 100644 --- a/Source/AliveLibCommon/CMakeLists.txt +++ b/Source/AliveLibCommon/CMakeLists.txt @@ -41,9 +41,10 @@ SET(AliveLibSrcCommon audio/Stream.cpp audio/MidiPlayer.hpp audio/MidiPlayer.cpp - audio/oddio.h) + audio/oddio.h +) -add_library(AliveLibCommon ${AliveLibSrcCommon}) +add_library(AliveLibCommon ${AliveLibSrcCommon} ) target_include_directories(AliveLibCommon PUBLIC $ diff --git a/Source/AliveLibCommon/audio/MidiPlayer.cpp b/Source/AliveLibCommon/audio/MidiPlayer.cpp index 517fcc002..83e36b9d5 100644 --- a/Source/AliveLibCommon/audio/MidiPlayer.cpp +++ b/Source/AliveLibCommon/audio/MidiPlayer.cpp @@ -662,4 +662,4 @@ void parseMidiStream(SPU::Sequence* seq, std::vector seqData, s32 trackId } } -} // namespace psx \ No newline at end of file +} // namespace psx diff --git a/Source/AliveLibCommon/audio/MidiPlayer.hpp b/Source/AliveLibCommon/audio/MidiPlayer.hpp index a8008deec..4618e3258 100644 --- a/Source/AliveLibCommon/audio/MidiPlayer.hpp +++ b/Source/AliveLibCommon/audio/MidiPlayer.hpp @@ -206,23 +206,13 @@ namespace psx { { public: /* Read a file within the game, example a .VB file */ - virtual ResourceData* readFile(char_type* name) - { - name; - throw std::invalid_argument("readFile is not overridden"); - } + 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) - { - fileName; - sequenceName; - throw std::invalid_argument("readSeq is not overridden"); - } + 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() - { - throw std::invalid_argument("sequenceCount is not overridden"); - } + virtual s32 sequenceCount() = 0; }; /* @@ -235,20 +225,9 @@ namespace psx { class SoundSampleParser { public: - virtual std::vector parseSamples(VabHeader* vabHeader, u8* ppVabBody) - { - vabHeader; - ppVabBody; - std::vector empty; - throw std::invalid_argument("parseSamples is not overridden"); - } - virtual void applyFix(char_type* headerName, s32 vag, s32 vagOffset, SPU::Sample* sample) - { - headerName; - vag; - vagOffset; - sample; - } + virtual std::vector parseSamples(VabHeader* vabHeader, u8* ppVabBody) = 0; + + virtual void applyFix(char_type* headerName, s32 vag, s32 vagOffset, SPU::Sample* sample) = 0; }; /* @@ -294,4 +273,4 @@ namespace psx { }; -} // namespace psx \ No newline at end of file +} // namespace psx diff --git a/Source/AliveLibCommon/audio/Sequencer.cpp b/Source/AliveLibCommon/audio/Sequencer.cpp index 2336bc917..3a43a7c70 100644 --- a/Source/AliveLibCommon/audio/Sequencer.cpp +++ b/Source/AliveLibCommon/audio/Sequencer.cpp @@ -63,7 +63,9 @@ void SPU::Init() void SPU::DeInit() { mutex.lock(); - // TODO - does it matter? + // 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(); } @@ -106,7 +108,6 @@ bool SeqPlay(s32 seqId, s32 repeats, bool stopDuplicateSeq) { SPUSeqStop(seqId); } - // SPUSeqSetVolume(seqId, 100, 100); bool res = SPUSeqPlay(seqId, repeats); mutex.unlock(); return res; @@ -180,8 +181,8 @@ void Sequence::Reset() play = false; actionPos = 0; trackStartTime = 0; - //voll = 127; - //volr = 127; + voll = 127; + volr = 127; repeats = 0; repeatLimit = 1; } @@ -343,9 +344,6 @@ Voice* SPUObtainVoice(s8 priority, u8 note, u8 patchId) note; patchId; - // TODO - logic needs to be revisited, I think it's wrong - // Go shoot slig in rupture farms non-stop and music stops playing - // // 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 From 35b59e69d9674ef71cb8f4afc222abb192bcea13 Mon Sep 17 00:00:00 2001 From: Sean Horton <3359434+sean-horton@users.noreply.github.com> Date: Sun, 9 Jun 2024 20:34:20 -0400 Subject: [PATCH 77/82] Fix memory leak causing crash --- Source/AliveLibAE/Sound/Midi.cpp | 6 +++++- Source/AliveLibAO/Midi.cpp | 5 ++++- Source/AliveLibCommon/audio/MidiPlayer.cpp | 15 +++++++++++++-- Source/AliveLibCommon/audio/MidiPlayer.hpp | 10 ++++++++++ 4 files changed, 32 insertions(+), 4 deletions(-) diff --git a/Source/AliveLibAE/Sound/Midi.cpp b/Source/AliveLibAE/Sound/Midi.cpp index dab780874..8c7d7d27f 100644 --- a/Source/AliveLibAE/Sound/Midi.cpp +++ b/Source/AliveLibAE/Sound/Midi.cpp @@ -914,8 +914,11 @@ class AEResourceProvider : public psx::ResourceProvider } u32 size = ResourceManager::Get_Header_49C410(ppSeq)->field_0_size; - resource->data = *ppSeq; + u8* data = new u8[size]; + memcpy(data, *ppSeq, size); + resource->data = data; resource->size = size; + ResourceManager::FreeResource_49C330(ppSeq); return resource; } @@ -981,6 +984,7 @@ class AESoundSampleParser : public psx::SoundSampleParser } soundDatFile.close(); + delete soundData; return samples; } diff --git a/Source/AliveLibAO/Midi.cpp b/Source/AliveLibAO/Midi.cpp index a9f1a8907..6ebcec5f8 100644 --- a/Source/AliveLibAO/Midi.cpp +++ b/Source/AliveLibAO/Midi.cpp @@ -1191,9 +1191,12 @@ class AOResourceProvider : public psx::ResourceProvider } u32 size = ResourceManager::Get_Header_455620(ppSeq)->field_0_size; - resource->data = *ppSeq; + u8* data = new u8[size]; + memcpy(data, *ppSeq, size); + resource->data = data; resource->size = size; resource->optionalHash = hash; + ResourceManager::FreeResource_455550(ppSeq); return resource; } diff --git a/Source/AliveLibCommon/audio/MidiPlayer.cpp b/Source/AliveLibCommon/audio/MidiPlayer.cpp index 83e36b9d5..c9fed6749 100644 --- a/Source/AliveLibCommon/audio/MidiPlayer.cpp +++ b/Source/AliveLibCommon/audio/MidiPlayer.cpp @@ -162,6 +162,11 @@ 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) { @@ -170,7 +175,6 @@ void MidiPlayer::SND_Load_VABS(SoundBlockInfo* pSoundBlockInfo, s32 reverb) break; } - // Read header ResourceData* vabHeaderResource = mResourceProvider->readFile(pSoundBlockInfo->header_name); VabHeader* vabHeader = reinterpret_cast(vabHeaderResource->data); @@ -233,7 +237,12 @@ void MidiPlayer::SND_Load_VABS(SoundBlockInfo* pSoundBlockInfo, s32 reverb) } } - delete[] ppVabBody; + + for (auto s : samples) + { + mActiveSamples.push_back(s); + } + delete vabBodyResource; pSoundBlockInfo++; } } @@ -267,6 +276,8 @@ void MidiPlayer::SND_Load_Seqs(OpenSeqHandle* pSeqTable, const char_type* bsqFil parseMidiStream(sequence, vec, i); SPU::SeqAdd(sequence); } + + delete resource; } } diff --git a/Source/AliveLibCommon/audio/MidiPlayer.hpp b/Source/AliveLibCommon/audio/MidiPlayer.hpp index 4618e3258..d7440ab3d 100644 --- a/Source/AliveLibCommon/audio/MidiPlayer.hpp +++ b/Source/AliveLibCommon/audio/MidiPlayer.hpp @@ -13,6 +13,11 @@ namespace psx { class Sample { public: + ~Sample() + { + delete m_SampleBuffer; + } + s16* m_SampleBuffer; u32 i_SampleSize; u32 sampleRate; @@ -194,6 +199,10 @@ namespace psx { */ struct ResourceData final { + ~ResourceData() + { + delete data; + } u8* data; u32 size; s32 optionalHash; @@ -267,6 +276,7 @@ namespace psx { private: ResourceProvider* mResourceProvider; SoundSampleParser* mSoundSampleParser; + std::vector mActiveSamples; void sanitizePitch(s32* src, s16 defaultPitch); void sanitizeVolume(s32* src, s32 low, s32 high); From 2ce139142301585e17db523fc146d5b4f7c2188f Mon Sep 17 00:00:00 2001 From: Sean Horton <3359434+sean-horton@users.noreply.github.com> Date: Tue, 11 Jun 2024 18:05:26 -0400 Subject: [PATCH 78/82] Fix memcpy of audio block --- Source/AliveLibCommon/audio/Sequencer.cpp | 146 +++++++++++++++++----- Source/AliveLibCommon/audio/Sequencer.hpp | 69 +++++++++- 2 files changed, 177 insertions(+), 38 deletions(-) diff --git a/Source/AliveLibCommon/audio/Sequencer.cpp b/Source/AliveLibCommon/audio/Sequencer.cpp index 3a43a7c70..48aa9d14d 100644 --- a/Source/AliveLibCommon/audio/Sequencer.cpp +++ b/Source/AliveLibCommon/audio/Sequencer.cpp @@ -592,8 +592,8 @@ void SPUTick(void* udata, Uint8* stream, int len) // make value usable by SDL leftSample = leftSample / 32767.0f; rightSample = rightSample / 32767.0f; - SDL_MixAudioFormat((Uint8*) (AudioStream + i), (const Uint8*) &leftSample, AUDIO_F32, sizeof(float), SDL_MIX_MAXVOLUME); - SDL_MixAudioFormat((Uint8*) (AudioStream + i + 1), (const Uint8*) &rightSample, AUDIO_F32, sizeof(float), SDL_MIX_MAXVOLUME); + SDL_MixAudioFormat((Uint8*) (AudioStream + i), (const Uint8*) &leftSample, AUDIO_F32, sizeof(float), 100); + SDL_MixAudioFormat((Uint8*) (AudioStream + i + 1), (const Uint8*) &rightSample, AUDIO_F32, sizeof(float), 100); } } @@ -756,6 +756,44 @@ static constexpr ADSRTableEntries ComputeADSRTableEntries() static constexpr ADSRTableEntries s_adsr_table = ComputeADSRTableEntries(); +////////////////////////// +// VoiceCounter + +// Interpolation window is centered and first 3 samples are from the previous block, +// so the first block has to start at index 2. +//const u32 VoiceCounter::StartOffset = 2 << Voice::InterpolationBitDept; +//const u32 VoiceCounter::InterpolationShift = Voice::SampleRateBitDepth - Voice::InterpolationBitDept; +//const u32 VoiceCounter::InterpolationAnd = (1 << Voice::InterpolationBitDept) - 1; +// +//u32 VoiceCounter::SampleIndex() +//{ +// return this->Counter >> Voice::SampleRateBitDepth; +//} +// +//u32 VoiceCounter::InterpolationIndex() +//{ +// return (this->Counter >> VoiceCounter::InterpolationShift) & VoiceCounter::InterpolationAnd; +//} +// +//bool VoiceCounter::NextSampleBlock(u32 sampleRate) +//{ +// this->Counter += sampleRate; +// +// if (this->SampleIndex() >= 28) +// { +// this->Counter -= (28 << Voice::SampleRateBitDepth); +// return true; +// } +// +// return false; +//} +// +//void VoiceCounter::Reset() +//{ +// this->Counter = VoiceCounter::StartOffset; +//} + + ////////////////////////// // Voice USHORT panMergeTable[] = { @@ -794,6 +832,13 @@ 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 + currSamples[0] = 0; + currSamples[1] = 0; + currSamples[2] = 0; + memcpy(&currSamples[3], sample->buffer, NUM_SAMPLES_PER_ADPCM_BLOCK * sizeof(s16)); } void Voice::Reset() @@ -813,10 +858,9 @@ void Voice::Reset() adsrCurrentLevel = 0; adsrTargetLevel = MAX_VOLUME; f_SampleOffset = 0; - vounter.bits = 0; - complete = false; + vounter.Reset(); + HasSamples = true; complete = false; - hasLooped = false; inUse = false; } @@ -847,6 +891,30 @@ USHORT _svm_ptable[] = { 7958, 7987, 8016, 8045, 8074, 8103, 8133, 8162}; // 192 total (0-191) // 8192}; This was originally here. Do we need? +//void Voice::ConfigureNote(u8 _note, u32 _pitch) +//{ +// this->note = _note; +// this->pitch = _pitch; +// +// f32 fix = sample->SampleRate / 8000.0f; +// fix; // TODO - this needs to be used somewhere. PC version has some non 8000hz samples +// +// s32 fineOffset = (note - sample->rootNote) * 256 + sample->rootNotePitchShift + (pitch - 64) * 4; +// +// u32 octaveOffset; +// u32 sampleRateOffset; +// if (fineOffset >= 0) { +// octaveOffset = fineOffset / 3072; +// sampleRateOffset = fineOffset - octaveOffset * 3072; +// noteStep = SampleRates[sampleRateOffset] << octaveOffset; +// return; +// } +// +// octaveOffset = (fineOffset + 1) / -3072 + 1; +// sampleRateOffset = fineOffset + octaveOffset * 3072; +// noteStep = SampleRates[sampleRateOffset] >> octaveOffset; +//} + void Voice::ConfigureNote(u8 _note, u32 _pitch) { this->note = _note; @@ -1067,55 +1135,65 @@ float Voice::Interpolate() 0x5997, 0x599E, 0x59A4, 0x59A9, 0x59AD, 0x59B0, 0x59B2, 0x59B3 // }}; - // vounter (gauss interpolation table) runs at 28 byte blocks. + // vounter (gauss interpolation table) runs at 28 byte blocks (NUM_SAMPLES_PER_ADPCM_BLOCK). // move the sample offset forward every 28 bytes - if (vounter.sample_index() >= NUM_SAMPLES_PER_ADPCM_BLOCK) + if (!this->HasSamples) { - // vounter holds sample position shifted << 12 - vounter.bits -= (NUM_SAMPLES_PER_ADPCM_BLOCK << 12); f_SampleOffset += NUM_SAMPLES_PER_ADPCM_BLOCK; - } - // Are we at the end of the sample? - if (f_SampleOffset + vounter.sample_index() >= sample->len) - { - if (!sample->loop) + // 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; } - // we can loop this sample - hasLooped = true; - vounter.bits = 0; - f_SampleOffset = 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], 3 * 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[3], &sample->buffer[f_SampleOffset], (NUM_SAMPLES_PER_ADPCM_BLOCK - diff) * sizeof(s16)); + memcpy(&currSamples[3 + NUM_SAMPLES_PER_ADPCM_BLOCK - diff], &sample->buffer[0], (diff) * sizeof(s16)); + } + else + { + memcpy(&currSamples[3], &sample->buffer[f_SampleOffset], NUM_SAMPLES_PER_ADPCM_BLOCK * sizeof(s16)); + } } // the interpolation index is based on the source files sample rate - const u8 i = (u8) vounter.interpolation_index(); - const u32 s = ((u32) f_SampleOffset) + ZeroExtend32(vounter.sample_index()); + const u32 i = vounter.InterpolationIndex(); + const u32 s = vounter.SampleIndex(); // interpolate on the 4 most recent samples from current position s32 out = 0; - out += s32(gauss[0x0FF - i]) * s32(sample->buffer[(int) (s)]); - out += s32(gauss[0x1FF - i]) * s32(sample->buffer[((int) (s) + 1) % sample->len]); - out += s32(gauss[0x100 + i]) * s32(sample->buffer[((int) (s) + 2) % sample->len]); - out += s32(gauss[0x000 + i]) * s32(sample->buffer[((int) (s) + 3) % sample->len]); + out += s32(gauss[0x0FF - i]) * s32(currSamples[(int) (s + 3 - 3)]); + out += s32(gauss[0x1FF - i]) * s32(currSamples[(int) (s + 3 - 2)]); + out += s32(gauss[0x100 + i]) * s32(currSamples[(int) (s + 3 - 1)]); + out += s32(gauss[0x000 + i]) * s32(currSamples[(int) (s + 3 - 0)]); + out = out >> 15; + + //f32 out = interpWeights[0][i] * currSamples[s] + // + interpWeights[1][i] * currSamples[s + 1] + // + interpWeights[1][InterpolationWindowLen - i] * currSamples[s + 2] + // + interpWeights[0][InterpolationWindowLen - i] * currSamples[s + 3]; // vounter.bits has two purposes // 1. change how samples are skipped to change the samples note // 2. maintain gauss table positioning for correct interpolation // noteStep is called in ConfigureNote() to save CPU cycles as this is called at 44100hz - u16 step = noteStep; - // if (v->isPitchModulated) - // { - // const s32 factor = std::clamp(sampleData, -0x8000, 0x7FFF) + 0x8000; - // step = Truncate16(static_cast((SignExtend32(step) * factor) >> 15)); - // } - step = std::min(step, 0x3FFF); - vounter.bits += step; - - return (f32) (out >> 15); + this->HasSamples = !this->vounter.NextSampleBlock(noteStep); + return (f32) (out); } s16 Voice::TickAdsr() diff --git a/Source/AliveLibCommon/audio/Sequencer.hpp b/Source/AliveLibCommon/audio/Sequencer.hpp index edd25c8ab..7eddc34e5 100644 --- a/Source/AliveLibCommon/audio/Sequencer.hpp +++ b/Source/AliveLibCommon/audio/Sequencer.hpp @@ -211,22 +211,56 @@ enum ADSRPhase RELEASE }; +class VoiceCounter2 +{ +public: + u32 SampleIndex(); + u32 InterpolationIndex(); + bool NextSampleBlock(u32 sampleRate); + void Reset(); + +private: + static const u32 StartOffset; + static const u32 InterpolationShift; + static const u32 InterpolationAnd; + + u32 Counter = 0; +}; + union VoiceCounter { // promoted to u32 because of overflow u32 bits = 0; //BitField interpolation_index; - u32 interpolation_index() + u32 InterpolationIndex() { return (bits >> 4) & mask(8); } //BitField sample_index; - u32 sample_index() + u32 SampleIndex() { return (bits >> 12) & mask(5); } + + bool NextSampleBlock(u32 step) + { + step = std::min(step, 0x3FFF); + bits += step; + if (this->SampleIndex() >= 28) + { + bits -= (NUM_SAMPLES_PER_ADPCM_BLOCK << 12); + return true; + } + + return false; + } + + void Reset() + { + bits = 0; + } }; class VolumeEnvelope @@ -243,7 +277,33 @@ class Voice { public: static const u32 InterpolationBitDept = 9; + static const u32 InterpolationWindowLen = 1 << InterpolationBitDept; static const f32 InterpolationWeights[][4]; + static const u32 SampleRateBitDepth = 26; + static const u32 BaseSampleRate = 1 << Voice::SampleRateBitDepth; + f32 interpWeights[2][InterpolationWindowLen + 1]; + + static const s32 SampleRateCount = 256 * 12; + u32 SampleRates[SampleRateCount]; + + Voice() + { + for (size_t i = 0; i < InterpolationWindowLen + 1; i++) + { + double pow1 = i / (double) InterpolationWindowLen; + double pow2 = pow1 * pow1; + double pow3 = pow2 * pow1; + + interpWeights[0][i] = (f32) (0.5f * (-pow3 + 2 * pow2 - pow1)); + interpWeights[1][i] = (f32) (0.5f * (3 * pow3 - 5 * pow2 + 2)); + } + + + for (size_t i = 0; i < SampleRateCount; i++) + { + SampleRates[i] = (u32) std::round(Voice::BaseSampleRate * std::pow(2, i / (double) SampleRateCount)); + } + } s32 id; @@ -284,11 +344,12 @@ class Voice // Interpolation/pitch Calculations VoiceCounter vounter; + s16 currSamples[31]; u8 note; s32 pitch; - u16 noteStep; - bool hasLooped = false; + u32 noteStep; u32 f_SampleOffset = 0; + bool HasSamples; std::tuple CalculateVolume(); f32 Interpolate(); From c3478865ca07bea6c213d4da55e0cd24b53be093 Mon Sep 17 00:00:00 2001 From: Sean Horton <3359434+sean-horton@users.noreply.github.com> Date: Wed, 12 Jun 2024 21:12:35 -0400 Subject: [PATCH 79/82] Interpolation is now an interface --- Source/AliveLibAE/Sound/Midi.cpp | 2 +- Source/AliveLibCommon/audio/Interpolation.cpp | 344 ++++++++++++++++++ Source/AliveLibCommon/audio/Interpolation.hpp | 58 +++ Source/AliveLibCommon/audio/Sequencer.cpp | 320 ++-------------- Source/AliveLibCommon/audio/Sequencer.hpp | 106 +----- 5 files changed, 453 insertions(+), 377 deletions(-) create mode 100644 Source/AliveLibCommon/audio/Interpolation.cpp create mode 100644 Source/AliveLibCommon/audio/Interpolation.hpp diff --git a/Source/AliveLibAE/Sound/Midi.cpp b/Source/AliveLibAE/Sound/Midi.cpp index 8c7d7d27f..cd0854220 100644 --- a/Source/AliveLibAE/Sound/Midi.cpp +++ b/Source/AliveLibAE/Sound/Midi.cpp @@ -984,7 +984,7 @@ class AESoundSampleParser : public psx::SoundSampleParser } soundDatFile.close(); - delete soundData; + delete[] soundData; return samples; } diff --git a/Source/AliveLibCommon/audio/Interpolation.cpp b/Source/AliveLibCommon/audio/Interpolation.cpp new file mode 100644 index 000000000..ff5dc9156 --- /dev/null +++ b/Source/AliveLibCommon/audio/Interpolation.cpp @@ -0,0 +1,344 @@ +#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) +{ + f32 fix = srcRate / 8000.0f; + fix; // TODO - this needs to be used somewhere. PC version has some non 8000hz samples + + s32 fineOffset = (note - root) * IlleprihFinePitchResolution + rootFine + (pitch - 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..5b3d61bc4 --- /dev/null +++ b/Source/AliveLibCommon/audio/Interpolation.hpp @@ -0,0 +1,58 @@ +#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; +}; + +} // namespace diff --git a/Source/AliveLibCommon/audio/Sequencer.cpp b/Source/AliveLibCommon/audio/Sequencer.cpp index 48aa9d14d..09d7b644f 100644 --- a/Source/AliveLibCommon/audio/Sequencer.cpp +++ b/Source/AliveLibCommon/audio/Sequencer.cpp @@ -22,7 +22,6 @@ std::vector sequences; const int PATCH_SIZE_LIMIT = 128; std::array patches; - ////////////////////////// // SPU Internal mangement methods void SPUInit(); @@ -298,7 +297,8 @@ void SPUInit() int id = 1; for (int i = 0; i < VOICE_SIZE_LIMIT; i++) { - voices[i] = new Voice(); + voices[i] = new Voice(new VoiceCounterDuckstation()); + //voices[i] = new Voice(new VoiceCounterIlleprih()); voices[i]->id = id; id = id << 1; } @@ -592,8 +592,8 @@ void SPUTick(void* udata, Uint8* stream, int len) // make value usable by SDL leftSample = leftSample / 32767.0f; rightSample = rightSample / 32767.0f; - SDL_MixAudioFormat((Uint8*) (AudioStream + i), (const Uint8*) &leftSample, AUDIO_F32, sizeof(float), 100); - SDL_MixAudioFormat((Uint8*) (AudioStream + i + 1), (const Uint8*) &rightSample, AUDIO_F32, sizeof(float), 100); + 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); } } @@ -756,44 +756,6 @@ static constexpr ADSRTableEntries ComputeADSRTableEntries() static constexpr ADSRTableEntries s_adsr_table = ComputeADSRTableEntries(); -////////////////////////// -// VoiceCounter - -// Interpolation window is centered and first 3 samples are from the previous block, -// so the first block has to start at index 2. -//const u32 VoiceCounter::StartOffset = 2 << Voice::InterpolationBitDept; -//const u32 VoiceCounter::InterpolationShift = Voice::SampleRateBitDepth - Voice::InterpolationBitDept; -//const u32 VoiceCounter::InterpolationAnd = (1 << Voice::InterpolationBitDept) - 1; -// -//u32 VoiceCounter::SampleIndex() -//{ -// return this->Counter >> Voice::SampleRateBitDepth; -//} -// -//u32 VoiceCounter::InterpolationIndex() -//{ -// return (this->Counter >> VoiceCounter::InterpolationShift) & VoiceCounter::InterpolationAnd; -//} -// -//bool VoiceCounter::NextSampleBlock(u32 sampleRate) -//{ -// this->Counter += sampleRate; -// -// if (this->SampleIndex() >= 28) -// { -// this->Counter -= (28 << Voice::SampleRateBitDepth); -// return true; -// } -// -// return false; -//} -// -//void VoiceCounter::Reset() -//{ -// this->Counter = VoiceCounter::StartOffset; -//} - - ////////////////////////// // Voice USHORT panMergeTable[] = { @@ -835,10 +797,11 @@ void Voice::Configure(Patch* pat, Sample* samp) // current sample block is 28 samples in this block + 3 from the next block // to achieve interpolation without going out of bounds - currSamples[0] = 0; - currSamples[1] = 0; - currSamples[2] = 0; - memcpy(&currSamples[3], sample->buffer, NUM_SAMPLES_PER_ADPCM_BLOCK * sizeof(s16)); + 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() @@ -858,138 +821,17 @@ void Voice::Reset() adsrCurrentLevel = 0; adsrTargetLevel = MAX_VOLUME; f_SampleOffset = 0; - vounter.Reset(); + vounter->Reset(); HasSamples = true; complete = false; inUse = false; } -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? - -//void Voice::ConfigureNote(u8 _note, u32 _pitch) -//{ -// this->note = _note; -// this->pitch = _pitch; -// -// f32 fix = sample->SampleRate / 8000.0f; -// fix; // TODO - this needs to be used somewhere. PC version has some non 8000hz samples -// -// s32 fineOffset = (note - sample->rootNote) * 256 + sample->rootNotePitchShift + (pitch - 64) * 4; -// -// u32 octaveOffset; -// u32 sampleRateOffset; -// if (fineOffset >= 0) { -// octaveOffset = fineOffset / 3072; -// sampleRateOffset = fineOffset - octaveOffset * 3072; -// noteStep = SampleRates[sampleRateOffset] << octaveOffset; -// return; -// } -// -// octaveOffset = (fineOffset + 1) / -3072 + 1; -// sampleRateOffset = fineOffset + octaveOffset * 3072; -// noteStep = SampleRates[sampleRateOffset] >> octaveOffset; -//} - void Voice::ConfigureNote(u8 _note, u32 _pitch) { this->note = _note; this->pitch = _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: This doesn't 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) sample->rootNotePitchShift); - if (sfine < 0) - { - sfine += 7; - } - sfine >>= 3; - - add = 0; - if (sfine > 15) - { - add = 1; - sfine -= 16; - } - - calc = (s16) (add + (note - (((s32) sample->rootNote) - 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; - } - - // freq = (ret * 44100) / 4096 - // step seems to be calculated for 8000hz samples hence the division - noteStep = (u16) ((sample->SampleRate / 8000.0) * double(ret)); - // std::cout << ret << " " << (int) note << " " << noteStep << std::endl; + noteStep = vounter->CalculateNoteStep(sample->rootNote, sample->rootNotePitchShift, sample->SampleRate, note, pitch); } u8 Voice::GetNote() @@ -997,29 +839,29 @@ 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]; - // 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}; - for (size_t i = 0; i < 0x80; i++) { panVolumes[i] = panTable[i] / 128.0f; @@ -1068,75 +910,9 @@ bool Voice::IsMatch(Sequence* seq, u8 channelId, u8 _note) float Voice::Interpolate() { - 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 // - }}; - // 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; @@ -1155,45 +931,27 @@ float Voice::Interpolate() } // Copy the last 3 samples from the previous block to the front - memcpy(&currSamples[0], &currSamples[NUM_SAMPLES_PER_ADPCM_BLOCK], 3 * sizeof(u16)); + 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[3], &sample->buffer[f_SampleOffset], (NUM_SAMPLES_PER_ADPCM_BLOCK - diff) * sizeof(s16)); - memcpy(&currSamples[3 + NUM_SAMPLES_PER_ADPCM_BLOCK - diff], &sample->buffer[0], (diff) * sizeof(s16)); + 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[3], &sample->buffer[f_SampleOffset], NUM_SAMPLES_PER_ADPCM_BLOCK * sizeof(s16)); + memcpy(&currSamples[sampOffset], &sample->buffer[f_SampleOffset], NUM_SAMPLES_PER_ADPCM_BLOCK * sizeof(s16)); } } - // the interpolation index is based on the source files sample rate - const u32 i = vounter.InterpolationIndex(); - const u32 s = vounter.SampleIndex(); - - // interpolate on the 4 most recent samples from current position - s32 out = 0; - out += s32(gauss[0x0FF - i]) * s32(currSamples[(int) (s + 3 - 3)]); - out += s32(gauss[0x1FF - i]) * s32(currSamples[(int) (s + 3 - 2)]); - out += s32(gauss[0x100 + i]) * s32(currSamples[(int) (s + 3 - 1)]); - out += s32(gauss[0x000 + i]) * s32(currSamples[(int) (s + 3 - 0)]); - out = out >> 15; - - //f32 out = interpWeights[0][i] * currSamples[s] - // + interpWeights[1][i] * currSamples[s + 1] - // + interpWeights[1][InterpolationWindowLen - i] * currSamples[s + 2] - // + interpWeights[0][InterpolationWindowLen - i] * currSamples[s + 3]; - - // vounter.bits has two purposes - // 1. change how samples are skipped to change the samples note - // 2. maintain gauss table positioning for correct interpolation + 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 (f32) (out); + this->HasSamples = !this->vounter->NextSampleBlock(noteStep); + return out; } s16 Voice::TickAdsr() diff --git a/Source/AliveLibCommon/audio/Sequencer.hpp b/Source/AliveLibCommon/audio/Sequencer.hpp index 7eddc34e5..6350555bf 100644 --- a/Source/AliveLibCommon/audio/Sequencer.hpp +++ b/Source/AliveLibCommon/audio/Sequencer.hpp @@ -1,5 +1,6 @@ #pragma once +#include "Interpolation.hpp" #include "SDL.h" #include #include @@ -8,22 +9,9 @@ namespace SPU { -const u32 NUM_SAMPLES_FROM_LAST_ADPCM_BLOCK = 3; -const u32 NUM_SAMPLES_PER_ADPCM_BLOCK = 28; - const s16 MIN_VOLUME = 0; const s16 MAX_VOLUME = 32767; -static u32 mask(u32 num) -{ - u32 res = 0; - while (num-- > 0) - { - res = (res << 1) | 1; - } - return res; -} - struct ASDR { // adsr1 @@ -149,7 +137,7 @@ struct Channel { // channels may have volums and pans too, but not implemented Patch* patch; - u8 vol = 127; + u8 vol = 127; // I almost think this should default to something lower (64?), but I have no proof u8 pan = 64; }; @@ -211,58 +199,6 @@ enum ADSRPhase RELEASE }; -class VoiceCounter2 -{ -public: - u32 SampleIndex(); - u32 InterpolationIndex(); - bool NextSampleBlock(u32 sampleRate); - void Reset(); - -private: - static const u32 StartOffset; - static const u32 InterpolationShift; - static const u32 InterpolationAnd; - - u32 Counter = 0; -}; - -union VoiceCounter -{ - // promoted to u32 because of overflow - u32 bits = 0; - - //BitField interpolation_index; - u32 InterpolationIndex() - { - return (bits >> 4) & mask(8); - } - - //BitField sample_index; - u32 SampleIndex() - { - return (bits >> 12) & mask(5); - } - - bool NextSampleBlock(u32 step) - { - step = std::min(step, 0x3FFF); - bits += step; - if (this->SampleIndex() >= 28) - { - bits -= (NUM_SAMPLES_PER_ADPCM_BLOCK << 12); - return true; - } - - return false; - } - - void Reset() - { - bits = 0; - } -}; - class VolumeEnvelope { public: @@ -276,33 +212,13 @@ class VolumeEnvelope class Voice { public: - static const u32 InterpolationBitDept = 9; - static const u32 InterpolationWindowLen = 1 << InterpolationBitDept; - static const f32 InterpolationWeights[][4]; - static const u32 SampleRateBitDepth = 26; - static const u32 BaseSampleRate = 1 << Voice::SampleRateBitDepth; - f32 interpWeights[2][InterpolationWindowLen + 1]; - - static const s32 SampleRateCount = 256 * 12; - u32 SampleRates[SampleRateCount]; - - Voice() - { - for (size_t i = 0; i < InterpolationWindowLen + 1; i++) - { - double pow1 = i / (double) InterpolationWindowLen; - double pow2 = pow1 * pow1; - double pow3 = pow2 * pow1; - - interpWeights[0][i] = (f32) (0.5f * (-pow3 + 2 * pow2 - pow1)); - interpWeights[1][i] = (f32) (0.5f * (3 * pow3 - 5 * pow2 + 2)); - } - - - for (size_t i = 0; i < SampleRateCount; i++) - { - SampleRates[i] = (u32) std::round(Voice::BaseSampleRate * std::pow(2, i / (double) SampleRateCount)); - } + Voice(VoiceCounter* voiceCounter) + { + this->vounter = voiceCounter; + } + ~Voice() + { + delete vounter; } s32 id; @@ -343,8 +259,8 @@ class Voice s16 adsrTargetLevel = MAX_VOLUME; // attack we want to reach max // Interpolation/pitch Calculations - VoiceCounter vounter; - s16 currSamples[31]; + VoiceCounter* vounter; + s16 currSamples[NUM_SAMPLES_FROM_LAST_ADPCM_BLOCK + NUM_SAMPLES_PER_ADPCM_BLOCK]; u8 note; s32 pitch; u32 noteStep; From d5b2a5ccefe697e7776a8c441cf0b11664ad955a Mon Sep 17 00:00:00 2001 From: Sean Horton <3359434+sean-horton@users.noreply.github.com> Date: Thu, 13 Jun 2024 21:09:12 -0400 Subject: [PATCH 80/82] Get Illeprih's reverb working --- Source/AliveLibCommon/CMakeLists.txt | 8 +- Source/AliveLibCommon/audio/Interpolation.cpp | 9 +- Source/AliveLibCommon/audio/Reverb.cpp | 502 ++++++++++++++++++ Source/AliveLibCommon/audio/Reverb.hpp | 122 +++++ Source/AliveLibCommon/audio/Sequencer.cpp | 325 +----------- 5 files changed, 661 insertions(+), 305 deletions(-) create mode 100644 Source/AliveLibCommon/audio/Reverb.cpp create mode 100644 Source/AliveLibCommon/audio/Reverb.hpp diff --git a/Source/AliveLibCommon/CMakeLists.txt b/Source/AliveLibCommon/CMakeLists.txt index 12e48ae11..bebed71c7 100644 --- a/Source/AliveLibCommon/CMakeLists.txt +++ b/Source/AliveLibCommon/CMakeLists.txt @@ -42,9 +42,13 @@ SET(AliveLibSrcCommon 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} ) +add_library(AliveLibCommon ${AliveLibSrcCommon}) target_include_directories(AliveLibCommon PUBLIC $ @@ -104,4 +108,4 @@ target_link_libraries(AliveLibCommon project_warnings ) -export(TARGETS AliveLibCommon FILE AliveLibCommon.cmake) \ No newline at end of file +export(TARGETS AliveLibCommon FILE AliveLibCommon.cmake) diff --git a/Source/AliveLibCommon/audio/Interpolation.cpp b/Source/AliveLibCommon/audio/Interpolation.cpp index ff5dc9156..7ac691d0c 100644 --- a/Source/AliveLibCommon/audio/Interpolation.cpp +++ b/Source/AliveLibCommon/audio/Interpolation.cpp @@ -282,10 +282,13 @@ const std::array SampleRates = GenerateSampleRates u32 VoiceCounterIlleprih::CalculateNoteStep(s32 root, s32 rootFine, u32 srcRate, u8 note, u32 pitch) { - f32 fix = srcRate / 8000.0f; - fix; // TODO - this needs to be used somewhere. PC version has some non 8000hz samples + // TODO - this needs to be used somewhere. PC version has some non 8000hz samples - s32 fineOffset = (note - root) * IlleprihFinePitchResolution + rootFine + (pitch - 64) * 2; + 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; diff --git a/Source/AliveLibCommon/audio/Reverb.cpp b/Source/AliveLibCommon/audio/Reverb.cpp new file mode 100644 index 000000000..f10fa9a54 --- /dev/null +++ b/Source/AliveLibCommon/audio/Reverb.cpp @@ -0,0 +1,502 @@ +#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(disable : 4838) +#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}}; + +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 index 09d7b644f..a157d991a 100644 --- a/Source/AliveLibCommon/audio/Sequencer.cpp +++ b/Source/AliveLibCommon/audio/Sequencer.cpp @@ -2,6 +2,7 @@ #include "Sequencer.hpp" #include "SDL.h" +#include "Reverb.hpp" namespace SPU { @@ -22,6 +23,9 @@ std::vector sequences; const int PATCH_SIZE_LIMIT = 128; std::array patches; +//Reverb* reverb = new ReverbDuckstation(); +Reverb* reverb = new ReverbIlleprih(); + ////////////////////////// // SPU Internal mangement methods void SPUInit(); @@ -45,9 +49,6 @@ void SPUOneShotStop(s32 mask); void SPUTick(void* udata, Uint8* stream, int len); void SPUTickSequences(); -void ProcessReverb(s16 left_in, s16 right_in, s32* left_out, s32* right_out); -static s16 ReverbRead(u32 address, s32 offset = 0); - ////////////////////////// // Public SPU methods @@ -297,8 +298,8 @@ void SPUInit() int id = 1; for (int i = 0; i < VOICE_SIZE_LIMIT; i++) { - voices[i] = new Voice(new VoiceCounterDuckstation()); - //voices[i] = new Voice(new VoiceCounterIlleprih()); + //voices[i] = new Voice(new VoiceCounterDuckstation()); + voices[i] = new Voice(new VoiceCounterIlleprih()); voices[i]->id = id; id = id << 1; } @@ -540,8 +541,8 @@ void SPUTick(void* udata, Uint8* stream, int len) float* AudioStream = (float*) stream; int StreamLength = len / sizeof(float); - s32 reverb_out_left = 0; - s32 reverb_out_right = 0; + 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) @@ -550,10 +551,10 @@ void SPUTick(void* udata, Uint8* stream, int len) AudioStream[i] = 0; AudioStream[i + 1] = 0; - float leftSample = 0; - float rightSample = 0; - s32 reverb_in_left = 0; - s32 reverb_in_right = 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) @@ -574,24 +575,25 @@ void SPUTick(void* udata, Uint8* stream, int len) // 4 Reverb if (v->sample->reverb == 4) { - reverb_in_left += (s32) std::get<0>(s); - reverb_in_right += (s32) std::get<1>(s); + 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 - ProcessReverb( - static_cast(Clamp16((s32) (reverb_in_left))), - static_cast(Clamp16((s32) (reverb_in_right))), - &reverb_out_left, - &reverb_out_right); + 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; - rightSample += reverb_out_right; + //leftSample = reverb_out_left / 32768.0f; + //rightSample = reverb_out_right / 32768.0f; + leftSample += reverb_out_left / 32768.0f; + rightSample += reverb_out_right / 32768.0f; - // make value usable by SDL - leftSample = leftSample / 32767.0f; - rightSample = rightSample / 32767.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); } @@ -1064,281 +1066,4 @@ std::tuple Voice::Tick() return std::make_tuple(leftA, rightA); } - -////////////////////////// -// REVERB - duckstation -static const u32 NUM_REVERB_REGS = 32; -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)); -} - -void ProcessReverb(s16 left_in, s16 right_in, s32* left_out, s32* right_out) -{ - 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; - - 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); -} - } // namespace \ No newline at end of file From 768eb27bb9012cc671afb0ea646f4bcd4eb08125 Mon Sep 17 00:00:00 2001 From: Sean Horton <3359434+sean-horton@users.noreply.github.com> Date: Thu, 13 Jun 2024 21:28:54 -0400 Subject: [PATCH 81/82] Allow easier swap out of implementations --- Source/AliveLibCommon/audio/Interpolation.hpp | 28 +++++++++++++++++++ Source/AliveLibCommon/audio/Reverb.cpp | 4 +++ Source/AliveLibCommon/audio/Sequencer.cpp | 11 ++++++-- Source/AliveLibCommon/audio/Sequencer.hpp | 10 ++----- 4 files changed, 43 insertions(+), 10 deletions(-) diff --git a/Source/AliveLibCommon/audio/Interpolation.hpp b/Source/AliveLibCommon/audio/Interpolation.hpp index 5b3d61bc4..d386410f7 100644 --- a/Source/AliveLibCommon/audio/Interpolation.hpp +++ b/Source/AliveLibCommon/audio/Interpolation.hpp @@ -55,4 +55,32 @@ class VoiceCounterIlleprih : public VoiceCounter 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/Reverb.cpp b/Source/AliveLibCommon/audio/Reverb.cpp index f10fa9a54..cfc23d432 100644 --- a/Source/AliveLibCommon/audio/Reverb.cpp +++ b/Source/AliveLibCommon/audio/Reverb.cpp @@ -314,7 +314,9 @@ std::pair ReverbDuckstation::Process(f32 _left_in, f32 _right_in) ////////////////////////// // 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 @@ -367,6 +369,8 @@ const ReverbType ReverbIlleprih::reverbTypes[10] = { 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() { diff --git a/Source/AliveLibCommon/audio/Sequencer.cpp b/Source/AliveLibCommon/audio/Sequencer.cpp index a157d991a..a7bcfa40a 100644 --- a/Source/AliveLibCommon/audio/Sequencer.cpp +++ b/Source/AliveLibCommon/audio/Sequencer.cpp @@ -23,8 +23,13 @@ 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 @@ -298,8 +303,7 @@ void SPUInit() int id = 1; for (int i = 0; i < VOICE_SIZE_LIMIT; i++) { - //voices[i] = new Voice(new VoiceCounterDuckstation()); - voices[i] = new Voice(new VoiceCounterIlleprih()); + voices[i] = new Voice(interpolation); voices[i]->id = id; id = id << 1; } @@ -573,8 +577,9 @@ void SPUTick(void* udata, Uint8* stream, int len) // 2 Portamento // 3 1 & 2(Portamento and Vibrate on) // 4 Reverb - if (v->sample->reverb == 4) + if (v->sample->reverb != 0) { + reverb->ChangeSetting(v->sample->reverb); reverb_in_left += std::get<0>(s); reverb_in_right += std::get<1>(s); } diff --git a/Source/AliveLibCommon/audio/Sequencer.hpp b/Source/AliveLibCommon/audio/Sequencer.hpp index 6350555bf..d8010be8e 100644 --- a/Source/AliveLibCommon/audio/Sequencer.hpp +++ b/Source/AliveLibCommon/audio/Sequencer.hpp @@ -212,13 +212,9 @@ class VolumeEnvelope class Voice { public: - Voice(VoiceCounter* voiceCounter) + Voice(InterpolationProvider* provider) { - this->vounter = voiceCounter; - } - ~Voice() - { - delete vounter; + this->vounter = provider->Create(); } s32 id; @@ -259,7 +255,7 @@ class Voice s16 adsrTargetLevel = MAX_VOLUME; // attack we want to reach max // Interpolation/pitch Calculations - VoiceCounter* vounter; + std::unique_ptr vounter; s16 currSamples[NUM_SAMPLES_FROM_LAST_ADPCM_BLOCK + NUM_SAMPLES_PER_ADPCM_BLOCK]; u8 note; s32 pitch; From 0275cc4caaefeb74c7f38be40707922a65f42be9 Mon Sep 17 00:00:00 2001 From: Sean Horton <3359434+sean-horton@users.noreply.github.com> Date: Thu, 13 Jun 2024 21:44:45 -0400 Subject: [PATCH 82/82] Add end of lines to files --- Source/AliveLibCommon/audio/Sequencer.cpp | 2 +- Source/AliveLibCommon/audio/Sequencer.hpp | 3 +-- Source/AliveLibCommon/audio/Stream.cpp | 2 +- Source/AliveLibCommon/audio/Stream.hpp | 2 +- Source/AliveLibCommon/audio/oddio.h | 2 +- 5 files changed, 5 insertions(+), 6 deletions(-) diff --git a/Source/AliveLibCommon/audio/Sequencer.cpp b/Source/AliveLibCommon/audio/Sequencer.cpp index a7bcfa40a..4e48fff0b 100644 --- a/Source/AliveLibCommon/audio/Sequencer.cpp +++ b/Source/AliveLibCommon/audio/Sequencer.cpp @@ -1071,4 +1071,4 @@ std::tuple Voice::Tick() return std::make_tuple(leftA, rightA); } -} // namespace \ No newline at end of file +} // namespace diff --git a/Source/AliveLibCommon/audio/Sequencer.hpp b/Source/AliveLibCommon/audio/Sequencer.hpp index d8010be8e..f11a966cb 100644 --- a/Source/AliveLibCommon/audio/Sequencer.hpp +++ b/Source/AliveLibCommon/audio/Sequencer.hpp @@ -295,5 +295,4 @@ void SeqSetVolume(s32 seqId, s16 voll, s16 volr); void SeqStop(s32 seqId); bool SeqIsDone(s32 seqId); - -} // namespace \ No newline at end of file +} // namespace diff --git a/Source/AliveLibCommon/audio/Stream.cpp b/Source/AliveLibCommon/audio/Stream.cpp index 16bb3750c..d272a1c08 100644 --- a/Source/AliveLibCommon/audio/Stream.cpp +++ b/Source/AliveLibCommon/audio/Stream.cpp @@ -89,4 +89,4 @@ size_t Stream::Size() const return mSize; } -} // namespace psx \ No newline at end of file +} // namespace psx diff --git a/Source/AliveLibCommon/audio/Stream.hpp b/Source/AliveLibCommon/audio/Stream.hpp index c9b602c85..29a312207 100644 --- a/Source/AliveLibCommon/audio/Stream.hpp +++ b/Source/AliveLibCommon/audio/Stream.hpp @@ -44,4 +44,4 @@ class Stream size_t mSize = 0; }; -} // namespace psx \ No newline at end of file +} // namespace psx diff --git a/Source/AliveLibCommon/audio/oddio.h b/Source/AliveLibCommon/audio/oddio.h index 26db586a5..150bb8d53 100644 --- a/Source/AliveLibCommon/audio/oddio.h +++ b/Source/AliveLibCommon/audio/oddio.h @@ -564,4 +564,4 @@ const std::vector> AO_RFENDER_VH = {{ {43, 673, 0x407f0478, 0x7f48005d, 0x0, 0xb2b10000, 0xdfeb80ff, 0x2b004e, 0xc100c0, 0xc300c2}, {44, 688, 0x407f0455, 0x7f250048, 0x0, 0xb2b10000, 0xdfeb80ff, 0x2c004f, 0xc100c0, 0xc300c2}}}; -} \ No newline at end of file +}