From dd59a563dcd5a811bd6ef20651ad74b1a13e083e Mon Sep 17 00:00:00 2001 From: louist103 <35883445+louist103@users.noreply.github.com> Date: Fri, 28 Jun 2024 11:20:06 -0400 Subject: [PATCH 01/25] Implement custom sequences and the audio editor. --- mm/2s2h/BenGui/BenGui.cpp | 8 + mm/2s2h/BenGui/BenMenuBar.cpp | 8 + mm/2s2h/BenPort.cpp | 3 + .../Enhancements/Audio/AudioCollection.cpp | 312 +++++++ mm/2s2h/Enhancements/Audio/AudioCollection.h | 76 ++ mm/2s2h/Enhancements/Audio/AudioEditor.cpp | 764 ++++++++++++++++++ mm/2s2h/Enhancements/Audio/AudioEditor.h | 38 + mm/src/audio/code_8019AF00.c | 25 + mm/src/audio/lib/heap.c | 4 +- mm/src/audio/lib/load.c | 121 ++- mm/src/audio/lib/playback.c | 13 +- mm/src/audio/lib/seqplayer.c | 27 +- mm/src/audio/sequence.c | 12 + mm/src/audio/sfx.c | 12 +- 14 files changed, 1377 insertions(+), 46 deletions(-) create mode 100644 mm/2s2h/Enhancements/Audio/AudioCollection.cpp create mode 100644 mm/2s2h/Enhancements/Audio/AudioCollection.h create mode 100644 mm/2s2h/Enhancements/Audio/AudioEditor.cpp create mode 100644 mm/2s2h/Enhancements/Audio/AudioEditor.h diff --git a/mm/2s2h/BenGui/BenGui.cpp b/mm/2s2h/BenGui/BenGui.cpp index b41e537e07..44df4bccd3 100644 --- a/mm/2s2h/BenGui/BenGui.cpp +++ b/mm/2s2h/BenGui/BenGui.cpp @@ -8,6 +8,8 @@ #include #include "UIWidgets.hpp" #include "HudEditor.h" +#include "../Enhancements/Audio/AudioEditor.h" + #ifdef __APPLE__ #include "graphic/Fast3D/gfx_metal.h" @@ -35,6 +37,7 @@ std::shared_ptr mHudEditorWindow; std::shared_ptr mActorViewerWindow; std::shared_ptr mCollisionViewerWindow; std::shared_ptr mEventLogWindow; +std::shared_ptr mAudioEditorWindow; void SetupGuiElements() { auto gui = Ship::Context::GetInstance()->GetWindow()->GetGui(); @@ -89,6 +92,9 @@ void SetupGuiElements() { mEventLogWindow = std::make_shared("gWindows.EventLog", "Event Log"); gui->AddGuiWindow(mEventLogWindow); + + mAudioEditorWindow = std::make_shared("gWindows.AudioEditor", "Audio Editor"); + gui->AddGuiWindow(mAudioEditorWindow); } void Destroy() { @@ -103,5 +109,7 @@ void Destroy() { mSaveEditorWindow = nullptr; mHudEditorWindow = nullptr; mActorViewerWindow = nullptr; + + mAudioEditorWindow = nullptr; } } // namespace BenGui diff --git a/mm/2s2h/BenGui/BenMenuBar.cpp b/mm/2s2h/BenGui/BenMenuBar.cpp index 764d2ea650..9e7b9aa560 100644 --- a/mm/2s2h/BenGui/BenMenuBar.cpp +++ b/mm/2s2h/BenGui/BenMenuBar.cpp @@ -15,6 +15,7 @@ #include "2s2h/DeveloperTools/WarpPoint.h" #include "2s2h/Enhancements/Cheats/Cheats.h" #include "2s2h/Enhancements/Player/Player.h" +#include "2s2h/Enhancements/Audio/AudioEditor.h" #include "HudEditor.h" extern "C" { @@ -679,6 +680,8 @@ extern std::shared_ptr mActorViewerWindow; extern std::shared_ptr mCollisionViewerWindow; extern std::shared_ptr mEventLogWindow; +extern std::shared_ptr mAudioEditorWindow; + const char* logLevels[] = { "trace", "debug", "info", "warn", "error", "critical", "off", }; @@ -804,6 +807,11 @@ void DrawDeveloperToolsMenu() { if (mEventLogWindow) { UIWidgets::WindowButton("Event Log", "gWindows.EventLog", mEventLogWindow); } + + if (mAudioEditorWindow) { + UIWidgets::WindowButton("Audio Editor", "gWindows.AudioEditor", mAudioEditorWindow); + } + ImGui::EndMenu(); } } diff --git a/mm/2s2h/BenPort.cpp b/mm/2s2h/BenPort.cpp index 5009eb2ce3..f15c865730 100644 --- a/mm/2s2h/BenPort.cpp +++ b/mm/2s2h/BenPort.cpp @@ -54,6 +54,7 @@ CrowdControl* CrowdControl::Instance; #include "2s2h/DeveloperTools/DebugConsole.h" #include "2s2h/DeveloperTools/DeveloperTools.h" #include "2s2h/SaveManager/SaveManager.h" +#include "2s2h/Enhancements/Audio/AudioCollection.h" // Resource Types/Factories #include "resource/type/Blob.h" @@ -98,6 +99,7 @@ CrowdControl* CrowdControl::Instance; OTRGlobals* OTRGlobals::Instance; GameInteractor* GameInteractor::Instance; +AudioCollection* AudioCollection::Instance; extern "C" char** cameraStrings; bool prevAltAssets = false; @@ -468,6 +470,7 @@ extern "C" void InitOTR() { OTRGlobals::Instance = new OTRGlobals(); GameInteractor::Instance = new GameInteractor(); + AudioCollection::Instance = new AudioCollection(); BenGui::SetupGuiElements(); InitEnhancements(); InitDeveloperTools(); diff --git a/mm/2s2h/Enhancements/Audio/AudioCollection.cpp b/mm/2s2h/Enhancements/Audio/AudioCollection.cpp new file mode 100644 index 0000000000..33e45bebc8 --- /dev/null +++ b/mm/2s2h/Enhancements/Audio/AudioCollection.cpp @@ -0,0 +1,312 @@ +#include "AudioCollection.h" +#include "sequence.h" +#include "sfx.h" +#include +#include +#include +#include +#include <2s2h/BenPort.h> +#include +#include + +#define SEQUENCE_MAP_ENTRY(sequenceId, label, sfxKey, category, canBeReplaced, canBeUsedAsReplacement) \ + { sequenceId, { sequenceId, label, sfxKey, category, canBeReplaced, canBeUsedAsReplacement } } + +AudioCollection::AudioCollection() { + // (originalSequenceId, label, sfxKey, category, canBeReplaced, canBeUsedAsReplacement), + sequenceMap = { + SEQUENCE_MAP_ENTRY(NA_BGM_GENERAL_SFX, "General SFX", "SEQUENCE_MAP_ENTRY", SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_AMBIENCE, "Ambience", "NA_BGM_AMBIENCE", SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_TERMINA_FIELD, "Termina Field", "NA_BGM_TERMINA_FIELD", SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_CHASE, "Chase", "NA_BGM_CHASE", SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_MAJORAS_THEME, "Majoras Theme", "NA_BGM_MAJORAS_THEME", SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_CLOCK_TOWER, "Clock Tower", "NA_BGM_CLOCK_TOWER", SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_STONE_TOWER_TEMPLE, "Stone Tower Temple", "NA_BGM_STONE_TOWER_TEMPLE", SEQ_BGM_WORLD, true, + true), + SEQUENCE_MAP_ENTRY(NA_BGM_INV_STONE_TOWER_TEMPLE, "Inverted Stone Tower Temple", "NA_BGM_INV_STONE_TOWER_TEMPLE", SEQ_BGM_WORLD, true, + true), + SEQUENCE_MAP_ENTRY(NA_BGM_FAILURE_0, "Missed Event 0", "NA_BGM_FAILURE_0", SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_FAILURE_1, "Missed Event 1", "NA_BGM_FAILURE_1", SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_HAPPY_MASK_SALESMAN, "Happy Mask Salesman's Theme", "NA_BGM_HAPPY_MASK_SALESMAN", SEQ_BGM_WORLD, true, + true), + SEQUENCE_MAP_ENTRY(NA_BGM_SONG_OF_HEALING, "Song Of Healing", "NA_BGM_SONG_OF_HEALING", SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_SWAMP_REGION, "Southern Swamp", "NA_BGM_SWAMP_REGION", SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_ALIEN_INVASION, "Alien Invasion", "NA_BGM_ALIEN_INVASION", SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_SWAMP_CRUISE, "Boat Cruise", "NA_BGM_SWAMP_CRUISE", SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_SHARPS_CURSE, "Sharp's Curse", "NA_BGM_SHARPS_CURSE", SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_GREAT_BAY_REGION, "Great Bay", "NA_BGM_GREAT_BAY_REGION", SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_IKANA_REGION, "Ikana", "NA_BGM_IKANA_REGION", SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_DEKU_PALACE, "Deku Palace", "NA_BGM_DEKU_PALACE", SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_MOUNTAIN_REGION, "Mountain Region", "NA_BGM_MOUNTAIN_REGION", SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_PIRATES_FORTRESS, "Pirates Fortress", "NA_BGM_PIRATES_FORTRESS", SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_CLOCK_TOWN_DAY_1, "Clock Town Day 1", "NA_BGM_CLOCK_TOWN_DAY_1", SEQ_BGM_WORLD, true, + true), + SEQUENCE_MAP_ENTRY(NA_BGM_CLOCK_TOWN_DAY_2, "Clock Town Day 2", "NA_BGM_CLOCK_TOWN_DAY_2", SEQ_BGM_WORLD, true, + true), + SEQUENCE_MAP_ENTRY(NA_BGM_CLOCK_TOWN_DAY_3, "Clock Town Day 3", "NA_BGM_CLOCK_TOWN_DAY_3", SEQ_BGM_WORLD, true, + true), + + SEQUENCE_MAP_ENTRY(NA_BGM_FILE_SELECT, "File Select", "NA_BGM_FILE_SELECT", SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_CLEAR_EVENT, "Clear Event", "NA_BGM_CLEAR_EVENT", SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_ENEMY, "Enemy", "NA_BGM_ENEMY", SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_BOSS, "Boss", "NA_BGM_BOSS", SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_WOODFALL_TEMPLE, "Woodfall Temple", "NA_BGM_WOODFALL_TEMPLE", SEQ_BGM_WORLD, true, + true), + SEQUENCE_MAP_ENTRY(NA_BGM_CLOCK_TOWN_MAIN_SEQUENCE, "Clock Town Main Sequence", + "NA_BGM_CLOCK_TOWN_MAIN_SEQUENCE", SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_OPENING, "Opening", "NA_BGM_OPENING", SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_INSIDE_A_HOUSE, "Inside House", "NA_BGM_INSIDE_A_HOUSE", SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_GAME_OVER, "Game Over", "NA_BGM_GAME_OVER", SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_CLEAR_BOSS, "Clear Bos", "NA_BGM_CLEAR_BOSS", SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_GET_ITEM, "Get Item", "NA_BGM_GET_ITEM", SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_CLOCK_TOWN_DAY_2_PTR, "Clock Town Day 2 (Alt)", "NA_BGM_CLOCK_TOWN_DAY_2_PTR", + SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_GET_HEART, "Get Heart", "NA_BGM_GET_HEART", SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_TIMED_MINI_GAME, "Timed Minigame", "NA_BGM_TIMED_MINI_GAME", SEQ_BGM_WORLD, true, + true), + SEQUENCE_MAP_ENTRY(NA_BGM_GORON_RACE, "Goron Race", "NA_BGM_GORON_RACE", SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_MUSIC_BOX_HOUSE, "Music Box House", "NA_BGM_MUSIC_BOX_HOUSE", SEQ_BGM_WORLD, true, + true), + SEQUENCE_MAP_ENTRY(NA_BGM_FAIRY_FOUNTAIN, "Fairy Fountain", "NA_BGM_FAIRY_FOUNTAIN", SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_ZELDAS_LULLABY, "Zelda's Lullaby", "NA_BGM_ZELDAS_LULLABY", SEQ_BGM_WORLD, true, + true), + SEQUENCE_MAP_ENTRY(NA_BGM_ROSA_SISTERS, "Rosa Sisters", "NA_BGM_ROSA_SISTERS", SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_OPEN_CHEST, "Open Chest", "NA_BGM_OPEN_CHEST", SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_MARINE_RESEARCH_LAB, "Marine Research Lab", "NA_BGM_MARINE_RESEARCH_LAB", + SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_GIANTS_THEME, "Giants Theme", "NA_BGM_GIANTS_THEME", SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_SONG_OF_STORMS, "Song Of Storms", "NA_BGM_SONG_OF_STORMS", SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_ROMANI_RANCH, "Romani Ranch", "NA_BGM_ROMANI_RANCH", SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_GORON_VILLAGE, "Goron Village", "NA_BGM_GORON_VILLAGE", SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_MAYORS_OFFICE, "Mayors Office", "NA_BGM_MAYORS_OFFICE", SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_OCARINA_EPONA, "Epona's Song", "NA_BGM_OCARINA_EPONA", SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_OCARINA_SUNS, "Sun's Song", "NA_BGM_OCARINA_SUNS", SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_OCARINA_TIME, "Song Of Time", "NA_BGM_OCARINA_TIME", SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_OCARINA_STORM, "Song of Storms (Ocarina)", "NA_BGM_OCARINA_STORM", SEQ_BGM_WORLD, + true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_ZORA_HALL, "Zora Hall", "NA_BGM_ZORA_HALL", SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_GET_NEW_MASK, "Get Mask", "NA_BGM_GET_NEW_MASK", SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_MINI_BOSS, "Mini Boss", "NA_BGM_MINI_BOSS", SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_GET_SMALL_ITEM, "Get Small Item", "NA_BGM_GET_SMALL_ITEM", SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_ASTRAL_OBSERVATORY, "Astral Observatory", "NA_BGM_ASTRAL_OBSERVATORY", SEQ_BGM_WORLD, + true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_CAVERN, "Cavern", "NA_BGM_CAVERN", SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_MILK_BAR, "Milk Bar", "NA_BGM_MILK_BAR", SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_ZELDA_APPEAR, "Zelda Appear", "NA_BGM_ZELDA_APPEAR", SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_SARIAS_SONG, "Saria's Song", "NA_BGM_SARIAS_SONG", SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_GORON_GOAL, "Goron Race Goal", "NA_BGM_GORON_GOAL", SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_HORSE, "Horse Race", "NA_BGM_HORSE", SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_HORSE_GOAL, "Horse Race Goal", "NA_BGM_HORSE_GOAL", SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_INGO, "Ingo", "NA_BGM_INGO", SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_KOTAKE_POTION_SHOP, "Potion Shop (Kotake)", "NA_BGM_KOTAKE_POTION_SHOP", + SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_SHOP, "Shop", "NA_BGM_SHOP", SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_OWL, "Owl", "NA_BGM_OWL", SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_SHOOTING_GALLERY, "Shooting Gallery", "NA_BGM_SHOOTING_GALLERY", SEQ_BGM_WORLD, true, + true), + SEQUENCE_MAP_ENTRY(NA_BGM_OCARINA_SOARING, "Song Of Soaring", "NA_BGM_OCARINA_SOARING", SEQ_BGM_WORLD, true, + true), + SEQUENCE_MAP_ENTRY(NA_BGM_OCARINA_HEALING, "Song Of Healing", "NA_BGM_OCARINA_HEALING", SEQ_BGM_WORLD, true, + true), + SEQUENCE_MAP_ENTRY(NA_BGM_INVERTED_SONG_OF_TIME, "Inverted Song Of Time", "NA_BGM_INVERTED_SONG_OF_TIME", + SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_SONG_OF_DOUBLE_TIME, "Song Of Double Time", "NA_BGM_SONG_OF_DOUBLE_TIME", + SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_SONATA_OF_AWAKENING, "Sonata Of Awakening", "NA_BGM_SONATA_OF_AWAKENING", + SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_GORON_LULLABY, "Goron Lullaby", "NA_BGM_GORON_LULLABY", SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_NEW_WAVE_BOSSA_NOVA, "New Wave Bossa Nova", "NA_BGM_NEW_WAVE_BOSSA_NOVA", + SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_ELEGY_OF_EMPTINESS, "Elegy Of Emptiness", "NA_BGM_ELEGY_OF_EMPTINESS", SEQ_BGM_WORLD, + true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_OATH_TO_ORDER, "Oath To Order", "NA_BGM_OATH_TO_ORDER", SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_SWORD_TRAINING_HALL, "Sword Training", "NA_BGM_SWORD_TRAINING_HALL", SEQ_BGM_WORLD, + true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_OCARINA_LULLABY_INTRO, "Lullaby Intro", "NA_BGM_OCARINA_LULLABY_INTRO", SEQ_BGM_WORLD, + true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_LEARNED_NEW_SONG, "Get Song", "NA_BGM_LEARNED_NEW_SONG", SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_BREMEN_MARCH, "Bremen March", "NA_BGM_BREMEN_MARCH", SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_BALLAD_OF_THE_WIND_FISH, "Balled Of The Wind Fish", "NA_BGM_BALLAD_OF_THE_WIND_FISH", + SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_SONG_OF_SOARING, "Song Of Soaring", "NA_BGM_SONG_OF_SOARING", SEQ_BGM_WORLD, true, + true), + SEQUENCE_MAP_ENTRY(NA_BGM_MILK_BAR_DUPLICATE, "Milk Bar Duplicate", "NA_BGM_MILK_BAR_DUPLICATE", SEQ_BGM_WORLD, + true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_FINAL_HOURS, "Final Hours", "NA_BGM_FINAL_HOURS", SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_MIKAU_RIFF, "Mikau Riff", "NA_BGM_MIKAU_RIFF", SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_MIKAU_FINALE, "Mikau Finale", "NA_BGM_MIKAU_FINALE", SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_FROG_SONG, "Frog Song", "NA_BGM_FROG_SONG", SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_OCARINA_SONATA, "Sonata Of Awakening (Ocarina)", "NA_BGM_OCARINA_SONATA", + SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_OCARINA_LULLABY, "Goron Lullaby", "NA_BGM_OCARINA_LULLABY", SEQ_BGM_WORLD, true, + true), + SEQUENCE_MAP_ENTRY(NA_BGM_OCARINA_NEW_WAVE, "New Wave Bossa Nova (Ocarina)", "NA_BGM_OCARINA_NEW_WAVE", + SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_OCARINA_ELEGY, "Elegy Of Emptiness (Ocarina)", "NA_BGM_OCARINA_ELEGY", SEQ_BGM_WORLD, + true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_OCARINA_OATH, "Oath To Order (Ocarina)", "NA_BGM_OCARINA_OATH", SEQ_BGM_WORLD, true, + true), + SEQUENCE_MAP_ENTRY(NA_BGM_MAJORAS_LAIR, "Majora's Lair", "NA_BGM_MAJORAS_LAIR", SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_OCARINA_LULLABY_INTRO_PTR, "Lullaby Intro Pointer", + "NA_BGM_OCARINA_LULLABY_INTRO_PTR", SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_OCARINA_GUITAR_BASS_SESSION, "Jam Session Bass", "NA_BGM_OCARINA_GUITAR_BASS_SESSION", + SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_PIANO_SESSION, "Jam Session Piano", "NA_BGM_PIANO_SESSION", SEQ_BGM_WORLD, true, + true), + SEQUENCE_MAP_ENTRY(NA_BGM_INDIGO_GO_SESSION, "Indigo Go Session (Credits)", "NA_BGM_INDIGO_GO_SESSION", + SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_SNOWHEAD_TEMPLE, "Snowhead Temple", "NA_BGM_SNOWHEAD_TEMPLE", SEQ_BGM_WORLD, true, + true), + SEQUENCE_MAP_ENTRY(NA_BGM_GREAT_BAY_TEMPLE, "Great Bay Temple", "NA_BGM_GREAT_BAY_TEMPLE", SEQ_BGM_WORLD, true, + true), + SEQUENCE_MAP_ENTRY(NA_BGM_NEW_WAVE_SAXOPHONE, "New Wave Saxophone", "NA_BGM_NEW_WAVE_SAXOPHONE", SEQ_BGM_WORLD, + true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_NEW_WAVE_VOCAL, "New Wave Vocal", "NA_BGM_NEW_WAVE_VOCAL", SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_MAJORAS_WRATH, "Majora's Wrath", "NA_BGM_MAJORAS_WRATH", SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_MAJORAS_INCARNATION, "Majora's Incarnation", "NA_BGM_MAJORAS_INCARNATION", + SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_MAJORAS_MASK, "Majora's Mask", "NA_BGM_MAJORAS_MASK", SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_BASS_PLAY, "Bass Play", "NA_BGM_BASS_PLAY", SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_DRUMS_PLAY, "Drums Play", "NA_BGM_DRUMS_PLAY", SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_PIANO_PLAY, "Piano Play", "NA_BGM_PIANO_PLAY", SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_IKANA_CASTLE, "Ikana Castle", "NA_BGM_IKANA_CASTLE", SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_GATHERING_GIANTS, "Gathering Giants", "NA_BGM_GATHERING_GIANTS", SEQ_BGM_WORLD, true, + true), + SEQUENCE_MAP_ENTRY(NA_BGM_KAMARO_DANCE, "Kamaro Dance", "NA_BGM_KAMARO_DANCE", SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_CREMIA_CARRIAGE, "Cremia Carriage", "NA_BGM_CREMIA_CARRIAGE", SEQ_BGM_WORLD, true, + true), + SEQUENCE_MAP_ENTRY(NA_BGM_KEATON_QUIZ, "Keaton Quiz", "NA_BGM_KEATON_QUIZ", SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_END_CREDITS, "Credits (First Half)", "NA_BGM_END_CREDITS", SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_OPENING_LOOP, "Opening Loop", "NA_BGM_OPENING_LOOP", SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_TITLE_THEME, "Title Theme", "NA_BGM_TITLE_THEME", SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_DUNGEON_APPEAR, "Dungeon Appear", "NA_BGM_DUNGEON_APPEAR", SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_WOODFALL_CLEAR, "Woodfall Clear", "NA_BGM_WOODFALL_CLEAR", SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_SNOWHEAD_CLEAR, "Snowhead Clear", "NA_BGM_SNOWHEAD_CLEAR", SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(0x7A, "IDK But there is an entry missing", "SEND_HELP", SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_INTO_THE_MOON, "Enter Moon", "NA_BGM_INTO_THE_MOON", SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_GOODBYE_GIANT, "Giants Leave", "NA_BGM_GOODBYE_GIANT", SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_TATL_AND_TAEL, "Tatl & Tale", "NA_BGM_TATL_AND_TAEL", SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_MOONS_DESTRUCTION, "Moon's Destruction", "NA_BGM_MOONS_DESTRUCTION", SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_END_CREDITS_SECOND_HALF, "Credits (Second Half)", "NA_BGM_END_CREDITS_SECOND_HALF", SEQ_BGM_WORLD, true, true), + }; + +} +#define CVAR_AUDIO(var) CVAR_PREFIX_AUDIO "." var +std::string AudioCollection::GetCvarKey(std::string sfxKey) { + auto prefix = CVAR_AUDIO("ReplacedSequences."); + return prefix + sfxKey + ".value"; +} + +std::string AudioCollection::GetCvarLockKey(std::string sfxKey) { + auto prefix = std::string(CVAR_AUDIO("ReplacedSequences.")); + return prefix + sfxKey + ".locked"; +} + +void AudioCollection::AddToCollection(char* otrPath, uint16_t seqNum) { + std::string fileName = std::filesystem::path(otrPath).filename().string(); + std::vector splitFileName = StringHelper::Split(fileName, "_"); + std::string sequenceName = splitFileName[0]; + SeqType type = SEQ_BGM_CUSTOM; + std::string typeString = splitFileName[splitFileName.size() - 1]; + std::locale loc; + for (size_t i = 0; i < typeString.length(); i++) { + typeString[i] = std::tolower(typeString[i], loc); + } + if (typeString == "fanfare") { + type = SEQ_FANFARE; + } + SequenceInfo info = {seqNum, + sequenceName, + StringHelper::Replace(StringHelper::Replace(StringHelper::Replace(sequenceName, " ", "_"), "~", "-"),".", ""), + type, false, true}; + sequenceMap.emplace(seqNum, info); +} + +uint16_t AudioCollection::GetReplacementSequence(uint16_t seqId) { + // if Hyrule Field Morning is about to play, but Hyrule Field is swapped, get the replacement sequence + // for Hyrule Field instead. Otherwise, leave it alone, so that without any sfx editor modifications we will + // play the normal track as usual. + + //BENTODO what did this do in ship? + //if (seqId == NA_BGM_FIELD_MORNING) { + // if (CVarGetInteger(CVAR_AUDIO("ReplacedSequences.NA_BGM_FIELD_LOGIC.value"), NA_BGM_FIELD_LOGIC) != NA_BGM_FIELD_LOGIC) { + // seqId = NA_BGM_FIELD_LOGIC; + // } + //} + + if (sequenceMap.find(seqId) == sequenceMap.end()) { + return seqId; + } + + const auto& sequenceInfo = sequenceMap.at(seqId); + const std::string cvarKey = GetCvarKey(sequenceInfo.sfxKey); + int replacementSeq = CVarGetInteger(cvarKey.c_str(), seqId); + if (!sequenceMap.contains(replacementSeq)) { + replacementSeq = seqId; + } + return static_cast(replacementSeq); +} + +void AudioCollection::RemoveFromShufflePool(SequenceInfo* seqInfo) { + const std::string cvarKey = std::string(CVAR_AUDIO("Excluded.")) + seqInfo->sfxKey; + excludedSequences.insert(seqInfo); + includedSequences.erase(seqInfo); + CVarSetInteger(cvarKey.c_str(), 1); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); +} + +void AudioCollection::AddToShufflePool(SequenceInfo* seqInfo) { + const std::string cvarKey = std::string(CVAR_AUDIO("Excluded.")) + seqInfo->sfxKey; + includedSequences.insert(seqInfo); + excludedSequences.erase(seqInfo); + CVarClear(cvarKey.c_str()); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); +} + +void AudioCollection::InitializeShufflePool() { + if (shufflePoolInitialized) return; + + for (auto& [seqId, seqInfo] : sequenceMap) { + if (!seqInfo.canBeUsedAsReplacement) continue; + const std::string cvarKey = std::string(CVAR_AUDIO("Excluded.")) + seqInfo.sfxKey; + if (CVarGetInteger(cvarKey.c_str(), 0)) { + excludedSequences.insert(&seqInfo); + } else { + includedSequences.insert(&seqInfo); + } + } + + shufflePoolInitialized = true; +}; + +extern "C" void AudioCollection_AddToCollection(char *otrPath, uint16_t seqNum) { + AudioCollection::Instance->AddToCollection(otrPath, seqNum); +} + +bool AudioCollection::HasSequenceNum(uint16_t seqId) { + return sequenceMap.contains(seqId); +} + +const char* AudioCollection::GetSequenceName(uint16_t seqId) { + auto seqIt = sequenceMap.find(seqId); + if (seqIt != sequenceMap.end()) { + return seqIt->second.label.c_str(); + } + return nullptr; +} + +size_t AudioCollection::SequenceMapSize() { + return sequenceMap.size(); +} + +extern "C" const char* AudioCollection_GetSequenceName(uint16_t seqId) { + return AudioCollection::Instance->GetSequenceName(seqId); +} + +extern "C" bool AudioCollection_HasSequenceNum(uint16_t seqId) { + return AudioCollection::Instance->HasSequenceNum(seqId); +} + +extern "C" size_t AudioCollection_SequenceMapSize() { + return AudioCollection::Instance->SequenceMapSize(); +} diff --git a/mm/2s2h/Enhancements/Audio/AudioCollection.h b/mm/2s2h/Enhancements/Audio/AudioCollection.h new file mode 100644 index 0000000000..f3fd964bc0 --- /dev/null +++ b/mm/2s2h/Enhancements/Audio/AudioCollection.h @@ -0,0 +1,76 @@ +#pragma once +#ifdef __cplusplus +#include +#include +#include +#include + +enum SeqType { + SEQ_NOSHUFFLE = 0, + SEQ_BGM_WORLD = 1 << 0, + SEQ_BGM_EVENT = 1 << 1, + SEQ_BGM_BATTLE = 1 << 2, + SEQ_OCARINA = 1 << 3, + SEQ_FANFARE = 1 << 4, + SEQ_BGM_ERROR = 1 << 5, + SEQ_SFX = 1 << 6, + SEQ_INSTRUMENT = 1 << 7, + SEQ_VOICE = 1 << 8, + SEQ_BGM_CUSTOM = SEQ_BGM_WORLD | SEQ_BGM_EVENT | SEQ_BGM_BATTLE, +}; + +#define INSTRUMENT_OFFSET 0x81 + +struct SequenceInfo { + uint16_t sequenceId; + std::string label; + std::string sfxKey; + SeqType category; + bool canBeReplaced; + bool canBeUsedAsReplacement; +}; + +class AudioCollection { + private: + // All Loaded Audio + std::map sequenceMap; + + // Sequences/SFX to include in/exclude from shuffle pool + struct compareSequenceLabel { + bool operator() (SequenceInfo* a, SequenceInfo* b) const { + return a->label < b->label; + }; + }; + std::set includedSequences; + std::set excludedSequences; + bool shufflePoolInitialized = false; + + public: + static AudioCollection* Instance; + AudioCollection(); + std::map GetAllSequences() const { + return sequenceMap; + } + std::set GetIncludedSequences() const { + return includedSequences; + }; + std::set GetExcludedSequences() const { + return excludedSequences; + }; + void AddToShufflePool(SequenceInfo*); + void RemoveFromShufflePool(SequenceInfo*); + void AddToCollection(char* otrPath, uint16_t seqNum); + uint16_t GetReplacementSequence(uint16_t seqId); + void InitializeShufflePool(); + const char* GetSequenceName(uint16_t seqId); + bool HasSequenceNum(uint16_t seqId); + size_t SequenceMapSize(); + std::string GetCvarKey(std::string sfxKey); + std::string GetCvarLockKey(std::string sfxKey); +}; +#else +void AudioCollection_AddToCollection(char *otrPath, uint16_t seqNum); +const char* AudioCollection_GetSequenceName(uint16_t seqId); +bool AudioCollection_HasSequenceNum(uint16_t seqId); +size_t AudioCollection_SequenceMapSize(); +#endif \ No newline at end of file diff --git a/mm/2s2h/Enhancements/Audio/AudioEditor.cpp b/mm/2s2h/Enhancements/Audio/AudioEditor.cpp new file mode 100644 index 0000000000..6618a816c9 --- /dev/null +++ b/mm/2s2h/Enhancements/Audio/AudioEditor.cpp @@ -0,0 +1,764 @@ +#include "AudioEditor.h" +#include "sequence.h" + +#include +#include +#include +#include +#include +#include +//#include "../randomizer/3drando/random.hpp" +#include "../../BenPort.h" +#include +#include "../../BenGui/UIWidgets.hpp" +#include "AudioCollection.h" +#include "2s2h/Enhancements/GameInteractor/GameInteractor.h" + + +extern "C" Vec3f gZeroVec3f; +extern "C" f32 gSfxDefaultFreqAndVolScale; +extern "C" s8 gSfxDefaultReverb; + +// Authentic sequence counts +// used to ensure we have enough to shuffle +#define SEQ_COUNT_BGM_WORLD 30 +#define SEQ_COUNT_BGM_BATTLE 6 +#define SEQ_COUNT_FANFARE 15 +#define SEQ_COUNT_OCARINA 12 +#define SEQ_COUNT_NOSHUFFLE 6 +#define SEQ_COUNT_BGM_EVENT 17 +#define SEQ_COUNT_INSTRUMENT 6 +#define SEQ_COUNT_SFX 57 +#define SEQ_COUNT_VOICE 108 + +size_t AuthenticCountBySequenceType(SeqType type) { + switch (type) { + case SEQ_NOSHUFFLE: + return SEQ_COUNT_NOSHUFFLE; + case SEQ_BGM_WORLD: + return SEQ_COUNT_BGM_WORLD; + case SEQ_BGM_EVENT: + return SEQ_COUNT_BGM_EVENT; + case SEQ_BGM_BATTLE: + return SEQ_COUNT_BGM_BATTLE; + case SEQ_OCARINA: + return SEQ_COUNT_OCARINA; + case SEQ_FANFARE: + return SEQ_COUNT_FANFARE; + case SEQ_SFX: + return SEQ_COUNT_SFX; + case SEQ_INSTRUMENT: + return SEQ_COUNT_INSTRUMENT; + case SEQ_VOICE: + return SEQ_COUNT_VOICE; + default: + return 0; + } +} + +#define CVAR_AUDIO(var) CVAR_PREFIX_AUDIO "." var + +// Grabs the current BGM sequence ID and replays it +// which will lookup the proper override, or reset back to vanilla +void ReplayCurrentBGM() { + u16 curSeqId = AudioSeq_GetActiveSeqId(SEQ_PLAYER_BGM_MAIN); + // TODO: replace with Audio_StartSeq when the macro is shared + // The fade time and audio player flags will always be 0 in the case of replaying the BGM, so they are not set here + AudioSeq_QueueSeqCmd(0x00000000 | curSeqId); +} + +// Attempt to update the BGM if it matches the current sequence that is being played +// The seqKey that is passed in should be the vanilla ID, not the override ID +void UpdateCurrentBGM(u16 seqKey, SeqType seqType) { + if (seqType != SEQ_BGM_WORLD) { + return; + } + + u16 curSeqId = AudioSeq_GetActiveSeqId(SEQ_PLAYER_BGM_MAIN); + if (curSeqId == seqKey) { + ReplayCurrentBGM(); + } +} + +void RandomizeGroup(SeqType type) { + std::vector values; + + // An empty IncludedSequences set means that the AudioEditor window has never been drawn + if (AudioCollection::Instance->GetIncludedSequences().empty()) { + AudioCollection::Instance->InitializeShufflePool(); + } + + // use a while loop to add duplicates if we don't have enough included sequences + while (values.size() < AuthenticCountBySequenceType(type)) { + for (const auto& seqData : AudioCollection::Instance->GetIncludedSequences()) { + if (seqData->category & type && seqData->canBeUsedAsReplacement) { + values.push_back(seqData->sequenceId); + } + } + + // if we didn't find any, return early without shuffling to prevent an infinite loop + if (!values.size()) return; + } + // BENTODO implement random + //Shuffle(values); + for (const auto& [seqId, seqData] : AudioCollection::Instance->GetAllSequences()) { + const std::string cvarKey = AudioCollection::Instance->GetCvarKey(seqData.sfxKey); + const std::string cvarLockKey = AudioCollection::Instance->GetCvarLockKey(seqData.sfxKey); + // don't randomize locked entries + if ((seqData.category & type) && CVarGetInteger(cvarLockKey.c_str(), 0) == 0) { + // Only save authentic sequence CVars + if ((((seqData.category & SEQ_BGM_CUSTOM) || seqData.category == SEQ_FANFARE) && seqData.sequenceId >= MAX_AUTHENTIC_SEQID) || seqData.canBeReplaced == false) { + continue; + } + const int randomValue = values.back(); + CVarSetInteger(cvarKey.c_str(), randomValue); + values.pop_back(); + } + } +} + +void ResetGroup(const std::map& map, SeqType type) { + for (const auto& [defaultValue, seqData] : map) { + if (seqData.category == type) { + // Only save authentic sequence CVars + if (seqData.category == SEQ_FANFARE && defaultValue >= MAX_AUTHENTIC_SEQID) { + continue; + } + const std::string cvarKey = AudioCollection::Instance->GetCvarKey(seqData.sfxKey); + const std::string cvarLockKey = AudioCollection::Instance->GetCvarLockKey(seqData.sfxKey); + if (CVarGetInteger(cvarLockKey.c_str(), 0) == 0) { + CVarClear(cvarKey.c_str()); + } + } + } +} + +void LockGroup(const std::map& map, SeqType type) { + for (const auto& [defaultValue, seqData] : map) { + if (seqData.category == type) { + // Only save authentic sequence CVars + if (seqData.category == SEQ_FANFARE && defaultValue >= MAX_AUTHENTIC_SEQID) { + continue; + } + const std::string cvarKey = AudioCollection::Instance->GetCvarKey(seqData.sfxKey); + const std::string cvarLockKey = AudioCollection::Instance->GetCvarLockKey(seqData.sfxKey); + CVarSetInteger(cvarLockKey.c_str(), 1); + } + } +} + +void UnlockGroup(const std::map& map, SeqType type) { + for (const auto& [defaultValue, seqData] : map) { + if (seqData.category == type) { + // Only save authentic sequence CVars + if (seqData.category == SEQ_FANFARE && defaultValue >= MAX_AUTHENTIC_SEQID) { + continue; + } + const std::string cvarKey = AudioCollection::Instance->GetCvarKey(seqData.sfxKey); + const std::string cvarLockKey = AudioCollection::Instance->GetCvarLockKey(seqData.sfxKey); + CVarSetInteger(cvarLockKey.c_str(), 0); + } + } +} + +extern "C" void Audio_ForceRestorePreviousBgm(void); +extern "C" void PreviewSequence(u16 seqId); + + +void DrawPreviewButton(uint16_t sequenceId, std::string sfxKey, SeqType sequenceType) { + const std::string cvarKey = AudioCollection::Instance->GetCvarKey(sfxKey); + const std::string hiddenKey = "##" + cvarKey; + const std::string stopButton = ICON_FA_STOP + hiddenKey; + const std::string previewButton = ICON_FA_PLAY + hiddenKey; + + if (CVarGetInteger(CVAR_AUDIO("Playing"), 0) == sequenceId) { + if (ImGui::Button(stopButton.c_str())) { + Audio_ForceRestorePreviousBgm(); + CVarSetInteger(CVAR_AUDIO("Playing"), 0); + } + UIWidgets::Tooltip("Stop Preview"); + } else { + if (ImGui::Button(previewButton.c_str())) { + if (CVarGetInteger(CVAR_AUDIO("Playing"), 0) != 0) { + Audio_ForceRestorePreviousBgm(); + CVarSetInteger(CVAR_AUDIO("Playing"), 0); + } else { + if (sequenceType == SEQ_SFX || sequenceType == SEQ_VOICE) { + AudioSfx_PlaySfx(sequenceId, &gZeroVec3f, 4, &gSfxDefaultFreqAndVolScale, + &gSfxDefaultFreqAndVolScale, &gSfxDefaultReverb); + } else if (sequenceType == SEQ_INSTRUMENT) { + AudioOcarina_SetInstrument(sequenceId - INSTRUMENT_OFFSET); + AudioOcarina_SetPlaybackSong(9, 1); + } else { + // TODO: Cant do both here, so have to click preview button twice + PreviewSequence(sequenceId); + CVarSetInteger(CVAR_AUDIO("Playing"), sequenceId); + } + } + } + UIWidgets::Tooltip("Play Preview"); + } +} + +#define CVAR_AUDIO(var) CVAR_PREFIX_AUDIO "." var + +void Draw_SfxTab(const std::string& tabId, SeqType type) { + const std::map& map = AudioCollection::Instance->GetAllSequences(); + + const std::string hiddenTabId = "##" + tabId; + const std::string resetAllButton = "Reset All" + hiddenTabId; + const std::string randomizeAllButton = "Randomize All" + hiddenTabId; + const std::string lockAllButton = "Lock All" + hiddenTabId; + const std::string unlockAllButton = "Unlock All" + hiddenTabId; + if (ImGui::Button(resetAllButton.c_str())) { + auto currentBGM = AudioSeq_GetActiveSeqId(SEQ_PLAYER_BGM_MAIN); + auto prevReplacement = AudioCollection::Instance->GetReplacementSequence(currentBGM); + ResetGroup(map, type); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); + auto curReplacement = AudioCollection::Instance->GetReplacementSequence(currentBGM); + if (type == SEQ_BGM_WORLD && prevReplacement != curReplacement) { + ReplayCurrentBGM(); + } + } + ImGui::SameLine(); + if (ImGui::Button(randomizeAllButton.c_str())) { + auto currentBGM = AudioSeq_GetActiveSeqId(SEQ_PLAYER_BGM_MAIN); + auto prevReplacement = AudioCollection::Instance->GetReplacementSequence(currentBGM); + RandomizeGroup(type); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); + auto curReplacement = AudioCollection::Instance->GetReplacementSequence(currentBGM); + if (type == SEQ_BGM_WORLD && prevReplacement != curReplacement) { + ReplayCurrentBGM(); + } + } + ImGui::SameLine(); + if (ImGui::Button(lockAllButton.c_str())) { + auto currentBGM = AudioSeq_GetActiveSeqId(SEQ_PLAYER_BGM_MAIN); + auto prevReplacement = AudioCollection::Instance->GetReplacementSequence(currentBGM); + LockGroup(map, type); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); + auto curReplacement = AudioCollection::Instance->GetReplacementSequence(currentBGM); + if (type == SEQ_BGM_WORLD && prevReplacement != curReplacement) { + ReplayCurrentBGM(); + } + } + ImGui::SameLine(); + if (ImGui::Button(unlockAllButton.c_str())) { + auto currentBGM = AudioSeq_GetActiveSeqId(SEQ_PLAYER_BGM_MAIN); + auto prevReplacement = AudioCollection::Instance->GetReplacementSequence(currentBGM); + UnlockGroup(map, type); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); + auto curReplacement = AudioCollection::Instance->GetReplacementSequence(currentBGM); + if (type == SEQ_BGM_WORLD && prevReplacement != curReplacement) { + ReplayCurrentBGM(); + } + } + + ImGui::BeginTable(tabId.c_str(), 3, ImGuiTableFlags_SizingFixedFit); + ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed, 100.0f); + for (const auto& [defaultValue, seqData] : map) { + if (~(seqData.category) & type) { + continue; + } + // Do not display custom sequences in the list + if ((((seqData.category & SEQ_BGM_CUSTOM) || seqData.category == SEQ_FANFARE) && defaultValue >= MAX_AUTHENTIC_SEQID) || seqData.canBeReplaced == false) { + continue; + } + + const std::string initialSfxKey = seqData.sfxKey; + const std::string cvarKey = AudioCollection::Instance->GetCvarKey(seqData.sfxKey); + const std::string cvarLockKey = AudioCollection::Instance->GetCvarLockKey(seqData.sfxKey); + const std::string hiddenKey = "##" + cvarKey; + const std::string resetButton = ICON_FA_UNDO + hiddenKey; + const std::string randomizeButton = ICON_FA_RANDOM + hiddenKey; + const std::string lockedButton = ICON_FA_LOCK + hiddenKey; + const std::string unlockedButton = ICON_FA_UNLOCK + hiddenKey; + const int currentValue = CVarGetInteger(cvarKey.c_str(), defaultValue); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("%s", seqData.label.c_str()); + ImGui::TableNextColumn(); + ImGui::PushItemWidth(-FLT_MIN); + const int initialValue = map.contains(currentValue) ? currentValue : defaultValue; + if (ImGui::BeginCombo(hiddenKey.c_str(), map.at(initialValue).label.c_str())) { + for (const auto& [value, seqData] : map) { + // If excluded as a replacement sequence, don't show in other dropdowns except the effect's own dropdown. + if (~(seqData.category) & type || (!seqData.canBeUsedAsReplacement && initialSfxKey != seqData.sfxKey)) { + continue; + } + + if (ImGui::Selectable(seqData.label.c_str())) { + CVarSetInteger(cvarKey.c_str(), value); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); + UpdateCurrentBGM(defaultValue, type); + } + + if (currentValue == value) { + ImGui::SetItemDefaultFocus(); + } + } + + ImGui::EndCombo(); + } + ImGui::TableNextColumn(); + ImGui::PushItemWidth(-FLT_MIN); + DrawPreviewButton((type == SEQ_SFX || type == SEQ_VOICE || type == SEQ_INSTRUMENT) ? defaultValue : currentValue, seqData.sfxKey, type); + auto locked = CVarGetInteger(cvarLockKey.c_str(), 0) == 1; + ImGui::SameLine(); + ImGui::PushItemWidth(-FLT_MIN); + if (ImGui::Button(resetButton.c_str())) { + CVarClear(cvarKey.c_str()); + CVarClear(cvarLockKey.c_str()); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); + UpdateCurrentBGM(defaultValue, seqData.category); + } + UIWidgets::Tooltip("Reset to default"); + ImGui::SameLine(); + ImGui::PushItemWidth(-FLT_MIN); + if (ImGui::Button(randomizeButton.c_str())) { + std::vector validSequences = {}; + for (const auto seqInfo : AudioCollection::Instance->GetIncludedSequences()) { + if (seqInfo->category & type) { + validSequences.push_back(seqInfo); + } + } + + if (validSequences.size()) { + auto it = validSequences.begin(); + const auto& seqData = *std::next(it, rand() % validSequences.size()); + CVarSetInteger(cvarKey.c_str(), seqData->sequenceId); + if (locked) { + CVarClear(cvarLockKey.c_str()); + } + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); + UpdateCurrentBGM(defaultValue, type); + } + } + UIWidgets::Tooltip("Randomize this sound"); + ImGui::SameLine(); + ImGui::PushItemWidth(-FLT_MIN); + if (ImGui::Button(locked ? lockedButton.c_str() : unlockedButton.c_str())) { + if (locked) { + CVarClear(cvarLockKey.c_str()); + } else { + CVarSetInteger(cvarLockKey.c_str(), 1); + } + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); + } + UIWidgets::Tooltip(locked ? "Sound locked" : "Sound unlocked"); + } + ImGui::EndTable(); +} + +extern "C" u16 AudioEditor_GetReplacementSeq(u16 seqId) { + return AudioCollection::Instance->GetReplacementSequence(seqId); +} + +std::string GetSequenceTypeName(SeqType type) { + switch (type) { + case SEQ_NOSHUFFLE: + return "No Shuffle"; + case SEQ_BGM_WORLD: + return "World"; + case SEQ_BGM_EVENT: + return "Event"; + case SEQ_BGM_BATTLE: + return "Battle"; + case SEQ_OCARINA: + return "Ocarina"; + case SEQ_FANFARE: + return "Fanfare"; + case SEQ_BGM_ERROR: + return "Error"; + case SEQ_SFX: + return "SFX"; + case SEQ_VOICE: + return "Voice"; + case SEQ_INSTRUMENT: + return "Instrument"; + case SEQ_BGM_CUSTOM: + return "Custom"; + default: + return "No Sequence Type"; + } +} + +ImVec4 GetSequenceTypeColor(SeqType type) { + switch (type) { + case SEQ_BGM_WORLD: + return ImVec4(0.0f, 0.2f, 0.0f, 1.0f); + case SEQ_BGM_EVENT: + return ImVec4(0.3f, 0.0f, 0.15f, 1.0f); + case SEQ_BGM_BATTLE: + return ImVec4(0.2f, 0.07f, 0.0f, 1.0f); + case SEQ_OCARINA: + return ImVec4(0.0f, 0.0f, 0.4f, 1.0f); + case SEQ_FANFARE: + return ImVec4(0.3f, 0.0f, 0.3f, 1.0f); + case SEQ_SFX: + return ImVec4(0.4f, 0.33f, 0.0f, 1.0f); + case SEQ_VOICE: + return ImVec4(0.3f, 0.42f, 0.09f, 1.0f); + case SEQ_INSTRUMENT: + return ImVec4(0.0f, 0.25f, 0.5f, 1.0f); + case SEQ_BGM_CUSTOM: + return ImVec4(0.9f, 0.0f, 0.9f, 1.0f); + default: + return ImVec4(1.0f, 0.0f, 0.0f, 1.0f); + } +} + +void DrawTypeChip(SeqType type) { + ImGui::BeginDisabled(); + ImGui::PushStyleColor(ImGuiCol_Button, GetSequenceTypeColor(type)); + ImGui::SmallButton(GetSequenceTypeName(type).c_str()); + ImGui::PopStyleColor(); + ImGui::EndDisabled(); +} + + +void AudioEditorRegisterOnSceneInitHook() { + // BENTODO implement this + //GameInteractor::Instance->RegisterGameHook([](int16_t sceneNum) { + // if (CVarGetInteger(CVAR_AUDIO("RandomizeAllOnNewScene"), 0)) { + // AudioEditor_RandomizeAll(); + // } + //}); +} + +void AudioEditor::InitElement() { + AudioEditorRegisterOnSceneInitHook(); +} + +void AudioEditor::DrawElement() { + AudioCollection::Instance->InitializeShufflePool(); + + ImGui::SetNextWindowSize(ImVec2(820, 630), ImGuiCond_FirstUseEver); + if (!ImGui::Begin("Audio Editor", &mIsVisible)) { + ImGui::End(); + return; + } + + float buttonSegments = ImGui::GetContentRegionAvail().x / 4; + if (ImGui::Button("Randomize All Groups", ImVec2(buttonSegments, 30.0f))) { + AudioEditor_RandomizeAll(); + } + UIWidgets::Tooltip("Randomizes all unlocked music and sound effects across tab groups"); + ImGui::SameLine(); + if (ImGui::Button("Reset All Groups", ImVec2(buttonSegments, 30.0f))) { + AudioEditor_ResetAll(); + } + UIWidgets::Tooltip("Resets all unlocked music and sound effects across tab groups"); + ImGui::SameLine(); + if (ImGui::Button("Lock All Groups", ImVec2(buttonSegments, 30.0f))) { + AudioEditor_LockAll(); + } + UIWidgets::Tooltip("Locks all music and sound effects across tab groups"); + ImGui::SameLine(); + if (ImGui::Button("Unlock All Groups", ImVec2(buttonSegments, 30.0f))) { + AudioEditor_UnlockAll(); + } + UIWidgets::Tooltip("Unlocks all music and sound effects across tab groups"); + + + if (ImGui::BeginTabBar("SfxContextTabBar", ImGuiTabBarFlags_NoCloseWithMiddleMouseButton)) { + if (ImGui::BeginTabItem("Background Music")) { + Draw_SfxTab("backgroundMusic", SEQ_BGM_WORLD); + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("Fanfares")) { + Draw_SfxTab("fanfares", SEQ_FANFARE); + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("Events")) { + Draw_SfxTab("event", SEQ_BGM_EVENT); + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("Battle Music")) { + Draw_SfxTab("battleMusic", SEQ_BGM_BATTLE); + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("Ocarina")) { + Draw_SfxTab("instrument", SEQ_INSTRUMENT); + Draw_SfxTab("ocarina", SEQ_OCARINA); + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("Sound Effects")) { + Draw_SfxTab("sfx", SEQ_SFX); + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("Voices")) { + Draw_SfxTab("voice", SEQ_VOICE); + ImGui::EndTabItem(); + } + + static ImVec2 cellPadding(8.0f, 8.0f); + if (ImGui::BeginTabItem("Options")) { + ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, cellPadding); + ImGui::BeginTable("Options", 1, ImGuiTableFlags_SizingStretchSame); + ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthStretch); + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + if (ImGui::BeginChild("SfxOptions", ImVec2(0, -8))) { + ImGui::PushItemWidth(-FLT_MIN); + UIWidgets::CVarCheckbox("Disable Enemy Proximity Music", CVAR_AUDIO("EnemyBGMDisable")); + UIWidgets::Tooltip( + "Disables the music change when getting close to enemies. Useful for hearing " + "your custom music for each scene more often."); + //UIWidgets::CVarCheckbox("Disable Leading Music in Lost Woods", CVAR_AUDIO("LostWoodsConsistentVolume")); + //UIWidgets::Tooltip( + // "Disables the volume shifting in the Lost Woods. Useful for hearing " + // "your custom music in the Lost Woods if you don't need the navigation assitance " + // "the volume changing provides. If toggling this while in the Lost Woods, reload " + // "the area for the effect to kick in." + //); + UIWidgets::CVarCheckbox("Display Sequence Name on Overlay", CVAR_AUDIO("SeqNameOverlay")); + UIWidgets::Tooltip( + "Displays the name of the current sequence in the corner of the screen whenever a new sequence " + "is loaded to the main sequence player (does not apply to fanfares or enemy BGM)." + ); + ImGui::SameLine(); + ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x); + UIWidgets::CVarSliderInt("Overlay Duration: %d seconds", CVAR_AUDIO("SeqNameOverlayDuration"), 1, 10, 5); + ImGui::PopItemWidth(); + ImGui::NewLine(); + ImGui::PopItemWidth(); + UIWidgets::CVarSliderFloat("Link's voice pitch multiplier: %.1f %%", CVAR_AUDIO("LinkVoiceFreqMultiplier"), 0.4, 2.5, 1.0); + ImGui::SameLine(); + const std::string resetButton = "Reset##linkVoiceFreqMultiplier"; + if (ImGui::Button(resetButton.c_str())) { + CVarSetFloat(CVAR_AUDIO("LinkVoiceFreqMultiplier"), 1.0f); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); + } + + ImGui::NewLine(); + UIWidgets::CVarCheckbox("Randomize All Music and Sound Effects on New Scene", CVAR_AUDIO("RandomizeAllOnNewScene")); + UIWidgets::Tooltip("Enables randomizing all unlocked music and sound effects when you enter a new scene."); + + ImGui::NewLine(); + ImGui::PushItemWidth(-FLT_MIN); + //UIWidgets::PaddedSeparator(); + //UIWidgets::PaddedText("The following options are experimental and may cause music\nto sound odd or have other undesireable effects."); + UIWidgets::CVarCheckbox("Lower Octaves of Unplayable High Notes", CVAR_AUDIO("ExperimentalOctaveDrop")); + UIWidgets::Tooltip("Some custom sequences may have notes that are too high for the game's audio " + "engine to play. Enabling this checkbox will cause these notes to drop a " + "couple of octaves so they can still harmonize with the other notes of the " + "sequence."); + ImGui::PopItemWidth(); + } + ImGui::EndChild(); + ImGui::EndTable(); + ImGui::PopStyleVar(1); + ImGui::EndTabItem(); + } + + static bool excludeTabOpen = false; + if (ImGui::BeginTabItem("Audio Shuffle Pool Management")) { + ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, cellPadding); + if (!excludeTabOpen) { + excludeTabOpen = true; + } + + static std::map showType { + {SEQ_BGM_WORLD, true}, + {SEQ_BGM_EVENT, true}, + {SEQ_BGM_BATTLE, true}, + {SEQ_OCARINA, true}, + {SEQ_FANFARE, true}, + {SEQ_SFX, true }, + {SEQ_VOICE, true }, + {SEQ_INSTRUMENT, true}, + {SEQ_BGM_CUSTOM, true} + }; + + // make temporary sets because removing from the set we're iterating through crashes ImGui + std::set seqsToInclude = {}; + std::set seqsToExclude = {}; + + static ImGuiTextFilter sequenceSearch; + sequenceSearch.Draw("Filter (inc,-exc)", 490.0f); + ImGui::SameLine(); + if (ImGui::Button("Exclude All")) { + for (auto seqInfo : AudioCollection::Instance->GetIncludedSequences()) { + if (sequenceSearch.PassFilter(seqInfo->label.c_str()) && showType[seqInfo->category]) { + seqsToExclude.insert(seqInfo); + } + } + } + ImGui::SameLine(); + if (ImGui::Button("Include All")) { + for (auto seqInfo : AudioCollection::Instance->GetExcludedSequences()) { + if (sequenceSearch.PassFilter(seqInfo->label.c_str()) && showType[seqInfo->category]) { + seqsToInclude.insert(seqInfo); + } + } + } + + ImGui::BeginTable("sequenceTypes", 9, ImGuiTableFlags_Resizable | ImGuiTableFlags_NoSavedSettings | ImGuiTableFlags_Borders); + + ImGui::TableNextColumn(); + ImGui::PushStyleColor(ImGuiCol_Header, GetSequenceTypeColor(SEQ_BGM_WORLD)); + ImGui::Selectable(GetSequenceTypeName(SEQ_BGM_WORLD).c_str(), &showType[SEQ_BGM_WORLD]); + ImGui::PopStyleColor(1); + + ImGui::TableNextColumn(); + ImGui::PushStyleColor(ImGuiCol_Header, GetSequenceTypeColor(SEQ_BGM_EVENT)); + ImGui::Selectable(GetSequenceTypeName(SEQ_BGM_EVENT).c_str(), &showType[SEQ_BGM_EVENT]); + ImGui::PopStyleColor(1); + + ImGui::TableNextColumn(); + ImGui::PushStyleColor(ImGuiCol_Header, GetSequenceTypeColor(SEQ_BGM_BATTLE)); + ImGui::Selectable(GetSequenceTypeName(SEQ_BGM_BATTLE).c_str(), &showType[SEQ_BGM_BATTLE]); + ImGui::PopStyleColor(1); + + ImGui::TableNextColumn(); + ImGui::PushStyleColor(ImGuiCol_Header, GetSequenceTypeColor(SEQ_OCARINA)); + ImGui::Selectable(GetSequenceTypeName(SEQ_OCARINA).c_str(), &showType[SEQ_OCARINA]); + ImGui::PopStyleColor(1); + + ImGui::TableNextColumn(); + ImGui::PushStyleColor(ImGuiCol_Header, GetSequenceTypeColor(SEQ_FANFARE)); + ImGui::Selectable(GetSequenceTypeName(SEQ_FANFARE).c_str(), &showType[SEQ_FANFARE]); + ImGui::PopStyleColor(1); + + ImGui::TableNextColumn(); + ImGui::PushStyleColor(ImGuiCol_Header, GetSequenceTypeColor(SEQ_SFX)); + ImGui::Selectable(GetSequenceTypeName(SEQ_SFX).c_str(), &showType[SEQ_SFX]); + ImGui::PopStyleColor(1); + + ImGui::TableNextColumn(); + ImGui::PushStyleColor(ImGuiCol_Header, GetSequenceTypeColor(SEQ_VOICE)); + ImGui::Selectable(GetSequenceTypeName(SEQ_VOICE).c_str(), &showType[SEQ_VOICE]); + ImGui::PopStyleColor(1); + + ImGui::TableNextColumn(); + ImGui::PushStyleColor(ImGuiCol_Header, GetSequenceTypeColor(SEQ_INSTRUMENT)); + ImGui::Selectable(GetSequenceTypeName(SEQ_INSTRUMENT).c_str(), &showType[SEQ_INSTRUMENT]); + ImGui::PopStyleColor(1); + + ImGui::TableNextColumn(); + ImGui::PushStyleColor(ImGuiCol_Header, GetSequenceTypeColor(SEQ_BGM_CUSTOM)); + ImGui::Selectable(GetSequenceTypeName(SEQ_BGM_CUSTOM).c_str(), &showType[SEQ_BGM_CUSTOM]); + ImGui::PopStyleColor(1); + + ImGui::EndTable(); + + if (ImGui::BeginTable("tableAllSequences", 2, ImGuiTableFlags_BordersH | ImGuiTableFlags_BordersV)) { + ImGui::TableSetupColumn("Included", ImGuiTableColumnFlags_WidthStretch, 200.0f); + ImGui::TableSetupColumn("Excluded", ImGuiTableColumnFlags_WidthStretch, 200.0f); + ImGui::TableHeadersRow(); + ImGui::TableNextRow(); + + // COLUMN 1 - INCLUDED SEQUENCES + ImGui::TableNextColumn(); + + ImGui::BeginChild("ChildIncludedSequences", ImVec2(0, -8)); + for (auto seqInfo : AudioCollection::Instance->GetIncludedSequences()) { + if (sequenceSearch.PassFilter(seqInfo->label.c_str()) && showType[seqInfo->category]) { + if (ImGui::Button(std::string(ICON_FA_TIMES "##" + seqInfo->sfxKey).c_str())) { + seqsToExclude.insert(seqInfo); + } + ImGui::SameLine(); + DrawPreviewButton(seqInfo->sequenceId, seqInfo->sfxKey, seqInfo->category); + ImGui::SameLine(); + DrawTypeChip(seqInfo->category); + ImGui::SameLine(); + ImGui::Text("%s", seqInfo->label.c_str()); + } + } + ImGui::EndChild(); + + // remove the sequences we added to the temp set + for (auto seqInfo : seqsToExclude) { + AudioCollection::Instance->RemoveFromShufflePool(seqInfo); + } + + // COLUMN 2 - EXCLUDED SEQUENCES + ImGui::TableNextColumn(); + + ImGui::BeginChild("ChildExcludedSequences", ImVec2(0, -8)); + for (auto seqInfo : AudioCollection::Instance->GetExcludedSequences()) { + if (sequenceSearch.PassFilter(seqInfo->label.c_str()) && showType[seqInfo->category]) { + if (ImGui::Button(std::string(ICON_FA_PLUS "##" + seqInfo->sfxKey).c_str())) { + seqsToInclude.insert(seqInfo); + } + ImGui::SameLine(); + DrawPreviewButton(seqInfo->sequenceId, seqInfo->sfxKey, seqInfo->category); + ImGui::SameLine(); + DrawTypeChip(seqInfo->category); + ImGui::SameLine(); + ImGui::Text("%s", seqInfo->label.c_str()); + } + } + ImGui::EndChild(); + + // add the sequences we added to the temp set + for (auto seqInfo : seqsToInclude) { + AudioCollection::Instance->AddToShufflePool(seqInfo); + } + + ImGui::EndTable(); + } + ImGui::PopStyleVar(1); + ImGui::EndTabItem(); + } else { + excludeTabOpen = false; + } + + ImGui::EndTabBar(); + } + ImGui::End(); +} + +static constexpr std::array allTypes = { SEQ_BGM_WORLD, SEQ_BGM_EVENT, SEQ_BGM_BATTLE, SEQ_OCARINA, SEQ_FANFARE, SEQ_INSTRUMENT, SEQ_SFX, SEQ_VOICE }; + +void AudioEditor_RandomizeAll() { + for (auto type : allTypes) { + RandomizeGroup(type); + } + + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); + ReplayCurrentBGM(); +} + +void AudioEditor_RandomizeGroup(SeqType group) { + RandomizeGroup(group); + + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); + ReplayCurrentBGM(); +} + +void AudioEditor_ResetAll() { + for (auto type : allTypes) { + ResetGroup(AudioCollection::Instance->GetAllSequences(), type); + } + + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); + ReplayCurrentBGM(); +} + +void AudioEditor_ResetGroup(SeqType group) { + ResetGroup(AudioCollection::Instance->GetAllSequences(), group); + + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); + ReplayCurrentBGM(); +} + +void AudioEditor_LockAll() { + for (auto type : allTypes) { + LockGroup(AudioCollection::Instance->GetAllSequences(), type); + } + + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); +} + +void AudioEditor_UnlockAll() { + for (auto type : allTypes) { + UnlockGroup(AudioCollection::Instance->GetAllSequences(), type); + } + + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); +} diff --git a/mm/2s2h/Enhancements/Audio/AudioEditor.h b/mm/2s2h/Enhancements/Audio/AudioEditor.h new file mode 100644 index 0000000000..93ae3c4406 --- /dev/null +++ b/mm/2s2h/Enhancements/Audio/AudioEditor.h @@ -0,0 +1,38 @@ +#pragma once +#include "stdint.h" + +#ifdef __cplusplus + +#include +#ifndef IMGUI_DEFINE_MATH_OPERATORS +#define IMGUI_DEFINE_MATH_OPERATORS +#endif +#include +#include "AudioCollection.h" + +class AudioEditor : public Ship::GuiWindow { + public: + using GuiWindow::GuiWindow; + + void DrawElement() override; + void InitElement() override; + void UpdateElement() override {}; + ~AudioEditor() {}; +}; + +void AudioEditor_RandomizeAll(); +void AudioEditor_RandomizeGroup(SeqType group); +void AudioEditor_ResetAll(); +void AudioEditor_ResetGroup(SeqType group); +void AudioEditor_LockAll(); +void AudioEditor_UnlockAll(); + +extern "C" { +#endif + +u16 AudioEditor_GetReplacementSeq(u16 seqId); + + +#ifdef __cplusplus +} +#endif diff --git a/mm/src/audio/code_8019AF00.c b/mm/src/audio/code_8019AF00.c index 7f9e75cde7..0bd21084c7 100644 --- a/mm/src/audio/code_8019AF00.c +++ b/mm/src/audio/code_8019AF00.c @@ -2070,6 +2070,23 @@ const char sAudioOcarinaUnusedText5[] = "last key is bad !!! %d %d %02X %02X\n"; const char sAudioOcarinaUnusedText6[] = "last key step is too short !!! %d:%d %d<%d\n"; const char sAudioOcarinaUnusedText7[] = "check is over!!! %d %d %d\n"; +// BENTODO find a final place for this function +// 2S2H [Port] Part of the audio editor +void PreviewSequence(u16 seqId) { + u16 curSeqId = AudioSeq_GetActiveSeqId(SEQ_PLAYER_BGM_MAIN); + + // if ((curSeqId & 0xFF) != NA_BGM_GANON_TOWER && (curSeqId & 0xFF) != NA_BGM_ESCAPE && curSeqId != seqId) { + Audio_SetSequenceMode(SEQ_MODE_IGNORE); + if (curSeqId != NA_BGM_DISABLED) { + sPrevMainBgmSeqId = curSeqId; + } else { + osSyncPrintf("Middle Boss BGM Start not stack \n"); + } + + AudioSeq_QueueSeqCmd(seqId); + // } +} + void AudioOcarina_ReadControllerInput(void) { Input inputs[MAXCONTROLLERS]; Input* input = &inputs[0]; @@ -2760,6 +2777,14 @@ void AudioOcarina_SetOcarinaDisableTimer(u8 unused, u8 timer) { void AudioOcarina_SetInstrument(u8 ocarinaInstrumentId) { if ((sOcarinaInstrumentId != ocarinaInstrumentId) || (ocarinaInstrumentId == OCARINA_INSTRUMENT_DEFAULT)) { + // #region 2S2H [Port] Custom Sequnces + u16 sfxEditorId = ocarinaInstrumentId + 0x81; + u16 neSeq = AudioEditor_GetReplacementSeq(sfxEditorId); + if (neSeq != sfxEditorId) { + gAudioCtx.seqReplaced[SEQ_PLAYER_SFX] = 1; + ocarinaInstrumentId = neSeq - 0x81; + } + // #end region SEQCMD_SET_CHANNEL_IO(SEQ_PLAYER_SFX, SFX_CHANNEL_OCARINA, 1, ocarinaInstrumentId); sOcarinaInstrumentId = ocarinaInstrumentId; diff --git a/mm/src/audio/lib/heap.c b/mm/src/audio/lib/heap.c index 709cfa873b..fdd7d6b606 100644 --- a/mm/src/audio/lib/heap.c +++ b/mm/src/audio/lib/heap.c @@ -13,7 +13,7 @@ void AudioHeap_ApplySampleBankCacheInternal(s32 apply, s32 sampleBankId); void AudioHeap_DiscardSampleBanks(void); void AudioHeap_InitReverb(s32 reverbIndex, ReverbSettings* settings, s32 isFirstInit); -extern size_t gSequenceToResourceSize; +extern size_t sequenceMapSize; #define gTatumsPerBeat (gAudioTatumInit[1]) @@ -68,7 +68,7 @@ void AudioHeap_ResetLoadStatus(void) { } } - for (i = 0; i < gSequenceToResourceSize; i++) { + for (i = 0; i < sequenceMapSize; i++) { if (gAudioCtx.seqLoadStatus[i] != LOAD_STATUS_PERMANENT) { gAudioCtx.seqLoadStatus[i] = LOAD_STATUS_NOT_LOADED; } diff --git a/mm/src/audio/lib/load.c b/mm/src/audio/lib/load.c index e38e232a67..7da9e4044e 100644 --- a/mm/src/audio/lib/load.c +++ b/mm/src/audio/lib/load.c @@ -14,7 +14,7 @@ #include "buffers.h" #include #include - +#include "2s2h/Enhancements/Audio/AudioCollection.h" #include "BenPort.h" /** @@ -106,10 +106,10 @@ DmaHandler sDmaHandler; //= osEPiStartDma; void* sUnusedHandler = NULL; s32 gAudioCtxInitalized = false; -char** gSequenceToResource; -size_t gSequenceToResourceSize; +char** sequenceMap; +size_t sequenceMapSize; u8 seqCachePolicyMap[MAX_AUTHENTIC_SEQID]; -char* gFontToResource[256]; +char* fontMap[256]; void AudioLoad_DecreaseSampleDmaTtls(void) { u32 i; @@ -510,11 +510,11 @@ u8* AudioLoad_GetFontsForSequence(s32 seqId, u32* outNumFonts, u8* buff) { // TODO: Sequence Remplacements - if (seqId > gSequenceToResourceSize || !gSequenceToResource[seqId]) { + if (seqId > sequenceMapSize || !sequenceMap[seqId]) { return NULL; } - SequenceData seqData = ResourceMgr_LoadSeqByName(gSequenceToResource[seqId]); + SequenceData seqData = ResourceMgr_LoadSeqByName(sequenceMap[seqId]); *outNumFonts = seqData.numFonts; if (seqData.numFonts == 0) @@ -611,7 +611,7 @@ s32 AudioLoad_SyncInitSeqPlayerInternal(s32 playerIndex, s32 seqId, s32 arg2) { authCachePolicy = seqCachePolicyMap[seqId]; seqId = gAudioCtx.seqToPlay[playerIndex]; } - SequenceData seqData2 = ResourceMgr_LoadSeqByName(gSequenceToResource[seqId]); + SequenceData seqData2 = ResourceMgr_LoadSeqByName(sequenceMap[seqId]); if (authCachePolicy != -1) { seqData2.cachePolicy = authCachePolicy; } @@ -708,7 +708,7 @@ SoundFontData* AudioLoad_SyncLoadFont(u32 fontId) { return NULL; } - SoundFont* sf = ResourceMgr_LoadAudioSoundFont(gFontToResource[fontId]); + SoundFont* sf = ResourceMgr_LoadAudioSoundFont(fontMap[fontId]); sampleBankId1 = sf->sampleBankId1; sampleBankId2 = sf->sampleBankId2; @@ -767,14 +767,14 @@ void* AudioLoad_SyncLoad(s32 tableType, u32 id, s32* didAllocate) { SoundFont* fnt; if (tableType == SEQUENCE_TABLE) { - SequenceData sData = ResourceMgr_LoadSeqByName(gSequenceToResource[id]); + SequenceData sData = ResourceMgr_LoadSeqByName(sequenceMap[id]); seqData = sData.seqData; size = sData.seqDataSize; medium = sData.medium; cachePolicy = sData.cachePolicy; romAddr = 0; } else if (tableType == FONT_TABLE) { - fnt = ResourceMgr_LoadAudioSoundFont(gFontToResource[id]); + fnt = ResourceMgr_LoadAudioSoundFont(fontMap[id]); size = sizeof(SoundFont); medium = 2; cachePolicy = 0; @@ -1116,6 +1116,18 @@ void AudioLoad_InitSoundFont(s32 fontId) { font->numSfx = entry->shortData3; } +// #region 2S2H [Port] Audio assets in the archive file and custom sequenes +int strcmp_sort(const void* str1, const void* str2) { + char* const* pp1 = str1; + char* const* pp2 = str2; + return strcmp(*pp1, *pp2); +} + +char** sequenceMap; +size_t sequenceMapSize; +char* fontMap[256]; +extern AudioContext gAudioCtx; +// #end region void AudioLoad_Init(void* heap, size_t heapSize) { s32 pad1[9]; s32 numFonts; @@ -1217,38 +1229,75 @@ void AudioLoad_Init(void* heap, size_t heapSize) { // AudioLoad_InitTable(gAudioCtx.sequenceTable, SEGMENT_ROM_START(Audioseq), 0); // AudioLoad_InitTable(gAudioCtx.soundFontTable, SEGMENT_ROM_START(Audiobank), 0); // AudioLoad_InitTable(gAudioCtx.sampleBankTable, SEGMENT_ROM_START(Audiotable), 0); - + + // #region 2S2H [Port] Audio in the archive and custom sequences int seqListSize = 0; + int customSeqListSize = 0; char** seqList = ResourceMgr_ListFiles("audio/sequences*", &seqListSize); - gSequenceToResourceSize = seqListSize; - gSequenceToResource = malloc(gSequenceToResourceSize * sizeof(*gSequenceToResource)); - gAudioCtx.seqLoadStatus = malloc(gSequenceToResourceSize * sizeof(*gAudioCtx.seqLoadStatus)); + char** customSeqList = ResourceMgr_ListFiles("custom/music/*", &customSeqListSize); + sequenceMapSize = (size_t)(AudioCollection_SequenceMapSize() + customSeqListSize); + sequenceMap = malloc(sequenceMapSize * sizeof(char*)); + gAudioCtx.seqLoadStatus = malloc(sequenceMapSize * sizeof(char*)); for (size_t i = 0; i < seqListSize; i++) { SequenceData sDat = ResourceMgr_LoadSeqByName(seqList[i]); - char* seqName = strdup(seqList[i]); - gSequenceToResource[sDat.seqNumber] = seqName; + + char* str = malloc(strlen(seqList[i]) + 1); + strcpy(str, seqList[i]); + + sequenceMap[sDat.seqNumber] = str; seqCachePolicyMap[sDat.seqNumber] = sDat.cachePolicy; } free(seqList); + //2S2H Port I think we need to take use seqListSize because entry 0x7A is missing. + int startingSeqNum = seqListSize; // MAX_AUTHENTIC_SEQID; // 109 is the highest vanilla sequence + qsort(customSeqList, customSeqListSize, sizeof(char*), strcmp_sort); + + // Because AudioCollection's sequenceMap actually has more than sequences (including instruments from 130-135 and + // sfx in the 2000s, 6000s, 10000s, 14000s, 18000s, and 26000s), it's better here to keep track of the next empty + // seqNum in AudioCollection instead of just skipping past the instruments at 130 with a higher MAX_AUTHENTIC_SEQID, + // especially if those others could be added to in the future. However, this really needs to be streamlined with + // specific ranges in AudioCollection for types, or unifying AudioCollection and the various maps in here + int seqNum = startingSeqNum; + + for (size_t i = startingSeqNum; i < startingSeqNum + customSeqListSize; i++) { + // ensure that what would be the next sequence number is actually unassigned in AudioCollection + while (AudioCollection_HasSequenceNum(seqNum)) { + seqNum++; + } + int j = i - startingSeqNum; + AudioCollection_AddToCollection(customSeqList[j], seqNum); + SequenceData sDat = ResourceMgr_LoadSeqByName(customSeqList[j]); + sDat.seqNumber = seqNum; + + char* str = malloc(strlen(customSeqList[j]) + 1); + strcpy(str, customSeqList[j]); + + sequenceMap[sDat.seqNumber] = str; + seqNum++; + } + + free(customSeqList); + int fntListSize = 0; char** fntList = ResourceMgr_ListFiles("audio/fonts*", &fntListSize); for (int i = 0; i < fntListSize; i++) { SoundFont* sf = ResourceMgr_LoadAudioSoundFont(fntList[i]); - gFontToResource[sf->fntIndex] = strdup(fntList[i]); - } - free(fntList); + char* str = malloc(strlen(fntList[i]) + 1); + strcpy(str, fntList[i]); + + fontMap[sf->fntIndex] = str; + } numFonts = fntListSize; - gAudioCtx.soundFontList = AudioHeap_Alloc(&gAudioCtx.initPool, numFonts * sizeof(SoundFont)); - // for (i = 0; i < numFonts; i++) { - // AudioLoad_InitSoundFont(i); - // } + free(fntList); + // #end region + gAudioCtx.soundFontList = AudioHeap_Alloc(&gAudioCtx.initPool, numFonts * sizeof(SoundFont)); if (addr = AudioHeap_Alloc(&gAudioCtx.initPool, gAudioHeapInitSizes.permanentPoolSize), addr == NULL) { // cast away const from gAudioHeapInitSizes @@ -1434,6 +1483,24 @@ s32 AudioLoad_SlowLoadSeq(s32 seqId, u8* ramAddr, s8* isDone) { } seqId = AudioLoad_GetRealTableIndex(SEQUENCE_TABLE, seqId); + // #region 2S2H [Port] Custom sequences + u16 newSeqId = AudioEditor_GetReplacementSeq(seqId); + if (seqId != newSeqId) { + gAudioCtx.seqToPlay[SEQ_PLAYER_BGM_MAIN] = newSeqId; + gAudioCtx.seqReplaced[SEQ_PLAYER_BGM_MAIN] = 1; + // This sequence command starts playing a sequence specified by seqId on the main BGM seq player. + // The sequence command is a bitpacked u32 where different bits of the number indicated different parameters. + // What those parameters are is dependent on the first 8 bits which represent an operation. + // First two digits (bits 31-24) - Sequence Command Operation (0x0 = play sequence immediately) + // Next two digits (bits 23-16) - Index of the SeqPlayer to operate on. (0, which is the main BGM player.) + // Next two digits (bits 15-8) - Fade Timer (0 in this case, we don't want any fade-in or out here.) + // Last two digits (bits 7-0) - the sequence ID to play. Not actually sure why it is cast to u16 instead of u8, + // copied this from authentic game code and adapted it. I think it might be so that you can choose to encode the + // fade timer into the seqId if you want to for some reason. + AudioSeq_QueueSeqCmd(0x00000000 | ((u8)SEQ_PLAYER_BGM_MAIN << 24) | ((u8)(0) << 16) | (u16)seqId); + return 0; + } + // #end region seqTable = AudioLoad_GetLoadTable(SEQUENCE_TABLE); slowLoad = &gAudioCtx.slowLoads[gAudioCtx.slowLoadPos]; if (slowLoad->status == LOAD_STATUS_DONE) { @@ -1443,7 +1510,7 @@ s32 AudioLoad_SlowLoadSeq(s32 seqId, u8* ramAddr, s8* isDone) { slowLoad->sample.sampleAddr = NULL; slowLoad->isDone = isDone; - SequenceData sData = ResourceMgr_LoadSeqByName(gSequenceToResource[seqId]); + SequenceData sData = ResourceMgr_LoadSeqByName(sequenceMap[seqId]); size = sData.seqDataSize; slowLoad->curDevAddr = sData.seqData; slowLoad->medium = seqTable->entries[seqId].medium; @@ -1957,7 +2024,7 @@ void AudioLoad_PreloadSamplesForFont(s32 fontId, s32 async, SampleBankRelocInfo* gAudioCtx.numUsedSamples = 0; - SoundFont* sf = ResourceMgr_LoadAudioSoundFont(gFontToResource[fontId]); + SoundFont* sf = ResourceMgr_LoadAudioSoundFont(fontMap[fontId]); numDrums = sf->numDrums; numInstruments = sf->numInstruments; numSfx = sf->numSfx; @@ -2083,8 +2150,8 @@ void AudioLoad_LoadPermanentSamples(void) { if (gAudioCtx.permanentEntries[i].tableType == FONT_TABLE) { fontId = AudioLoad_GetRealTableIndex(FONT_TABLE, gAudioCtx.permanentEntries[i].id); - - SoundFont* sf = ResourceMgr_LoadAudioSoundFont(gFontToResource[fontId]); + // 2S2H [Port] Audio assets in the archive + SoundFont* sf = ResourceMgr_LoadAudioSoundFont(fontMap[fontId]); sampleBankReloc.sampleBankId1 = sf->sampleBankId1; sampleBankReloc.sampleBankId2 = sf->sampleBankId2; // sampleBankReloc.sampleBankId1 = gAudioCtx.soundFontList[fontId].sampleBankId1; diff --git a/mm/src/audio/lib/playback.c b/mm/src/audio/lib/playback.c index 33e1337ed4..e2d713babb 100644 --- a/mm/src/audio/lib/playback.c +++ b/mm/src/audio/lib/playback.c @@ -8,7 +8,7 @@ void AudioPlayback_NoteInitForLayer(Note* note, SequenceLayer* layer); SoundFont* ResourceMgr_LoadAudioSoundFont(const char* path); -extern char* gFontToResource[256]; +extern char* fontMap[256]; void AudioPlayback_InitSampleState(Note* note, NoteSampleState* sampleState, NoteSubAttributes* subAttrs) { f32 volLeft; @@ -366,7 +366,8 @@ Instrument* AudioPlayback_GetInstrumentInner(s32 fontId, s32 instId) { } int instCnt = 0; - SoundFont* sf = ResourceMgr_LoadAudioSoundFont(gFontToResource[fontId]); + // 2S2H [Port] Audio assets in the archive + SoundFont* sf = ResourceMgr_LoadAudioSoundFont(fontMap[fontId]); if (instId >= sf->numInstruments) return NULL; @@ -392,8 +393,8 @@ Drum* AudioPlayback_GetDrum(s32 fontId, s32 drumId) { gAudioCtx.audioErrorFlags = AUDIO_ERROR(0, fontId, AUDIO_ERROR_FONT_NOT_LOADED); return NULL; } - - SoundFont* sf = ResourceMgr_LoadAudioSoundFont(gFontToResource[fontId]); + // 2S2H [Port] Audio assets in the archive + SoundFont* sf = ResourceMgr_LoadAudioSoundFont(fontMap[fontId]); if (drumId < sf->numDrums) { drum = sf->drums[drumId]; } @@ -416,8 +417,8 @@ SoundEffect* AudioPlayback_GetSoundEffect(s32 fontId, s32 sfxId) { gAudioCtx.audioErrorFlags = AUDIO_ERROR(0, fontId, AUDIO_ERROR_FONT_NOT_LOADED); return NULL; } - - SoundFont* sf = ResourceMgr_LoadAudioSoundFont(gFontToResource[fontId]); + // 2S2H [Port] Audio assets in the archive + SoundFont* sf = ResourceMgr_LoadAudioSoundFont(fontMap[fontId]); if (sfxId < sf->numSfx) { soundEffect = &sf->soundEffects[sfxId]; } diff --git a/mm/src/audio/lib/seqplayer.c b/mm/src/audio/lib/seqplayer.c index be884ecfdc..aa006f580e 100644 --- a/mm/src/audio/lib/seqplayer.c +++ b/mm/src/audio/lib/seqplayer.c @@ -17,6 +17,7 @@ #include "endianness.h" #include "global.h" #include "BenPort.h" +#include "2s2h/Enhancements/Audio/AudioEditor.h" #define PROCESS_SCRIPT_END -1 @@ -31,7 +32,7 @@ s32 AudioScript_SeqLayerProcessScriptStep3(SequenceLayer* layer, s32 cmd); u8 AudioScript_GetInstrument(SequenceChannel* channel, u8 instId, Instrument** instOut, AdsrSettings* adsr); SequenceData ResourceMgr_LoadSeqByName(const char* path); -extern char** gSequenceToResource; +extern char** sequenceMap; /** * sSeqInstructionArgsTable is a table for each sequence instruction @@ -1191,7 +1192,7 @@ void AudioScript_SetInstrument(SequenceChannel* channel, u8 instId) { void AudioScript_SequenceChannelSetVolume(SequenceChannel* channel, u8 volume) { channel->volume = (s32)volume / 127.0f; } - +#include void AudioScript_SequenceChannelProcessScript(SequenceChannel* channel) { s32 i; u8* data; @@ -1288,14 +1289,20 @@ void AudioScript_SequenceChannelProcessScript(SequenceChannel* channel) { break; case 0xEB: // channel: set soundFont and instrument + // #region 2S2H [Port] Custom sequences + u8 result = (u8)cmdArgs[0]; cmd = (u8)cmdArgs[0]; if (seqPlayer->defaultFont != 0xFF) { - cmdArgU16 = ((u16*)gAudioCtx.sequenceFontTable)[seqPlayer->seqId]; - lowBits = gAudioCtx.sequenceFontTable[cmdArgU16]; - cmd = gAudioCtx.sequenceFontTable[cmdArgU16 + lowBits - cmd]; + if (gAudioCtx.seqReplaced[seqPlayer->playerIndex]) { + seqPlayer->seqId = gAudioCtx.seqToPlay[seqPlayer->playerIndex]; + gAudioCtx.seqReplaced[seqPlayer->playerIndex] = 0; + } + u16 seqId = AudioEditor_GetReplacementSeq(seqPlayer->seqId); + SequenceData sDat = ResourceMgr_LoadSeqByName(sequenceMap[seqId]); + cmd = sDat.fonts[sDat.numFonts - result - 1]; } - + // #end region if (AudioHeap_SearchCaches(FONT_TABLE, CACHE_EITHER, cmd)) { channel->fontId = cmd; } @@ -1418,20 +1425,20 @@ void AudioScript_SequenceChannelProcessScript(SequenceChannel* channel) { case 0xC6: // channel: set soundFont cmd = (u8)cmdArgs[0]; - + // #region 2S2H [Port] Audio assets in the archive and custom sequences if (seqPlayer->defaultFont != 0xFF) { if (gAudioCtx.seqReplaced[seqPlayer->playerIndex]) { seqPlayer->seqId = gAudioCtx.seqToPlay[seqPlayer->playerIndex]; gAudioCtx.seqReplaced[seqPlayer->playerIndex] = 0; } - u16 seqId = seqPlayer->seqId; // AudioEditor_GetReplacementSeq(seqPlayer->seqId); - SequenceData sDat = ResourceMgr_LoadSeqByName(gSequenceToResource[seqId]); + u16 seqId = AudioEditor_GetReplacementSeq(seqPlayer->seqId); + SequenceData sDat = ResourceMgr_LoadSeqByName(sequenceMap[seqId]); // The game apparantely would sometimes do negative array lookups, the result of which would get // rejected by AudioHeap_SearchCaches, never changing the actual fontid. if (cmd > sDat.numFonts) break; - + // #end region cmd = sDat.fonts[(sDat.numFonts - cmd - 1)]; } diff --git a/mm/src/audio/sequence.c b/mm/src/audio/sequence.c index 3ad2266e3d..791daeaaeb 100644 --- a/mm/src/audio/sequence.c +++ b/mm/src/audio/sequence.c @@ -18,6 +18,7 @@ * the graph thread to the audio thread. */ #include "global.h" +#include "2s2h/Enhancements/Audio/AudioEditor.h" // Direct audio command (skips the queueing system) #define SEQCMD_SET_SEQPLAYER_VOLUME_NOW(seqPlayerIndex, duration, volume) \ @@ -434,6 +435,17 @@ void AudioSeq_ProcessSeqCmd(u32 cmd) { * Add the sequence cmd to the `sAudioSeqCmds` queue */ void AudioSeq_QueueSeqCmd(u32 cmd) { + // 2S2H [Port] Allow loading custom sequences + u8 op = cmd >> 28; + if (op == 0 || op == 2 || op == 12) { + u8 seqId = cmd & 0xFF; + u8 playerIdx = (cmd & 0xF000000) >> 24; + u16 newSeqId = AudioEditor_GetReplacementSeq(seqId); + gAudioCtx.seqReplaced[playerIdx] = (seqId != newSeqId); + gAudioCtx.seqToPlay[playerIdx] = newSeqId; + cmd |= (seqId & 0xFF); + } + sAudioSeqCmds[sSeqCmdWritePos++] = cmd; } diff --git a/mm/src/audio/sfx.c b/mm/src/audio/sfx.c index f667479999..323a87a411 100644 --- a/mm/src/audio/sfx.c +++ b/mm/src/audio/sfx.c @@ -217,7 +217,17 @@ void AudioSfx_ProcessRequest(void) { if (req->sfxId == NA_SE_NONE) { return; } - + // #region 2S2H [Port] Custom sequences + evictIndex = 0x80; + if (req->sfxId == 0) { + return; + } + u16 newSfxId = AudioEditor_GetReplacementSeq(req->sfxId); + if (req->sfxId != newSfxId) { + gAudioCtx.seqReplaced[SEQ_PLAYER_SFX] = 1; + req->sfxId = newSfxId; + } + // #end region bankId = SFX_BANK(req->sfxId); channelCount = 0; index = gSfxBanks[bankId][0].next; From e90cab79dc5330f4b10fb3b60f2762421e1a1521 Mon Sep 17 00:00:00 2001 From: louist103 <35883445+louist103@users.noreply.github.com> Date: Fri, 28 Jun 2024 11:57:34 -0400 Subject: [PATCH 02/25] Fix implicit function declaration `AudioEditor_GetReplacementSeq` --- mm/src/audio/code_8019AF00.c | 1 + 1 file changed, 1 insertion(+) diff --git a/mm/src/audio/code_8019AF00.c b/mm/src/audio/code_8019AF00.c index 0bd21084c7..530be71a5f 100644 --- a/mm/src/audio/code_8019AF00.c +++ b/mm/src/audio/code_8019AF00.c @@ -1,6 +1,7 @@ #include "global.h" #include "Enhancements/GameInteractor/GameInteractor.h" +#include "2s2h/Enhancements/Audio/AudioEditor.h" typedef struct { /* 0x0 */ s8 x; From 79cd0202c800794171e2feaea21b2194b0fb055c Mon Sep 17 00:00:00 2001 From: louist103 <35883445+louist103@users.noreply.github.com> Date: Fri, 28 Jun 2024 12:37:39 -0400 Subject: [PATCH 03/25] Fix mac again. --- mm/src/audio/lib/load.c | 1 + mm/src/audio/lib/seqplayer.c | 5 +++-- mm/src/audio/sfx.c | 1 + 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/mm/src/audio/lib/load.c b/mm/src/audio/lib/load.c index 7da9e4044e..41aec275e2 100644 --- a/mm/src/audio/lib/load.c +++ b/mm/src/audio/lib/load.c @@ -15,6 +15,7 @@ #include #include #include "2s2h/Enhancements/Audio/AudioCollection.h" +#include "2s2h/Enhancements/Audio/AudioEditor.h" #include "BenPort.h" /** diff --git a/mm/src/audio/lib/seqplayer.c b/mm/src/audio/lib/seqplayer.c index aa006f580e..d4a60ccbb7 100644 --- a/mm/src/audio/lib/seqplayer.c +++ b/mm/src/audio/lib/seqplayer.c @@ -1288,9 +1288,9 @@ void AudioScript_SequenceChannelProcessScript(SequenceChannel* channel) { } break; - case 0xEB: // channel: set soundFont and instrument + case 0xEB: { // channel: set soundFont and instrument // #region 2S2H [Port] Custom sequences - u8 result = (u8)cmdArgs[0]; + uint8_t result = (uint8_t)cmdArgs[0]; cmd = (u8)cmdArgs[0]; if (seqPlayer->defaultFont != 0xFF) { @@ -1308,6 +1308,7 @@ void AudioScript_SequenceChannelProcessScript(SequenceChannel* channel) { } cmdArgs[0] = cmdArgs[1]; + } // fallthrough case 0xC1: // channel: set instrument cmd = (u8)cmdArgs[0]; diff --git a/mm/src/audio/sfx.c b/mm/src/audio/sfx.c index 323a87a411..e902d06c94 100644 --- a/mm/src/audio/sfx.c +++ b/mm/src/audio/sfx.c @@ -1,4 +1,5 @@ #include "global.h" +#include "2s2h/Enhancements/Audio/AudioEditor.h" typedef struct { /* 0x00 */ u16 sfxId; From cac05a1038249f195abe8739a445c0ed150a57a2 Mon Sep 17 00:00:00 2001 From: louist103 <35883445+louist103@users.noreply.github.com> Date: Fri, 28 Jun 2024 15:07:42 -0400 Subject: [PATCH 04/25] String and header cleanup --- .../Enhancements/Audio/AudioCollection.cpp | 4 +- mm/2s2h/Enhancements/Audio/AudioCollection.h | 2 +- mm/2s2h/Enhancements/Audio/AudioEditor.cpp | 69 +++++++++---------- mm/2s2h/Enhancements/Audio/AudioEditor.h | 12 +++- 4 files changed, 44 insertions(+), 43 deletions(-) diff --git a/mm/2s2h/Enhancements/Audio/AudioCollection.cpp b/mm/2s2h/Enhancements/Audio/AudioCollection.cpp index 33e45bebc8..5a246fa4d9 100644 --- a/mm/2s2h/Enhancements/Audio/AudioCollection.cpp +++ b/mm/2s2h/Enhancements/Audio/AudioCollection.cpp @@ -216,7 +216,7 @@ void AudioCollection::AddToCollection(char* otrPath, uint16_t seqNum) { type = SEQ_FANFARE; } SequenceInfo info = {seqNum, - sequenceName, + sequenceName.c_str(), StringHelper::Replace(StringHelper::Replace(StringHelper::Replace(sequenceName, " ", "_"), "~", "-"),".", ""), type, false, true}; sequenceMap.emplace(seqNum, info); @@ -290,7 +290,7 @@ bool AudioCollection::HasSequenceNum(uint16_t seqId) { const char* AudioCollection::GetSequenceName(uint16_t seqId) { auto seqIt = sequenceMap.find(seqId); if (seqIt != sequenceMap.end()) { - return seqIt->second.label.c_str(); + return seqIt->second.label; } return nullptr; } diff --git a/mm/2s2h/Enhancements/Audio/AudioCollection.h b/mm/2s2h/Enhancements/Audio/AudioCollection.h index f3fd964bc0..9f4f9f3002 100644 --- a/mm/2s2h/Enhancements/Audio/AudioCollection.h +++ b/mm/2s2h/Enhancements/Audio/AudioCollection.h @@ -23,7 +23,7 @@ enum SeqType { struct SequenceInfo { uint16_t sequenceId; - std::string label; + const char* label; std::string sfxKey; SeqType category; bool canBeReplaced; diff --git a/mm/2s2h/Enhancements/Audio/AudioEditor.cpp b/mm/2s2h/Enhancements/Audio/AudioEditor.cpp index 6618a816c9..4f6cc9adf8 100644 --- a/mm/2s2h/Enhancements/Audio/AudioEditor.cpp +++ b/mm/2s2h/Enhancements/Audio/AudioEditor.cpp @@ -4,7 +4,6 @@ #include #include #include -#include #include #include //#include "../randomizer/3drando/random.hpp" @@ -165,23 +164,23 @@ extern "C" void Audio_ForceRestorePreviousBgm(void); extern "C" void PreviewSequence(u16 seqId); -void DrawPreviewButton(uint16_t sequenceId, std::string sfxKey, SeqType sequenceType) { +void AudioEditor::DrawPreviewButton(uint16_t sequenceId, std::string sfxKey, SeqType sequenceType) { const std::string cvarKey = AudioCollection::Instance->GetCvarKey(sfxKey); const std::string hiddenKey = "##" + cvarKey; const std::string stopButton = ICON_FA_STOP + hiddenKey; const std::string previewButton = ICON_FA_PLAY + hiddenKey; - if (CVarGetInteger(CVAR_AUDIO("Playing"), 0) == sequenceId) { + if (mPlayingSeq == sequenceId) { if (ImGui::Button(stopButton.c_str())) { Audio_ForceRestorePreviousBgm(); - CVarSetInteger(CVAR_AUDIO("Playing"), 0); + mPlayingSeq = 0; } UIWidgets::Tooltip("Stop Preview"); } else { if (ImGui::Button(previewButton.c_str())) { - if (CVarGetInteger(CVAR_AUDIO("Playing"), 0) != 0) { + if (mPlayingSeq != 0) { Audio_ForceRestorePreviousBgm(); - CVarSetInteger(CVAR_AUDIO("Playing"), 0); + mPlayingSeq = 0; } else { if (sequenceType == SEQ_SFX || sequenceType == SEQ_VOICE) { AudioSfx_PlaySfx(sequenceId, &gZeroVec3f, 4, &gSfxDefaultFreqAndVolScale, @@ -192,7 +191,7 @@ void DrawPreviewButton(uint16_t sequenceId, std::string sfxKey, SeqType sequence } else { // TODO: Cant do both here, so have to click preview button twice PreviewSequence(sequenceId); - CVarSetInteger(CVAR_AUDIO("Playing"), sequenceId); + mPlayingSeq = sequenceId; } } } @@ -202,7 +201,7 @@ void DrawPreviewButton(uint16_t sequenceId, std::string sfxKey, SeqType sequence #define CVAR_AUDIO(var) CVAR_PREFIX_AUDIO "." var -void Draw_SfxTab(const std::string& tabId, SeqType type) { +void AudioEditor::Draw_SfxTab(const std::string& tabId, SeqType type) { const std::map& map = AudioCollection::Instance->GetAllSequences(); const std::string hiddenTabId = "##" + tabId; @@ -279,18 +278,18 @@ void Draw_SfxTab(const std::string& tabId, SeqType type) { ImGui::TableNextRow(); ImGui::TableNextColumn(); - ImGui::Text("%s", seqData.label.c_str()); + ImGui::Text("%s", seqData.label); ImGui::TableNextColumn(); ImGui::PushItemWidth(-FLT_MIN); const int initialValue = map.contains(currentValue) ? currentValue : defaultValue; - if (ImGui::BeginCombo(hiddenKey.c_str(), map.at(initialValue).label.c_str())) { + if (ImGui::BeginCombo(hiddenKey.c_str(), map.at(initialValue).label)) { for (const auto& [value, seqData] : map) { // If excluded as a replacement sequence, don't show in other dropdowns except the effect's own dropdown. if (~(seqData.category) & type || (!seqData.canBeUsedAsReplacement && initialSfxKey != seqData.sfxKey)) { continue; } - if (ImGui::Selectable(seqData.label.c_str())) { + if (ImGui::Selectable(seqData.label)) { CVarSetInteger(cvarKey.c_str(), value); Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); UpdateCurrentBGM(defaultValue, type); @@ -357,7 +356,7 @@ extern "C" u16 AudioEditor_GetReplacementSeq(u16 seqId) { return AudioCollection::Instance->GetReplacementSequence(seqId); } -std::string GetSequenceTypeName(SeqType type) { +const char* GetSequenceTypeName(SeqType type) { switch (type) { case SEQ_NOSHUFFLE: return "No Shuffle"; @@ -414,7 +413,7 @@ ImVec4 GetSequenceTypeColor(SeqType type) { void DrawTypeChip(SeqType type) { ImGui::BeginDisabled(); ImGui::PushStyleColor(ImGuiCol_Button, GetSequenceTypeColor(type)); - ImGui::SmallButton(GetSequenceTypeName(type).c_str()); + ImGui::SmallButton(GetSequenceTypeName(type)); ImGui::PopStyleColor(); ImGui::EndDisabled(); } @@ -497,6 +496,9 @@ void AudioEditor::DrawElement() { static ImVec2 cellPadding(8.0f, 8.0f); if (ImGui::BeginTabItem("Options")) { + // BENTODO implement this + ImGui::Text("TODO: Implement this"); + #if 0 ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, cellPadding); ImGui::BeginTable("Options", 1, ImGuiTableFlags_SizingStretchSame); ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthStretch); @@ -508,13 +510,6 @@ void AudioEditor::DrawElement() { UIWidgets::Tooltip( "Disables the music change when getting close to enemies. Useful for hearing " "your custom music for each scene more often."); - //UIWidgets::CVarCheckbox("Disable Leading Music in Lost Woods", CVAR_AUDIO("LostWoodsConsistentVolume")); - //UIWidgets::Tooltip( - // "Disables the volume shifting in the Lost Woods. Useful for hearing " - // "your custom music in the Lost Woods if you don't need the navigation assitance " - // "the volume changing provides. If toggling this while in the Lost Woods, reload " - // "the area for the effect to kick in." - //); UIWidgets::CVarCheckbox("Display Sequence Name on Overlay", CVAR_AUDIO("SeqNameOverlay")); UIWidgets::Tooltip( "Displays the name of the current sequence in the corner of the screen whenever a new sequence " @@ -528,8 +523,7 @@ void AudioEditor::DrawElement() { ImGui::PopItemWidth(); UIWidgets::CVarSliderFloat("Link's voice pitch multiplier: %.1f %%", CVAR_AUDIO("LinkVoiceFreqMultiplier"), 0.4, 2.5, 1.0); ImGui::SameLine(); - const std::string resetButton = "Reset##linkVoiceFreqMultiplier"; - if (ImGui::Button(resetButton.c_str())) { + if (ImGui::Button("Reset##linkVoiceFreqMultiplier")) { CVarSetFloat(CVAR_AUDIO("LinkVoiceFreqMultiplier"), 1.0f); Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); } @@ -548,6 +542,7 @@ void AudioEditor::DrawElement() { "couple of octaves so they can still harmonize with the other notes of the " "sequence."); ImGui::PopItemWidth(); + #endif } ImGui::EndChild(); ImGui::EndTable(); @@ -583,7 +578,7 @@ void AudioEditor::DrawElement() { ImGui::SameLine(); if (ImGui::Button("Exclude All")) { for (auto seqInfo : AudioCollection::Instance->GetIncludedSequences()) { - if (sequenceSearch.PassFilter(seqInfo->label.c_str()) && showType[seqInfo->category]) { + if (sequenceSearch.PassFilter(seqInfo->label) && showType[seqInfo->category]) { seqsToExclude.insert(seqInfo); } } @@ -591,7 +586,7 @@ void AudioEditor::DrawElement() { ImGui::SameLine(); if (ImGui::Button("Include All")) { for (auto seqInfo : AudioCollection::Instance->GetExcludedSequences()) { - if (sequenceSearch.PassFilter(seqInfo->label.c_str()) && showType[seqInfo->category]) { + if (sequenceSearch.PassFilter(seqInfo->label) && showType[seqInfo->category]) { seqsToInclude.insert(seqInfo); } } @@ -601,47 +596,47 @@ void AudioEditor::DrawElement() { ImGui::TableNextColumn(); ImGui::PushStyleColor(ImGuiCol_Header, GetSequenceTypeColor(SEQ_BGM_WORLD)); - ImGui::Selectable(GetSequenceTypeName(SEQ_BGM_WORLD).c_str(), &showType[SEQ_BGM_WORLD]); + ImGui::Selectable(GetSequenceTypeName(SEQ_BGM_WORLD), &showType[SEQ_BGM_WORLD]); ImGui::PopStyleColor(1); ImGui::TableNextColumn(); ImGui::PushStyleColor(ImGuiCol_Header, GetSequenceTypeColor(SEQ_BGM_EVENT)); - ImGui::Selectable(GetSequenceTypeName(SEQ_BGM_EVENT).c_str(), &showType[SEQ_BGM_EVENT]); + ImGui::Selectable(GetSequenceTypeName(SEQ_BGM_EVENT), &showType[SEQ_BGM_EVENT]); ImGui::PopStyleColor(1); ImGui::TableNextColumn(); ImGui::PushStyleColor(ImGuiCol_Header, GetSequenceTypeColor(SEQ_BGM_BATTLE)); - ImGui::Selectable(GetSequenceTypeName(SEQ_BGM_BATTLE).c_str(), &showType[SEQ_BGM_BATTLE]); + ImGui::Selectable(GetSequenceTypeName(SEQ_BGM_BATTLE), &showType[SEQ_BGM_BATTLE]); ImGui::PopStyleColor(1); ImGui::TableNextColumn(); ImGui::PushStyleColor(ImGuiCol_Header, GetSequenceTypeColor(SEQ_OCARINA)); - ImGui::Selectable(GetSequenceTypeName(SEQ_OCARINA).c_str(), &showType[SEQ_OCARINA]); + ImGui::Selectable(GetSequenceTypeName(SEQ_OCARINA), &showType[SEQ_OCARINA]); ImGui::PopStyleColor(1); ImGui::TableNextColumn(); ImGui::PushStyleColor(ImGuiCol_Header, GetSequenceTypeColor(SEQ_FANFARE)); - ImGui::Selectable(GetSequenceTypeName(SEQ_FANFARE).c_str(), &showType[SEQ_FANFARE]); + ImGui::Selectable(GetSequenceTypeName(SEQ_FANFARE), &showType[SEQ_FANFARE]); ImGui::PopStyleColor(1); ImGui::TableNextColumn(); ImGui::PushStyleColor(ImGuiCol_Header, GetSequenceTypeColor(SEQ_SFX)); - ImGui::Selectable(GetSequenceTypeName(SEQ_SFX).c_str(), &showType[SEQ_SFX]); + ImGui::Selectable(GetSequenceTypeName(SEQ_SFX), &showType[SEQ_SFX]); ImGui::PopStyleColor(1); ImGui::TableNextColumn(); ImGui::PushStyleColor(ImGuiCol_Header, GetSequenceTypeColor(SEQ_VOICE)); - ImGui::Selectable(GetSequenceTypeName(SEQ_VOICE).c_str(), &showType[SEQ_VOICE]); + ImGui::Selectable(GetSequenceTypeName(SEQ_VOICE), &showType[SEQ_VOICE]); ImGui::PopStyleColor(1); ImGui::TableNextColumn(); ImGui::PushStyleColor(ImGuiCol_Header, GetSequenceTypeColor(SEQ_INSTRUMENT)); - ImGui::Selectable(GetSequenceTypeName(SEQ_INSTRUMENT).c_str(), &showType[SEQ_INSTRUMENT]); + ImGui::Selectable(GetSequenceTypeName(SEQ_INSTRUMENT), &showType[SEQ_INSTRUMENT]); ImGui::PopStyleColor(1); ImGui::TableNextColumn(); ImGui::PushStyleColor(ImGuiCol_Header, GetSequenceTypeColor(SEQ_BGM_CUSTOM)); - ImGui::Selectable(GetSequenceTypeName(SEQ_BGM_CUSTOM).c_str(), &showType[SEQ_BGM_CUSTOM]); + ImGui::Selectable(GetSequenceTypeName(SEQ_BGM_CUSTOM), &showType[SEQ_BGM_CUSTOM]); ImGui::PopStyleColor(1); ImGui::EndTable(); @@ -657,7 +652,7 @@ void AudioEditor::DrawElement() { ImGui::BeginChild("ChildIncludedSequences", ImVec2(0, -8)); for (auto seqInfo : AudioCollection::Instance->GetIncludedSequences()) { - if (sequenceSearch.PassFilter(seqInfo->label.c_str()) && showType[seqInfo->category]) { + if (sequenceSearch.PassFilter(seqInfo->label) && showType[seqInfo->category]) { if (ImGui::Button(std::string(ICON_FA_TIMES "##" + seqInfo->sfxKey).c_str())) { seqsToExclude.insert(seqInfo); } @@ -666,7 +661,7 @@ void AudioEditor::DrawElement() { ImGui::SameLine(); DrawTypeChip(seqInfo->category); ImGui::SameLine(); - ImGui::Text("%s", seqInfo->label.c_str()); + ImGui::Text("%s", seqInfo->label); } } ImGui::EndChild(); @@ -681,7 +676,7 @@ void AudioEditor::DrawElement() { ImGui::BeginChild("ChildExcludedSequences", ImVec2(0, -8)); for (auto seqInfo : AudioCollection::Instance->GetExcludedSequences()) { - if (sequenceSearch.PassFilter(seqInfo->label.c_str()) && showType[seqInfo->category]) { + if (sequenceSearch.PassFilter(seqInfo->label) && showType[seqInfo->category]) { if (ImGui::Button(std::string(ICON_FA_PLUS "##" + seqInfo->sfxKey).c_str())) { seqsToInclude.insert(seqInfo); } @@ -690,7 +685,7 @@ void AudioEditor::DrawElement() { ImGui::SameLine(); DrawTypeChip(seqInfo->category); ImGui::SameLine(); - ImGui::Text("%s", seqInfo->label.c_str()); + ImGui::Text("%s", seqInfo->label); } } ImGui::EndChild(); diff --git a/mm/2s2h/Enhancements/Audio/AudioEditor.h b/mm/2s2h/Enhancements/Audio/AudioEditor.h index 93ae3c4406..768cc9e122 100644 --- a/mm/2s2h/Enhancements/Audio/AudioEditor.h +++ b/mm/2s2h/Enhancements/Audio/AudioEditor.h @@ -1,14 +1,15 @@ #pragma once #include "stdint.h" +#include "libultraship/libultra/types.h" #ifdef __cplusplus +#include "window/gui/Gui.h" +#include "window/gui/GuiWindow.h" +#include "AudioCollection.h" -#include #ifndef IMGUI_DEFINE_MATH_OPERATORS #define IMGUI_DEFINE_MATH_OPERATORS #endif -#include -#include "AudioCollection.h" class AudioEditor : public Ship::GuiWindow { public: @@ -18,6 +19,11 @@ class AudioEditor : public Ship::GuiWindow { void InitElement() override; void UpdateElement() override {}; ~AudioEditor() {}; + + private: + void DrawPreviewButton(uint16_t sequenceId, std::string sfxKey, SeqType sequenceType); + void Draw_SfxTab(const std::string& tabId, SeqType type); + uint16_t mPlayingSeq = 0; }; void AudioEditor_RandomizeAll(); From a6fae26b5784a1ee41979ddbd8c87fba03ba4bf5 Mon Sep 17 00:00:00 2001 From: louist103 <35883445+louist103@users.noreply.github.com> Date: Fri, 28 Jun 2024 15:09:22 -0400 Subject: [PATCH 05/25] Oops --- mm/2s2h/Enhancements/Audio/AudioEditor.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mm/2s2h/Enhancements/Audio/AudioEditor.cpp b/mm/2s2h/Enhancements/Audio/AudioEditor.cpp index 4f6cc9adf8..c9ad1df7e9 100644 --- a/mm/2s2h/Enhancements/Audio/AudioEditor.cpp +++ b/mm/2s2h/Enhancements/Audio/AudioEditor.cpp @@ -542,12 +542,12 @@ void AudioEditor::DrawElement() { "couple of octaves so they can still harmonize with the other notes of the " "sequence."); ImGui::PopItemWidth(); - #endif - } ImGui::EndChild(); ImGui::EndTable(); ImGui::PopStyleVar(1); ImGui::EndTabItem(); + } + #endif } static bool excludeTabOpen = false; From eb9f3e6fbedab9e9205de4ba45026ffef1b4ecb6 Mon Sep 17 00:00:00 2001 From: louist103 <35883445+louist103@users.noreply.github.com> Date: Sat, 29 Jun 2024 23:57:48 -0400 Subject: [PATCH 06/25] WIP ZIP Sound fonts --- OTRExporter | 2 +- mm/2s2h/BenPort.cpp | 6 + .../importer/AudioSoundFontFactory.cpp | 229 ++++++++++++++++++ .../resource/importer/AudioSoundFontFactory.h | 16 ++ 4 files changed, 252 insertions(+), 1 deletion(-) diff --git a/OTRExporter b/OTRExporter index 375489d5f1..b37fed44e0 160000 --- a/OTRExporter +++ b/OTRExporter @@ -1 +1 @@ -Subproject commit 375489d5f1f5fa4b9144ac9669c411e3b149f32a +Subproject commit b37fed44e0e826dbc82be80257f2c3b2ebbaae81 diff --git a/mm/2s2h/BenPort.cpp b/mm/2s2h/BenPort.cpp index f15c865730..75a24b54eb 100644 --- a/mm/2s2h/BenPort.cpp +++ b/mm/2s2h/BenPort.cpp @@ -207,9 +207,15 @@ OTRGlobals::OTRGlobals() { "TextMM", static_cast(SOH::ResourceType::TSH_TextMM), 0); loader->RegisterResourceFactory(std::make_shared(), RESOURCE_FORMAT_BINARY, "AudioSample", static_cast(SOH::ResourceType::SOH_AudioSample), 2); + loader->RegisterResourceFactory(std::make_shared(), RESOURCE_FORMAT_BINARY, "AudioSoundFont", static_cast(SOH::ResourceType::SOH_AudioSoundFont), 2); + loader->RegisterResourceFactory(std::make_shared(), + RESOURCE_FORMAT_XML, "SoundFont", + static_cast(SOH::ResourceType::SOH_AudioSoundFont), 0); + + loader->RegisterResourceFactory(std::make_shared(), RESOURCE_FORMAT_BINARY, "AudioSequence", static_cast(SOH::ResourceType::SOH_AudioSequence), 2); diff --git a/mm/2s2h/resource/importer/AudioSoundFontFactory.cpp b/mm/2s2h/resource/importer/AudioSoundFontFactory.cpp index 39cc6f47fe..0e2698a6ce 100644 --- a/mm/2s2h/resource/importer/AudioSoundFontFactory.cpp +++ b/mm/2s2h/resource/importer/AudioSoundFontFactory.cpp @@ -2,6 +2,7 @@ #include "2s2h/resource/type/AudioSoundFont.h" #include "spdlog/spdlog.h" #include "libultraship/libultraship.h" +#include "StringHelper.h" namespace SOH { std::shared_ptr ResourceFactoryBinaryAudioSoundFontV2::ReadResource(std::shared_ptr file) { @@ -172,4 +173,232 @@ std::shared_ptr ResourceFactoryBinaryAudioSoundFontV2::ReadReso return audioSoundFont; } + +int8_t SOH::ResourceFactoryXMLSoundFontV0::MediumStrToInt(const char* str) { + if (!strcmp("Ram", str)) { + return 0; + } else if (!strcmp("Unk", str)) { + return 1; + } else if (!strcmp("Cart", str)) { + return 2; + } else if (!strcmp("Disk", str)) { + return 3; + //4 is skipped + } else if (!strcmp("RamUnloaded", str)) { + return 5; + } else { + throw std::runtime_error(StringHelper::Sprintf("Bad medium value. Got %s, expected Ram, Unk, Cart, or Disk.", str)); + } +} + +int8_t ResourceFactoryXMLSoundFontV0::CachePolicyToInt(const char* str) { + if (!strcmp("Temporary", str)) { + return 0; + } else if (!strcmp("Persistent", str)) { + return 1; + } else if (!strcmp("Either", str)) { + return 2; + } else if (!strcmp("Permanent", str)) { + return 3; + } else { + throw std::runtime_error( + StringHelper::Sprintf("Bad cache policy value. Got %s, expected Temporary, Persistent, Either, or Permanent.", str)); + } +} + +void ResourceFactoryXMLSoundFontV0::ParseDrums(AudioSoundFont* soundFont, tinyxml2::XMLElement* element) { + element = (tinyxml2::XMLElement*)element->FirstChildElement(); + // No drums + if (element == nullptr) { + soundFont->soundFont.drums = nullptr; + soundFont->soundFont.numDrums = 0; + return; + } + do { + Drum drum; + std::vector envelopes; + drum.releaseRate = element->IntAttribute("ReleaseRate"); + drum.pan = element->IntAttribute("Pan"); + drum.loaded = element->IntAttribute("Loaded"); + drum.sound.tuning = element->FloatAttribute("Tuning"); + const char* sampleStr = element->Attribute("SampleRef"); + if (sampleStr != nullptr && sampleStr[0] != 0) { + auto res = Ship::Context::GetInstance()->GetResourceManager()->LoadResourceProcess(sampleStr); + drum.sound.sample = static_cast(res ? res->GetRawPointer() : nullptr); + } else { + drum.sound.sample = nullptr; + } + element = (tinyxml2::XMLElement*)element->FirstChildElement(); + if (!strcmp(element->Name(), "Envelopes")) { + //element = (tinyxml2::XMLElement*)element->FirstChildElement(); + unsigned int envCount = 0; + envelopes = ParseEnvelopes(soundFont, element, &envCount); + element = (tinyxml2::XMLElement*)element->Parent(); + soundFont->drumEnvelopeArrays.push_back(envelopes); + drum.envelope = soundFont->drumEnvelopeArrays.back().data(); + } else { + drum.envelope = nullptr; + } + + // BENTODO the binary importer does this not sure why... @jack or @kenix? + soundFont->drums.push_back(drum); + //if (drum.sound.sample == nullptr) { + // soundFont->drumAddresses.push_back(nullptr); + //} else { + soundFont->drumAddresses.push_back(&soundFont->drums.back()); + //} + + + element = element->NextSiblingElement(); + } while (element != nullptr); + soundFont->soundFont.numDrums = soundFont->drumAddresses.size(); + soundFont->soundFont.drums = soundFont->drumAddresses.data(); +} + + void SOH::ResourceFactoryXMLSoundFontV0::ParseInstruments(AudioSoundFont* soundFont, tinyxml2::XMLElement* element) { + element = element->FirstChildElement(); + do { + Instrument instrument = { 0 }; + unsigned int envCount = 0; + std::vector envelopes; + + int isValid = element->BoolAttribute("IsValid"); + instrument.loaded = element->IntAttribute("Loaded"); + instrument.normalRangeLo = element->IntAttribute("NormalRangeLo"); + instrument.normalRangeHi = element->IntAttribute("NormalRangeHi"); + instrument.releaseRate = element->IntAttribute("ReleseRate"); // BENTODO fix the spelling + tinyxml2::XMLElement* instrumentElement = element->FirstChildElement(); + tinyxml2::XMLElement* instrumentElementCopy = instrumentElement; + if (instrumentElement != nullptr && !strcmp(instrumentElement->Name(), "Envelopes")) { + envelopes = ParseEnvelopes(soundFont, instrumentElement, &envCount); + soundFont->instrumentEnvelopeCounts.push_back(envCount); + soundFont->instrumentEnvelopeArrays.push_back(envelopes); + instrument.envelope = soundFont->instrumentEnvelopeArrays.back().data(); + instrumentElement = instrumentElement->NextSiblingElement(); + } + if (instrumentElement != nullptr && !strcmp("LowNotesSound", instrumentElement->Name())) { + instrument.lowNotesSound.tuning = instrumentElement->FloatAttribute("Tuning"); + const char* sampleStr = instrumentElement->Attribute("SampleRef"); + if (sampleStr != nullptr && sampleStr[0] != 0) { + auto res = Ship::Context::GetInstance()->GetResourceManager()->LoadResourceProcess(sampleStr); + instrument.lowNotesSound.sample = static_cast(res ? res->GetRawPointer() : nullptr); + } + instrumentElement = instrumentElement->NextSiblingElement(); + } + if (instrumentElement != nullptr && !strcmp("NormalNotesSound", instrumentElement->Name())) { + instrument.normalNotesSound.tuning = instrumentElement->FloatAttribute("Tuning"); + const char* sampleStr = instrumentElement->Attribute("SampleRef"); + if (sampleStr != nullptr && sampleStr[0] != 0) { + auto res = Ship::Context::GetInstance()->GetResourceManager()->LoadResourceProcess(sampleStr); + instrument.normalNotesSound.sample = static_cast(res ? res->GetRawPointer() : nullptr); + } + instrumentElement = instrumentElement->NextSiblingElement(); + } + if (instrumentElement != nullptr && !strcmp("HighNotesSound", instrumentElement->Name())) { + instrument.highNotesSound.tuning = instrumentElement->FloatAttribute("Tuning"); + const char* sampleStr = instrumentElement->Attribute("SampleRef"); + if (sampleStr != nullptr && sampleStr[0] != 0) { + auto res = Ship::Context::GetInstance()->GetResourceManager()->LoadResourceProcess(sampleStr); + instrument.highNotesSound.sample = static_cast(res ? res->GetRawPointer() : nullptr); + } + instrumentElement = instrumentElement->NextSiblingElement(); + } + element = instrumentElementCopy; + element = (tinyxml2::XMLElement*)element->Parent(); + element = element->NextSiblingElement(); + soundFont->instruments.push_back(instrument); + soundFont->instrumentAddresses.push_back(isValid ? &soundFont->instruments.back() : nullptr); + } while (element != nullptr); + soundFont->soundFont.instruments = soundFont->instrumentAddresses.data(); + soundFont->soundFont.numInstruments = soundFont->instrumentAddresses.size(); + } + + +void SOH::ResourceFactoryXMLSoundFontV0::ParseSfxTable(AudioSoundFont* soundFont, tinyxml2::XMLElement* element) { + element = (tinyxml2::XMLElement*)element->FirstChildElement(); + + while (element != nullptr) { + SoundFontSound sound; + sound.tuning = element->FloatAttribute("Tuning"); + const char* sampleStr = element->Attribute("SampleRef"); + if (sampleStr != nullptr && sampleStr[0] != 0) { + auto res = Ship::Context::GetInstance()->GetResourceManager()->LoadResourceProcess(sampleStr); + sound.sample = static_cast(res ? res->GetRawPointer() : nullptr); + } + element = element->NextSiblingElement(); + soundFont->soundEffects.push_back(sound); + } + soundFont->soundFont.soundEffects = soundFont->soundEffects.data(); + soundFont->soundFont.numSfx = soundFont->soundEffects.size(); +} + + + std::vector SOH::ResourceFactoryXMLSoundFontV0::ParseEnvelopes(AudioSoundFont* soundFont, + tinyxml2::XMLElement* element, + unsigned int* count) { + std::vector envelopes; + unsigned int total = 0; + element = element->FirstChildElement("Envelope"); + + while (element != nullptr) { + AdsrEnvelope env = { + .delay = (s16)element->IntAttribute("Delay"), + .arg = (s16)element->IntAttribute("Arg"), + }; + env.delay = BSWAP16(env.delay); + env.arg = BSWAP16(env.arg); + envelopes.emplace_back(env); + element = element->NextSiblingElement("Envelope"); + total++; + } + *count = total; + return envelopes; +} + +std::shared_ptr ResourceFactoryXMLSoundFontV0::ReadResource(std::shared_ptr file) { + if (!FileHasValidFormatAndReader(file)) { + return nullptr; + } + + auto audioSoundFont = std::make_shared(file->InitData); + auto child = + std::get>(file->Reader)->FirstChildElement(); + // Header data + memset(&audioSoundFont->soundFont, 0, sizeof(audioSoundFont->soundFont)); + audioSoundFont->soundFont.fntIndex = child->IntAttribute("Num", 0); + if (audioSoundFont->soundFont.fntIndex == 1) { + int bp = 0; + } + const char* mediumStr = child->Attribute("Medium"); + audioSoundFont->medium = MediumStrToInt(mediumStr); + + const char* cachePolicyStr = child->Attribute("CachePolicy"); + audioSoundFont->cachePolicy = CachePolicyToInt(cachePolicyStr); + + audioSoundFont->data1 = child->IntAttribute("Data1"); + audioSoundFont->data2 = child->IntAttribute("Data2"); + audioSoundFont->data3 = child->IntAttribute("Data3"); + + audioSoundFont->soundFont.sampleBankId1 = audioSoundFont->data1 >> 8; + audioSoundFont->soundFont.sampleBankId2 = audioSoundFont->data1 & 0xFF; + + child = (tinyxml2::XMLElement*)child->FirstChildElement(); + + while (child != nullptr) { + const char* name = child->Name(); + + if (!strcmp(name, "Drums")) { + ParseDrums(audioSoundFont.get(), child); + } else if (!strcmp(name, "Instruments")) { + ParseInstruments(audioSoundFont.get(), child); + } else if (!strcmp(name, "SfxTable")) { + ParseSfxTable(audioSoundFont.get(), child); + } + child = child->NextSiblingElement(); + } + + return audioSoundFont; +} + + } // namespace SOH diff --git a/mm/2s2h/resource/importer/AudioSoundFontFactory.h b/mm/2s2h/resource/importer/AudioSoundFontFactory.h index a80b8fe972..bd3577d10e 100644 --- a/mm/2s2h/resource/importer/AudioSoundFontFactory.h +++ b/mm/2s2h/resource/importer/AudioSoundFontFactory.h @@ -2,10 +2,26 @@ #include "Resource.h" #include "ResourceFactoryBinary.h" +#include "ResourceFactoryXML.h" +#include "resource/type/AudioSoundFont.h" namespace SOH { class ResourceFactoryBinaryAudioSoundFontV2 : public Ship::ResourceFactoryBinary { public: std::shared_ptr ReadResource(std::shared_ptr file) override; }; + +class ResourceFactoryXMLSoundFontV0 : public Ship::ResourceFactoryXML { + public: + std::shared_ptr ReadResource(std::shared_ptr file) override; + + private: + int8_t MediumStrToInt(const char* str); + int8_t CachePolicyToInt(const char* str); + void ParseDrums(AudioSoundFont* soundFont, tinyxml2::XMLElement* element); + void ParseInstruments(AudioSoundFont* soundFont, tinyxml2::XMLElement* element); + void ParseSfxTable(AudioSoundFont* soundFont, tinyxml2::XMLElement* element); + std::vector ParseEnvelopes(AudioSoundFont* soundFont, tinyxml2::XMLElement* element, unsigned int* count); +}; + } // namespace SOH From 65a1405a79b87c6989fba01642679c223f237373 Mon Sep 17 00:00:00 2001 From: louist103 <35883445+louist103@users.noreply.github.com> Date: Sun, 30 Jun 2024 18:59:20 -0400 Subject: [PATCH 07/25] The game opens and audio seems to play correctly. Needs extensive testing. --- OTRExporter | 2 +- mm/2s2h/BenGui/BenGui.cpp | 1 - mm/2s2h/BenPort.cpp | 8 +- .../Enhancements/Audio/AudioCollection.cpp | 69 ++++--- mm/2s2h/Enhancements/Audio/AudioCollection.h | 86 ++++----- mm/2s2h/Enhancements/Audio/AudioEditor.cpp | 65 +++---- mm/2s2h/Enhancements/Audio/AudioEditor.h | 25 ++- mm/2s2h/mixer.c | 3 +- .../importer/AudioSoundFontFactory.cpp | 174 +++++++++--------- .../resource/importer/AudioSoundFontFactory.h | 3 +- 10 files changed, 229 insertions(+), 207 deletions(-) diff --git a/OTRExporter b/OTRExporter index b37fed44e0..89ffb98da6 160000 --- a/OTRExporter +++ b/OTRExporter @@ -1 +1 @@ -Subproject commit b37fed44e0e826dbc82be80257f2c3b2ebbaae81 +Subproject commit 89ffb98da6e5857750136efa2297793564e22dc0 diff --git a/mm/2s2h/BenGui/BenGui.cpp b/mm/2s2h/BenGui/BenGui.cpp index 44df4bccd3..22bba53d3d 100644 --- a/mm/2s2h/BenGui/BenGui.cpp +++ b/mm/2s2h/BenGui/BenGui.cpp @@ -10,7 +10,6 @@ #include "HudEditor.h" #include "../Enhancements/Audio/AudioEditor.h" - #ifdef __APPLE__ #include "graphic/Fast3D/gfx_metal.h" #endif diff --git a/mm/2s2h/BenPort.cpp b/mm/2s2h/BenPort.cpp index 75a24b54eb..9175310181 100644 --- a/mm/2s2h/BenPort.cpp +++ b/mm/2s2h/BenPort.cpp @@ -207,14 +207,12 @@ OTRGlobals::OTRGlobals() { "TextMM", static_cast(SOH::ResourceType::TSH_TextMM), 0); loader->RegisterResourceFactory(std::make_shared(), RESOURCE_FORMAT_BINARY, "AudioSample", static_cast(SOH::ResourceType::SOH_AudioSample), 2); - + loader->RegisterResourceFactory(std::make_shared(), RESOURCE_FORMAT_BINARY, "AudioSoundFont", static_cast(SOH::ResourceType::SOH_AudioSoundFont), 2); - loader->RegisterResourceFactory(std::make_shared(), - RESOURCE_FORMAT_XML, "SoundFont", - static_cast(SOH::ResourceType::SOH_AudioSoundFont), 0); - + loader->RegisterResourceFactory(std::make_shared(), RESOURCE_FORMAT_XML, + "SoundFont", static_cast(SOH::ResourceType::SOH_AudioSoundFont), 0); loader->RegisterResourceFactory(std::make_shared(), RESOURCE_FORMAT_BINARY, "AudioSequence", diff --git a/mm/2s2h/Enhancements/Audio/AudioCollection.cpp b/mm/2s2h/Enhancements/Audio/AudioCollection.cpp index 5a246fa4d9..4694cce9b2 100644 --- a/mm/2s2h/Enhancements/Audio/AudioCollection.cpp +++ b/mm/2s2h/Enhancements/Audio/AudioCollection.cpp @@ -10,26 +10,32 @@ #include #define SEQUENCE_MAP_ENTRY(sequenceId, label, sfxKey, category, canBeReplaced, canBeUsedAsReplacement) \ - { sequenceId, { sequenceId, label, sfxKey, category, canBeReplaced, canBeUsedAsReplacement } } + { \ + sequenceId, { \ + sequenceId, label, sfxKey, category, canBeReplaced, canBeUsedAsReplacement \ + } \ + } AudioCollection::AudioCollection() { - // (originalSequenceId, label, sfxKey, category, canBeReplaced, canBeUsedAsReplacement), - sequenceMap = { + // (originalSequenceId, label, sfxKey, + // category, canBeReplaced, canBeUsedAsReplacement), + sequenceMap = { SEQUENCE_MAP_ENTRY(NA_BGM_GENERAL_SFX, "General SFX", "SEQUENCE_MAP_ENTRY", SEQ_BGM_WORLD, true, true), - SEQUENCE_MAP_ENTRY(NA_BGM_AMBIENCE, "Ambience", "NA_BGM_AMBIENCE", SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_AMBIENCE, "Ambience", "NA_BGM_AMBIENCE", SEQ_BGM_WORLD, true, true), SEQUENCE_MAP_ENTRY(NA_BGM_TERMINA_FIELD, "Termina Field", "NA_BGM_TERMINA_FIELD", SEQ_BGM_WORLD, true, true), SEQUENCE_MAP_ENTRY(NA_BGM_CHASE, "Chase", "NA_BGM_CHASE", SEQ_BGM_WORLD, true, true), SEQUENCE_MAP_ENTRY(NA_BGM_MAJORAS_THEME, "Majoras Theme", "NA_BGM_MAJORAS_THEME", SEQ_BGM_WORLD, true, true), SEQUENCE_MAP_ENTRY(NA_BGM_CLOCK_TOWER, "Clock Tower", "NA_BGM_CLOCK_TOWER", SEQ_BGM_WORLD, true, true), - SEQUENCE_MAP_ENTRY(NA_BGM_STONE_TOWER_TEMPLE, "Stone Tower Temple", "NA_BGM_STONE_TOWER_TEMPLE", SEQ_BGM_WORLD, true, - true), - SEQUENCE_MAP_ENTRY(NA_BGM_INV_STONE_TOWER_TEMPLE, "Inverted Stone Tower Temple", "NA_BGM_INV_STONE_TOWER_TEMPLE", SEQ_BGM_WORLD, true, - true), + SEQUENCE_MAP_ENTRY(NA_BGM_STONE_TOWER_TEMPLE, "Stone Tower Temple", "NA_BGM_STONE_TOWER_TEMPLE", SEQ_BGM_WORLD, + true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_INV_STONE_TOWER_TEMPLE, "Inverted Stone Tower Temple", + "NA_BGM_INV_STONE_TOWER_TEMPLE", SEQ_BGM_WORLD, true, true), SEQUENCE_MAP_ENTRY(NA_BGM_FAILURE_0, "Missed Event 0", "NA_BGM_FAILURE_0", SEQ_BGM_WORLD, true, true), SEQUENCE_MAP_ENTRY(NA_BGM_FAILURE_1, "Missed Event 1", "NA_BGM_FAILURE_1", SEQ_BGM_WORLD, true, true), - SEQUENCE_MAP_ENTRY(NA_BGM_HAPPY_MASK_SALESMAN, "Happy Mask Salesman's Theme", "NA_BGM_HAPPY_MASK_SALESMAN", SEQ_BGM_WORLD, true, + SEQUENCE_MAP_ENTRY(NA_BGM_HAPPY_MASK_SALESMAN, "Happy Mask Salesman's Theme", "NA_BGM_HAPPY_MASK_SALESMAN", + SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_SONG_OF_HEALING, "Song Of Healing", "NA_BGM_SONG_OF_HEALING", SEQ_BGM_WORLD, true, true), - SEQUENCE_MAP_ENTRY(NA_BGM_SONG_OF_HEALING, "Song Of Healing", "NA_BGM_SONG_OF_HEALING", SEQ_BGM_WORLD, true, true), SEQUENCE_MAP_ENTRY(NA_BGM_SWAMP_REGION, "Southern Swamp", "NA_BGM_SWAMP_REGION", SEQ_BGM_WORLD, true, true), SEQUENCE_MAP_ENTRY(NA_BGM_ALIEN_INVASION, "Alien Invasion", "NA_BGM_ALIEN_INVASION", SEQ_BGM_WORLD, true, true), SEQUENCE_MAP_ENTRY(NA_BGM_SWAMP_CRUISE, "Boat Cruise", "NA_BGM_SWAMP_CRUISE", SEQ_BGM_WORLD, true, true), @@ -37,8 +43,10 @@ AudioCollection::AudioCollection() { SEQUENCE_MAP_ENTRY(NA_BGM_GREAT_BAY_REGION, "Great Bay", "NA_BGM_GREAT_BAY_REGION", SEQ_BGM_WORLD, true, true), SEQUENCE_MAP_ENTRY(NA_BGM_IKANA_REGION, "Ikana", "NA_BGM_IKANA_REGION", SEQ_BGM_WORLD, true, true), SEQUENCE_MAP_ENTRY(NA_BGM_DEKU_PALACE, "Deku Palace", "NA_BGM_DEKU_PALACE", SEQ_BGM_WORLD, true, true), - SEQUENCE_MAP_ENTRY(NA_BGM_MOUNTAIN_REGION, "Mountain Region", "NA_BGM_MOUNTAIN_REGION", SEQ_BGM_WORLD, true, true), - SEQUENCE_MAP_ENTRY(NA_BGM_PIRATES_FORTRESS, "Pirates Fortress", "NA_BGM_PIRATES_FORTRESS", SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_MOUNTAIN_REGION, "Mountain Region", "NA_BGM_MOUNTAIN_REGION", SEQ_BGM_WORLD, true, + true), + SEQUENCE_MAP_ENTRY(NA_BGM_PIRATES_FORTRESS, "Pirates Fortress", "NA_BGM_PIRATES_FORTRESS", SEQ_BGM_WORLD, true, + true), SEQUENCE_MAP_ENTRY(NA_BGM_CLOCK_TOWN_DAY_1, "Clock Town Day 1", "NA_BGM_CLOCK_TOWN_DAY_1", SEQ_BGM_WORLD, true, true), SEQUENCE_MAP_ENTRY(NA_BGM_CLOCK_TOWN_DAY_2, "Clock Town Day 2", "NA_BGM_CLOCK_TOWN_DAY_2", SEQ_BGM_WORLD, true, @@ -186,10 +194,11 @@ AudioCollection::AudioCollection() { SEQUENCE_MAP_ENTRY(NA_BGM_INTO_THE_MOON, "Enter Moon", "NA_BGM_INTO_THE_MOON", SEQ_BGM_WORLD, true, true), SEQUENCE_MAP_ENTRY(NA_BGM_GOODBYE_GIANT, "Giants Leave", "NA_BGM_GOODBYE_GIANT", SEQ_BGM_WORLD, true, true), SEQUENCE_MAP_ENTRY(NA_BGM_TATL_AND_TAEL, "Tatl & Tale", "NA_BGM_TATL_AND_TAEL", SEQ_BGM_WORLD, true, true), - SEQUENCE_MAP_ENTRY(NA_BGM_MOONS_DESTRUCTION, "Moon's Destruction", "NA_BGM_MOONS_DESTRUCTION", SEQ_BGM_WORLD, true, true), - SEQUENCE_MAP_ENTRY(NA_BGM_END_CREDITS_SECOND_HALF, "Credits (Second Half)", "NA_BGM_END_CREDITS_SECOND_HALF", SEQ_BGM_WORLD, true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_MOONS_DESTRUCTION, "Moon's Destruction", "NA_BGM_MOONS_DESTRUCTION", SEQ_BGM_WORLD, + true, true), + SEQUENCE_MAP_ENTRY(NA_BGM_END_CREDITS_SECOND_HALF, "Credits (Second Half)", "NA_BGM_END_CREDITS_SECOND_HALF", + SEQ_BGM_WORLD, true, true), }; - } #define CVAR_AUDIO(var) CVAR_PREFIX_AUDIO "." var std::string AudioCollection::GetCvarKey(std::string sfxKey) { @@ -215,10 +224,13 @@ void AudioCollection::AddToCollection(char* otrPath, uint16_t seqNum) { if (typeString == "fanfare") { type = SEQ_FANFARE; } - SequenceInfo info = {seqNum, - sequenceName.c_str(), - StringHelper::Replace(StringHelper::Replace(StringHelper::Replace(sequenceName, " ", "_"), "~", "-"),".", ""), - type, false, true}; + SequenceInfo info = { seqNum, + sequenceName.c_str(), + StringHelper::Replace( + StringHelper::Replace(StringHelper::Replace(sequenceName, " ", "_"), "~", "-"), ".", ""), + type, + false, + true }; sequenceMap.emplace(seqNum, info); } @@ -226,10 +238,11 @@ uint16_t AudioCollection::GetReplacementSequence(uint16_t seqId) { // if Hyrule Field Morning is about to play, but Hyrule Field is swapped, get the replacement sequence // for Hyrule Field instead. Otherwise, leave it alone, so that without any sfx editor modifications we will // play the normal track as usual. - - //BENTODO what did this do in ship? - //if (seqId == NA_BGM_FIELD_MORNING) { - // if (CVarGetInteger(CVAR_AUDIO("ReplacedSequences.NA_BGM_FIELD_LOGIC.value"), NA_BGM_FIELD_LOGIC) != NA_BGM_FIELD_LOGIC) { + + // BENTODO what did this do in ship? + // if (seqId == NA_BGM_FIELD_MORNING) { + // if (CVarGetInteger(CVAR_AUDIO("ReplacedSequences.NA_BGM_FIELD_LOGIC.value"), NA_BGM_FIELD_LOGIC) != + // NA_BGM_FIELD_LOGIC) { // seqId = NA_BGM_FIELD_LOGIC; // } //} @@ -264,10 +277,12 @@ void AudioCollection::AddToShufflePool(SequenceInfo* seqInfo) { } void AudioCollection::InitializeShufflePool() { - if (shufflePoolInitialized) return; - + if (shufflePoolInitialized) + return; + for (auto& [seqId, seqInfo] : sequenceMap) { - if (!seqInfo.canBeUsedAsReplacement) continue; + if (!seqInfo.canBeUsedAsReplacement) + continue; const std::string cvarKey = std::string(CVAR_AUDIO("Excluded.")) + seqInfo.sfxKey; if (CVarGetInteger(cvarKey.c_str(), 0)) { excludedSequences.insert(&seqInfo); @@ -279,7 +294,7 @@ void AudioCollection::InitializeShufflePool() { shufflePoolInitialized = true; }; -extern "C" void AudioCollection_AddToCollection(char *otrPath, uint16_t seqNum) { +extern "C" void AudioCollection_AddToCollection(char* otrPath, uint16_t seqNum) { AudioCollection::Instance->AddToCollection(otrPath, seqNum); } diff --git a/mm/2s2h/Enhancements/Audio/AudioCollection.h b/mm/2s2h/Enhancements/Audio/AudioCollection.h index 9f4f9f3002..671b303525 100644 --- a/mm/2s2h/Enhancements/Audio/AudioCollection.h +++ b/mm/2s2h/Enhancements/Audio/AudioCollection.h @@ -6,16 +6,16 @@ #include enum SeqType { - SEQ_NOSHUFFLE = 0, - SEQ_BGM_WORLD = 1 << 0, - SEQ_BGM_EVENT = 1 << 1, + SEQ_NOSHUFFLE = 0, + SEQ_BGM_WORLD = 1 << 0, + SEQ_BGM_EVENT = 1 << 1, SEQ_BGM_BATTLE = 1 << 2, - SEQ_OCARINA = 1 << 3, - SEQ_FANFARE = 1 << 4, - SEQ_BGM_ERROR = 1 << 5, - SEQ_SFX = 1 << 6, + SEQ_OCARINA = 1 << 3, + SEQ_FANFARE = 1 << 4, + SEQ_BGM_ERROR = 1 << 5, + SEQ_SFX = 1 << 6, SEQ_INSTRUMENT = 1 << 7, - SEQ_VOICE = 1 << 8, + SEQ_VOICE = 1 << 8, SEQ_BGM_CUSTOM = SEQ_BGM_WORLD | SEQ_BGM_EVENT | SEQ_BGM_BATTLE, }; @@ -31,45 +31,45 @@ struct SequenceInfo { }; class AudioCollection { - private: - // All Loaded Audio - std::map sequenceMap; - - // Sequences/SFX to include in/exclude from shuffle pool - struct compareSequenceLabel { - bool operator() (SequenceInfo* a, SequenceInfo* b) const { - return a->label < b->label; - }; - }; - std::set includedSequences; - std::set excludedSequences; - bool shufflePoolInitialized = false; + private: + // All Loaded Audio + std::map sequenceMap; - public: - static AudioCollection* Instance; - AudioCollection(); - std::map GetAllSequences() const { - return sequenceMap; - } - std::set GetIncludedSequences() const { - return includedSequences; - }; - std::set GetExcludedSequences() const { - return excludedSequences; + // Sequences/SFX to include in/exclude from shuffle pool + struct compareSequenceLabel { + bool operator()(SequenceInfo* a, SequenceInfo* b) const { + return a->label < b->label; }; - void AddToShufflePool(SequenceInfo*); - void RemoveFromShufflePool(SequenceInfo*); - void AddToCollection(char* otrPath, uint16_t seqNum); - uint16_t GetReplacementSequence(uint16_t seqId); - void InitializeShufflePool(); - const char* GetSequenceName(uint16_t seqId); - bool HasSequenceNum(uint16_t seqId); - size_t SequenceMapSize(); - std::string GetCvarKey(std::string sfxKey); - std::string GetCvarLockKey(std::string sfxKey); + }; + std::set includedSequences; + std::set excludedSequences; + bool shufflePoolInitialized = false; + + public: + static AudioCollection* Instance; + AudioCollection(); + std::map GetAllSequences() const { + return sequenceMap; + } + std::set GetIncludedSequences() const { + return includedSequences; + }; + std::set GetExcludedSequences() const { + return excludedSequences; + }; + void AddToShufflePool(SequenceInfo*); + void RemoveFromShufflePool(SequenceInfo*); + void AddToCollection(char* otrPath, uint16_t seqNum); + uint16_t GetReplacementSequence(uint16_t seqId); + void InitializeShufflePool(); + const char* GetSequenceName(uint16_t seqId); + bool HasSequenceNum(uint16_t seqId); + size_t SequenceMapSize(); + std::string GetCvarKey(std::string sfxKey); + std::string GetCvarLockKey(std::string sfxKey); }; #else -void AudioCollection_AddToCollection(char *otrPath, uint16_t seqNum); +void AudioCollection_AddToCollection(char* otrPath, uint16_t seqNum); const char* AudioCollection_GetSequenceName(uint16_t seqId); bool AudioCollection_HasSequenceNum(uint16_t seqId); size_t AudioCollection_SequenceMapSize(); diff --git a/mm/2s2h/Enhancements/Audio/AudioEditor.cpp b/mm/2s2h/Enhancements/Audio/AudioEditor.cpp index c9ad1df7e9..73b74bae00 100644 --- a/mm/2s2h/Enhancements/Audio/AudioEditor.cpp +++ b/mm/2s2h/Enhancements/Audio/AudioEditor.cpp @@ -13,7 +13,6 @@ #include "AudioCollection.h" #include "2s2h/Enhancements/GameInteractor/GameInteractor.h" - extern "C" Vec3f gZeroVec3f; extern "C" f32 gSfxDefaultFreqAndVolScale; extern "C" s8 gSfxDefaultReverb; @@ -51,7 +50,7 @@ size_t AuthenticCountBySequenceType(SeqType type) { case SEQ_VOICE: return SEQ_COUNT_VOICE; default: - return 0; + return 0; } } @@ -96,17 +95,20 @@ void RandomizeGroup(SeqType type) { } // if we didn't find any, return early without shuffling to prevent an infinite loop - if (!values.size()) return; + if (!values.size()) + return; } // BENTODO implement random - //Shuffle(values); + // Shuffle(values); for (const auto& [seqId, seqData] : AudioCollection::Instance->GetAllSequences()) { const std::string cvarKey = AudioCollection::Instance->GetCvarKey(seqData.sfxKey); const std::string cvarLockKey = AudioCollection::Instance->GetCvarLockKey(seqData.sfxKey); // don't randomize locked entries if ((seqData.category & type) && CVarGetInteger(cvarLockKey.c_str(), 0) == 0) { // Only save authentic sequence CVars - if ((((seqData.category & SEQ_BGM_CUSTOM) || seqData.category == SEQ_FANFARE) && seqData.sequenceId >= MAX_AUTHENTIC_SEQID) || seqData.canBeReplaced == false) { + if ((((seqData.category & SEQ_BGM_CUSTOM) || seqData.category == SEQ_FANFARE) && + seqData.sequenceId >= MAX_AUTHENTIC_SEQID) || + seqData.canBeReplaced == false) { continue; } const int randomValue = values.back(); @@ -163,14 +165,13 @@ void UnlockGroup(const std::map& map, SeqType type) { extern "C" void Audio_ForceRestorePreviousBgm(void); extern "C" void PreviewSequence(u16 seqId); - void AudioEditor::DrawPreviewButton(uint16_t sequenceId, std::string sfxKey, SeqType sequenceType) { const std::string cvarKey = AudioCollection::Instance->GetCvarKey(sfxKey); const std::string hiddenKey = "##" + cvarKey; const std::string stopButton = ICON_FA_STOP + hiddenKey; const std::string previewButton = ICON_FA_PLAY + hiddenKey; - if (mPlayingSeq == sequenceId) { + if (mPlayingSeq == sequenceId) { if (ImGui::Button(stopButton.c_str())) { Audio_ForceRestorePreviousBgm(); mPlayingSeq = 0; @@ -178,7 +179,7 @@ void AudioEditor::DrawPreviewButton(uint16_t sequenceId, std::string sfxKey, Seq UIWidgets::Tooltip("Stop Preview"); } else { if (ImGui::Button(previewButton.c_str())) { - if (mPlayingSeq != 0) { + if (mPlayingSeq != 0) { Audio_ForceRestorePreviousBgm(); mPlayingSeq = 0; } else { @@ -262,7 +263,9 @@ void AudioEditor::Draw_SfxTab(const std::string& tabId, SeqType type) { continue; } // Do not display custom sequences in the list - if ((((seqData.category & SEQ_BGM_CUSTOM) || seqData.category == SEQ_FANFARE) && defaultValue >= MAX_AUTHENTIC_SEQID) || seqData.canBeReplaced == false) { + if ((((seqData.category & SEQ_BGM_CUSTOM) || seqData.category == SEQ_FANFARE) && + defaultValue >= MAX_AUTHENTIC_SEQID) || + seqData.canBeReplaced == false) { continue; } @@ -284,8 +287,10 @@ void AudioEditor::Draw_SfxTab(const std::string& tabId, SeqType type) { const int initialValue = map.contains(currentValue) ? currentValue : defaultValue; if (ImGui::BeginCombo(hiddenKey.c_str(), map.at(initialValue).label)) { for (const auto& [value, seqData] : map) { - // If excluded as a replacement sequence, don't show in other dropdowns except the effect's own dropdown. - if (~(seqData.category) & type || (!seqData.canBeUsedAsReplacement && initialSfxKey != seqData.sfxKey)) { + // If excluded as a replacement sequence, don't show in other dropdowns except the effect's own + // dropdown. + if (~(seqData.category) & type || + (!seqData.canBeUsedAsReplacement && initialSfxKey != seqData.sfxKey)) { continue; } @@ -304,8 +309,10 @@ void AudioEditor::Draw_SfxTab(const std::string& tabId, SeqType type) { } ImGui::TableNextColumn(); ImGui::PushItemWidth(-FLT_MIN); - DrawPreviewButton((type == SEQ_SFX || type == SEQ_VOICE || type == SEQ_INSTRUMENT) ? defaultValue : currentValue, seqData.sfxKey, type); - auto locked = CVarGetInteger(cvarLockKey.c_str(), 0) == 1; + DrawPreviewButton((type == SEQ_SFX || type == SEQ_VOICE || type == SEQ_INSTRUMENT) ? defaultValue + : currentValue, + seqData.sfxKey, type); + auto locked = CVarGetInteger(cvarLockKey.c_str(), 0) == 1; ImGui::SameLine(); ImGui::PushItemWidth(-FLT_MIN); if (ImGui::Button(resetButton.c_str())) { @@ -334,7 +341,7 @@ void AudioEditor::Draw_SfxTab(const std::string& tabId, SeqType type) { } Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); UpdateCurrentBGM(defaultValue, type); - } + } } UIWidgets::Tooltip("Randomize this sound"); ImGui::SameLine(); @@ -418,10 +425,9 @@ void DrawTypeChip(SeqType type) { ImGui::EndDisabled(); } - void AudioEditorRegisterOnSceneInitHook() { // BENTODO implement this - //GameInteractor::Instance->RegisterGameHook([](int16_t sceneNum) { + // GameInteractor::Instance->RegisterGameHook([](int16_t sceneNum) { // if (CVarGetInteger(CVAR_AUDIO("RandomizeAllOnNewScene"), 0)) { // AudioEditor_RandomizeAll(); // } @@ -462,7 +468,6 @@ void AudioEditor::DrawElement() { } UIWidgets::Tooltip("Unlocks all music and sound effects across tab groups"); - if (ImGui::BeginTabBar("SfxContextTabBar", ImGuiTabBarFlags_NoCloseWithMiddleMouseButton)) { if (ImGui::BeginTabItem("Background Music")) { Draw_SfxTab("backgroundMusic", SEQ_BGM_WORLD); @@ -498,7 +503,7 @@ void AudioEditor::DrawElement() { if (ImGui::BeginTabItem("Options")) { // BENTODO implement this ImGui::Text("TODO: Implement this"); - #if 0 +#if 0 ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, cellPadding); ImGui::BeginTable("Options", 1, ImGuiTableFlags_SizingStretchSame); ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthStretch); @@ -547,7 +552,7 @@ void AudioEditor::DrawElement() { ImGui::PopStyleVar(1); ImGui::EndTabItem(); } - #endif +#endif } static bool excludeTabOpen = false; @@ -557,17 +562,11 @@ void AudioEditor::DrawElement() { excludeTabOpen = true; } - static std::map showType { - {SEQ_BGM_WORLD, true}, - {SEQ_BGM_EVENT, true}, - {SEQ_BGM_BATTLE, true}, - {SEQ_OCARINA, true}, - {SEQ_FANFARE, true}, - {SEQ_SFX, true }, - {SEQ_VOICE, true }, - {SEQ_INSTRUMENT, true}, - {SEQ_BGM_CUSTOM, true} - }; + static std::map showType{ { SEQ_BGM_WORLD, true }, { SEQ_BGM_EVENT, true }, + { SEQ_BGM_BATTLE, true }, { SEQ_OCARINA, true }, + { SEQ_FANFARE, true }, { SEQ_SFX, true }, + { SEQ_VOICE, true }, { SEQ_INSTRUMENT, true }, + { SEQ_BGM_CUSTOM, true } }; // make temporary sets because removing from the set we're iterating through crashes ImGui std::set seqsToInclude = {}; @@ -592,7 +591,8 @@ void AudioEditor::DrawElement() { } } - ImGui::BeginTable("sequenceTypes", 9, ImGuiTableFlags_Resizable | ImGuiTableFlags_NoSavedSettings | ImGuiTableFlags_Borders); + ImGui::BeginTable("sequenceTypes", 9, + ImGuiTableFlags_Resizable | ImGuiTableFlags_NoSavedSettings | ImGuiTableFlags_Borders); ImGui::TableNextColumn(); ImGui::PushStyleColor(ImGuiCol_Header, GetSequenceTypeColor(SEQ_BGM_WORLD)); @@ -708,7 +708,8 @@ void AudioEditor::DrawElement() { ImGui::End(); } -static constexpr std::array allTypes = { SEQ_BGM_WORLD, SEQ_BGM_EVENT, SEQ_BGM_BATTLE, SEQ_OCARINA, SEQ_FANFARE, SEQ_INSTRUMENT, SEQ_SFX, SEQ_VOICE }; +static constexpr std::array allTypes = { SEQ_BGM_WORLD, SEQ_BGM_EVENT, SEQ_BGM_BATTLE, SEQ_OCARINA, + SEQ_FANFARE, SEQ_INSTRUMENT, SEQ_SFX, SEQ_VOICE }; void AudioEditor_RandomizeAll() { for (auto type : allTypes) { diff --git a/mm/2s2h/Enhancements/Audio/AudioEditor.h b/mm/2s2h/Enhancements/Audio/AudioEditor.h index 768cc9e122..89e7c04bf9 100644 --- a/mm/2s2h/Enhancements/Audio/AudioEditor.h +++ b/mm/2s2h/Enhancements/Audio/AudioEditor.h @@ -12,18 +12,18 @@ #endif class AudioEditor : public Ship::GuiWindow { - public: - using GuiWindow::GuiWindow; - - void DrawElement() override; - void InitElement() override; - void UpdateElement() override {}; - ~AudioEditor() {}; - - private: - void DrawPreviewButton(uint16_t sequenceId, std::string sfxKey, SeqType sequenceType); - void Draw_SfxTab(const std::string& tabId, SeqType type); - uint16_t mPlayingSeq = 0; + public: + using GuiWindow::GuiWindow; + + void DrawElement() override; + void InitElement() override; + void UpdateElement() override{}; + ~AudioEditor(){}; + + private: + void DrawPreviewButton(uint16_t sequenceId, std::string sfxKey, SeqType sequenceType); + void Draw_SfxTab(const std::string& tabId, SeqType type); + uint16_t mPlayingSeq = 0; }; void AudioEditor_RandomizeAll(); @@ -38,7 +38,6 @@ extern "C" { u16 AudioEditor_GetReplacementSeq(u16 seqId); - #ifdef __cplusplus } #endif diff --git a/mm/2s2h/mixer.c b/mm/2s2h/mixer.c index 2e6e45cc94..1e5057c574 100644 --- a/mm/2s2h/mixer.c +++ b/mm/2s2h/mixer.c @@ -91,7 +91,8 @@ void aClearBufferImpl(uint16_t addr, int nbytes) { memset(BUF_U8(addr), 0, nbytes); } -void aLoadBufferImpl(const void* source_addr, uint16_t dest_addr, uint16_t nbytes) { +void aLoadBufferImpl(const void* source_addr, uint16_t dest_addr, + uint16_t nbytes) { #if __SANITIZE_ADDRESS__ for (size_t i = 0; i < ROUND_DOWN_16(nbytes); i++) { BUF_U8(dest_addr)[i] = ((const unsigned char*)source_addr)[i]; diff --git a/mm/2s2h/resource/importer/AudioSoundFontFactory.cpp b/mm/2s2h/resource/importer/AudioSoundFontFactory.cpp index 0e2698a6ce..6f7986414f 100644 --- a/mm/2s2h/resource/importer/AudioSoundFontFactory.cpp +++ b/mm/2s2h/resource/importer/AudioSoundFontFactory.cpp @@ -183,11 +183,12 @@ int8_t SOH::ResourceFactoryXMLSoundFontV0::MediumStrToInt(const char* str) { return 2; } else if (!strcmp("Disk", str)) { return 3; - //4 is skipped + // 4 is skipped } else if (!strcmp("RamUnloaded", str)) { return 5; } else { - throw std::runtime_error(StringHelper::Sprintf("Bad medium value. Got %s, expected Ram, Unk, Cart, or Disk.", str)); + throw std::runtime_error( + StringHelper::Sprintf("Bad medium value. Got %s, expected Ram, Unk, Cart, or Disk.", str)); } } @@ -201,8 +202,8 @@ int8_t ResourceFactoryXMLSoundFontV0::CachePolicyToInt(const char* str) { } else if (!strcmp("Permanent", str)) { return 3; } else { - throw std::runtime_error( - StringHelper::Sprintf("Bad cache policy value. Got %s, expected Temporary, Persistent, Either, or Permanent.", str)); + throw std::runtime_error(StringHelper::Sprintf( + "Bad cache policy value. Got %s, expected Temporary, Persistent, Either, or Permanent.", str)); } } @@ -230,101 +231,114 @@ void ResourceFactoryXMLSoundFontV0::ParseDrums(AudioSoundFont* soundFont, tinyxm } element = (tinyxml2::XMLElement*)element->FirstChildElement(); if (!strcmp(element->Name(), "Envelopes")) { - //element = (tinyxml2::XMLElement*)element->FirstChildElement(); + // element = (tinyxml2::XMLElement*)element->FirstChildElement(); unsigned int envCount = 0; envelopes = ParseEnvelopes(soundFont, element, &envCount); element = (tinyxml2::XMLElement*)element->Parent(); soundFont->drumEnvelopeArrays.push_back(envelopes); - drum.envelope = soundFont->drumEnvelopeArrays.back().data(); + drum.envelope = new AdsrEnvelope[envelopes.size()]; + memcpy(drum.envelope, envelopes.data(), envelopes.size() * sizeof(AdsrEnvelope)); } else { drum.envelope = nullptr; } // BENTODO the binary importer does this not sure why... @jack or @kenix? - soundFont->drums.push_back(drum); - //if (drum.sound.sample == nullptr) { - // soundFont->drumAddresses.push_back(nullptr); - //} else { - soundFont->drumAddresses.push_back(&soundFont->drums.back()); - //} - - + // soundFont->drums.push_back(drum); + // BENTODO clean this up in V3. + + if (drum.sound.sample == nullptr) { + soundFont->drumAddresses.push_back(nullptr); + } else { + Drum* drumCopy = new Drum; + memcpy(drumCopy, &drum, sizeof(drum)); + soundFont->drumAddresses.push_back(drumCopy); + } + element = element->NextSiblingElement(); } while (element != nullptr); soundFont->soundFont.numDrums = soundFont->drumAddresses.size(); soundFont->soundFont.drums = soundFont->drumAddresses.data(); } - void SOH::ResourceFactoryXMLSoundFontV0::ParseInstruments(AudioSoundFont* soundFont, tinyxml2::XMLElement* element) { - element = element->FirstChildElement(); - do { - Instrument instrument = { 0 }; - unsigned int envCount = 0; - std::vector envelopes; - - int isValid = element->BoolAttribute("IsValid"); - instrument.loaded = element->IntAttribute("Loaded"); - instrument.normalRangeLo = element->IntAttribute("NormalRangeLo"); - instrument.normalRangeHi = element->IntAttribute("NormalRangeHi"); - instrument.releaseRate = element->IntAttribute("ReleseRate"); // BENTODO fix the spelling - tinyxml2::XMLElement* instrumentElement = element->FirstChildElement(); - tinyxml2::XMLElement* instrumentElementCopy = instrumentElement; - if (instrumentElement != nullptr && !strcmp(instrumentElement->Name(), "Envelopes")) { - envelopes = ParseEnvelopes(soundFont, instrumentElement, &envCount); - soundFont->instrumentEnvelopeCounts.push_back(envCount); - soundFont->instrumentEnvelopeArrays.push_back(envelopes); - instrument.envelope = soundFont->instrumentEnvelopeArrays.back().data(); - instrumentElement = instrumentElement->NextSiblingElement(); - } - if (instrumentElement != nullptr && !strcmp("LowNotesSound", instrumentElement->Name())) { - instrument.lowNotesSound.tuning = instrumentElement->FloatAttribute("Tuning"); - const char* sampleStr = instrumentElement->Attribute("SampleRef"); - if (sampleStr != nullptr && sampleStr[0] != 0) { - auto res = Ship::Context::GetInstance()->GetResourceManager()->LoadResourceProcess(sampleStr); - instrument.lowNotesSound.sample = static_cast(res ? res->GetRawPointer() : nullptr); - } - instrumentElement = instrumentElement->NextSiblingElement(); - } - if (instrumentElement != nullptr && !strcmp("NormalNotesSound", instrumentElement->Name())) { - instrument.normalNotesSound.tuning = instrumentElement->FloatAttribute("Tuning"); - const char* sampleStr = instrumentElement->Attribute("SampleRef"); - if (sampleStr != nullptr && sampleStr[0] != 0) { - auto res = Ship::Context::GetInstance()->GetResourceManager()->LoadResourceProcess(sampleStr); - instrument.normalNotesSound.sample = static_cast(res ? res->GetRawPointer() : nullptr); - } - instrumentElement = instrumentElement->NextSiblingElement(); - } - if (instrumentElement != nullptr && !strcmp("HighNotesSound", instrumentElement->Name())) { - instrument.highNotesSound.tuning = instrumentElement->FloatAttribute("Tuning"); - const char* sampleStr = instrumentElement->Attribute("SampleRef"); - if (sampleStr != nullptr && sampleStr[0] != 0) { - auto res = Ship::Context::GetInstance()->GetResourceManager()->LoadResourceProcess(sampleStr); - instrument.highNotesSound.sample = static_cast(res ? res->GetRawPointer() : nullptr); - } - instrumentElement = instrumentElement->NextSiblingElement(); - } - element = instrumentElementCopy; - element = (tinyxml2::XMLElement*)element->Parent(); - element = element->NextSiblingElement(); - soundFont->instruments.push_back(instrument); - soundFont->instrumentAddresses.push_back(isValid ? &soundFont->instruments.back() : nullptr); +void SOH::ResourceFactoryXMLSoundFontV0::ParseInstruments(AudioSoundFont* soundFont, tinyxml2::XMLElement* element) { + element = element->FirstChildElement(); + do { + Instrument instrument = { 0 }; + unsigned int envCount = 0; + std::vector envelopes; + + int isValid = element->BoolAttribute("IsValid"); + instrument.loaded = element->IntAttribute("Loaded"); + instrument.normalRangeLo = element->IntAttribute("NormalRangeLo"); + instrument.normalRangeHi = element->IntAttribute("NormalRangeHi"); + instrument.releaseRate = element->IntAttribute("ReleaseRate"); + tinyxml2::XMLElement* instrumentElement = element->FirstChildElement(); + tinyxml2::XMLElement* instrumentElementCopy = instrumentElement; + if (instrumentElement != nullptr && !strcmp(instrumentElement->Name(), "Envelopes")) { + envelopes = ParseEnvelopes(soundFont, instrumentElement, &envCount); + soundFont->instrumentEnvelopeCounts.push_back(envCount); + instrument.envelope = new AdsrEnvelope[envelopes.size()]; + memcpy(instrument.envelope, envelopes.data(), envelopes.size() * sizeof(AdsrEnvelope)); + instrumentElement = instrumentElement->NextSiblingElement(); + } + if (instrumentElement != nullptr && !strcmp("LowNotesSound", instrumentElement->Name())) { + instrument.lowNotesSound.tuning = instrumentElement->FloatAttribute("Tuning"); + const char* sampleStr = instrumentElement->Attribute("SampleRef"); + if (sampleStr != nullptr && sampleStr[0] != 0) { + auto res = Ship::Context::GetInstance()->GetResourceManager()->LoadResourceProcess(sampleStr); + instrument.lowNotesSound.sample = static_cast(res ? res->GetRawPointer() : nullptr); + } + instrumentElement = instrumentElement->NextSiblingElement(); + } + if (instrumentElement != nullptr && !strcmp("NormalNotesSound", instrumentElement->Name())) { + instrument.normalNotesSound.tuning = instrumentElement->FloatAttribute("Tuning"); + const char* sampleStr = instrumentElement->Attribute("SampleRef"); + if (sampleStr != nullptr && sampleStr[0] != 0) { + auto res = Ship::Context::GetInstance()->GetResourceManager()->LoadResourceProcess(sampleStr); + instrument.normalNotesSound.sample = static_cast(res ? res->GetRawPointer() : nullptr); + } + instrumentElement = instrumentElement->NextSiblingElement(); + } + if (instrumentElement != nullptr && !strcmp("HighNotesSound", instrumentElement->Name())) { + instrument.highNotesSound.tuning = instrumentElement->FloatAttribute("Tuning"); + const char* sampleStr = instrumentElement->Attribute("SampleRef"); + if (sampleStr != nullptr && sampleStr[0] != 0) { + auto res = Ship::Context::GetInstance()->GetResourceManager()->LoadResourceProcess(sampleStr); + instrument.highNotesSound.sample = static_cast(res ? res->GetRawPointer() : nullptr); + } + instrumentElement = instrumentElement->NextSiblingElement(); + } + element = instrumentElementCopy; + element = (tinyxml2::XMLElement*)element->Parent(); + element = element->NextSiblingElement(); + Instrument* instrumentCopy = new Instrument; + memcpy(instrumentCopy, &instrument, sizeof(instrument)); + // soundFont->instruments.push_back(instrument); + soundFont->instrumentAddresses.push_back(instrumentCopy); } while (element != nullptr); soundFont->soundFont.instruments = soundFont->instrumentAddresses.data(); soundFont->soundFont.numInstruments = soundFont->instrumentAddresses.size(); - } - +} void SOH::ResourceFactoryXMLSoundFontV0::ParseSfxTable(AudioSoundFont* soundFont, tinyxml2::XMLElement* element) { + size_t count = element->IntAttribute("Count"); + element = (tinyxml2::XMLElement*)element->FirstChildElement(); while (element != nullptr) { - SoundFontSound sound; - sound.tuning = element->FloatAttribute("Tuning"); + SoundFontSound sound = { 0 }; const char* sampleStr = element->Attribute("SampleRef"); + // Insert an empty sound effect. The game assumes the empty slots are + // filled so we can't just skip them + if (sampleStr == 0) + goto skip; + + sound.tuning = element->FloatAttribute("Tuning"); if (sampleStr != nullptr && sampleStr[0] != 0) { auto res = Ship::Context::GetInstance()->GetResourceManager()->LoadResourceProcess(sampleStr); sound.sample = static_cast(res ? res->GetRawPointer() : nullptr); } + skip: element = element->NextSiblingElement(); soundFont->soundEffects.push_back(sound); } @@ -332,14 +346,12 @@ void SOH::ResourceFactoryXMLSoundFontV0::ParseSfxTable(AudioSoundFont* soundFont soundFont->soundFont.numSfx = soundFont->soundEffects.size(); } - - std::vector SOH::ResourceFactoryXMLSoundFontV0::ParseEnvelopes(AudioSoundFont* soundFont, - tinyxml2::XMLElement* element, - unsigned int* count) { +std::vector SOH::ResourceFactoryXMLSoundFontV0::ParseEnvelopes(AudioSoundFont* soundFont, + tinyxml2::XMLElement* element, + unsigned int* count) { std::vector envelopes; unsigned int total = 0; element = element->FirstChildElement("Envelope"); - while (element != nullptr) { AdsrEnvelope env = { .delay = (s16)element->IntAttribute("Delay"), @@ -350,7 +362,7 @@ void SOH::ResourceFactoryXMLSoundFontV0::ParseSfxTable(AudioSoundFont* soundFont envelopes.emplace_back(env); element = element->NextSiblingElement("Envelope"); total++; - } + } *count = total; return envelopes; } @@ -361,14 +373,11 @@ std::shared_ptr ResourceFactoryXMLSoundFontV0::ReadResource(std } auto audioSoundFont = std::make_shared(file->InitData); - auto child = - std::get>(file->Reader)->FirstChildElement(); + auto child = std::get>(file->Reader)->FirstChildElement(); // Header data memset(&audioSoundFont->soundFont, 0, sizeof(audioSoundFont->soundFont)); audioSoundFont->soundFont.fntIndex = child->IntAttribute("Num", 0); - if (audioSoundFont->soundFont.fntIndex == 1) { - int bp = 0; - } + const char* mediumStr = child->Attribute("Medium"); audioSoundFont->medium = MediumStrToInt(mediumStr); @@ -400,5 +409,4 @@ std::shared_ptr ResourceFactoryXMLSoundFontV0::ReadResource(std return audioSoundFont; } - } // namespace SOH diff --git a/mm/2s2h/resource/importer/AudioSoundFontFactory.h b/mm/2s2h/resource/importer/AudioSoundFontFactory.h index bd3577d10e..b6ec1f2e40 100644 --- a/mm/2s2h/resource/importer/AudioSoundFontFactory.h +++ b/mm/2s2h/resource/importer/AudioSoundFontFactory.h @@ -21,7 +21,8 @@ class ResourceFactoryXMLSoundFontV0 : public Ship::ResourceFactoryXML { void ParseDrums(AudioSoundFont* soundFont, tinyxml2::XMLElement* element); void ParseInstruments(AudioSoundFont* soundFont, tinyxml2::XMLElement* element); void ParseSfxTable(AudioSoundFont* soundFont, tinyxml2::XMLElement* element); - std::vector ParseEnvelopes(AudioSoundFont* soundFont, tinyxml2::XMLElement* element, unsigned int* count); + std::vector ParseEnvelopes(AudioSoundFont* soundFont, tinyxml2::XMLElement* element, + unsigned int* count); }; } // namespace SOH From 5b25e62e6a5fca8ac6024ee3bb28102a22c7cc75 Mon Sep 17 00:00:00 2001 From: louist103 <35883445+louist103@users.noreply.github.com> Date: Sun, 30 Jun 2024 20:49:44 -0400 Subject: [PATCH 08/25] Name sequences --- mm/assets/xml/N64_US/audio/Audio.xml | 250 +++++++++++++-------------- 1 file changed, 125 insertions(+), 125 deletions(-) diff --git a/mm/assets/xml/N64_US/audio/Audio.xml b/mm/assets/xml/N64_US/audio/Audio.xml index dd1cfefc1f..ec5f3963d1 100644 --- a/mm/assets/xml/N64_US/audio/Audio.xml +++ b/mm/assets/xml/N64_US/audio/Audio.xml @@ -4,132 +4,132 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - + + + + + From bc92ebf89d8399fb16fe0341e58f026663609ebd Mon Sep 17 00:00:00 2001 From: louist103 <35883445+louist103@users.noreply.github.com> Date: Sun, 30 Jun 2024 23:03:39 -0400 Subject: [PATCH 09/25] Cleanup the SF importer a little. --- .../importer/AudioSoundFontFactory.cpp | 73 +++++++++++-------- 1 file changed, 41 insertions(+), 32 deletions(-) diff --git a/mm/2s2h/resource/importer/AudioSoundFontFactory.cpp b/mm/2s2h/resource/importer/AudioSoundFontFactory.cpp index 6f7986414f..87db89cbcf 100644 --- a/mm/2s2h/resource/importer/AudioSoundFontFactory.cpp +++ b/mm/2s2h/resource/importer/AudioSoundFontFactory.cpp @@ -215,20 +215,23 @@ void ResourceFactoryXMLSoundFontV0::ParseDrums(AudioSoundFont* soundFont, tinyxm soundFont->soundFont.numDrums = 0; return; } + do { - Drum drum; + Drum* drum = new Drum; std::vector envelopes; - drum.releaseRate = element->IntAttribute("ReleaseRate"); - drum.pan = element->IntAttribute("Pan"); - drum.loaded = element->IntAttribute("Loaded"); - drum.sound.tuning = element->FloatAttribute("Tuning"); + drum->releaseRate = element->IntAttribute("ReleaseRate"); + drum->pan = element->IntAttribute("Pan"); + drum->loaded = element->IntAttribute("Loaded"); + drum->sound.tuning = element->FloatAttribute("Tuning"); const char* sampleStr = element->Attribute("SampleRef"); + if (sampleStr != nullptr && sampleStr[0] != 0) { auto res = Ship::Context::GetInstance()->GetResourceManager()->LoadResourceProcess(sampleStr); - drum.sound.sample = static_cast(res ? res->GetRawPointer() : nullptr); + drum->sound.sample = static_cast(res ? res->GetRawPointer() : nullptr); } else { - drum.sound.sample = nullptr; + drum->sound.sample = nullptr; } + element = (tinyxml2::XMLElement*)element->FirstChildElement(); if (!strcmp(element->Name(), "Envelopes")) { // element = (tinyxml2::XMLElement*)element->FirstChildElement(); @@ -236,26 +239,25 @@ void ResourceFactoryXMLSoundFontV0::ParseDrums(AudioSoundFont* soundFont, tinyxm envelopes = ParseEnvelopes(soundFont, element, &envCount); element = (tinyxml2::XMLElement*)element->Parent(); soundFont->drumEnvelopeArrays.push_back(envelopes); - drum.envelope = new AdsrEnvelope[envelopes.size()]; - memcpy(drum.envelope, envelopes.data(), envelopes.size() * sizeof(AdsrEnvelope)); + drum->envelope = new AdsrEnvelope[envelopes.size()]; + memcpy(drum->envelope, envelopes.data(), envelopes.size() * sizeof(AdsrEnvelope)); } else { - drum.envelope = nullptr; + drum->envelope = nullptr; } // BENTODO the binary importer does this not sure why... @jack or @kenix? // soundFont->drums.push_back(drum); // BENTODO clean this up in V3. - if (drum.sound.sample == nullptr) { + if (drum->sound.sample == nullptr) { soundFont->drumAddresses.push_back(nullptr); } else { - Drum* drumCopy = new Drum; - memcpy(drumCopy, &drum, sizeof(drum)); - soundFont->drumAddresses.push_back(drumCopy); + soundFont->drumAddresses.push_back(drum); } element = element->NextSiblingElement(); } while (element != nullptr); + soundFont->soundFont.numDrums = soundFont->drumAddresses.size(); soundFont->soundFont.drums = soundFont->drumAddresses.data(); } @@ -263,59 +265,64 @@ void ResourceFactoryXMLSoundFontV0::ParseDrums(AudioSoundFont* soundFont, tinyxm void SOH::ResourceFactoryXMLSoundFontV0::ParseInstruments(AudioSoundFont* soundFont, tinyxml2::XMLElement* element) { element = element->FirstChildElement(); do { - Instrument instrument = { 0 }; + Instrument* instrument = new Instrument; + memset(instrument, 0, sizeof(Instrument)); unsigned int envCount = 0; std::vector envelopes; int isValid = element->BoolAttribute("IsValid"); - instrument.loaded = element->IntAttribute("Loaded"); - instrument.normalRangeLo = element->IntAttribute("NormalRangeLo"); - instrument.normalRangeHi = element->IntAttribute("NormalRangeHi"); - instrument.releaseRate = element->IntAttribute("ReleaseRate"); + instrument->loaded = element->IntAttribute("Loaded"); + instrument->normalRangeLo = element->IntAttribute("NormalRangeLo"); + instrument->normalRangeHi = element->IntAttribute("NormalRangeHi"); + instrument->releaseRate = element->IntAttribute("ReleaseRate"); tinyxml2::XMLElement* instrumentElement = element->FirstChildElement(); tinyxml2::XMLElement* instrumentElementCopy = instrumentElement; + if (instrumentElement != nullptr && !strcmp(instrumentElement->Name(), "Envelopes")) { envelopes = ParseEnvelopes(soundFont, instrumentElement, &envCount); soundFont->instrumentEnvelopeCounts.push_back(envCount); - instrument.envelope = new AdsrEnvelope[envelopes.size()]; - memcpy(instrument.envelope, envelopes.data(), envelopes.size() * sizeof(AdsrEnvelope)); + instrument->envelope = new AdsrEnvelope[envelopes.size()]; + memcpy(instrument->envelope, envelopes.data(), envelopes.size() * sizeof(AdsrEnvelope)); instrumentElement = instrumentElement->NextSiblingElement(); } + if (instrumentElement != nullptr && !strcmp("LowNotesSound", instrumentElement->Name())) { - instrument.lowNotesSound.tuning = instrumentElement->FloatAttribute("Tuning"); + instrument->lowNotesSound.tuning = instrumentElement->FloatAttribute("Tuning"); const char* sampleStr = instrumentElement->Attribute("SampleRef"); if (sampleStr != nullptr && sampleStr[0] != 0) { auto res = Ship::Context::GetInstance()->GetResourceManager()->LoadResourceProcess(sampleStr); - instrument.lowNotesSound.sample = static_cast(res ? res->GetRawPointer() : nullptr); + instrument->lowNotesSound.sample = static_cast(res ? res->GetRawPointer() : nullptr); } instrumentElement = instrumentElement->NextSiblingElement(); } + if (instrumentElement != nullptr && !strcmp("NormalNotesSound", instrumentElement->Name())) { - instrument.normalNotesSound.tuning = instrumentElement->FloatAttribute("Tuning"); + instrument->normalNotesSound.tuning = instrumentElement->FloatAttribute("Tuning"); const char* sampleStr = instrumentElement->Attribute("SampleRef"); if (sampleStr != nullptr && sampleStr[0] != 0) { auto res = Ship::Context::GetInstance()->GetResourceManager()->LoadResourceProcess(sampleStr); - instrument.normalNotesSound.sample = static_cast(res ? res->GetRawPointer() : nullptr); + instrument->normalNotesSound.sample = static_cast(res ? res->GetRawPointer() : nullptr); } instrumentElement = instrumentElement->NextSiblingElement(); } + if (instrumentElement != nullptr && !strcmp("HighNotesSound", instrumentElement->Name())) { - instrument.highNotesSound.tuning = instrumentElement->FloatAttribute("Tuning"); + instrument->highNotesSound.tuning = instrumentElement->FloatAttribute("Tuning"); const char* sampleStr = instrumentElement->Attribute("SampleRef"); if (sampleStr != nullptr && sampleStr[0] != 0) { auto res = Ship::Context::GetInstance()->GetResourceManager()->LoadResourceProcess(sampleStr); - instrument.highNotesSound.sample = static_cast(res ? res->GetRawPointer() : nullptr); + instrument->highNotesSound.sample = static_cast(res ? res->GetRawPointer() : nullptr); } instrumentElement = instrumentElement->NextSiblingElement(); } + + soundFont->instrumentAddresses.push_back(instrument); + element = instrumentElementCopy; element = (tinyxml2::XMLElement*)element->Parent(); element = element->NextSiblingElement(); - Instrument* instrumentCopy = new Instrument; - memcpy(instrumentCopy, &instrument, sizeof(instrument)); - // soundFont->instruments.push_back(instrument); - soundFont->instrumentAddresses.push_back(instrumentCopy); } while (element != nullptr); + soundFont->soundFont.instruments = soundFont->instrumentAddresses.data(); soundFont->soundFont.numInstruments = soundFont->instrumentAddresses.size(); } @@ -371,7 +378,6 @@ std::shared_ptr ResourceFactoryXMLSoundFontV0::ReadResource(std if (!FileHasValidFormatAndReader(file)) { return nullptr; } - auto audioSoundFont = std::make_shared(file->InitData); auto child = std::get>(file->Reader)->FirstChildElement(); // Header data @@ -383,6 +389,9 @@ std::shared_ptr ResourceFactoryXMLSoundFontV0::ReadResource(std const char* cachePolicyStr = child->Attribute("CachePolicy"); audioSoundFont->cachePolicy = CachePolicyToInt(cachePolicyStr); + if (audioSoundFont->soundFont.fntIndex == 1) { + int bp = 1; + } audioSoundFont->data1 = child->IntAttribute("Data1"); audioSoundFont->data2 = child->IntAttribute("Data2"); From aae5f59a45e26248d90cea2b1529539c98e708d5 Mon Sep 17 00:00:00 2001 From: louist103 <35883445+louist103@users.noreply.github.com> Date: Wed, 3 Jul 2024 17:05:11 -0400 Subject: [PATCH 10/25] XML Sequences good --- mm/2s2h/BenPort.cpp | 5 ++ .../importer/AudioSequenceFactory.cpp | 75 +++++++++++++++++++ .../resource/importer/AudioSequenceFactory.h | 11 +++ 3 files changed, 91 insertions(+) diff --git a/mm/2s2h/BenPort.cpp b/mm/2s2h/BenPort.cpp index 9175310181..9b0359b1e1 100644 --- a/mm/2s2h/BenPort.cpp +++ b/mm/2s2h/BenPort.cpp @@ -205,9 +205,11 @@ OTRGlobals::OTRGlobals() { "Cutscene", static_cast(SOH::ResourceType::SOH_Cutscene), 0); loader->RegisterResourceFactory(std::make_shared(), RESOURCE_FORMAT_BINARY, "TextMM", static_cast(SOH::ResourceType::TSH_TextMM), 0); + loader->RegisterResourceFactory(std::make_shared(), RESOURCE_FORMAT_BINARY, "AudioSample", static_cast(SOH::ResourceType::SOH_AudioSample), 2); + loader->RegisterResourceFactory(std::make_shared(), RESOURCE_FORMAT_BINARY, "AudioSoundFont", static_cast(SOH::ResourceType::SOH_AudioSoundFont), 2); @@ -217,6 +219,9 @@ OTRGlobals::OTRGlobals() { loader->RegisterResourceFactory(std::make_shared(), RESOURCE_FORMAT_BINARY, "AudioSequence", static_cast(SOH::ResourceType::SOH_AudioSequence), 2); + loader->RegisterResourceFactory(std::make_shared(), RESOURCE_FORMAT_XML, + "Sequence", static_cast(SOH::ResourceType::SOH_AudioSample), 0); + loader->RegisterResourceFactory(std::make_shared(), RESOURCE_FORMAT_BINARY, "Background", static_cast(SOH::ResourceType::SOH_Background), 0); loader->RegisterResourceFactory(std::make_shared(), diff --git a/mm/2s2h/resource/importer/AudioSequenceFactory.cpp b/mm/2s2h/resource/importer/AudioSequenceFactory.cpp index 5b8899501e..404018744c 100644 --- a/mm/2s2h/resource/importer/AudioSequenceFactory.cpp +++ b/mm/2s2h/resource/importer/AudioSequenceFactory.cpp @@ -1,6 +1,9 @@ #include "2s2h/resource/importer/AudioSequenceFactory.h" #include "2s2h/resource/type/AudioSequence.h" #include "spdlog/spdlog.h" +#include "StringHelper.h" +#include "libultraship/libultraship.h" + namespace SOH { std::shared_ptr ResourceFactoryBinaryAudioSequenceV2::ReadResource(std::shared_ptr file) { @@ -32,4 +35,76 @@ std::shared_ptr ResourceFactoryBinaryAudioSequenceV2::ReadResou return audioSequence; } + + +int8_t SOH::ResourceFactoryXMLAudioSequenceV0::MediumStrToInt(const char* str) { + if (!strcmp("Ram", str)) { + return 0; + } else if (!strcmp("Unk", str)) { + return 1; + } else if (!strcmp("Cart", str)) { + return 2; + } else if (!strcmp("Disk", str)) { + return 3; + // 4 is skipped + } else if (!strcmp("RamUnloaded", str)) { + return 5; + } else { + throw std::runtime_error( + StringHelper::Sprintf("Bad medium value. Got %s, expected Ram, Unk, Cart, or Disk.", str)); + } +} + +int8_t ResourceFactoryXMLAudioSequenceV0::CachePolicyToInt(const char* str) { + if (!strcmp("Temporary", str)) { + return 0; + } else if (!strcmp("Persistent", str)) { + return 1; + } else if (!strcmp("Either", str)) { + return 2; + } else if (!strcmp("Permanent", str)) { + return 3; + } else { + throw std::runtime_error(StringHelper::Sprintf( + "Bad cache policy value. Got %s, expected Temporary, Persistent, Either, or Permanent.", str)); + } +} + +std::shared_ptr ResourceFactoryXMLAudioSequenceV0::ReadResource(std::shared_ptr file) { + if (!FileHasValidFormatAndReader(file)) { + return nullptr; + } + auto sequence = std::make_shared(file->InitData); + auto child = std::get>(file->Reader)->FirstChildElement(); + unsigned int i = 0; + std::shared_ptr initData = std::make_shared(); + + sequence->sequence.medium = MediumStrToInt(child->Attribute("Medium")); + sequence->sequence.cachePolicy = CachePolicyToInt(child->Attribute("CachePolicy")); + sequence->sequence.seqDataSize = child->IntAttribute("Size"); + sequence->sequence.seqNumber = child->IntAttribute("Index"); + + memset(sequence->sequence.fonts, 0, sizeof(sequence->sequence.fonts)); + + tinyxml2::XMLElement* fontsElement = child->FirstChildElement(); + tinyxml2::XMLElement* fontElement = fontsElement->FirstChildElement(); + while (fontElement != nullptr) { + sequence->sequence.fonts[i] = fontElement->IntAttribute("FontIdx"); + fontElement = fontElement->NextSiblingElement(); + i++; + } + sequence->sequence.numFonts = i; + + const char* path = child->Attribute("Path"); + initData->Path = path; + initData->IsCustom = false; + initData->ByteOrder = Ship::Endianness::Native; + + auto seqFile = Ship::Context::GetInstance()->GetResourceManager()->GetArchiveManager()->LoadFile(path, initData); + + sequence->sequenceData = *seqFile->Buffer.get(); + sequence->sequence.seqData = sequence->sequenceData.data(); + + return sequence; +} } // namespace SOH diff --git a/mm/2s2h/resource/importer/AudioSequenceFactory.h b/mm/2s2h/resource/importer/AudioSequenceFactory.h index 35fd1ebef5..30d4961e61 100644 --- a/mm/2s2h/resource/importer/AudioSequenceFactory.h +++ b/mm/2s2h/resource/importer/AudioSequenceFactory.h @@ -2,10 +2,21 @@ #include "Resource.h" #include "ResourceFactoryBinary.h" +#include "ResourceFactoryXML.h" namespace SOH { class ResourceFactoryBinaryAudioSequenceV2 : public Ship::ResourceFactoryBinary { public: std::shared_ptr ReadResource(std::shared_ptr file) override; }; + +class ResourceFactoryXMLAudioSequenceV0 : public Ship::ResourceFactoryXML { + public: + std::shared_ptr ReadResource(std::shared_ptr file) override; + + private: + int8_t MediumStrToInt(const char* str); + int8_t CachePolicyToInt(const char* str); +}; + } // namespace SOH From eadf08dcf03f2c930adac126e664c8088dbdbd3a Mon Sep 17 00:00:00 2001 From: louist103 <35883445+louist103@users.noreply.github.com> Date: Wed, 3 Jul 2024 17:30:37 -0400 Subject: [PATCH 11/25] Fix mac --- OTRExporter | 2 +- mm/2s2h/resource/importer/AudioSoundFontFactory.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/OTRExporter b/OTRExporter index 89ffb98da6..db81fab784 160000 --- a/OTRExporter +++ b/OTRExporter @@ -1 +1 @@ -Subproject commit 89ffb98da6e5857750136efa2297793564e22dc0 +Subproject commit db81fab784796e036240da6434d1b52e8a7e45f8 diff --git a/mm/2s2h/resource/importer/AudioSoundFontFactory.cpp b/mm/2s2h/resource/importer/AudioSoundFontFactory.cpp index 87db89cbcf..2694c55e5a 100644 --- a/mm/2s2h/resource/importer/AudioSoundFontFactory.cpp +++ b/mm/2s2h/resource/importer/AudioSoundFontFactory.cpp @@ -337,7 +337,7 @@ void SOH::ResourceFactoryXMLSoundFontV0::ParseSfxTable(AudioSoundFont* soundFont const char* sampleStr = element->Attribute("SampleRef"); // Insert an empty sound effect. The game assumes the empty slots are // filled so we can't just skip them - if (sampleStr == 0) + if (sampleStr == nullptr) goto skip; sound.tuning = element->FloatAttribute("Tuning"); @@ -355,7 +355,7 @@ void SOH::ResourceFactoryXMLSoundFontV0::ParseSfxTable(AudioSoundFont* soundFont std::vector SOH::ResourceFactoryXMLSoundFontV0::ParseEnvelopes(AudioSoundFont* soundFont, tinyxml2::XMLElement* element, - unsigned int* count) { + unsigned int* count) { std::vector envelopes; unsigned int total = 0; element = element->FirstChildElement("Envelope"); From 3e9e50a306fda104a8e1166080d592334c9b919c Mon Sep 17 00:00:00 2001 From: louist103 <35883445+louist103@users.noreply.github.com> Date: Wed, 3 Jul 2024 20:21:55 -0400 Subject: [PATCH 12/25] Destructor --- .../importer/AudioSoundFontFactory.cpp | 107 ++++++++---------- mm/2s2h/resource/type/AudioSoundFont.cpp | 17 +++ mm/2s2h/resource/type/AudioSoundFont.h | 4 +- 3 files changed, 66 insertions(+), 62 deletions(-) diff --git a/mm/2s2h/resource/importer/AudioSoundFontFactory.cpp b/mm/2s2h/resource/importer/AudioSoundFontFactory.cpp index 2694c55e5a..3e4969e9d4 100644 --- a/mm/2s2h/resource/importer/AudioSoundFontFactory.cpp +++ b/mm/2s2h/resource/importer/AudioSoundFontFactory.cpp @@ -34,122 +34,115 @@ std::shared_ptr ResourceFactoryBinaryAudioSoundFontV2::ReadReso audioSoundFont->soundFont.numSfx = soundEffectCount; // 🥁 DRUMS 🥁 - audioSoundFont->drums.reserve(audioSoundFont->soundFont.numDrums); + //audioSoundFont->drums.reserve(audioSoundFont->soundFont.numDrums); audioSoundFont->drumAddresses.reserve(audioSoundFont->soundFont.numDrums); for (uint32_t i = 0; i < audioSoundFont->soundFont.numDrums; i++) { - Drum drum; - drum.releaseRate = reader->ReadUByte(); - drum.pan = reader->ReadUByte(); - drum.loaded = reader->ReadUByte(); - drum.loaded = 0; // this was always getting set to zero in ResourceMgr_LoadAudioSoundFont + Drum* drum = new Drum; + drum->releaseRate = reader->ReadUByte(); + drum->pan = reader->ReadUByte(); + drum->loaded = reader->ReadUByte(); + drum->loaded = 0; // this was always getting set to zero in ResourceMgr_LoadAudioSoundFont uint32_t envelopeCount = reader->ReadUInt32(); - audioSoundFont->drumEnvelopeCounts.push_back(envelopeCount); - std::vector drumEnvelopes; - drumEnvelopes.reserve(audioSoundFont->drumEnvelopeCounts[i]); - for (uint32_t j = 0; j < audioSoundFont->drumEnvelopeCounts.back(); j++) { - AdsrEnvelope env; - + drum->envelope = new AdsrEnvelope[envelopeCount]; + for (uint32_t j = 0; j < envelopeCount; j++) { int16_t delay = reader->ReadInt16(); int16_t arg = reader->ReadInt16(); - env.delay = BE16SWAP(delay); - env.arg = BE16SWAP(arg); - - drumEnvelopes.push_back(env); + drum->envelope[j].delay = BE16SWAP(delay); + drum->envelope[j].arg = BE16SWAP(arg); } - audioSoundFont->drumEnvelopeArrays.push_back(drumEnvelopes); - drum.envelope = audioSoundFont->drumEnvelopeArrays.back().data(); bool hasSample = reader->ReadInt8(); std::string sampleFileName = reader->ReadString(); - drum.sound.tuning = reader->ReadFloat(); + drum->sound.tuning = reader->ReadFloat(); if (sampleFileName.empty()) { - drum.sound.sample = nullptr; + drum->sound.sample = nullptr; } else { auto res = Ship::Context::GetInstance()->GetResourceManager()->LoadResourceProcess(sampleFileName.c_str()); - drum.sound.sample = static_cast(res ? res->GetRawPointer() : nullptr); + drum->sound.sample = static_cast(res ? res->GetRawPointer() : nullptr); } - audioSoundFont->drums.push_back(drum); + //audioSoundFont->drums.push_back(drum); // BENTODO clean this up in V3. - if (drum.sound.sample == nullptr) { + if (drum->sound.sample == nullptr) { + delete[] drum->envelope; + delete drum; audioSoundFont->drumAddresses.push_back(nullptr); } else { - audioSoundFont->drumAddresses.push_back(&audioSoundFont->drums.back()); + audioSoundFont->drumAddresses.push_back(drum); } } audioSoundFont->soundFont.drums = audioSoundFont->drumAddresses.data(); // 🎺🎻🎷🎸🎹 INSTRUMENTS 🎹🎸🎷🎻🎺 - audioSoundFont->instruments.reserve(audioSoundFont->soundFont.numInstruments); for (uint32_t i = 0; i < audioSoundFont->soundFont.numInstruments; i++) { - Instrument instrument; + Instrument* instrument = new Instrument; uint8_t isValidEntry = reader->ReadUByte(); - instrument.loaded = reader->ReadUByte(); - instrument.loaded = 0; // this was always getting set to zero in ResourceMgr_LoadAudioSoundFont + instrument->loaded = reader->ReadUByte(); + instrument->loaded = 0; // this was always getting set to zero in ResourceMgr_LoadAudioSoundFont - instrument.normalRangeLo = reader->ReadUByte(); - instrument.normalRangeHi = reader->ReadUByte(); - instrument.releaseRate = reader->ReadUByte(); + instrument->normalRangeLo = reader->ReadUByte(); + instrument->normalRangeHi = reader->ReadUByte(); + instrument->releaseRate = reader->ReadUByte(); uint32_t envelopeCount = reader->ReadInt32(); - audioSoundFont->instrumentEnvelopeCounts.push_back(envelopeCount); - std::vector instrumentEnvelopes; - for (uint32_t j = 0; j < audioSoundFont->instrumentEnvelopeCounts.back(); j++) { - AdsrEnvelope env; + instrument->envelope = new AdsrEnvelope[envelopeCount]; + for (uint32_t j = 0; j ReadInt16(); int16_t arg = reader->ReadInt16(); - env.delay = BE16SWAP(delay); - env.arg = BE16SWAP(arg); - - instrumentEnvelopes.push_back(env); + instrument->envelope[j].delay = BE16SWAP(delay); + instrument->envelope[j].arg = BE16SWAP(arg); } - audioSoundFont->instrumentEnvelopeArrays.push_back(instrumentEnvelopes); - instrument.envelope = audioSoundFont->instrumentEnvelopeArrays.back().data(); bool hasLowNoteSoundFontEntry = reader->ReadInt8(); if (hasLowNoteSoundFontEntry) { bool hasSampleRef = reader->ReadInt8(); std::string sampleFileName = reader->ReadString(); - instrument.lowNotesSound.tuning = reader->ReadFloat(); + instrument->lowNotesSound.tuning = reader->ReadFloat(); auto res = Ship::Context::GetInstance()->GetResourceManager()->LoadResourceProcess(sampleFileName.c_str()); - instrument.lowNotesSound.sample = static_cast(res ? res->GetRawPointer() : nullptr); + instrument->lowNotesSound.sample = static_cast(res ? res->GetRawPointer() : nullptr); } else { - instrument.lowNotesSound.sample = nullptr; - instrument.lowNotesSound.tuning = 0; + instrument->lowNotesSound.sample = nullptr; + instrument->lowNotesSound.tuning = 0; } bool hasNormalNoteSoundFontEntry = reader->ReadInt8(); if (hasNormalNoteSoundFontEntry) { + // BENTODO remove in V3 bool hasSampleRef = reader->ReadInt8(); std::string sampleFileName = reader->ReadString(); - instrument.normalNotesSound.tuning = reader->ReadFloat(); + instrument->normalNotesSound.tuning = reader->ReadFloat(); auto res = Ship::Context::GetInstance()->GetResourceManager()->LoadResourceProcess(sampleFileName.c_str()); - instrument.normalNotesSound.sample = static_cast(res ? res->GetRawPointer() : nullptr); + instrument->normalNotesSound.sample = static_cast(res ? res->GetRawPointer() : nullptr); } else { - instrument.normalNotesSound.sample = nullptr; - instrument.normalNotesSound.tuning = 0; + instrument->normalNotesSound.sample = nullptr; + instrument->normalNotesSound.tuning = 0; } bool hasHighNoteSoundFontEntry = reader->ReadInt8(); if (hasHighNoteSoundFontEntry) { bool hasSampleRef = reader->ReadInt8(); std::string sampleFileName = reader->ReadString(); - instrument.highNotesSound.tuning = reader->ReadFloat(); + instrument->highNotesSound.tuning = reader->ReadFloat(); auto res = Ship::Context::GetInstance()->GetResourceManager()->LoadResourceProcess(sampleFileName.c_str()); - instrument.highNotesSound.sample = static_cast(res ? res->GetRawPointer() : nullptr); + instrument->highNotesSound.sample = static_cast(res ? res->GetRawPointer() : nullptr); } else { - instrument.highNotesSound.sample = nullptr; - instrument.highNotesSound.tuning = 0; + instrument->highNotesSound.sample = nullptr; + instrument->highNotesSound.tuning = 0; } - audioSoundFont->instruments.push_back(instrument); - audioSoundFont->instrumentAddresses.push_back(isValidEntry ? &audioSoundFont->instruments.back() : nullptr); + if (isValidEntry) { + audioSoundFont->instrumentAddresses.push_back(instrument); + } else { + delete[] instrument->envelope; + delete instrument; + audioSoundFont->instrumentAddresses.push_back(nullptr); + } } audioSoundFont->soundFont.instruments = audioSoundFont->instrumentAddresses.data(); @@ -245,9 +238,6 @@ void ResourceFactoryXMLSoundFontV0::ParseDrums(AudioSoundFont* soundFont, tinyxm drum->envelope = nullptr; } - // BENTODO the binary importer does this not sure why... @jack or @kenix? - // soundFont->drums.push_back(drum); - // BENTODO clean this up in V3. if (drum->sound.sample == nullptr) { soundFont->drumAddresses.push_back(nullptr); @@ -280,7 +270,6 @@ void SOH::ResourceFactoryXMLSoundFontV0::ParseInstruments(AudioSoundFont* soundF if (instrumentElement != nullptr && !strcmp(instrumentElement->Name(), "Envelopes")) { envelopes = ParseEnvelopes(soundFont, instrumentElement, &envCount); - soundFont->instrumentEnvelopeCounts.push_back(envCount); instrument->envelope = new AdsrEnvelope[envelopes.size()]; memcpy(instrument->envelope, envelopes.data(), envelopes.size() * sizeof(AdsrEnvelope)); instrumentElement = instrumentElement->NextSiblingElement(); diff --git a/mm/2s2h/resource/type/AudioSoundFont.cpp b/mm/2s2h/resource/type/AudioSoundFont.cpp index 12218cb648..7f323ddf48 100644 --- a/mm/2s2h/resource/type/AudioSoundFont.cpp +++ b/mm/2s2h/resource/type/AudioSoundFont.cpp @@ -1,6 +1,23 @@ #include "AudioSoundFont.h" namespace SOH { + +AudioSoundFont::~AudioSoundFont() { + for (auto i : instrumentAddresses) { + if (i != nullptr) { + delete[] i->envelope; + delete i; + } + } + + for (auto d : drumAddresses) { + if (d != nullptr) { + delete[] d->envelope; + delete d; + } + } +} + SoundFont* AudioSoundFont::GetPointer() { return &soundFont; } diff --git a/mm/2s2h/resource/type/AudioSoundFont.h b/mm/2s2h/resource/type/AudioSoundFont.h index 302c182d21..d093eaec2f 100644 --- a/mm/2s2h/resource/type/AudioSoundFont.h +++ b/mm/2s2h/resource/type/AudioSoundFont.h @@ -58,6 +58,7 @@ class AudioSoundFont : public Ship::Resource { AudioSoundFont() : Resource(std::shared_ptr()) { } + ~AudioSoundFont(); SoundFont* GetPointer(); size_t GetPointerSize(); @@ -68,14 +69,11 @@ class AudioSoundFont : public Ship::Resource { uint16_t data2; uint16_t data3; - std::vector drums; std::vector drumAddresses; std::vector drumEnvelopeCounts; std::vector> drumEnvelopeArrays; - std::vector instruments; std::vector instrumentAddresses; - std::vector instrumentEnvelopeCounts; std::vector> instrumentEnvelopeArrays; std::vector soundEffects; From 6a0bcbe547b4777dfc8d20fea220e450488c0ff4 Mon Sep 17 00:00:00 2001 From: louist103 <35883445+louist103@users.noreply.github.com> Date: Wed, 3 Jul 2024 20:22:02 -0400 Subject: [PATCH 13/25] Bump submodule --- OTRExporter | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OTRExporter b/OTRExporter index db81fab784..1e4dea2b4b 160000 --- a/OTRExporter +++ b/OTRExporter @@ -1 +1 @@ -Subproject commit db81fab784796e036240da6434d1b52e8a7e45f8 +Subproject commit 1e4dea2b4b125e116576f32e4b685f5778a579b2 From e0d55e7d3e725aca61b6ee7f8a6c02df2d01e502 Mon Sep 17 00:00:00 2001 From: louist103 <35883445+louist103@users.noreply.github.com> Date: Wed, 17 Jul 2024 23:59:50 -0400 Subject: [PATCH 14/25] Thread safe queue --- mm/2s2h/Enhancements/Audio/AudioSeqQueue.cpp | 39 ++++++++++++ mm/2s2h/Enhancements/Audio/AudioSeqQueue.h | 66 ++++++++++++++++++++ 2 files changed, 105 insertions(+) create mode 100644 mm/2s2h/Enhancements/Audio/AudioSeqQueue.cpp create mode 100644 mm/2s2h/Enhancements/Audio/AudioSeqQueue.h diff --git a/mm/2s2h/Enhancements/Audio/AudioSeqQueue.cpp b/mm/2s2h/Enhancements/Audio/AudioSeqQueue.cpp new file mode 100644 index 0000000000..e6e66187cc --- /dev/null +++ b/mm/2s2h/Enhancements/Audio/AudioSeqQueue.cpp @@ -0,0 +1,39 @@ +#include "AudioSeqQueue.h" + +#include "resource/type/AudioSequence.h" +#include "Context.h" + +static SafeQueue audioQueue; + +extern "C" { + +void AudioQueue_Enqueue(char* seqId) { + audioQueue.enqueue(seqId); +} + +char* AudioQueue_Dequeue(void) { + return audioQueue.dequeue(); +} + +int32_t AudioQueue_IsEmpty(void) { + return audioQueue.isEmpty(); +} + +void AudioQueue_GetSeqInfo(const char* path, uint64_t* numFrames, uint32_t* numChannels, uint32_t* sampleRate, int16_t** sampleData) { + auto seqData = + static_pointer_cast(Ship::Context::GetInstance()->GetResourceManager()->LoadResource(path)); + if (numFrames != nullptr) { + *numFrames = seqData->sequence.seqDataSize / sizeof(uint16_t); + } + + if (numChannels != nullptr) { + *numChannels = seqData->numChannels; + } + + if (numChannels != nullptr) { + *sampleRate = seqData->sampleRate; + } + *sampleData = (s16*)seqData->sequence.seqData; +} + +} \ No newline at end of file diff --git a/mm/2s2h/Enhancements/Audio/AudioSeqQueue.h b/mm/2s2h/Enhancements/Audio/AudioSeqQueue.h new file mode 100644 index 0000000000..a4a2dadf7b --- /dev/null +++ b/mm/2s2h/Enhancements/Audio/AudioSeqQueue.h @@ -0,0 +1,66 @@ +#ifndef AUDIO_SEQ_QUEUE_H +#define AUDIO_SEQ_QUEUE_H + +#include + +#ifdef __cplusplus + +#include +#include +#include + + +// A threadsafe-queue. +template class SafeQueue { + public: + SafeQueue(void) : q(), m(), c() { + } + + ~SafeQueue(void) { + } + + // Add an element to the queue. + void enqueue(T t) { + std::lock_guard lock(m); + q.push(t); + c.notify_one(); + } + + // Get the "front"-element. + // If the queue is empty, wait till a element is avaiable. + T dequeue(void) { + std::unique_lock lock(m); + while (q.empty()) { + // release lock as long as the wait and reaquire it afterwards. + c.wait(lock); + } + T val = q.front(); + q.pop(); + return val; + } + + int32_t isEmpty() { + return static_cast(q.empty()); + } + + private: + std::queue q; + mutable std::mutex m; + std::condition_variable c; +}; +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +void AudioQueue_Enqueue(char* seqId); +char* AudioQueue_Dequeue(void); +int32_t AudioQueue_IsEmpty(void); +void AudioQueue_GetSeqInfo(const char* path, uint64_t* numFrames, uint32_t* numChannels, uint32_t* sampleRate, int16_t** sampleData); + +#ifdef __cplusplus +} +#endif + +#endif From 92d4eeef867c01f2d7f1b61161b5e4222e309ced Mon Sep 17 00:00:00 2001 From: louist103 <35883445+louist103@users.noreply.github.com> Date: Thu, 18 Jul 2024 00:00:00 -0400 Subject: [PATCH 15/25] Add WAV decoder --- mm/2s2h/dr_wav.h | 8816 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 8816 insertions(+) create mode 100644 mm/2s2h/dr_wav.h diff --git a/mm/2s2h/dr_wav.h b/mm/2s2h/dr_wav.h new file mode 100644 index 0000000000..0b75c21d85 --- /dev/null +++ b/mm/2s2h/dr_wav.h @@ -0,0 +1,8816 @@ +#pragma once +/* +WAV audio loader and writer. Choice of public domain or MIT-0. See license statements at the end of this file. +dr_wav - v0.13.16 - 2024-02-27 + +David Reid - mackron@gmail.com + +GitHub: https://github.com/mackron/dr_libs +*/ + +/* +Introduction +============ +This is a single file library. To use it, do something like the following in one .c file. + + ```c + #define DR_WAV_IMPLEMENTATION + #include "dr_wav.h" + ``` + +You can then #include this file in other parts of the program as you would with any other header file. Do something like the following to read audio data: + + ```c + drwav wav; + if (!drwav_init_file(&wav, "my_song.wav", NULL)) { + // Error opening WAV file. + } + + drwav_int32* pDecodedInterleavedPCMFrames = malloc(wav.totalPCMFrameCount * wav.channels * sizeof(drwav_int32)); + size_t numberOfSamplesActuallyDecoded = drwav_read_pcm_frames_s32(&wav, wav.totalPCMFrameCount, pDecodedInterleavedPCMFrames); + + ... + + drwav_uninit(&wav); + ``` + +If you just want to quickly open and read the audio data in a single operation you can do something like this: + + ```c + unsigned int channels; + unsigned int sampleRate; + drwav_uint64 totalPCMFrameCount; + float* pSampleData = drwav_open_file_and_read_pcm_frames_f32("my_song.wav", &channels, &sampleRate, &totalPCMFrameCount, NULL); + if (pSampleData == NULL) { + // Error opening and reading WAV file. + } + + ... + + drwav_free(pSampleData, NULL); + ``` + +The examples above use versions of the API that convert the audio data to a consistent format (32-bit signed PCM, in this case), but you can still output the +audio data in its internal format (see notes below for supported formats): + + ```c + size_t framesRead = drwav_read_pcm_frames(&wav, wav.totalPCMFrameCount, pDecodedInterleavedPCMFrames); + ``` + +You can also read the raw bytes of audio data, which could be useful if dr_wav does not have native support for a particular data format: + + ```c + size_t bytesRead = drwav_read_raw(&wav, bytesToRead, pRawDataBuffer); + ``` + +dr_wav can also be used to output WAV files. This does not currently support compressed formats. To use this, look at `drwav_init_write()`, +`drwav_init_file_write()`, etc. Use `drwav_write_pcm_frames()` to write samples, or `drwav_write_raw()` to write raw data in the "data" chunk. + + ```c + drwav_data_format format; + format.container = drwav_container_riff; // <-- drwav_container_riff = normal WAV files, drwav_container_w64 = Sony Wave64. + format.format = DR_WAVE_FORMAT_PCM; // <-- Any of the DR_WAVE_FORMAT_* codes. + format.channels = 2; + format.sampleRate = 44100; + format.bitsPerSample = 16; + drwav_init_file_write(&wav, "data/recording.wav", &format, NULL); + + ... + + drwav_uint64 framesWritten = drwav_write_pcm_frames(pWav, frameCount, pSamples); + ``` + +Note that writing to AIFF or RIFX is not supported. + +dr_wav has support for decoding from a number of different encapsulation formats. See below for details. + + +Build Options +============= +#define these options before including this file. + +#define DR_WAV_NO_CONVERSION_API + Disables conversion APIs such as `drwav_read_pcm_frames_f32()` and `drwav_s16_to_f32()`. + +#define DR_WAV_NO_STDIO + Disables APIs that initialize a decoder from a file such as `drwav_init_file()`, `drwav_init_file_write()`, etc. + +#define DR_WAV_NO_WCHAR + Disables all functions ending with `_w`. Use this if your compiler does not provide wchar.h. Not required if DR_WAV_NO_STDIO is also defined. + + +Supported Encapsulations +======================== +- RIFF (Regular WAV) +- RIFX (Big-Endian) +- AIFF (Does not currently support ADPCM) +- RF64 +- W64 + +Note that AIFF and RIFX do not support write mode, nor do they support reading of metadata. + + +Supported Encodings +=================== +- Unsigned 8-bit PCM +- Signed 12-bit PCM +- Signed 16-bit PCM +- Signed 24-bit PCM +- Signed 32-bit PCM +- IEEE 32-bit floating point +- IEEE 64-bit floating point +- A-law and u-law +- Microsoft ADPCM +- IMA ADPCM (DVI, format code 0x11) + +8-bit PCM encodings are always assumed to be unsigned. Signed 8-bit encoding can only be read with `drwav_read_raw()`. + +Note that ADPCM is not currently supported with AIFF. Contributions welcome. + + +Notes +===== +- Samples are always interleaved. +- The default read function does not do any data conversion. Use `drwav_read_pcm_frames_f32()`, `drwav_read_pcm_frames_s32()` and `drwav_read_pcm_frames_s16()` + to read and convert audio data to 32-bit floating point, signed 32-bit integer and signed 16-bit integer samples respectively. +- dr_wav will try to read the WAV file as best it can, even if it's not strictly conformant to the WAV format. +*/ + +#ifndef dr_wav_h +#define dr_wav_h + +#ifdef __cplusplus +extern "C" { +#endif + +#define DRWAV_STRINGIFY(x) #x +#define DRWAV_XSTRINGIFY(x) DRWAV_STRINGIFY(x) + +#define DRWAV_VERSION_MAJOR 0 +#define DRWAV_VERSION_MINOR 13 +#define DRWAV_VERSION_REVISION 16 +#define DRWAV_VERSION_STRING DRWAV_XSTRINGIFY(DRWAV_VERSION_MAJOR) "." DRWAV_XSTRINGIFY(DRWAV_VERSION_MINOR) "." DRWAV_XSTRINGIFY(DRWAV_VERSION_REVISION) + +#include /* For size_t. */ + +/* Sized Types */ +typedef signed char drwav_int8; +typedef unsigned char drwav_uint8; +typedef signed short drwav_int16; +typedef unsigned short drwav_uint16; +typedef signed int drwav_int32; +typedef unsigned int drwav_uint32; +#if defined(_MSC_VER) && !defined(__clang__) + typedef signed __int64 drwav_int64; + typedef unsigned __int64 drwav_uint64; +#else + #if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wlong-long" + #if defined(__clang__) + #pragma GCC diagnostic ignored "-Wc++11-long-long" + #endif + #endif + typedef signed long long drwav_int64; + typedef unsigned long long drwav_uint64; + #if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) + #pragma GCC diagnostic pop + #endif +#endif +#if defined(__LP64__) || defined(_WIN64) || (defined(__x86_64__) && !defined(__ILP32__)) || defined(_M_X64) || defined(__ia64) || defined (_M_IA64) || defined(__aarch64__) || defined(_M_ARM64) || defined(__powerpc64__) + typedef drwav_uint64 drwav_uintptr; +#else + typedef drwav_uint32 drwav_uintptr; +#endif +typedef drwav_uint8 drwav_bool8; +typedef drwav_uint32 drwav_bool32; +#define DRWAV_TRUE 1 +#define DRWAV_FALSE 0 +/* End Sized Types */ + +/* Decorations */ +#if !defined(DRWAV_API) + #if defined(DRWAV_DLL) + #if defined(_WIN32) + #define DRWAV_DLL_IMPORT __declspec(dllimport) + #define DRWAV_DLL_EXPORT __declspec(dllexport) + #define DRWAV_DLL_PRIVATE static + #else + #if defined(__GNUC__) && __GNUC__ >= 4 + #define DRWAV_DLL_IMPORT __attribute__((visibility("default"))) + #define DRWAV_DLL_EXPORT __attribute__((visibility("default"))) + #define DRWAV_DLL_PRIVATE __attribute__((visibility("hidden"))) + #else + #define DRWAV_DLL_IMPORT + #define DRWAV_DLL_EXPORT + #define DRWAV_DLL_PRIVATE static + #endif + #endif + + #if defined(DR_WAV_IMPLEMENTATION) || defined(DRWAV_IMPLEMENTATION) + #define DRWAV_API DRWAV_DLL_EXPORT + #else + #define DRWAV_API DRWAV_DLL_IMPORT + #endif + #define DRWAV_PRIVATE DRWAV_DLL_PRIVATE + #else + #define DRWAV_API extern + #define DRWAV_PRIVATE static + #endif +#endif +/* End Decorations */ + +/* Result Codes */ +typedef drwav_int32 drwav_result; +#define DRWAV_SUCCESS 0 +#define DRWAV_ERROR -1 /* A generic error. */ +#define DRWAV_INVALID_ARGS -2 +#define DRWAV_INVALID_OPERATION -3 +#define DRWAV_OUT_OF_MEMORY -4 +#define DRWAV_OUT_OF_RANGE -5 +#define DRWAV_ACCESS_DENIED -6 +#define DRWAV_DOES_NOT_EXIST -7 +#define DRWAV_ALREADY_EXISTS -8 +#define DRWAV_TOO_MANY_OPEN_FILES -9 +#define DRWAV_INVALID_FILE -10 +#define DRWAV_TOO_BIG -11 +#define DRWAV_PATH_TOO_LONG -12 +#define DRWAV_NAME_TOO_LONG -13 +#define DRWAV_NOT_DIRECTORY -14 +#define DRWAV_IS_DIRECTORY -15 +#define DRWAV_DIRECTORY_NOT_EMPTY -16 +#define DRWAV_END_OF_FILE -17 +#define DRWAV_NO_SPACE -18 +#define DRWAV_BUSY -19 +#define DRWAV_IO_ERROR -20 +#define DRWAV_INTERRUPT -21 +#define DRWAV_UNAVAILABLE -22 +#define DRWAV_ALREADY_IN_USE -23 +#define DRWAV_BAD_ADDRESS -24 +#define DRWAV_BAD_SEEK -25 +#define DRWAV_BAD_PIPE -26 +#define DRWAV_DEADLOCK -27 +#define DRWAV_TOO_MANY_LINKS -28 +#define DRWAV_NOT_IMPLEMENTED -29 +#define DRWAV_NO_MESSAGE -30 +#define DRWAV_BAD_MESSAGE -31 +#define DRWAV_NO_DATA_AVAILABLE -32 +#define DRWAV_INVALID_DATA -33 +#define DRWAV_TIMEOUT -34 +#define DRWAV_NO_NETWORK -35 +#define DRWAV_NOT_UNIQUE -36 +#define DRWAV_NOT_SOCKET -37 +#define DRWAV_NO_ADDRESS -38 +#define DRWAV_BAD_PROTOCOL -39 +#define DRWAV_PROTOCOL_UNAVAILABLE -40 +#define DRWAV_PROTOCOL_NOT_SUPPORTED -41 +#define DRWAV_PROTOCOL_FAMILY_NOT_SUPPORTED -42 +#define DRWAV_ADDRESS_FAMILY_NOT_SUPPORTED -43 +#define DRWAV_SOCKET_NOT_SUPPORTED -44 +#define DRWAV_CONNECTION_RESET -45 +#define DRWAV_ALREADY_CONNECTED -46 +#define DRWAV_NOT_CONNECTED -47 +#define DRWAV_CONNECTION_REFUSED -48 +#define DRWAV_NO_HOST -49 +#define DRWAV_IN_PROGRESS -50 +#define DRWAV_CANCELLED -51 +#define DRWAV_MEMORY_ALREADY_MAPPED -52 +#define DRWAV_AT_END -53 +/* End Result Codes */ + +/* Common data formats. */ +#define DR_WAVE_FORMAT_PCM 0x1 +#define DR_WAVE_FORMAT_ADPCM 0x2 +#define DR_WAVE_FORMAT_IEEE_FLOAT 0x3 +#define DR_WAVE_FORMAT_ALAW 0x6 +#define DR_WAVE_FORMAT_MULAW 0x7 +#define DR_WAVE_FORMAT_DVI_ADPCM 0x11 +#define DR_WAVE_FORMAT_EXTENSIBLE 0xFFFE + +/* Flags to pass into drwav_init_ex(), etc. */ +#define DRWAV_SEQUENTIAL 0x00000001 +#define DRWAV_WITH_METADATA 0x00000002 + +DRWAV_API void drwav_version(drwav_uint32* pMajor, drwav_uint32* pMinor, drwav_uint32* pRevision); +DRWAV_API const char* drwav_version_string(void); + +/* Allocation Callbacks */ +typedef struct +{ + void* pUserData; + void* (* onMalloc)(size_t sz, void* pUserData); + void* (* onRealloc)(void* p, size_t sz, void* pUserData); + void (* onFree)(void* p, void* pUserData); +} drwav_allocation_callbacks; +/* End Allocation Callbacks */ + +typedef enum +{ + drwav_seek_origin_start, + drwav_seek_origin_current +} drwav_seek_origin; + +typedef enum +{ + drwav_container_riff, + drwav_container_rifx, + drwav_container_w64, + drwav_container_rf64, + drwav_container_aiff +} drwav_container; + +typedef struct +{ + union + { + drwav_uint8 fourcc[4]; + drwav_uint8 guid[16]; + } id; + + /* The size in bytes of the chunk. */ + drwav_uint64 sizeInBytes; + + /* + RIFF = 2 byte alignment. + W64 = 8 byte alignment. + */ + unsigned int paddingSize; +} drwav_chunk_header; + +typedef struct +{ + /* + The format tag exactly as specified in the wave file's "fmt" chunk. This can be used by applications + that require support for data formats not natively supported by dr_wav. + */ + drwav_uint16 formatTag; + + /* The number of channels making up the audio data. When this is set to 1 it is mono, 2 is stereo, etc. */ + drwav_uint16 channels; + + /* The sample rate. Usually set to something like 44100. */ + drwav_uint32 sampleRate; + + /* Average bytes per second. You probably don't need this, but it's left here for informational purposes. */ + drwav_uint32 avgBytesPerSec; + + /* Block align. This is equal to the number of channels * bytes per sample. */ + drwav_uint16 blockAlign; + + /* Bits per sample. */ + drwav_uint16 bitsPerSample; + + /* The size of the extended data. Only used internally for validation, but left here for informational purposes. */ + drwav_uint16 extendedSize; + + /* + The number of valid bits per sample. When is equal to WAVE_FORMAT_EXTENSIBLE, + is always rounded up to the nearest multiple of 8. This variable contains information about exactly how + many bits are valid per sample. Mainly used for informational purposes. + */ + drwav_uint16 validBitsPerSample; + + /* The channel mask. Not used at the moment. */ + drwav_uint32 channelMask; + + /* The sub-format, exactly as specified by the wave file. */ + drwav_uint8 subFormat[16]; +} drwav_fmt; + +DRWAV_API drwav_uint16 drwav_fmt_get_format(const drwav_fmt* pFMT); + + +/* +Callback for when data is read. Return value is the number of bytes actually read. + +pUserData [in] The user data that was passed to drwav_init() and family. +pBufferOut [out] The output buffer. +bytesToRead [in] The number of bytes to read. + +Returns the number of bytes actually read. + +A return value of less than bytesToRead indicates the end of the stream. Do _not_ return from this callback until +either the entire bytesToRead is filled or you have reached the end of the stream. +*/ +typedef size_t (* drwav_read_proc)(void* pUserData, void* pBufferOut, size_t bytesToRead); + +/* +Callback for when data is written. Returns value is the number of bytes actually written. + +pUserData [in] The user data that was passed to drwav_init_write() and family. +pData [out] A pointer to the data to write. +bytesToWrite [in] The number of bytes to write. + +Returns the number of bytes actually written. + +If the return value differs from bytesToWrite, it indicates an error. +*/ +typedef size_t (* drwav_write_proc)(void* pUserData, const void* pData, size_t bytesToWrite); + +/* +Callback for when data needs to be seeked. + +pUserData [in] The user data that was passed to drwav_init() and family. +offset [in] The number of bytes to move, relative to the origin. Will never be negative. +origin [in] The origin of the seek - the current position or the start of the stream. + +Returns whether or not the seek was successful. + +Whether or not it is relative to the beginning or current position is determined by the "origin" parameter which will be either drwav_seek_origin_start or +drwav_seek_origin_current. +*/ +typedef drwav_bool32 (* drwav_seek_proc)(void* pUserData, int offset, drwav_seek_origin origin); + +/* +Callback for when drwav_init_ex() finds a chunk. + +pChunkUserData [in] The user data that was passed to the pChunkUserData parameter of drwav_init_ex() and family. +onRead [in] A pointer to the function to call when reading. +onSeek [in] A pointer to the function to call when seeking. +pReadSeekUserData [in] The user data that was passed to the pReadSeekUserData parameter of drwav_init_ex() and family. +pChunkHeader [in] A pointer to an object containing basic header information about the chunk. Use this to identify the chunk. +container [in] Whether or not the WAV file is a RIFF or Wave64 container. If you're unsure of the difference, assume RIFF. +pFMT [in] A pointer to the object containing the contents of the "fmt" chunk. + +Returns the number of bytes read + seeked. + +To read data from the chunk, call onRead(), passing in pReadSeekUserData as the first parameter. Do the same for seeking with onSeek(). The return value must +be the total number of bytes you have read _plus_ seeked. + +Use the `container` argument to discriminate the fields in `pChunkHeader->id`. If the container is `drwav_container_riff` or `drwav_container_rf64` you should +use `id.fourcc`, otherwise you should use `id.guid`. + +The `pFMT` parameter can be used to determine the data format of the wave file. Use `drwav_fmt_get_format()` to get the sample format, which will be one of the +`DR_WAVE_FORMAT_*` identifiers. + +The read pointer will be sitting on the first byte after the chunk's header. You must not attempt to read beyond the boundary of the chunk. +*/ +typedef drwav_uint64 (* drwav_chunk_proc)(void* pChunkUserData, drwav_read_proc onRead, drwav_seek_proc onSeek, void* pReadSeekUserData, const drwav_chunk_header* pChunkHeader, drwav_container container, const drwav_fmt* pFMT); + + +/* Structure for internal use. Only used for loaders opened with drwav_init_memory(). */ +typedef struct +{ + const drwav_uint8* data; + size_t dataSize; + size_t currentReadPos; +} drwav__memory_stream; + +/* Structure for internal use. Only used for writers opened with drwav_init_memory_write(). */ +typedef struct +{ + void** ppData; + size_t* pDataSize; + size_t dataSize; + size_t dataCapacity; + size_t currentWritePos; +} drwav__memory_stream_write; + +typedef struct +{ + drwav_container container; /* RIFF, W64. */ + drwav_uint32 format; /* DR_WAVE_FORMAT_* */ + drwav_uint32 channels; + drwav_uint32 sampleRate; + drwav_uint32 bitsPerSample; +} drwav_data_format; + +typedef enum +{ + drwav_metadata_type_none = 0, + + /* + Unknown simply means a chunk that drwav does not handle specifically. You can still ask to + receive these chunks as metadata objects. It is then up to you to interpret the chunk's data. + You can also write unknown metadata to a wav file. Be careful writing unknown chunks if you + have also edited the audio data. The unknown chunks could represent offsets/sizes that no + longer correctly correspond to the audio data. + */ + drwav_metadata_type_unknown = 1 << 0, + + /* Only 1 of each of these metadata items are allowed in a wav file. */ + drwav_metadata_type_smpl = 1 << 1, + drwav_metadata_type_inst = 1 << 2, + drwav_metadata_type_cue = 1 << 3, + drwav_metadata_type_acid = 1 << 4, + drwav_metadata_type_bext = 1 << 5, + + /* + Wav files often have a LIST chunk. This is a chunk that contains a set of subchunks. For this + higher-level metadata API, we don't make a distinction between a regular chunk and a LIST + subchunk. Instead, they are all just 'metadata' items. + + There can be multiple of these metadata items in a wav file. + */ + drwav_metadata_type_list_label = 1 << 6, + drwav_metadata_type_list_note = 1 << 7, + drwav_metadata_type_list_labelled_cue_region = 1 << 8, + + drwav_metadata_type_list_info_software = 1 << 9, + drwav_metadata_type_list_info_copyright = 1 << 10, + drwav_metadata_type_list_info_title = 1 << 11, + drwav_metadata_type_list_info_artist = 1 << 12, + drwav_metadata_type_list_info_comment = 1 << 13, + drwav_metadata_type_list_info_date = 1 << 14, + drwav_metadata_type_list_info_genre = 1 << 15, + drwav_metadata_type_list_info_album = 1 << 16, + drwav_metadata_type_list_info_tracknumber = 1 << 17, + + /* Other type constants for convenience. */ + drwav_metadata_type_list_all_info_strings = drwav_metadata_type_list_info_software + | drwav_metadata_type_list_info_copyright + | drwav_metadata_type_list_info_title + | drwav_metadata_type_list_info_artist + | drwav_metadata_type_list_info_comment + | drwav_metadata_type_list_info_date + | drwav_metadata_type_list_info_genre + | drwav_metadata_type_list_info_album + | drwav_metadata_type_list_info_tracknumber, + + drwav_metadata_type_list_all_adtl = drwav_metadata_type_list_label + | drwav_metadata_type_list_note + | drwav_metadata_type_list_labelled_cue_region, + + drwav_metadata_type_all = -2, /*0xFFFFFFFF & ~drwav_metadata_type_unknown,*/ + drwav_metadata_type_all_including_unknown = -1 /*0xFFFFFFFF,*/ +} drwav_metadata_type; + +/* +Sampler Metadata + +The sampler chunk contains information about how a sound should be played in the context of a whole +audio production, and when used in a sampler. See https://en.wikipedia.org/wiki/Sample-based_synthesis. +*/ +typedef enum +{ + drwav_smpl_loop_type_forward = 0, + drwav_smpl_loop_type_pingpong = 1, + drwav_smpl_loop_type_backward = 2 +} drwav_smpl_loop_type; + +typedef struct +{ + /* The ID of the associated cue point, see drwav_cue and drwav_cue_point. As with all cue point IDs, this can correspond to a label chunk to give this loop a name, see drwav_list_label_or_note. */ + drwav_uint32 cuePointId; + + /* See drwav_smpl_loop_type. */ + drwav_uint32 type; + + /* The byte offset of the first sample to be played in the loop. */ + drwav_uint32 firstSampleByteOffset; + + /* The byte offset into the audio data of the last sample to be played in the loop. */ + drwav_uint32 lastSampleByteOffset; + + /* A value to represent that playback should occur at a point between samples. This value ranges from 0 to UINT32_MAX. Where a value of 0 means no fraction, and a value of (UINT32_MAX / 2) would mean half a sample. */ + drwav_uint32 sampleFraction; + + /* Number of times to play the loop. 0 means loop infinitely. */ + drwav_uint32 playCount; +} drwav_smpl_loop; + +typedef struct +{ + /* IDs for a particular MIDI manufacturer. 0 if not used. */ + drwav_uint32 manufacturerId; + drwav_uint32 productId; + + /* The period of 1 sample in nanoseconds. */ + drwav_uint32 samplePeriodNanoseconds; + + /* The MIDI root note of this file. 0 to 127. */ + drwav_uint32 midiUnityNote; + + /* The fraction of a semitone up from the given MIDI note. This is a value from 0 to UINT32_MAX, where 0 means no change and (UINT32_MAX / 2) is half a semitone (AKA 50 cents). */ + drwav_uint32 midiPitchFraction; + + /* Data relating to SMPTE standards which are used for syncing audio and video. 0 if not used. */ + drwav_uint32 smpteFormat; + drwav_uint32 smpteOffset; + + /* drwav_smpl_loop loops. */ + drwav_uint32 sampleLoopCount; + + /* Optional sampler-specific data. */ + drwav_uint32 samplerSpecificDataSizeInBytes; + + drwav_smpl_loop* pLoops; + drwav_uint8* pSamplerSpecificData; +} drwav_smpl; + +/* +Instrument Metadata + +The inst metadata contains data about how a sound should be played as part of an instrument. This +commonly read by samplers. See https://en.wikipedia.org/wiki/Sample-based_synthesis. +*/ +typedef struct +{ + drwav_int8 midiUnityNote; /* The root note of the audio as a MIDI note number. 0 to 127. */ + drwav_int8 fineTuneCents; /* -50 to +50 */ + drwav_int8 gainDecibels; /* -64 to +64 */ + drwav_int8 lowNote; /* 0 to 127 */ + drwav_int8 highNote; /* 0 to 127 */ + drwav_int8 lowVelocity; /* 1 to 127 */ + drwav_int8 highVelocity; /* 1 to 127 */ +} drwav_inst; + +/* +Cue Metadata + +Cue points are markers at specific points in the audio. They often come with an associated piece of +drwav_list_label_or_note metadata which contains the text for the marker. +*/ +typedef struct +{ + /* Unique identification value. */ + drwav_uint32 id; + + /* Set to 0. This is only relevant if there is a 'playlist' chunk - which is not supported by dr_wav. */ + drwav_uint32 playOrderPosition; + + /* Should always be "data". This represents the fourcc value of the chunk that this cue point corresponds to. dr_wav only supports a single data chunk so this should always be "data". */ + drwav_uint8 dataChunkId[4]; + + /* Set to 0. This is only relevant if there is a wave list chunk. dr_wav, like lots of readers/writers, do not support this. */ + drwav_uint32 chunkStart; + + /* Set to 0 for uncompressed formats. Else the last byte in compressed wave data where decompression can begin to find the value of the corresponding sample value. */ + drwav_uint32 blockStart; + + /* For uncompressed formats this is the byte offset of the cue point into the audio data. For compressed formats this is relative to the block specified with blockStart. */ + drwav_uint32 sampleByteOffset; +} drwav_cue_point; + +typedef struct +{ + drwav_uint32 cuePointCount; + drwav_cue_point *pCuePoints; +} drwav_cue; + +/* +Acid Metadata + +This chunk contains some information about the time signature and the tempo of the audio. +*/ +typedef enum +{ + drwav_acid_flag_one_shot = 1, /* If this is not set, then it is a loop instead of a one-shot. */ + drwav_acid_flag_root_note_set = 2, + drwav_acid_flag_stretch = 4, + drwav_acid_flag_disk_based = 8, + drwav_acid_flag_acidizer = 16 /* Not sure what this means. */ +} drwav_acid_flag; + +typedef struct +{ + /* A bit-field, see drwav_acid_flag. */ + drwav_uint32 flags; + + /* Valid if flags contains drwav_acid_flag_root_note_set. It represents the MIDI root note the file - a value from 0 to 127. */ + drwav_uint16 midiUnityNote; + + /* Reserved values that should probably be ignored. reserved1 seems to often be 128 and reserved2 is 0. */ + drwav_uint16 reserved1; + float reserved2; + + /* Number of beats. */ + drwav_uint32 numBeats; + + /* The time signature of the audio. */ + drwav_uint16 meterDenominator; + drwav_uint16 meterNumerator; + + /* Beats per minute of the track. Setting a value of 0 suggests that there is no tempo. */ + float tempo; +} drwav_acid; + +/* +Cue Label or Note metadata + +These are 2 different types of metadata, but they have the exact same format. Labels tend to be the +more common and represent a short name for a cue point. Notes might be used to represent a longer +comment. +*/ +typedef struct +{ + /* The ID of a cue point that this label or note corresponds to. */ + drwav_uint32 cuePointId; + + /* Size of the string not including any null terminator. */ + drwav_uint32 stringLength; + + /* The string. The *init_with_metadata functions null terminate this for convenience. */ + char* pString; +} drwav_list_label_or_note; + +/* +BEXT metadata, also known as Broadcast Wave Format (BWF) + +This metadata adds some extra description to an audio file. You must check the version field to +determine if the UMID or the loudness fields are valid. +*/ +typedef struct +{ + /* + These top 3 fields, and the umid field are actually defined in the standard as a statically + sized buffers. In order to reduce the size of this struct (and therefore the union in the + metadata struct), we instead store these as pointers. + */ + char* pDescription; /* Can be NULL or a null-terminated string, must be <= 256 characters. */ + char* pOriginatorName; /* Can be NULL or a null-terminated string, must be <= 32 characters. */ + char* pOriginatorReference; /* Can be NULL or a null-terminated string, must be <= 32 characters. */ + char pOriginationDate[10]; /* ASCII "yyyy:mm:dd". */ + char pOriginationTime[8]; /* ASCII "hh:mm:ss". */ + drwav_uint64 timeReference; /* First sample count since midnight. */ + drwav_uint16 version; /* Version of the BWF, check this to see if the fields below are valid. */ + + /* + Unrestricted ASCII characters containing a collection of strings terminated by CR/LF. Each + string shall contain a description of a coding process applied to the audio data. + */ + char* pCodingHistory; + drwav_uint32 codingHistorySize; + + /* Fields below this point are only valid if the version is 1 or above. */ + drwav_uint8* pUMID; /* Exactly 64 bytes of SMPTE UMID */ + + /* Fields below this point are only valid if the version is 2 or above. */ + drwav_uint16 loudnessValue; /* Integrated Loudness Value of the file in LUFS (multiplied by 100). */ + drwav_uint16 loudnessRange; /* Loudness Range of the file in LU (multiplied by 100). */ + drwav_uint16 maxTruePeakLevel; /* Maximum True Peak Level of the file expressed as dBTP (multiplied by 100). */ + drwav_uint16 maxMomentaryLoudness; /* Highest value of the Momentary Loudness Level of the file in LUFS (multiplied by 100). */ + drwav_uint16 maxShortTermLoudness; /* Highest value of the Short-Term Loudness Level of the file in LUFS (multiplied by 100). */ +} drwav_bext; + +/* +Info Text Metadata + +There a many different types of information text that can be saved in this format. This is where +things like the album name, the artists, the year it was produced, etc are saved. See +drwav_metadata_type for the full list of types that dr_wav supports. +*/ +typedef struct +{ + /* Size of the string not including any null terminator. */ + drwav_uint32 stringLength; + + /* The string. The *init_with_metadata functions null terminate this for convenience. */ + char* pString; +} drwav_list_info_text; + +/* +Labelled Cue Region Metadata + +The labelled cue region metadata is used to associate some region of audio with text. The region +starts at a cue point, and extends for the given number of samples. +*/ +typedef struct +{ + /* The ID of a cue point that this object corresponds to. */ + drwav_uint32 cuePointId; + + /* The number of samples from the cue point forwards that should be considered this region */ + drwav_uint32 sampleLength; + + /* Four characters used to say what the purpose of this region is. */ + drwav_uint8 purposeId[4]; + + /* Unsure of the exact meanings of these. It appears to be acceptable to set them all to 0. */ + drwav_uint16 country; + drwav_uint16 language; + drwav_uint16 dialect; + drwav_uint16 codePage; + + /* Size of the string not including any null terminator. */ + drwav_uint32 stringLength; + + /* The string. The *init_with_metadata functions null terminate this for convenience. */ + char* pString; +} drwav_list_labelled_cue_region; + +/* +Unknown Metadata + +This chunk just represents a type of chunk that dr_wav does not understand. + +Unknown metadata has a location attached to it. This is because wav files can have a LIST chunk +that contains subchunks. These LIST chunks can be one of two types. An adtl list, or an INFO +list. This enum is used to specify the location of a chunk that dr_wav currently doesn't support. +*/ +typedef enum +{ + drwav_metadata_location_invalid, + drwav_metadata_location_top_level, + drwav_metadata_location_inside_info_list, + drwav_metadata_location_inside_adtl_list +} drwav_metadata_location; + +typedef struct +{ + drwav_uint8 id[4]; + drwav_metadata_location chunkLocation; + drwav_uint32 dataSizeInBytes; + drwav_uint8* pData; +} drwav_unknown_metadata; + +/* +Metadata is saved as a union of all the supported types. +*/ +typedef struct +{ + /* Determines which item in the union is valid. */ + drwav_metadata_type type; + + union + { + drwav_cue cue; + drwav_smpl smpl; + drwav_acid acid; + drwav_inst inst; + drwav_bext bext; + drwav_list_label_or_note labelOrNote; /* List label or list note. */ + drwav_list_labelled_cue_region labelledCueRegion; + drwav_list_info_text infoText; /* Any of the list info types. */ + drwav_unknown_metadata unknown; + } data; +} drwav_metadata; + +typedef struct +{ + /* A pointer to the function to call when more data is needed. */ + drwav_read_proc onRead; + + /* A pointer to the function to call when data needs to be written. Only used when the drwav object is opened in write mode. */ + drwav_write_proc onWrite; + + /* A pointer to the function to call when the wav file needs to be seeked. */ + drwav_seek_proc onSeek; + + /* The user data to pass to callbacks. */ + void* pUserData; + + /* Allocation callbacks. */ + drwav_allocation_callbacks allocationCallbacks; + + + /* Whether or not the WAV file is formatted as a standard RIFF file or W64. */ + drwav_container container; + + + /* Structure containing format information exactly as specified by the wav file. */ + drwav_fmt fmt; + + /* The sample rate. Will be set to something like 44100. */ + drwav_uint32 sampleRate; + + /* The number of channels. This will be set to 1 for monaural streams, 2 for stereo, etc. */ + drwav_uint16 channels; + + /* The bits per sample. Will be set to something like 16, 24, etc. */ + drwav_uint16 bitsPerSample; + + /* Equal to fmt.formatTag, or the value specified by fmt.subFormat if fmt.formatTag is equal to 65534 (WAVE_FORMAT_EXTENSIBLE). */ + drwav_uint16 translatedFormatTag; + + /* The total number of PCM frames making up the audio data. */ + drwav_uint64 totalPCMFrameCount; + + + /* The size in bytes of the data chunk. */ + drwav_uint64 dataChunkDataSize; + + /* The position in the stream of the first data byte of the data chunk. This is used for seeking. */ + drwav_uint64 dataChunkDataPos; + + /* The number of bytes remaining in the data chunk. */ + drwav_uint64 bytesRemaining; + + /* The current read position in PCM frames. */ + drwav_uint64 readCursorInPCMFrames; + + + /* + Only used in sequential write mode. Keeps track of the desired size of the "data" chunk at the point of initialization time. Always + set to 0 for non-sequential writes and when the drwav object is opened in read mode. Used for validation. + */ + drwav_uint64 dataChunkDataSizeTargetWrite; + + /* Keeps track of whether or not the wav writer was initialized in sequential mode. */ + drwav_bool32 isSequentialWrite; + + + /* A array of metadata. This is valid after the *init_with_metadata call returns. It will be valid until drwav_uninit() is called. You can take ownership of this data with drwav_take_ownership_of_metadata(). */ + drwav_metadata* pMetadata; + drwav_uint32 metadataCount; + + + /* A hack to avoid a DRWAV_MALLOC() when opening a decoder with drwav_init_memory(). */ + drwav__memory_stream memoryStream; + drwav__memory_stream_write memoryStreamWrite; + + + /* Microsoft ADPCM specific data. */ + struct + { + drwav_uint32 bytesRemainingInBlock; + drwav_uint16 predictor[2]; + drwav_int32 delta[2]; + drwav_int32 cachedFrames[4]; /* Samples are stored in this cache during decoding. */ + drwav_uint32 cachedFrameCount; + drwav_int32 prevFrames[2][2]; /* The previous 2 samples for each channel (2 channels at most). */ + } msadpcm; + + /* IMA ADPCM specific data. */ + struct + { + drwav_uint32 bytesRemainingInBlock; + drwav_int32 predictor[2]; + drwav_int32 stepIndex[2]; + drwav_int32 cachedFrames[16]; /* Samples are stored in this cache during decoding. */ + drwav_uint32 cachedFrameCount; + } ima; + + /* AIFF specific data. */ + struct + { + drwav_bool8 isLE; /* Will be set to true if the audio data is little-endian encoded. */ + drwav_bool8 isUnsigned; /* Only used for 8-bit samples. When set to true, will be treated as unsigned. */ + } aiff; +} drwav; + + +/* +Initializes a pre-allocated drwav object for reading. + +pWav [out] A pointer to the drwav object being initialized. +onRead [in] The function to call when data needs to be read from the client. +onSeek [in] The function to call when the read position of the client data needs to move. +onChunk [in, optional] The function to call when a chunk is enumerated at initialized time. +pUserData, pReadSeekUserData [in, optional] A pointer to application defined data that will be passed to onRead and onSeek. +pChunkUserData [in, optional] A pointer to application defined data that will be passed to onChunk. +flags [in, optional] A set of flags for controlling how things are loaded. + +Returns true if successful; false otherwise. + +Close the loader with drwav_uninit(). + +This is the lowest level function for initializing a WAV file. You can also use drwav_init_file() and drwav_init_memory() +to open the stream from a file or from a block of memory respectively. + +Possible values for flags: + DRWAV_SEQUENTIAL: Never perform a backwards seek while loading. This disables the chunk callback and will cause this function + to return as soon as the data chunk is found. Any chunks after the data chunk will be ignored. + +drwav_init() is equivalent to "drwav_init_ex(pWav, onRead, onSeek, NULL, pUserData, NULL, 0);". + +The onChunk callback is not called for the WAVE or FMT chunks. The contents of the FMT chunk can be read from pWav->fmt +after the function returns. + +See also: drwav_init_file(), drwav_init_memory(), drwav_uninit() +*/ +DRWAV_API drwav_bool32 drwav_init(drwav* pWav, drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, const drwav_allocation_callbacks* pAllocationCallbacks); +DRWAV_API drwav_bool32 drwav_init_ex(drwav* pWav, drwav_read_proc onRead, drwav_seek_proc onSeek, drwav_chunk_proc onChunk, void* pReadSeekUserData, void* pChunkUserData, drwav_uint32 flags, const drwav_allocation_callbacks* pAllocationCallbacks); +DRWAV_API drwav_bool32 drwav_init_with_metadata(drwav* pWav, drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, drwav_uint32 flags, const drwav_allocation_callbacks* pAllocationCallbacks); + +/* +Initializes a pre-allocated drwav object for writing. + +onWrite [in] The function to call when data needs to be written. +onSeek [in] The function to call when the write position needs to move. +pUserData [in, optional] A pointer to application defined data that will be passed to onWrite and onSeek. +metadata, numMetadata [in, optional] An array of metadata objects that should be written to the file. The array is not edited. You are responsible for this metadata memory and it must maintain valid until drwav_uninit() is called. + +Returns true if successful; false otherwise. + +Close the writer with drwav_uninit(). + +This is the lowest level function for initializing a WAV file. You can also use drwav_init_file_write() and drwav_init_memory_write() +to open the stream from a file or from a block of memory respectively. + +If the total sample count is known, you can use drwav_init_write_sequential(). This avoids the need for dr_wav to perform +a post-processing step for storing the total sample count and the size of the data chunk which requires a backwards seek. + +See also: drwav_init_file_write(), drwav_init_memory_write(), drwav_uninit() +*/ +DRWAV_API drwav_bool32 drwav_init_write(drwav* pWav, const drwav_data_format* pFormat, drwav_write_proc onWrite, drwav_seek_proc onSeek, void* pUserData, const drwav_allocation_callbacks* pAllocationCallbacks); +DRWAV_API drwav_bool32 drwav_init_write_sequential(drwav* pWav, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, drwav_write_proc onWrite, void* pUserData, const drwav_allocation_callbacks* pAllocationCallbacks); +DRWAV_API drwav_bool32 drwav_init_write_sequential_pcm_frames(drwav* pWav, const drwav_data_format* pFormat, drwav_uint64 totalPCMFrameCount, drwav_write_proc onWrite, void* pUserData, const drwav_allocation_callbacks* pAllocationCallbacks); +DRWAV_API drwav_bool32 drwav_init_write_with_metadata(drwav* pWav, const drwav_data_format* pFormat, drwav_write_proc onWrite, drwav_seek_proc onSeek, void* pUserData, const drwav_allocation_callbacks* pAllocationCallbacks, drwav_metadata* pMetadata, drwav_uint32 metadataCount); + +/* +Utility function to determine the target size of the entire data to be written (including all headers and chunks). + +Returns the target size in bytes. + +The metadata argument can be NULL meaning no metadata exists. + +Useful if the application needs to know the size to allocate. + +Only writing to the RIFF chunk and one data chunk is currently supported. + +See also: drwav_init_write(), drwav_init_file_write(), drwav_init_memory_write() +*/ +DRWAV_API drwav_uint64 drwav_target_write_size_bytes(const drwav_data_format* pFormat, drwav_uint64 totalFrameCount, drwav_metadata* pMetadata, drwav_uint32 metadataCount); + +/* +Take ownership of the metadata objects that were allocated via one of the init_with_metadata() function calls. The init_with_metdata functions perform a single heap allocation for this metadata. + +Useful if you want the data to persist beyond the lifetime of the drwav object. + +You must free the data returned from this function using drwav_free(). +*/ +DRWAV_API drwav_metadata* drwav_take_ownership_of_metadata(drwav* pWav); + +/* +Uninitializes the given drwav object. + +Use this only for objects initialized with drwav_init*() functions (drwav_init(), drwav_init_ex(), drwav_init_write(), drwav_init_write_sequential()). +*/ +DRWAV_API drwav_result drwav_uninit(drwav* pWav); + + +/* +Reads raw audio data. + +This is the lowest level function for reading audio data. It simply reads the given number of +bytes of the raw internal sample data. + +Consider using drwav_read_pcm_frames_s16(), drwav_read_pcm_frames_s32() or drwav_read_pcm_frames_f32() for +reading sample data in a consistent format. + +pBufferOut can be NULL in which case a seek will be performed. + +Returns the number of bytes actually read. +*/ +DRWAV_API size_t drwav_read_raw(drwav* pWav, size_t bytesToRead, void* pBufferOut); + +/* +Reads up to the specified number of PCM frames from the WAV file. + +The output data will be in the file's internal format, converted to native-endian byte order. Use +drwav_read_pcm_frames_s16/f32/s32() to read data in a specific format. + +If the return value is less than it means the end of the file has been reached or +you have requested more PCM frames than can possibly fit in the output buffer. + +This function will only work when sample data is of a fixed size and uncompressed. If you are +using a compressed format consider using drwav_read_raw() or drwav_read_pcm_frames_s16/s32/f32(). + +pBufferOut can be NULL in which case a seek will be performed. +*/ +DRWAV_API drwav_uint64 drwav_read_pcm_frames(drwav* pWav, drwav_uint64 framesToRead, void* pBufferOut); +DRWAV_API drwav_uint64 drwav_read_pcm_frames_le(drwav* pWav, drwav_uint64 framesToRead, void* pBufferOut); +DRWAV_API drwav_uint64 drwav_read_pcm_frames_be(drwav* pWav, drwav_uint64 framesToRead, void* pBufferOut); + +/* +Seeks to the given PCM frame. + +Returns true if successful; false otherwise. +*/ +DRWAV_API drwav_bool32 drwav_seek_to_pcm_frame(drwav* pWav, drwav_uint64 targetFrameIndex); + +/* +Retrieves the current read position in pcm frames. +*/ +DRWAV_API drwav_result drwav_get_cursor_in_pcm_frames(drwav* pWav, drwav_uint64* pCursor); + +/* +Retrieves the length of the file. +*/ +DRWAV_API drwav_result drwav_get_length_in_pcm_frames(drwav* pWav, drwav_uint64* pLength); + + +/* +Writes raw audio data. + +Returns the number of bytes actually written. If this differs from bytesToWrite, it indicates an error. +*/ +DRWAV_API size_t drwav_write_raw(drwav* pWav, size_t bytesToWrite, const void* pData); + +/* +Writes PCM frames. + +Returns the number of PCM frames written. + +Input samples need to be in native-endian byte order. On big-endian architectures the input data will be converted to +little-endian. Use drwav_write_raw() to write raw audio data without performing any conversion. +*/ +DRWAV_API drwav_uint64 drwav_write_pcm_frames(drwav* pWav, drwav_uint64 framesToWrite, const void* pData); +DRWAV_API drwav_uint64 drwav_write_pcm_frames_le(drwav* pWav, drwav_uint64 framesToWrite, const void* pData); +DRWAV_API drwav_uint64 drwav_write_pcm_frames_be(drwav* pWav, drwav_uint64 framesToWrite, const void* pData); + +/* Conversion Utilities */ +#ifndef DR_WAV_NO_CONVERSION_API + +/* +Reads a chunk of audio data and converts it to signed 16-bit PCM samples. + +pBufferOut can be NULL in which case a seek will be performed. + +Returns the number of PCM frames actually read. + +If the return value is less than it means the end of the file has been reached. +*/ +DRWAV_API drwav_uint64 drwav_read_pcm_frames_s16(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut); +DRWAV_API drwav_uint64 drwav_read_pcm_frames_s16le(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut); +DRWAV_API drwav_uint64 drwav_read_pcm_frames_s16be(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut); + +/* Low-level function for converting unsigned 8-bit PCM samples to signed 16-bit PCM samples. */ +DRWAV_API void drwav_u8_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t sampleCount); + +/* Low-level function for converting signed 24-bit PCM samples to signed 16-bit PCM samples. */ +DRWAV_API void drwav_s24_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t sampleCount); + +/* Low-level function for converting signed 32-bit PCM samples to signed 16-bit PCM samples. */ +DRWAV_API void drwav_s32_to_s16(drwav_int16* pOut, const drwav_int32* pIn, size_t sampleCount); + +/* Low-level function for converting IEEE 32-bit floating point samples to signed 16-bit PCM samples. */ +DRWAV_API void drwav_f32_to_s16(drwav_int16* pOut, const float* pIn, size_t sampleCount); + +/* Low-level function for converting IEEE 64-bit floating point samples to signed 16-bit PCM samples. */ +DRWAV_API void drwav_f64_to_s16(drwav_int16* pOut, const double* pIn, size_t sampleCount); + +/* Low-level function for converting A-law samples to signed 16-bit PCM samples. */ +DRWAV_API void drwav_alaw_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t sampleCount); + +/* Low-level function for converting u-law samples to signed 16-bit PCM samples. */ +DRWAV_API void drwav_mulaw_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t sampleCount); + + +/* +Reads a chunk of audio data and converts it to IEEE 32-bit floating point samples. + +pBufferOut can be NULL in which case a seek will be performed. + +Returns the number of PCM frames actually read. + +If the return value is less than it means the end of the file has been reached. +*/ +DRWAV_API drwav_uint64 drwav_read_pcm_frames_f32(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut); +DRWAV_API drwav_uint64 drwav_read_pcm_frames_f32le(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut); +DRWAV_API drwav_uint64 drwav_read_pcm_frames_f32be(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut); + +/* Low-level function for converting unsigned 8-bit PCM samples to IEEE 32-bit floating point samples. */ +DRWAV_API void drwav_u8_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount); + +/* Low-level function for converting signed 16-bit PCM samples to IEEE 32-bit floating point samples. */ +DRWAV_API void drwav_s16_to_f32(float* pOut, const drwav_int16* pIn, size_t sampleCount); + +/* Low-level function for converting signed 24-bit PCM samples to IEEE 32-bit floating point samples. */ +DRWAV_API void drwav_s24_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount); + +/* Low-level function for converting signed 32-bit PCM samples to IEEE 32-bit floating point samples. */ +DRWAV_API void drwav_s32_to_f32(float* pOut, const drwav_int32* pIn, size_t sampleCount); + +/* Low-level function for converting IEEE 64-bit floating point samples to IEEE 32-bit floating point samples. */ +DRWAV_API void drwav_f64_to_f32(float* pOut, const double* pIn, size_t sampleCount); + +/* Low-level function for converting A-law samples to IEEE 32-bit floating point samples. */ +DRWAV_API void drwav_alaw_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount); + +/* Low-level function for converting u-law samples to IEEE 32-bit floating point samples. */ +DRWAV_API void drwav_mulaw_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount); + + +/* +Reads a chunk of audio data and converts it to signed 32-bit PCM samples. + +pBufferOut can be NULL in which case a seek will be performed. + +Returns the number of PCM frames actually read. + +If the return value is less than it means the end of the file has been reached. +*/ +DRWAV_API drwav_uint64 drwav_read_pcm_frames_s32(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut); +DRWAV_API drwav_uint64 drwav_read_pcm_frames_s32le(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut); +DRWAV_API drwav_uint64 drwav_read_pcm_frames_s32be(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut); + +/* Low-level function for converting unsigned 8-bit PCM samples to signed 32-bit PCM samples. */ +DRWAV_API void drwav_u8_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t sampleCount); + +/* Low-level function for converting signed 16-bit PCM samples to signed 32-bit PCM samples. */ +DRWAV_API void drwav_s16_to_s32(drwav_int32* pOut, const drwav_int16* pIn, size_t sampleCount); + +/* Low-level function for converting signed 24-bit PCM samples to signed 32-bit PCM samples. */ +DRWAV_API void drwav_s24_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t sampleCount); + +/* Low-level function for converting IEEE 32-bit floating point samples to signed 32-bit PCM samples. */ +DRWAV_API void drwav_f32_to_s32(drwav_int32* pOut, const float* pIn, size_t sampleCount); + +/* Low-level function for converting IEEE 64-bit floating point samples to signed 32-bit PCM samples. */ +DRWAV_API void drwav_f64_to_s32(drwav_int32* pOut, const double* pIn, size_t sampleCount); + +/* Low-level function for converting A-law samples to signed 32-bit PCM samples. */ +DRWAV_API void drwav_alaw_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t sampleCount); + +/* Low-level function for converting u-law samples to signed 32-bit PCM samples. */ +DRWAV_API void drwav_mulaw_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t sampleCount); + +#endif /* DR_WAV_NO_CONVERSION_API */ + + +/* High-Level Convenience Helpers */ + +#ifndef DR_WAV_NO_STDIO +/* +Helper for initializing a wave file for reading using stdio. + +This holds the internal FILE object until drwav_uninit() is called. Keep this in mind if you're caching drwav +objects because the operating system may restrict the number of file handles an application can have open at +any given time. +*/ +DRWAV_API drwav_bool32 drwav_init_file(drwav* pWav, const char* filename, const drwav_allocation_callbacks* pAllocationCallbacks); +DRWAV_API drwav_bool32 drwav_init_file_ex(drwav* pWav, const char* filename, drwav_chunk_proc onChunk, void* pChunkUserData, drwav_uint32 flags, const drwav_allocation_callbacks* pAllocationCallbacks); +DRWAV_API drwav_bool32 drwav_init_file_w(drwav* pWav, const wchar_t* filename, const drwav_allocation_callbacks* pAllocationCallbacks); +DRWAV_API drwav_bool32 drwav_init_file_ex_w(drwav* pWav, const wchar_t* filename, drwav_chunk_proc onChunk, void* pChunkUserData, drwav_uint32 flags, const drwav_allocation_callbacks* pAllocationCallbacks); +DRWAV_API drwav_bool32 drwav_init_file_with_metadata(drwav* pWav, const char* filename, drwav_uint32 flags, const drwav_allocation_callbacks* pAllocationCallbacks); +DRWAV_API drwav_bool32 drwav_init_file_with_metadata_w(drwav* pWav, const wchar_t* filename, drwav_uint32 flags, const drwav_allocation_callbacks* pAllocationCallbacks); + + +/* +Helper for initializing a wave file for writing using stdio. + +This holds the internal FILE object until drwav_uninit() is called. Keep this in mind if you're caching drwav +objects because the operating system may restrict the number of file handles an application can have open at +any given time. +*/ +DRWAV_API drwav_bool32 drwav_init_file_write(drwav* pWav, const char* filename, const drwav_data_format* pFormat, const drwav_allocation_callbacks* pAllocationCallbacks); +DRWAV_API drwav_bool32 drwav_init_file_write_sequential(drwav* pWav, const char* filename, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, const drwav_allocation_callbacks* pAllocationCallbacks); +DRWAV_API drwav_bool32 drwav_init_file_write_sequential_pcm_frames(drwav* pWav, const char* filename, const drwav_data_format* pFormat, drwav_uint64 totalPCMFrameCount, const drwav_allocation_callbacks* pAllocationCallbacks); +DRWAV_API drwav_bool32 drwav_init_file_write_w(drwav* pWav, const wchar_t* filename, const drwav_data_format* pFormat, const drwav_allocation_callbacks* pAllocationCallbacks); +DRWAV_API drwav_bool32 drwav_init_file_write_sequential_w(drwav* pWav, const wchar_t* filename, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, const drwav_allocation_callbacks* pAllocationCallbacks); +DRWAV_API drwav_bool32 drwav_init_file_write_sequential_pcm_frames_w(drwav* pWav, const wchar_t* filename, const drwav_data_format* pFormat, drwav_uint64 totalPCMFrameCount, const drwav_allocation_callbacks* pAllocationCallbacks); +#endif /* DR_WAV_NO_STDIO */ + +/* +Helper for initializing a loader from a pre-allocated memory buffer. + +This does not create a copy of the data. It is up to the application to ensure the buffer remains valid for +the lifetime of the drwav object. + +The buffer should contain the contents of the entire wave file, not just the sample data. +*/ +DRWAV_API drwav_bool32 drwav_init_memory(drwav* pWav, const void* data, size_t dataSize, const drwav_allocation_callbacks* pAllocationCallbacks); +DRWAV_API drwav_bool32 drwav_init_memory_ex(drwav* pWav, const void* data, size_t dataSize, drwav_chunk_proc onChunk, void* pChunkUserData, drwav_uint32 flags, const drwav_allocation_callbacks* pAllocationCallbacks); +DRWAV_API drwav_bool32 drwav_init_memory_with_metadata(drwav* pWav, const void* data, size_t dataSize, drwav_uint32 flags, const drwav_allocation_callbacks* pAllocationCallbacks); + +/* +Helper for initializing a writer which outputs data to a memory buffer. + +dr_wav will manage the memory allocations, however it is up to the caller to free the data with drwav_free(). + +The buffer will remain allocated even after drwav_uninit() is called. The buffer should not be considered valid +until after drwav_uninit() has been called. +*/ +DRWAV_API drwav_bool32 drwav_init_memory_write(drwav* pWav, void** ppData, size_t* pDataSize, const drwav_data_format* pFormat, const drwav_allocation_callbacks* pAllocationCallbacks); +DRWAV_API drwav_bool32 drwav_init_memory_write_sequential(drwav* pWav, void** ppData, size_t* pDataSize, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, const drwav_allocation_callbacks* pAllocationCallbacks); +DRWAV_API drwav_bool32 drwav_init_memory_write_sequential_pcm_frames(drwav* pWav, void** ppData, size_t* pDataSize, const drwav_data_format* pFormat, drwav_uint64 totalPCMFrameCount, const drwav_allocation_callbacks* pAllocationCallbacks); + + +#ifndef DR_WAV_NO_CONVERSION_API +/* +Opens and reads an entire wav file in a single operation. + +The return value is a heap-allocated buffer containing the audio data. Use drwav_free() to free the buffer. +*/ +DRWAV_API drwav_int16* drwav_open_and_read_pcm_frames_s16(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks); +DRWAV_API float* drwav_open_and_read_pcm_frames_f32(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks); +DRWAV_API drwav_int32* drwav_open_and_read_pcm_frames_s32(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks); +#ifndef DR_WAV_NO_STDIO +/* +Opens and decodes an entire wav file in a single operation. + +The return value is a heap-allocated buffer containing the audio data. Use drwav_free() to free the buffer. +*/ +DRWAV_API drwav_int16* drwav_open_file_and_read_pcm_frames_s16(const char* filename, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks); +DRWAV_API float* drwav_open_file_and_read_pcm_frames_f32(const char* filename, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks); +DRWAV_API drwav_int32* drwav_open_file_and_read_pcm_frames_s32(const char* filename, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks); +DRWAV_API drwav_int16* drwav_open_file_and_read_pcm_frames_s16_w(const wchar_t* filename, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks); +DRWAV_API float* drwav_open_file_and_read_pcm_frames_f32_w(const wchar_t* filename, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks); +DRWAV_API drwav_int32* drwav_open_file_and_read_pcm_frames_s32_w(const wchar_t* filename, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks); +#endif +/* +Opens and decodes an entire wav file from a block of memory in a single operation. + +The return value is a heap-allocated buffer containing the audio data. Use drwav_free() to free the buffer. +*/ +DRWAV_API drwav_int16* drwav_open_memory_and_read_pcm_frames_s16(const void* data, size_t dataSize, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks); +DRWAV_API float* drwav_open_memory_and_read_pcm_frames_f32(const void* data, size_t dataSize, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks); +DRWAV_API drwav_int32* drwav_open_memory_and_read_pcm_frames_s32(const void* data, size_t dataSize, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks); +#endif + +/* Frees data that was allocated internally by dr_wav. */ +DRWAV_API void drwav_free(void* p, const drwav_allocation_callbacks* pAllocationCallbacks); + +/* Converts bytes from a wav stream to a sized type of native endian. */ +DRWAV_API drwav_uint16 drwav_bytes_to_u16(const drwav_uint8* data); +DRWAV_API drwav_int16 drwav_bytes_to_s16(const drwav_uint8* data); +DRWAV_API drwav_uint32 drwav_bytes_to_u32(const drwav_uint8* data); +DRWAV_API drwav_int32 drwav_bytes_to_s32(const drwav_uint8* data); +DRWAV_API drwav_uint64 drwav_bytes_to_u64(const drwav_uint8* data); +DRWAV_API drwav_int64 drwav_bytes_to_s64(const drwav_uint8* data); +DRWAV_API float drwav_bytes_to_f32(const drwav_uint8* data); + +/* Compares a GUID for the purpose of checking the type of a Wave64 chunk. */ +DRWAV_API drwav_bool32 drwav_guid_equal(const drwav_uint8 a[16], const drwav_uint8 b[16]); + +/* Compares a four-character-code for the purpose of checking the type of a RIFF chunk. */ +DRWAV_API drwav_bool32 drwav_fourcc_equal(const drwav_uint8* a, const char* b); + +#ifdef __cplusplus +} +#endif +#endif /* dr_wav_h */ + + +/************************************************************************************************************************************************************ + ************************************************************************************************************************************************************ + + IMPLEMENTATION + + ************************************************************************************************************************************************************ + ************************************************************************************************************************************************************/ +#if defined(DR_WAV_IMPLEMENTATION) || defined(DRWAV_IMPLEMENTATION) +#ifndef dr_wav_c +#define dr_wav_c + +#ifdef __MRC__ +/* MrC currently doesn't compile dr_wav correctly with any optimizations enabled. */ +#pragma options opt off +#endif + +#include +#include +#include /* For INT_MAX */ + +#ifndef DR_WAV_NO_STDIO +#include +#ifndef DR_WAV_NO_WCHAR +#include +#endif +#endif + +/* Standard library stuff. */ +#ifndef DRWAV_ASSERT +#include +#define DRWAV_ASSERT(expression) assert(expression) +#endif +#ifndef DRWAV_MALLOC +#define DRWAV_MALLOC(sz) malloc((sz)) +#endif +#ifndef DRWAV_REALLOC +#define DRWAV_REALLOC(p, sz) realloc((p), (sz)) +#endif +#ifndef DRWAV_FREE +#define DRWAV_FREE(p) free((p)) +#endif +#ifndef DRWAV_COPY_MEMORY +#define DRWAV_COPY_MEMORY(dst, src, sz) memcpy((dst), (src), (sz)) +#endif +#ifndef DRWAV_ZERO_MEMORY +#define DRWAV_ZERO_MEMORY(p, sz) memset((p), 0, (sz)) +#endif +#ifndef DRWAV_ZERO_OBJECT +#define DRWAV_ZERO_OBJECT(p) DRWAV_ZERO_MEMORY((p), sizeof(*p)) +#endif + +#define drwav_countof(x) (sizeof(x) / sizeof(x[0])) +#define drwav_align(x, a) ((((x) + (a) - 1) / (a)) * (a)) +#define drwav_min(a, b) (((a) < (b)) ? (a) : (b)) +#define drwav_max(a, b) (((a) > (b)) ? (a) : (b)) +#define drwav_clamp(x, lo, hi) (drwav_max((lo), drwav_min((hi), (x)))) +#define drwav_offset_ptr(p, offset) (((drwav_uint8*)(p)) + (offset)) + +#define DRWAV_MAX_SIMD_VECTOR_SIZE 32 + +/* Architecture Detection */ +#if defined(__x86_64__) || defined(_M_X64) + #define DRWAV_X64 +#elif defined(__i386) || defined(_M_IX86) + #define DRWAV_X86 +#elif defined(__arm__) || defined(_M_ARM) + #define DRWAV_ARM +#endif +/* End Architecture Detection */ + +/* Inline */ +#ifdef _MSC_VER + #define DRWAV_INLINE __forceinline +#elif defined(__GNUC__) + /* + I've had a bug report where GCC is emitting warnings about functions possibly not being inlineable. This warning happens when + the __attribute__((always_inline)) attribute is defined without an "inline" statement. I think therefore there must be some + case where "__inline__" is not always defined, thus the compiler emitting these warnings. When using -std=c89 or -ansi on the + command line, we cannot use the "inline" keyword and instead need to use "__inline__". In an attempt to work around this issue + I am using "__inline__" only when we're compiling in strict ANSI mode. + */ + #if defined(__STRICT_ANSI__) + #define DRWAV_GNUC_INLINE_HINT __inline__ + #else + #define DRWAV_GNUC_INLINE_HINT inline + #endif + + #if (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 2)) || defined(__clang__) + #define DRWAV_INLINE DRWAV_GNUC_INLINE_HINT __attribute__((always_inline)) + #else + #define DRWAV_INLINE DRWAV_GNUC_INLINE_HINT + #endif +#elif defined(__WATCOMC__) + #define DRWAV_INLINE __inline +#else + #define DRWAV_INLINE +#endif +/* End Inline */ + +/* SIZE_MAX */ +#if defined(SIZE_MAX) + #define DRWAV_SIZE_MAX SIZE_MAX +#else + #if defined(_WIN64) || defined(_LP64) || defined(__LP64__) + #define DRWAV_SIZE_MAX ((drwav_uint64)0xFFFFFFFFFFFFFFFF) + #else + #define DRWAV_SIZE_MAX 0xFFFFFFFF + #endif +#endif +/* End SIZE_MAX */ + +/* Weird bit manipulation is for C89 compatibility (no direct support for 64-bit integers). */ +#define DRWAV_INT64_MIN ((drwav_int64) ((drwav_uint64)0x80000000 << 32)) +#define DRWAV_INT64_MAX ((drwav_int64)(((drwav_uint64)0x7FFFFFFF << 32) | 0xFFFFFFFF)) + +#if defined(_MSC_VER) && _MSC_VER >= 1400 + #define DRWAV_HAS_BYTESWAP16_INTRINSIC + #define DRWAV_HAS_BYTESWAP32_INTRINSIC + #define DRWAV_HAS_BYTESWAP64_INTRINSIC +#elif defined(__clang__) + #if defined(__has_builtin) + #if __has_builtin(__builtin_bswap16) + #define DRWAV_HAS_BYTESWAP16_INTRINSIC + #endif + #if __has_builtin(__builtin_bswap32) + #define DRWAV_HAS_BYTESWAP32_INTRINSIC + #endif + #if __has_builtin(__builtin_bswap64) + #define DRWAV_HAS_BYTESWAP64_INTRINSIC + #endif + #endif +#elif defined(__GNUC__) + #if ((__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3)) + #define DRWAV_HAS_BYTESWAP32_INTRINSIC + #define DRWAV_HAS_BYTESWAP64_INTRINSIC + #endif + #if ((__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8)) + #define DRWAV_HAS_BYTESWAP16_INTRINSIC + #endif +#endif + +DRWAV_API void drwav_version(drwav_uint32* pMajor, drwav_uint32* pMinor, drwav_uint32* pRevision) +{ + if (pMajor) { + *pMajor = DRWAV_VERSION_MAJOR; + } + + if (pMinor) { + *pMinor = DRWAV_VERSION_MINOR; + } + + if (pRevision) { + *pRevision = DRWAV_VERSION_REVISION; + } +} + +DRWAV_API const char* drwav_version_string(void) +{ + return DRWAV_VERSION_STRING; +} + +/* +These limits are used for basic validation when initializing the decoder. If you exceed these limits, first of all: what on Earth are +you doing?! (Let me know, I'd be curious!) Second, you can adjust these by #define-ing them before the dr_wav implementation. +*/ +#ifndef DRWAV_MAX_SAMPLE_RATE +#define DRWAV_MAX_SAMPLE_RATE 384000 +#endif +#ifndef DRWAV_MAX_CHANNELS +#define DRWAV_MAX_CHANNELS 256 +#endif +#ifndef DRWAV_MAX_BITS_PER_SAMPLE +#define DRWAV_MAX_BITS_PER_SAMPLE 64 +#endif + +static const drwav_uint8 drwavGUID_W64_RIFF[16] = {0x72,0x69,0x66,0x66, 0x2E,0x91, 0xCF,0x11, 0xA5,0xD6, 0x28,0xDB,0x04,0xC1,0x00,0x00}; /* 66666972-912E-11CF-A5D6-28DB04C10000 */ +static const drwav_uint8 drwavGUID_W64_WAVE[16] = {0x77,0x61,0x76,0x65, 0xF3,0xAC, 0xD3,0x11, 0x8C,0xD1, 0x00,0xC0,0x4F,0x8E,0xDB,0x8A}; /* 65766177-ACF3-11D3-8CD1-00C04F8EDB8A */ +/*static const drwav_uint8 drwavGUID_W64_JUNK[16] = {0x6A,0x75,0x6E,0x6B, 0xF3,0xAC, 0xD3,0x11, 0x8C,0xD1, 0x00,0xC0,0x4F,0x8E,0xDB,0x8A};*/ /* 6B6E756A-ACF3-11D3-8CD1-00C04F8EDB8A */ +static const drwav_uint8 drwavGUID_W64_FMT [16] = {0x66,0x6D,0x74,0x20, 0xF3,0xAC, 0xD3,0x11, 0x8C,0xD1, 0x00,0xC0,0x4F,0x8E,0xDB,0x8A}; /* 20746D66-ACF3-11D3-8CD1-00C04F8EDB8A */ +static const drwav_uint8 drwavGUID_W64_FACT[16] = {0x66,0x61,0x63,0x74, 0xF3,0xAC, 0xD3,0x11, 0x8C,0xD1, 0x00,0xC0,0x4F,0x8E,0xDB,0x8A}; /* 74636166-ACF3-11D3-8CD1-00C04F8EDB8A */ +static const drwav_uint8 drwavGUID_W64_DATA[16] = {0x64,0x61,0x74,0x61, 0xF3,0xAC, 0xD3,0x11, 0x8C,0xD1, 0x00,0xC0,0x4F,0x8E,0xDB,0x8A}; /* 61746164-ACF3-11D3-8CD1-00C04F8EDB8A */ +/*static const drwav_uint8 drwavGUID_W64_SMPL[16] = {0x73,0x6D,0x70,0x6C, 0xF3,0xAC, 0xD3,0x11, 0x8C,0xD1, 0x00,0xC0,0x4F,0x8E,0xDB,0x8A};*/ /* 6C706D73-ACF3-11D3-8CD1-00C04F8EDB8A */ + + +static DRWAV_INLINE int drwav__is_little_endian(void) +{ +#if defined(DRWAV_X86) || defined(DRWAV_X64) + return DRWAV_TRUE; +#elif defined(__BYTE_ORDER) && defined(__LITTLE_ENDIAN) && __BYTE_ORDER == __LITTLE_ENDIAN + return DRWAV_TRUE; +#else + int n = 1; + return (*(char*)&n) == 1; +#endif +} + + +static DRWAV_INLINE void drwav_bytes_to_guid(const drwav_uint8* data, drwav_uint8* guid) +{ + int i; + for (i = 0; i < 16; ++i) { + guid[i] = data[i]; + } +} + + +static DRWAV_INLINE drwav_uint16 drwav__bswap16(drwav_uint16 n) +{ +#ifdef DRWAV_HAS_BYTESWAP16_INTRINSIC + #if defined(_MSC_VER) + return _byteswap_ushort(n); + #elif defined(__GNUC__) || defined(__clang__) + return __builtin_bswap16(n); + #else + #error "This compiler does not support the byte swap intrinsic." + #endif +#else + return ((n & 0xFF00) >> 8) | + ((n & 0x00FF) << 8); +#endif +} + +static DRWAV_INLINE drwav_uint32 drwav__bswap32(drwav_uint32 n) +{ +#ifdef DRWAV_HAS_BYTESWAP32_INTRINSIC + #if defined(_MSC_VER) + return _byteswap_ulong(n); + #elif defined(__GNUC__) || defined(__clang__) + #if defined(DRWAV_ARM) && (defined(__ARM_ARCH) && __ARM_ARCH >= 6) && !defined(DRWAV_64BIT) /* <-- 64-bit inline assembly has not been tested, so disabling for now. */ + /* Inline assembly optimized implementation for ARM. In my testing, GCC does not generate optimized code with __builtin_bswap32(). */ + drwav_uint32 r; + __asm__ __volatile__ ( + #if defined(DRWAV_64BIT) + "rev %w[out], %w[in]" : [out]"=r"(r) : [in]"r"(n) /* <-- This is untested. If someone in the community could test this, that would be appreciated! */ + #else + "rev %[out], %[in]" : [out]"=r"(r) : [in]"r"(n) + #endif + ); + return r; + #else + return __builtin_bswap32(n); + #endif + #else + #error "This compiler does not support the byte swap intrinsic." + #endif +#else + return ((n & 0xFF000000) >> 24) | + ((n & 0x00FF0000) >> 8) | + ((n & 0x0000FF00) << 8) | + ((n & 0x000000FF) << 24); +#endif +} + +static DRWAV_INLINE drwav_uint64 drwav__bswap64(drwav_uint64 n) +{ +#ifdef DRWAV_HAS_BYTESWAP64_INTRINSIC + #if defined(_MSC_VER) + return _byteswap_uint64(n); + #elif defined(__GNUC__) || defined(__clang__) + return __builtin_bswap64(n); + #else + #error "This compiler does not support the byte swap intrinsic." + #endif +#else + /* Weird "<< 32" bitshift is required for C89 because it doesn't support 64-bit constants. Should be optimized out by a good compiler. */ + return ((n & ((drwav_uint64)0xFF000000 << 32)) >> 56) | + ((n & ((drwav_uint64)0x00FF0000 << 32)) >> 40) | + ((n & ((drwav_uint64)0x0000FF00 << 32)) >> 24) | + ((n & ((drwav_uint64)0x000000FF << 32)) >> 8) | + ((n & ((drwav_uint64)0xFF000000 )) << 8) | + ((n & ((drwav_uint64)0x00FF0000 )) << 24) | + ((n & ((drwav_uint64)0x0000FF00 )) << 40) | + ((n & ((drwav_uint64)0x000000FF )) << 56); +#endif +} + + +static DRWAV_INLINE drwav_int16 drwav__bswap_s16(drwav_int16 n) +{ + return (drwav_int16)drwav__bswap16((drwav_uint16)n); +} + +static DRWAV_INLINE void drwav__bswap_samples_s16(drwav_int16* pSamples, drwav_uint64 sampleCount) +{ + drwav_uint64 iSample; + for (iSample = 0; iSample < sampleCount; iSample += 1) { + pSamples[iSample] = drwav__bswap_s16(pSamples[iSample]); + } +} + + +static DRWAV_INLINE void drwav__bswap_s24(drwav_uint8* p) +{ + drwav_uint8 t; + t = p[0]; + p[0] = p[2]; + p[2] = t; +} + +static DRWAV_INLINE void drwav__bswap_samples_s24(drwav_uint8* pSamples, drwav_uint64 sampleCount) +{ + drwav_uint64 iSample; + for (iSample = 0; iSample < sampleCount; iSample += 1) { + drwav_uint8* pSample = pSamples + (iSample*3); + drwav__bswap_s24(pSample); + } +} + + +static DRWAV_INLINE drwav_int32 drwav__bswap_s32(drwav_int32 n) +{ + return (drwav_int32)drwav__bswap32((drwav_uint32)n); +} + +static DRWAV_INLINE void drwav__bswap_samples_s32(drwav_int32* pSamples, drwav_uint64 sampleCount) +{ + drwav_uint64 iSample; + for (iSample = 0; iSample < sampleCount; iSample += 1) { + pSamples[iSample] = drwav__bswap_s32(pSamples[iSample]); + } +} + + +static DRWAV_INLINE drwav_int64 drwav__bswap_s64(drwav_int64 n) +{ + return (drwav_int64)drwav__bswap64((drwav_uint64)n); +} + +static DRWAV_INLINE void drwav__bswap_samples_s64(drwav_int64* pSamples, drwav_uint64 sampleCount) +{ + drwav_uint64 iSample; + for (iSample = 0; iSample < sampleCount; iSample += 1) { + pSamples[iSample] = drwav__bswap_s64(pSamples[iSample]); + } +} + + +static DRWAV_INLINE float drwav__bswap_f32(float n) +{ + union { + drwav_uint32 i; + float f; + } x; + x.f = n; + x.i = drwav__bswap32(x.i); + + return x.f; +} + +static DRWAV_INLINE void drwav__bswap_samples_f32(float* pSamples, drwav_uint64 sampleCount) +{ + drwav_uint64 iSample; + for (iSample = 0; iSample < sampleCount; iSample += 1) { + pSamples[iSample] = drwav__bswap_f32(pSamples[iSample]); + } +} + + +static DRWAV_INLINE void drwav__bswap_samples(void* pSamples, drwav_uint64 sampleCount, drwav_uint32 bytesPerSample) +{ + switch (bytesPerSample) + { + case 1: + { + /* No-op. */ + } break; + case 2: + { + drwav__bswap_samples_s16((drwav_int16*)pSamples, sampleCount); + } break; + case 3: + { + drwav__bswap_samples_s24((drwav_uint8*)pSamples, sampleCount); + } break; + case 4: + { + drwav__bswap_samples_s32((drwav_int32*)pSamples, sampleCount); + } break; + case 8: + { + drwav__bswap_samples_s64((drwav_int64*)pSamples, sampleCount); + } break; + default: + { + /* Unsupported format. */ + DRWAV_ASSERT(DRWAV_FALSE); + } break; + } +} + + + +DRWAV_PRIVATE DRWAV_INLINE drwav_bool32 drwav_is_container_be(drwav_container container) +{ + if (container == drwav_container_rifx || container == drwav_container_aiff) { + return DRWAV_TRUE; + } else { + return DRWAV_FALSE; + } +} + + +DRWAV_PRIVATE DRWAV_INLINE drwav_uint16 drwav_bytes_to_u16_le(const drwav_uint8* data) +{ + return ((drwav_uint16)data[0] << 0) | ((drwav_uint16)data[1] << 8); +} + +DRWAV_PRIVATE DRWAV_INLINE drwav_uint16 drwav_bytes_to_u16_be(const drwav_uint8* data) +{ + return ((drwav_uint16)data[1] << 0) | ((drwav_uint16)data[0] << 8); +} + +DRWAV_PRIVATE DRWAV_INLINE drwav_uint16 drwav_bytes_to_u16_ex(const drwav_uint8* data, drwav_container container) +{ + if (drwav_is_container_be(container)) { + return drwav_bytes_to_u16_be(data); + } else { + return drwav_bytes_to_u16_le(data); + } +} + + +DRWAV_PRIVATE DRWAV_INLINE drwav_uint32 drwav_bytes_to_u32_le(const drwav_uint8* data) +{ + return ((drwav_uint32)data[0] << 0) | ((drwav_uint32)data[1] << 8) | ((drwav_uint32)data[2] << 16) | ((drwav_uint32)data[3] << 24); +} + +DRWAV_PRIVATE DRWAV_INLINE drwav_uint32 drwav_bytes_to_u32_be(const drwav_uint8* data) +{ + return ((drwav_uint32)data[3] << 0) | ((drwav_uint32)data[2] << 8) | ((drwav_uint32)data[1] << 16) | ((drwav_uint32)data[0] << 24); +} + +DRWAV_PRIVATE DRWAV_INLINE drwav_uint32 drwav_bytes_to_u32_ex(const drwav_uint8* data, drwav_container container) +{ + if (drwav_is_container_be(container)) { + return drwav_bytes_to_u32_be(data); + } else { + return drwav_bytes_to_u32_le(data); + } +} + + + +DRWAV_PRIVATE drwav_int64 drwav_aiff_extented_to_s64(const drwav_uint8* data) +{ + drwav_uint32 exponent = ((drwav_uint32)data[0] << 8) | data[1]; + drwav_uint64 hi = ((drwav_uint64)data[2] << 24) | ((drwav_uint64)data[3] << 16) | ((drwav_uint64)data[4] << 8) | ((drwav_uint64)data[5] << 0); + drwav_uint64 lo = ((drwav_uint64)data[6] << 24) | ((drwav_uint64)data[7] << 16) | ((drwav_uint64)data[8] << 8) | ((drwav_uint64)data[9] << 0); + drwav_uint64 significand = (hi << 32) | lo; + int sign = exponent >> 15; + + /* Remove sign bit. */ + exponent &= 0x7FFF; + + /* Special cases. */ + if (exponent == 0 && significand == 0) { + return 0; + } else if (exponent == 0x7FFF) { + return sign ? DRWAV_INT64_MIN : DRWAV_INT64_MAX; /* Infinite. */ + } + + exponent -= 16383; + + if (exponent > 63) { + return sign ? DRWAV_INT64_MIN : DRWAV_INT64_MAX; /* Too big for a 64-bit integer. */ + } else if (exponent < 1) { + return 0; /* Number is less than 1, so rounds down to 0. */ + } + + significand >>= (63 - exponent); + + if (sign) { + return -(drwav_int64)significand; + } else { + return (drwav_int64)significand; + } +} + + +DRWAV_PRIVATE void* drwav__malloc_default(size_t sz, void* pUserData) +{ + (void)pUserData; + return DRWAV_MALLOC(sz); +} + +DRWAV_PRIVATE void* drwav__realloc_default(void* p, size_t sz, void* pUserData) +{ + (void)pUserData; + return DRWAV_REALLOC(p, sz); +} + +DRWAV_PRIVATE void drwav__free_default(void* p, void* pUserData) +{ + (void)pUserData; + DRWAV_FREE(p); +} + + +DRWAV_PRIVATE void* drwav__malloc_from_callbacks(size_t sz, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + if (pAllocationCallbacks == NULL) { + return NULL; + } + + if (pAllocationCallbacks->onMalloc != NULL) { + return pAllocationCallbacks->onMalloc(sz, pAllocationCallbacks->pUserData); + } + + /* Try using realloc(). */ + if (pAllocationCallbacks->onRealloc != NULL) { + return pAllocationCallbacks->onRealloc(NULL, sz, pAllocationCallbacks->pUserData); + } + + return NULL; +} + +DRWAV_PRIVATE void* drwav__realloc_from_callbacks(void* p, size_t szNew, size_t szOld, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + if (pAllocationCallbacks == NULL) { + return NULL; + } + + if (pAllocationCallbacks->onRealloc != NULL) { + return pAllocationCallbacks->onRealloc(p, szNew, pAllocationCallbacks->pUserData); + } + + /* Try emulating realloc() in terms of malloc()/free(). */ + if (pAllocationCallbacks->onMalloc != NULL && pAllocationCallbacks->onFree != NULL) { + void* p2; + + p2 = pAllocationCallbacks->onMalloc(szNew, pAllocationCallbacks->pUserData); + if (p2 == NULL) { + return NULL; + } + + if (p != NULL) { + DRWAV_COPY_MEMORY(p2, p, szOld); + pAllocationCallbacks->onFree(p, pAllocationCallbacks->pUserData); + } + + return p2; + } + + return NULL; +} + +DRWAV_PRIVATE void drwav__free_from_callbacks(void* p, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + if (p == NULL || pAllocationCallbacks == NULL) { + return; + } + + if (pAllocationCallbacks->onFree != NULL) { + pAllocationCallbacks->onFree(p, pAllocationCallbacks->pUserData); + } +} + + +DRWAV_PRIVATE drwav_allocation_callbacks drwav_copy_allocation_callbacks_or_defaults(const drwav_allocation_callbacks* pAllocationCallbacks) +{ + if (pAllocationCallbacks != NULL) { + /* Copy. */ + return *pAllocationCallbacks; + } else { + /* Defaults. */ + drwav_allocation_callbacks allocationCallbacks; + allocationCallbacks.pUserData = NULL; + allocationCallbacks.onMalloc = drwav__malloc_default; + allocationCallbacks.onRealloc = drwav__realloc_default; + allocationCallbacks.onFree = drwav__free_default; + return allocationCallbacks; + } +} + + +static DRWAV_INLINE drwav_bool32 drwav__is_compressed_format_tag(drwav_uint16 formatTag) +{ + return + formatTag == DR_WAVE_FORMAT_ADPCM || + formatTag == DR_WAVE_FORMAT_DVI_ADPCM; +} + +DRWAV_PRIVATE unsigned int drwav__chunk_padding_size_riff(drwav_uint64 chunkSize) +{ + return (unsigned int)(chunkSize % 2); +} + +DRWAV_PRIVATE unsigned int drwav__chunk_padding_size_w64(drwav_uint64 chunkSize) +{ + return (unsigned int)(chunkSize % 8); +} + +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__msadpcm(drwav* pWav, drwav_uint64 samplesToRead, drwav_int16* pBufferOut); +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__ima(drwav* pWav, drwav_uint64 samplesToRead, drwav_int16* pBufferOut); +DRWAV_PRIVATE drwav_bool32 drwav_init_write__internal(drwav* pWav, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount); + +DRWAV_PRIVATE drwav_result drwav__read_chunk_header(drwav_read_proc onRead, void* pUserData, drwav_container container, drwav_uint64* pRunningBytesReadOut, drwav_chunk_header* pHeaderOut) +{ + if (container == drwav_container_riff || container == drwav_container_rifx || container == drwav_container_rf64 || container == drwav_container_aiff) { + drwav_uint8 sizeInBytes[4]; + + if (onRead(pUserData, pHeaderOut->id.fourcc, 4) != 4) { + return DRWAV_AT_END; + } + + if (onRead(pUserData, sizeInBytes, 4) != 4) { + return DRWAV_INVALID_FILE; + } + + pHeaderOut->sizeInBytes = drwav_bytes_to_u32_ex(sizeInBytes, container); + pHeaderOut->paddingSize = drwav__chunk_padding_size_riff(pHeaderOut->sizeInBytes); + + *pRunningBytesReadOut += 8; + } else if (container == drwav_container_w64) { + drwav_uint8 sizeInBytes[8]; + + if (onRead(pUserData, pHeaderOut->id.guid, 16) != 16) { + return DRWAV_AT_END; + } + + if (onRead(pUserData, sizeInBytes, 8) != 8) { + return DRWAV_INVALID_FILE; + } + + pHeaderOut->sizeInBytes = drwav_bytes_to_u64(sizeInBytes) - 24; /* <-- Subtract 24 because w64 includes the size of the header. */ + pHeaderOut->paddingSize = drwav__chunk_padding_size_w64(pHeaderOut->sizeInBytes); + *pRunningBytesReadOut += 24; + } else { + return DRWAV_INVALID_FILE; + } + + return DRWAV_SUCCESS; +} + +DRWAV_PRIVATE drwav_bool32 drwav__seek_forward(drwav_seek_proc onSeek, drwav_uint64 offset, void* pUserData) +{ + drwav_uint64 bytesRemainingToSeek = offset; + while (bytesRemainingToSeek > 0) { + if (bytesRemainingToSeek > 0x7FFFFFFF) { + if (!onSeek(pUserData, 0x7FFFFFFF, drwav_seek_origin_current)) { + return DRWAV_FALSE; + } + bytesRemainingToSeek -= 0x7FFFFFFF; + } else { + if (!onSeek(pUserData, (int)bytesRemainingToSeek, drwav_seek_origin_current)) { + return DRWAV_FALSE; + } + bytesRemainingToSeek = 0; + } + } + + return DRWAV_TRUE; +} + +DRWAV_PRIVATE drwav_bool32 drwav__seek_from_start(drwav_seek_proc onSeek, drwav_uint64 offset, void* pUserData) +{ + if (offset <= 0x7FFFFFFF) { + return onSeek(pUserData, (int)offset, drwav_seek_origin_start); + } + + /* Larger than 32-bit seek. */ + if (!onSeek(pUserData, 0x7FFFFFFF, drwav_seek_origin_start)) { + return DRWAV_FALSE; + } + offset -= 0x7FFFFFFF; + + for (;;) { + if (offset <= 0x7FFFFFFF) { + return onSeek(pUserData, (int)offset, drwav_seek_origin_current); + } + + if (!onSeek(pUserData, 0x7FFFFFFF, drwav_seek_origin_current)) { + return DRWAV_FALSE; + } + offset -= 0x7FFFFFFF; + } + + /* Should never get here. */ + /*return DRWAV_TRUE; */ +} + + + +DRWAV_PRIVATE size_t drwav__on_read(drwav_read_proc onRead, void* pUserData, void* pBufferOut, size_t bytesToRead, drwav_uint64* pCursor) +{ + size_t bytesRead; + + DRWAV_ASSERT(onRead != NULL); + DRWAV_ASSERT(pCursor != NULL); + + bytesRead = onRead(pUserData, pBufferOut, bytesToRead); + *pCursor += bytesRead; + return bytesRead; +} + +#if 0 +DRWAV_PRIVATE drwav_bool32 drwav__on_seek(drwav_seek_proc onSeek, void* pUserData, int offset, drwav_seek_origin origin, drwav_uint64* pCursor) +{ + DRWAV_ASSERT(onSeek != NULL); + DRWAV_ASSERT(pCursor != NULL); + + if (!onSeek(pUserData, offset, origin)) { + return DRWAV_FALSE; + } + + if (origin == drwav_seek_origin_start) { + *pCursor = offset; + } else { + *pCursor += offset; + } + + return DRWAV_TRUE; +} +#endif + + +#define DRWAV_SMPL_BYTES 36 +#define DRWAV_SMPL_LOOP_BYTES 24 +#define DRWAV_INST_BYTES 7 +#define DRWAV_ACID_BYTES 24 +#define DRWAV_CUE_BYTES 4 +#define DRWAV_BEXT_BYTES 602 +#define DRWAV_BEXT_DESCRIPTION_BYTES 256 +#define DRWAV_BEXT_ORIGINATOR_NAME_BYTES 32 +#define DRWAV_BEXT_ORIGINATOR_REF_BYTES 32 +#define DRWAV_BEXT_RESERVED_BYTES 180 +#define DRWAV_BEXT_UMID_BYTES 64 +#define DRWAV_CUE_POINT_BYTES 24 +#define DRWAV_LIST_LABEL_OR_NOTE_BYTES 4 +#define DRWAV_LIST_LABELLED_TEXT_BYTES 20 + +#define DRWAV_METADATA_ALIGNMENT 8 + +typedef enum +{ + drwav__metadata_parser_stage_count, + drwav__metadata_parser_stage_read +} drwav__metadata_parser_stage; + +typedef struct +{ + drwav_read_proc onRead; + drwav_seek_proc onSeek; + void *pReadSeekUserData; + drwav__metadata_parser_stage stage; + drwav_metadata *pMetadata; + drwav_uint32 metadataCount; + drwav_uint8 *pData; + drwav_uint8 *pDataCursor; + drwav_uint64 metadataCursor; + drwav_uint64 extraCapacity; +} drwav__metadata_parser; + +DRWAV_PRIVATE size_t drwav__metadata_memory_capacity(drwav__metadata_parser* pParser) +{ + drwav_uint64 cap = sizeof(drwav_metadata) * (drwav_uint64)pParser->metadataCount + pParser->extraCapacity; + if (cap > DRWAV_SIZE_MAX) { + return 0; /* Too big. */ + } + + return (size_t)cap; /* Safe cast thanks to the check above. */ +} + +DRWAV_PRIVATE drwav_uint8* drwav__metadata_get_memory(drwav__metadata_parser* pParser, size_t size, size_t align) +{ + drwav_uint8* pResult; + + if (align) { + drwav_uintptr modulo = (drwav_uintptr)pParser->pDataCursor % align; + if (modulo != 0) { + pParser->pDataCursor += align - modulo; + } + } + + pResult = pParser->pDataCursor; + + /* + Getting to the point where this function is called means there should always be memory + available. Out of memory checks should have been done at an earlier stage. + */ + DRWAV_ASSERT((pResult + size) <= (pParser->pData + drwav__metadata_memory_capacity(pParser))); + + pParser->pDataCursor += size; + return pResult; +} + +DRWAV_PRIVATE void drwav__metadata_request_extra_memory_for_stage_2(drwav__metadata_parser* pParser, size_t bytes, size_t align) +{ + size_t extra = bytes + (align ? (align - 1) : 0); + pParser->extraCapacity += extra; +} + +DRWAV_PRIVATE drwav_result drwav__metadata_alloc(drwav__metadata_parser* pParser, drwav_allocation_callbacks* pAllocationCallbacks) +{ + if (pParser->extraCapacity != 0 || pParser->metadataCount != 0) { + pAllocationCallbacks->onFree(pParser->pData, pAllocationCallbacks->pUserData); + + pParser->pData = (drwav_uint8*)pAllocationCallbacks->onMalloc(drwav__metadata_memory_capacity(pParser), pAllocationCallbacks->pUserData); + pParser->pDataCursor = pParser->pData; + + if (pParser->pData == NULL) { + return DRWAV_OUT_OF_MEMORY; + } + + /* + We don't need to worry about specifying an alignment here because malloc always returns something + of suitable alignment. This also means pParser->pMetadata is all that we need to store in order + for us to free when we are done. + */ + pParser->pMetadata = (drwav_metadata*)drwav__metadata_get_memory(pParser, sizeof(drwav_metadata) * pParser->metadataCount, 1); + pParser->metadataCursor = 0; + } + + return DRWAV_SUCCESS; +} + +DRWAV_PRIVATE size_t drwav__metadata_parser_read(drwav__metadata_parser* pParser, void* pBufferOut, size_t bytesToRead, drwav_uint64* pCursor) +{ + if (pCursor != NULL) { + return drwav__on_read(pParser->onRead, pParser->pReadSeekUserData, pBufferOut, bytesToRead, pCursor); + } else { + return pParser->onRead(pParser->pReadSeekUserData, pBufferOut, bytesToRead); + } +} + +DRWAV_PRIVATE drwav_uint64 drwav__read_smpl_to_metadata_obj(drwav__metadata_parser* pParser, const drwav_chunk_header* pChunkHeader, drwav_metadata* pMetadata) +{ + drwav_uint8 smplHeaderData[DRWAV_SMPL_BYTES]; + drwav_uint64 totalBytesRead = 0; + size_t bytesJustRead; + + if (pMetadata == NULL) { + return 0; + } + + bytesJustRead = drwav__metadata_parser_read(pParser, smplHeaderData, sizeof(smplHeaderData), &totalBytesRead); + + DRWAV_ASSERT(pParser->stage == drwav__metadata_parser_stage_read); + DRWAV_ASSERT(pChunkHeader != NULL); + + if (pMetadata != NULL && bytesJustRead == sizeof(smplHeaderData)) { + drwav_uint32 iSampleLoop; + + pMetadata->type = drwav_metadata_type_smpl; + pMetadata->data.smpl.manufacturerId = drwav_bytes_to_u32(smplHeaderData + 0); + pMetadata->data.smpl.productId = drwav_bytes_to_u32(smplHeaderData + 4); + pMetadata->data.smpl.samplePeriodNanoseconds = drwav_bytes_to_u32(smplHeaderData + 8); + pMetadata->data.smpl.midiUnityNote = drwav_bytes_to_u32(smplHeaderData + 12); + pMetadata->data.smpl.midiPitchFraction = drwav_bytes_to_u32(smplHeaderData + 16); + pMetadata->data.smpl.smpteFormat = drwav_bytes_to_u32(smplHeaderData + 20); + pMetadata->data.smpl.smpteOffset = drwav_bytes_to_u32(smplHeaderData + 24); + pMetadata->data.smpl.sampleLoopCount = drwav_bytes_to_u32(smplHeaderData + 28); + pMetadata->data.smpl.samplerSpecificDataSizeInBytes = drwav_bytes_to_u32(smplHeaderData + 32); + + /* + The loop count needs to be validated against the size of the chunk for safety so we don't + attempt to read over the boundary of the chunk. + */ + if (pMetadata->data.smpl.sampleLoopCount == (pChunkHeader->sizeInBytes - DRWAV_SMPL_BYTES) / DRWAV_SMPL_LOOP_BYTES) { + pMetadata->data.smpl.pLoops = (drwav_smpl_loop*)drwav__metadata_get_memory(pParser, sizeof(drwav_smpl_loop) * pMetadata->data.smpl.sampleLoopCount, DRWAV_METADATA_ALIGNMENT); + + for (iSampleLoop = 0; iSampleLoop < pMetadata->data.smpl.sampleLoopCount; ++iSampleLoop) { + drwav_uint8 smplLoopData[DRWAV_SMPL_LOOP_BYTES]; + bytesJustRead = drwav__metadata_parser_read(pParser, smplLoopData, sizeof(smplLoopData), &totalBytesRead); + + if (bytesJustRead == sizeof(smplLoopData)) { + pMetadata->data.smpl.pLoops[iSampleLoop].cuePointId = drwav_bytes_to_u32(smplLoopData + 0); + pMetadata->data.smpl.pLoops[iSampleLoop].type = drwav_bytes_to_u32(smplLoopData + 4); + pMetadata->data.smpl.pLoops[iSampleLoop].firstSampleByteOffset = drwav_bytes_to_u32(smplLoopData + 8); + pMetadata->data.smpl.pLoops[iSampleLoop].lastSampleByteOffset = drwav_bytes_to_u32(smplLoopData + 12); + pMetadata->data.smpl.pLoops[iSampleLoop].sampleFraction = drwav_bytes_to_u32(smplLoopData + 16); + pMetadata->data.smpl.pLoops[iSampleLoop].playCount = drwav_bytes_to_u32(smplLoopData + 20); + } else { + break; + } + } + + if (pMetadata->data.smpl.samplerSpecificDataSizeInBytes > 0) { + pMetadata->data.smpl.pSamplerSpecificData = drwav__metadata_get_memory(pParser, pMetadata->data.smpl.samplerSpecificDataSizeInBytes, 1); + DRWAV_ASSERT(pMetadata->data.smpl.pSamplerSpecificData != NULL); + + drwav__metadata_parser_read(pParser, pMetadata->data.smpl.pSamplerSpecificData, pMetadata->data.smpl.samplerSpecificDataSizeInBytes, &totalBytesRead); + } + } + } + + return totalBytesRead; +} + +DRWAV_PRIVATE drwav_uint64 drwav__read_cue_to_metadata_obj(drwav__metadata_parser* pParser, const drwav_chunk_header* pChunkHeader, drwav_metadata* pMetadata) +{ + drwav_uint8 cueHeaderSectionData[DRWAV_CUE_BYTES]; + drwav_uint64 totalBytesRead = 0; + size_t bytesJustRead; + + if (pMetadata == NULL) { + return 0; + } + + bytesJustRead = drwav__metadata_parser_read(pParser, cueHeaderSectionData, sizeof(cueHeaderSectionData), &totalBytesRead); + + DRWAV_ASSERT(pParser->stage == drwav__metadata_parser_stage_read); + + if (bytesJustRead == sizeof(cueHeaderSectionData)) { + pMetadata->type = drwav_metadata_type_cue; + pMetadata->data.cue.cuePointCount = drwav_bytes_to_u32(cueHeaderSectionData); + + /* + We need to validate the cue point count against the size of the chunk so we don't read + beyond the chunk. + */ + if (pMetadata->data.cue.cuePointCount == (pChunkHeader->sizeInBytes - DRWAV_CUE_BYTES) / DRWAV_CUE_POINT_BYTES) { + pMetadata->data.cue.pCuePoints = (drwav_cue_point*)drwav__metadata_get_memory(pParser, sizeof(drwav_cue_point) * pMetadata->data.cue.cuePointCount, DRWAV_METADATA_ALIGNMENT); + DRWAV_ASSERT(pMetadata->data.cue.pCuePoints != NULL); + + if (pMetadata->data.cue.cuePointCount > 0) { + drwav_uint32 iCuePoint; + + for (iCuePoint = 0; iCuePoint < pMetadata->data.cue.cuePointCount; ++iCuePoint) { + drwav_uint8 cuePointData[DRWAV_CUE_POINT_BYTES]; + bytesJustRead = drwav__metadata_parser_read(pParser, cuePointData, sizeof(cuePointData), &totalBytesRead); + + if (bytesJustRead == sizeof(cuePointData)) { + pMetadata->data.cue.pCuePoints[iCuePoint].id = drwav_bytes_to_u32(cuePointData + 0); + pMetadata->data.cue.pCuePoints[iCuePoint].playOrderPosition = drwav_bytes_to_u32(cuePointData + 4); + pMetadata->data.cue.pCuePoints[iCuePoint].dataChunkId[0] = cuePointData[8]; + pMetadata->data.cue.pCuePoints[iCuePoint].dataChunkId[1] = cuePointData[9]; + pMetadata->data.cue.pCuePoints[iCuePoint].dataChunkId[2] = cuePointData[10]; + pMetadata->data.cue.pCuePoints[iCuePoint].dataChunkId[3] = cuePointData[11]; + pMetadata->data.cue.pCuePoints[iCuePoint].chunkStart = drwav_bytes_to_u32(cuePointData + 12); + pMetadata->data.cue.pCuePoints[iCuePoint].blockStart = drwav_bytes_to_u32(cuePointData + 16); + pMetadata->data.cue.pCuePoints[iCuePoint].sampleByteOffset = drwav_bytes_to_u32(cuePointData + 20); + } else { + break; + } + } + } + } + } + + return totalBytesRead; +} + +DRWAV_PRIVATE drwav_uint64 drwav__read_inst_to_metadata_obj(drwav__metadata_parser* pParser, drwav_metadata* pMetadata) +{ + drwav_uint8 instData[DRWAV_INST_BYTES]; + drwav_uint64 bytesRead; + + if (pMetadata == NULL) { + return 0; + } + + bytesRead = drwav__metadata_parser_read(pParser, instData, sizeof(instData), NULL); + + DRWAV_ASSERT(pParser->stage == drwav__metadata_parser_stage_read); + + if (bytesRead == sizeof(instData)) { + pMetadata->type = drwav_metadata_type_inst; + pMetadata->data.inst.midiUnityNote = (drwav_int8)instData[0]; + pMetadata->data.inst.fineTuneCents = (drwav_int8)instData[1]; + pMetadata->data.inst.gainDecibels = (drwav_int8)instData[2]; + pMetadata->data.inst.lowNote = (drwav_int8)instData[3]; + pMetadata->data.inst.highNote = (drwav_int8)instData[4]; + pMetadata->data.inst.lowVelocity = (drwav_int8)instData[5]; + pMetadata->data.inst.highVelocity = (drwav_int8)instData[6]; + } + + return bytesRead; +} + +DRWAV_PRIVATE drwav_uint64 drwav__read_acid_to_metadata_obj(drwav__metadata_parser* pParser, drwav_metadata* pMetadata) +{ + drwav_uint8 acidData[DRWAV_ACID_BYTES]; + drwav_uint64 bytesRead; + + if (pMetadata == NULL) { + return 0; + } + + bytesRead = drwav__metadata_parser_read(pParser, acidData, sizeof(acidData), NULL); + + DRWAV_ASSERT(pParser->stage == drwav__metadata_parser_stage_read); + + if (bytesRead == sizeof(acidData)) { + pMetadata->type = drwav_metadata_type_acid; + pMetadata->data.acid.flags = drwav_bytes_to_u32(acidData + 0); + pMetadata->data.acid.midiUnityNote = drwav_bytes_to_u16(acidData + 4); + pMetadata->data.acid.reserved1 = drwav_bytes_to_u16(acidData + 6); + pMetadata->data.acid.reserved2 = drwav_bytes_to_f32(acidData + 8); + pMetadata->data.acid.numBeats = drwav_bytes_to_u32(acidData + 12); + pMetadata->data.acid.meterDenominator = drwav_bytes_to_u16(acidData + 16); + pMetadata->data.acid.meterNumerator = drwav_bytes_to_u16(acidData + 18); + pMetadata->data.acid.tempo = drwav_bytes_to_f32(acidData + 20); + } + + return bytesRead; +} + +DRWAV_PRIVATE size_t drwav__strlen(const char* str) +{ + size_t result = 0; + + while (*str++) { + result += 1; + } + + return result; +} + +DRWAV_PRIVATE size_t drwav__strlen_clamped(const char* str, size_t maxToRead) +{ + size_t result = 0; + + while (*str++ && result < maxToRead) { + result += 1; + } + + return result; +} + +DRWAV_PRIVATE char* drwav__metadata_copy_string(drwav__metadata_parser* pParser, const char* str, size_t maxToRead) +{ + size_t len = drwav__strlen_clamped(str, maxToRead); + + if (len) { + char* result = (char*)drwav__metadata_get_memory(pParser, len + 1, 1); + DRWAV_ASSERT(result != NULL); + + DRWAV_COPY_MEMORY(result, str, len); + result[len] = '\0'; + + return result; + } else { + return NULL; + } +} + +typedef struct +{ + const void* pBuffer; + size_t sizeInBytes; + size_t cursor; +} drwav_buffer_reader; + +DRWAV_PRIVATE drwav_result drwav_buffer_reader_init(const void* pBuffer, size_t sizeInBytes, drwav_buffer_reader* pReader) +{ + DRWAV_ASSERT(pBuffer != NULL); + DRWAV_ASSERT(pReader != NULL); + + DRWAV_ZERO_OBJECT(pReader); + + pReader->pBuffer = pBuffer; + pReader->sizeInBytes = sizeInBytes; + pReader->cursor = 0; + + return DRWAV_SUCCESS; +} + +DRWAV_PRIVATE const void* drwav_buffer_reader_ptr(const drwav_buffer_reader* pReader) +{ + DRWAV_ASSERT(pReader != NULL); + + return drwav_offset_ptr(pReader->pBuffer, pReader->cursor); +} + +DRWAV_PRIVATE drwav_result drwav_buffer_reader_seek(drwav_buffer_reader* pReader, size_t bytesToSeek) +{ + DRWAV_ASSERT(pReader != NULL); + + if (pReader->cursor + bytesToSeek > pReader->sizeInBytes) { + return DRWAV_BAD_SEEK; /* Seeking too far forward. */ + } + + pReader->cursor += bytesToSeek; + + return DRWAV_SUCCESS; +} + +DRWAV_PRIVATE drwav_result drwav_buffer_reader_read(drwav_buffer_reader* pReader, void* pDst, size_t bytesToRead, size_t* pBytesRead) +{ + drwav_result result = DRWAV_SUCCESS; + size_t bytesRemaining; + + DRWAV_ASSERT(pReader != NULL); + + if (pBytesRead != NULL) { + *pBytesRead = 0; + } + + bytesRemaining = (pReader->sizeInBytes - pReader->cursor); + if (bytesToRead > bytesRemaining) { + bytesToRead = bytesRemaining; + } + + if (pDst == NULL) { + /* Seek. */ + result = drwav_buffer_reader_seek(pReader, bytesToRead); + } else { + /* Read. */ + DRWAV_COPY_MEMORY(pDst, drwav_buffer_reader_ptr(pReader), bytesToRead); + pReader->cursor += bytesToRead; + } + + DRWAV_ASSERT(pReader->cursor <= pReader->sizeInBytes); + + if (result == DRWAV_SUCCESS) { + if (pBytesRead != NULL) { + *pBytesRead = bytesToRead; + } + } + + return DRWAV_SUCCESS; +} + +DRWAV_PRIVATE drwav_result drwav_buffer_reader_read_u16(drwav_buffer_reader* pReader, drwav_uint16* pDst) +{ + drwav_result result; + size_t bytesRead; + drwav_uint8 data[2]; + + DRWAV_ASSERT(pReader != NULL); + DRWAV_ASSERT(pDst != NULL); + + *pDst = 0; /* Safety. */ + + result = drwav_buffer_reader_read(pReader, data, sizeof(*pDst), &bytesRead); + if (result != DRWAV_SUCCESS || bytesRead != sizeof(*pDst)) { + return result; + } + + *pDst = drwav_bytes_to_u16(data); + + return DRWAV_SUCCESS; +} + +DRWAV_PRIVATE drwav_result drwav_buffer_reader_read_u32(drwav_buffer_reader* pReader, drwav_uint32* pDst) +{ + drwav_result result; + size_t bytesRead; + drwav_uint8 data[4]; + + DRWAV_ASSERT(pReader != NULL); + DRWAV_ASSERT(pDst != NULL); + + *pDst = 0; /* Safety. */ + + result = drwav_buffer_reader_read(pReader, data, sizeof(*pDst), &bytesRead); + if (result != DRWAV_SUCCESS || bytesRead != sizeof(*pDst)) { + return result; + } + + *pDst = drwav_bytes_to_u32(data); + + return DRWAV_SUCCESS; +} + + + +DRWAV_PRIVATE drwav_uint64 drwav__read_bext_to_metadata_obj(drwav__metadata_parser* pParser, drwav_metadata* pMetadata, drwav_uint64 chunkSize) +{ + drwav_uint8 bextData[DRWAV_BEXT_BYTES]; + size_t bytesRead = drwav__metadata_parser_read(pParser, bextData, sizeof(bextData), NULL); + + DRWAV_ASSERT(pParser->stage == drwav__metadata_parser_stage_read); + + if (bytesRead == sizeof(bextData)) { + drwav_buffer_reader reader; + drwav_uint32 timeReferenceLow; + drwav_uint32 timeReferenceHigh; + size_t extraBytes; + + pMetadata->type = drwav_metadata_type_bext; + + if (drwav_buffer_reader_init(bextData, bytesRead, &reader) == DRWAV_SUCCESS) { + pMetadata->data.bext.pDescription = drwav__metadata_copy_string(pParser, (const char*)drwav_buffer_reader_ptr(&reader), DRWAV_BEXT_DESCRIPTION_BYTES); + drwav_buffer_reader_seek(&reader, DRWAV_BEXT_DESCRIPTION_BYTES); + + pMetadata->data.bext.pOriginatorName = drwav__metadata_copy_string(pParser, (const char*)drwav_buffer_reader_ptr(&reader), DRWAV_BEXT_ORIGINATOR_NAME_BYTES); + drwav_buffer_reader_seek(&reader, DRWAV_BEXT_ORIGINATOR_NAME_BYTES); + + pMetadata->data.bext.pOriginatorReference = drwav__metadata_copy_string(pParser, (const char*)drwav_buffer_reader_ptr(&reader), DRWAV_BEXT_ORIGINATOR_REF_BYTES); + drwav_buffer_reader_seek(&reader, DRWAV_BEXT_ORIGINATOR_REF_BYTES); + + drwav_buffer_reader_read(&reader, pMetadata->data.bext.pOriginationDate, sizeof(pMetadata->data.bext.pOriginationDate), NULL); + drwav_buffer_reader_read(&reader, pMetadata->data.bext.pOriginationTime, sizeof(pMetadata->data.bext.pOriginationTime), NULL); + + drwav_buffer_reader_read_u32(&reader, &timeReferenceLow); + drwav_buffer_reader_read_u32(&reader, &timeReferenceHigh); + pMetadata->data.bext.timeReference = ((drwav_uint64)timeReferenceHigh << 32) + timeReferenceLow; + + drwav_buffer_reader_read_u16(&reader, &pMetadata->data.bext.version); + + pMetadata->data.bext.pUMID = drwav__metadata_get_memory(pParser, DRWAV_BEXT_UMID_BYTES, 1); + drwav_buffer_reader_read(&reader, pMetadata->data.bext.pUMID, DRWAV_BEXT_UMID_BYTES, NULL); + + drwav_buffer_reader_read_u16(&reader, &pMetadata->data.bext.loudnessValue); + drwav_buffer_reader_read_u16(&reader, &pMetadata->data.bext.loudnessRange); + drwav_buffer_reader_read_u16(&reader, &pMetadata->data.bext.maxTruePeakLevel); + drwav_buffer_reader_read_u16(&reader, &pMetadata->data.bext.maxMomentaryLoudness); + drwav_buffer_reader_read_u16(&reader, &pMetadata->data.bext.maxShortTermLoudness); + + DRWAV_ASSERT((drwav_offset_ptr(drwav_buffer_reader_ptr(&reader), DRWAV_BEXT_RESERVED_BYTES)) == (bextData + DRWAV_BEXT_BYTES)); + + extraBytes = (size_t)(chunkSize - DRWAV_BEXT_BYTES); + if (extraBytes > 0) { + pMetadata->data.bext.pCodingHistory = (char*)drwav__metadata_get_memory(pParser, extraBytes + 1, 1); + DRWAV_ASSERT(pMetadata->data.bext.pCodingHistory != NULL); + + bytesRead += drwav__metadata_parser_read(pParser, pMetadata->data.bext.pCodingHistory, extraBytes, NULL); + pMetadata->data.bext.codingHistorySize = (drwav_uint32)drwav__strlen(pMetadata->data.bext.pCodingHistory); + } else { + pMetadata->data.bext.pCodingHistory = NULL; + pMetadata->data.bext.codingHistorySize = 0; + } + } + } + + return bytesRead; +} + +DRWAV_PRIVATE drwav_uint64 drwav__read_list_label_or_note_to_metadata_obj(drwav__metadata_parser* pParser, drwav_metadata* pMetadata, drwav_uint64 chunkSize, drwav_metadata_type type) +{ + drwav_uint8 cueIDBuffer[DRWAV_LIST_LABEL_OR_NOTE_BYTES]; + drwav_uint64 totalBytesRead = 0; + size_t bytesJustRead = drwav__metadata_parser_read(pParser, cueIDBuffer, sizeof(cueIDBuffer), &totalBytesRead); + + DRWAV_ASSERT(pParser->stage == drwav__metadata_parser_stage_read); + + if (bytesJustRead == sizeof(cueIDBuffer)) { + drwav_uint32 sizeIncludingNullTerminator; + + pMetadata->type = type; + pMetadata->data.labelOrNote.cuePointId = drwav_bytes_to_u32(cueIDBuffer); + + sizeIncludingNullTerminator = (drwav_uint32)chunkSize - DRWAV_LIST_LABEL_OR_NOTE_BYTES; + if (sizeIncludingNullTerminator > 0) { + pMetadata->data.labelOrNote.stringLength = sizeIncludingNullTerminator - 1; + pMetadata->data.labelOrNote.pString = (char*)drwav__metadata_get_memory(pParser, sizeIncludingNullTerminator, 1); + DRWAV_ASSERT(pMetadata->data.labelOrNote.pString != NULL); + + drwav__metadata_parser_read(pParser, pMetadata->data.labelOrNote.pString, sizeIncludingNullTerminator, &totalBytesRead); + } else { + pMetadata->data.labelOrNote.stringLength = 0; + pMetadata->data.labelOrNote.pString = NULL; + } + } + + return totalBytesRead; +} + +DRWAV_PRIVATE drwav_uint64 drwav__read_list_labelled_cue_region_to_metadata_obj(drwav__metadata_parser* pParser, drwav_metadata* pMetadata, drwav_uint64 chunkSize) +{ + drwav_uint8 buffer[DRWAV_LIST_LABELLED_TEXT_BYTES]; + drwav_uint64 totalBytesRead = 0; + size_t bytesJustRead = drwav__metadata_parser_read(pParser, buffer, sizeof(buffer), &totalBytesRead); + + DRWAV_ASSERT(pParser->stage == drwav__metadata_parser_stage_read); + + if (bytesJustRead == sizeof(buffer)) { + drwav_uint32 sizeIncludingNullTerminator; + + pMetadata->type = drwav_metadata_type_list_labelled_cue_region; + pMetadata->data.labelledCueRegion.cuePointId = drwav_bytes_to_u32(buffer + 0); + pMetadata->data.labelledCueRegion.sampleLength = drwav_bytes_to_u32(buffer + 4); + pMetadata->data.labelledCueRegion.purposeId[0] = buffer[8]; + pMetadata->data.labelledCueRegion.purposeId[1] = buffer[9]; + pMetadata->data.labelledCueRegion.purposeId[2] = buffer[10]; + pMetadata->data.labelledCueRegion.purposeId[3] = buffer[11]; + pMetadata->data.labelledCueRegion.country = drwav_bytes_to_u16(buffer + 12); + pMetadata->data.labelledCueRegion.language = drwav_bytes_to_u16(buffer + 14); + pMetadata->data.labelledCueRegion.dialect = drwav_bytes_to_u16(buffer + 16); + pMetadata->data.labelledCueRegion.codePage = drwav_bytes_to_u16(buffer + 18); + + sizeIncludingNullTerminator = (drwav_uint32)chunkSize - DRWAV_LIST_LABELLED_TEXT_BYTES; + if (sizeIncludingNullTerminator > 0) { + pMetadata->data.labelledCueRegion.stringLength = sizeIncludingNullTerminator - 1; + pMetadata->data.labelledCueRegion.pString = (char*)drwav__metadata_get_memory(pParser, sizeIncludingNullTerminator, 1); + DRWAV_ASSERT(pMetadata->data.labelledCueRegion.pString != NULL); + + drwav__metadata_parser_read(pParser, pMetadata->data.labelledCueRegion.pString, sizeIncludingNullTerminator, &totalBytesRead); + } else { + pMetadata->data.labelledCueRegion.stringLength = 0; + pMetadata->data.labelledCueRegion.pString = NULL; + } + } + + return totalBytesRead; +} + +DRWAV_PRIVATE drwav_uint64 drwav__metadata_process_info_text_chunk(drwav__metadata_parser* pParser, drwav_uint64 chunkSize, drwav_metadata_type type) +{ + drwav_uint64 bytesRead = 0; + drwav_uint32 stringSizeWithNullTerminator = (drwav_uint32)chunkSize; + + if (pParser->stage == drwav__metadata_parser_stage_count) { + pParser->metadataCount += 1; + drwav__metadata_request_extra_memory_for_stage_2(pParser, stringSizeWithNullTerminator, 1); + } else { + drwav_metadata* pMetadata = &pParser->pMetadata[pParser->metadataCursor]; + pMetadata->type = type; + if (stringSizeWithNullTerminator > 0) { + pMetadata->data.infoText.stringLength = stringSizeWithNullTerminator - 1; + pMetadata->data.infoText.pString = (char*)drwav__metadata_get_memory(pParser, stringSizeWithNullTerminator, 1); + DRWAV_ASSERT(pMetadata->data.infoText.pString != NULL); + + bytesRead = drwav__metadata_parser_read(pParser, pMetadata->data.infoText.pString, (size_t)stringSizeWithNullTerminator, NULL); + if (bytesRead == chunkSize) { + pParser->metadataCursor += 1; + } else { + /* Failed to parse. */ + } + } else { + pMetadata->data.infoText.stringLength = 0; + pMetadata->data.infoText.pString = NULL; + pParser->metadataCursor += 1; + } + } + + return bytesRead; +} + +DRWAV_PRIVATE drwav_uint64 drwav__metadata_process_unknown_chunk(drwav__metadata_parser* pParser, const drwav_uint8* pChunkId, drwav_uint64 chunkSize, drwav_metadata_location location) +{ + drwav_uint64 bytesRead = 0; + + if (location == drwav_metadata_location_invalid) { + return 0; + } + + if (drwav_fourcc_equal(pChunkId, "data") || drwav_fourcc_equal(pChunkId, "fmt ") || drwav_fourcc_equal(pChunkId, "fact")) { + return 0; + } + + if (pParser->stage == drwav__metadata_parser_stage_count) { + pParser->metadataCount += 1; + drwav__metadata_request_extra_memory_for_stage_2(pParser, (size_t)chunkSize, 1); + } else { + drwav_metadata* pMetadata = &pParser->pMetadata[pParser->metadataCursor]; + pMetadata->type = drwav_metadata_type_unknown; + pMetadata->data.unknown.chunkLocation = location; + pMetadata->data.unknown.id[0] = pChunkId[0]; + pMetadata->data.unknown.id[1] = pChunkId[1]; + pMetadata->data.unknown.id[2] = pChunkId[2]; + pMetadata->data.unknown.id[3] = pChunkId[3]; + pMetadata->data.unknown.dataSizeInBytes = (drwav_uint32)chunkSize; + pMetadata->data.unknown.pData = (drwav_uint8 *)drwav__metadata_get_memory(pParser, (size_t)chunkSize, 1); + DRWAV_ASSERT(pMetadata->data.unknown.pData != NULL); + + bytesRead = drwav__metadata_parser_read(pParser, pMetadata->data.unknown.pData, pMetadata->data.unknown.dataSizeInBytes, NULL); + if (bytesRead == pMetadata->data.unknown.dataSizeInBytes) { + pParser->metadataCursor += 1; + } else { + /* Failed to read. */ + } + } + + return bytesRead; +} + +DRWAV_PRIVATE drwav_bool32 drwav__chunk_matches(drwav_metadata_type allowedMetadataTypes, const drwav_uint8* pChunkID, drwav_metadata_type type, const char* pID) +{ + return (allowedMetadataTypes & type) && drwav_fourcc_equal(pChunkID, pID); +} + +DRWAV_PRIVATE drwav_uint64 drwav__metadata_process_chunk(drwav__metadata_parser* pParser, const drwav_chunk_header* pChunkHeader, drwav_metadata_type allowedMetadataTypes) +{ + const drwav_uint8 *pChunkID = pChunkHeader->id.fourcc; + drwav_uint64 bytesRead = 0; + + if (drwav__chunk_matches(allowedMetadataTypes, pChunkID, drwav_metadata_type_smpl, "smpl")) { + if (pChunkHeader->sizeInBytes >= DRWAV_SMPL_BYTES) { + if (pParser->stage == drwav__metadata_parser_stage_count) { + drwav_uint8 buffer[4]; + size_t bytesJustRead; + + if (!pParser->onSeek(pParser->pReadSeekUserData, 28, drwav_seek_origin_current)) { + return bytesRead; + } + bytesRead += 28; + + bytesJustRead = drwav__metadata_parser_read(pParser, buffer, sizeof(buffer), &bytesRead); + if (bytesJustRead == sizeof(buffer)) { + drwav_uint32 loopCount = drwav_bytes_to_u32(buffer); + drwav_uint64 calculatedLoopCount; + + /* The loop count must be validated against the size of the chunk. */ + calculatedLoopCount = (pChunkHeader->sizeInBytes - DRWAV_SMPL_BYTES) / DRWAV_SMPL_LOOP_BYTES; + if (calculatedLoopCount == loopCount) { + bytesJustRead = drwav__metadata_parser_read(pParser, buffer, sizeof(buffer), &bytesRead); + if (bytesJustRead == sizeof(buffer)) { + drwav_uint32 samplerSpecificDataSizeInBytes = drwav_bytes_to_u32(buffer); + + pParser->metadataCount += 1; + drwav__metadata_request_extra_memory_for_stage_2(pParser, sizeof(drwav_smpl_loop) * loopCount, DRWAV_METADATA_ALIGNMENT); + drwav__metadata_request_extra_memory_for_stage_2(pParser, samplerSpecificDataSizeInBytes, 1); + } + } else { + /* Loop count in header does not match the size of the chunk. */ + } + } + } else { + bytesRead = drwav__read_smpl_to_metadata_obj(pParser, pChunkHeader, &pParser->pMetadata[pParser->metadataCursor]); + if (bytesRead == pChunkHeader->sizeInBytes) { + pParser->metadataCursor += 1; + } else { + /* Failed to parse. */ + } + } + } else { + /* Incorrectly formed chunk. */ + } + } else if (drwav__chunk_matches(allowedMetadataTypes, pChunkID, drwav_metadata_type_inst, "inst")) { + if (pChunkHeader->sizeInBytes == DRWAV_INST_BYTES) { + if (pParser->stage == drwav__metadata_parser_stage_count) { + pParser->metadataCount += 1; + } else { + bytesRead = drwav__read_inst_to_metadata_obj(pParser, &pParser->pMetadata[pParser->metadataCursor]); + if (bytesRead == pChunkHeader->sizeInBytes) { + pParser->metadataCursor += 1; + } else { + /* Failed to parse. */ + } + } + } else { + /* Incorrectly formed chunk. */ + } + } else if (drwav__chunk_matches(allowedMetadataTypes, pChunkID, drwav_metadata_type_acid, "acid")) { + if (pChunkHeader->sizeInBytes == DRWAV_ACID_BYTES) { + if (pParser->stage == drwav__metadata_parser_stage_count) { + pParser->metadataCount += 1; + } else { + bytesRead = drwav__read_acid_to_metadata_obj(pParser, &pParser->pMetadata[pParser->metadataCursor]); + if (bytesRead == pChunkHeader->sizeInBytes) { + pParser->metadataCursor += 1; + } else { + /* Failed to parse. */ + } + } + } else { + /* Incorrectly formed chunk. */ + } + } else if (drwav__chunk_matches(allowedMetadataTypes, pChunkID, drwav_metadata_type_cue, "cue ")) { + if (pChunkHeader->sizeInBytes >= DRWAV_CUE_BYTES) { + if (pParser->stage == drwav__metadata_parser_stage_count) { + size_t cueCount; + + pParser->metadataCount += 1; + cueCount = (size_t)(pChunkHeader->sizeInBytes - DRWAV_CUE_BYTES) / DRWAV_CUE_POINT_BYTES; + drwav__metadata_request_extra_memory_for_stage_2(pParser, sizeof(drwav_cue_point) * cueCount, DRWAV_METADATA_ALIGNMENT); + } else { + bytesRead = drwav__read_cue_to_metadata_obj(pParser, pChunkHeader, &pParser->pMetadata[pParser->metadataCursor]); + if (bytesRead == pChunkHeader->sizeInBytes) { + pParser->metadataCursor += 1; + } else { + /* Failed to parse. */ + } + } + } else { + /* Incorrectly formed chunk. */ + } + } else if (drwav__chunk_matches(allowedMetadataTypes, pChunkID, drwav_metadata_type_bext, "bext")) { + if (pChunkHeader->sizeInBytes >= DRWAV_BEXT_BYTES) { + if (pParser->stage == drwav__metadata_parser_stage_count) { + /* The description field is the largest one in a bext chunk, so that is the max size of this temporary buffer. */ + char buffer[DRWAV_BEXT_DESCRIPTION_BYTES + 1]; + size_t allocSizeNeeded = DRWAV_BEXT_UMID_BYTES; /* We know we will need SMPTE umid size. */ + size_t bytesJustRead; + + buffer[DRWAV_BEXT_DESCRIPTION_BYTES] = '\0'; + bytesJustRead = drwav__metadata_parser_read(pParser, buffer, DRWAV_BEXT_DESCRIPTION_BYTES, &bytesRead); + if (bytesJustRead != DRWAV_BEXT_DESCRIPTION_BYTES) { + return bytesRead; + } + allocSizeNeeded += drwav__strlen(buffer) + 1; + + buffer[DRWAV_BEXT_ORIGINATOR_NAME_BYTES] = '\0'; + bytesJustRead = drwav__metadata_parser_read(pParser, buffer, DRWAV_BEXT_ORIGINATOR_NAME_BYTES, &bytesRead); + if (bytesJustRead != DRWAV_BEXT_ORIGINATOR_NAME_BYTES) { + return bytesRead; + } + allocSizeNeeded += drwav__strlen(buffer) + 1; + + buffer[DRWAV_BEXT_ORIGINATOR_REF_BYTES] = '\0'; + bytesJustRead = drwav__metadata_parser_read(pParser, buffer, DRWAV_BEXT_ORIGINATOR_REF_BYTES, &bytesRead); + if (bytesJustRead != DRWAV_BEXT_ORIGINATOR_REF_BYTES) { + return bytesRead; + } + allocSizeNeeded += drwav__strlen(buffer) + 1; + allocSizeNeeded += (size_t)pChunkHeader->sizeInBytes - DRWAV_BEXT_BYTES; /* Coding history. */ + + drwav__metadata_request_extra_memory_for_stage_2(pParser, allocSizeNeeded, 1); + + pParser->metadataCount += 1; + } else { + bytesRead = drwav__read_bext_to_metadata_obj(pParser, &pParser->pMetadata[pParser->metadataCursor], pChunkHeader->sizeInBytes); + if (bytesRead == pChunkHeader->sizeInBytes) { + pParser->metadataCursor += 1; + } else { + /* Failed to parse. */ + } + } + } else { + /* Incorrectly formed chunk. */ + } + } else if (drwav_fourcc_equal(pChunkID, "LIST") || drwav_fourcc_equal(pChunkID, "list")) { + drwav_metadata_location listType = drwav_metadata_location_invalid; + while (bytesRead < pChunkHeader->sizeInBytes) { + drwav_uint8 subchunkId[4]; + drwav_uint8 subchunkSizeBuffer[4]; + drwav_uint64 subchunkDataSize; + drwav_uint64 subchunkBytesRead = 0; + drwav_uint64 bytesJustRead = drwav__metadata_parser_read(pParser, subchunkId, sizeof(subchunkId), &bytesRead); + if (bytesJustRead != sizeof(subchunkId)) { + break; + } + + /* + The first thing in a list chunk should be "adtl" or "INFO". + + - adtl means this list is a Associated Data List Chunk and will contain labels, notes + or labelled cue regions. + - INFO means this list is an Info List Chunk containing info text chunks such as IPRD + which would specifies the album of this wav file. + + No data follows the adtl or INFO id so we just make note of what type this list is and + continue. + */ + if (drwav_fourcc_equal(subchunkId, "adtl")) { + listType = drwav_metadata_location_inside_adtl_list; + continue; + } else if (drwav_fourcc_equal(subchunkId, "INFO")) { + listType = drwav_metadata_location_inside_info_list; + continue; + } + + bytesJustRead = drwav__metadata_parser_read(pParser, subchunkSizeBuffer, sizeof(subchunkSizeBuffer), &bytesRead); + if (bytesJustRead != sizeof(subchunkSizeBuffer)) { + break; + } + subchunkDataSize = drwav_bytes_to_u32(subchunkSizeBuffer); + + if (drwav__chunk_matches(allowedMetadataTypes, subchunkId, drwav_metadata_type_list_label, "labl") || drwav__chunk_matches(allowedMetadataTypes, subchunkId, drwav_metadata_type_list_note, "note")) { + if (subchunkDataSize >= DRWAV_LIST_LABEL_OR_NOTE_BYTES) { + drwav_uint64 stringSizeWithNullTerm = subchunkDataSize - DRWAV_LIST_LABEL_OR_NOTE_BYTES; + if (pParser->stage == drwav__metadata_parser_stage_count) { + pParser->metadataCount += 1; + drwav__metadata_request_extra_memory_for_stage_2(pParser, (size_t)stringSizeWithNullTerm, 1); + } else { + subchunkBytesRead = drwav__read_list_label_or_note_to_metadata_obj(pParser, &pParser->pMetadata[pParser->metadataCursor], subchunkDataSize, drwav_fourcc_equal(subchunkId, "labl") ? drwav_metadata_type_list_label : drwav_metadata_type_list_note); + if (subchunkBytesRead == subchunkDataSize) { + pParser->metadataCursor += 1; + } else { + /* Failed to parse. */ + } + } + } else { + /* Incorrectly formed chunk. */ + } + } else if (drwav__chunk_matches(allowedMetadataTypes, subchunkId, drwav_metadata_type_list_labelled_cue_region, "ltxt")) { + if (subchunkDataSize >= DRWAV_LIST_LABELLED_TEXT_BYTES) { + drwav_uint64 stringSizeWithNullTerminator = subchunkDataSize - DRWAV_LIST_LABELLED_TEXT_BYTES; + if (pParser->stage == drwav__metadata_parser_stage_count) { + pParser->metadataCount += 1; + drwav__metadata_request_extra_memory_for_stage_2(pParser, (size_t)stringSizeWithNullTerminator, 1); + } else { + subchunkBytesRead = drwav__read_list_labelled_cue_region_to_metadata_obj(pParser, &pParser->pMetadata[pParser->metadataCursor], subchunkDataSize); + if (subchunkBytesRead == subchunkDataSize) { + pParser->metadataCursor += 1; + } else { + /* Failed to parse. */ + } + } + } else { + /* Incorrectly formed chunk. */ + } + } else if (drwav__chunk_matches(allowedMetadataTypes, subchunkId, drwav_metadata_type_list_info_software, "ISFT")) { + subchunkBytesRead = drwav__metadata_process_info_text_chunk(pParser, subchunkDataSize, drwav_metadata_type_list_info_software); + } else if (drwav__chunk_matches(allowedMetadataTypes, subchunkId, drwav_metadata_type_list_info_copyright, "ICOP")) { + subchunkBytesRead = drwav__metadata_process_info_text_chunk(pParser, subchunkDataSize, drwav_metadata_type_list_info_copyright); + } else if (drwav__chunk_matches(allowedMetadataTypes, subchunkId, drwav_metadata_type_list_info_title, "INAM")) { + subchunkBytesRead = drwav__metadata_process_info_text_chunk(pParser, subchunkDataSize, drwav_metadata_type_list_info_title); + } else if (drwav__chunk_matches(allowedMetadataTypes, subchunkId, drwav_metadata_type_list_info_artist, "IART")) { + subchunkBytesRead = drwav__metadata_process_info_text_chunk(pParser, subchunkDataSize, drwav_metadata_type_list_info_artist); + } else if (drwav__chunk_matches(allowedMetadataTypes, subchunkId, drwav_metadata_type_list_info_comment, "ICMT")) { + subchunkBytesRead = drwav__metadata_process_info_text_chunk(pParser, subchunkDataSize, drwav_metadata_type_list_info_comment); + } else if (drwav__chunk_matches(allowedMetadataTypes, subchunkId, drwav_metadata_type_list_info_date, "ICRD")) { + subchunkBytesRead = drwav__metadata_process_info_text_chunk(pParser, subchunkDataSize, drwav_metadata_type_list_info_date); + } else if (drwav__chunk_matches(allowedMetadataTypes, subchunkId, drwav_metadata_type_list_info_genre, "IGNR")) { + subchunkBytesRead = drwav__metadata_process_info_text_chunk(pParser, subchunkDataSize, drwav_metadata_type_list_info_genre); + } else if (drwav__chunk_matches(allowedMetadataTypes, subchunkId, drwav_metadata_type_list_info_album, "IPRD")) { + subchunkBytesRead = drwav__metadata_process_info_text_chunk(pParser, subchunkDataSize, drwav_metadata_type_list_info_album); + } else if (drwav__chunk_matches(allowedMetadataTypes, subchunkId, drwav_metadata_type_list_info_tracknumber, "ITRK")) { + subchunkBytesRead = drwav__metadata_process_info_text_chunk(pParser, subchunkDataSize, drwav_metadata_type_list_info_tracknumber); + } else if ((allowedMetadataTypes & drwav_metadata_type_unknown) != 0) { + subchunkBytesRead = drwav__metadata_process_unknown_chunk(pParser, subchunkId, subchunkDataSize, listType); + } + + bytesRead += subchunkBytesRead; + DRWAV_ASSERT(subchunkBytesRead <= subchunkDataSize); + + if (subchunkBytesRead < subchunkDataSize) { + drwav_uint64 bytesToSeek = subchunkDataSize - subchunkBytesRead; + + if (!pParser->onSeek(pParser->pReadSeekUserData, (int)bytesToSeek, drwav_seek_origin_current)) { + break; + } + bytesRead += bytesToSeek; + } + + if ((subchunkDataSize % 2) == 1) { + if (!pParser->onSeek(pParser->pReadSeekUserData, 1, drwav_seek_origin_current)) { + break; + } + bytesRead += 1; + } + } + } else if ((allowedMetadataTypes & drwav_metadata_type_unknown) != 0) { + bytesRead = drwav__metadata_process_unknown_chunk(pParser, pChunkID, pChunkHeader->sizeInBytes, drwav_metadata_location_top_level); + } + + return bytesRead; +} + + +DRWAV_PRIVATE drwav_uint32 drwav_get_bytes_per_pcm_frame(drwav* pWav) +{ + drwav_uint32 bytesPerFrame; + + /* + The bytes per frame is a bit ambiguous. It can be either be based on the bits per sample, or the block align. The way I'm doing it here + is that if the bits per sample is a multiple of 8, use floor(bitsPerSample*channels/8), otherwise fall back to the block align. + */ + if ((pWav->bitsPerSample & 0x7) == 0) { + /* Bits per sample is a multiple of 8. */ + bytesPerFrame = (pWav->bitsPerSample * pWav->fmt.channels) >> 3; + } else { + bytesPerFrame = pWav->fmt.blockAlign; + } + + /* Validation for known formats. a-law and mu-law should be 1 byte per channel. If it's not, it's not decodable. */ + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ALAW || pWav->translatedFormatTag == DR_WAVE_FORMAT_MULAW) { + if (bytesPerFrame != pWav->fmt.channels) { + return 0; /* Invalid file. */ + } + } + + return bytesPerFrame; +} + +DRWAV_API drwav_uint16 drwav_fmt_get_format(const drwav_fmt* pFMT) +{ + if (pFMT == NULL) { + return 0; + } + + if (pFMT->formatTag != DR_WAVE_FORMAT_EXTENSIBLE) { + return pFMT->formatTag; + } else { + return drwav_bytes_to_u16(pFMT->subFormat); /* Only the first two bytes are required. */ + } +} + +DRWAV_PRIVATE drwav_bool32 drwav_preinit(drwav* pWav, drwav_read_proc onRead, drwav_seek_proc onSeek, void* pReadSeekUserData, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + if (pWav == NULL || onRead == NULL || onSeek == NULL) { + return DRWAV_FALSE; + } + + DRWAV_ZERO_MEMORY(pWav, sizeof(*pWav)); + pWav->onRead = onRead; + pWav->onSeek = onSeek; + pWav->pUserData = pReadSeekUserData; + pWav->allocationCallbacks = drwav_copy_allocation_callbacks_or_defaults(pAllocationCallbacks); + + if (pWav->allocationCallbacks.onFree == NULL || (pWav->allocationCallbacks.onMalloc == NULL && pWav->allocationCallbacks.onRealloc == NULL)) { + return DRWAV_FALSE; /* Invalid allocation callbacks. */ + } + + return DRWAV_TRUE; +} + +DRWAV_PRIVATE drwav_bool32 drwav_init__internal(drwav* pWav, drwav_chunk_proc onChunk, void* pChunkUserData, drwav_uint32 flags) +{ + /* This function assumes drwav_preinit() has been called beforehand. */ + drwav_result result; + drwav_uint64 cursor; /* <-- Keeps track of the byte position so we can seek to specific locations. */ + drwav_bool32 sequential; + drwav_uint8 riff[4]; + drwav_fmt fmt; + unsigned short translatedFormatTag; + drwav_uint64 dataChunkSize = 0; /* <-- Important! Don't explicitly set this to 0 anywhere else. Calculation of the size of the data chunk is performed in different paths depending on the container. */ + drwav_uint64 sampleCountFromFactChunk = 0; /* Same as dataChunkSize - make sure this is the only place this is initialized to 0. */ + drwav_uint64 metadataStartPos; + drwav__metadata_parser metadataParser; + drwav_bool8 isProcessingMetadata = DRWAV_FALSE; + drwav_bool8 foundChunk_fmt = DRWAV_FALSE; + drwav_bool8 foundChunk_data = DRWAV_FALSE; + drwav_bool8 isAIFCFormType = DRWAV_FALSE; /* Only used with AIFF. */ + drwav_uint64 aiffFrameCount = 0; + + cursor = 0; + sequential = (flags & DRWAV_SEQUENTIAL) != 0; + DRWAV_ZERO_OBJECT(&fmt); + + /* The first 4 bytes should be the RIFF identifier. */ + if (drwav__on_read(pWav->onRead, pWav->pUserData, riff, sizeof(riff), &cursor) != sizeof(riff)) { + return DRWAV_FALSE; + } + + /* + The first 4 bytes can be used to identify the container. For RIFF files it will start with "RIFF" and for + w64 it will start with "riff". + */ + if (drwav_fourcc_equal(riff, "RIFF")) { + pWav->container = drwav_container_riff; + } else if (drwav_fourcc_equal(riff, "RIFX")) { + pWav->container = drwav_container_rifx; + } else if (drwav_fourcc_equal(riff, "riff")) { + int i; + drwav_uint8 riff2[12]; + + pWav->container = drwav_container_w64; + + /* Check the rest of the GUID for validity. */ + if (drwav__on_read(pWav->onRead, pWav->pUserData, riff2, sizeof(riff2), &cursor) != sizeof(riff2)) { + return DRWAV_FALSE; + } + + for (i = 0; i < 12; ++i) { + if (riff2[i] != drwavGUID_W64_RIFF[i+4]) { + return DRWAV_FALSE; + } + } + } else if (drwav_fourcc_equal(riff, "RF64")) { + pWav->container = drwav_container_rf64; + } else if (drwav_fourcc_equal(riff, "FORM")) { + pWav->container = drwav_container_aiff; + } else { + return DRWAV_FALSE; /* Unknown or unsupported container. */ + } + + + if (pWav->container == drwav_container_riff || pWav->container == drwav_container_rifx || pWav->container == drwav_container_rf64) { + drwav_uint8 chunkSizeBytes[4]; + drwav_uint8 wave[4]; + + if (drwav__on_read(pWav->onRead, pWav->pUserData, chunkSizeBytes, sizeof(chunkSizeBytes), &cursor) != sizeof(chunkSizeBytes)) { + return DRWAV_FALSE; + } + + if (pWav->container == drwav_container_riff || pWav->container == drwav_container_rifx) { + if (drwav_bytes_to_u32_ex(chunkSizeBytes, pWav->container) < 36) { + /* + I've had a report of a WAV file failing to load when the size of the WAVE chunk is not encoded + and is instead just set to 0. I'm going to relax the validation here to allow these files to + load. Considering the chunk size isn't actually used this should be safe. With this change my + test suite still passes. + */ + /*return DRWAV_FALSE;*/ /* Chunk size should always be at least 36 bytes. */ + } + } else if (pWav->container == drwav_container_rf64) { + if (drwav_bytes_to_u32_le(chunkSizeBytes) != 0xFFFFFFFF) { + return DRWAV_FALSE; /* Chunk size should always be set to -1/0xFFFFFFFF for RF64. The actual size is retrieved later. */ + } + } else { + return DRWAV_FALSE; /* Should never hit this. */ + } + + if (drwav__on_read(pWav->onRead, pWav->pUserData, wave, sizeof(wave), &cursor) != sizeof(wave)) { + return DRWAV_FALSE; + } + + if (!drwav_fourcc_equal(wave, "WAVE")) { + return DRWAV_FALSE; /* Expecting "WAVE". */ + } + } else if (pWav->container == drwav_container_w64) { + drwav_uint8 chunkSizeBytes[8]; + drwav_uint8 wave[16]; + + if (drwav__on_read(pWav->onRead, pWav->pUserData, chunkSizeBytes, sizeof(chunkSizeBytes), &cursor) != sizeof(chunkSizeBytes)) { + return DRWAV_FALSE; + } + + if (drwav_bytes_to_u64(chunkSizeBytes) < 80) { + return DRWAV_FALSE; + } + + if (drwav__on_read(pWav->onRead, pWav->pUserData, wave, sizeof(wave), &cursor) != sizeof(wave)) { + return DRWAV_FALSE; + } + + if (!drwav_guid_equal(wave, drwavGUID_W64_WAVE)) { + return DRWAV_FALSE; + } + } else if (pWav->container == drwav_container_aiff) { + drwav_uint8 chunkSizeBytes[4]; + drwav_uint8 aiff[4]; + + if (drwav__on_read(pWav->onRead, pWav->pUserData, chunkSizeBytes, sizeof(chunkSizeBytes), &cursor) != sizeof(chunkSizeBytes)) { + return DRWAV_FALSE; + } + + if (drwav_bytes_to_u32_be(chunkSizeBytes) < 18) { + return DRWAV_FALSE; + } + + if (drwav__on_read(pWav->onRead, pWav->pUserData, aiff, sizeof(aiff), &cursor) != sizeof(aiff)) { + return DRWAV_FALSE; + } + + if (drwav_fourcc_equal(aiff, "AIFF")) { + isAIFCFormType = DRWAV_FALSE; + } else if (drwav_fourcc_equal(aiff, "AIFC")) { + isAIFCFormType = DRWAV_TRUE; + } else { + return DRWAV_FALSE; /* Expecting "AIFF" or "AIFC". */ + } + } else { + return DRWAV_FALSE; + } + + + /* For RF64, the "ds64" chunk must come next, before the "fmt " chunk. */ + if (pWav->container == drwav_container_rf64) { + drwav_uint8 sizeBytes[8]; + drwav_uint64 bytesRemainingInChunk; + drwav_chunk_header header; + result = drwav__read_chunk_header(pWav->onRead, pWav->pUserData, pWav->container, &cursor, &header); + if (result != DRWAV_SUCCESS) { + return DRWAV_FALSE; + } + + if (!drwav_fourcc_equal(header.id.fourcc, "ds64")) { + return DRWAV_FALSE; /* Expecting "ds64". */ + } + + bytesRemainingInChunk = header.sizeInBytes + header.paddingSize; + + /* We don't care about the size of the RIFF chunk - skip it. */ + if (!drwav__seek_forward(pWav->onSeek, 8, pWav->pUserData)) { + return DRWAV_FALSE; + } + bytesRemainingInChunk -= 8; + cursor += 8; + + + /* Next 8 bytes is the size of the "data" chunk. */ + if (drwav__on_read(pWav->onRead, pWav->pUserData, sizeBytes, sizeof(sizeBytes), &cursor) != sizeof(sizeBytes)) { + return DRWAV_FALSE; + } + bytesRemainingInChunk -= 8; + dataChunkSize = drwav_bytes_to_u64(sizeBytes); + + + /* Next 8 bytes is the same count which we would usually derived from the FACT chunk if it was available. */ + if (drwav__on_read(pWav->onRead, pWav->pUserData, sizeBytes, sizeof(sizeBytes), &cursor) != sizeof(sizeBytes)) { + return DRWAV_FALSE; + } + bytesRemainingInChunk -= 8; + sampleCountFromFactChunk = drwav_bytes_to_u64(sizeBytes); + + + /* Skip over everything else. */ + if (!drwav__seek_forward(pWav->onSeek, bytesRemainingInChunk, pWav->pUserData)) { + return DRWAV_FALSE; + } + cursor += bytesRemainingInChunk; + } + + + metadataStartPos = cursor; + + /* + Whether or not we are processing metadata controls how we load. We can load more efficiently when + metadata is not being processed, but we also cannot process metadata for Wave64 because I have not + been able to test it. If someone is able to test this and provide a patch I'm happy to enable it. + + Seqential mode cannot support metadata because it involves seeking backwards. + */ + isProcessingMetadata = !sequential && ((flags & DRWAV_WITH_METADATA) != 0); + + /* Don't allow processing of metadata with untested containers. */ + if (pWav->container != drwav_container_riff && pWav->container != drwav_container_rf64) { + isProcessingMetadata = DRWAV_FALSE; + } + + DRWAV_ZERO_MEMORY(&metadataParser, sizeof(metadataParser)); + if (isProcessingMetadata) { + metadataParser.onRead = pWav->onRead; + metadataParser.onSeek = pWav->onSeek; + metadataParser.pReadSeekUserData = pWav->pUserData; + metadataParser.stage = drwav__metadata_parser_stage_count; + } + + + /* + From here on out, chunks might be in any order. In order to robustly handle metadata we'll need + to loop through every chunk and handle them as we find them. In sequential mode we need to get + out of the loop as soon as we find the data chunk because we won't be able to seek back. + */ + for (;;) { /* For each chunk... */ + drwav_chunk_header header; + drwav_uint64 chunkSize; + + result = drwav__read_chunk_header(pWav->onRead, pWav->pUserData, pWav->container, &cursor, &header); + if (result != DRWAV_SUCCESS) { + break; + } + + chunkSize = header.sizeInBytes; + + + /* + Always tell the caller about this chunk. We cannot do this in sequential mode because the + callback is allowed to read from the file, in which case we'll need to rewind. + */ + if (!sequential && onChunk != NULL) { + drwav_uint64 callbackBytesRead = onChunk(pChunkUserData, pWav->onRead, pWav->onSeek, pWav->pUserData, &header, pWav->container, &fmt); + + /* + dr_wav may need to read the contents of the chunk, so we now need to seek back to the position before + we called the callback. + */ + if (callbackBytesRead > 0) { + if (drwav__seek_from_start(pWav->onSeek, cursor, pWav->pUserData) == DRWAV_FALSE) { + return DRWAV_FALSE; + } + } + } + + + /* Explicitly handle known chunks first. */ + + /* "fmt " */ + if (((pWav->container == drwav_container_riff || pWav->container == drwav_container_rifx || pWav->container == drwav_container_rf64) && drwav_fourcc_equal(header.id.fourcc, "fmt ")) || + ((pWav->container == drwav_container_w64) && drwav_guid_equal(header.id.guid, drwavGUID_W64_FMT))) { + drwav_uint8 fmtData[16]; + + foundChunk_fmt = DRWAV_TRUE; + + if (pWav->onRead(pWav->pUserData, fmtData, sizeof(fmtData)) != sizeof(fmtData)) { + return DRWAV_FALSE; + } + cursor += sizeof(fmtData); + + fmt.formatTag = drwav_bytes_to_u16_ex(fmtData + 0, pWav->container); + fmt.channels = drwav_bytes_to_u16_ex(fmtData + 2, pWav->container); + fmt.sampleRate = drwav_bytes_to_u32_ex(fmtData + 4, pWav->container); + fmt.avgBytesPerSec = drwav_bytes_to_u32_ex(fmtData + 8, pWav->container); + fmt.blockAlign = drwav_bytes_to_u16_ex(fmtData + 12, pWav->container); + fmt.bitsPerSample = drwav_bytes_to_u16_ex(fmtData + 14, pWav->container); + + fmt.extendedSize = 0; + fmt.validBitsPerSample = 0; + fmt.channelMask = 0; + DRWAV_ZERO_MEMORY(fmt.subFormat, sizeof(fmt.subFormat)); + + if (header.sizeInBytes > 16) { + drwav_uint8 fmt_cbSize[2]; + int bytesReadSoFar = 0; + + if (pWav->onRead(pWav->pUserData, fmt_cbSize, sizeof(fmt_cbSize)) != sizeof(fmt_cbSize)) { + return DRWAV_FALSE; /* Expecting more data. */ + } + cursor += sizeof(fmt_cbSize); + + bytesReadSoFar = 18; + + fmt.extendedSize = drwav_bytes_to_u16_ex(fmt_cbSize, pWav->container); + if (fmt.extendedSize > 0) { + /* Simple validation. */ + if (fmt.formatTag == DR_WAVE_FORMAT_EXTENSIBLE) { + if (fmt.extendedSize != 22) { + return DRWAV_FALSE; + } + } + + if (fmt.formatTag == DR_WAVE_FORMAT_EXTENSIBLE) { + drwav_uint8 fmtext[22]; + + if (pWav->onRead(pWav->pUserData, fmtext, fmt.extendedSize) != fmt.extendedSize) { + return DRWAV_FALSE; /* Expecting more data. */ + } + + fmt.validBitsPerSample = drwav_bytes_to_u16_ex(fmtext + 0, pWav->container); + fmt.channelMask = drwav_bytes_to_u32_ex(fmtext + 2, pWav->container); + drwav_bytes_to_guid(fmtext + 6, fmt.subFormat); + } else { + if (pWav->onSeek(pWav->pUserData, fmt.extendedSize, drwav_seek_origin_current) == DRWAV_FALSE) { + return DRWAV_FALSE; + } + } + cursor += fmt.extendedSize; + + bytesReadSoFar += fmt.extendedSize; + } + + /* Seek past any leftover bytes. For w64 the leftover will be defined based on the chunk size. */ + if (pWav->onSeek(pWav->pUserData, (int)(header.sizeInBytes - bytesReadSoFar), drwav_seek_origin_current) == DRWAV_FALSE) { + return DRWAV_FALSE; + } + cursor += (header.sizeInBytes - bytesReadSoFar); + } + + if (header.paddingSize > 0) { + if (drwav__seek_forward(pWav->onSeek, header.paddingSize, pWav->pUserData) == DRWAV_FALSE) { + break; + } + cursor += header.paddingSize; + } + + /* Go to the next chunk. Don't include this chunk in metadata. */ + continue; + } + + /* "data" */ + if (((pWav->container == drwav_container_riff || pWav->container == drwav_container_rifx || pWav->container == drwav_container_rf64) && drwav_fourcc_equal(header.id.fourcc, "data")) || + ((pWav->container == drwav_container_w64) && drwav_guid_equal(header.id.guid, drwavGUID_W64_DATA))) { + foundChunk_data = DRWAV_TRUE; + + pWav->dataChunkDataPos = cursor; + + if (pWav->container != drwav_container_rf64) { /* The data chunk size for RF64 will always be set to 0xFFFFFFFF here. It was set to it's true value earlier. */ + dataChunkSize = chunkSize; + } + + /* If we're running in sequential mode, or we're not reading metadata, we have enough now that we can get out of the loop. */ + if (sequential || !isProcessingMetadata) { + break; /* No need to keep reading beyond the data chunk. */ + } else { + chunkSize += header.paddingSize; /* <-- Make sure we seek past the padding. */ + if (drwav__seek_forward(pWav->onSeek, chunkSize, pWav->pUserData) == DRWAV_FALSE) { + break; + } + cursor += chunkSize; + + continue; /* There may be some more metadata to read. */ + } + } + + /* "fact". This is optional. Can use this to get the sample count which is useful for compressed formats. For RF64 we retrieved the sample count from the ds64 chunk earlier. */ + if (((pWav->container == drwav_container_riff || pWav->container == drwav_container_rifx || pWav->container == drwav_container_rf64) && drwav_fourcc_equal(header.id.fourcc, "fact")) || + ((pWav->container == drwav_container_w64) && drwav_guid_equal(header.id.guid, drwavGUID_W64_FACT))) { + if (pWav->container == drwav_container_riff || pWav->container == drwav_container_rifx) { + drwav_uint8 sampleCount[4]; + if (drwav__on_read(pWav->onRead, pWav->pUserData, &sampleCount, 4, &cursor) != 4) { + return DRWAV_FALSE; + } + + chunkSize -= 4; + + /* + The sample count in the "fact" chunk is either unreliable, or I'm not understanding it properly. For now I am only enabling this + for Microsoft ADPCM formats. + */ + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM) { + sampleCountFromFactChunk = drwav_bytes_to_u32_ex(sampleCount, pWav->container); + } else { + sampleCountFromFactChunk = 0; + } + } else if (pWav->container == drwav_container_w64) { + if (drwav__on_read(pWav->onRead, pWav->pUserData, &sampleCountFromFactChunk, 8, &cursor) != 8) { + return DRWAV_FALSE; + } + + chunkSize -= 8; + } else if (pWav->container == drwav_container_rf64) { + /* We retrieved the sample count from the ds64 chunk earlier so no need to do that here. */ + } + + /* Seek to the next chunk in preparation for the next iteration. */ + chunkSize += header.paddingSize; /* <-- Make sure we seek past the padding. */ + if (drwav__seek_forward(pWav->onSeek, chunkSize, pWav->pUserData) == DRWAV_FALSE) { + break; + } + cursor += chunkSize; + + continue; + } + + + /* "COMM". AIFF/AIFC only. */ + if (pWav->container == drwav_container_aiff && drwav_fourcc_equal(header.id.fourcc, "COMM")) { + drwav_uint8 commData[24]; + drwav_uint32 commDataBytesToRead; + drwav_uint16 channels; + drwav_uint32 frameCount; + drwav_uint16 sampleSizeInBits; + drwav_int64 sampleRate; + drwav_uint16 compressionFormat; + + foundChunk_fmt = DRWAV_TRUE; + + if (isAIFCFormType) { + commDataBytesToRead = 24; + if (header.sizeInBytes < commDataBytesToRead) { + return DRWAV_FALSE; /* Invalid COMM chunk. */ + } + } else { + commDataBytesToRead = 18; + if (header.sizeInBytes != commDataBytesToRead) { + return DRWAV_FALSE; /* INVALID COMM chunk. */ + } + } + + if (drwav__on_read(pWav->onRead, pWav->pUserData, commData, commDataBytesToRead, &cursor) != commDataBytesToRead) { + return DRWAV_FALSE; + } + + + channels = drwav_bytes_to_u16_ex (commData + 0, pWav->container); + frameCount = drwav_bytes_to_u32_ex (commData + 2, pWav->container); + sampleSizeInBits = drwav_bytes_to_u16_ex (commData + 6, pWav->container); + sampleRate = drwav_aiff_extented_to_s64(commData + 8); + + if (sampleRate < 0 || sampleRate > 0xFFFFFFFF) { + return DRWAV_FALSE; /* Invalid sample rate. */ + } + + if (isAIFCFormType) { + const drwav_uint8* type = commData + 18; + + if (drwav_fourcc_equal(type, "NONE")) { + compressionFormat = DR_WAVE_FORMAT_PCM; /* PCM, big-endian. */ + } else if (drwav_fourcc_equal(type, "raw ")) { + compressionFormat = DR_WAVE_FORMAT_PCM; + + /* In my testing, it looks like when the "raw " compression type is used, 8-bit samples should be considered unsigned. */ + if (sampleSizeInBits == 8) { + pWav->aiff.isUnsigned = DRWAV_TRUE; + } + } else if (drwav_fourcc_equal(type, "sowt")) { + compressionFormat = DR_WAVE_FORMAT_PCM; /* PCM, little-endian. */ + pWav->aiff.isLE = DRWAV_TRUE; + } else if (drwav_fourcc_equal(type, "fl32") || drwav_fourcc_equal(type, "fl64") || drwav_fourcc_equal(type, "FL32") || drwav_fourcc_equal(type, "FL64")) { + compressionFormat = DR_WAVE_FORMAT_IEEE_FLOAT; + } else if (drwav_fourcc_equal(type, "alaw") || drwav_fourcc_equal(type, "ALAW")) { + compressionFormat = DR_WAVE_FORMAT_ALAW; + } else if (drwav_fourcc_equal(type, "ulaw") || drwav_fourcc_equal(type, "ULAW")) { + compressionFormat = DR_WAVE_FORMAT_MULAW; + } else if (drwav_fourcc_equal(type, "ima4")) { + compressionFormat = DR_WAVE_FORMAT_DVI_ADPCM; + sampleSizeInBits = 4; + + /* + I haven't been able to figure out how to get correct decoding for IMA ADPCM. Until this is figured out + we'll need to abort when we encounter such an encoding. Advice welcome! + */ + return DRWAV_FALSE; + } else { + return DRWAV_FALSE; /* Unknown or unsupported compression format. Need to abort. */ + } + } else { + compressionFormat = DR_WAVE_FORMAT_PCM; /* It's a standard AIFF form which is always compressed. */ + } + + /* With AIFF we want to use the explicitly defined frame count rather than deriving it from the size of the chunk. */ + aiffFrameCount = frameCount; + + /* We should now have enough information to fill out our fmt structure. */ + fmt.formatTag = compressionFormat; + fmt.channels = channels; + fmt.sampleRate = (drwav_uint32)sampleRate; + fmt.bitsPerSample = sampleSizeInBits; + fmt.blockAlign = (drwav_uint16)(fmt.channels * fmt.bitsPerSample / 8); + fmt.avgBytesPerSec = fmt.blockAlign * fmt.sampleRate; + + if (fmt.blockAlign == 0 && compressionFormat == DR_WAVE_FORMAT_DVI_ADPCM) { + fmt.blockAlign = 34 * fmt.channels; + } + + /* + Weird one. I've seen some alaw and ulaw encoded files that for some reason set the bits per sample to 16 when + it should be 8. To get this working I need to explicitly check for this and change it. + */ + if (compressionFormat == DR_WAVE_FORMAT_ALAW || compressionFormat == DR_WAVE_FORMAT_MULAW) { + if (fmt.bitsPerSample > 8) { + fmt.bitsPerSample = 8; + fmt.blockAlign = fmt.channels; + } + } + + /* In AIFF, samples are padded to 8 byte boundaries. We need to round up our bits per sample here. */ + fmt.bitsPerSample += (fmt.bitsPerSample & 7); + + + /* If the form type is AIFC there will be some additional data in the chunk. We need to seek past it. */ + if (isAIFCFormType) { + if (drwav__seek_forward(pWav->onSeek, (chunkSize - commDataBytesToRead), pWav->pUserData) == DRWAV_FALSE) { + return DRWAV_FALSE; + } + cursor += (chunkSize - commDataBytesToRead); + } + + /* Don't fall through or else we'll end up treating this chunk as metadata which is incorrect. */ + continue; + } + + + /* "SSND". AIFF/AIFC only. This is the AIFF equivalent of the "data" chunk. */ + if (pWav->container == drwav_container_aiff && drwav_fourcc_equal(header.id.fourcc, "SSND")) { + drwav_uint8 offsetAndBlockSizeData[8]; + drwav_uint32 offset; + + foundChunk_data = DRWAV_TRUE; + + if (drwav__on_read(pWav->onRead, pWav->pUserData, offsetAndBlockSizeData, sizeof(offsetAndBlockSizeData), &cursor) != sizeof(offsetAndBlockSizeData)) { + return DRWAV_FALSE; + } + + /* We need to seek forward by the offset. */ + offset = drwav_bytes_to_u32_ex(offsetAndBlockSizeData + 0, pWav->container); + if (drwav__seek_forward(pWav->onSeek, offset, pWav->pUserData) == DRWAV_FALSE) { + return DRWAV_FALSE; + } + cursor += offset; + + pWav->dataChunkDataPos = cursor; + dataChunkSize = chunkSize; + + /* If we're running in sequential mode, or we're not reading metadata, we have enough now that we can get out of the loop. */ + if (sequential || !isProcessingMetadata) { + break; /* No need to keep reading beyond the data chunk. */ + } else { + if (drwav__seek_forward(pWav->onSeek, chunkSize, pWav->pUserData) == DRWAV_FALSE) { + break; + } + cursor += chunkSize; + + continue; /* There may be some more metadata to read. */ + } + } + + + + /* Getting here means it's not a chunk that we care about internally, but might need to be handled as metadata by the caller. */ + if (isProcessingMetadata) { + drwav__metadata_process_chunk(&metadataParser, &header, drwav_metadata_type_all_including_unknown); + + /* Go back to the start of the chunk so we can normalize the position of the cursor. */ + if (drwav__seek_from_start(pWav->onSeek, cursor, pWav->pUserData) == DRWAV_FALSE) { + break; /* Failed to seek. Can't reliable read the remaining chunks. Get out. */ + } + } + + + /* Make sure we skip past the content of this chunk before we go to the next one. */ + chunkSize += header.paddingSize; /* <-- Make sure we seek past the padding. */ + if (drwav__seek_forward(pWav->onSeek, chunkSize, pWav->pUserData) == DRWAV_FALSE) { + break; + } + cursor += chunkSize; + } + + /* There's some mandatory chunks that must exist. If they were not found in the iteration above we must abort. */ + if (!foundChunk_fmt || !foundChunk_data) { + return DRWAV_FALSE; + } + + /* Basic validation. */ + if ((fmt.sampleRate == 0 || fmt.sampleRate > DRWAV_MAX_SAMPLE_RATE ) || + (fmt.channels == 0 || fmt.channels > DRWAV_MAX_CHANNELS ) || + (fmt.bitsPerSample == 0 || fmt.bitsPerSample > DRWAV_MAX_BITS_PER_SAMPLE) || + fmt.blockAlign == 0) { + return DRWAV_FALSE; /* Probably an invalid WAV file. */ + } + + /* Translate the internal format. */ + translatedFormatTag = fmt.formatTag; + if (translatedFormatTag == DR_WAVE_FORMAT_EXTENSIBLE) { + translatedFormatTag = drwav_bytes_to_u16_ex(fmt.subFormat + 0, pWav->container); + } + + /* We may have moved passed the data chunk. If so we need to move back. If running in sequential mode we can assume we are already sitting on the data chunk. */ + if (!sequential) { + if (!drwav__seek_from_start(pWav->onSeek, pWav->dataChunkDataPos, pWav->pUserData)) { + return DRWAV_FALSE; + } + cursor = pWav->dataChunkDataPos; + } + + + /* + At this point we should have done the initial parsing of each of our chunks, but we now need to + do a second pass to extract the actual contents of the metadata (the first pass just calculated + the length of the memory allocation). + + We only do this if we've actually got metadata to parse. + */ + if (isProcessingMetadata && metadataParser.metadataCount > 0) { + if (drwav__seek_from_start(pWav->onSeek, metadataStartPos, pWav->pUserData) == DRWAV_FALSE) { + return DRWAV_FALSE; + } + + result = drwav__metadata_alloc(&metadataParser, &pWav->allocationCallbacks); + if (result != DRWAV_SUCCESS) { + return DRWAV_FALSE; + } + + metadataParser.stage = drwav__metadata_parser_stage_read; + + for (;;) { + drwav_chunk_header header; + drwav_uint64 metadataBytesRead; + + result = drwav__read_chunk_header(pWav->onRead, pWav->pUserData, pWav->container, &cursor, &header); + if (result != DRWAV_SUCCESS) { + break; + } + + metadataBytesRead = drwav__metadata_process_chunk(&metadataParser, &header, drwav_metadata_type_all_including_unknown); + + /* Move to the end of the chunk so we can keep iterating. */ + if (drwav__seek_forward(pWav->onSeek, (header.sizeInBytes + header.paddingSize) - metadataBytesRead, pWav->pUserData) == DRWAV_FALSE) { + drwav_free(metadataParser.pMetadata, &pWav->allocationCallbacks); + return DRWAV_FALSE; + } + } + + /* Getting here means we're finished parsing the metadata. */ + pWav->pMetadata = metadataParser.pMetadata; + pWav->metadataCount = metadataParser.metadataCount; + } + + + /* At this point we should be sitting on the first byte of the raw audio data. */ + + /* + I've seen a WAV file in the wild where a RIFF-ecapsulated file has the size of it's "RIFF" and + "data" chunks set to 0xFFFFFFFF when the file is definitely not that big. In this case we're + going to have to calculate the size by reading and discarding bytes, and then seeking back. We + cannot do this in sequential mode. We just assume that the rest of the file is audio data. + */ + if (dataChunkSize == 0xFFFFFFFF && (pWav->container == drwav_container_riff || pWav->container == drwav_container_rifx) && pWav->isSequentialWrite == DRWAV_FALSE) { + dataChunkSize = 0; + + for (;;) { + drwav_uint8 temp[4096]; + size_t bytesRead = pWav->onRead(pWav->pUserData, temp, sizeof(temp)); + dataChunkSize += bytesRead; + + if (bytesRead < sizeof(temp)) { + break; + } + } + } + + if (drwav__seek_from_start(pWav->onSeek, pWav->dataChunkDataPos, pWav->pUserData) == DRWAV_FALSE) { + drwav_free(pWav->pMetadata, &pWav->allocationCallbacks); + return DRWAV_FALSE; + } + + + pWav->fmt = fmt; + pWav->sampleRate = fmt.sampleRate; + pWav->channels = fmt.channels; + pWav->bitsPerSample = fmt.bitsPerSample; + pWav->bytesRemaining = dataChunkSize; + pWav->translatedFormatTag = translatedFormatTag; + pWav->dataChunkDataSize = dataChunkSize; + + if (sampleCountFromFactChunk != 0) { + pWav->totalPCMFrameCount = sampleCountFromFactChunk; + } else if (aiffFrameCount != 0) { + pWav->totalPCMFrameCount = aiffFrameCount; + } else { + drwav_uint32 bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); + if (bytesPerFrame == 0) { + drwav_free(pWav->pMetadata, &pWav->allocationCallbacks); + return DRWAV_FALSE; /* Invalid file. */ + } + + pWav->totalPCMFrameCount = dataChunkSize / bytesPerFrame; + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM) { + drwav_uint64 totalBlockHeaderSizeInBytes; + drwav_uint64 blockCount = dataChunkSize / fmt.blockAlign; + + /* Make sure any trailing partial block is accounted for. */ + if ((blockCount * fmt.blockAlign) < dataChunkSize) { + blockCount += 1; + } + + /* We decode two samples per byte. There will be blockCount headers in the data chunk. This is enough to know how to calculate the total PCM frame count. */ + totalBlockHeaderSizeInBytes = blockCount * (6*fmt.channels); + pWav->totalPCMFrameCount = ((dataChunkSize - totalBlockHeaderSizeInBytes) * 2) / fmt.channels; + } + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_DVI_ADPCM) { + drwav_uint64 totalBlockHeaderSizeInBytes; + drwav_uint64 blockCount = dataChunkSize / fmt.blockAlign; + + /* Make sure any trailing partial block is accounted for. */ + if ((blockCount * fmt.blockAlign) < dataChunkSize) { + blockCount += 1; + } + + /* We decode two samples per byte. There will be blockCount headers in the data chunk. This is enough to know how to calculate the total PCM frame count. */ + totalBlockHeaderSizeInBytes = blockCount * (4*fmt.channels); + pWav->totalPCMFrameCount = ((dataChunkSize - totalBlockHeaderSizeInBytes) * 2) / fmt.channels; + + /* The header includes a decoded sample for each channel which acts as the initial predictor sample. */ + pWav->totalPCMFrameCount += blockCount; + } + } + + /* Some formats only support a certain number of channels. */ + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM || pWav->translatedFormatTag == DR_WAVE_FORMAT_DVI_ADPCM) { + if (pWav->channels > 2) { + drwav_free(pWav->pMetadata, &pWav->allocationCallbacks); + return DRWAV_FALSE; + } + } + + /* The number of bytes per frame must be known. If not, it's an invalid file and not decodable. */ + if (drwav_get_bytes_per_pcm_frame(pWav) == 0) { + drwav_free(pWav->pMetadata, &pWav->allocationCallbacks); + return DRWAV_FALSE; + } + +#ifdef DR_WAV_LIBSNDFILE_COMPAT + /* + I use libsndfile as a benchmark for testing, however in the version I'm using (from the Windows installer on the libsndfile website), + it appears the total sample count libsndfile uses for MS-ADPCM is incorrect. It would seem they are computing the total sample count + from the number of blocks, however this results in the inclusion of extra silent samples at the end of the last block. The correct + way to know the total sample count is to inspect the "fact" chunk, which should always be present for compressed formats, and should + always include the sample count. This little block of code below is only used to emulate the libsndfile logic so I can properly run my + correctness tests against libsndfile, and is disabled by default. + */ + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM) { + drwav_uint64 blockCount = dataChunkSize / fmt.blockAlign; + pWav->totalPCMFrameCount = (((blockCount * (fmt.blockAlign - (6*pWav->channels))) * 2)) / fmt.channels; /* x2 because two samples per byte. */ + } + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_DVI_ADPCM) { + drwav_uint64 blockCount = dataChunkSize / fmt.blockAlign; + pWav->totalPCMFrameCount = (((blockCount * (fmt.blockAlign - (4*pWav->channels))) * 2) + (blockCount * pWav->channels)) / fmt.channels; + } +#endif + + return DRWAV_TRUE; +} + +DRWAV_API drwav_bool32 drwav_init(drwav* pWav, drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + return drwav_init_ex(pWav, onRead, onSeek, NULL, pUserData, NULL, 0, pAllocationCallbacks); +} + +DRWAV_API drwav_bool32 drwav_init_ex(drwav* pWav, drwav_read_proc onRead, drwav_seek_proc onSeek, drwav_chunk_proc onChunk, void* pReadSeekUserData, void* pChunkUserData, drwav_uint32 flags, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + if (!drwav_preinit(pWav, onRead, onSeek, pReadSeekUserData, pAllocationCallbacks)) { + return DRWAV_FALSE; + } + + return drwav_init__internal(pWav, onChunk, pChunkUserData, flags); +} + +DRWAV_API drwav_bool32 drwav_init_with_metadata(drwav* pWav, drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, drwav_uint32 flags, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + if (!drwav_preinit(pWav, onRead, onSeek, pUserData, pAllocationCallbacks)) { + return DRWAV_FALSE; + } + + return drwav_init__internal(pWav, NULL, NULL, flags | DRWAV_WITH_METADATA); +} + +DRWAV_API drwav_metadata* drwav_take_ownership_of_metadata(drwav* pWav) +{ + drwav_metadata *result = pWav->pMetadata; + + pWav->pMetadata = NULL; + pWav->metadataCount = 0; + + return result; +} + + +DRWAV_PRIVATE size_t drwav__write(drwav* pWav, const void* pData, size_t dataSize) +{ + DRWAV_ASSERT(pWav != NULL); + DRWAV_ASSERT(pWav->onWrite != NULL); + + /* Generic write. Assumes no byte reordering required. */ + return pWav->onWrite(pWav->pUserData, pData, dataSize); +} + +DRWAV_PRIVATE size_t drwav__write_byte(drwav* pWav, drwav_uint8 byte) +{ + DRWAV_ASSERT(pWav != NULL); + DRWAV_ASSERT(pWav->onWrite != NULL); + + return pWav->onWrite(pWav->pUserData, &byte, 1); +} + +DRWAV_PRIVATE size_t drwav__write_u16ne_to_le(drwav* pWav, drwav_uint16 value) +{ + DRWAV_ASSERT(pWav != NULL); + DRWAV_ASSERT(pWav->onWrite != NULL); + + if (!drwav__is_little_endian()) { + value = drwav__bswap16(value); + } + + return drwav__write(pWav, &value, 2); +} + +DRWAV_PRIVATE size_t drwav__write_u32ne_to_le(drwav* pWav, drwav_uint32 value) +{ + DRWAV_ASSERT(pWav != NULL); + DRWAV_ASSERT(pWav->onWrite != NULL); + + if (!drwav__is_little_endian()) { + value = drwav__bswap32(value); + } + + return drwav__write(pWav, &value, 4); +} + +DRWAV_PRIVATE size_t drwav__write_u64ne_to_le(drwav* pWav, drwav_uint64 value) +{ + DRWAV_ASSERT(pWav != NULL); + DRWAV_ASSERT(pWav->onWrite != NULL); + + if (!drwav__is_little_endian()) { + value = drwav__bswap64(value); + } + + return drwav__write(pWav, &value, 8); +} + +DRWAV_PRIVATE size_t drwav__write_f32ne_to_le(drwav* pWav, float value) +{ + union { + drwav_uint32 u32; + float f32; + } u; + + DRWAV_ASSERT(pWav != NULL); + DRWAV_ASSERT(pWav->onWrite != NULL); + + u.f32 = value; + + if (!drwav__is_little_endian()) { + u.u32 = drwav__bswap32(u.u32); + } + + return drwav__write(pWav, &u.u32, 4); +} + +DRWAV_PRIVATE size_t drwav__write_or_count(drwav* pWav, const void* pData, size_t dataSize) +{ + if (pWav == NULL) { + return dataSize; + } + + return drwav__write(pWav, pData, dataSize); +} + +DRWAV_PRIVATE size_t drwav__write_or_count_byte(drwav* pWav, drwav_uint8 byte) +{ + if (pWav == NULL) { + return 1; + } + + return drwav__write_byte(pWav, byte); +} + +DRWAV_PRIVATE size_t drwav__write_or_count_u16ne_to_le(drwav* pWav, drwav_uint16 value) +{ + if (pWav == NULL) { + return 2; + } + + return drwav__write_u16ne_to_le(pWav, value); +} + +DRWAV_PRIVATE size_t drwav__write_or_count_u32ne_to_le(drwav* pWav, drwav_uint32 value) +{ + if (pWav == NULL) { + return 4; + } + + return drwav__write_u32ne_to_le(pWav, value); +} + +#if 0 /* Unused for now. */ +DRWAV_PRIVATE size_t drwav__write_or_count_u64ne_to_le(drwav* pWav, drwav_uint64 value) +{ + if (pWav == NULL) { + return 8; + } + + return drwav__write_u64ne_to_le(pWav, value); +} +#endif + +DRWAV_PRIVATE size_t drwav__write_or_count_f32ne_to_le(drwav* pWav, float value) +{ + if (pWav == NULL) { + return 4; + } + + return drwav__write_f32ne_to_le(pWav, value); +} + +DRWAV_PRIVATE size_t drwav__write_or_count_string_to_fixed_size_buf(drwav* pWav, char* str, size_t bufFixedSize) +{ + size_t len; + + if (pWav == NULL) { + return bufFixedSize; + } + + len = drwav__strlen_clamped(str, bufFixedSize); + drwav__write_or_count(pWav, str, len); + + if (len < bufFixedSize) { + size_t i; + for (i = 0; i < bufFixedSize - len; ++i) { + drwav__write_byte(pWav, 0); + } + } + + return bufFixedSize; +} + + +/* pWav can be NULL meaning just count the bytes that would be written. */ +DRWAV_PRIVATE size_t drwav__write_or_count_metadata(drwav* pWav, drwav_metadata* pMetadatas, drwav_uint32 metadataCount) +{ + size_t bytesWritten = 0; + drwav_bool32 hasListAdtl = DRWAV_FALSE; + drwav_bool32 hasListInfo = DRWAV_FALSE; + drwav_uint32 iMetadata; + + if (pMetadatas == NULL || metadataCount == 0) { + return 0; + } + + for (iMetadata = 0; iMetadata < metadataCount; ++iMetadata) { + drwav_metadata* pMetadata = &pMetadatas[iMetadata]; + drwav_uint32 chunkSize = 0; + + if ((pMetadata->type & drwav_metadata_type_list_all_info_strings) || (pMetadata->type == drwav_metadata_type_unknown && pMetadata->data.unknown.chunkLocation == drwav_metadata_location_inside_info_list)) { + hasListInfo = DRWAV_TRUE; + } + + if ((pMetadata->type & drwav_metadata_type_list_all_adtl) || (pMetadata->type == drwav_metadata_type_unknown && pMetadata->data.unknown.chunkLocation == drwav_metadata_location_inside_adtl_list)) { + hasListAdtl = DRWAV_TRUE; + } + + switch (pMetadata->type) { + case drwav_metadata_type_smpl: + { + drwav_uint32 iLoop; + + chunkSize = DRWAV_SMPL_BYTES + DRWAV_SMPL_LOOP_BYTES * pMetadata->data.smpl.sampleLoopCount + pMetadata->data.smpl.samplerSpecificDataSizeInBytes; + + bytesWritten += drwav__write_or_count(pWav, "smpl", 4); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, chunkSize); + + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.smpl.manufacturerId); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.smpl.productId); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.smpl.samplePeriodNanoseconds); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.smpl.midiUnityNote); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.smpl.midiPitchFraction); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.smpl.smpteFormat); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.smpl.smpteOffset); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.smpl.sampleLoopCount); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.smpl.samplerSpecificDataSizeInBytes); + + for (iLoop = 0; iLoop < pMetadata->data.smpl.sampleLoopCount; ++iLoop) { + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.smpl.pLoops[iLoop].cuePointId); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.smpl.pLoops[iLoop].type); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.smpl.pLoops[iLoop].firstSampleByteOffset); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.smpl.pLoops[iLoop].lastSampleByteOffset); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.smpl.pLoops[iLoop].sampleFraction); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.smpl.pLoops[iLoop].playCount); + } + + if (pMetadata->data.smpl.samplerSpecificDataSizeInBytes > 0) { + bytesWritten += drwav__write_or_count(pWav, pMetadata->data.smpl.pSamplerSpecificData, pMetadata->data.smpl.samplerSpecificDataSizeInBytes); + } + } break; + + case drwav_metadata_type_inst: + { + chunkSize = DRWAV_INST_BYTES; + + bytesWritten += drwav__write_or_count(pWav, "inst", 4); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, chunkSize); + bytesWritten += drwav__write_or_count(pWav, &pMetadata->data.inst.midiUnityNote, 1); + bytesWritten += drwav__write_or_count(pWav, &pMetadata->data.inst.fineTuneCents, 1); + bytesWritten += drwav__write_or_count(pWav, &pMetadata->data.inst.gainDecibels, 1); + bytesWritten += drwav__write_or_count(pWav, &pMetadata->data.inst.lowNote, 1); + bytesWritten += drwav__write_or_count(pWav, &pMetadata->data.inst.highNote, 1); + bytesWritten += drwav__write_or_count(pWav, &pMetadata->data.inst.lowVelocity, 1); + bytesWritten += drwav__write_or_count(pWav, &pMetadata->data.inst.highVelocity, 1); + } break; + + case drwav_metadata_type_cue: + { + drwav_uint32 iCuePoint; + + chunkSize = DRWAV_CUE_BYTES + DRWAV_CUE_POINT_BYTES * pMetadata->data.cue.cuePointCount; + + bytesWritten += drwav__write_or_count(pWav, "cue ", 4); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, chunkSize); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.cue.cuePointCount); + for (iCuePoint = 0; iCuePoint < pMetadata->data.cue.cuePointCount; ++iCuePoint) { + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.cue.pCuePoints[iCuePoint].id); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.cue.pCuePoints[iCuePoint].playOrderPosition); + bytesWritten += drwav__write_or_count(pWav, pMetadata->data.cue.pCuePoints[iCuePoint].dataChunkId, 4); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.cue.pCuePoints[iCuePoint].chunkStart); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.cue.pCuePoints[iCuePoint].blockStart); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.cue.pCuePoints[iCuePoint].sampleByteOffset); + } + } break; + + case drwav_metadata_type_acid: + { + chunkSize = DRWAV_ACID_BYTES; + + bytesWritten += drwav__write_or_count(pWav, "acid", 4); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, chunkSize); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.acid.flags); + bytesWritten += drwav__write_or_count_u16ne_to_le(pWav, pMetadata->data.acid.midiUnityNote); + bytesWritten += drwav__write_or_count_u16ne_to_le(pWav, pMetadata->data.acid.reserved1); + bytesWritten += drwav__write_or_count_f32ne_to_le(pWav, pMetadata->data.acid.reserved2); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.acid.numBeats); + bytesWritten += drwav__write_or_count_u16ne_to_le(pWav, pMetadata->data.acid.meterDenominator); + bytesWritten += drwav__write_or_count_u16ne_to_le(pWav, pMetadata->data.acid.meterNumerator); + bytesWritten += drwav__write_or_count_f32ne_to_le(pWav, pMetadata->data.acid.tempo); + } break; + + case drwav_metadata_type_bext: + { + char reservedBuf[DRWAV_BEXT_RESERVED_BYTES]; + drwav_uint32 timeReferenceLow; + drwav_uint32 timeReferenceHigh; + + chunkSize = DRWAV_BEXT_BYTES + pMetadata->data.bext.codingHistorySize; + + bytesWritten += drwav__write_or_count(pWav, "bext", 4); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, chunkSize); + + bytesWritten += drwav__write_or_count_string_to_fixed_size_buf(pWav, pMetadata->data.bext.pDescription, DRWAV_BEXT_DESCRIPTION_BYTES); + bytesWritten += drwav__write_or_count_string_to_fixed_size_buf(pWav, pMetadata->data.bext.pOriginatorName, DRWAV_BEXT_ORIGINATOR_NAME_BYTES); + bytesWritten += drwav__write_or_count_string_to_fixed_size_buf(pWav, pMetadata->data.bext.pOriginatorReference, DRWAV_BEXT_ORIGINATOR_REF_BYTES); + bytesWritten += drwav__write_or_count(pWav, pMetadata->data.bext.pOriginationDate, sizeof(pMetadata->data.bext.pOriginationDate)); + bytesWritten += drwav__write_or_count(pWav, pMetadata->data.bext.pOriginationTime, sizeof(pMetadata->data.bext.pOriginationTime)); + + timeReferenceLow = (drwav_uint32)(pMetadata->data.bext.timeReference & 0xFFFFFFFF); + timeReferenceHigh = (drwav_uint32)(pMetadata->data.bext.timeReference >> 32); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, timeReferenceLow); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, timeReferenceHigh); + + bytesWritten += drwav__write_or_count_u16ne_to_le(pWav, pMetadata->data.bext.version); + bytesWritten += drwav__write_or_count(pWav, pMetadata->data.bext.pUMID, DRWAV_BEXT_UMID_BYTES); + bytesWritten += drwav__write_or_count_u16ne_to_le(pWav, pMetadata->data.bext.loudnessValue); + bytesWritten += drwav__write_or_count_u16ne_to_le(pWav, pMetadata->data.bext.loudnessRange); + bytesWritten += drwav__write_or_count_u16ne_to_le(pWav, pMetadata->data.bext.maxTruePeakLevel); + bytesWritten += drwav__write_or_count_u16ne_to_le(pWav, pMetadata->data.bext.maxMomentaryLoudness); + bytesWritten += drwav__write_or_count_u16ne_to_le(pWav, pMetadata->data.bext.maxShortTermLoudness); + + DRWAV_ZERO_MEMORY(reservedBuf, sizeof(reservedBuf)); + bytesWritten += drwav__write_or_count(pWav, reservedBuf, sizeof(reservedBuf)); + + if (pMetadata->data.bext.codingHistorySize > 0) { + bytesWritten += drwav__write_or_count(pWav, pMetadata->data.bext.pCodingHistory, pMetadata->data.bext.codingHistorySize); + } + } break; + + case drwav_metadata_type_unknown: + { + if (pMetadata->data.unknown.chunkLocation == drwav_metadata_location_top_level) { + chunkSize = pMetadata->data.unknown.dataSizeInBytes; + + bytesWritten += drwav__write_or_count(pWav, pMetadata->data.unknown.id, 4); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, chunkSize); + bytesWritten += drwav__write_or_count(pWav, pMetadata->data.unknown.pData, pMetadata->data.unknown.dataSizeInBytes); + } + } break; + + default: break; + } + if ((chunkSize % 2) != 0) { + bytesWritten += drwav__write_or_count_byte(pWav, 0); + } + } + + if (hasListInfo) { + drwav_uint32 chunkSize = 4; /* Start with 4 bytes for "INFO". */ + for (iMetadata = 0; iMetadata < metadataCount; ++iMetadata) { + drwav_metadata* pMetadata = &pMetadatas[iMetadata]; + + if ((pMetadata->type & drwav_metadata_type_list_all_info_strings)) { + chunkSize += 8; /* For id and string size. */ + chunkSize += pMetadata->data.infoText.stringLength + 1; /* Include null terminator. */ + } else if (pMetadata->type == drwav_metadata_type_unknown && pMetadata->data.unknown.chunkLocation == drwav_metadata_location_inside_info_list) { + chunkSize += 8; /* For id string size. */ + chunkSize += pMetadata->data.unknown.dataSizeInBytes; + } + + if ((chunkSize % 2) != 0) { + chunkSize += 1; + } + } + + bytesWritten += drwav__write_or_count(pWav, "LIST", 4); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, chunkSize); + bytesWritten += drwav__write_or_count(pWav, "INFO", 4); + + for (iMetadata = 0; iMetadata < metadataCount; ++iMetadata) { + drwav_metadata* pMetadata = &pMetadatas[iMetadata]; + drwav_uint32 subchunkSize = 0; + + if (pMetadata->type & drwav_metadata_type_list_all_info_strings) { + const char* pID = NULL; + + switch (pMetadata->type) { + case drwav_metadata_type_list_info_software: pID = "ISFT"; break; + case drwav_metadata_type_list_info_copyright: pID = "ICOP"; break; + case drwav_metadata_type_list_info_title: pID = "INAM"; break; + case drwav_metadata_type_list_info_artist: pID = "IART"; break; + case drwav_metadata_type_list_info_comment: pID = "ICMT"; break; + case drwav_metadata_type_list_info_date: pID = "ICRD"; break; + case drwav_metadata_type_list_info_genre: pID = "IGNR"; break; + case drwav_metadata_type_list_info_album: pID = "IPRD"; break; + case drwav_metadata_type_list_info_tracknumber: pID = "ITRK"; break; + default: break; + } + + DRWAV_ASSERT(pID != NULL); + + if (pMetadata->data.infoText.stringLength) { + subchunkSize = pMetadata->data.infoText.stringLength + 1; + bytesWritten += drwav__write_or_count(pWav, pID, 4); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, subchunkSize); + bytesWritten += drwav__write_or_count(pWav, pMetadata->data.infoText.pString, pMetadata->data.infoText.stringLength); + bytesWritten += drwav__write_or_count_byte(pWav, '\0'); + } + } else if (pMetadata->type == drwav_metadata_type_unknown && pMetadata->data.unknown.chunkLocation == drwav_metadata_location_inside_info_list) { + if (pMetadata->data.unknown.dataSizeInBytes) { + subchunkSize = pMetadata->data.unknown.dataSizeInBytes; + + bytesWritten += drwav__write_or_count(pWav, pMetadata->data.unknown.id, 4); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.unknown.dataSizeInBytes); + bytesWritten += drwav__write_or_count(pWav, pMetadata->data.unknown.pData, subchunkSize); + } + } + + if ((subchunkSize % 2) != 0) { + bytesWritten += drwav__write_or_count_byte(pWav, 0); + } + } + } + + if (hasListAdtl) { + drwav_uint32 chunkSize = 4; /* start with 4 bytes for "adtl" */ + + for (iMetadata = 0; iMetadata < metadataCount; ++iMetadata) { + drwav_metadata* pMetadata = &pMetadatas[iMetadata]; + + switch (pMetadata->type) + { + case drwav_metadata_type_list_label: + case drwav_metadata_type_list_note: + { + chunkSize += 8; /* for id and chunk size */ + chunkSize += DRWAV_LIST_LABEL_OR_NOTE_BYTES; + + if (pMetadata->data.labelOrNote.stringLength > 0) { + chunkSize += pMetadata->data.labelOrNote.stringLength + 1; + } + } break; + + case drwav_metadata_type_list_labelled_cue_region: + { + chunkSize += 8; /* for id and chunk size */ + chunkSize += DRWAV_LIST_LABELLED_TEXT_BYTES; + + if (pMetadata->data.labelledCueRegion.stringLength > 0) { + chunkSize += pMetadata->data.labelledCueRegion.stringLength + 1; + } + } break; + + case drwav_metadata_type_unknown: + { + if (pMetadata->data.unknown.chunkLocation == drwav_metadata_location_inside_adtl_list) { + chunkSize += 8; /* for id and chunk size */ + chunkSize += pMetadata->data.unknown.dataSizeInBytes; + } + } break; + + default: break; + } + + if ((chunkSize % 2) != 0) { + chunkSize += 1; + } + } + + bytesWritten += drwav__write_or_count(pWav, "LIST", 4); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, chunkSize); + bytesWritten += drwav__write_or_count(pWav, "adtl", 4); + + for (iMetadata = 0; iMetadata < metadataCount; ++iMetadata) { + drwav_metadata* pMetadata = &pMetadatas[iMetadata]; + drwav_uint32 subchunkSize = 0; + + switch (pMetadata->type) + { + case drwav_metadata_type_list_label: + case drwav_metadata_type_list_note: + { + if (pMetadata->data.labelOrNote.stringLength > 0) { + const char *pID = NULL; + + if (pMetadata->type == drwav_metadata_type_list_label) { + pID = "labl"; + } + else if (pMetadata->type == drwav_metadata_type_list_note) { + pID = "note"; + } + + DRWAV_ASSERT(pID != NULL); + DRWAV_ASSERT(pMetadata->data.labelOrNote.pString != NULL); + + subchunkSize = DRWAV_LIST_LABEL_OR_NOTE_BYTES; + + bytesWritten += drwav__write_or_count(pWav, pID, 4); + subchunkSize += pMetadata->data.labelOrNote.stringLength + 1; + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, subchunkSize); + + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.labelOrNote.cuePointId); + bytesWritten += drwav__write_or_count(pWav, pMetadata->data.labelOrNote.pString, pMetadata->data.labelOrNote.stringLength); + bytesWritten += drwav__write_or_count_byte(pWav, '\0'); + } + } break; + + case drwav_metadata_type_list_labelled_cue_region: + { + subchunkSize = DRWAV_LIST_LABELLED_TEXT_BYTES; + + bytesWritten += drwav__write_or_count(pWav, "ltxt", 4); + if (pMetadata->data.labelledCueRegion.stringLength > 0) { + subchunkSize += pMetadata->data.labelledCueRegion.stringLength + 1; + } + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, subchunkSize); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.labelledCueRegion.cuePointId); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.labelledCueRegion.sampleLength); + bytesWritten += drwav__write_or_count(pWav, pMetadata->data.labelledCueRegion.purposeId, 4); + bytesWritten += drwav__write_or_count_u16ne_to_le(pWav, pMetadata->data.labelledCueRegion.country); + bytesWritten += drwav__write_or_count_u16ne_to_le(pWav, pMetadata->data.labelledCueRegion.language); + bytesWritten += drwav__write_or_count_u16ne_to_le(pWav, pMetadata->data.labelledCueRegion.dialect); + bytesWritten += drwav__write_or_count_u16ne_to_le(pWav, pMetadata->data.labelledCueRegion.codePage); + + if (pMetadata->data.labelledCueRegion.stringLength > 0) { + DRWAV_ASSERT(pMetadata->data.labelledCueRegion.pString != NULL); + + bytesWritten += drwav__write_or_count(pWav, pMetadata->data.labelledCueRegion.pString, pMetadata->data.labelledCueRegion.stringLength); + bytesWritten += drwav__write_or_count_byte(pWav, '\0'); + } + } break; + + case drwav_metadata_type_unknown: + { + if (pMetadata->data.unknown.chunkLocation == drwav_metadata_location_inside_adtl_list) { + subchunkSize = pMetadata->data.unknown.dataSizeInBytes; + + DRWAV_ASSERT(pMetadata->data.unknown.pData != NULL); + bytesWritten += drwav__write_or_count(pWav, pMetadata->data.unknown.id, 4); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, subchunkSize); + bytesWritten += drwav__write_or_count(pWav, pMetadata->data.unknown.pData, subchunkSize); + } + } break; + + default: break; + } + + if ((subchunkSize % 2) != 0) { + bytesWritten += drwav__write_or_count_byte(pWav, 0); + } + } + } + + DRWAV_ASSERT((bytesWritten % 2) == 0); + + return bytesWritten; +} + +DRWAV_PRIVATE drwav_uint32 drwav__riff_chunk_size_riff(drwav_uint64 dataChunkSize, drwav_metadata* pMetadata, drwav_uint32 metadataCount) +{ + drwav_uint64 chunkSize = 4 + 24 + (drwav_uint64)drwav__write_or_count_metadata(NULL, pMetadata, metadataCount) + 8 + dataChunkSize + drwav__chunk_padding_size_riff(dataChunkSize); /* 4 = "WAVE". 24 = "fmt " chunk. 8 = "data" + u32 data size. */ + if (chunkSize > 0xFFFFFFFFUL) { + chunkSize = 0xFFFFFFFFUL; + } + + return (drwav_uint32)chunkSize; /* Safe cast due to the clamp above. */ +} + +DRWAV_PRIVATE drwav_uint32 drwav__data_chunk_size_riff(drwav_uint64 dataChunkSize) +{ + if (dataChunkSize <= 0xFFFFFFFFUL) { + return (drwav_uint32)dataChunkSize; + } else { + return 0xFFFFFFFFUL; + } +} + +DRWAV_PRIVATE drwav_uint64 drwav__riff_chunk_size_w64(drwav_uint64 dataChunkSize) +{ + drwav_uint64 dataSubchunkPaddingSize = drwav__chunk_padding_size_w64(dataChunkSize); + + return 80 + 24 + dataChunkSize + dataSubchunkPaddingSize; /* +24 because W64 includes the size of the GUID and size fields. */ +} + +DRWAV_PRIVATE drwav_uint64 drwav__data_chunk_size_w64(drwav_uint64 dataChunkSize) +{ + return 24 + dataChunkSize; /* +24 because W64 includes the size of the GUID and size fields. */ +} + +DRWAV_PRIVATE drwav_uint64 drwav__riff_chunk_size_rf64(drwav_uint64 dataChunkSize, drwav_metadata *metadata, drwav_uint32 numMetadata) +{ + drwav_uint64 chunkSize = 4 + 36 + 24 + (drwav_uint64)drwav__write_or_count_metadata(NULL, metadata, numMetadata) + 8 + dataChunkSize + drwav__chunk_padding_size_riff(dataChunkSize); /* 4 = "WAVE". 36 = "ds64" chunk. 24 = "fmt " chunk. 8 = "data" + u32 data size. */ + if (chunkSize > 0xFFFFFFFFUL) { + chunkSize = 0xFFFFFFFFUL; + } + + return chunkSize; +} + +DRWAV_PRIVATE drwav_uint64 drwav__data_chunk_size_rf64(drwav_uint64 dataChunkSize) +{ + return dataChunkSize; +} + + + +DRWAV_PRIVATE drwav_bool32 drwav_preinit_write(drwav* pWav, const drwav_data_format* pFormat, drwav_bool32 isSequential, drwav_write_proc onWrite, drwav_seek_proc onSeek, void* pUserData, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + if (pWav == NULL || onWrite == NULL) { + return DRWAV_FALSE; + } + + if (!isSequential && onSeek == NULL) { + return DRWAV_FALSE; /* <-- onSeek is required when in non-sequential mode. */ + } + + /* Not currently supporting compressed formats. Will need to add support for the "fact" chunk before we enable this. */ + if (pFormat->format == DR_WAVE_FORMAT_EXTENSIBLE) { + return DRWAV_FALSE; + } + if (pFormat->format == DR_WAVE_FORMAT_ADPCM || pFormat->format == DR_WAVE_FORMAT_DVI_ADPCM) { + return DRWAV_FALSE; + } + + DRWAV_ZERO_MEMORY(pWav, sizeof(*pWav)); + pWav->onWrite = onWrite; + pWav->onSeek = onSeek; + pWav->pUserData = pUserData; + pWav->allocationCallbacks = drwav_copy_allocation_callbacks_or_defaults(pAllocationCallbacks); + + if (pWav->allocationCallbacks.onFree == NULL || (pWav->allocationCallbacks.onMalloc == NULL && pWav->allocationCallbacks.onRealloc == NULL)) { + return DRWAV_FALSE; /* Invalid allocation callbacks. */ + } + + pWav->fmt.formatTag = (drwav_uint16)pFormat->format; + pWav->fmt.channels = (drwav_uint16)pFormat->channels; + pWav->fmt.sampleRate = pFormat->sampleRate; + pWav->fmt.avgBytesPerSec = (drwav_uint32)((pFormat->bitsPerSample * pFormat->sampleRate * pFormat->channels) / 8); + pWav->fmt.blockAlign = (drwav_uint16)((pFormat->channels * pFormat->bitsPerSample) / 8); + pWav->fmt.bitsPerSample = (drwav_uint16)pFormat->bitsPerSample; + pWav->fmt.extendedSize = 0; + pWav->isSequentialWrite = isSequential; + + return DRWAV_TRUE; +} + + +DRWAV_PRIVATE drwav_bool32 drwav_init_write__internal(drwav* pWav, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount) +{ + /* The function assumes drwav_preinit_write() was called beforehand. */ + + size_t runningPos = 0; + drwav_uint64 initialDataChunkSize = 0; + drwav_uint64 chunkSizeFMT; + + /* + The initial values for the "RIFF" and "data" chunks depends on whether or not we are initializing in sequential mode or not. In + sequential mode we set this to its final values straight away since they can be calculated from the total sample count. In non- + sequential mode we initialize it all to zero and fill it out in drwav_uninit() using a backwards seek. + */ + if (pWav->isSequentialWrite) { + initialDataChunkSize = (totalSampleCount * pWav->fmt.bitsPerSample) / 8; + + /* + The RIFF container has a limit on the number of samples. drwav is not allowing this. There's no practical limits for Wave64 + so for the sake of simplicity I'm not doing any validation for that. + */ + if (pFormat->container == drwav_container_riff) { + if (initialDataChunkSize > (0xFFFFFFFFUL - 36)) { + return DRWAV_FALSE; /* Not enough room to store every sample. */ + } + } + } + + pWav->dataChunkDataSizeTargetWrite = initialDataChunkSize; + + + /* "RIFF" chunk. */ + if (pFormat->container == drwav_container_riff) { + drwav_uint32 chunkSizeRIFF = 28 + (drwav_uint32)initialDataChunkSize; /* +28 = "WAVE" + [sizeof "fmt " chunk] */ + runningPos += drwav__write(pWav, "RIFF", 4); + runningPos += drwav__write_u32ne_to_le(pWav, chunkSizeRIFF); + runningPos += drwav__write(pWav, "WAVE", 4); + } else if (pFormat->container == drwav_container_w64) { + drwav_uint64 chunkSizeRIFF = 80 + 24 + initialDataChunkSize; /* +24 because W64 includes the size of the GUID and size fields. */ + runningPos += drwav__write(pWav, drwavGUID_W64_RIFF, 16); + runningPos += drwav__write_u64ne_to_le(pWav, chunkSizeRIFF); + runningPos += drwav__write(pWav, drwavGUID_W64_WAVE, 16); + } else if (pFormat->container == drwav_container_rf64) { + runningPos += drwav__write(pWav, "RF64", 4); + runningPos += drwav__write_u32ne_to_le(pWav, 0xFFFFFFFF); /* Always 0xFFFFFFFF for RF64. Set to a proper value in the "ds64" chunk. */ + runningPos += drwav__write(pWav, "WAVE", 4); + } else { + return DRWAV_FALSE; /* Container not supported for writing. */ + } + + + /* "ds64" chunk (RF64 only). */ + if (pFormat->container == drwav_container_rf64) { + drwav_uint32 initialds64ChunkSize = 28; /* 28 = [Size of RIFF (8 bytes)] + [Size of DATA (8 bytes)] + [Sample Count (8 bytes)] + [Table Length (4 bytes)]. Table length always set to 0. */ + drwav_uint64 initialRiffChunkSize = 8 + initialds64ChunkSize + initialDataChunkSize; /* +8 for the ds64 header. */ + + runningPos += drwav__write(pWav, "ds64", 4); + runningPos += drwav__write_u32ne_to_le(pWav, initialds64ChunkSize); /* Size of ds64. */ + runningPos += drwav__write_u64ne_to_le(pWav, initialRiffChunkSize); /* Size of RIFF. Set to true value at the end. */ + runningPos += drwav__write_u64ne_to_le(pWav, initialDataChunkSize); /* Size of DATA. Set to true value at the end. */ + runningPos += drwav__write_u64ne_to_le(pWav, totalSampleCount); /* Sample count. */ + runningPos += drwav__write_u32ne_to_le(pWav, 0); /* Table length. Always set to zero in our case since we're not doing any other chunks than "DATA". */ + } + + + /* "fmt " chunk. */ + if (pFormat->container == drwav_container_riff || pFormat->container == drwav_container_rf64) { + chunkSizeFMT = 16; + runningPos += drwav__write(pWav, "fmt ", 4); + runningPos += drwav__write_u32ne_to_le(pWav, (drwav_uint32)chunkSizeFMT); + } else if (pFormat->container == drwav_container_w64) { + chunkSizeFMT = 40; + runningPos += drwav__write(pWav, drwavGUID_W64_FMT, 16); + runningPos += drwav__write_u64ne_to_le(pWav, chunkSizeFMT); + } + + runningPos += drwav__write_u16ne_to_le(pWav, pWav->fmt.formatTag); + runningPos += drwav__write_u16ne_to_le(pWav, pWav->fmt.channels); + runningPos += drwav__write_u32ne_to_le(pWav, pWav->fmt.sampleRate); + runningPos += drwav__write_u32ne_to_le(pWav, pWav->fmt.avgBytesPerSec); + runningPos += drwav__write_u16ne_to_le(pWav, pWav->fmt.blockAlign); + runningPos += drwav__write_u16ne_to_le(pWav, pWav->fmt.bitsPerSample); + + /* TODO: is a 'fact' chunk required for DR_WAVE_FORMAT_IEEE_FLOAT? */ + + if (!pWav->isSequentialWrite && pWav->pMetadata != NULL && pWav->metadataCount > 0 && (pFormat->container == drwav_container_riff || pFormat->container == drwav_container_rf64)) { + runningPos += drwav__write_or_count_metadata(pWav, pWav->pMetadata, pWav->metadataCount); + } + + pWav->dataChunkDataPos = runningPos; + + /* "data" chunk. */ + if (pFormat->container == drwav_container_riff) { + drwav_uint32 chunkSizeDATA = (drwav_uint32)initialDataChunkSize; + runningPos += drwav__write(pWav, "data", 4); + runningPos += drwav__write_u32ne_to_le(pWav, chunkSizeDATA); + } else if (pFormat->container == drwav_container_w64) { + drwav_uint64 chunkSizeDATA = 24 + initialDataChunkSize; /* +24 because W64 includes the size of the GUID and size fields. */ + runningPos += drwav__write(pWav, drwavGUID_W64_DATA, 16); + runningPos += drwav__write_u64ne_to_le(pWav, chunkSizeDATA); + } else if (pFormat->container == drwav_container_rf64) { + runningPos += drwav__write(pWav, "data", 4); + runningPos += drwav__write_u32ne_to_le(pWav, 0xFFFFFFFF); /* Always set to 0xFFFFFFFF for RF64. The true size of the data chunk is specified in the ds64 chunk. */ + } + + /* Set some properties for the client's convenience. */ + pWav->container = pFormat->container; + pWav->channels = (drwav_uint16)pFormat->channels; + pWav->sampleRate = pFormat->sampleRate; + pWav->bitsPerSample = (drwav_uint16)pFormat->bitsPerSample; + pWav->translatedFormatTag = (drwav_uint16)pFormat->format; + pWav->dataChunkDataPos = runningPos; + + return DRWAV_TRUE; +} + + +DRWAV_API drwav_bool32 drwav_init_write(drwav* pWav, const drwav_data_format* pFormat, drwav_write_proc onWrite, drwav_seek_proc onSeek, void* pUserData, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + if (!drwav_preinit_write(pWav, pFormat, DRWAV_FALSE, onWrite, onSeek, pUserData, pAllocationCallbacks)) { + return DRWAV_FALSE; + } + + return drwav_init_write__internal(pWav, pFormat, 0); /* DRWAV_FALSE = Not Sequential */ +} + +DRWAV_API drwav_bool32 drwav_init_write_sequential(drwav* pWav, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, drwav_write_proc onWrite, void* pUserData, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + if (!drwav_preinit_write(pWav, pFormat, DRWAV_TRUE, onWrite, NULL, pUserData, pAllocationCallbacks)) { + return DRWAV_FALSE; + } + + return drwav_init_write__internal(pWav, pFormat, totalSampleCount); /* DRWAV_TRUE = Sequential */ +} + +DRWAV_API drwav_bool32 drwav_init_write_sequential_pcm_frames(drwav* pWav, const drwav_data_format* pFormat, drwav_uint64 totalPCMFrameCount, drwav_write_proc onWrite, void* pUserData, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + if (pFormat == NULL) { + return DRWAV_FALSE; + } + + return drwav_init_write_sequential(pWav, pFormat, totalPCMFrameCount*pFormat->channels, onWrite, pUserData, pAllocationCallbacks); +} + +DRWAV_API drwav_bool32 drwav_init_write_with_metadata(drwav* pWav, const drwav_data_format* pFormat, drwav_write_proc onWrite, drwav_seek_proc onSeek, void* pUserData, const drwav_allocation_callbacks* pAllocationCallbacks, drwav_metadata* pMetadata, drwav_uint32 metadataCount) +{ + if (!drwav_preinit_write(pWav, pFormat, DRWAV_FALSE, onWrite, onSeek, pUserData, pAllocationCallbacks)) { + return DRWAV_FALSE; + } + + pWav->pMetadata = pMetadata; + pWav->metadataCount = metadataCount; + + return drwav_init_write__internal(pWav, pFormat, 0); +} + + +DRWAV_API drwav_uint64 drwav_target_write_size_bytes(const drwav_data_format* pFormat, drwav_uint64 totalFrameCount, drwav_metadata* pMetadata, drwav_uint32 metadataCount) +{ + /* Casting totalFrameCount to drwav_int64 for VC6 compatibility. No issues in practice because nobody is going to exhaust the whole 63 bits. */ + drwav_uint64 targetDataSizeBytes = (drwav_uint64)((drwav_int64)totalFrameCount * pFormat->channels * pFormat->bitsPerSample/8.0); + drwav_uint64 riffChunkSizeBytes; + drwav_uint64 fileSizeBytes = 0; + + if (pFormat->container == drwav_container_riff) { + riffChunkSizeBytes = drwav__riff_chunk_size_riff(targetDataSizeBytes, pMetadata, metadataCount); + fileSizeBytes = (8 + riffChunkSizeBytes); /* +8 because WAV doesn't include the size of the ChunkID and ChunkSize fields. */ + } else if (pFormat->container == drwav_container_w64) { + riffChunkSizeBytes = drwav__riff_chunk_size_w64(targetDataSizeBytes); + fileSizeBytes = riffChunkSizeBytes; + } else if (pFormat->container == drwav_container_rf64) { + riffChunkSizeBytes = drwav__riff_chunk_size_rf64(targetDataSizeBytes, pMetadata, metadataCount); + fileSizeBytes = (8 + riffChunkSizeBytes); /* +8 because WAV doesn't include the size of the ChunkID and ChunkSize fields. */ + } + + return fileSizeBytes; +} + + +#ifndef DR_WAV_NO_STDIO + +/* Errno */ +/* drwav_result_from_errno() is only used for fopen() and wfopen() so putting it inside DR_WAV_NO_STDIO for now. If something else needs this later we can move it out. */ +#include +DRWAV_PRIVATE drwav_result drwav_result_from_errno(int e) +{ + switch (e) + { + case 0: return DRWAV_SUCCESS; + #ifdef EPERM + case EPERM: return DRWAV_INVALID_OPERATION; + #endif + #ifdef ENOENT + case ENOENT: return DRWAV_DOES_NOT_EXIST; + #endif + #ifdef ESRCH + case ESRCH: return DRWAV_DOES_NOT_EXIST; + #endif + #ifdef EINTR + case EINTR: return DRWAV_INTERRUPT; + #endif + #ifdef EIO + case EIO: return DRWAV_IO_ERROR; + #endif + #ifdef ENXIO + case ENXIO: return DRWAV_DOES_NOT_EXIST; + #endif + #ifdef E2BIG + case E2BIG: return DRWAV_INVALID_ARGS; + #endif + #ifdef ENOEXEC + case ENOEXEC: return DRWAV_INVALID_FILE; + #endif + #ifdef EBADF + case EBADF: return DRWAV_INVALID_FILE; + #endif + #ifdef ECHILD + case ECHILD: return DRWAV_ERROR; + #endif + #ifdef EAGAIN + case EAGAIN: return DRWAV_UNAVAILABLE; + #endif + #ifdef ENOMEM + case ENOMEM: return DRWAV_OUT_OF_MEMORY; + #endif + #ifdef EACCES + case EACCES: return DRWAV_ACCESS_DENIED; + #endif + #ifdef EFAULT + case EFAULT: return DRWAV_BAD_ADDRESS; + #endif + #ifdef ENOTBLK + case ENOTBLK: return DRWAV_ERROR; + #endif + #ifdef EBUSY + case EBUSY: return DRWAV_BUSY; + #endif + #ifdef EEXIST + case EEXIST: return DRWAV_ALREADY_EXISTS; + #endif + #ifdef EXDEV + case EXDEV: return DRWAV_ERROR; + #endif + #ifdef ENODEV + case ENODEV: return DRWAV_DOES_NOT_EXIST; + #endif + #ifdef ENOTDIR + case ENOTDIR: return DRWAV_NOT_DIRECTORY; + #endif + #ifdef EISDIR + case EISDIR: return DRWAV_IS_DIRECTORY; + #endif + #ifdef EINVAL + case EINVAL: return DRWAV_INVALID_ARGS; + #endif + #ifdef ENFILE + case ENFILE: return DRWAV_TOO_MANY_OPEN_FILES; + #endif + #ifdef EMFILE + case EMFILE: return DRWAV_TOO_MANY_OPEN_FILES; + #endif + #ifdef ENOTTY + case ENOTTY: return DRWAV_INVALID_OPERATION; + #endif + #ifdef ETXTBSY + case ETXTBSY: return DRWAV_BUSY; + #endif + #ifdef EFBIG + case EFBIG: return DRWAV_TOO_BIG; + #endif + #ifdef ENOSPC + case ENOSPC: return DRWAV_NO_SPACE; + #endif + #ifdef ESPIPE + case ESPIPE: return DRWAV_BAD_SEEK; + #endif + #ifdef EROFS + case EROFS: return DRWAV_ACCESS_DENIED; + #endif + #ifdef EMLINK + case EMLINK: return DRWAV_TOO_MANY_LINKS; + #endif + #ifdef EPIPE + case EPIPE: return DRWAV_BAD_PIPE; + #endif + #ifdef EDOM + case EDOM: return DRWAV_OUT_OF_RANGE; + #endif + #ifdef ERANGE + case ERANGE: return DRWAV_OUT_OF_RANGE; + #endif + #ifdef EDEADLK + case EDEADLK: return DRWAV_DEADLOCK; + #endif + #ifdef ENAMETOOLONG + case ENAMETOOLONG: return DRWAV_PATH_TOO_LONG; + #endif + #ifdef ENOLCK + case ENOLCK: return DRWAV_ERROR; + #endif + #ifdef ENOSYS + case ENOSYS: return DRWAV_NOT_IMPLEMENTED; + #endif + #ifdef ENOTEMPTY + case ENOTEMPTY: return DRWAV_DIRECTORY_NOT_EMPTY; + #endif + #ifdef ELOOP + case ELOOP: return DRWAV_TOO_MANY_LINKS; + #endif + #ifdef ENOMSG + case ENOMSG: return DRWAV_NO_MESSAGE; + #endif + #ifdef EIDRM + case EIDRM: return DRWAV_ERROR; + #endif + #ifdef ECHRNG + case ECHRNG: return DRWAV_ERROR; + #endif + #ifdef EL2NSYNC + case EL2NSYNC: return DRWAV_ERROR; + #endif + #ifdef EL3HLT + case EL3HLT: return DRWAV_ERROR; + #endif + #ifdef EL3RST + case EL3RST: return DRWAV_ERROR; + #endif + #ifdef ELNRNG + case ELNRNG: return DRWAV_OUT_OF_RANGE; + #endif + #ifdef EUNATCH + case EUNATCH: return DRWAV_ERROR; + #endif + #ifdef ENOCSI + case ENOCSI: return DRWAV_ERROR; + #endif + #ifdef EL2HLT + case EL2HLT: return DRWAV_ERROR; + #endif + #ifdef EBADE + case EBADE: return DRWAV_ERROR; + #endif + #ifdef EBADR + case EBADR: return DRWAV_ERROR; + #endif + #ifdef EXFULL + case EXFULL: return DRWAV_ERROR; + #endif + #ifdef ENOANO + case ENOANO: return DRWAV_ERROR; + #endif + #ifdef EBADRQC + case EBADRQC: return DRWAV_ERROR; + #endif + #ifdef EBADSLT + case EBADSLT: return DRWAV_ERROR; + #endif + #ifdef EBFONT + case EBFONT: return DRWAV_INVALID_FILE; + #endif + #ifdef ENOSTR + case ENOSTR: return DRWAV_ERROR; + #endif + #ifdef ENODATA + case ENODATA: return DRWAV_NO_DATA_AVAILABLE; + #endif + #ifdef ETIME + case ETIME: return DRWAV_TIMEOUT; + #endif + #ifdef ENOSR + case ENOSR: return DRWAV_NO_DATA_AVAILABLE; + #endif + #ifdef ENONET + case ENONET: return DRWAV_NO_NETWORK; + #endif + #ifdef ENOPKG + case ENOPKG: return DRWAV_ERROR; + #endif + #ifdef EREMOTE + case EREMOTE: return DRWAV_ERROR; + #endif + #ifdef ENOLINK + case ENOLINK: return DRWAV_ERROR; + #endif + #ifdef EADV + case EADV: return DRWAV_ERROR; + #endif + #ifdef ESRMNT + case ESRMNT: return DRWAV_ERROR; + #endif + #ifdef ECOMM + case ECOMM: return DRWAV_ERROR; + #endif + #ifdef EPROTO + case EPROTO: return DRWAV_ERROR; + #endif + #ifdef EMULTIHOP + case EMULTIHOP: return DRWAV_ERROR; + #endif + #ifdef EDOTDOT + case EDOTDOT: return DRWAV_ERROR; + #endif + #ifdef EBADMSG + case EBADMSG: return DRWAV_BAD_MESSAGE; + #endif + #ifdef EOVERFLOW + case EOVERFLOW: return DRWAV_TOO_BIG; + #endif + #ifdef ENOTUNIQ + case ENOTUNIQ: return DRWAV_NOT_UNIQUE; + #endif + #ifdef EBADFD + case EBADFD: return DRWAV_ERROR; + #endif + #ifdef EREMCHG + case EREMCHG: return DRWAV_ERROR; + #endif + #ifdef ELIBACC + case ELIBACC: return DRWAV_ACCESS_DENIED; + #endif + #ifdef ELIBBAD + case ELIBBAD: return DRWAV_INVALID_FILE; + #endif + #ifdef ELIBSCN + case ELIBSCN: return DRWAV_INVALID_FILE; + #endif + #ifdef ELIBMAX + case ELIBMAX: return DRWAV_ERROR; + #endif + #ifdef ELIBEXEC + case ELIBEXEC: return DRWAV_ERROR; + #endif + #ifdef EILSEQ + case EILSEQ: return DRWAV_INVALID_DATA; + #endif + #ifdef ERESTART + case ERESTART: return DRWAV_ERROR; + #endif + #ifdef ESTRPIPE + case ESTRPIPE: return DRWAV_ERROR; + #endif + #ifdef EUSERS + case EUSERS: return DRWAV_ERROR; + #endif + #ifdef ENOTSOCK + case ENOTSOCK: return DRWAV_NOT_SOCKET; + #endif + #ifdef EDESTADDRREQ + case EDESTADDRREQ: return DRWAV_NO_ADDRESS; + #endif + #ifdef EMSGSIZE + case EMSGSIZE: return DRWAV_TOO_BIG; + #endif + #ifdef EPROTOTYPE + case EPROTOTYPE: return DRWAV_BAD_PROTOCOL; + #endif + #ifdef ENOPROTOOPT + case ENOPROTOOPT: return DRWAV_PROTOCOL_UNAVAILABLE; + #endif + #ifdef EPROTONOSUPPORT + case EPROTONOSUPPORT: return DRWAV_PROTOCOL_NOT_SUPPORTED; + #endif + #ifdef ESOCKTNOSUPPORT + case ESOCKTNOSUPPORT: return DRWAV_SOCKET_NOT_SUPPORTED; + #endif + #ifdef EOPNOTSUPP + case EOPNOTSUPP: return DRWAV_INVALID_OPERATION; + #endif + #ifdef EPFNOSUPPORT + case EPFNOSUPPORT: return DRWAV_PROTOCOL_FAMILY_NOT_SUPPORTED; + #endif + #ifdef EAFNOSUPPORT + case EAFNOSUPPORT: return DRWAV_ADDRESS_FAMILY_NOT_SUPPORTED; + #endif + #ifdef EADDRINUSE + case EADDRINUSE: return DRWAV_ALREADY_IN_USE; + #endif + #ifdef EADDRNOTAVAIL + case EADDRNOTAVAIL: return DRWAV_ERROR; + #endif + #ifdef ENETDOWN + case ENETDOWN: return DRWAV_NO_NETWORK; + #endif + #ifdef ENETUNREACH + case ENETUNREACH: return DRWAV_NO_NETWORK; + #endif + #ifdef ENETRESET + case ENETRESET: return DRWAV_NO_NETWORK; + #endif + #ifdef ECONNABORTED + case ECONNABORTED: return DRWAV_NO_NETWORK; + #endif + #ifdef ECONNRESET + case ECONNRESET: return DRWAV_CONNECTION_RESET; + #endif + #ifdef ENOBUFS + case ENOBUFS: return DRWAV_NO_SPACE; + #endif + #ifdef EISCONN + case EISCONN: return DRWAV_ALREADY_CONNECTED; + #endif + #ifdef ENOTCONN + case ENOTCONN: return DRWAV_NOT_CONNECTED; + #endif + #ifdef ESHUTDOWN + case ESHUTDOWN: return DRWAV_ERROR; + #endif + #ifdef ETOOMANYREFS + case ETOOMANYREFS: return DRWAV_ERROR; + #endif + #ifdef ETIMEDOUT + case ETIMEDOUT: return DRWAV_TIMEOUT; + #endif + #ifdef ECONNREFUSED + case ECONNREFUSED: return DRWAV_CONNECTION_REFUSED; + #endif + #ifdef EHOSTDOWN + case EHOSTDOWN: return DRWAV_NO_HOST; + #endif + #ifdef EHOSTUNREACH + case EHOSTUNREACH: return DRWAV_NO_HOST; + #endif + #ifdef EALREADY + case EALREADY: return DRWAV_IN_PROGRESS; + #endif + #ifdef EINPROGRESS + case EINPROGRESS: return DRWAV_IN_PROGRESS; + #endif + #ifdef ESTALE + case ESTALE: return DRWAV_INVALID_FILE; + #endif + #ifdef EUCLEAN + case EUCLEAN: return DRWAV_ERROR; + #endif + #ifdef ENOTNAM + case ENOTNAM: return DRWAV_ERROR; + #endif + #ifdef ENAVAIL + case ENAVAIL: return DRWAV_ERROR; + #endif + #ifdef EISNAM + case EISNAM: return DRWAV_ERROR; + #endif + #ifdef EREMOTEIO + case EREMOTEIO: return DRWAV_IO_ERROR; + #endif + #ifdef EDQUOT + case EDQUOT: return DRWAV_NO_SPACE; + #endif + #ifdef ENOMEDIUM + case ENOMEDIUM: return DRWAV_DOES_NOT_EXIST; + #endif + #ifdef EMEDIUMTYPE + case EMEDIUMTYPE: return DRWAV_ERROR; + #endif + #ifdef ECANCELED + case ECANCELED: return DRWAV_CANCELLED; + #endif + #ifdef ENOKEY + case ENOKEY: return DRWAV_ERROR; + #endif + #ifdef EKEYEXPIRED + case EKEYEXPIRED: return DRWAV_ERROR; + #endif + #ifdef EKEYREVOKED + case EKEYREVOKED: return DRWAV_ERROR; + #endif + #ifdef EKEYREJECTED + case EKEYREJECTED: return DRWAV_ERROR; + #endif + #ifdef EOWNERDEAD + case EOWNERDEAD: return DRWAV_ERROR; + #endif + #ifdef ENOTRECOVERABLE + case ENOTRECOVERABLE: return DRWAV_ERROR; + #endif + #ifdef ERFKILL + case ERFKILL: return DRWAV_ERROR; + #endif + #ifdef EHWPOISON + case EHWPOISON: return DRWAV_ERROR; + #endif + default: return DRWAV_ERROR; + } +} +/* End Errno */ + +/* fopen */ +DRWAV_PRIVATE drwav_result drwav_fopen(FILE** ppFile, const char* pFilePath, const char* pOpenMode) +{ +#if defined(_MSC_VER) && _MSC_VER >= 1400 + errno_t err; +#endif + + if (ppFile != NULL) { + *ppFile = NULL; /* Safety. */ + } + + if (pFilePath == NULL || pOpenMode == NULL || ppFile == NULL) { + return DRWAV_INVALID_ARGS; + } + +#if defined(_MSC_VER) && _MSC_VER >= 1400 + err = fopen_s(ppFile, pFilePath, pOpenMode); + if (err != 0) { + return drwav_result_from_errno(err); + } +#else +#if defined(_WIN32) || defined(__APPLE__) + *ppFile = fopen(pFilePath, pOpenMode); +#else + #if defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS == 64 && defined(_LARGEFILE64_SOURCE) + *ppFile = fopen64(pFilePath, pOpenMode); + #else + *ppFile = fopen(pFilePath, pOpenMode); + #endif +#endif + if (*ppFile == NULL) { + drwav_result result = drwav_result_from_errno(errno); + if (result == DRWAV_SUCCESS) { + result = DRWAV_ERROR; /* Just a safety check to make sure we never ever return success when pFile == NULL. */ + } + + return result; + } +#endif + + return DRWAV_SUCCESS; +} + +/* +_wfopen() isn't always available in all compilation environments. + + * Windows only. + * MSVC seems to support it universally as far back as VC6 from what I can tell (haven't checked further back). + * MinGW-64 (both 32- and 64-bit) seems to support it. + * MinGW wraps it in !defined(__STRICT_ANSI__). + * OpenWatcom wraps it in !defined(_NO_EXT_KEYS). + +This can be reviewed as compatibility issues arise. The preference is to use _wfopen_s() and _wfopen() as opposed to the wcsrtombs() +fallback, so if you notice your compiler not detecting this properly I'm happy to look at adding support. +*/ +#if defined(_WIN32) + #if defined(_MSC_VER) || defined(__MINGW64__) || (!defined(__STRICT_ANSI__) && !defined(_NO_EXT_KEYS)) + #define DRWAV_HAS_WFOPEN + #endif +#endif + +#ifndef DR_WAV_NO_WCHAR +DRWAV_PRIVATE drwav_result drwav_wfopen(FILE** ppFile, const wchar_t* pFilePath, const wchar_t* pOpenMode, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + if (ppFile != NULL) { + *ppFile = NULL; /* Safety. */ + } + + if (pFilePath == NULL || pOpenMode == NULL || ppFile == NULL) { + return DRWAV_INVALID_ARGS; + } + +#if defined(DRWAV_HAS_WFOPEN) + { + /* Use _wfopen() on Windows. */ + #if defined(_MSC_VER) && _MSC_VER >= 1400 + errno_t err = _wfopen_s(ppFile, pFilePath, pOpenMode); + if (err != 0) { + return drwav_result_from_errno(err); + } + #else + *ppFile = _wfopen(pFilePath, pOpenMode); + if (*ppFile == NULL) { + return drwav_result_from_errno(errno); + } + #endif + (void)pAllocationCallbacks; + } +#else + /* + Use fopen() on anything other than Windows. Requires a conversion. This is annoying because + fopen() is locale specific. The only real way I can think of to do this is with wcsrtombs(). Note + that wcstombs() is apparently not thread-safe because it uses a static global mbstate_t object for + maintaining state. I've checked this with -std=c89 and it works, but if somebody get's a compiler + error I'll look into improving compatibility. + */ + + /* + Some compilers don't support wchar_t or wcsrtombs() which we're using below. In this case we just + need to abort with an error. If you encounter a compiler lacking such support, add it to this list + and submit a bug report and it'll be added to the library upstream. + */ + #if defined(__DJGPP__) + { + /* Nothing to do here. This will fall through to the error check below. */ + } + #else + { + mbstate_t mbs; + size_t lenMB; + const wchar_t* pFilePathTemp = pFilePath; + char* pFilePathMB = NULL; + char pOpenModeMB[32] = {0}; + + /* Get the length first. */ + DRWAV_ZERO_OBJECT(&mbs); + lenMB = wcsrtombs(NULL, &pFilePathTemp, 0, &mbs); + if (lenMB == (size_t)-1) { + return drwav_result_from_errno(errno); + } + + pFilePathMB = (char*)drwav__malloc_from_callbacks(lenMB + 1, pAllocationCallbacks); + if (pFilePathMB == NULL) { + return DRWAV_OUT_OF_MEMORY; + } + + pFilePathTemp = pFilePath; + DRWAV_ZERO_OBJECT(&mbs); + wcsrtombs(pFilePathMB, &pFilePathTemp, lenMB + 1, &mbs); + + /* The open mode should always consist of ASCII characters so we should be able to do a trivial conversion. */ + { + size_t i = 0; + for (;;) { + if (pOpenMode[i] == 0) { + pOpenModeMB[i] = '\0'; + break; + } + + pOpenModeMB[i] = (char)pOpenMode[i]; + i += 1; + } + } + + *ppFile = fopen(pFilePathMB, pOpenModeMB); + + drwav__free_from_callbacks(pFilePathMB, pAllocationCallbacks); + } + #endif + + if (*ppFile == NULL) { + return DRWAV_ERROR; + } +#endif + + return DRWAV_SUCCESS; +} +#endif +/* End fopen */ + + +DRWAV_PRIVATE size_t drwav__on_read_stdio(void* pUserData, void* pBufferOut, size_t bytesToRead) +{ + return fread(pBufferOut, 1, bytesToRead, (FILE*)pUserData); +} + +DRWAV_PRIVATE size_t drwav__on_write_stdio(void* pUserData, const void* pData, size_t bytesToWrite) +{ + return fwrite(pData, 1, bytesToWrite, (FILE*)pUserData); +} + +DRWAV_PRIVATE drwav_bool32 drwav__on_seek_stdio(void* pUserData, int offset, drwav_seek_origin origin) +{ + return fseek((FILE*)pUserData, offset, (origin == drwav_seek_origin_current) ? SEEK_CUR : SEEK_SET) == 0; +} + +DRWAV_API drwav_bool32 drwav_init_file(drwav* pWav, const char* filename, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + return drwav_init_file_ex(pWav, filename, NULL, NULL, 0, pAllocationCallbacks); +} + + +DRWAV_PRIVATE drwav_bool32 drwav_init_file__internal_FILE(drwav* pWav, FILE* pFile, drwav_chunk_proc onChunk, void* pChunkUserData, drwav_uint32 flags, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + drwav_bool32 result; + + result = drwav_preinit(pWav, drwav__on_read_stdio, drwav__on_seek_stdio, (void*)pFile, pAllocationCallbacks); + if (result != DRWAV_TRUE) { + fclose(pFile); + return result; + } + + result = drwav_init__internal(pWav, onChunk, pChunkUserData, flags); + if (result != DRWAV_TRUE) { + fclose(pFile); + return result; + } + + return DRWAV_TRUE; +} + +DRWAV_API drwav_bool32 drwav_init_file_ex(drwav* pWav, const char* filename, drwav_chunk_proc onChunk, void* pChunkUserData, drwav_uint32 flags, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + FILE* pFile; + if (drwav_fopen(&pFile, filename, "rb") != DRWAV_SUCCESS) { + return DRWAV_FALSE; + } + + /* This takes ownership of the FILE* object. */ + return drwav_init_file__internal_FILE(pWav, pFile, onChunk, pChunkUserData, flags, pAllocationCallbacks); +} + +#ifndef DR_WAV_NO_WCHAR +DRWAV_API drwav_bool32 drwav_init_file_w(drwav* pWav, const wchar_t* filename, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + return drwav_init_file_ex_w(pWav, filename, NULL, NULL, 0, pAllocationCallbacks); +} + +DRWAV_API drwav_bool32 drwav_init_file_ex_w(drwav* pWav, const wchar_t* filename, drwav_chunk_proc onChunk, void* pChunkUserData, drwav_uint32 flags, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + FILE* pFile; + if (drwav_wfopen(&pFile, filename, L"rb", pAllocationCallbacks) != DRWAV_SUCCESS) { + return DRWAV_FALSE; + } + + /* This takes ownership of the FILE* object. */ + return drwav_init_file__internal_FILE(pWav, pFile, onChunk, pChunkUserData, flags, pAllocationCallbacks); +} +#endif + +DRWAV_API drwav_bool32 drwav_init_file_with_metadata(drwav* pWav, const char* filename, drwav_uint32 flags, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + FILE* pFile; + if (drwav_fopen(&pFile, filename, "rb") != DRWAV_SUCCESS) { + return DRWAV_FALSE; + } + + /* This takes ownership of the FILE* object. */ + return drwav_init_file__internal_FILE(pWav, pFile, NULL, NULL, flags | DRWAV_WITH_METADATA, pAllocationCallbacks); +} + +#ifndef DR_WAV_NO_WCHAR +DRWAV_API drwav_bool32 drwav_init_file_with_metadata_w(drwav* pWav, const wchar_t* filename, drwav_uint32 flags, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + FILE* pFile; + if (drwav_wfopen(&pFile, filename, L"rb", pAllocationCallbacks) != DRWAV_SUCCESS) { + return DRWAV_FALSE; + } + + /* This takes ownership of the FILE* object. */ + return drwav_init_file__internal_FILE(pWav, pFile, NULL, NULL, flags | DRWAV_WITH_METADATA, pAllocationCallbacks); +} +#endif + + +DRWAV_PRIVATE drwav_bool32 drwav_init_file_write__internal_FILE(drwav* pWav, FILE* pFile, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, drwav_bool32 isSequential, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + drwav_bool32 result; + + result = drwav_preinit_write(pWav, pFormat, isSequential, drwav__on_write_stdio, drwav__on_seek_stdio, (void*)pFile, pAllocationCallbacks); + if (result != DRWAV_TRUE) { + fclose(pFile); + return result; + } + + result = drwav_init_write__internal(pWav, pFormat, totalSampleCount); + if (result != DRWAV_TRUE) { + fclose(pFile); + return result; + } + + return DRWAV_TRUE; +} + +DRWAV_PRIVATE drwav_bool32 drwav_init_file_write__internal(drwav* pWav, const char* filename, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, drwav_bool32 isSequential, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + FILE* pFile; + if (drwav_fopen(&pFile, filename, "wb") != DRWAV_SUCCESS) { + return DRWAV_FALSE; + } + + /* This takes ownership of the FILE* object. */ + return drwav_init_file_write__internal_FILE(pWav, pFile, pFormat, totalSampleCount, isSequential, pAllocationCallbacks); +} + +#ifndef DR_WAV_NO_WCHAR +DRWAV_PRIVATE drwav_bool32 drwav_init_file_write_w__internal(drwav* pWav, const wchar_t* filename, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, drwav_bool32 isSequential, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + FILE* pFile; + if (drwav_wfopen(&pFile, filename, L"wb", pAllocationCallbacks) != DRWAV_SUCCESS) { + return DRWAV_FALSE; + } + + /* This takes ownership of the FILE* object. */ + return drwav_init_file_write__internal_FILE(pWav, pFile, pFormat, totalSampleCount, isSequential, pAllocationCallbacks); +} +#endif + +DRWAV_API drwav_bool32 drwav_init_file_write(drwav* pWav, const char* filename, const drwav_data_format* pFormat, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + return drwav_init_file_write__internal(pWav, filename, pFormat, 0, DRWAV_FALSE, pAllocationCallbacks); +} + +DRWAV_API drwav_bool32 drwav_init_file_write_sequential(drwav* pWav, const char* filename, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + return drwav_init_file_write__internal(pWav, filename, pFormat, totalSampleCount, DRWAV_TRUE, pAllocationCallbacks); +} + +DRWAV_API drwav_bool32 drwav_init_file_write_sequential_pcm_frames(drwav* pWav, const char* filename, const drwav_data_format* pFormat, drwav_uint64 totalPCMFrameCount, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + if (pFormat == NULL) { + return DRWAV_FALSE; + } + + return drwav_init_file_write_sequential(pWav, filename, pFormat, totalPCMFrameCount*pFormat->channels, pAllocationCallbacks); +} + +#ifndef DR_WAV_NO_WCHAR +DRWAV_API drwav_bool32 drwav_init_file_write_w(drwav* pWav, const wchar_t* filename, const drwav_data_format* pFormat, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + return drwav_init_file_write_w__internal(pWav, filename, pFormat, 0, DRWAV_FALSE, pAllocationCallbacks); +} + +DRWAV_API drwav_bool32 drwav_init_file_write_sequential_w(drwav* pWav, const wchar_t* filename, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + return drwav_init_file_write_w__internal(pWav, filename, pFormat, totalSampleCount, DRWAV_TRUE, pAllocationCallbacks); +} + +DRWAV_API drwav_bool32 drwav_init_file_write_sequential_pcm_frames_w(drwav* pWav, const wchar_t* filename, const drwav_data_format* pFormat, drwav_uint64 totalPCMFrameCount, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + if (pFormat == NULL) { + return DRWAV_FALSE; + } + + return drwav_init_file_write_sequential_w(pWav, filename, pFormat, totalPCMFrameCount*pFormat->channels, pAllocationCallbacks); +} +#endif +#endif /* DR_WAV_NO_STDIO */ + + +DRWAV_PRIVATE size_t drwav__on_read_memory(void* pUserData, void* pBufferOut, size_t bytesToRead) +{ + drwav* pWav = (drwav*)pUserData; + size_t bytesRemaining; + + DRWAV_ASSERT(pWav != NULL); + DRWAV_ASSERT(pWav->memoryStream.dataSize >= pWav->memoryStream.currentReadPos); + + bytesRemaining = pWav->memoryStream.dataSize - pWav->memoryStream.currentReadPos; + if (bytesToRead > bytesRemaining) { + bytesToRead = bytesRemaining; + } + + if (bytesToRead > 0) { + DRWAV_COPY_MEMORY(pBufferOut, pWav->memoryStream.data + pWav->memoryStream.currentReadPos, bytesToRead); + pWav->memoryStream.currentReadPos += bytesToRead; + } + + return bytesToRead; +} + +DRWAV_PRIVATE drwav_bool32 drwav__on_seek_memory(void* pUserData, int offset, drwav_seek_origin origin) +{ + drwav* pWav = (drwav*)pUserData; + DRWAV_ASSERT(pWav != NULL); + + if (origin == drwav_seek_origin_current) { + if (offset > 0) { + if (pWav->memoryStream.currentReadPos + offset > pWav->memoryStream.dataSize) { + return DRWAV_FALSE; /* Trying to seek too far forward. */ + } + } else { + if (pWav->memoryStream.currentReadPos < (size_t)-offset) { + return DRWAV_FALSE; /* Trying to seek too far backwards. */ + } + } + + /* This will never underflow thanks to the clamps above. */ + pWav->memoryStream.currentReadPos += offset; + } else { + if ((drwav_uint32)offset <= pWav->memoryStream.dataSize) { + pWav->memoryStream.currentReadPos = offset; + } else { + return DRWAV_FALSE; /* Trying to seek too far forward. */ + } + } + + return DRWAV_TRUE; +} + +DRWAV_PRIVATE size_t drwav__on_write_memory(void* pUserData, const void* pDataIn, size_t bytesToWrite) +{ + drwav* pWav = (drwav*)pUserData; + size_t bytesRemaining; + + DRWAV_ASSERT(pWav != NULL); + DRWAV_ASSERT(pWav->memoryStreamWrite.dataCapacity >= pWav->memoryStreamWrite.currentWritePos); + + bytesRemaining = pWav->memoryStreamWrite.dataCapacity - pWav->memoryStreamWrite.currentWritePos; + if (bytesRemaining < bytesToWrite) { + /* Need to reallocate. */ + void* pNewData; + size_t newDataCapacity = (pWav->memoryStreamWrite.dataCapacity == 0) ? 256 : pWav->memoryStreamWrite.dataCapacity * 2; + + /* If doubling wasn't enough, just make it the minimum required size to write the data. */ + if ((newDataCapacity - pWav->memoryStreamWrite.currentWritePos) < bytesToWrite) { + newDataCapacity = pWav->memoryStreamWrite.currentWritePos + bytesToWrite; + } + + pNewData = drwav__realloc_from_callbacks(*pWav->memoryStreamWrite.ppData, newDataCapacity, pWav->memoryStreamWrite.dataCapacity, &pWav->allocationCallbacks); + if (pNewData == NULL) { + return 0; + } + + *pWav->memoryStreamWrite.ppData = pNewData; + pWav->memoryStreamWrite.dataCapacity = newDataCapacity; + } + + DRWAV_COPY_MEMORY(((drwav_uint8*)(*pWav->memoryStreamWrite.ppData)) + pWav->memoryStreamWrite.currentWritePos, pDataIn, bytesToWrite); + + pWav->memoryStreamWrite.currentWritePos += bytesToWrite; + if (pWav->memoryStreamWrite.dataSize < pWav->memoryStreamWrite.currentWritePos) { + pWav->memoryStreamWrite.dataSize = pWav->memoryStreamWrite.currentWritePos; + } + + *pWav->memoryStreamWrite.pDataSize = pWav->memoryStreamWrite.dataSize; + + return bytesToWrite; +} + +DRWAV_PRIVATE drwav_bool32 drwav__on_seek_memory_write(void* pUserData, int offset, drwav_seek_origin origin) +{ + drwav* pWav = (drwav*)pUserData; + DRWAV_ASSERT(pWav != NULL); + + if (origin == drwav_seek_origin_current) { + if (offset > 0) { + if (pWav->memoryStreamWrite.currentWritePos + offset > pWav->memoryStreamWrite.dataSize) { + offset = (int)(pWav->memoryStreamWrite.dataSize - pWav->memoryStreamWrite.currentWritePos); /* Trying to seek too far forward. */ + } + } else { + if (pWav->memoryStreamWrite.currentWritePos < (size_t)-offset) { + offset = -(int)pWav->memoryStreamWrite.currentWritePos; /* Trying to seek too far backwards. */ + } + } + + /* This will never underflow thanks to the clamps above. */ + pWav->memoryStreamWrite.currentWritePos += offset; + } else { + if ((drwav_uint32)offset <= pWav->memoryStreamWrite.dataSize) { + pWav->memoryStreamWrite.currentWritePos = offset; + } else { + pWav->memoryStreamWrite.currentWritePos = pWav->memoryStreamWrite.dataSize; /* Trying to seek too far forward. */ + } + } + + return DRWAV_TRUE; +} + +DRWAV_API drwav_bool32 drwav_init_memory(drwav* pWav, const void* data, size_t dataSize, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + return drwav_init_memory_ex(pWav, data, dataSize, NULL, NULL, 0, pAllocationCallbacks); +} + +DRWAV_API drwav_bool32 drwav_init_memory_ex(drwav* pWav, const void* data, size_t dataSize, drwav_chunk_proc onChunk, void* pChunkUserData, drwav_uint32 flags, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + if (data == NULL || dataSize == 0) { + return DRWAV_FALSE; + } + + if (!drwav_preinit(pWav, drwav__on_read_memory, drwav__on_seek_memory, pWav, pAllocationCallbacks)) { + return DRWAV_FALSE; + } + + pWav->memoryStream.data = (const drwav_uint8*)data; + pWav->memoryStream.dataSize = dataSize; + pWav->memoryStream.currentReadPos = 0; + + return drwav_init__internal(pWav, onChunk, pChunkUserData, flags); +} + +DRWAV_API drwav_bool32 drwav_init_memory_with_metadata(drwav* pWav, const void* data, size_t dataSize, drwav_uint32 flags, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + if (data == NULL || dataSize == 0) { + return DRWAV_FALSE; + } + + if (!drwav_preinit(pWav, drwav__on_read_memory, drwav__on_seek_memory, pWav, pAllocationCallbacks)) { + return DRWAV_FALSE; + } + + pWav->memoryStream.data = (const drwav_uint8*)data; + pWav->memoryStream.dataSize = dataSize; + pWav->memoryStream.currentReadPos = 0; + + return drwav_init__internal(pWav, NULL, NULL, flags | DRWAV_WITH_METADATA); +} + + +DRWAV_PRIVATE drwav_bool32 drwav_init_memory_write__internal(drwav* pWav, void** ppData, size_t* pDataSize, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, drwav_bool32 isSequential, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + if (ppData == NULL || pDataSize == NULL) { + return DRWAV_FALSE; + } + + *ppData = NULL; /* Important because we're using realloc()! */ + *pDataSize = 0; + + if (!drwav_preinit_write(pWav, pFormat, isSequential, drwav__on_write_memory, drwav__on_seek_memory_write, pWav, pAllocationCallbacks)) { + return DRWAV_FALSE; + } + + pWav->memoryStreamWrite.ppData = ppData; + pWav->memoryStreamWrite.pDataSize = pDataSize; + pWav->memoryStreamWrite.dataSize = 0; + pWav->memoryStreamWrite.dataCapacity = 0; + pWav->memoryStreamWrite.currentWritePos = 0; + + return drwav_init_write__internal(pWav, pFormat, totalSampleCount); +} + +DRWAV_API drwav_bool32 drwav_init_memory_write(drwav* pWav, void** ppData, size_t* pDataSize, const drwav_data_format* pFormat, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + return drwav_init_memory_write__internal(pWav, ppData, pDataSize, pFormat, 0, DRWAV_FALSE, pAllocationCallbacks); +} + +DRWAV_API drwav_bool32 drwav_init_memory_write_sequential(drwav* pWav, void** ppData, size_t* pDataSize, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + return drwav_init_memory_write__internal(pWav, ppData, pDataSize, pFormat, totalSampleCount, DRWAV_TRUE, pAllocationCallbacks); +} + +DRWAV_API drwav_bool32 drwav_init_memory_write_sequential_pcm_frames(drwav* pWav, void** ppData, size_t* pDataSize, const drwav_data_format* pFormat, drwav_uint64 totalPCMFrameCount, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + if (pFormat == NULL) { + return DRWAV_FALSE; + } + + return drwav_init_memory_write_sequential(pWav, ppData, pDataSize, pFormat, totalPCMFrameCount*pFormat->channels, pAllocationCallbacks); +} + + + +DRWAV_API drwav_result drwav_uninit(drwav* pWav) +{ + drwav_result result = DRWAV_SUCCESS; + + if (pWav == NULL) { + return DRWAV_INVALID_ARGS; + } + + /* + If the drwav object was opened in write mode we'll need to finalize a few things: + - Make sure the "data" chunk is aligned to 16-bits for RIFF containers, or 64 bits for W64 containers. + - Set the size of the "data" chunk. + */ + if (pWav->onWrite != NULL) { + drwav_uint32 paddingSize = 0; + + /* Padding. Do not adjust pWav->dataChunkDataSize - this should not include the padding. */ + if (pWav->container == drwav_container_riff || pWav->container == drwav_container_rf64) { + paddingSize = drwav__chunk_padding_size_riff(pWav->dataChunkDataSize); + } else { + paddingSize = drwav__chunk_padding_size_w64(pWav->dataChunkDataSize); + } + + if (paddingSize > 0) { + drwav_uint64 paddingData = 0; + drwav__write(pWav, &paddingData, paddingSize); /* Byte order does not matter for this. */ + } + + /* + Chunk sizes. When using sequential mode, these will have been filled in at initialization time. We only need + to do this when using non-sequential mode. + */ + if (pWav->onSeek && !pWav->isSequentialWrite) { + if (pWav->container == drwav_container_riff) { + /* The "RIFF" chunk size. */ + if (pWav->onSeek(pWav->pUserData, 4, drwav_seek_origin_start)) { + drwav_uint32 riffChunkSize = drwav__riff_chunk_size_riff(pWav->dataChunkDataSize, pWav->pMetadata, pWav->metadataCount); + drwav__write_u32ne_to_le(pWav, riffChunkSize); + } + + /* The "data" chunk size. */ + if (pWav->onSeek(pWav->pUserData, (int)pWav->dataChunkDataPos - 4, drwav_seek_origin_start)) { + drwav_uint32 dataChunkSize = drwav__data_chunk_size_riff(pWav->dataChunkDataSize); + drwav__write_u32ne_to_le(pWav, dataChunkSize); + } + } else if (pWav->container == drwav_container_w64) { + /* The "RIFF" chunk size. */ + if (pWav->onSeek(pWav->pUserData, 16, drwav_seek_origin_start)) { + drwav_uint64 riffChunkSize = drwav__riff_chunk_size_w64(pWav->dataChunkDataSize); + drwav__write_u64ne_to_le(pWav, riffChunkSize); + } + + /* The "data" chunk size. */ + if (pWav->onSeek(pWav->pUserData, (int)pWav->dataChunkDataPos - 8, drwav_seek_origin_start)) { + drwav_uint64 dataChunkSize = drwav__data_chunk_size_w64(pWav->dataChunkDataSize); + drwav__write_u64ne_to_le(pWav, dataChunkSize); + } + } else if (pWav->container == drwav_container_rf64) { + /* We only need to update the ds64 chunk. The "RIFF" and "data" chunks always have their sizes set to 0xFFFFFFFF for RF64. */ + int ds64BodyPos = 12 + 8; + + /* The "RIFF" chunk size. */ + if (pWav->onSeek(pWav->pUserData, ds64BodyPos + 0, drwav_seek_origin_start)) { + drwav_uint64 riffChunkSize = drwav__riff_chunk_size_rf64(pWav->dataChunkDataSize, pWav->pMetadata, pWav->metadataCount); + drwav__write_u64ne_to_le(pWav, riffChunkSize); + } + + /* The "data" chunk size. */ + if (pWav->onSeek(pWav->pUserData, ds64BodyPos + 8, drwav_seek_origin_start)) { + drwav_uint64 dataChunkSize = drwav__data_chunk_size_rf64(pWav->dataChunkDataSize); + drwav__write_u64ne_to_le(pWav, dataChunkSize); + } + } + } + + /* Validation for sequential mode. */ + if (pWav->isSequentialWrite) { + if (pWav->dataChunkDataSize != pWav->dataChunkDataSizeTargetWrite) { + result = DRWAV_INVALID_FILE; + } + } + } else { + drwav_free(pWav->pMetadata, &pWav->allocationCallbacks); + } + +#ifndef DR_WAV_NO_STDIO + /* + If we opened the file with drwav_open_file() we will want to close the file handle. We can know whether or not drwav_open_file() + was used by looking at the onRead and onSeek callbacks. + */ + if (pWav->onRead == drwav__on_read_stdio || pWav->onWrite == drwav__on_write_stdio) { + fclose((FILE*)pWav->pUserData); + } +#endif + + return result; +} + + + +DRWAV_API size_t drwav_read_raw(drwav* pWav, size_t bytesToRead, void* pBufferOut) +{ + size_t bytesRead; + drwav_uint32 bytesPerFrame; + + if (pWav == NULL || bytesToRead == 0) { + return 0; /* Invalid args. */ + } + + if (bytesToRead > pWav->bytesRemaining) { + bytesToRead = (size_t)pWav->bytesRemaining; + } + + if (bytesToRead == 0) { + return 0; /* At end. */ + } + + bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); + if (bytesPerFrame == 0) { + return 0; /* Could not determine the bytes per frame. */ + } + + if (pBufferOut != NULL) { + bytesRead = pWav->onRead(pWav->pUserData, pBufferOut, bytesToRead); + } else { + /* We need to seek. If we fail, we need to read-and-discard to make sure we get a good byte count. */ + bytesRead = 0; + while (bytesRead < bytesToRead) { + size_t bytesToSeek = (bytesToRead - bytesRead); + if (bytesToSeek > 0x7FFFFFFF) { + bytesToSeek = 0x7FFFFFFF; + } + + if (pWav->onSeek(pWav->pUserData, (int)bytesToSeek, drwav_seek_origin_current) == DRWAV_FALSE) { + break; + } + + bytesRead += bytesToSeek; + } + + /* When we get here we may need to read-and-discard some data. */ + while (bytesRead < bytesToRead) { + drwav_uint8 buffer[4096]; + size_t bytesSeeked; + size_t bytesToSeek = (bytesToRead - bytesRead); + if (bytesToSeek > sizeof(buffer)) { + bytesToSeek = sizeof(buffer); + } + + bytesSeeked = pWav->onRead(pWav->pUserData, buffer, bytesToSeek); + bytesRead += bytesSeeked; + + if (bytesSeeked < bytesToSeek) { + break; /* Reached the end. */ + } + } + } + + pWav->readCursorInPCMFrames += bytesRead / bytesPerFrame; + + pWav->bytesRemaining -= bytesRead; + return bytesRead; +} + + + +DRWAV_API drwav_uint64 drwav_read_pcm_frames_le(drwav* pWav, drwav_uint64 framesToRead, void* pBufferOut) +{ + drwav_uint32 bytesPerFrame; + drwav_uint64 bytesToRead; /* Intentionally uint64 instead of size_t so we can do a check that we're not reading too much on 32-bit builds. */ + drwav_uint64 framesRemainingInFile; + + if (pWav == NULL || framesToRead == 0) { + return 0; + } + + /* Cannot use this function for compressed formats. */ + if (drwav__is_compressed_format_tag(pWav->translatedFormatTag)) { + return 0; + } + + framesRemainingInFile = pWav->totalPCMFrameCount - pWav->readCursorInPCMFrames; + if (framesToRead > framesRemainingInFile) { + framesToRead = framesRemainingInFile; + } + + bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); + if (bytesPerFrame == 0) { + return 0; + } + + /* Don't try to read more samples than can potentially fit in the output buffer. */ + bytesToRead = framesToRead * bytesPerFrame; + if (bytesToRead > DRWAV_SIZE_MAX) { + bytesToRead = (DRWAV_SIZE_MAX / bytesPerFrame) * bytesPerFrame; /* Round the number of bytes to read to a clean frame boundary. */ + } + + /* + Doing an explicit check here just to make it clear that we don't want to be attempt to read anything if there's no bytes to read. There + *could* be a time where it evaluates to 0 due to overflowing. + */ + if (bytesToRead == 0) { + return 0; + } + + return drwav_read_raw(pWav, (size_t)bytesToRead, pBufferOut) / bytesPerFrame; +} + +DRWAV_API drwav_uint64 drwav_read_pcm_frames_be(drwav* pWav, drwav_uint64 framesToRead, void* pBufferOut) +{ + drwav_uint64 framesRead = drwav_read_pcm_frames_le(pWav, framesToRead, pBufferOut); + + if (pBufferOut != NULL) { + drwav_uint32 bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); + if (bytesPerFrame == 0) { + return 0; /* Could not get the bytes per frame which means bytes per sample cannot be determined and we don't know how to byte swap. */ + } + + drwav__bswap_samples(pBufferOut, framesRead*pWav->channels, bytesPerFrame/pWav->channels); + } + + return framesRead; +} + +DRWAV_API drwav_uint64 drwav_read_pcm_frames(drwav* pWav, drwav_uint64 framesToRead, void* pBufferOut) +{ + drwav_uint64 framesRead = 0; + + if (drwav_is_container_be(pWav->container)) { + /* + Special case for AIFF. AIFF is a big-endian encoded format, but it supports a format that is + PCM in little-endian encoding. In this case, we fall through this branch and treate it as + little-endian. + */ + if (pWav->container != drwav_container_aiff || pWav->aiff.isLE == DRWAV_FALSE) { + if (drwav__is_little_endian()) { + framesRead = drwav_read_pcm_frames_be(pWav, framesToRead, pBufferOut); + } else { + framesRead = drwav_read_pcm_frames_le(pWav, framesToRead, pBufferOut); + } + + goto post_process; + } + } + + /* Getting here means the data should be considered little-endian. */ + if (drwav__is_little_endian()) { + framesRead = drwav_read_pcm_frames_le(pWav, framesToRead, pBufferOut); + } else { + framesRead = drwav_read_pcm_frames_be(pWav, framesToRead, pBufferOut); + } + + /* + Here is where we check if we need to do a signed/unsigned conversion for AIFF. The reason we need to do this + is because dr_wav always assumes an 8-bit sample is unsigned, whereas AIFF can have signed 8-bit formats. + */ + post_process: + { + if (pWav->container == drwav_container_aiff && pWav->bitsPerSample == 8 && pWav->aiff.isUnsigned == DRWAV_FALSE) { + if (pBufferOut != NULL) { + drwav_uint64 iSample; + + for (iSample = 0; iSample < framesRead * pWav->channels; iSample += 1) { + ((drwav_uint8*)pBufferOut)[iSample] += 128; + } + } + } + } + + return framesRead; +} + + + +DRWAV_PRIVATE drwav_bool32 drwav_seek_to_first_pcm_frame(drwav* pWav) +{ + if (pWav->onWrite != NULL) { + return DRWAV_FALSE; /* No seeking in write mode. */ + } + + if (!pWav->onSeek(pWav->pUserData, (int)pWav->dataChunkDataPos, drwav_seek_origin_start)) { + return DRWAV_FALSE; + } + + if (drwav__is_compressed_format_tag(pWav->translatedFormatTag)) { + /* Cached data needs to be cleared for compressed formats. */ + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM) { + DRWAV_ZERO_OBJECT(&pWav->msadpcm); + } else if (pWav->translatedFormatTag == DR_WAVE_FORMAT_DVI_ADPCM) { + DRWAV_ZERO_OBJECT(&pWav->ima); + } else { + DRWAV_ASSERT(DRWAV_FALSE); /* If this assertion is triggered it means I've implemented a new compressed format but forgot to add a branch for it here. */ + } + } + + pWav->readCursorInPCMFrames = 0; + pWav->bytesRemaining = pWav->dataChunkDataSize; + + return DRWAV_TRUE; +} + +DRWAV_API drwav_bool32 drwav_seek_to_pcm_frame(drwav* pWav, drwav_uint64 targetFrameIndex) +{ + /* Seeking should be compatible with wave files > 2GB. */ + + if (pWav == NULL || pWav->onSeek == NULL) { + return DRWAV_FALSE; + } + + /* No seeking in write mode. */ + if (pWav->onWrite != NULL) { + return DRWAV_FALSE; + } + + /* If there are no samples, just return DRWAV_TRUE without doing anything. */ + if (pWav->totalPCMFrameCount == 0) { + return DRWAV_TRUE; + } + + /* Make sure the sample is clamped. */ + if (targetFrameIndex > pWav->totalPCMFrameCount) { + targetFrameIndex = pWav->totalPCMFrameCount; + } + + /* + For compressed formats we just use a slow generic seek. If we are seeking forward we just seek forward. If we are going backwards we need + to seek back to the start. + */ + if (drwav__is_compressed_format_tag(pWav->translatedFormatTag)) { + /* TODO: This can be optimized. */ + + /* + If we're seeking forward it's simple - just keep reading samples until we hit the sample we're requesting. If we're seeking backwards, + we first need to seek back to the start and then just do the same thing as a forward seek. + */ + if (targetFrameIndex < pWav->readCursorInPCMFrames) { + if (!drwav_seek_to_first_pcm_frame(pWav)) { + return DRWAV_FALSE; + } + } + + if (targetFrameIndex > pWav->readCursorInPCMFrames) { + drwav_uint64 offsetInFrames = targetFrameIndex - pWav->readCursorInPCMFrames; + + drwav_int16 devnull[2048]; + while (offsetInFrames > 0) { + drwav_uint64 framesRead = 0; + drwav_uint64 framesToRead = offsetInFrames; + if (framesToRead > drwav_countof(devnull)/pWav->channels) { + framesToRead = drwav_countof(devnull)/pWav->channels; + } + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM) { + framesRead = drwav_read_pcm_frames_s16__msadpcm(pWav, framesToRead, devnull); + } else if (pWav->translatedFormatTag == DR_WAVE_FORMAT_DVI_ADPCM) { + framesRead = drwav_read_pcm_frames_s16__ima(pWav, framesToRead, devnull); + } else { + DRWAV_ASSERT(DRWAV_FALSE); /* If this assertion is triggered it means I've implemented a new compressed format but forgot to add a branch for it here. */ + } + + if (framesRead != framesToRead) { + return DRWAV_FALSE; + } + + offsetInFrames -= framesRead; + } + } + } else { + drwav_uint64 totalSizeInBytes; + drwav_uint64 currentBytePos; + drwav_uint64 targetBytePos; + drwav_uint64 offset; + drwav_uint32 bytesPerFrame; + + bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); + if (bytesPerFrame == 0) { + return DRWAV_FALSE; /* Not able to calculate offset. */ + } + + totalSizeInBytes = pWav->totalPCMFrameCount * bytesPerFrame; + /*DRWAV_ASSERT(totalSizeInBytes >= pWav->bytesRemaining);*/ + + currentBytePos = totalSizeInBytes - pWav->bytesRemaining; + targetBytePos = targetFrameIndex * bytesPerFrame; + + if (currentBytePos < targetBytePos) { + /* Offset forwards. */ + offset = (targetBytePos - currentBytePos); + } else { + /* Offset backwards. */ + if (!drwav_seek_to_first_pcm_frame(pWav)) { + return DRWAV_FALSE; + } + offset = targetBytePos; + } + + while (offset > 0) { + int offset32 = ((offset > INT_MAX) ? INT_MAX : (int)offset); + if (!pWav->onSeek(pWav->pUserData, offset32, drwav_seek_origin_current)) { + return DRWAV_FALSE; + } + + pWav->readCursorInPCMFrames += offset32 / bytesPerFrame; + pWav->bytesRemaining -= offset32; + offset -= offset32; + } + } + + return DRWAV_TRUE; +} + +DRWAV_API drwav_result drwav_get_cursor_in_pcm_frames(drwav* pWav, drwav_uint64* pCursor) +{ + if (pCursor == NULL) { + return DRWAV_INVALID_ARGS; + } + + *pCursor = 0; /* Safety. */ + + if (pWav == NULL) { + return DRWAV_INVALID_ARGS; + } + + *pCursor = pWav->readCursorInPCMFrames; + + return DRWAV_SUCCESS; +} + +DRWAV_API drwav_result drwav_get_length_in_pcm_frames(drwav* pWav, drwav_uint64* pLength) +{ + if (pLength == NULL) { + return DRWAV_INVALID_ARGS; + } + + *pLength = 0; /* Safety. */ + + if (pWav == NULL) { + return DRWAV_INVALID_ARGS; + } + + *pLength = pWav->totalPCMFrameCount; + + return DRWAV_SUCCESS; +} + + +DRWAV_API size_t drwav_write_raw(drwav* pWav, size_t bytesToWrite, const void* pData) +{ + size_t bytesWritten; + + if (pWav == NULL || bytesToWrite == 0 || pData == NULL) { + return 0; + } + + bytesWritten = pWav->onWrite(pWav->pUserData, pData, bytesToWrite); + pWav->dataChunkDataSize += bytesWritten; + + return bytesWritten; +} + +DRWAV_API drwav_uint64 drwav_write_pcm_frames_le(drwav* pWav, drwav_uint64 framesToWrite, const void* pData) +{ + drwav_uint64 bytesToWrite; + drwav_uint64 bytesWritten; + const drwav_uint8* pRunningData; + + if (pWav == NULL || framesToWrite == 0 || pData == NULL) { + return 0; + } + + bytesToWrite = ((framesToWrite * pWav->channels * pWav->bitsPerSample) / 8); + if (bytesToWrite > DRWAV_SIZE_MAX) { + return 0; + } + + bytesWritten = 0; + pRunningData = (const drwav_uint8*)pData; + + while (bytesToWrite > 0) { + size_t bytesJustWritten; + drwav_uint64 bytesToWriteThisIteration; + + bytesToWriteThisIteration = bytesToWrite; + DRWAV_ASSERT(bytesToWriteThisIteration <= DRWAV_SIZE_MAX); /* <-- This is checked above. */ + + bytesJustWritten = drwav_write_raw(pWav, (size_t)bytesToWriteThisIteration, pRunningData); + if (bytesJustWritten == 0) { + break; + } + + bytesToWrite -= bytesJustWritten; + bytesWritten += bytesJustWritten; + pRunningData += bytesJustWritten; + } + + return (bytesWritten * 8) / pWav->bitsPerSample / pWav->channels; +} + +DRWAV_API drwav_uint64 drwav_write_pcm_frames_be(drwav* pWav, drwav_uint64 framesToWrite, const void* pData) +{ + drwav_uint64 bytesToWrite; + drwav_uint64 bytesWritten; + drwav_uint32 bytesPerSample; + const drwav_uint8* pRunningData; + + if (pWav == NULL || framesToWrite == 0 || pData == NULL) { + return 0; + } + + bytesToWrite = ((framesToWrite * pWav->channels * pWav->bitsPerSample) / 8); + if (bytesToWrite > DRWAV_SIZE_MAX) { + return 0; + } + + bytesWritten = 0; + pRunningData = (const drwav_uint8*)pData; + + bytesPerSample = drwav_get_bytes_per_pcm_frame(pWav) / pWav->channels; + if (bytesPerSample == 0) { + return 0; /* Cannot determine bytes per sample, or bytes per sample is less than one byte. */ + } + + while (bytesToWrite > 0) { + drwav_uint8 temp[4096]; + drwav_uint32 sampleCount; + size_t bytesJustWritten; + drwav_uint64 bytesToWriteThisIteration; + + bytesToWriteThisIteration = bytesToWrite; + DRWAV_ASSERT(bytesToWriteThisIteration <= DRWAV_SIZE_MAX); /* <-- This is checked above. */ + + /* + WAV files are always little-endian. We need to byte swap on big-endian architectures. Since our input buffer is read-only we need + to use an intermediary buffer for the conversion. + */ + sampleCount = sizeof(temp)/bytesPerSample; + + if (bytesToWriteThisIteration > ((drwav_uint64)sampleCount)*bytesPerSample) { + bytesToWriteThisIteration = ((drwav_uint64)sampleCount)*bytesPerSample; + } + + DRWAV_COPY_MEMORY(temp, pRunningData, (size_t)bytesToWriteThisIteration); + drwav__bswap_samples(temp, sampleCount, bytesPerSample); + + bytesJustWritten = drwav_write_raw(pWav, (size_t)bytesToWriteThisIteration, temp); + if (bytesJustWritten == 0) { + break; + } + + bytesToWrite -= bytesJustWritten; + bytesWritten += bytesJustWritten; + pRunningData += bytesJustWritten; + } + + return (bytesWritten * 8) / pWav->bitsPerSample / pWav->channels; +} + +DRWAV_API drwav_uint64 drwav_write_pcm_frames(drwav* pWav, drwav_uint64 framesToWrite, const void* pData) +{ + if (drwav__is_little_endian()) { + return drwav_write_pcm_frames_le(pWav, framesToWrite, pData); + } else { + return drwav_write_pcm_frames_be(pWav, framesToWrite, pData); + } +} + + +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__msadpcm(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut) +{ + drwav_uint64 totalFramesRead = 0; + + DRWAV_ASSERT(pWav != NULL); + DRWAV_ASSERT(framesToRead > 0); + + /* TODO: Lots of room for optimization here. */ + + while (pWav->readCursorInPCMFrames < pWav->totalPCMFrameCount) { + DRWAV_ASSERT(framesToRead > 0); /* This loop iteration will never get hit with framesToRead == 0 because it's asserted at the top, and we check for 0 inside the loop just below. */ + + /* If there are no cached frames we need to load a new block. */ + if (pWav->msadpcm.cachedFrameCount == 0 && pWav->msadpcm.bytesRemainingInBlock == 0) { + if (pWav->channels == 1) { + /* Mono. */ + drwav_uint8 header[7]; + if (pWav->onRead(pWav->pUserData, header, sizeof(header)) != sizeof(header)) { + return totalFramesRead; + } + pWav->msadpcm.bytesRemainingInBlock = pWav->fmt.blockAlign - sizeof(header); + + pWav->msadpcm.predictor[0] = header[0]; + pWav->msadpcm.delta[0] = drwav_bytes_to_s16(header + 1); + pWav->msadpcm.prevFrames[0][1] = (drwav_int32)drwav_bytes_to_s16(header + 3); + pWav->msadpcm.prevFrames[0][0] = (drwav_int32)drwav_bytes_to_s16(header + 5); + pWav->msadpcm.cachedFrames[2] = pWav->msadpcm.prevFrames[0][0]; + pWav->msadpcm.cachedFrames[3] = pWav->msadpcm.prevFrames[0][1]; + pWav->msadpcm.cachedFrameCount = 2; + } else { + /* Stereo. */ + drwav_uint8 header[14]; + if (pWav->onRead(pWav->pUserData, header, sizeof(header)) != sizeof(header)) { + return totalFramesRead; + } + pWav->msadpcm.bytesRemainingInBlock = pWav->fmt.blockAlign - sizeof(header); + + pWav->msadpcm.predictor[0] = header[0]; + pWav->msadpcm.predictor[1] = header[1]; + pWav->msadpcm.delta[0] = drwav_bytes_to_s16(header + 2); + pWav->msadpcm.delta[1] = drwav_bytes_to_s16(header + 4); + pWav->msadpcm.prevFrames[0][1] = (drwav_int32)drwav_bytes_to_s16(header + 6); + pWav->msadpcm.prevFrames[1][1] = (drwav_int32)drwav_bytes_to_s16(header + 8); + pWav->msadpcm.prevFrames[0][0] = (drwav_int32)drwav_bytes_to_s16(header + 10); + pWav->msadpcm.prevFrames[1][0] = (drwav_int32)drwav_bytes_to_s16(header + 12); + + pWav->msadpcm.cachedFrames[0] = pWav->msadpcm.prevFrames[0][0]; + pWav->msadpcm.cachedFrames[1] = pWav->msadpcm.prevFrames[1][0]; + pWav->msadpcm.cachedFrames[2] = pWav->msadpcm.prevFrames[0][1]; + pWav->msadpcm.cachedFrames[3] = pWav->msadpcm.prevFrames[1][1]; + pWav->msadpcm.cachedFrameCount = 2; + } + } + + /* Output anything that's cached. */ + while (framesToRead > 0 && pWav->msadpcm.cachedFrameCount > 0 && pWav->readCursorInPCMFrames < pWav->totalPCMFrameCount) { + if (pBufferOut != NULL) { + drwav_uint32 iSample = 0; + for (iSample = 0; iSample < pWav->channels; iSample += 1) { + pBufferOut[iSample] = (drwav_int16)pWav->msadpcm.cachedFrames[(drwav_countof(pWav->msadpcm.cachedFrames) - (pWav->msadpcm.cachedFrameCount*pWav->channels)) + iSample]; + } + + pBufferOut += pWav->channels; + } + + framesToRead -= 1; + totalFramesRead += 1; + pWav->readCursorInPCMFrames += 1; + pWav->msadpcm.cachedFrameCount -= 1; + } + + if (framesToRead == 0) { + break; + } + + + /* + If there's nothing left in the cache, just go ahead and load more. If there's nothing left to load in the current block we just continue to the next + loop iteration which will trigger the loading of a new block. + */ + if (pWav->msadpcm.cachedFrameCount == 0) { + if (pWav->msadpcm.bytesRemainingInBlock == 0) { + continue; + } else { + static drwav_int32 adaptationTable[] = { + 230, 230, 230, 230, 307, 409, 512, 614, + 768, 614, 512, 409, 307, 230, 230, 230 + }; + static drwav_int32 coeff1Table[] = { 256, 512, 0, 192, 240, 460, 392 }; + static drwav_int32 coeff2Table[] = { 0, -256, 0, 64, 0, -208, -232 }; + + drwav_uint8 nibbles; + drwav_int32 nibble0; + drwav_int32 nibble1; + + if (pWav->onRead(pWav->pUserData, &nibbles, 1) != 1) { + return totalFramesRead; + } + pWav->msadpcm.bytesRemainingInBlock -= 1; + + /* TODO: Optimize away these if statements. */ + nibble0 = ((nibbles & 0xF0) >> 4); if ((nibbles & 0x80)) { nibble0 |= 0xFFFFFFF0UL; } + nibble1 = ((nibbles & 0x0F) >> 0); if ((nibbles & 0x08)) { nibble1 |= 0xFFFFFFF0UL; } + + if (pWav->channels == 1) { + /* Mono. */ + drwav_int32 newSample0; + drwav_int32 newSample1; + + newSample0 = ((pWav->msadpcm.prevFrames[0][1] * coeff1Table[pWav->msadpcm.predictor[0]]) + (pWav->msadpcm.prevFrames[0][0] * coeff2Table[pWav->msadpcm.predictor[0]])) >> 8; + newSample0 += nibble0 * pWav->msadpcm.delta[0]; + newSample0 = drwav_clamp(newSample0, -32768, 32767); + + pWav->msadpcm.delta[0] = (adaptationTable[((nibbles & 0xF0) >> 4)] * pWav->msadpcm.delta[0]) >> 8; + if (pWav->msadpcm.delta[0] < 16) { + pWav->msadpcm.delta[0] = 16; + } + + pWav->msadpcm.prevFrames[0][0] = pWav->msadpcm.prevFrames[0][1]; + pWav->msadpcm.prevFrames[0][1] = newSample0; + + + newSample1 = ((pWav->msadpcm.prevFrames[0][1] * coeff1Table[pWav->msadpcm.predictor[0]]) + (pWav->msadpcm.prevFrames[0][0] * coeff2Table[pWav->msadpcm.predictor[0]])) >> 8; + newSample1 += nibble1 * pWav->msadpcm.delta[0]; + newSample1 = drwav_clamp(newSample1, -32768, 32767); + + pWav->msadpcm.delta[0] = (adaptationTable[((nibbles & 0x0F) >> 0)] * pWav->msadpcm.delta[0]) >> 8; + if (pWav->msadpcm.delta[0] < 16) { + pWav->msadpcm.delta[0] = 16; + } + + pWav->msadpcm.prevFrames[0][0] = pWav->msadpcm.prevFrames[0][1]; + pWav->msadpcm.prevFrames[0][1] = newSample1; + + + pWav->msadpcm.cachedFrames[2] = newSample0; + pWav->msadpcm.cachedFrames[3] = newSample1; + pWav->msadpcm.cachedFrameCount = 2; + } else { + /* Stereo. */ + drwav_int32 newSample0; + drwav_int32 newSample1; + + /* Left. */ + newSample0 = ((pWav->msadpcm.prevFrames[0][1] * coeff1Table[pWav->msadpcm.predictor[0]]) + (pWav->msadpcm.prevFrames[0][0] * coeff2Table[pWav->msadpcm.predictor[0]])) >> 8; + newSample0 += nibble0 * pWav->msadpcm.delta[0]; + newSample0 = drwav_clamp(newSample0, -32768, 32767); + + pWav->msadpcm.delta[0] = (adaptationTable[((nibbles & 0xF0) >> 4)] * pWav->msadpcm.delta[0]) >> 8; + if (pWav->msadpcm.delta[0] < 16) { + pWav->msadpcm.delta[0] = 16; + } + + pWav->msadpcm.prevFrames[0][0] = pWav->msadpcm.prevFrames[0][1]; + pWav->msadpcm.prevFrames[0][1] = newSample0; + + + /* Right. */ + newSample1 = ((pWav->msadpcm.prevFrames[1][1] * coeff1Table[pWav->msadpcm.predictor[1]]) + (pWav->msadpcm.prevFrames[1][0] * coeff2Table[pWav->msadpcm.predictor[1]])) >> 8; + newSample1 += nibble1 * pWav->msadpcm.delta[1]; + newSample1 = drwav_clamp(newSample1, -32768, 32767); + + pWav->msadpcm.delta[1] = (adaptationTable[((nibbles & 0x0F) >> 0)] * pWav->msadpcm.delta[1]) >> 8; + if (pWav->msadpcm.delta[1] < 16) { + pWav->msadpcm.delta[1] = 16; + } + + pWav->msadpcm.prevFrames[1][0] = pWav->msadpcm.prevFrames[1][1]; + pWav->msadpcm.prevFrames[1][1] = newSample1; + + pWav->msadpcm.cachedFrames[2] = newSample0; + pWav->msadpcm.cachedFrames[3] = newSample1; + pWav->msadpcm.cachedFrameCount = 1; + } + } + } + } + + return totalFramesRead; +} + + +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__ima(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut) +{ + drwav_uint64 totalFramesRead = 0; + drwav_uint32 iChannel; + + static drwav_int32 indexTable[16] = { + -1, -1, -1, -1, 2, 4, 6, 8, + -1, -1, -1, -1, 2, 4, 6, 8 + }; + + static drwav_int32 stepTable[89] = { + 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, + 19, 21, 23, 25, 28, 31, 34, 37, 41, 45, + 50, 55, 60, 66, 73, 80, 88, 97, 107, 118, + 130, 143, 157, 173, 190, 209, 230, 253, 279, 307, + 337, 371, 408, 449, 494, 544, 598, 658, 724, 796, + 876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066, + 2272, 2499, 2749, 3024, 3327, 3660, 4026, 4428, 4871, 5358, + 5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899, + 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767 + }; + + DRWAV_ASSERT(pWav != NULL); + DRWAV_ASSERT(framesToRead > 0); + + /* TODO: Lots of room for optimization here. */ + + while (pWav->readCursorInPCMFrames < pWav->totalPCMFrameCount) { + DRWAV_ASSERT(framesToRead > 0); /* This loop iteration will never get hit with framesToRead == 0 because it's asserted at the top, and we check for 0 inside the loop just below. */ + + /* If there are no cached samples we need to load a new block. */ + if (pWav->ima.cachedFrameCount == 0 && pWav->ima.bytesRemainingInBlock == 0) { + if (pWav->channels == 1) { + /* Mono. */ + drwav_uint8 header[4]; + if (pWav->onRead(pWav->pUserData, header, sizeof(header)) != sizeof(header)) { + return totalFramesRead; + } + pWav->ima.bytesRemainingInBlock = pWav->fmt.blockAlign - sizeof(header); + + if (header[2] >= drwav_countof(stepTable)) { + pWav->onSeek(pWav->pUserData, pWav->ima.bytesRemainingInBlock, drwav_seek_origin_current); + pWav->ima.bytesRemainingInBlock = 0; + return totalFramesRead; /* Invalid data. */ + } + + pWav->ima.predictor[0] = (drwav_int16)drwav_bytes_to_u16(header + 0); + pWav->ima.stepIndex[0] = drwav_clamp(header[2], 0, (drwav_int32)drwav_countof(stepTable)-1); /* Clamp not necessary because we checked above, but adding here to silence a static analysis warning. */ + pWav->ima.cachedFrames[drwav_countof(pWav->ima.cachedFrames) - 1] = pWav->ima.predictor[0]; + pWav->ima.cachedFrameCount = 1; + } else { + /* Stereo. */ + drwav_uint8 header[8]; + if (pWav->onRead(pWav->pUserData, header, sizeof(header)) != sizeof(header)) { + return totalFramesRead; + } + pWav->ima.bytesRemainingInBlock = pWav->fmt.blockAlign - sizeof(header); + + if (header[2] >= drwav_countof(stepTable) || header[6] >= drwav_countof(stepTable)) { + pWav->onSeek(pWav->pUserData, pWav->ima.bytesRemainingInBlock, drwav_seek_origin_current); + pWav->ima.bytesRemainingInBlock = 0; + return totalFramesRead; /* Invalid data. */ + } + + pWav->ima.predictor[0] = drwav_bytes_to_s16(header + 0); + pWav->ima.stepIndex[0] = drwav_clamp(header[2], 0, (drwav_int32)drwav_countof(stepTable)-1); /* Clamp not necessary because we checked above, but adding here to silence a static analysis warning. */ + pWav->ima.predictor[1] = drwav_bytes_to_s16(header + 4); + pWav->ima.stepIndex[1] = drwav_clamp(header[6], 0, (drwav_int32)drwav_countof(stepTable)-1); /* Clamp not necessary because we checked above, but adding here to silence a static analysis warning. */ + + pWav->ima.cachedFrames[drwav_countof(pWav->ima.cachedFrames) - 2] = pWav->ima.predictor[0]; + pWav->ima.cachedFrames[drwav_countof(pWav->ima.cachedFrames) - 1] = pWav->ima.predictor[1]; + pWav->ima.cachedFrameCount = 1; + } + } + + /* Output anything that's cached. */ + while (framesToRead > 0 && pWav->ima.cachedFrameCount > 0 && pWav->readCursorInPCMFrames < pWav->totalPCMFrameCount) { + if (pBufferOut != NULL) { + drwav_uint32 iSample; + for (iSample = 0; iSample < pWav->channels; iSample += 1) { + pBufferOut[iSample] = (drwav_int16)pWav->ima.cachedFrames[(drwav_countof(pWav->ima.cachedFrames) - (pWav->ima.cachedFrameCount*pWav->channels)) + iSample]; + } + pBufferOut += pWav->channels; + } + + framesToRead -= 1; + totalFramesRead += 1; + pWav->readCursorInPCMFrames += 1; + pWav->ima.cachedFrameCount -= 1; + } + + if (framesToRead == 0) { + break; + } + + /* + If there's nothing left in the cache, just go ahead and load more. If there's nothing left to load in the current block we just continue to the next + loop iteration which will trigger the loading of a new block. + */ + if (pWav->ima.cachedFrameCount == 0) { + if (pWav->ima.bytesRemainingInBlock == 0) { + continue; + } else { + /* + From what I can tell with stereo streams, it looks like every 4 bytes (8 samples) is for one channel. So it goes 4 bytes for the + left channel, 4 bytes for the right channel. + */ + pWav->ima.cachedFrameCount = 8; + for (iChannel = 0; iChannel < pWav->channels; ++iChannel) { + drwav_uint32 iByte; + drwav_uint8 nibbles[4]; + if (pWav->onRead(pWav->pUserData, &nibbles, 4) != 4) { + pWav->ima.cachedFrameCount = 0; + return totalFramesRead; + } + pWav->ima.bytesRemainingInBlock -= 4; + + for (iByte = 0; iByte < 4; ++iByte) { + drwav_uint8 nibble0 = ((nibbles[iByte] & 0x0F) >> 0); + drwav_uint8 nibble1 = ((nibbles[iByte] & 0xF0) >> 4); + + drwav_int32 step = stepTable[pWav->ima.stepIndex[iChannel]]; + drwav_int32 predictor = pWav->ima.predictor[iChannel]; + + drwav_int32 diff = step >> 3; + if (nibble0 & 1) diff += step >> 2; + if (nibble0 & 2) diff += step >> 1; + if (nibble0 & 4) diff += step; + if (nibble0 & 8) diff = -diff; + + predictor = drwav_clamp(predictor + diff, -32768, 32767); + pWav->ima.predictor[iChannel] = predictor; + pWav->ima.stepIndex[iChannel] = drwav_clamp(pWav->ima.stepIndex[iChannel] + indexTable[nibble0], 0, (drwav_int32)drwav_countof(stepTable)-1); + pWav->ima.cachedFrames[(drwav_countof(pWav->ima.cachedFrames) - (pWav->ima.cachedFrameCount*pWav->channels)) + (iByte*2+0)*pWav->channels + iChannel] = predictor; + + + step = stepTable[pWav->ima.stepIndex[iChannel]]; + predictor = pWav->ima.predictor[iChannel]; + + diff = step >> 3; + if (nibble1 & 1) diff += step >> 2; + if (nibble1 & 2) diff += step >> 1; + if (nibble1 & 4) diff += step; + if (nibble1 & 8) diff = -diff; + + predictor = drwav_clamp(predictor + diff, -32768, 32767); + pWav->ima.predictor[iChannel] = predictor; + pWav->ima.stepIndex[iChannel] = drwav_clamp(pWav->ima.stepIndex[iChannel] + indexTable[nibble1], 0, (drwav_int32)drwav_countof(stepTable)-1); + pWav->ima.cachedFrames[(drwav_countof(pWav->ima.cachedFrames) - (pWav->ima.cachedFrameCount*pWav->channels)) + (iByte*2+1)*pWav->channels + iChannel] = predictor; + } + } + } + } + } + + return totalFramesRead; +} + + +#ifndef DR_WAV_NO_CONVERSION_API +static unsigned short g_drwavAlawTable[256] = { + 0xEA80, 0xEB80, 0xE880, 0xE980, 0xEE80, 0xEF80, 0xEC80, 0xED80, 0xE280, 0xE380, 0xE080, 0xE180, 0xE680, 0xE780, 0xE480, 0xE580, + 0xF540, 0xF5C0, 0xF440, 0xF4C0, 0xF740, 0xF7C0, 0xF640, 0xF6C0, 0xF140, 0xF1C0, 0xF040, 0xF0C0, 0xF340, 0xF3C0, 0xF240, 0xF2C0, + 0xAA00, 0xAE00, 0xA200, 0xA600, 0xBA00, 0xBE00, 0xB200, 0xB600, 0x8A00, 0x8E00, 0x8200, 0x8600, 0x9A00, 0x9E00, 0x9200, 0x9600, + 0xD500, 0xD700, 0xD100, 0xD300, 0xDD00, 0xDF00, 0xD900, 0xDB00, 0xC500, 0xC700, 0xC100, 0xC300, 0xCD00, 0xCF00, 0xC900, 0xCB00, + 0xFEA8, 0xFEB8, 0xFE88, 0xFE98, 0xFEE8, 0xFEF8, 0xFEC8, 0xFED8, 0xFE28, 0xFE38, 0xFE08, 0xFE18, 0xFE68, 0xFE78, 0xFE48, 0xFE58, + 0xFFA8, 0xFFB8, 0xFF88, 0xFF98, 0xFFE8, 0xFFF8, 0xFFC8, 0xFFD8, 0xFF28, 0xFF38, 0xFF08, 0xFF18, 0xFF68, 0xFF78, 0xFF48, 0xFF58, + 0xFAA0, 0xFAE0, 0xFA20, 0xFA60, 0xFBA0, 0xFBE0, 0xFB20, 0xFB60, 0xF8A0, 0xF8E0, 0xF820, 0xF860, 0xF9A0, 0xF9E0, 0xF920, 0xF960, + 0xFD50, 0xFD70, 0xFD10, 0xFD30, 0xFDD0, 0xFDF0, 0xFD90, 0xFDB0, 0xFC50, 0xFC70, 0xFC10, 0xFC30, 0xFCD0, 0xFCF0, 0xFC90, 0xFCB0, + 0x1580, 0x1480, 0x1780, 0x1680, 0x1180, 0x1080, 0x1380, 0x1280, 0x1D80, 0x1C80, 0x1F80, 0x1E80, 0x1980, 0x1880, 0x1B80, 0x1A80, + 0x0AC0, 0x0A40, 0x0BC0, 0x0B40, 0x08C0, 0x0840, 0x09C0, 0x0940, 0x0EC0, 0x0E40, 0x0FC0, 0x0F40, 0x0CC0, 0x0C40, 0x0DC0, 0x0D40, + 0x5600, 0x5200, 0x5E00, 0x5A00, 0x4600, 0x4200, 0x4E00, 0x4A00, 0x7600, 0x7200, 0x7E00, 0x7A00, 0x6600, 0x6200, 0x6E00, 0x6A00, + 0x2B00, 0x2900, 0x2F00, 0x2D00, 0x2300, 0x2100, 0x2700, 0x2500, 0x3B00, 0x3900, 0x3F00, 0x3D00, 0x3300, 0x3100, 0x3700, 0x3500, + 0x0158, 0x0148, 0x0178, 0x0168, 0x0118, 0x0108, 0x0138, 0x0128, 0x01D8, 0x01C8, 0x01F8, 0x01E8, 0x0198, 0x0188, 0x01B8, 0x01A8, + 0x0058, 0x0048, 0x0078, 0x0068, 0x0018, 0x0008, 0x0038, 0x0028, 0x00D8, 0x00C8, 0x00F8, 0x00E8, 0x0098, 0x0088, 0x00B8, 0x00A8, + 0x0560, 0x0520, 0x05E0, 0x05A0, 0x0460, 0x0420, 0x04E0, 0x04A0, 0x0760, 0x0720, 0x07E0, 0x07A0, 0x0660, 0x0620, 0x06E0, 0x06A0, + 0x02B0, 0x0290, 0x02F0, 0x02D0, 0x0230, 0x0210, 0x0270, 0x0250, 0x03B0, 0x0390, 0x03F0, 0x03D0, 0x0330, 0x0310, 0x0370, 0x0350 +}; + +static unsigned short g_drwavMulawTable[256] = { + 0x8284, 0x8684, 0x8A84, 0x8E84, 0x9284, 0x9684, 0x9A84, 0x9E84, 0xA284, 0xA684, 0xAA84, 0xAE84, 0xB284, 0xB684, 0xBA84, 0xBE84, + 0xC184, 0xC384, 0xC584, 0xC784, 0xC984, 0xCB84, 0xCD84, 0xCF84, 0xD184, 0xD384, 0xD584, 0xD784, 0xD984, 0xDB84, 0xDD84, 0xDF84, + 0xE104, 0xE204, 0xE304, 0xE404, 0xE504, 0xE604, 0xE704, 0xE804, 0xE904, 0xEA04, 0xEB04, 0xEC04, 0xED04, 0xEE04, 0xEF04, 0xF004, + 0xF0C4, 0xF144, 0xF1C4, 0xF244, 0xF2C4, 0xF344, 0xF3C4, 0xF444, 0xF4C4, 0xF544, 0xF5C4, 0xF644, 0xF6C4, 0xF744, 0xF7C4, 0xF844, + 0xF8A4, 0xF8E4, 0xF924, 0xF964, 0xF9A4, 0xF9E4, 0xFA24, 0xFA64, 0xFAA4, 0xFAE4, 0xFB24, 0xFB64, 0xFBA4, 0xFBE4, 0xFC24, 0xFC64, + 0xFC94, 0xFCB4, 0xFCD4, 0xFCF4, 0xFD14, 0xFD34, 0xFD54, 0xFD74, 0xFD94, 0xFDB4, 0xFDD4, 0xFDF4, 0xFE14, 0xFE34, 0xFE54, 0xFE74, + 0xFE8C, 0xFE9C, 0xFEAC, 0xFEBC, 0xFECC, 0xFEDC, 0xFEEC, 0xFEFC, 0xFF0C, 0xFF1C, 0xFF2C, 0xFF3C, 0xFF4C, 0xFF5C, 0xFF6C, 0xFF7C, + 0xFF88, 0xFF90, 0xFF98, 0xFFA0, 0xFFA8, 0xFFB0, 0xFFB8, 0xFFC0, 0xFFC8, 0xFFD0, 0xFFD8, 0xFFE0, 0xFFE8, 0xFFF0, 0xFFF8, 0x0000, + 0x7D7C, 0x797C, 0x757C, 0x717C, 0x6D7C, 0x697C, 0x657C, 0x617C, 0x5D7C, 0x597C, 0x557C, 0x517C, 0x4D7C, 0x497C, 0x457C, 0x417C, + 0x3E7C, 0x3C7C, 0x3A7C, 0x387C, 0x367C, 0x347C, 0x327C, 0x307C, 0x2E7C, 0x2C7C, 0x2A7C, 0x287C, 0x267C, 0x247C, 0x227C, 0x207C, + 0x1EFC, 0x1DFC, 0x1CFC, 0x1BFC, 0x1AFC, 0x19FC, 0x18FC, 0x17FC, 0x16FC, 0x15FC, 0x14FC, 0x13FC, 0x12FC, 0x11FC, 0x10FC, 0x0FFC, + 0x0F3C, 0x0EBC, 0x0E3C, 0x0DBC, 0x0D3C, 0x0CBC, 0x0C3C, 0x0BBC, 0x0B3C, 0x0ABC, 0x0A3C, 0x09BC, 0x093C, 0x08BC, 0x083C, 0x07BC, + 0x075C, 0x071C, 0x06DC, 0x069C, 0x065C, 0x061C, 0x05DC, 0x059C, 0x055C, 0x051C, 0x04DC, 0x049C, 0x045C, 0x041C, 0x03DC, 0x039C, + 0x036C, 0x034C, 0x032C, 0x030C, 0x02EC, 0x02CC, 0x02AC, 0x028C, 0x026C, 0x024C, 0x022C, 0x020C, 0x01EC, 0x01CC, 0x01AC, 0x018C, + 0x0174, 0x0164, 0x0154, 0x0144, 0x0134, 0x0124, 0x0114, 0x0104, 0x00F4, 0x00E4, 0x00D4, 0x00C4, 0x00B4, 0x00A4, 0x0094, 0x0084, + 0x0078, 0x0070, 0x0068, 0x0060, 0x0058, 0x0050, 0x0048, 0x0040, 0x0038, 0x0030, 0x0028, 0x0020, 0x0018, 0x0010, 0x0008, 0x0000 +}; + +static DRWAV_INLINE drwav_int16 drwav__alaw_to_s16(drwav_uint8 sampleIn) +{ + return (short)g_drwavAlawTable[sampleIn]; +} + +static DRWAV_INLINE drwav_int16 drwav__mulaw_to_s16(drwav_uint8 sampleIn) +{ + return (short)g_drwavMulawTable[sampleIn]; +} + + + +DRWAV_PRIVATE void drwav__pcm_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t totalSampleCount, unsigned int bytesPerSample) +{ + size_t i; + + /* Special case for 8-bit sample data because it's treated as unsigned. */ + if (bytesPerSample == 1) { + drwav_u8_to_s16(pOut, pIn, totalSampleCount); + return; + } + + + /* Slightly more optimal implementation for common formats. */ + if (bytesPerSample == 2) { + for (i = 0; i < totalSampleCount; ++i) { + *pOut++ = ((const drwav_int16*)pIn)[i]; + } + return; + } + if (bytesPerSample == 3) { + drwav_s24_to_s16(pOut, pIn, totalSampleCount); + return; + } + if (bytesPerSample == 4) { + drwav_s32_to_s16(pOut, (const drwav_int32*)pIn, totalSampleCount); + return; + } + + + /* Anything more than 64 bits per sample is not supported. */ + if (bytesPerSample > 8) { + DRWAV_ZERO_MEMORY(pOut, totalSampleCount * sizeof(*pOut)); + return; + } + + + /* Generic, slow converter. */ + for (i = 0; i < totalSampleCount; ++i) { + drwav_uint64 sample = 0; + unsigned int shift = (8 - bytesPerSample) * 8; + + unsigned int j; + for (j = 0; j < bytesPerSample; j += 1) { + DRWAV_ASSERT(j < 8); + sample |= (drwav_uint64)(pIn[j]) << shift; + shift += 8; + } + + pIn += j; + *pOut++ = (drwav_int16)((drwav_int64)sample >> 48); + } +} + +DRWAV_PRIVATE void drwav__ieee_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t totalSampleCount, unsigned int bytesPerSample) +{ + if (bytesPerSample == 4) { + drwav_f32_to_s16(pOut, (const float*)pIn, totalSampleCount); + return; + } else if (bytesPerSample == 8) { + drwav_f64_to_s16(pOut, (const double*)pIn, totalSampleCount); + return; + } else { + /* Only supporting 32- and 64-bit float. Output silence in all other cases. Contributions welcome for 16-bit float. */ + DRWAV_ZERO_MEMORY(pOut, totalSampleCount * sizeof(*pOut)); + return; + } +} + +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__pcm(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut) +{ + drwav_uint64 totalFramesRead; + drwav_uint8 sampleData[4096] = {0}; + drwav_uint32 bytesPerFrame; + drwav_uint32 bytesPerSample; + drwav_uint64 samplesRead; + + /* Fast path. */ + if ((pWav->translatedFormatTag == DR_WAVE_FORMAT_PCM && pWav->bitsPerSample == 16) || pBufferOut == NULL) { + return drwav_read_pcm_frames(pWav, framesToRead, pBufferOut); + } + + bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); + if (bytesPerFrame == 0) { + return 0; + } + + bytesPerSample = bytesPerFrame / pWav->channels; + if (bytesPerSample == 0 || (bytesPerFrame % pWav->channels) != 0) { + return 0; /* Only byte-aligned formats are supported. */ + } + + totalFramesRead = 0; + + while (framesToRead > 0) { + drwav_uint64 framesToReadThisIteration = drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame); + drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, framesToReadThisIteration, sampleData); + if (framesRead == 0) { + break; + } + + DRWAV_ASSERT(framesRead <= framesToReadThisIteration); /* If this fails it means there's a bug in drwav_read_pcm_frames(). */ + + /* Validation to ensure we don't read too much from out intermediary buffer. This is to protect from invalid files. */ + samplesRead = framesRead * pWav->channels; + if ((samplesRead * bytesPerSample) > sizeof(sampleData)) { + DRWAV_ASSERT(DRWAV_FALSE); /* This should never happen with a valid file. */ + break; + } + + drwav__pcm_to_s16(pBufferOut, sampleData, (size_t)samplesRead, bytesPerSample); + + pBufferOut += samplesRead; + framesToRead -= framesRead; + totalFramesRead += framesRead; + } + + return totalFramesRead; +} + +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__ieee(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut) +{ + drwav_uint64 totalFramesRead; + drwav_uint8 sampleData[4096] = {0}; + drwav_uint32 bytesPerFrame; + drwav_uint32 bytesPerSample; + drwav_uint64 samplesRead; + + if (pBufferOut == NULL) { + return drwav_read_pcm_frames(pWav, framesToRead, NULL); + } + + bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); + if (bytesPerFrame == 0) { + return 0; + } + + bytesPerSample = bytesPerFrame / pWav->channels; + if (bytesPerSample == 0 || (bytesPerFrame % pWav->channels) != 0) { + return 0; /* Only byte-aligned formats are supported. */ + } + + totalFramesRead = 0; + + while (framesToRead > 0) { + drwav_uint64 framesToReadThisIteration = drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame); + drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, framesToReadThisIteration, sampleData); + if (framesRead == 0) { + break; + } + + DRWAV_ASSERT(framesRead <= framesToReadThisIteration); /* If this fails it means there's a bug in drwav_read_pcm_frames(). */ + + /* Validation to ensure we don't read too much from out intermediary buffer. This is to protect from invalid files. */ + samplesRead = framesRead * pWav->channels; + if ((samplesRead * bytesPerSample) > sizeof(sampleData)) { + DRWAV_ASSERT(DRWAV_FALSE); /* This should never happen with a valid file. */ + break; + } + + drwav__ieee_to_s16(pBufferOut, sampleData, (size_t)samplesRead, bytesPerSample); /* Safe cast. */ + + pBufferOut += samplesRead; + framesToRead -= framesRead; + totalFramesRead += framesRead; + } + + return totalFramesRead; +} + +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__alaw(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut) +{ + drwav_uint64 totalFramesRead; + drwav_uint8 sampleData[4096] = {0}; + drwav_uint32 bytesPerFrame; + drwav_uint32 bytesPerSample; + drwav_uint64 samplesRead; + + if (pBufferOut == NULL) { + return drwav_read_pcm_frames(pWav, framesToRead, NULL); + } + + bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); + if (bytesPerFrame == 0) { + return 0; + } + + bytesPerSample = bytesPerFrame / pWav->channels; + if (bytesPerSample == 0 || (bytesPerFrame % pWav->channels) != 0) { + return 0; /* Only byte-aligned formats are supported. */ + } + + totalFramesRead = 0; + + while (framesToRead > 0) { + drwav_uint64 framesToReadThisIteration = drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame); + drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, framesToReadThisIteration, sampleData); + if (framesRead == 0) { + break; + } + + DRWAV_ASSERT(framesRead <= framesToReadThisIteration); /* If this fails it means there's a bug in drwav_read_pcm_frames(). */ + + /* Validation to ensure we don't read too much from out intermediary buffer. This is to protect from invalid files. */ + samplesRead = framesRead * pWav->channels; + if ((samplesRead * bytesPerSample) > sizeof(sampleData)) { + DRWAV_ASSERT(DRWAV_FALSE); /* This should never happen with a valid file. */ + break; + } + + drwav_alaw_to_s16(pBufferOut, sampleData, (size_t)samplesRead); + + /* + For some reason libsndfile seems to be returning samples of the opposite sign for a-law, but only + with AIFF files. For WAV files it seems to be the same as dr_wav. This is resulting in dr_wav's + automated tests failing. I'm not sure which is correct, but will assume dr_wav. If we're enforcing + libsndfile compatibility we'll swap the signs here. + */ + #ifdef DR_WAV_LIBSNDFILE_COMPAT + { + if (pWav->container == drwav_container_aiff) { + drwav_uint64 iSample; + for (iSample = 0; iSample < samplesRead; iSample += 1) { + pBufferOut[iSample] = -pBufferOut[iSample]; + } + } + } + #endif + + pBufferOut += samplesRead; + framesToRead -= framesRead; + totalFramesRead += framesRead; + } + + return totalFramesRead; +} + +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__mulaw(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut) +{ + drwav_uint64 totalFramesRead; + drwav_uint8 sampleData[4096] = {0}; + drwav_uint32 bytesPerFrame; + drwav_uint32 bytesPerSample; + drwav_uint64 samplesRead; + + if (pBufferOut == NULL) { + return drwav_read_pcm_frames(pWav, framesToRead, NULL); + } + + bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); + if (bytesPerFrame == 0) { + return 0; + } + + bytesPerSample = bytesPerFrame / pWav->channels; + if (bytesPerSample == 0 || (bytesPerFrame % pWav->channels) != 0) { + return 0; /* Only byte-aligned formats are supported. */ + } + + totalFramesRead = 0; + + while (framesToRead > 0) { + drwav_uint64 framesToReadThisIteration = drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame); + drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, framesToReadThisIteration, sampleData); + if (framesRead == 0) { + break; + } + + DRWAV_ASSERT(framesRead <= framesToReadThisIteration); /* If this fails it means there's a bug in drwav_read_pcm_frames(). */ + + /* Validation to ensure we don't read too much from out intermediary buffer. This is to protect from invalid files. */ + samplesRead = framesRead * pWav->channels; + if ((samplesRead * bytesPerSample) > sizeof(sampleData)) { + DRWAV_ASSERT(DRWAV_FALSE); /* This should never happen with a valid file. */ + break; + } + + drwav_mulaw_to_s16(pBufferOut, sampleData, (size_t)samplesRead); + + /* + Just like with alaw, for some reason the signs between libsndfile and dr_wav are opposite. We just need to + swap the sign if we're compiling with libsndfile compatiblity so our automated tests don't fail. + */ + #ifdef DR_WAV_LIBSNDFILE_COMPAT + { + if (pWav->container == drwav_container_aiff) { + drwav_uint64 iSample; + for (iSample = 0; iSample < samplesRead; iSample += 1) { + pBufferOut[iSample] = -pBufferOut[iSample]; + } + } + } + #endif + + pBufferOut += samplesRead; + framesToRead -= framesRead; + totalFramesRead += framesRead; + } + + return totalFramesRead; +} + +DRWAV_API drwav_uint64 drwav_read_pcm_frames_s16(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut) +{ + if (pWav == NULL || framesToRead == 0) { + return 0; + } + + if (pBufferOut == NULL) { + return drwav_read_pcm_frames(pWav, framesToRead, NULL); + } + + /* Don't try to read more samples than can potentially fit in the output buffer. */ + if (framesToRead * pWav->channels * sizeof(drwav_int16) > DRWAV_SIZE_MAX) { + framesToRead = DRWAV_SIZE_MAX / sizeof(drwav_int16) / pWav->channels; + } + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_PCM) { + return drwav_read_pcm_frames_s16__pcm(pWav, framesToRead, pBufferOut); + } + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_IEEE_FLOAT) { + return drwav_read_pcm_frames_s16__ieee(pWav, framesToRead, pBufferOut); + } + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ALAW) { + return drwav_read_pcm_frames_s16__alaw(pWav, framesToRead, pBufferOut); + } + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_MULAW) { + return drwav_read_pcm_frames_s16__mulaw(pWav, framesToRead, pBufferOut); + } + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM) { + return drwav_read_pcm_frames_s16__msadpcm(pWav, framesToRead, pBufferOut); + } + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_DVI_ADPCM) { + return drwav_read_pcm_frames_s16__ima(pWav, framesToRead, pBufferOut); + } + + return 0; +} + +DRWAV_API drwav_uint64 drwav_read_pcm_frames_s16le(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut) +{ + drwav_uint64 framesRead = drwav_read_pcm_frames_s16(pWav, framesToRead, pBufferOut); + if (pBufferOut != NULL && drwav__is_little_endian() == DRWAV_FALSE) { + drwav__bswap_samples_s16(pBufferOut, framesRead*pWav->channels); + } + + return framesRead; +} + +DRWAV_API drwav_uint64 drwav_read_pcm_frames_s16be(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut) +{ + drwav_uint64 framesRead = drwav_read_pcm_frames_s16(pWav, framesToRead, pBufferOut); + if (pBufferOut != NULL && drwav__is_little_endian() == DRWAV_TRUE) { + drwav__bswap_samples_s16(pBufferOut, framesRead*pWav->channels); + } + + return framesRead; +} + + +DRWAV_API void drwav_u8_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t sampleCount) +{ + int r; + size_t i; + for (i = 0; i < sampleCount; ++i) { + int x = pIn[i]; + r = x << 8; + r = r - 32768; + pOut[i] = (short)r; + } +} + +DRWAV_API void drwav_s24_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t sampleCount) +{ + int r; + size_t i; + for (i = 0; i < sampleCount; ++i) { + int x = ((int)(((unsigned int)(((const drwav_uint8*)pIn)[i*3+0]) << 8) | ((unsigned int)(((const drwav_uint8*)pIn)[i*3+1]) << 16) | ((unsigned int)(((const drwav_uint8*)pIn)[i*3+2])) << 24)) >> 8; + r = x >> 8; + pOut[i] = (short)r; + } +} + +DRWAV_API void drwav_s32_to_s16(drwav_int16* pOut, const drwav_int32* pIn, size_t sampleCount) +{ + int r; + size_t i; + for (i = 0; i < sampleCount; ++i) { + int x = pIn[i]; + r = x >> 16; + pOut[i] = (short)r; + } +} + +DRWAV_API void drwav_f32_to_s16(drwav_int16* pOut, const float* pIn, size_t sampleCount) +{ + int r; + size_t i; + for (i = 0; i < sampleCount; ++i) { + float x = pIn[i]; + float c; + c = ((x < -1) ? -1 : ((x > 1) ? 1 : x)); + c = c + 1; + r = (int)(c * 32767.5f); + r = r - 32768; + pOut[i] = (short)r; + } +} + +DRWAV_API void drwav_f64_to_s16(drwav_int16* pOut, const double* pIn, size_t sampleCount) +{ + int r; + size_t i; + for (i = 0; i < sampleCount; ++i) { + double x = pIn[i]; + double c; + c = ((x < -1) ? -1 : ((x > 1) ? 1 : x)); + c = c + 1; + r = (int)(c * 32767.5); + r = r - 32768; + pOut[i] = (short)r; + } +} + +DRWAV_API void drwav_alaw_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t sampleCount) +{ + size_t i; + for (i = 0; i < sampleCount; ++i) { + pOut[i] = drwav__alaw_to_s16(pIn[i]); + } +} + +DRWAV_API void drwav_mulaw_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t sampleCount) +{ + size_t i; + for (i = 0; i < sampleCount; ++i) { + pOut[i] = drwav__mulaw_to_s16(pIn[i]); + } +} + + +DRWAV_PRIVATE void drwav__pcm_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount, unsigned int bytesPerSample) +{ + unsigned int i; + + /* Special case for 8-bit sample data because it's treated as unsigned. */ + if (bytesPerSample == 1) { + drwav_u8_to_f32(pOut, pIn, sampleCount); + return; + } + + /* Slightly more optimal implementation for common formats. */ + if (bytesPerSample == 2) { + drwav_s16_to_f32(pOut, (const drwav_int16*)pIn, sampleCount); + return; + } + if (bytesPerSample == 3) { + drwav_s24_to_f32(pOut, pIn, sampleCount); + return; + } + if (bytesPerSample == 4) { + drwav_s32_to_f32(pOut, (const drwav_int32*)pIn, sampleCount); + return; + } + + + /* Anything more than 64 bits per sample is not supported. */ + if (bytesPerSample > 8) { + DRWAV_ZERO_MEMORY(pOut, sampleCount * sizeof(*pOut)); + return; + } + + + /* Generic, slow converter. */ + for (i = 0; i < sampleCount; ++i) { + drwav_uint64 sample = 0; + unsigned int shift = (8 - bytesPerSample) * 8; + + unsigned int j; + for (j = 0; j < bytesPerSample; j += 1) { + DRWAV_ASSERT(j < 8); + sample |= (drwav_uint64)(pIn[j]) << shift; + shift += 8; + } + + pIn += j; + *pOut++ = (float)((drwav_int64)sample / 9223372036854775807.0); + } +} + +DRWAV_PRIVATE void drwav__ieee_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount, unsigned int bytesPerSample) +{ + if (bytesPerSample == 4) { + unsigned int i; + for (i = 0; i < sampleCount; ++i) { + *pOut++ = ((const float*)pIn)[i]; + } + return; + } else if (bytesPerSample == 8) { + drwav_f64_to_f32(pOut, (const double*)pIn, sampleCount); + return; + } else { + /* Only supporting 32- and 64-bit float. Output silence in all other cases. Contributions welcome for 16-bit float. */ + DRWAV_ZERO_MEMORY(pOut, sampleCount * sizeof(*pOut)); + return; + } +} + + +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_f32__pcm(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut) +{ + drwav_uint64 totalFramesRead; + drwav_uint8 sampleData[4096] = {0}; + drwav_uint32 bytesPerFrame; + drwav_uint32 bytesPerSample; + drwav_uint64 samplesRead; + + bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); + if (bytesPerFrame == 0) { + return 0; + } + + bytesPerSample = bytesPerFrame / pWav->channels; + if (bytesPerSample == 0 || (bytesPerFrame % pWav->channels) != 0) { + return 0; /* Only byte-aligned formats are supported. */ + } + + totalFramesRead = 0; + + while (framesToRead > 0) { + drwav_uint64 framesToReadThisIteration = drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame); + drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, framesToReadThisIteration, sampleData); + if (framesRead == 0) { + break; + } + + DRWAV_ASSERT(framesRead <= framesToReadThisIteration); /* If this fails it means there's a bug in drwav_read_pcm_frames(). */ + + /* Validation to ensure we don't read too much from out intermediary buffer. This is to protect from invalid files. */ + samplesRead = framesRead * pWav->channels; + if ((samplesRead * bytesPerSample) > sizeof(sampleData)) { + DRWAV_ASSERT(DRWAV_FALSE); /* This should never happen with a valid file. */ + break; + } + + drwav__pcm_to_f32(pBufferOut, sampleData, (size_t)samplesRead, bytesPerSample); + + pBufferOut += samplesRead; + framesToRead -= framesRead; + totalFramesRead += framesRead; + } + + return totalFramesRead; +} + +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_f32__msadpcm_ima(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut) +{ + /* + We're just going to borrow the implementation from the drwav_read_s16() since ADPCM is a little bit more complicated than other formats and I don't + want to duplicate that code. + */ + drwav_uint64 totalFramesRead; + drwav_int16 samples16[2048]; + + totalFramesRead = 0; + + while (framesToRead > 0) { + drwav_uint64 framesToReadThisIteration = drwav_min(framesToRead, drwav_countof(samples16)/pWav->channels); + drwav_uint64 framesRead = drwav_read_pcm_frames_s16(pWav, framesToReadThisIteration, samples16); + if (framesRead == 0) { + break; + } + + DRWAV_ASSERT(framesRead <= framesToReadThisIteration); /* If this fails it means there's a bug in drwav_read_pcm_frames(). */ + + drwav_s16_to_f32(pBufferOut, samples16, (size_t)(framesRead*pWav->channels)); /* <-- Safe cast because we're clamping to 2048. */ + + pBufferOut += framesRead*pWav->channels; + framesToRead -= framesRead; + totalFramesRead += framesRead; + } + + return totalFramesRead; +} + +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_f32__ieee(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut) +{ + drwav_uint64 totalFramesRead; + drwav_uint8 sampleData[4096] = {0}; + drwav_uint32 bytesPerFrame; + drwav_uint32 bytesPerSample; + drwav_uint64 samplesRead; + + /* Fast path. */ + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_IEEE_FLOAT && pWav->bitsPerSample == 32) { + return drwav_read_pcm_frames(pWav, framesToRead, pBufferOut); + } + + bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); + if (bytesPerFrame == 0) { + return 0; + } + + bytesPerSample = bytesPerFrame / pWav->channels; + if (bytesPerSample == 0 || (bytesPerFrame % pWav->channels) != 0) { + return 0; /* Only byte-aligned formats are supported. */ + } + + totalFramesRead = 0; + + while (framesToRead > 0) { + drwav_uint64 framesToReadThisIteration = drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame); + drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, framesToReadThisIteration, sampleData); + if (framesRead == 0) { + break; + } + + DRWAV_ASSERT(framesRead <= framesToReadThisIteration); /* If this fails it means there's a bug in drwav_read_pcm_frames(). */ + + /* Validation to ensure we don't read too much from out intermediary buffer. This is to protect from invalid files. */ + samplesRead = framesRead * pWav->channels; + if ((samplesRead * bytesPerSample) > sizeof(sampleData)) { + DRWAV_ASSERT(DRWAV_FALSE); /* This should never happen with a valid file. */ + break; + } + + drwav__ieee_to_f32(pBufferOut, sampleData, (size_t)samplesRead, bytesPerSample); + + pBufferOut += samplesRead; + framesToRead -= framesRead; + totalFramesRead += framesRead; + } + + return totalFramesRead; +} + +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_f32__alaw(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut) +{ + drwav_uint64 totalFramesRead; + drwav_uint8 sampleData[4096] = {0}; + drwav_uint32 bytesPerFrame; + drwav_uint32 bytesPerSample; + drwav_uint64 samplesRead; + + bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); + if (bytesPerFrame == 0) { + return 0; + } + + bytesPerSample = bytesPerFrame / pWav->channels; + if (bytesPerSample == 0 || (bytesPerFrame % pWav->channels) != 0) { + return 0; /* Only byte-aligned formats are supported. */ + } + + totalFramesRead = 0; + + while (framesToRead > 0) { + drwav_uint64 framesToReadThisIteration = drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame); + drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, framesToReadThisIteration, sampleData); + if (framesRead == 0) { + break; + } + + DRWAV_ASSERT(framesRead <= framesToReadThisIteration); /* If this fails it means there's a bug in drwav_read_pcm_frames(). */ + + /* Validation to ensure we don't read too much from out intermediary buffer. This is to protect from invalid files. */ + samplesRead = framesRead * pWav->channels; + if ((samplesRead * bytesPerSample) > sizeof(sampleData)) { + DRWAV_ASSERT(DRWAV_FALSE); /* This should never happen with a valid file. */ + break; + } + + drwav_alaw_to_f32(pBufferOut, sampleData, (size_t)samplesRead); + + #ifdef DR_WAV_LIBSNDFILE_COMPAT + { + if (pWav->container == drwav_container_aiff) { + drwav_uint64 iSample; + for (iSample = 0; iSample < samplesRead; iSample += 1) { + pBufferOut[iSample] = -pBufferOut[iSample]; + } + } + } + #endif + + pBufferOut += samplesRead; + framesToRead -= framesRead; + totalFramesRead += framesRead; + } + + return totalFramesRead; +} + +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_f32__mulaw(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut) +{ + drwav_uint64 totalFramesRead; + drwav_uint8 sampleData[4096] = {0}; + drwav_uint32 bytesPerFrame; + drwav_uint32 bytesPerSample; + drwav_uint64 samplesRead; + + bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); + if (bytesPerFrame == 0) { + return 0; + } + + bytesPerSample = bytesPerFrame / pWav->channels; + if (bytesPerSample == 0 || (bytesPerFrame % pWav->channels) != 0) { + return 0; /* Only byte-aligned formats are supported. */ + } + + totalFramesRead = 0; + + while (framesToRead > 0) { + drwav_uint64 framesToReadThisIteration = drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame); + drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, framesToReadThisIteration, sampleData); + if (framesRead == 0) { + break; + } + + DRWAV_ASSERT(framesRead <= framesToReadThisIteration); /* If this fails it means there's a bug in drwav_read_pcm_frames(). */ + + /* Validation to ensure we don't read too much from out intermediary buffer. This is to protect from invalid files. */ + samplesRead = framesRead * pWav->channels; + if ((samplesRead * bytesPerSample) > sizeof(sampleData)) { + DRWAV_ASSERT(DRWAV_FALSE); /* This should never happen with a valid file. */ + break; + } + + drwav_mulaw_to_f32(pBufferOut, sampleData, (size_t)samplesRead); + + #ifdef DR_WAV_LIBSNDFILE_COMPAT + { + if (pWav->container == drwav_container_aiff) { + drwav_uint64 iSample; + for (iSample = 0; iSample < samplesRead; iSample += 1) { + pBufferOut[iSample] = -pBufferOut[iSample]; + } + } + } + #endif + + pBufferOut += samplesRead; + framesToRead -= framesRead; + totalFramesRead += framesRead; + } + + return totalFramesRead; +} + +DRWAV_API drwav_uint64 drwav_read_pcm_frames_f32(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut) +{ + if (pWav == NULL || framesToRead == 0) { + return 0; + } + + if (pBufferOut == NULL) { + return drwav_read_pcm_frames(pWav, framesToRead, NULL); + } + + /* Don't try to read more samples than can potentially fit in the output buffer. */ + if (framesToRead * pWav->channels * sizeof(float) > DRWAV_SIZE_MAX) { + framesToRead = DRWAV_SIZE_MAX / sizeof(float) / pWav->channels; + } + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_PCM) { + return drwav_read_pcm_frames_f32__pcm(pWav, framesToRead, pBufferOut); + } + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM || pWav->translatedFormatTag == DR_WAVE_FORMAT_DVI_ADPCM) { + return drwav_read_pcm_frames_f32__msadpcm_ima(pWav, framesToRead, pBufferOut); + } + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_IEEE_FLOAT) { + return drwav_read_pcm_frames_f32__ieee(pWav, framesToRead, pBufferOut); + } + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ALAW) { + return drwav_read_pcm_frames_f32__alaw(pWav, framesToRead, pBufferOut); + } + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_MULAW) { + return drwav_read_pcm_frames_f32__mulaw(pWav, framesToRead, pBufferOut); + } + + return 0; +} + +DRWAV_API drwav_uint64 drwav_read_pcm_frames_f32le(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut) +{ + drwav_uint64 framesRead = drwav_read_pcm_frames_f32(pWav, framesToRead, pBufferOut); + if (pBufferOut != NULL && drwav__is_little_endian() == DRWAV_FALSE) { + drwav__bswap_samples_f32(pBufferOut, framesRead*pWav->channels); + } + + return framesRead; +} + +DRWAV_API drwav_uint64 drwav_read_pcm_frames_f32be(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut) +{ + drwav_uint64 framesRead = drwav_read_pcm_frames_f32(pWav, framesToRead, pBufferOut); + if (pBufferOut != NULL && drwav__is_little_endian() == DRWAV_TRUE) { + drwav__bswap_samples_f32(pBufferOut, framesRead*pWav->channels); + } + + return framesRead; +} + + +DRWAV_API void drwav_u8_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount) +{ + size_t i; + + if (pOut == NULL || pIn == NULL) { + return; + } + +#ifdef DR_WAV_LIBSNDFILE_COMPAT + /* + It appears libsndfile uses slightly different logic for the u8 -> f32 conversion to dr_wav, which in my opinion is incorrect. It appears + libsndfile performs the conversion something like "f32 = (u8 / 256) * 2 - 1", however I think it should be "f32 = (u8 / 255) * 2 - 1" (note + the divisor of 256 vs 255). I use libsndfile as a benchmark for testing, so I'm therefore leaving this block here just for my automated + correctness testing. This is disabled by default. + */ + for (i = 0; i < sampleCount; ++i) { + *pOut++ = (pIn[i] / 256.0f) * 2 - 1; + } +#else + for (i = 0; i < sampleCount; ++i) { + float x = pIn[i]; + x = x * 0.00784313725490196078f; /* 0..255 to 0..2 */ + x = x - 1; /* 0..2 to -1..1 */ + + *pOut++ = x; + } +#endif +} + +DRWAV_API void drwav_s16_to_f32(float* pOut, const drwav_int16* pIn, size_t sampleCount) +{ + size_t i; + + if (pOut == NULL || pIn == NULL) { + return; + } + + for (i = 0; i < sampleCount; ++i) { + *pOut++ = pIn[i] * 0.000030517578125f; + } +} + +DRWAV_API void drwav_s24_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount) +{ + size_t i; + + if (pOut == NULL || pIn == NULL) { + return; + } + + for (i = 0; i < sampleCount; ++i) { + double x; + drwav_uint32 a = ((drwav_uint32)(pIn[i*3+0]) << 8); + drwav_uint32 b = ((drwav_uint32)(pIn[i*3+1]) << 16); + drwav_uint32 c = ((drwav_uint32)(pIn[i*3+2]) << 24); + + x = (double)((drwav_int32)(a | b | c) >> 8); + *pOut++ = (float)(x * 0.00000011920928955078125); + } +} + +DRWAV_API void drwav_s32_to_f32(float* pOut, const drwav_int32* pIn, size_t sampleCount) +{ + size_t i; + if (pOut == NULL || pIn == NULL) { + return; + } + + for (i = 0; i < sampleCount; ++i) { + *pOut++ = (float)(pIn[i] / 2147483648.0); + } +} + +DRWAV_API void drwav_f64_to_f32(float* pOut, const double* pIn, size_t sampleCount) +{ + size_t i; + + if (pOut == NULL || pIn == NULL) { + return; + } + + for (i = 0; i < sampleCount; ++i) { + *pOut++ = (float)pIn[i]; + } +} + +DRWAV_API void drwav_alaw_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount) +{ + size_t i; + + if (pOut == NULL || pIn == NULL) { + return; + } + + for (i = 0; i < sampleCount; ++i) { + *pOut++ = drwav__alaw_to_s16(pIn[i]) / 32768.0f; + } +} + +DRWAV_API void drwav_mulaw_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount) +{ + size_t i; + + if (pOut == NULL || pIn == NULL) { + return; + } + + for (i = 0; i < sampleCount; ++i) { + *pOut++ = drwav__mulaw_to_s16(pIn[i]) / 32768.0f; + } +} + + + +DRWAV_PRIVATE void drwav__pcm_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t totalSampleCount, unsigned int bytesPerSample) +{ + unsigned int i; + + /* Special case for 8-bit sample data because it's treated as unsigned. */ + if (bytesPerSample == 1) { + drwav_u8_to_s32(pOut, pIn, totalSampleCount); + return; + } + + /* Slightly more optimal implementation for common formats. */ + if (bytesPerSample == 2) { + drwav_s16_to_s32(pOut, (const drwav_int16*)pIn, totalSampleCount); + return; + } + if (bytesPerSample == 3) { + drwav_s24_to_s32(pOut, pIn, totalSampleCount); + return; + } + if (bytesPerSample == 4) { + for (i = 0; i < totalSampleCount; ++i) { + *pOut++ = ((const drwav_int32*)pIn)[i]; + } + return; + } + + + /* Anything more than 64 bits per sample is not supported. */ + if (bytesPerSample > 8) { + DRWAV_ZERO_MEMORY(pOut, totalSampleCount * sizeof(*pOut)); + return; + } + + + /* Generic, slow converter. */ + for (i = 0; i < totalSampleCount; ++i) { + drwav_uint64 sample = 0; + unsigned int shift = (8 - bytesPerSample) * 8; + + unsigned int j; + for (j = 0; j < bytesPerSample; j += 1) { + DRWAV_ASSERT(j < 8); + sample |= (drwav_uint64)(pIn[j]) << shift; + shift += 8; + } + + pIn += j; + *pOut++ = (drwav_int32)((drwav_int64)sample >> 32); + } +} + +DRWAV_PRIVATE void drwav__ieee_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t totalSampleCount, unsigned int bytesPerSample) +{ + if (bytesPerSample == 4) { + drwav_f32_to_s32(pOut, (const float*)pIn, totalSampleCount); + return; + } else if (bytesPerSample == 8) { + drwav_f64_to_s32(pOut, (const double*)pIn, totalSampleCount); + return; + } else { + /* Only supporting 32- and 64-bit float. Output silence in all other cases. Contributions welcome for 16-bit float. */ + DRWAV_ZERO_MEMORY(pOut, totalSampleCount * sizeof(*pOut)); + return; + } +} + + +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s32__pcm(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut) +{ + drwav_uint64 totalFramesRead; + drwav_uint8 sampleData[4096] = {0}; + drwav_uint32 bytesPerFrame; + drwav_uint32 bytesPerSample; + drwav_uint64 samplesRead; + + /* Fast path. */ + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_PCM && pWav->bitsPerSample == 32) { + return drwav_read_pcm_frames(pWav, framesToRead, pBufferOut); + } + + bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); + if (bytesPerFrame == 0) { + return 0; + } + + bytesPerSample = bytesPerFrame / pWav->channels; + if (bytesPerSample == 0 || (bytesPerFrame % pWav->channels) != 0) { + return 0; /* Only byte-aligned formats are supported. */ + } + + totalFramesRead = 0; + + while (framesToRead > 0) { + drwav_uint64 framesToReadThisIteration = drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame); + drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, framesToReadThisIteration, sampleData); + if (framesRead == 0) { + break; + } + + DRWAV_ASSERT(framesRead <= framesToReadThisIteration); /* If this fails it means there's a bug in drwav_read_pcm_frames(). */ + + /* Validation to ensure we don't read too much from out intermediary buffer. This is to protect from invalid files. */ + samplesRead = framesRead * pWav->channels; + if ((samplesRead * bytesPerSample) > sizeof(sampleData)) { + DRWAV_ASSERT(DRWAV_FALSE); /* This should never happen with a valid file. */ + break; + } + + drwav__pcm_to_s32(pBufferOut, sampleData, (size_t)samplesRead, bytesPerSample); + + pBufferOut += samplesRead; + framesToRead -= framesRead; + totalFramesRead += framesRead; + } + + return totalFramesRead; +} + +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s32__msadpcm_ima(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut) +{ + /* + We're just going to borrow the implementation from the drwav_read_s16() since ADPCM is a little bit more complicated than other formats and I don't + want to duplicate that code. + */ + drwav_uint64 totalFramesRead = 0; + drwav_int16 samples16[2048]; + + while (framesToRead > 0) { + drwav_uint64 framesToReadThisIteration = drwav_min(framesToRead, drwav_countof(samples16)/pWav->channels); + drwav_uint64 framesRead = drwav_read_pcm_frames_s16(pWav, framesToReadThisIteration, samples16); + if (framesRead == 0) { + break; + } + + DRWAV_ASSERT(framesRead <= framesToReadThisIteration); /* If this fails it means there's a bug in drwav_read_pcm_frames(). */ + + drwav_s16_to_s32(pBufferOut, samples16, (size_t)(framesRead*pWav->channels)); /* <-- Safe cast because we're clamping to 2048. */ + + pBufferOut += framesRead*pWav->channels; + framesToRead -= framesRead; + totalFramesRead += framesRead; + } + + return totalFramesRead; +} + +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s32__ieee(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut) +{ + drwav_uint64 totalFramesRead; + drwav_uint8 sampleData[4096] = {0}; + drwav_uint32 bytesPerFrame; + drwav_uint32 bytesPerSample; + drwav_uint64 samplesRead; + + bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); + if (bytesPerFrame == 0) { + return 0; + } + + bytesPerSample = bytesPerFrame / pWav->channels; + if (bytesPerSample == 0 || (bytesPerFrame % pWav->channels) != 0) { + return 0; /* Only byte-aligned formats are supported. */ + } + + totalFramesRead = 0; + + while (framesToRead > 0) { + drwav_uint64 framesToReadThisIteration = drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame); + drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, framesToReadThisIteration, sampleData); + if (framesRead == 0) { + break; + } + + DRWAV_ASSERT(framesRead <= framesToReadThisIteration); /* If this fails it means there's a bug in drwav_read_pcm_frames(). */ + + /* Validation to ensure we don't read too much from out intermediary buffer. This is to protect from invalid files. */ + samplesRead = framesRead * pWav->channels; + if ((samplesRead * bytesPerSample) > sizeof(sampleData)) { + DRWAV_ASSERT(DRWAV_FALSE); /* This should never happen with a valid file. */ + break; + } + + drwav__ieee_to_s32(pBufferOut, sampleData, (size_t)samplesRead, bytesPerSample); + + pBufferOut += samplesRead; + framesToRead -= framesRead; + totalFramesRead += framesRead; + } + + return totalFramesRead; +} + +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s32__alaw(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut) +{ + drwav_uint64 totalFramesRead; + drwav_uint8 sampleData[4096] = {0}; + drwav_uint32 bytesPerFrame; + drwav_uint32 bytesPerSample; + drwav_uint64 samplesRead; + + bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); + if (bytesPerFrame == 0) { + return 0; + } + + bytesPerSample = bytesPerFrame / pWav->channels; + if (bytesPerSample == 0 || (bytesPerFrame % pWav->channels) != 0) { + return 0; /* Only byte-aligned formats are supported. */ + } + + totalFramesRead = 0; + + while (framesToRead > 0) { + drwav_uint64 framesToReadThisIteration = drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame); + drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, framesToReadThisIteration, sampleData); + if (framesRead == 0) { + break; + } + + DRWAV_ASSERT(framesRead <= framesToReadThisIteration); /* If this fails it means there's a bug in drwav_read_pcm_frames(). */ + + /* Validation to ensure we don't read too much from out intermediary buffer. This is to protect from invalid files. */ + samplesRead = framesRead * pWav->channels; + if ((samplesRead * bytesPerSample) > sizeof(sampleData)) { + DRWAV_ASSERT(DRWAV_FALSE); /* This should never happen with a valid file. */ + break; + } + + drwav_alaw_to_s32(pBufferOut, sampleData, (size_t)samplesRead); + + #ifdef DR_WAV_LIBSNDFILE_COMPAT + { + if (pWav->container == drwav_container_aiff) { + drwav_uint64 iSample; + for (iSample = 0; iSample < samplesRead; iSample += 1) { + pBufferOut[iSample] = -pBufferOut[iSample]; + } + } + } + #endif + + pBufferOut += samplesRead; + framesToRead -= framesRead; + totalFramesRead += framesRead; + } + + return totalFramesRead; +} + +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s32__mulaw(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut) +{ + drwav_uint64 totalFramesRead; + drwav_uint8 sampleData[4096] = {0}; + drwav_uint32 bytesPerFrame; + drwav_uint32 bytesPerSample; + drwav_uint64 samplesRead; + + bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); + if (bytesPerFrame == 0) { + return 0; + } + + bytesPerSample = bytesPerFrame / pWav->channels; + if (bytesPerSample == 0 || (bytesPerFrame % pWav->channels) != 0) { + return 0; /* Only byte-aligned formats are supported. */ + } + + totalFramesRead = 0; + + while (framesToRead > 0) { + drwav_uint64 framesToReadThisIteration = drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame); + drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, framesToReadThisIteration, sampleData); + if (framesRead == 0) { + break; + } + + DRWAV_ASSERT(framesRead <= framesToReadThisIteration); /* If this fails it means there's a bug in drwav_read_pcm_frames(). */ + + /* Validation to ensure we don't read too much from out intermediary buffer. This is to protect from invalid files. */ + samplesRead = framesRead * pWav->channels; + if ((samplesRead * bytesPerSample) > sizeof(sampleData)) { + DRWAV_ASSERT(DRWAV_FALSE); /* This should never happen with a valid file. */ + break; + } + + drwav_mulaw_to_s32(pBufferOut, sampleData, (size_t)samplesRead); + + #ifdef DR_WAV_LIBSNDFILE_COMPAT + { + if (pWav->container == drwav_container_aiff) { + drwav_uint64 iSample; + for (iSample = 0; iSample < samplesRead; iSample += 1) { + pBufferOut[iSample] = -pBufferOut[iSample]; + } + } + } + #endif + + pBufferOut += samplesRead; + framesToRead -= framesRead; + totalFramesRead += framesRead; + } + + return totalFramesRead; +} + +DRWAV_API drwav_uint64 drwav_read_pcm_frames_s32(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut) +{ + if (pWav == NULL || framesToRead == 0) { + return 0; + } + + if (pBufferOut == NULL) { + return drwav_read_pcm_frames(pWav, framesToRead, NULL); + } + + /* Don't try to read more samples than can potentially fit in the output buffer. */ + if (framesToRead * pWav->channels * sizeof(drwav_int32) > DRWAV_SIZE_MAX) { + framesToRead = DRWAV_SIZE_MAX / sizeof(drwav_int32) / pWav->channels; + } + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_PCM) { + return drwav_read_pcm_frames_s32__pcm(pWav, framesToRead, pBufferOut); + } + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM || pWav->translatedFormatTag == DR_WAVE_FORMAT_DVI_ADPCM) { + return drwav_read_pcm_frames_s32__msadpcm_ima(pWav, framesToRead, pBufferOut); + } + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_IEEE_FLOAT) { + return drwav_read_pcm_frames_s32__ieee(pWav, framesToRead, pBufferOut); + } + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ALAW) { + return drwav_read_pcm_frames_s32__alaw(pWav, framesToRead, pBufferOut); + } + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_MULAW) { + return drwav_read_pcm_frames_s32__mulaw(pWav, framesToRead, pBufferOut); + } + + return 0; +} + +DRWAV_API drwav_uint64 drwav_read_pcm_frames_s32le(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut) +{ + drwav_uint64 framesRead = drwav_read_pcm_frames_s32(pWav, framesToRead, pBufferOut); + if (pBufferOut != NULL && drwav__is_little_endian() == DRWAV_FALSE) { + drwav__bswap_samples_s32(pBufferOut, framesRead*pWav->channels); + } + + return framesRead; +} + +DRWAV_API drwav_uint64 drwav_read_pcm_frames_s32be(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut) +{ + drwav_uint64 framesRead = drwav_read_pcm_frames_s32(pWav, framesToRead, pBufferOut); + if (pBufferOut != NULL && drwav__is_little_endian() == DRWAV_TRUE) { + drwav__bswap_samples_s32(pBufferOut, framesRead*pWav->channels); + } + + return framesRead; +} + + +DRWAV_API void drwav_u8_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t sampleCount) +{ + size_t i; + + if (pOut == NULL || pIn == NULL) { + return; + } + + for (i = 0; i < sampleCount; ++i) { + *pOut++ = ((int)pIn[i] - 128) << 24; + } +} + +DRWAV_API void drwav_s16_to_s32(drwav_int32* pOut, const drwav_int16* pIn, size_t sampleCount) +{ + size_t i; + + if (pOut == NULL || pIn == NULL) { + return; + } + + for (i = 0; i < sampleCount; ++i) { + *pOut++ = pIn[i] << 16; + } +} + +DRWAV_API void drwav_s24_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t sampleCount) +{ + size_t i; + + if (pOut == NULL || pIn == NULL) { + return; + } + + for (i = 0; i < sampleCount; ++i) { + unsigned int s0 = pIn[i*3 + 0]; + unsigned int s1 = pIn[i*3 + 1]; + unsigned int s2 = pIn[i*3 + 2]; + + drwav_int32 sample32 = (drwav_int32)((s0 << 8) | (s1 << 16) | (s2 << 24)); + *pOut++ = sample32; + } +} + +DRWAV_API void drwav_f32_to_s32(drwav_int32* pOut, const float* pIn, size_t sampleCount) +{ + size_t i; + + if (pOut == NULL || pIn == NULL) { + return; + } + + for (i = 0; i < sampleCount; ++i) { + *pOut++ = (drwav_int32)(2147483648.0f * pIn[i]); + } +} + +DRWAV_API void drwav_f64_to_s32(drwav_int32* pOut, const double* pIn, size_t sampleCount) +{ + size_t i; + + if (pOut == NULL || pIn == NULL) { + return; + } + + for (i = 0; i < sampleCount; ++i) { + *pOut++ = (drwav_int32)(2147483648.0 * pIn[i]); + } +} + +DRWAV_API void drwav_alaw_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t sampleCount) +{ + size_t i; + + if (pOut == NULL || pIn == NULL) { + return; + } + + for (i = 0; i < sampleCount; ++i) { + *pOut++ = ((drwav_int32)drwav__alaw_to_s16(pIn[i])) << 16; + } +} + +DRWAV_API void drwav_mulaw_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t sampleCount) +{ + size_t i; + + if (pOut == NULL || pIn == NULL) { + return; + } + + for (i= 0; i < sampleCount; ++i) { + *pOut++ = ((drwav_int32)drwav__mulaw_to_s16(pIn[i])) << 16; + } +} + + + +DRWAV_PRIVATE drwav_int16* drwav__read_pcm_frames_and_close_s16(drwav* pWav, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalFrameCount) +{ + drwav_uint64 sampleDataSize; + drwav_int16* pSampleData; + drwav_uint64 framesRead; + + DRWAV_ASSERT(pWav != NULL); + + sampleDataSize = pWav->totalPCMFrameCount * pWav->channels * sizeof(drwav_int16); + if (sampleDataSize > DRWAV_SIZE_MAX) { + drwav_uninit(pWav); + return NULL; /* File's too big. */ + } + + pSampleData = (drwav_int16*)drwav__malloc_from_callbacks((size_t)sampleDataSize, &pWav->allocationCallbacks); /* <-- Safe cast due to the check above. */ + if (pSampleData == NULL) { + drwav_uninit(pWav); + return NULL; /* Failed to allocate memory. */ + } + + framesRead = drwav_read_pcm_frames_s16(pWav, (size_t)pWav->totalPCMFrameCount, pSampleData); + if (framesRead != pWav->totalPCMFrameCount) { + drwav__free_from_callbacks(pSampleData, &pWav->allocationCallbacks); + drwav_uninit(pWav); + return NULL; /* There was an error reading the samples. */ + } + + drwav_uninit(pWav); + + if (sampleRate) { + *sampleRate = pWav->sampleRate; + } + if (channels) { + *channels = pWav->channels; + } + if (totalFrameCount) { + *totalFrameCount = pWav->totalPCMFrameCount; + } + + return pSampleData; +} + +DRWAV_PRIVATE float* drwav__read_pcm_frames_and_close_f32(drwav* pWav, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalFrameCount) +{ + drwav_uint64 sampleDataSize; + float* pSampleData; + drwav_uint64 framesRead; + + DRWAV_ASSERT(pWav != NULL); + + sampleDataSize = pWav->totalPCMFrameCount * pWav->channels * sizeof(float); + if (sampleDataSize > DRWAV_SIZE_MAX) { + drwav_uninit(pWav); + return NULL; /* File's too big. */ + } + + pSampleData = (float*)drwav__malloc_from_callbacks((size_t)sampleDataSize, &pWav->allocationCallbacks); /* <-- Safe cast due to the check above. */ + if (pSampleData == NULL) { + drwav_uninit(pWav); + return NULL; /* Failed to allocate memory. */ + } + + framesRead = drwav_read_pcm_frames_f32(pWav, (size_t)pWav->totalPCMFrameCount, pSampleData); + if (framesRead != pWav->totalPCMFrameCount) { + drwav__free_from_callbacks(pSampleData, &pWav->allocationCallbacks); + drwav_uninit(pWav); + return NULL; /* There was an error reading the samples. */ + } + + drwav_uninit(pWav); + + if (sampleRate) { + *sampleRate = pWav->sampleRate; + } + if (channels) { + *channels = pWav->channels; + } + if (totalFrameCount) { + *totalFrameCount = pWav->totalPCMFrameCount; + } + + return pSampleData; +} + +DRWAV_PRIVATE drwav_int32* drwav__read_pcm_frames_and_close_s32(drwav* pWav, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalFrameCount) +{ + drwav_uint64 sampleDataSize; + drwav_int32* pSampleData; + drwav_uint64 framesRead; + + DRWAV_ASSERT(pWav != NULL); + + sampleDataSize = pWav->totalPCMFrameCount * pWav->channels * sizeof(drwav_int32); + if (sampleDataSize > DRWAV_SIZE_MAX) { + drwav_uninit(pWav); + return NULL; /* File's too big. */ + } + + pSampleData = (drwav_int32*)drwav__malloc_from_callbacks((size_t)sampleDataSize, &pWav->allocationCallbacks); /* <-- Safe cast due to the check above. */ + if (pSampleData == NULL) { + drwav_uninit(pWav); + return NULL; /* Failed to allocate memory. */ + } + + framesRead = drwav_read_pcm_frames_s32(pWav, (size_t)pWav->totalPCMFrameCount, pSampleData); + if (framesRead != pWav->totalPCMFrameCount) { + drwav__free_from_callbacks(pSampleData, &pWav->allocationCallbacks); + drwav_uninit(pWav); + return NULL; /* There was an error reading the samples. */ + } + + drwav_uninit(pWav); + + if (sampleRate) { + *sampleRate = pWav->sampleRate; + } + if (channels) { + *channels = pWav->channels; + } + if (totalFrameCount) { + *totalFrameCount = pWav->totalPCMFrameCount; + } + + return pSampleData; +} + + + +DRWAV_API drwav_int16* drwav_open_and_read_pcm_frames_s16(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + drwav wav; + + if (channelsOut) { + *channelsOut = 0; + } + if (sampleRateOut) { + *sampleRateOut = 0; + } + if (totalFrameCountOut) { + *totalFrameCountOut = 0; + } + + if (!drwav_init(&wav, onRead, onSeek, pUserData, pAllocationCallbacks)) { + return NULL; + } + + return drwav__read_pcm_frames_and_close_s16(&wav, channelsOut, sampleRateOut, totalFrameCountOut); +} + +DRWAV_API float* drwav_open_and_read_pcm_frames_f32(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + drwav wav; + + if (channelsOut) { + *channelsOut = 0; + } + if (sampleRateOut) { + *sampleRateOut = 0; + } + if (totalFrameCountOut) { + *totalFrameCountOut = 0; + } + + if (!drwav_init(&wav, onRead, onSeek, pUserData, pAllocationCallbacks)) { + return NULL; + } + + return drwav__read_pcm_frames_and_close_f32(&wav, channelsOut, sampleRateOut, totalFrameCountOut); +} + +DRWAV_API drwav_int32* drwav_open_and_read_pcm_frames_s32(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + drwav wav; + + if (channelsOut) { + *channelsOut = 0; + } + if (sampleRateOut) { + *sampleRateOut = 0; + } + if (totalFrameCountOut) { + *totalFrameCountOut = 0; + } + + if (!drwav_init(&wav, onRead, onSeek, pUserData, pAllocationCallbacks)) { + return NULL; + } + + return drwav__read_pcm_frames_and_close_s32(&wav, channelsOut, sampleRateOut, totalFrameCountOut); +} + +#ifndef DR_WAV_NO_STDIO +DRWAV_API drwav_int16* drwav_open_file_and_read_pcm_frames_s16(const char* filename, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + drwav wav; + + if (channelsOut) { + *channelsOut = 0; + } + if (sampleRateOut) { + *sampleRateOut = 0; + } + if (totalFrameCountOut) { + *totalFrameCountOut = 0; + } + + if (!drwav_init_file(&wav, filename, pAllocationCallbacks)) { + return NULL; + } + + return drwav__read_pcm_frames_and_close_s16(&wav, channelsOut, sampleRateOut, totalFrameCountOut); +} + +DRWAV_API float* drwav_open_file_and_read_pcm_frames_f32(const char* filename, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + drwav wav; + + if (channelsOut) { + *channelsOut = 0; + } + if (sampleRateOut) { + *sampleRateOut = 0; + } + if (totalFrameCountOut) { + *totalFrameCountOut = 0; + } + + if (!drwav_init_file(&wav, filename, pAllocationCallbacks)) { + return NULL; + } + + return drwav__read_pcm_frames_and_close_f32(&wav, channelsOut, sampleRateOut, totalFrameCountOut); +} + +DRWAV_API drwav_int32* drwav_open_file_and_read_pcm_frames_s32(const char* filename, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + drwav wav; + + if (channelsOut) { + *channelsOut = 0; + } + if (sampleRateOut) { + *sampleRateOut = 0; + } + if (totalFrameCountOut) { + *totalFrameCountOut = 0; + } + + if (!drwav_init_file(&wav, filename, pAllocationCallbacks)) { + return NULL; + } + + return drwav__read_pcm_frames_and_close_s32(&wav, channelsOut, sampleRateOut, totalFrameCountOut); +} + + +#ifndef DR_WAV_NO_WCHAR +DRWAV_API drwav_int16* drwav_open_file_and_read_pcm_frames_s16_w(const wchar_t* filename, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + drwav wav; + + if (sampleRateOut) { + *sampleRateOut = 0; + } + if (channelsOut) { + *channelsOut = 0; + } + if (totalFrameCountOut) { + *totalFrameCountOut = 0; + } + + if (!drwav_init_file_w(&wav, filename, pAllocationCallbacks)) { + return NULL; + } + + return drwav__read_pcm_frames_and_close_s16(&wav, channelsOut, sampleRateOut, totalFrameCountOut); +} + +DRWAV_API float* drwav_open_file_and_read_pcm_frames_f32_w(const wchar_t* filename, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + drwav wav; + + if (sampleRateOut) { + *sampleRateOut = 0; + } + if (channelsOut) { + *channelsOut = 0; + } + if (totalFrameCountOut) { + *totalFrameCountOut = 0; + } + + if (!drwav_init_file_w(&wav, filename, pAllocationCallbacks)) { + return NULL; + } + + return drwav__read_pcm_frames_and_close_f32(&wav, channelsOut, sampleRateOut, totalFrameCountOut); +} + +DRWAV_API drwav_int32* drwav_open_file_and_read_pcm_frames_s32_w(const wchar_t* filename, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + drwav wav; + + if (sampleRateOut) { + *sampleRateOut = 0; + } + if (channelsOut) { + *channelsOut = 0; + } + if (totalFrameCountOut) { + *totalFrameCountOut = 0; + } + + if (!drwav_init_file_w(&wav, filename, pAllocationCallbacks)) { + return NULL; + } + + return drwav__read_pcm_frames_and_close_s32(&wav, channelsOut, sampleRateOut, totalFrameCountOut); +} +#endif /* DR_WAV_NO_WCHAR */ +#endif /* DR_WAV_NO_STDIO */ + +DRWAV_API drwav_int16* drwav_open_memory_and_read_pcm_frames_s16(const void* data, size_t dataSize, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + drwav wav; + + if (channelsOut) { + *channelsOut = 0; + } + if (sampleRateOut) { + *sampleRateOut = 0; + } + if (totalFrameCountOut) { + *totalFrameCountOut = 0; + } + + if (!drwav_init_memory(&wav, data, dataSize, pAllocationCallbacks)) { + return NULL; + } + + return drwav__read_pcm_frames_and_close_s16(&wav, channelsOut, sampleRateOut, totalFrameCountOut); +} + +DRWAV_API float* drwav_open_memory_and_read_pcm_frames_f32(const void* data, size_t dataSize, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + drwav wav; + + if (channelsOut) { + *channelsOut = 0; + } + if (sampleRateOut) { + *sampleRateOut = 0; + } + if (totalFrameCountOut) { + *totalFrameCountOut = 0; + } + + if (!drwav_init_memory(&wav, data, dataSize, pAllocationCallbacks)) { + return NULL; + } + + return drwav__read_pcm_frames_and_close_f32(&wav, channelsOut, sampleRateOut, totalFrameCountOut); +} + +DRWAV_API drwav_int32* drwav_open_memory_and_read_pcm_frames_s32(const void* data, size_t dataSize, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + drwav wav; + + if (channelsOut) { + *channelsOut = 0; + } + if (sampleRateOut) { + *sampleRateOut = 0; + } + if (totalFrameCountOut) { + *totalFrameCountOut = 0; + } + + if (!drwav_init_memory(&wav, data, dataSize, pAllocationCallbacks)) { + return NULL; + } + + return drwav__read_pcm_frames_and_close_s32(&wav, channelsOut, sampleRateOut, totalFrameCountOut); +} +#endif /* DR_WAV_NO_CONVERSION_API */ + + +DRWAV_API void drwav_free(void* p, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + if (pAllocationCallbacks != NULL) { + drwav__free_from_callbacks(p, pAllocationCallbacks); + } else { + drwav__free_default(p, NULL); + } +} + +DRWAV_API drwav_uint16 drwav_bytes_to_u16(const drwav_uint8* data) +{ + return ((drwav_uint16)data[0] << 0) | ((drwav_uint16)data[1] << 8); +} + +DRWAV_API drwav_int16 drwav_bytes_to_s16(const drwav_uint8* data) +{ + return (drwav_int16)drwav_bytes_to_u16(data); +} + +DRWAV_API drwav_uint32 drwav_bytes_to_u32(const drwav_uint8* data) +{ + return drwav_bytes_to_u32_le(data); +} + +DRWAV_API float drwav_bytes_to_f32(const drwav_uint8* data) +{ + union { + drwav_uint32 u32; + float f32; + } value; + + value.u32 = drwav_bytes_to_u32(data); + return value.f32; +} + +DRWAV_API drwav_int32 drwav_bytes_to_s32(const drwav_uint8* data) +{ + return (drwav_int32)drwav_bytes_to_u32(data); +} + +DRWAV_API drwav_uint64 drwav_bytes_to_u64(const drwav_uint8* data) +{ + return + ((drwav_uint64)data[0] << 0) | ((drwav_uint64)data[1] << 8) | ((drwav_uint64)data[2] << 16) | ((drwav_uint64)data[3] << 24) | + ((drwav_uint64)data[4] << 32) | ((drwav_uint64)data[5] << 40) | ((drwav_uint64)data[6] << 48) | ((drwav_uint64)data[7] << 56); +} + +DRWAV_API drwav_int64 drwav_bytes_to_s64(const drwav_uint8* data) +{ + return (drwav_int64)drwav_bytes_to_u64(data); +} + + +DRWAV_API drwav_bool32 drwav_guid_equal(const drwav_uint8 a[16], const drwav_uint8 b[16]) +{ + int i; + for (i = 0; i < 16; i += 1) { + if (a[i] != b[i]) { + return DRWAV_FALSE; + } + } + + return DRWAV_TRUE; +} + +DRWAV_API drwav_bool32 drwav_fourcc_equal(const drwav_uint8* a, const char* b) +{ + return + a[0] == b[0] && + a[1] == b[1] && + a[2] == b[2] && + a[3] == b[3]; +} + +#ifdef __MRC__ +/* Undo the pragma at the beginning of this file. */ +#pragma options opt reset +#endif + +#endif /* dr_wav_c */ +#endif /* DR_WAV_IMPLEMENTATION */ + +/* +REVISION HISTORY +================ +v0.13.16 - 2024-02-27 + - Fix a Wdouble-promotion warning. + +v0.13.15 - 2024-01-23 + - Relax some unnecessary validation that prevented some files from loading. + +v0.13.14 - 2023-12-02 + - Fix a warning about an unused variable. + +v0.13.13 - 2023-11-02 + - Fix a warning when compiling with Clang. + +v0.13.12 - 2023-08-07 + - Fix a possible crash in drwav_read_pcm_frames(). + +v0.13.11 - 2023-07-07 + - AIFF compatibility improvements. + +v0.13.10 - 2023-05-29 + - Fix a bug where drwav_init_with_metadata() does not decode any frames after initializtion. + +v0.13.9 - 2023-05-22 + - Add support for AIFF decoding (writing and metadata not supported). + - Add support for RIFX decoding (writing and metadata not supported). + - Fix a bug where metadata is not processed if it's located before the "fmt " chunk. + - Add a workaround for a type of malformed WAV file where the size of the "RIFF" and "data" chunks + are incorrectly set to 0xFFFFFFFF. + +v0.13.8 - 2023-03-25 + - Fix a possible null pointer dereference. + - Fix a crash when loading files with badly formed metadata. + +v0.13.7 - 2022-09-17 + - Fix compilation with DJGPP. + - Add support for disabling wchar_t with DR_WAV_NO_WCHAR. + +v0.13.6 - 2022-04-10 + - Fix compilation error on older versions of GCC. + - Remove some dependencies on the standard library. + +v0.13.5 - 2022-01-26 + - Fix an error when seeking to the end of the file. + +v0.13.4 - 2021-12-08 + - Fix some static analysis warnings. + +v0.13.3 - 2021-11-24 + - Fix an incorrect assertion when trying to endian swap 1-byte sample formats. This is now a no-op + rather than a failed assertion. + - Fix a bug with parsing of the bext chunk. + - Fix some static analysis warnings. + +v0.13.2 - 2021-10-02 + - Fix a possible buffer overflow when reading from compressed formats. + +v0.13.1 - 2021-07-31 + - Fix platform detection for ARM64. + +v0.13.0 - 2021-07-01 + - Improve support for reading and writing metadata. Use the `_with_metadata()` APIs to initialize + a WAV decoder and store the metadata within the `drwav` object. Use the `pMetadata` and + `metadataCount` members of the `drwav` object to read the data. The old way of handling metadata + via a callback is still usable and valid. + - API CHANGE: drwav_target_write_size_bytes() now takes extra parameters for calculating the + required write size when writing metadata. + - Add drwav_get_cursor_in_pcm_frames() + - Add drwav_get_length_in_pcm_frames() + - Fix a bug where drwav_read_raw() can call the read callback with a byte count of zero. + +v0.12.20 - 2021-06-11 + - Fix some undefined behavior. + +v0.12.19 - 2021-02-21 + - Fix a warning due to referencing _MSC_VER when it is undefined. + - Minor improvements to the management of some internal state concerning the data chunk cursor. + +v0.12.18 - 2021-01-31 + - Clean up some static analysis warnings. + +v0.12.17 - 2021-01-17 + - Minor fix to sample code in documentation. + - Correctly qualify a private API as private rather than public. + - Code cleanup. + +v0.12.16 - 2020-12-02 + - Fix a bug when trying to read more bytes than can fit in a size_t. + +v0.12.15 - 2020-11-21 + - Fix compilation with OpenWatcom. + +v0.12.14 - 2020-11-13 + - Minor code clean up. + +v0.12.13 - 2020-11-01 + - Improve compiler support for older versions of GCC. + +v0.12.12 - 2020-09-28 + - Add support for RF64. + - Fix a bug in writing mode where the size of the RIFF chunk incorrectly includes the header section. + +v0.12.11 - 2020-09-08 + - Fix a compilation error on older compilers. + +v0.12.10 - 2020-08-24 + - Fix a bug when seeking with ADPCM formats. + +v0.12.9 - 2020-08-02 + - Simplify sized types. + +v0.12.8 - 2020-07-25 + - Fix a compilation warning. + +v0.12.7 - 2020-07-15 + - Fix some bugs on big-endian architectures. + - Fix an error in s24 to f32 conversion. + +v0.12.6 - 2020-06-23 + - Change drwav_read_*() to allow NULL to be passed in as the output buffer which is equivalent to a forward seek. + - Fix a buffer overflow when trying to decode invalid IMA-ADPCM files. + - Add include guard for the implementation section. + +v0.12.5 - 2020-05-27 + - Minor documentation fix. + +v0.12.4 - 2020-05-16 + - Replace assert() with DRWAV_ASSERT(). + - Add compile-time and run-time version querying. + - DRWAV_VERSION_MINOR + - DRWAV_VERSION_MAJOR + - DRWAV_VERSION_REVISION + - DRWAV_VERSION_STRING + - drwav_version() + - drwav_version_string() + +v0.12.3 - 2020-04-30 + - Fix compilation errors with VC6. + +v0.12.2 - 2020-04-21 + - Fix a bug where drwav_init_file() does not close the file handle after attempting to load an erroneous file. + +v0.12.1 - 2020-04-13 + - Fix some pedantic warnings. + +v0.12.0 - 2020-04-04 + - API CHANGE: Add container and format parameters to the chunk callback. + - Minor documentation updates. + +v0.11.5 - 2020-03-07 + - Fix compilation error with Visual Studio .NET 2003. + +v0.11.4 - 2020-01-29 + - Fix some static analysis warnings. + - Fix a bug when reading f32 samples from an A-law encoded stream. + +v0.11.3 - 2020-01-12 + - Minor changes to some f32 format conversion routines. + - Minor bug fix for ADPCM conversion when end of file is reached. + +v0.11.2 - 2019-12-02 + - Fix a possible crash when using custom memory allocators without a custom realloc() implementation. + - Fix an integer overflow bug. + - Fix a null pointer dereference bug. + - Add limits to sample rate, channels and bits per sample to tighten up some validation. + +v0.11.1 - 2019-10-07 + - Internal code clean up. + +v0.11.0 - 2019-10-06 + - API CHANGE: Add support for user defined memory allocation routines. This system allows the program to specify their own memory allocation + routines with a user data pointer for client-specific contextual data. This adds an extra parameter to the end of the following APIs: + - drwav_init() + - drwav_init_ex() + - drwav_init_file() + - drwav_init_file_ex() + - drwav_init_file_w() + - drwav_init_file_w_ex() + - drwav_init_memory() + - drwav_init_memory_ex() + - drwav_init_write() + - drwav_init_write_sequential() + - drwav_init_write_sequential_pcm_frames() + - drwav_init_file_write() + - drwav_init_file_write_sequential() + - drwav_init_file_write_sequential_pcm_frames() + - drwav_init_file_write_w() + - drwav_init_file_write_sequential_w() + - drwav_init_file_write_sequential_pcm_frames_w() + - drwav_init_memory_write() + - drwav_init_memory_write_sequential() + - drwav_init_memory_write_sequential_pcm_frames() + - drwav_open_and_read_pcm_frames_s16() + - drwav_open_and_read_pcm_frames_f32() + - drwav_open_and_read_pcm_frames_s32() + - drwav_open_file_and_read_pcm_frames_s16() + - drwav_open_file_and_read_pcm_frames_f32() + - drwav_open_file_and_read_pcm_frames_s32() + - drwav_open_file_and_read_pcm_frames_s16_w() + - drwav_open_file_and_read_pcm_frames_f32_w() + - drwav_open_file_and_read_pcm_frames_s32_w() + - drwav_open_memory_and_read_pcm_frames_s16() + - drwav_open_memory_and_read_pcm_frames_f32() + - drwav_open_memory_and_read_pcm_frames_s32() + Set this extra parameter to NULL to use defaults which is the same as the previous behaviour. Setting this NULL will use + DRWAV_MALLOC, DRWAV_REALLOC and DRWAV_FREE. + - Add support for reading and writing PCM frames in an explicit endianness. New APIs: + - drwav_read_pcm_frames_le() + - drwav_read_pcm_frames_be() + - drwav_read_pcm_frames_s16le() + - drwav_read_pcm_frames_s16be() + - drwav_read_pcm_frames_f32le() + - drwav_read_pcm_frames_f32be() + - drwav_read_pcm_frames_s32le() + - drwav_read_pcm_frames_s32be() + - drwav_write_pcm_frames_le() + - drwav_write_pcm_frames_be() + - Remove deprecated APIs. + - API CHANGE: The following APIs now return native-endian data. Previously they returned little-endian data. + - drwav_read_pcm_frames() + - drwav_read_pcm_frames_s16() + - drwav_read_pcm_frames_s32() + - drwav_read_pcm_frames_f32() + - drwav_open_and_read_pcm_frames_s16() + - drwav_open_and_read_pcm_frames_s32() + - drwav_open_and_read_pcm_frames_f32() + - drwav_open_file_and_read_pcm_frames_s16() + - drwav_open_file_and_read_pcm_frames_s32() + - drwav_open_file_and_read_pcm_frames_f32() + - drwav_open_file_and_read_pcm_frames_s16_w() + - drwav_open_file_and_read_pcm_frames_s32_w() + - drwav_open_file_and_read_pcm_frames_f32_w() + - drwav_open_memory_and_read_pcm_frames_s16() + - drwav_open_memory_and_read_pcm_frames_s32() + - drwav_open_memory_and_read_pcm_frames_f32() + +v0.10.1 - 2019-08-31 + - Correctly handle partial trailing ADPCM blocks. + +v0.10.0 - 2019-08-04 + - Remove deprecated APIs. + - Add wchar_t variants for file loading APIs: + drwav_init_file_w() + drwav_init_file_ex_w() + drwav_init_file_write_w() + drwav_init_file_write_sequential_w() + - Add drwav_target_write_size_bytes() which calculates the total size in bytes of a WAV file given a format and sample count. + - Add APIs for specifying the PCM frame count instead of the sample count when opening in sequential write mode: + drwav_init_write_sequential_pcm_frames() + drwav_init_file_write_sequential_pcm_frames() + drwav_init_file_write_sequential_pcm_frames_w() + drwav_init_memory_write_sequential_pcm_frames() + - Deprecate drwav_open*() and drwav_close(): + drwav_open() + drwav_open_ex() + drwav_open_write() + drwav_open_write_sequential() + drwav_open_file() + drwav_open_file_ex() + drwav_open_file_write() + drwav_open_file_write_sequential() + drwav_open_memory() + drwav_open_memory_ex() + drwav_open_memory_write() + drwav_open_memory_write_sequential() + drwav_close() + - Minor documentation updates. + +v0.9.2 - 2019-05-21 + - Fix warnings. + +v0.9.1 - 2019-05-05 + - Add support for C89. + - Change license to choice of public domain or MIT-0. + +v0.9.0 - 2018-12-16 + - API CHANGE: Add new reading APIs for reading by PCM frames instead of samples. Old APIs have been deprecated and + will be removed in v0.10.0. Deprecated APIs and their replacements: + drwav_read() -> drwav_read_pcm_frames() + drwav_read_s16() -> drwav_read_pcm_frames_s16() + drwav_read_f32() -> drwav_read_pcm_frames_f32() + drwav_read_s32() -> drwav_read_pcm_frames_s32() + drwav_seek_to_sample() -> drwav_seek_to_pcm_frame() + drwav_write() -> drwav_write_pcm_frames() + drwav_open_and_read_s16() -> drwav_open_and_read_pcm_frames_s16() + drwav_open_and_read_f32() -> drwav_open_and_read_pcm_frames_f32() + drwav_open_and_read_s32() -> drwav_open_and_read_pcm_frames_s32() + drwav_open_file_and_read_s16() -> drwav_open_file_and_read_pcm_frames_s16() + drwav_open_file_and_read_f32() -> drwav_open_file_and_read_pcm_frames_f32() + drwav_open_file_and_read_s32() -> drwav_open_file_and_read_pcm_frames_s32() + drwav_open_memory_and_read_s16() -> drwav_open_memory_and_read_pcm_frames_s16() + drwav_open_memory_and_read_f32() -> drwav_open_memory_and_read_pcm_frames_f32() + drwav_open_memory_and_read_s32() -> drwav_open_memory_and_read_pcm_frames_s32() + drwav::totalSampleCount -> drwav::totalPCMFrameCount + - API CHANGE: Rename drwav_open_and_read_file_*() to drwav_open_file_and_read_*(). + - API CHANGE: Rename drwav_open_and_read_memory_*() to drwav_open_memory_and_read_*(). + - Add built-in support for smpl chunks. + - Add support for firing a callback for each chunk in the file at initialization time. + - This is enabled through the drwav_init_ex(), etc. family of APIs. + - Handle invalid FMT chunks more robustly. + +v0.8.5 - 2018-09-11 + - Const correctness. + - Fix a potential stack overflow. + +v0.8.4 - 2018-08-07 + - Improve 64-bit detection. + +v0.8.3 - 2018-08-05 + - Fix C++ build on older versions of GCC. + +v0.8.2 - 2018-08-02 + - Fix some big-endian bugs. + +v0.8.1 - 2018-06-29 + - Add support for sequential writing APIs. + - Disable seeking in write mode. + - Fix bugs with Wave64. + - Fix typos. + +v0.8 - 2018-04-27 + - Bug fix. + - Start using major.minor.revision versioning. + +v0.7f - 2018-02-05 + - Restrict ADPCM formats to a maximum of 2 channels. + +v0.7e - 2018-02-02 + - Fix a crash. + +v0.7d - 2018-02-01 + - Fix a crash. + +v0.7c - 2018-02-01 + - Set drwav.bytesPerSample to 0 for all compressed formats. + - Fix a crash when reading 16-bit floating point WAV files. In this case dr_wav will output silence for + all format conversion reading APIs (*_s16, *_s32, *_f32 APIs). + - Fix some divide-by-zero errors. + +v0.7b - 2018-01-22 + - Fix errors with seeking of compressed formats. + - Fix compilation error when DR_WAV_NO_CONVERSION_API + +v0.7a - 2017-11-17 + - Fix some GCC warnings. + +v0.7 - 2017-11-04 + - Add writing APIs. + +v0.6 - 2017-08-16 + - API CHANGE: Rename dr_* types to drwav_*. + - Add support for custom implementations of malloc(), realloc(), etc. + - Add support for Microsoft ADPCM. + - Add support for IMA ADPCM (DVI, format code 0x11). + - Optimizations to drwav_read_s16(). + - Bug fixes. + +v0.5g - 2017-07-16 + - Change underlying type for booleans to unsigned. + +v0.5f - 2017-04-04 + - Fix a minor bug with drwav_open_and_read_s16() and family. + +v0.5e - 2016-12-29 + - Added support for reading samples as signed 16-bit integers. Use the _s16() family of APIs for this. + - Minor fixes to documentation. + +v0.5d - 2016-12-28 + - Use drwav_int* and drwav_uint* sized types to improve compiler support. + +v0.5c - 2016-11-11 + - Properly handle JUNK chunks that come before the FMT chunk. + +v0.5b - 2016-10-23 + - A minor change to drwav_bool8 and drwav_bool32 types. + +v0.5a - 2016-10-11 + - Fixed a bug with drwav_open_and_read() and family due to incorrect argument ordering. + - Improve A-law and mu-law efficiency. + +v0.5 - 2016-09-29 + - API CHANGE. Swap the order of "channels" and "sampleRate" parameters in drwav_open_and_read*(). Rationale for this is to + keep it consistent with dr_audio and dr_flac. + +v0.4b - 2016-09-18 + - Fixed a typo in documentation. + +v0.4a - 2016-09-18 + - Fixed a typo. + - Change date format to ISO 8601 (YYYY-MM-DD) + +v0.4 - 2016-07-13 + - API CHANGE. Make onSeek consistent with dr_flac. + - API CHANGE. Rename drwav_seek() to drwav_seek_to_sample() for clarity and consistency with dr_flac. + - Added support for Sony Wave64. + +v0.3a - 2016-05-28 + - API CHANGE. Return drwav_bool32 instead of int in onSeek callback. + - Fixed a memory leak. + +v0.3 - 2016-05-22 + - Lots of API changes for consistency. + +v0.2a - 2016-05-16 + - Fixed Linux/GCC build. + +v0.2 - 2016-05-11 + - Added support for reading data as signed 32-bit PCM for consistency with dr_flac. + +v0.1a - 2016-05-07 + - Fixed a bug in drwav_open_file() where the file handle would not be closed if the loader failed to initialize. + +v0.1 - 2016-05-04 + - Initial versioned release. +*/ + +/* +This software is available as a choice of the following licenses. Choose +whichever you prefer. + +=============================================================================== +ALTERNATIVE 1 - Public Domain (www.unlicense.org) +=============================================================================== +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. + +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to + +=============================================================================== +ALTERNATIVE 2 - MIT No Attribution +=============================================================================== +Copyright 2023 David Reid + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ From 0eb669f3c57339bbe6909a46f0c7a33f8ff25b52 Mon Sep 17 00:00:00 2001 From: louist103 <35883445+louist103@users.noreply.github.com> Date: Thu, 18 Jul 2024 00:02:24 -0400 Subject: [PATCH 16/25] sequenceMap->mSequenceMap and a string fix --- .../Enhancements/Audio/AudioCollection.cpp | 24 +++++++++---------- mm/2s2h/Enhancements/Audio/AudioCollection.h | 6 ++--- mm/2s2h/Enhancements/Audio/AudioEditor.cpp | 14 +++++------ 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/mm/2s2h/Enhancements/Audio/AudioCollection.cpp b/mm/2s2h/Enhancements/Audio/AudioCollection.cpp index 4694cce9b2..2197d8afc4 100644 --- a/mm/2s2h/Enhancements/Audio/AudioCollection.cpp +++ b/mm/2s2h/Enhancements/Audio/AudioCollection.cpp @@ -19,7 +19,7 @@ AudioCollection::AudioCollection() { // (originalSequenceId, label, sfxKey, // category, canBeReplaced, canBeUsedAsReplacement), - sequenceMap = { + mSequenceMap = { SEQUENCE_MAP_ENTRY(NA_BGM_GENERAL_SFX, "General SFX", "SEQUENCE_MAP_ENTRY", SEQ_BGM_WORLD, true, true), SEQUENCE_MAP_ENTRY(NA_BGM_AMBIENCE, "Ambience", "NA_BGM_AMBIENCE", SEQ_BGM_WORLD, true, true), SEQUENCE_MAP_ENTRY(NA_BGM_TERMINA_FIELD, "Termina Field", "NA_BGM_TERMINA_FIELD", SEQ_BGM_WORLD, true, true), @@ -225,13 +225,13 @@ void AudioCollection::AddToCollection(char* otrPath, uint16_t seqNum) { type = SEQ_FANFARE; } SequenceInfo info = { seqNum, - sequenceName.c_str(), + sequenceName, StringHelper::Replace( StringHelper::Replace(StringHelper::Replace(sequenceName, " ", "_"), "~", "-"), ".", ""), type, false, true }; - sequenceMap.emplace(seqNum, info); + mSequenceMap.emplace(seqNum, info); } uint16_t AudioCollection::GetReplacementSequence(uint16_t seqId) { @@ -247,14 +247,14 @@ uint16_t AudioCollection::GetReplacementSequence(uint16_t seqId) { // } //} - if (sequenceMap.find(seqId) == sequenceMap.end()) { + if (mSequenceMap.find(seqId) == mSequenceMap.end()) { return seqId; } - const auto& sequenceInfo = sequenceMap.at(seqId); + const auto& sequenceInfo = mSequenceMap.at(seqId); const std::string cvarKey = GetCvarKey(sequenceInfo.sfxKey); int replacementSeq = CVarGetInteger(cvarKey.c_str(), seqId); - if (!sequenceMap.contains(replacementSeq)) { + if (!mSequenceMap.contains(replacementSeq)) { replacementSeq = seqId; } return static_cast(replacementSeq); @@ -280,7 +280,7 @@ void AudioCollection::InitializeShufflePool() { if (shufflePoolInitialized) return; - for (auto& [seqId, seqInfo] : sequenceMap) { + for (auto& [seqId, seqInfo] : mSequenceMap) { if (!seqInfo.canBeUsedAsReplacement) continue; const std::string cvarKey = std::string(CVAR_AUDIO("Excluded.")) + seqInfo.sfxKey; @@ -299,19 +299,19 @@ extern "C" void AudioCollection_AddToCollection(char* otrPath, uint16_t seqNum) } bool AudioCollection::HasSequenceNum(uint16_t seqId) { - return sequenceMap.contains(seqId); + return mSequenceMap.contains(seqId); } const char* AudioCollection::GetSequenceName(uint16_t seqId) { - auto seqIt = sequenceMap.find(seqId); - if (seqIt != sequenceMap.end()) { - return seqIt->second.label; + auto seqIt = mSequenceMap.find(seqId); + if (seqIt != mSequenceMap.end()) { + return seqIt->second.label.c_str(); } return nullptr; } size_t AudioCollection::SequenceMapSize() { - return sequenceMap.size(); + return mSequenceMap.size(); } extern "C" const char* AudioCollection_GetSequenceName(uint16_t seqId) { diff --git a/mm/2s2h/Enhancements/Audio/AudioCollection.h b/mm/2s2h/Enhancements/Audio/AudioCollection.h index 671b303525..37d31c125b 100644 --- a/mm/2s2h/Enhancements/Audio/AudioCollection.h +++ b/mm/2s2h/Enhancements/Audio/AudioCollection.h @@ -23,7 +23,7 @@ enum SeqType { struct SequenceInfo { uint16_t sequenceId; - const char* label; + std::string label; std::string sfxKey; SeqType category; bool canBeReplaced; @@ -33,7 +33,7 @@ struct SequenceInfo { class AudioCollection { private: // All Loaded Audio - std::map sequenceMap; + std::map mSequenceMap; // Sequences/SFX to include in/exclude from shuffle pool struct compareSequenceLabel { @@ -49,7 +49,7 @@ class AudioCollection { static AudioCollection* Instance; AudioCollection(); std::map GetAllSequences() const { - return sequenceMap; + return mSequenceMap; } std::set GetIncludedSequences() const { return includedSequences; diff --git a/mm/2s2h/Enhancements/Audio/AudioEditor.cpp b/mm/2s2h/Enhancements/Audio/AudioEditor.cpp index 73b74bae00..7d78c71e76 100644 --- a/mm/2s2h/Enhancements/Audio/AudioEditor.cpp +++ b/mm/2s2h/Enhancements/Audio/AudioEditor.cpp @@ -281,11 +281,11 @@ void AudioEditor::Draw_SfxTab(const std::string& tabId, SeqType type) { ImGui::TableNextRow(); ImGui::TableNextColumn(); - ImGui::Text("%s", seqData.label); + ImGui::Text("%s", seqData.label.c_str()); ImGui::TableNextColumn(); ImGui::PushItemWidth(-FLT_MIN); const int initialValue = map.contains(currentValue) ? currentValue : defaultValue; - if (ImGui::BeginCombo(hiddenKey.c_str(), map.at(initialValue).label)) { + if (ImGui::BeginCombo(hiddenKey.c_str(), map.at(initialValue).label.c_str())) { for (const auto& [value, seqData] : map) { // If excluded as a replacement sequence, don't show in other dropdowns except the effect's own // dropdown. @@ -294,7 +294,7 @@ void AudioEditor::Draw_SfxTab(const std::string& tabId, SeqType type) { continue; } - if (ImGui::Selectable(seqData.label)) { + if (ImGui::Selectable(seqData.label.c_str())) { CVarSetInteger(cvarKey.c_str(), value); Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); UpdateCurrentBGM(defaultValue, type); @@ -577,7 +577,7 @@ void AudioEditor::DrawElement() { ImGui::SameLine(); if (ImGui::Button("Exclude All")) { for (auto seqInfo : AudioCollection::Instance->GetIncludedSequences()) { - if (sequenceSearch.PassFilter(seqInfo->label) && showType[seqInfo->category]) { + if (sequenceSearch.PassFilter(seqInfo->label.c_str()) && showType[seqInfo->category]) { seqsToExclude.insert(seqInfo); } } @@ -585,7 +585,7 @@ void AudioEditor::DrawElement() { ImGui::SameLine(); if (ImGui::Button("Include All")) { for (auto seqInfo : AudioCollection::Instance->GetExcludedSequences()) { - if (sequenceSearch.PassFilter(seqInfo->label) && showType[seqInfo->category]) { + if (sequenceSearch.PassFilter(seqInfo->label.c_str()) && showType[seqInfo->category]) { seqsToInclude.insert(seqInfo); } } @@ -652,7 +652,7 @@ void AudioEditor::DrawElement() { ImGui::BeginChild("ChildIncludedSequences", ImVec2(0, -8)); for (auto seqInfo : AudioCollection::Instance->GetIncludedSequences()) { - if (sequenceSearch.PassFilter(seqInfo->label) && showType[seqInfo->category]) { + if (sequenceSearch.PassFilter(seqInfo->label.c_str()) && showType[seqInfo->category]) { if (ImGui::Button(std::string(ICON_FA_TIMES "##" + seqInfo->sfxKey).c_str())) { seqsToExclude.insert(seqInfo); } @@ -676,7 +676,7 @@ void AudioEditor::DrawElement() { ImGui::BeginChild("ChildExcludedSequences", ImVec2(0, -8)); for (auto seqInfo : AudioCollection::Instance->GetExcludedSequences()) { - if (sequenceSearch.PassFilter(seqInfo->label) && showType[seqInfo->category]) { + if (sequenceSearch.PassFilter(seqInfo->label.c_str()) && showType[seqInfo->category]) { if (ImGui::Button(std::string(ICON_FA_PLUS "##" + seqInfo->sfxKey).c_str())) { seqsToInclude.insert(seqInfo); } From c46d73820f66f4144cb367958b1c436c96e66272 Mon Sep 17 00:00:00 2001 From: louist103 <35883445+louist103@users.noreply.github.com> Date: Thu, 18 Jul 2024 00:04:02 -0400 Subject: [PATCH 17/25] Streamed sequence importer --- .../importer/AudioSequenceFactory.cpp | 28 ++++++++++++++++--- mm/2s2h/resource/type/AudioSample.h | 3 +- mm/2s2h/resource/type/AudioSequence.h | 3 ++ mm/include/audio/soundfont.h | 3 +- 4 files changed, 30 insertions(+), 7 deletions(-) diff --git a/mm/2s2h/resource/importer/AudioSequenceFactory.cpp b/mm/2s2h/resource/importer/AudioSequenceFactory.cpp index 404018744c..ee78a7dfec 100644 --- a/mm/2s2h/resource/importer/AudioSequenceFactory.cpp +++ b/mm/2s2h/resource/importer/AudioSequenceFactory.cpp @@ -4,6 +4,7 @@ #include "StringHelper.h" #include "libultraship/libultraship.h" +#include "dr_wav.h" namespace SOH { std::shared_ptr ResourceFactoryBinaryAudioSequenceV2::ReadResource(std::shared_ptr file) { @@ -83,6 +84,8 @@ std::shared_ptr ResourceFactoryXMLAudioSequenceV0::ReadResource sequence->sequence.cachePolicy = CachePolicyToInt(child->Attribute("CachePolicy")); sequence->sequence.seqDataSize = child->IntAttribute("Size"); sequence->sequence.seqNumber = child->IntAttribute("Index"); + const char* customStr = child->Attribute("CustomFormat"); + memset(sequence->sequence.fonts, 0, sizeof(sequence->sequence.fonts)); @@ -99,12 +102,29 @@ std::shared_ptr ResourceFactoryXMLAudioSequenceV0::ReadResource initData->Path = path; initData->IsCustom = false; initData->ByteOrder = Ship::Endianness::Native; - auto seqFile = Ship::Context::GetInstance()->GetResourceManager()->GetArchiveManager()->LoadFile(path, initData); - sequence->sequenceData = *seqFile->Buffer.get(); - sequence->sequence.seqData = sequence->sequenceData.data(); - + if (customStr == nullptr) { + sequence->sequenceData = *seqFile->Buffer.get(); + sequence->sequence.seqData = sequence->sequenceData.data(); + } else { + drwav wav; + drwav_uint64 numFrames; + + drwav_bool32 ret = drwav_init_memory(&wav, seqFile->Buffer.get()->data(), seqFile->Buffer.get()->size(), nullptr); + + drwav_get_length_in_pcm_frames(&wav, &numFrames); + + sequence->sampleRate = wav.fmt.sampleRate; + sequence->numChannels = wav.fmt.channels; + sequence->customSeqData = std::make_unique(numFrames); + drwav_read_pcm_frames_s16(&wav, numFrames, sequence->customSeqData.get()); + + sequence->sequence.seqData = (char*)sequence->customSeqData.get(); + // Write a marker to show this is a streamed audio file + memcpy(sequence->sequence.fonts, "07151129", sizeof("07151129")); + drwav_uninit(&wav); + } return sequence; } } // namespace SOH diff --git a/mm/2s2h/resource/type/AudioSample.h b/mm/2s2h/resource/type/AudioSample.h index 192b5a6f52..bdb7b6f4e9 100644 --- a/mm/2s2h/resource/type/AudioSample.h +++ b/mm/2s2h/resource/type/AudioSample.h @@ -27,11 +27,10 @@ typedef struct { /* 0x00 */ u32 medium : 2; /* 0x00 */ u32 unk_bit26 : 1; /* 0x00 */ u32 unk_bit25 : 1; // this has been named isRelocated in zret - /* 0x01 */ u32 size : 24; }; u32 asU32; }; - + /* 0x01 */ u32 size; /* 0x04 */ u8* sampleAddr; /* 0x08 */ AdpcmLoop* loop; /* 0x0C */ AdpcmBook* book; diff --git a/mm/2s2h/resource/type/AudioSequence.h b/mm/2s2h/resource/type/AudioSequence.h index 5ff4c9a351..79ca8bdd26 100644 --- a/mm/2s2h/resource/type/AudioSequence.h +++ b/mm/2s2h/resource/type/AudioSequence.h @@ -29,5 +29,8 @@ class AudioSequence : public Ship::Resource { Sequence sequence; std::vector sequenceData; + std::unique_ptr customSeqData = nullptr; + uint32_t sampleRate = 0; // Streamed audio only + uint8_t numChannels = 0; }; }; // namespace SOH diff --git a/mm/include/audio/soundfont.h b/mm/include/audio/soundfont.h index 8ace4eb141..26fcd2ffa2 100644 --- a/mm/include/audio/soundfont.h +++ b/mm/include/audio/soundfont.h @@ -50,10 +50,11 @@ typedef struct Sample { /* 0x0 */ u32 medium : 2; // Medium where sample is currently stored. See `SampleMedium` /* 0x0 */ u32 unk_bit26 : 1; /* 0x0 */ u32 isRelocated : 1; // Has the sample header been relocated (offsets to pointers) - /* 0x1 */ u32 size : 24; // Size of the sample + }; u32 asU32; }; + /* 0x1 */ u32 size; // Size of the sample /* 0x4 */ u8* sampleAddr; // Raw sample data. Offset from the start of the sample bank or absolute address to either rom or ram /* 0x8 */ AdpcmLoop* loop; // Adpcm loop parameters used by the sample. Offset from the start of the sound font / pointer to ram /* 0xC */ AdpcmBook* book; // Adpcm book parameters used by the sample. Offset from the start of the sound font / pointer to ram From 2ba7f56ac993149cf0ea7107dcd4d8e52f452722 Mon Sep 17 00:00:00 2001 From: louist103 <35883445+louist103@users.noreply.github.com> Date: Thu, 18 Jul 2024 00:04:54 -0400 Subject: [PATCH 18/25] Streamed sequence playback --- mm/include/z64audio.h | 3 +- mm/src/audio/code_8019AF00.c | 4 +- mm/src/audio/lib/load.c | 17 ++++- mm/src/audio/lib/playback.c | 8 +- mm/src/audio/lib/synthesis.c | 142 +++++++++++++++++++++++++++++++++-- 5 files changed, 161 insertions(+), 13 deletions(-) diff --git a/mm/include/z64audio.h b/mm/include/z64audio.h index fe315ee3c1..a4d02d82f0 100644 --- a/mm/include/z64audio.h +++ b/mm/include/z64audio.h @@ -476,7 +476,8 @@ typedef struct { /* 0x00 */ volatile u8 enabled : 1; /* 0x00 */ u8 needsInit : 1; /* 0x00 */ u8 finished : 1; - /* 0x00 */ u8 unused : 1; + ///* 0x00 */ u8 unused : 1; + /* 0x00 */ u8 ignoreNoteState : 1; // For streamed audio where our voices don't come from a note /* 0x00 */ u8 strongRight : 1; /* 0x00 */ u8 strongLeft : 1; /* 0x00 */ u8 strongReverbRight : 1; diff --git a/mm/src/audio/code_8019AF00.c b/mm/src/audio/code_8019AF00.c index 530be71a5f..134d9c1eef 100644 --- a/mm/src/audio/code_8019AF00.c +++ b/mm/src/audio/code_8019AF00.c @@ -5508,8 +5508,8 @@ void Audio_StartSceneSequence(u16 seqId) { void Audio_UpdateSceneSequenceResumePoint(void) { u16 seqId = AudioSeq_GetActiveSeqId(SEQ_PLAYER_BGM_MAIN); - - if ((seqId != NA_BGM_DISABLED) && (sSeqFlags[seqId & 0xFF & 0xFF] & SEQ_FLAG_RESUME)) { + // BENTODO find a better fix + if (seqId >= ARRAY_COUNT(sSeqFlags) || (seqId != NA_BGM_DISABLED) && (sSeqFlags[seqId & 0xFF & 0xFF] & SEQ_FLAG_RESUME)) { if (sSeqResumePoint != SEQ_RESUME_POINT_NONE) { // Get the current point to resume from sSeqResumePoint = gAudioCtx.seqPlayers[SEQ_PLAYER_BGM_MAIN].seqScriptIO[3]; diff --git a/mm/src/audio/lib/load.c b/mm/src/audio/lib/load.c index 41aec275e2..b4597de467 100644 --- a/mm/src/audio/lib/load.c +++ b/mm/src/audio/lib/load.c @@ -16,6 +16,7 @@ #include #include "2s2h/Enhancements/Audio/AudioCollection.h" #include "2s2h/Enhancements/Audio/AudioEditor.h" +#include "2s2h/Enhancements/Audio/AudioSeqQueue.h" #include "BenPort.h" /** @@ -764,11 +765,21 @@ void* AudioLoad_SyncLoad(s32 tableType, u32 id, s32* didAllocate) { // cachePolicy = table->entries[id].cachePolicy; // romAddr = table->entries[realId].romAddr; - char* seqData = 0; + char* seqData = NULL; SoundFont* fnt; if (tableType == SEQUENCE_TABLE) { + // 2S2H [Port] Custom Audio. Read audio data from the archive and if needed, handle streamed audio. SequenceData sData = ResourceMgr_LoadSeqByName(sequenceMap[id]); + // Streamed audio + if (memcmp(sData.fonts, "07151129", sizeof("07151129")) == 0) { + // BENTODO Why do we need to add this 4 times? + AudioQueue_Enqueue(sequenceMap[id]); + AudioQueue_Enqueue(sequenceMap[id]); + AudioQueue_Enqueue(sequenceMap[id]); + AudioQueue_Enqueue(sequenceMap[id]); + return; + } seqData = sData.seqData; size = sData.seqDataSize; medium = sData.medium; @@ -1253,7 +1264,7 @@ void AudioLoad_Init(void* heap, size_t heapSize) { free(seqList); //2S2H Port I think we need to take use seqListSize because entry 0x7A is missing. - int startingSeqNum = seqListSize; // MAX_AUTHENTIC_SEQID; // 109 is the highest vanilla sequence + int startingSeqNum = MAX_AUTHENTIC_SEQID; // 109 is the highest vanilla sequence qsort(customSeqList, customSeqListSize, sizeof(char*), strcmp_sort); // Because AudioCollection's sequenceMap actually has more than sequences (including instruments from 130-135 and @@ -1302,7 +1313,7 @@ void AudioLoad_Init(void* heap, size_t heapSize) { if (addr = AudioHeap_Alloc(&gAudioCtx.initPool, gAudioHeapInitSizes.permanentPoolSize), addr == NULL) { // cast away const from gAudioHeapInitSizes - *((u32*)&gAudioHeapInitSizes.permanentPoolSize) = 0; + *((size_t*)&gAudioHeapInitSizes.permanentPoolSize) = 0; } AudioHeap_InitPool(&gAudioCtx.permanentPool, addr, gAudioHeapInitSizes.permanentPoolSize); diff --git a/mm/src/audio/lib/playback.c b/mm/src/audio/lib/playback.c index e2d713babb..b0c255353a 100644 --- a/mm/src/audio/lib/playback.c +++ b/mm/src/audio/lib/playback.c @@ -153,8 +153,8 @@ void AudioPlayback_NoteInit(Note* note) { note->sampleState = gDefaultSampleState; } -void AudioPlayback_NoteDisable(Note* note) { - if (note->sampleState.bitField0.needsInit == true) { +void AudioPlayback_NoteDisable(Note* note) { + if (note->sampleState.bitField0.needsInit == true) { note->sampleState.bitField0.needsInit = false; } note->playbackState.priority = 0; @@ -183,6 +183,10 @@ void AudioPlayback_ProcessNotes(void) { for (i = 0; i < gAudioCtx.numNotes; i++) { note = &gAudioCtx.notes[i]; sampleState = &gAudioCtx.sampleStateList[gAudioCtx.sampleStateOffset + i]; + // BENTODO: Verify there are no side effects from this... + if (sampleState->bitField0.ignoreNoteState) { + goto skip; + } playbackState = ¬e->playbackState; if (playbackState->parentLayer != NO_LAYER) { diff --git a/mm/src/audio/lib/synthesis.c b/mm/src/audio/lib/synthesis.c index 8cd0805be7..727d6fb960 100644 --- a/mm/src/audio/lib/synthesis.c +++ b/mm/src/audio/lib/synthesis.c @@ -208,13 +208,111 @@ void AudioSynth_SyncSampleStates(s32 updateIndex) { if (noteSampleState->bitField0.enabled) { noteSampleState->bitField0.needsInit = false; } else { - sampleState->bitField0.enabled = false; + if (!sampleState->bitField0.ignoreNoteState) + sampleState->bitField0.enabled = false; } noteSampleState->harmonicIndexCurAndPrev = 0; } } +#include +#include +#include + +typedef struct TunedSamplePool { + TunedSample tunedSample; + Sample sample; + AdpcmLoop loop; + uint8_t inUse; + + struct TunedSamplePool* next; +} TunedSamplePool; + +static TunedSamplePool samplePool; + +static TunedSamplePool* AudioSamplePool_CreateNew(void) { + TunedSamplePool* cur = &samplePool; + + while (cur->next != NULL && cur->inUse == true) { + cur = cur->next; + } + if (cur->next == NULL) { + cur->next = malloc(sizeof(TunedSamplePool)); + // BENTODO real error handling + if (cur->next == NULL) { + printf("Uh oh. Time to download more ram\n"); + assert(0); + } + cur = cur->next; + cur->next = NULL; + } + cur->inUse = true; + return cur; +} + +static TunedSamplePool* AudioSamplePool_FindElemenyByPtr(Sample* key) { + TunedSamplePool* cur = &samplePool; + + while (cur->next != NULL && cur->inUse == true) { + if (&cur->sample == key) { + return cur; + } + cur = cur->next; + } + return NULL; + +} + + +void Audio_LoadCustomBgm(int reverseUpdateIndex, uint64_t numFrames, uint32_t numChannels, uint32_t sampleRate, s16* sampleData) { + int updateIndex = (gAudioCtx.audioBufferParameters.updatesPerFrame - reverseUpdateIndex) * gAudioCtx.numNotes; + for (int i = 1; i < gAudioCtx.numNotes; i++) { + NoteSampleState* sampleState = &gAudioCtx.sampleStateList[i + updateIndex]; + NoteSampleState* noteSampleState = &gAudioCtx.notes[i + updateIndex].sampleState; + if (!sampleState->bitField0.enabled) { + sampleState->bitField0.enabled = true; + sampleState->bitField0.ignoreNoteState = true; + noteSampleState->bitField0.enabled = true; + sampleState->bitField0.needsInit = true; + + sampleState->bitField0.finished = 0; + sampleState->frequencyFixedPoint = 24000; + sampleState->targetVolLeft = 1024; + sampleState->targetVolRight = 1024; + gAudioCtx.notes[i + updateIndex].playbackState.priority = 14; + gAudioCtx.notes[i + updateIndex].playbackState.status = 1; + + //// Did someone say ***memory leaks***? + TunedSamplePool* poolEntry = AudioSamplePool_CreateNew(); + + sampleState->tunedSample = &poolEntry->tunedSample; + memset(sampleState->tunedSample, 0, sizeof(TunedSample)); + + sampleState->tunedSample->sample = &poolEntry->sample; + memset(sampleState->tunedSample->sample, 0, sizeof(Sample)); + + sampleState->tunedSample->sample->loop = &poolEntry->loop; + memset(sampleState->tunedSample->sample->loop, 0, sizeof(AdpcmLoop)); + + sampleState->bitField1.isSyntheticWave = 0; + sampleState->tunedSample->sample->isRelocated = true; + sampleState->tunedSample->sample->medium = MEDIUM_RAM; + sampleState->gain = 1 << 4; // 1.0 + sampleState->tunedSample->tuning = 1.0f; + sampleState->tunedSample->sample->codec = CODEC_S16; + sampleState->tunedSample->sample->size = numFrames * 2; + sampleState->tunedSample->sample->loop->sampleEnd = numFrames - 5; + sampleState->tunedSample->sample->loop->loopEnd = numFrames - 5; + sampleState->tunedSample->sample->loop->count = -1; + sampleState->tunedSample->sample->sampleAddr = sampleData; + break; + } + } +} + +#include "2s2h/Enhancements/Audio/AudioSeqQueue.h" + Acmd* AudioSynth_Update(Acmd* abiCmdStart, s32* numAbiCmds, s16* aiBufStart, s32 numSamplesPerFrame) { s32 numSamplesPerUpdate; s16* curAiBufPos; @@ -255,6 +353,16 @@ Acmd* AudioSynth_Update(Acmd* abiCmdStart, s32* numAbiCmds, s16* aiBufStart, s32 reverbIndex); } } + + if (!AudioQueue_IsEmpty()) { + uint64_t numFrames; + uint32_t numChannels; + uint32_t sampleRate; + s16* sampleData; + AudioQueue_GetSeqInfo(AudioQueue_Dequeue(), &numFrames, &numChannels, &sampleRate, &sampleData); + + Audio_LoadCustomBgm(reverseUpdateIndex, numFrames, numChannels, sampleRate, sampleData); + } curCmd = AudioSynth_ProcessSamples(curAiBufPos, numSamplesPerUpdate, curCmd, gAudioCtx.audioBufferParameters.updatesPerFrame - reverseUpdateIndex); @@ -844,7 +952,7 @@ Acmd* AudioSynth_ProcessSamples(s16* aiBuf, s32 numSamplesPerUpdate, Acmd* cmd, if (useReverb) { if ((reverb->filterLeft != NULL) || (reverb->filterRight != NULL)) { - cmd = AudioSynth_FilterReverb(cmd, numSamplesPerUpdate * SAMPLE_SIZE, reverb); + //cmd = AudioSynth_FilterReverb(cmd, numSamplesPerUpdate * SAMPLE_SIZE, reverb); } // Saves the wet channel sample from DMEM (DMEM_WET_LEFT_CH) into (ringBuffer) DRAM for future use @@ -1138,7 +1246,7 @@ Acmd* AudioSynth_ProcessSample(s32 noteIndex, NoteSampleState* sampleState, Note (numSamplesToLoadAdj + SAMPLES_PER_FRAME) * SAMPLE_SIZE); flags = A_CONTINUE; skipBytes = 0; - numSamplesProcessed = numSamplesToLoadAdj; + numSamplesProcessed += numSamplesToLoadAdj; dmemUncompressedAddrOffset1 = numSamplesToLoadAdj; goto skip; @@ -1147,8 +1255,19 @@ Acmd* AudioSynth_ProcessSample(s32 noteIndex, NoteSampleState* sampleState, Note (numSamplesToLoadAdj + SAMPLES_PER_FRAME) * SAMPLE_SIZE); flags = A_CONTINUE; skipBytes = 0; - numSamplesProcessed = numSamplesToLoadAdj; + numSamplesProcessed += numSamplesToLoadAdj; dmemUncompressedAddrOffset1 = numSamplesToLoadAdj; + size_t bytesToRead; + + if (((synthState->samplePosInt * 2) + (numSamplesToLoadAdj + SAMPLES_PER_FRAME) * SAMPLE_SIZE) < + sample->size) { + bytesToRead = (numSamplesToLoadAdj + SAMPLES_PER_FRAME) * SAMPLE_SIZE; + } else { + bytesToRead = sample->size - (synthState->samplePosInt * 2); + } + + aLoadBuffer(cmd++, sampleAddr + (synthState->samplePosInt * 2), DMEM_UNCOMPRESSED_NOTE, bytesToRead); + goto skip; default: @@ -1184,10 +1303,15 @@ Acmd* AudioSynth_ProcessSample(s32 noteIndex, NoteSampleState* sampleState, Note // Move the raw sample chunk from ram to the rsp // DMEM at the addresses before DMEM_COMPRESSED_ADPCM_DATA - sampleDataChunkAlignPad = (uintptr_t)samplesToLoadAddr & 0xF; + sampleDataChunkAlignPad = 0;//(uintptr_t)samplesToLoadAddr & 0xF; sampleDataChunkSize = ALIGN16((numFramesToDecode * frameSize) + SAMPLES_PER_FRAME); sampleDataDmemAddr = DMEM_COMPRESSED_ADPCM_DATA - sampleDataChunkSize; + uintptr_t actualAddrLoaded = samplesToLoadAddr - sampleDataChunkAlignPad; + uintptr_t offset = actualAddrLoaded - (uintptr_t)sampleAddr; + if (offset + sampleDataChunkSize > sample->size) { + sampleDataChunkSize -= (offset + sampleDataChunkSize - sample->size); + } // BEN: This will crash the asan. We can just ignore alignment since we don't have those strictures. // if (sampleDataChunkSize + sampleAddrOffset > sample->size) { // sampleDataChunkSize = sample->size - sampleAddrOffset; @@ -1297,6 +1421,14 @@ Acmd* AudioSynth_ProcessSample(s32 noteIndex, NoteSampleState* sampleState, Note } finished = true; note->sampleState.bitField0.finished = true; + + if (sampleState->bitField0.ignoreNoteState) { + sampleState->bitField0.enabled = false; + note->sampleState.bitField0.enabled = false; + TunedSamplePool* entry = AudioSamplePool_FindElemenyByPtr(sampleState->tunedSample->sample); + entry->inUse = false; + } + AudioSynth_DisableSampleStates(updateIndex, noteIndex); break; // break out of the for-loop } else if (loopToPoint) { From a28b2eb2a2167f950c27afb7a2a499f28f3c4a0a Mon Sep 17 00:00:00 2001 From: louist103 <35883445+louist103@users.noreply.github.com> Date: Thu, 18 Jul 2024 18:02:48 -0400 Subject: [PATCH 19/25] Add missing drwav define --- mm/2s2h/resource/importer/AudioSequenceFactory.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/mm/2s2h/resource/importer/AudioSequenceFactory.cpp b/mm/2s2h/resource/importer/AudioSequenceFactory.cpp index ee78a7dfec..990e33936d 100644 --- a/mm/2s2h/resource/importer/AudioSequenceFactory.cpp +++ b/mm/2s2h/resource/importer/AudioSequenceFactory.cpp @@ -4,6 +4,7 @@ #include "StringHelper.h" #include "libultraship/libultraship.h" +#define DR_WAV_IMPLEMENTATION #include "dr_wav.h" namespace SOH { From 2af3784c6a481fa45080c3b18cd54e67ab2e07bc Mon Sep 17 00:00:00 2001 From: louist103 <35883445+louist103@users.noreply.github.com> Date: Thu, 18 Jul 2024 18:11:10 -0400 Subject: [PATCH 20/25] Make mac happy --- mm/2s2h/Enhancements/Audio/AudioEditor.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mm/2s2h/Enhancements/Audio/AudioEditor.cpp b/mm/2s2h/Enhancements/Audio/AudioEditor.cpp index 7d78c71e76..35371320cc 100644 --- a/mm/2s2h/Enhancements/Audio/AudioEditor.cpp +++ b/mm/2s2h/Enhancements/Audio/AudioEditor.cpp @@ -661,7 +661,7 @@ void AudioEditor::DrawElement() { ImGui::SameLine(); DrawTypeChip(seqInfo->category); ImGui::SameLine(); - ImGui::Text("%s", seqInfo->label); + ImGui::Text("%s", seqInfo->label.c_str()); } } ImGui::EndChild(); @@ -685,7 +685,7 @@ void AudioEditor::DrawElement() { ImGui::SameLine(); DrawTypeChip(seqInfo->category); ImGui::SameLine(); - ImGui::Text("%s", seqInfo->label); + ImGui::Text("%s", seqInfo->label.c_str()); } } ImGui::EndChild(); From 40afc2fc66c40cb4e3ae4274dbb2a1d8177c69c0 Mon Sep 17 00:00:00 2001 From: louist103 <35883445+louist103@users.noreply.github.com> Date: Fri, 19 Jul 2024 21:15:32 -0400 Subject: [PATCH 21/25] Jaaaaaaaaaaaaaaaack --- mm/2s2h/BenPort.cpp | 6 +++ .../importer/AudioSequenceFactory.cpp | 45 +++++++++++------- mm/src/audio/lib/load.c | 33 ++++++++----- mm/src/audio/lib/synthesis.c | 9 +++- mm/src/audio/sequence.c | 47 +++++++++++++++++++ 5 files changed, 109 insertions(+), 31 deletions(-) diff --git a/mm/2s2h/BenPort.cpp b/mm/2s2h/BenPort.cpp index 9b0359b1e1..2cb653f129 100644 --- a/mm/2s2h/BenPort.cpp +++ b/mm/2s2h/BenPort.cpp @@ -1129,6 +1129,12 @@ extern "C" SequenceData ResourceMgr_LoadSeqByName(const char* path) { SequenceData* sequence = (SequenceData*)ResourceGetDataByName(path); return *sequence; } + +extern "C" SequenceData* ResourceMgr_LoadSeqPtrByName(const char* path) { + SequenceData* sequence = (SequenceData*)ResourceGetDataByName(path); + return sequence; +} + extern "C" KeyFrameSkeleton* ResourceMgr_LoadKeyFrameSkelByName(const char* path) { return (KeyFrameSkeleton*)ResourceGetDataByName(path); } diff --git a/mm/2s2h/resource/importer/AudioSequenceFactory.cpp b/mm/2s2h/resource/importer/AudioSequenceFactory.cpp index 990e33936d..e37b94645a 100644 --- a/mm/2s2h/resource/importer/AudioSequenceFactory.cpp +++ b/mm/2s2h/resource/importer/AudioSequenceFactory.cpp @@ -109,22 +109,35 @@ std::shared_ptr ResourceFactoryXMLAudioSequenceV0::ReadResource sequence->sequenceData = *seqFile->Buffer.get(); sequence->sequence.seqData = sequence->sequenceData.data(); } else { - drwav wav; - drwav_uint64 numFrames; - - drwav_bool32 ret = drwav_init_memory(&wav, seqFile->Buffer.get()->data(), seqFile->Buffer.get()->size(), nullptr); - - drwav_get_length_in_pcm_frames(&wav, &numFrames); - - sequence->sampleRate = wav.fmt.sampleRate; - sequence->numChannels = wav.fmt.channels; - sequence->customSeqData = std::make_unique(numFrames); - drwav_read_pcm_frames_s16(&wav, numFrames, sequence->customSeqData.get()); - - sequence->sequence.seqData = (char*)sequence->customSeqData.get(); - // Write a marker to show this is a streamed audio file - memcpy(sequence->sequence.fonts, "07151129", sizeof("07151129")); - drwav_uninit(&wav); + for (size_t i = 0;customStr[i] != 0;i++) { + const_cast(customStr)[i] = tolower(customStr[i]); + } + if (strcmp(customStr, "wav") == 0) { + drwav wav; + drwav_uint64 numFrames; + + drwav_bool32 ret = + drwav_init_memory(&wav, seqFile->Buffer.get()->data(), seqFile->Buffer.get()->size(), nullptr); + + drwav_get_length_in_pcm_frames(&wav, &numFrames); + + sequence->sampleRate = wav.fmt.sampleRate; + sequence->numChannels = wav.fmt.channels; + sequence->customSeqData = std::make_unique(numFrames); + drwav_read_pcm_frames_s16(&wav, numFrames, sequence->customSeqData.get()); + + sequence->sequence.seqData = (char*)sequence->customSeqData.get(); + // Write a marker to show this is a streamed audio file + memcpy(sequence->sequence.fonts, "07151129", sizeof("07151129")); + drwav_uninit(&wav); + } else if (strcmp(customStr, "mp3") == 0) { + // BENTODO + } else if (strcmp(customStr, "flac") == 0) { + // BENTODO + } else { + throw std::runtime_error( + StringHelper::Sprintf("Bad audio format value. Got %s, expected wav, mp3, or flac.", customStr)); + } } return sequence; } diff --git a/mm/src/audio/lib/load.c b/mm/src/audio/lib/load.c index b4597de467..e8fda00a33 100644 --- a/mm/src/audio/lib/load.c +++ b/mm/src/audio/lib/load.c @@ -70,6 +70,7 @@ void AudioLoad_RelocateFontAndPreloadSamples(s32 fontId, SoundFontData* fontData s32 AudioLoad_ProcessSamplePreloads(s32 resetStatus); SequenceData ResourceMgr_LoadSeqByName(const char* path); +SequenceData* ResourceMgr_LoadSeqPtrByName(const char* path); SoundFont* ResourceMgr_LoadAudioSoundFont(const char* path); // TODO: what's that for? it seems to rely on an uninitizalied variable in soh @@ -623,11 +624,26 @@ s32 AudioLoad_SyncInitSeqPlayerInternal(s32 playerIndex, s32 seqId, s32 arg2) { AudioLoad_SyncLoadFont(fontId); } + // BENTODO leave a note seqData = AudioLoad_SyncLoadSeq(seqId); + if (seqData == 0x1) { + seqPlayer->seqId = seqId; + gActiveSeqs[playerIndex].seqId = seqId; + seqPlayer->seqData = seqData; + seqPlayer->enabled = true; + seqPlayer->scriptState.depth = 0; + seqPlayer->delay = 0; + seqPlayer->finished = false; + seqPlayer->playerIndex = playerIndex; + return 1; + } + if (seqData == NULL) { return 0; } + + AudioScript_ResetSequencePlayer(seqPlayer); seqPlayer->seqId = seqId; @@ -771,14 +787,8 @@ void* AudioLoad_SyncLoad(s32 tableType, u32 id, s32* didAllocate) { if (tableType == SEQUENCE_TABLE) { // 2S2H [Port] Custom Audio. Read audio data from the archive and if needed, handle streamed audio. SequenceData sData = ResourceMgr_LoadSeqByName(sequenceMap[id]); - // Streamed audio if (memcmp(sData.fonts, "07151129", sizeof("07151129")) == 0) { - // BENTODO Why do we need to add this 4 times? - AudioQueue_Enqueue(sequenceMap[id]); - AudioQueue_Enqueue(sequenceMap[id]); - AudioQueue_Enqueue(sequenceMap[id]); - AudioQueue_Enqueue(sequenceMap[id]); - return; + return (uintptr_t)1; } seqData = sData.seqData; size = sData.seqDataSize; @@ -1135,9 +1145,6 @@ int strcmp_sort(const void* str1, const void* str2) { return strcmp(*pp1, *pp2); } -char** sequenceMap; -size_t sequenceMapSize; -char* fontMap[256]; extern AudioContext gAudioCtx; // #end region void AudioLoad_Init(void* heap, size_t heapSize) { @@ -1281,13 +1288,13 @@ void AudioLoad_Init(void* heap, size_t heapSize) { } int j = i - startingSeqNum; AudioCollection_AddToCollection(customSeqList[j], seqNum); - SequenceData sDat = ResourceMgr_LoadSeqByName(customSeqList[j]); - sDat.seqNumber = seqNum; + SequenceData* sDat = ResourceMgr_LoadSeqPtrByName(customSeqList[j]); + sDat->seqNumber = seqNum; char* str = malloc(strlen(customSeqList[j]) + 1); strcpy(str, customSeqList[j]); - sequenceMap[sDat.seqNumber] = str; + sequenceMap[sDat->seqNumber] = str; seqNum++; } diff --git a/mm/src/audio/lib/synthesis.c b/mm/src/audio/lib/synthesis.c index 727d6fb960..8eb1c3ddb5 100644 --- a/mm/src/audio/lib/synthesis.c +++ b/mm/src/audio/lib/synthesis.c @@ -251,7 +251,7 @@ static TunedSamplePool* AudioSamplePool_CreateNew(void) { return cur; } -static TunedSamplePool* AudioSamplePool_FindElemenyByPtr(Sample* key) { +TunedSamplePool* AudioSamplePool_FindElemenyByPtr(Sample* key) { TunedSamplePool* cur = &samplePool; while (cur->next != NULL && cur->inUse == true) { @@ -265,6 +265,8 @@ static TunedSamplePool* AudioSamplePool_FindElemenyByPtr(Sample* key) { } + + void Audio_LoadCustomBgm(int reverseUpdateIndex, uint64_t numFrames, uint32_t numChannels, uint32_t sampleRate, s16* sampleData) { int updateIndex = (gAudioCtx.audioBufferParameters.updatesPerFrame - reverseUpdateIndex) * gAudioCtx.numNotes; for (int i = 1; i < gAudioCtx.numNotes; i++) { @@ -985,6 +987,8 @@ Acmd* AudioSynth_ProcessSamples(s16* aiBuf, s32 numSamplesPerUpdate, Acmd* cmd, return cmd; } +uint8_t gForceStopSeq = false; + Acmd* AudioSynth_ProcessSample(s32 noteIndex, NoteSampleState* sampleState, NoteSynthesisState* synthState, s16* aiBuf, s32 numSamplesPerUpdate, Acmd* cmd, s32 updateIndex) { s32 pad1[2]; @@ -1414,7 +1418,8 @@ Acmd* AudioSynth_ProcessSample(s32 noteIndex, NoteSampleState* sampleState, Note skip: // Update what to do with the samples next - if (sampleFinished) { + if (sampleFinished || gForceStopSeq) { + gForceStopSeq = false; if ((numSamplesToLoadAdj - numSamplesProcessed) != 0) { AudioSynth_ClearBuffer(cmd++, DMEM_UNCOMPRESSED_NOTE + dmemUncompressedAddrOffset1, (numSamplesToLoadAdj - numSamplesProcessed) * SAMPLE_SIZE); diff --git a/mm/src/audio/sequence.c b/mm/src/audio/sequence.c index 791daeaaeb..536c1a165c 100644 --- a/mm/src/audio/sequence.c +++ b/mm/src/audio/sequence.c @@ -35,11 +35,34 @@ u8 gAudioSpecId = 0; u8 gAudioHeapResetState = AUDIO_HEAP_RESET_STATE_NONE; u32 sResetAudioHeapSeqCmd = 0; +// 2S2H [Port] Part of streamed audio support +extern SequenceData ResourceMgr_LoadSeqByName(const char* path); +extern char** sequenceMap; +extern size_t sequenceMapSize; + +bool AudioSeq_IsSeqStreamedByPath(char* path) { + SequenceData sData = ResourceMgr_LoadSeqByName(path); + // Streamed audio + return memcmp(sData.fonts, "07151129", sizeof("07151129")) == 0; +} + +bool AudioSeq_IsSeqStreamedById(u16 seqId) { + if (seqId > sequenceMapSize) { + return false; + } + return AudioSeq_IsSeqStreamedByPath(sequenceMap[seqId]); +} + +SequenceData* ResourceMgr_LoadSeqPtrByName(const char* path); +void AudioQueue_Enqueue(char* seqId); + void AudioSeq_StartSequence(u8 seqPlayerIndex, u8 seqId, u8 seqArgs, u16 fadeInDuration) { u8 channelIndex; u16 skipTicks; s32 pad; + seqId = AudioEditor_GetReplacementSeq(seqId); + if (!sStartSeqDisabled || (seqPlayerIndex == SEQ_PLAYER_SFX)) { seqArgs &= 0x7F; if (seqArgs == 0x7F) { @@ -52,6 +75,20 @@ void AudioSeq_StartSequence(u8 seqPlayerIndex, u8 seqId, u8 seqArgs, u16 fadeInD (fadeInDuration * (u16)gAudioCtx.audioBufferParameters.updatesPerFrame) / 4); } + SequenceData* sData = ResourceMgr_LoadSeqPtrByName(sequenceMap[seqId]); + + if (memcmp(sData->fonts, "07151129", sizeof("07151129")) == 0) { + // BENTODO Why do we need to add this 4 times? + AudioQueue_Enqueue(sequenceMap[seqId]); + AudioQueue_Enqueue(sequenceMap[seqId]); + AudioQueue_Enqueue(sequenceMap[seqId]); + AudioQueue_Enqueue(sequenceMap[seqId]); + AudioQueue_Enqueue(sequenceMap[seqId]); + AudioQueue_Enqueue(sequenceMap[seqId]); + AudioQueue_Enqueue(sequenceMap[seqId]); + AudioQueue_Enqueue(sequenceMap[seqId]); + } + gActiveSeqs[seqPlayerIndex].seqId = seqId | (seqArgs << 8); gActiveSeqs[seqPlayerIndex].prevSeqId = seqId | (seqArgs << 8); gActiveSeqs[seqPlayerIndex].isSeqPlayerInit = true; @@ -75,10 +112,20 @@ void AudioSeq_StartSequence(u8 seqPlayerIndex, u8 seqId, u8 seqArgs, u16 fadeInD gActiveSeqs[seqPlayerIndex].volChannelFlags = 0; } } +extern uint8_t gForceStopSeq; void AudioSeq_StopSequence(u8 seqPlayerIndex, u16 fadeOutDuration) { AUDIOCMD_GLOBAL_DISABLE_SEQPLAYER(seqPlayerIndex, (fadeOutDuration * (u16)gAudioCtx.audioBufferParameters.updatesPerFrame) / 4); + + if (AudioSeq_IsSeqStreamedById(gActiveSeqs[seqPlayerIndex].seqId)) { + SequenceData sData = ResourceMgr_LoadSeqByName(sequenceMap[gActiveSeqs[seqPlayerIndex].seqId]); + //TUnedSa + //extern uint8_t gForceStopSeq; + gForceStopSeq = true; + } + + gActiveSeqs[seqPlayerIndex].seqId = NA_BGM_DISABLED; } From 58e0966f48c78ad1a45dbd1c32f893b8ecd8b768 Mon Sep 17 00:00:00 2001 From: Nicholas Estelami Date: Sun, 21 Jul 2024 17:05:56 -0400 Subject: [PATCH 22/25] Fix music not playing during scene switches --- mm/include/z64audio.h | 1 + mm/src/audio/code_8019AF00.c | 3 +++ mm/src/audio/lib/load.c | 2 +- mm/src/audio/lib/synthesis.c | 27 ++++++++++++++++++++++++--- mm/src/audio/sequence.c | 34 +++++++++++++++++++++++----------- 5 files changed, 52 insertions(+), 15 deletions(-) diff --git a/mm/include/z64audio.h b/mm/include/z64audio.h index a4d02d82f0..247d700d6a 100644 --- a/mm/include/z64audio.h +++ b/mm/include/z64audio.h @@ -802,6 +802,7 @@ typedef struct { /* 0x219 */ u8 setupCmdNum; // number of setup commands /* 0x21A */ u8 setupFadeTimer; /* 0x21B */ u8 isSeqPlayerInit; + /* 0x21B */ u8 initCustomSeq; } ActiveSequence; // size = 0x21C typedef struct { diff --git a/mm/src/audio/code_8019AF00.c b/mm/src/audio/code_8019AF00.c index 134d9c1eef..5564306a6c 100644 --- a/mm/src/audio/code_8019AF00.c +++ b/mm/src/audio/code_8019AF00.c @@ -5762,6 +5762,9 @@ void Audio_SetSequenceMode(u8 seqMode) { seqId = gActiveSeqs[SEQ_PLAYER_BGM_MAIN].seqId; + if (seqId >= ARRAY_COUNT(sSeqFlags)) + seqId = ARRAY_COUNT(sSeqFlags) - 1; + if ((seqId == NA_BGM_DISABLED) || (sSeqFlags[(u8)(seqId & 0xFF)] & SEQ_FLAG_ENEMY) || ((sPrevSeqMode & 0x7F) == SEQ_MODE_ENEMY)) { if (seqMode != (sPrevSeqMode & 0x7F)) { diff --git a/mm/src/audio/lib/load.c b/mm/src/audio/lib/load.c index e8fda00a33..a2f75c7d0c 100644 --- a/mm/src/audio/lib/load.c +++ b/mm/src/audio/lib/load.c @@ -610,7 +610,7 @@ s32 AudioLoad_SyncInitSeqPlayerInternal(s32 playerIndex, s32 seqId, s32 arg2) { AudioScript_SequencePlayerDisable(seqPlayer); fontId = 0xFF; - if (gAudioCtx.seqReplaced[playerIndex]) { + if (gAudioCtx.seqReplaced[playerIndex] && seqId < ARRAY_COUNT(seqCachePolicyMap)) { authCachePolicy = seqCachePolicyMap[seqId]; seqId = gAudioCtx.seqToPlay[playerIndex]; } diff --git a/mm/src/audio/lib/synthesis.c b/mm/src/audio/lib/synthesis.c index 8eb1c3ddb5..9d315a3db1 100644 --- a/mm/src/audio/lib/synthesis.c +++ b/mm/src/audio/lib/synthesis.c @@ -886,6 +886,12 @@ Acmd* AudioSynth_ProcessSamples(s16* aiBuf, s32 numSamplesPerUpdate, Acmd* cmd, for (reverbIndex = 0; reverbIndex < gAudioCtx.numSynthesisReverbs; reverbIndex++) { for (i = 0; i < gAudioCtx.numNotes; i++) { sampleState = &gAudioCtx.sampleStateList[sampleStateOffset + i]; + if (sampleState->tunedSample != NULL && + sampleState->tunedSample->sample != NULL && sampleState->tunedSample->sample->codec == CODEC_S16) + { + int bp = 0; + } + if (sampleState->bitField0.enabled && (sampleState->bitField1.reverbIndex == reverbIndex)) { noteIndices[noteCount++] = i; } @@ -894,6 +900,12 @@ Acmd* AudioSynth_ProcessSamples(s16* aiBuf, s32 numSamplesPerUpdate, Acmd* cmd, for (i = 0; i < gAudioCtx.numNotes; i++) { sampleState = &gAudioCtx.sampleStateList[sampleStateOffset + i]; + + if (sampleState->tunedSample != NULL && + sampleState->tunedSample->sample != NULL && sampleState->tunedSample->sample->codec == CODEC_S16) { + int bp = 0; + } + if (sampleState->bitField0.enabled && (sampleState->bitField1.reverbIndex >= gAudioCtx.numSynthesisReverbs)) { noteIndices[noteCount++] = i; @@ -1105,6 +1117,11 @@ Acmd* AudioSynth_ProcessSample(s32 noteIndex, NoteSampleState* sampleState, Note sampleEndPos = loopInfo->loopEnd; } + + if (sample->codec == CODEC_S16) { + int bp = 0; + } + sampleAddr = sample->sampleAddr; numSamplesToLoadFirstPart = 0; @@ -1418,8 +1435,8 @@ Acmd* AudioSynth_ProcessSample(s32 noteIndex, NoteSampleState* sampleState, Note skip: // Update what to do with the samples next - if (sampleFinished || gForceStopSeq) { - gForceStopSeq = false; + if (sampleFinished || gForceStopSeq) + { if ((numSamplesToLoadAdj - numSamplesProcessed) != 0) { AudioSynth_ClearBuffer(cmd++, DMEM_UNCOMPRESSED_NOTE + dmemUncompressedAddrOffset1, (numSamplesToLoadAdj - numSamplesProcessed) * SAMPLE_SIZE); @@ -1431,9 +1448,13 @@ Acmd* AudioSynth_ProcessSample(s32 noteIndex, NoteSampleState* sampleState, Note sampleState->bitField0.enabled = false; note->sampleState.bitField0.enabled = false; TunedSamplePool* entry = AudioSamplePool_FindElemenyByPtr(sampleState->tunedSample->sample); - entry->inUse = false; + + if (entry != NULL) + entry->inUse = false; } + gForceStopSeq = false; + AudioSynth_DisableSampleStates(updateIndex, noteIndex); break; // break out of the for-loop } else if (loopToPoint) { diff --git a/mm/src/audio/sequence.c b/mm/src/audio/sequence.c index 536c1a165c..daae707df7 100644 --- a/mm/src/audio/sequence.c +++ b/mm/src/audio/sequence.c @@ -75,19 +75,11 @@ void AudioSeq_StartSequence(u8 seqPlayerIndex, u8 seqId, u8 seqArgs, u16 fadeInD (fadeInDuration * (u16)gAudioCtx.audioBufferParameters.updatesPerFrame) / 4); } - SequenceData* sData = ResourceMgr_LoadSeqPtrByName(sequenceMap[seqId]); + SequenceData* sData = ResourceMgr_LoadSeqPtrByName(sequenceMap[seqId]); if (memcmp(sData->fonts, "07151129", sizeof("07151129")) == 0) { - // BENTODO Why do we need to add this 4 times? - AudioQueue_Enqueue(sequenceMap[seqId]); - AudioQueue_Enqueue(sequenceMap[seqId]); - AudioQueue_Enqueue(sequenceMap[seqId]); - AudioQueue_Enqueue(sequenceMap[seqId]); - AudioQueue_Enqueue(sequenceMap[seqId]); - AudioQueue_Enqueue(sequenceMap[seqId]); - AudioQueue_Enqueue(sequenceMap[seqId]); - AudioQueue_Enqueue(sequenceMap[seqId]); - } + gActiveSeqs[seqPlayerIndex].initCustomSeq = 2; + } gActiveSeqs[seqPlayerIndex].seqId = seqId | (seqArgs << 8); gActiveSeqs[seqPlayerIndex].prevSeqId = seqId | (seqArgs << 8); @@ -595,6 +587,26 @@ void AudioSeq_UpdateActiveSequences(void) { gActiveSeqs[seqPlayerIndex].isSeqPlayerInit = false; } + if (gActiveSeqs[seqPlayerIndex].initCustomSeq > 0) + { + gActiveSeqs[seqPlayerIndex].initCustomSeq--; + + if (gActiveSeqs[seqPlayerIndex].initCustomSeq == 0) + { + seqId = gActiveSeqs[seqPlayerIndex].seqId & 0xFF; + + SequenceData* sData = ResourceMgr_LoadSeqPtrByName(sequenceMap[seqId]); + + if (memcmp(sData->fonts, "07151129", sizeof("07151129")) == 0) { + // BENTODO Why do we need to add this 4 times? + AudioQueue_Enqueue(sequenceMap[seqId]); + AudioQueue_Enqueue(sequenceMap[seqId]); + AudioQueue_Enqueue(sequenceMap[seqId]); + AudioQueue_Enqueue(sequenceMap[seqId]); + } + } + } + // The seqPlayer is no longer playing the active sequences if ((AudioSeq_GetActiveSeqId(seqPlayerIndex) != NA_BGM_DISABLED) && !gAudioCtx.seqPlayers[seqPlayerIndex].enabled && (!gActiveSeqs[seqPlayerIndex].isSeqPlayerInit)) { From 50e48a13bd809302e781bc768b069f420ac73bd2 Mon Sep 17 00:00:00 2001 From: louist103 <35883445+louist103@users.noreply.github.com> Date: Sun, 21 Jul 2024 20:53:56 -0400 Subject: [PATCH 23/25] Remove BPs --- mm/src/audio/lib/synthesis.c | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/mm/src/audio/lib/synthesis.c b/mm/src/audio/lib/synthesis.c index 9d315a3db1..4a4ef196a1 100644 --- a/mm/src/audio/lib/synthesis.c +++ b/mm/src/audio/lib/synthesis.c @@ -886,11 +886,7 @@ Acmd* AudioSynth_ProcessSamples(s16* aiBuf, s32 numSamplesPerUpdate, Acmd* cmd, for (reverbIndex = 0; reverbIndex < gAudioCtx.numSynthesisReverbs; reverbIndex++) { for (i = 0; i < gAudioCtx.numNotes; i++) { sampleState = &gAudioCtx.sampleStateList[sampleStateOffset + i]; - if (sampleState->tunedSample != NULL && - sampleState->tunedSample->sample != NULL && sampleState->tunedSample->sample->codec == CODEC_S16) - { - int bp = 0; - } + if (sampleState->bitField0.enabled && (sampleState->bitField1.reverbIndex == reverbIndex)) { noteIndices[noteCount++] = i; @@ -901,10 +897,6 @@ Acmd* AudioSynth_ProcessSamples(s16* aiBuf, s32 numSamplesPerUpdate, Acmd* cmd, for (i = 0; i < gAudioCtx.numNotes; i++) { sampleState = &gAudioCtx.sampleStateList[sampleStateOffset + i]; - if (sampleState->tunedSample != NULL && - sampleState->tunedSample->sample != NULL && sampleState->tunedSample->sample->codec == CODEC_S16) { - int bp = 0; - } if (sampleState->bitField0.enabled && (sampleState->bitField1.reverbIndex >= gAudioCtx.numSynthesisReverbs)) { @@ -1117,11 +1109,6 @@ Acmd* AudioSynth_ProcessSample(s32 noteIndex, NoteSampleState* sampleState, Note sampleEndPos = loopInfo->loopEnd; } - - if (sample->codec == CODEC_S16) { - int bp = 0; - } - sampleAddr = sample->sampleAddr; numSamplesToLoadFirstPart = 0; From 87f913412e1905122ec77a24df5b18a86d90c9da Mon Sep 17 00:00:00 2001 From: louist103 <35883445+louist103@users.noreply.github.com> Date: Sun, 21 Jul 2024 20:57:40 -0400 Subject: [PATCH 24/25] Format --- mm/2s2h/BenPort.cpp | 1 - mm/2s2h/Enhancements/Audio/AudioSeqQueue.cpp | 4 ++-- mm/2s2h/Enhancements/Audio/AudioSeqQueue.h | 4 ++-- mm/2s2h/dr_wav.h | 2 ++ mm/2s2h/resource/importer/AudioSequenceFactory.cpp | 4 +--- mm/2s2h/resource/importer/AudioSoundFontFactory.cpp | 13 ++++++------- 6 files changed, 13 insertions(+), 15 deletions(-) diff --git a/mm/2s2h/BenPort.cpp b/mm/2s2h/BenPort.cpp index 2cb653f129..aa8d02bf5b 100644 --- a/mm/2s2h/BenPort.cpp +++ b/mm/2s2h/BenPort.cpp @@ -209,7 +209,6 @@ OTRGlobals::OTRGlobals() { loader->RegisterResourceFactory(std::make_shared(), RESOURCE_FORMAT_BINARY, "AudioSample", static_cast(SOH::ResourceType::SOH_AudioSample), 2); - loader->RegisterResourceFactory(std::make_shared(), RESOURCE_FORMAT_BINARY, "AudioSoundFont", static_cast(SOH::ResourceType::SOH_AudioSoundFont), 2); diff --git a/mm/2s2h/Enhancements/Audio/AudioSeqQueue.cpp b/mm/2s2h/Enhancements/Audio/AudioSeqQueue.cpp index e6e66187cc..1b15dbe41d 100644 --- a/mm/2s2h/Enhancements/Audio/AudioSeqQueue.cpp +++ b/mm/2s2h/Enhancements/Audio/AudioSeqQueue.cpp @@ -19,7 +19,8 @@ int32_t AudioQueue_IsEmpty(void) { return audioQueue.isEmpty(); } -void AudioQueue_GetSeqInfo(const char* path, uint64_t* numFrames, uint32_t* numChannels, uint32_t* sampleRate, int16_t** sampleData) { +void AudioQueue_GetSeqInfo(const char* path, uint64_t* numFrames, uint32_t* numChannels, uint32_t* sampleRate, + int16_t** sampleData) { auto seqData = static_pointer_cast(Ship::Context::GetInstance()->GetResourceManager()->LoadResource(path)); if (numFrames != nullptr) { @@ -35,5 +36,4 @@ void AudioQueue_GetSeqInfo(const char* path, uint64_t* numFrames, uint32_t* numC } *sampleData = (s16*)seqData->sequence.seqData; } - } \ No newline at end of file diff --git a/mm/2s2h/Enhancements/Audio/AudioSeqQueue.h b/mm/2s2h/Enhancements/Audio/AudioSeqQueue.h index a4a2dadf7b..1076d6ac80 100644 --- a/mm/2s2h/Enhancements/Audio/AudioSeqQueue.h +++ b/mm/2s2h/Enhancements/Audio/AudioSeqQueue.h @@ -9,7 +9,6 @@ #include #include - // A threadsafe-queue. template class SafeQueue { public: @@ -57,7 +56,8 @@ extern "C" { void AudioQueue_Enqueue(char* seqId); char* AudioQueue_Dequeue(void); int32_t AudioQueue_IsEmpty(void); -void AudioQueue_GetSeqInfo(const char* path, uint64_t* numFrames, uint32_t* numChannels, uint32_t* sampleRate, int16_t** sampleData); +void AudioQueue_GetSeqInfo(const char* path, uint64_t* numFrames, uint32_t* numChannels, uint32_t* sampleRate, + int16_t** sampleData); #ifdef __cplusplus } diff --git a/mm/2s2h/dr_wav.h b/mm/2s2h/dr_wav.h index 0b75c21d85..1e1b912c58 100644 --- a/mm/2s2h/dr_wav.h +++ b/mm/2s2h/dr_wav.h @@ -1,4 +1,5 @@ #pragma once +// clang-format off /* WAV audio loader and writer. Choice of public domain or MIT-0. See license statements at the end of this file. dr_wav - v0.13.16 - 2024-02-27 @@ -8814,3 +8815,4 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +// clang-format on diff --git a/mm/2s2h/resource/importer/AudioSequenceFactory.cpp b/mm/2s2h/resource/importer/AudioSequenceFactory.cpp index e37b94645a..430768fd61 100644 --- a/mm/2s2h/resource/importer/AudioSequenceFactory.cpp +++ b/mm/2s2h/resource/importer/AudioSequenceFactory.cpp @@ -38,7 +38,6 @@ std::shared_ptr ResourceFactoryBinaryAudioSequenceV2::ReadResou return audioSequence; } - int8_t SOH::ResourceFactoryXMLAudioSequenceV0::MediumStrToInt(const char* str) { if (!strcmp("Ram", str)) { return 0; @@ -87,7 +86,6 @@ std::shared_ptr ResourceFactoryXMLAudioSequenceV0::ReadResource sequence->sequence.seqNumber = child->IntAttribute("Index"); const char* customStr = child->Attribute("CustomFormat"); - memset(sequence->sequence.fonts, 0, sizeof(sequence->sequence.fonts)); tinyxml2::XMLElement* fontsElement = child->FirstChildElement(); @@ -109,7 +107,7 @@ std::shared_ptr ResourceFactoryXMLAudioSequenceV0::ReadResource sequence->sequenceData = *seqFile->Buffer.get(); sequence->sequence.seqData = sequence->sequenceData.data(); } else { - for (size_t i = 0;customStr[i] != 0;i++) { + for (size_t i = 0; customStr[i] != 0; i++) { const_cast(customStr)[i] = tolower(customStr[i]); } if (strcmp(customStr, "wav") == 0) { diff --git a/mm/2s2h/resource/importer/AudioSoundFontFactory.cpp b/mm/2s2h/resource/importer/AudioSoundFontFactory.cpp index 3e4969e9d4..7793b97782 100644 --- a/mm/2s2h/resource/importer/AudioSoundFontFactory.cpp +++ b/mm/2s2h/resource/importer/AudioSoundFontFactory.cpp @@ -34,7 +34,7 @@ std::shared_ptr ResourceFactoryBinaryAudioSoundFontV2::ReadReso audioSoundFont->soundFont.numSfx = soundEffectCount; // 🥁 DRUMS 🥁 - //audioSoundFont->drums.reserve(audioSoundFont->soundFont.numDrums); + // audioSoundFont->drums.reserve(audioSoundFont->soundFont.numDrums); audioSoundFont->drumAddresses.reserve(audioSoundFont->soundFont.numDrums); for (uint32_t i = 0; i < audioSoundFont->soundFont.numDrums; i++) { Drum* drum = new Drum; @@ -64,7 +64,7 @@ std::shared_ptr ResourceFactoryBinaryAudioSoundFontV2::ReadReso drum->sound.sample = static_cast(res ? res->GetRawPointer() : nullptr); } - //audioSoundFont->drums.push_back(drum); + // audioSoundFont->drums.push_back(drum); // BENTODO clean this up in V3. if (drum->sound.sample == nullptr) { delete[] drum->envelope; @@ -91,7 +91,7 @@ std::shared_ptr ResourceFactoryBinaryAudioSoundFontV2::ReadReso uint32_t envelopeCount = reader->ReadInt32(); instrument->envelope = new AdsrEnvelope[envelopeCount]; - for (uint32_t j = 0; j ReadInt16(); int16_t arg = reader->ReadInt16(); @@ -238,7 +238,6 @@ void ResourceFactoryXMLSoundFontV0::ParseDrums(AudioSoundFont* soundFont, tinyxm drum->envelope = nullptr; } - if (drum->sound.sample == nullptr) { soundFont->drumAddresses.push_back(nullptr); } else { @@ -294,7 +293,7 @@ void SOH::ResourceFactoryXMLSoundFontV0::ParseInstruments(AudioSoundFont* soundF } instrumentElement = instrumentElement->NextSiblingElement(); } - + if (instrumentElement != nullptr && !strcmp("HighNotesSound", instrumentElement->Name())) { instrument->highNotesSound.tuning = instrumentElement->FloatAttribute("Tuning"); const char* sampleStr = instrumentElement->Attribute("SampleRef"); @@ -306,7 +305,7 @@ void SOH::ResourceFactoryXMLSoundFontV0::ParseInstruments(AudioSoundFont* soundF } soundFont->instrumentAddresses.push_back(instrument); - + element = instrumentElementCopy; element = (tinyxml2::XMLElement*)element->Parent(); element = element->NextSiblingElement(); @@ -344,7 +343,7 @@ void SOH::ResourceFactoryXMLSoundFontV0::ParseSfxTable(AudioSoundFont* soundFont std::vector SOH::ResourceFactoryXMLSoundFontV0::ParseEnvelopes(AudioSoundFont* soundFont, tinyxml2::XMLElement* element, - unsigned int* count) { + unsigned int* count) { std::vector envelopes; unsigned int total = 0; element = element->FirstChildElement("Envelope"); From 70487c5ed6d80a7d9da732d16c3fcdd805678777 Mon Sep 17 00:00:00 2001 From: louist103 <35883445+louist103@users.noreply.github.com> Date: Sat, 27 Jul 2024 00:22:24 -0400 Subject: [PATCH 25/25] More work --- .../Enhancements/Audio/AudioCollection.cpp | 17 +++++++++++ mm/2s2h/Enhancements/Audio/AudioCollection.h | 1 + mm/2s2h/Enhancements/Audio/AudioEditor.cpp | 4 +++ mm/2s2h/Enhancements/Audio/AudioEditor.h | 1 + mm/2s2h/Enhancements/Audio/AudioSeqQueue.cpp | 8 +++--- mm/2s2h/Enhancements/Audio/AudioSeqQueue.h | 8 ++++-- mm/src/audio/code_8019AF00.c | 17 +++++++---- mm/src/audio/lib/playback.c | 24 ++++++++++++---- mm/src/audio/lib/synthesis.c | 28 +++++++++++++++---- mm/src/audio/sequence.c | 10 +++---- mm/src/code/stubs.c | 1 - 11 files changed, 90 insertions(+), 29 deletions(-) diff --git a/mm/2s2h/Enhancements/Audio/AudioCollection.cpp b/mm/2s2h/Enhancements/Audio/AudioCollection.cpp index 2197d8afc4..d21b54c696 100644 --- a/mm/2s2h/Enhancements/Audio/AudioCollection.cpp +++ b/mm/2s2h/Enhancements/Audio/AudioCollection.cpp @@ -260,6 +260,23 @@ uint16_t AudioCollection::GetReplacementSequence(uint16_t seqId) { return static_cast(replacementSeq); } +uint16_t AudioCollection::GetOriginalSequence(uint16_t seqId) { + // BENTODO there is probably a better way to do this. + if (seqId <=0x7f) { + return seqId; + } + + for (const auto& a : mSequenceMap) { + const std::string cvarKey = GetCvarKey(a.second.sfxKey); + int replacementSeq = CVarGetInteger(cvarKey.c_str(), NA_BGM_DISABLED); + if (replacementSeq == seqId) { + return a.first; + } + } + return seqId; + +} + void AudioCollection::RemoveFromShufflePool(SequenceInfo* seqInfo) { const std::string cvarKey = std::string(CVAR_AUDIO("Excluded.")) + seqInfo->sfxKey; excludedSequences.insert(seqInfo); diff --git a/mm/2s2h/Enhancements/Audio/AudioCollection.h b/mm/2s2h/Enhancements/Audio/AudioCollection.h index 37d31c125b..f92a23f33b 100644 --- a/mm/2s2h/Enhancements/Audio/AudioCollection.h +++ b/mm/2s2h/Enhancements/Audio/AudioCollection.h @@ -61,6 +61,7 @@ class AudioCollection { void RemoveFromShufflePool(SequenceInfo*); void AddToCollection(char* otrPath, uint16_t seqNum); uint16_t GetReplacementSequence(uint16_t seqId); + uint16_t GetOriginalSequence(uint16_t seqId); void InitializeShufflePool(); const char* GetSequenceName(uint16_t seqId); bool HasSequenceNum(uint16_t seqId); diff --git a/mm/2s2h/Enhancements/Audio/AudioEditor.cpp b/mm/2s2h/Enhancements/Audio/AudioEditor.cpp index 35371320cc..d0a10ece33 100644 --- a/mm/2s2h/Enhancements/Audio/AudioEditor.cpp +++ b/mm/2s2h/Enhancements/Audio/AudioEditor.cpp @@ -363,6 +363,10 @@ extern "C" u16 AudioEditor_GetReplacementSeq(u16 seqId) { return AudioCollection::Instance->GetReplacementSequence(seqId); } +extern "C" u16 AudioEditor_GetOriginalSequence(u16 seqId) { + return AudioCollection::Instance->GetOriginalSequence(seqId); +} + const char* GetSequenceTypeName(SeqType type) { switch (type) { case SEQ_NOSHUFFLE: diff --git a/mm/2s2h/Enhancements/Audio/AudioEditor.h b/mm/2s2h/Enhancements/Audio/AudioEditor.h index 89e7c04bf9..9bbaef367e 100644 --- a/mm/2s2h/Enhancements/Audio/AudioEditor.h +++ b/mm/2s2h/Enhancements/Audio/AudioEditor.h @@ -37,6 +37,7 @@ extern "C" { #endif u16 AudioEditor_GetReplacementSeq(u16 seqId); +u16 AudioEditor_GetOriginaltSeq(u16 seqId); #ifdef __cplusplus } diff --git a/mm/2s2h/Enhancements/Audio/AudioSeqQueue.cpp b/mm/2s2h/Enhancements/Audio/AudioSeqQueue.cpp index 1b15dbe41d..388a4d31ce 100644 --- a/mm/2s2h/Enhancements/Audio/AudioSeqQueue.cpp +++ b/mm/2s2h/Enhancements/Audio/AudioSeqQueue.cpp @@ -3,15 +3,15 @@ #include "resource/type/AudioSequence.h" #include "Context.h" -static SafeQueue audioQueue; +static SafeQueue audioQueue; extern "C" { -void AudioQueue_Enqueue(char* seqId) { - audioQueue.enqueue(seqId); +void AudioQueue_Enqueue(QueuePair pair) { + audioQueue.enqueue(pair); } -char* AudioQueue_Dequeue(void) { +QueuePair AudioQueue_Dequeue(void) { return audioQueue.dequeue(); } diff --git a/mm/2s2h/Enhancements/Audio/AudioSeqQueue.h b/mm/2s2h/Enhancements/Audio/AudioSeqQueue.h index 1076d6ac80..80033c61ab 100644 --- a/mm/2s2h/Enhancements/Audio/AudioSeqQueue.h +++ b/mm/2s2h/Enhancements/Audio/AudioSeqQueue.h @@ -2,6 +2,10 @@ #define AUDIO_SEQ_QUEUE_H #include +typedef struct QueuePair { + char* path; + uint8_t seqPlayer; +} QueuePair; #ifdef __cplusplus @@ -53,8 +57,8 @@ template class SafeQueue { extern "C" { #endif -void AudioQueue_Enqueue(char* seqId); -char* AudioQueue_Dequeue(void); +void AudioQueue_Enqueue(QueuePair seqId); +QueuePair AudioQueue_Dequeue(void); int32_t AudioQueue_IsEmpty(void); void AudioQueue_GetSeqInfo(const char* path, uint64_t* numFrames, uint32_t* numChannels, uint32_t* sampleRate, int16_t** sampleData); diff --git a/mm/src/audio/code_8019AF00.c b/mm/src/audio/code_8019AF00.c index 5564306a6c..6dcdfa926a 100644 --- a/mm/src/audio/code_8019AF00.c +++ b/mm/src/audio/code_8019AF00.c @@ -2073,6 +2073,9 @@ const char sAudioOcarinaUnusedText7[] = "check is over!!! %d %d %d\n"; // BENTODO find a final place for this function // 2S2H [Port] Part of the audio editor +#define SEQCMD_PLAY_SEQUENCE(seqPlayerIndex, fadeInDuration, seqId) \ + AudioSeq_QueueSeqCmd((SEQCMD_OP_PLAY_SEQUENCE << 28) | ((seqPlayerIndex) << 24) | ((u32)(fadeInDuration) << 16) | \ + (u32)(seqId)) void PreviewSequence(u16 seqId) { u16 curSeqId = AudioSeq_GetActiveSeqId(SEQ_PLAYER_BGM_MAIN); @@ -2084,7 +2087,7 @@ void PreviewSequence(u16 seqId) { osSyncPrintf("Middle Boss BGM Start not stack \n"); } - AudioSeq_QueueSeqCmd(seqId); + SEQCMD_PLAY_SEQUENCE(SEQ_PLAYER_BGM_MAIN, 1, seqId); // } } @@ -5506,10 +5509,13 @@ void Audio_StartSceneSequence(u16 seqId) { sPrevSceneSeqId = seqId & 0xFF; } +extern uint16_t AudioEditor_GetOriginalSequence(uint16_t seqId); + void Audio_UpdateSceneSequenceResumePoint(void) { u16 seqId = AudioSeq_GetActiveSeqId(SEQ_PLAYER_BGM_MAIN); + seqId = AudioEditor_GetOriginalSequence(seqId); // BENTODO find a better fix - if (seqId >= ARRAY_COUNT(sSeqFlags) || (seqId != NA_BGM_DISABLED) && (sSeqFlags[seqId & 0xFF & 0xFF] & SEQ_FLAG_RESUME)) { + if ((seqId != NA_BGM_DISABLED) && (sSeqFlags[seqId & 0xFF & 0xFF] & SEQ_FLAG_RESUME)) { if (sSeqResumePoint != SEQ_RESUME_POINT_NONE) { // Get the current point to resume from sSeqResumePoint = gAudioCtx.seqPlayers[SEQ_PLAYER_BGM_MAIN].seqScriptIO[3]; @@ -5761,9 +5767,8 @@ void Audio_SetSequenceMode(u8 seqMode) { // clang-format on seqId = gActiveSeqs[SEQ_PLAYER_BGM_MAIN].seqId; + seqId = AudioEditor_GetOriginalSequence(seqId); - if (seqId >= ARRAY_COUNT(sSeqFlags)) - seqId = ARRAY_COUNT(sSeqFlags) - 1; if ((seqId == NA_BGM_DISABLED) || (sSeqFlags[(u8)(seqId & 0xFF)] & SEQ_FLAG_ENEMY) || ((sPrevSeqMode & 0x7F) == SEQ_MODE_ENEMY)) { @@ -5849,7 +5854,7 @@ void Audio_UpdateEnemyBgmVolume(f32 dist) { sBgmEnemyVolume = ((350.0f - adjDist) * 127.0f) / 350.0f; AudioSeq_SetVolumeScale(SEQ_PLAYER_BGM_SUB, VOL_SCALE_INDEX_BGM_SUB, sBgmEnemyVolume, 10); - + seqId = AudioEditor_GetOriginalSequence(seqId); if ((seqId >= NA_BGM_TERMINA_FIELD) && !(sSeqFlags[seqId & 0xFF & 0xFF] & SEQ_FLAG_FANFARE_KAMARO)) { AudioSeq_SetVolumeScale(SEQ_PLAYER_BGM_MAIN, VOL_SCALE_INDEX_BGM_SUB, (0x7F - sBgmEnemyVolume), 10); } @@ -6241,7 +6246,7 @@ void func_801A4348(void) { void Audio_SetSfxVolumeExceptSystemAndOcarinaBanks(u8 volume) { u8 channelIndex; - + printf("%d\n", volume); if (!sAllPlayersMutedExceptSystemAndOcarina) { for (channelIndex = 0; channelIndex < SEQ_NUM_CHANNELS; channelIndex++) { switch (channelIndex) { diff --git a/mm/src/audio/lib/playback.c b/mm/src/audio/lib/playback.c index b0c255353a..e95bd4b7ba 100644 --- a/mm/src/audio/lib/playback.c +++ b/mm/src/audio/lib/playback.c @@ -183,11 +183,12 @@ void AudioPlayback_ProcessNotes(void) { for (i = 0; i < gAudioCtx.numNotes; i++) { note = &gAudioCtx.notes[i]; sampleState = &gAudioCtx.sampleStateList[gAudioCtx.sampleStateOffset + i]; - // BENTODO: Verify there are no side effects from this... + playbackState = ¬e->playbackState; + + // BENTODO: Verify there are no side effects from this... if (sampleState->bitField0.ignoreNoteState) { - goto skip; + goto out; } - playbackState = ¬e->playbackState; if (playbackState->parentLayer != NO_LAYER) { // OTRTODO: This skips playback if the pointer is below where memory on the N64 normally would be. @@ -234,7 +235,7 @@ void AudioPlayback_ProcessNotes(void) { noteSampleState = ¬e->sampleState; if ((playbackState->status >= 1) || noteSampleState->bitField0.finished) { if ((playbackState->adsr.action.s.status == ADSR_STATUS_DISABLED) || - noteSampleState->bitField0.finished) { + noteSampleState->bitField0.finished && !sampleState->bitField0.ignoreNoteState) { if (playbackState->wantedParentLayer != NO_LAYER) { AudioPlayback_NoteDisable(note); if (playbackState->wantedParentLayer->channel != NULL) { @@ -272,6 +273,7 @@ void AudioPlayback_ProcessNotes(void) { continue; } + out2: adsrVolumeScale = AudioEffects_UpdateAdsr(&playbackState->adsr); AudioEffects_UpdatePortamentoAndVibrato(note); playbackStatus = playbackState->status; @@ -296,6 +298,13 @@ void AudioPlayback_ProcessNotes(void) { subAttrs.velocity = layer->noteVelocity; subAttrs.pan = layer->notePan; + if (sampleState->tunedSample != NULL && sampleState->tunedSample->sample != NULL && + sampleState->tunedSample->sample->codec == CODEC_S16) + { + subAttrs.velocity = layer->noteVelocity; + volatile int bp = 0; + } + if (layer->surroundEffectIndex == 0x80) { subAttrs.surroundEffectIndex = channel->surroundEffectIndex; } else { @@ -336,8 +345,11 @@ void AudioPlayback_ProcessNotes(void) { subAttrs.frequency *= playbackState->vibratoFreqScale * playbackState->portamentoFreqScale; subAttrs.frequency *= gAudioCtx.audioBufferParameters.resampleRate; subAttrs.velocity *= adsrVolumeScale; - AudioPlayback_InitSampleState(note, sampleState, &subAttrs); - noteSampleState->bitField1.bookOffset = bookOffset; + + if (!sampleState->bitField0.ignoreNoteState) + AudioPlayback_InitSampleState(note, sampleState, &subAttrs); + + noteSampleState->bitField1.bookOffset = bookOffset; skip:; } } diff --git a/mm/src/audio/lib/synthesis.c b/mm/src/audio/lib/synthesis.c index 4a4ef196a1..e5ab3a765d 100644 --- a/mm/src/audio/lib/synthesis.c +++ b/mm/src/audio/lib/synthesis.c @@ -267,11 +267,11 @@ TunedSamplePool* AudioSamplePool_FindElemenyByPtr(Sample* key) { -void Audio_LoadCustomBgm(int reverseUpdateIndex, uint64_t numFrames, uint32_t numChannels, uint32_t sampleRate, s16* sampleData) { +void Audio_LoadCustomBgm(int reverseUpdateIndex, uint64_t numFrames, uint32_t numChannels, uint32_t sampleRate, s16* sampleData, uint8_t seqPlayer) { int updateIndex = (gAudioCtx.audioBufferParameters.updatesPerFrame - reverseUpdateIndex) * gAudioCtx.numNotes; for (int i = 1; i < gAudioCtx.numNotes; i++) { NoteSampleState* sampleState = &gAudioCtx.sampleStateList[i + updateIndex]; - NoteSampleState* noteSampleState = &gAudioCtx.notes[i + updateIndex].sampleState; + NoteSampleState* noteSampleState = &gAudioCtx.notes[i + updateIndex].sampleState; if (!sampleState->bitField0.enabled) { sampleState->bitField0.enabled = true; sampleState->bitField0.ignoreNoteState = true; @@ -285,7 +285,6 @@ void Audio_LoadCustomBgm(int reverseUpdateIndex, uint64_t numFrames, uint32_t nu gAudioCtx.notes[i + updateIndex].playbackState.priority = 14; gAudioCtx.notes[i + updateIndex].playbackState.status = 1; - //// Did someone say ***memory leaks***? TunedSamplePool* poolEntry = AudioSamplePool_CreateNew(); sampleState->tunedSample = &poolEntry->tunedSample; @@ -308,6 +307,24 @@ void Audio_LoadCustomBgm(int reverseUpdateIndex, uint64_t numFrames, uint32_t nu sampleState->tunedSample->sample->loop->loopEnd = numFrames - 5; sampleState->tunedSample->sample->loop->count = -1; sampleState->tunedSample->sample->sampleAddr = sampleData; + + for (int j = 0; j < ARRAY_COUNT(gAudioCtx.seqPlayers[seqPlayer].channels); j++) { + SequenceChannel* channel = gAudioCtx.seqPlayers[seqPlayer].channels[j]; + if (!channel->enabled) { + memset(channel, 0, sizeof(SequenceChannel)); + channel->enabled = true; + channel->volume = 1.0f; + channel->layers[0] = malloc(sizeof(SequenceLayer)); + channel->layers[0]->enabled = true; + channel->layers[0]->tunedSample = sampleState->tunedSample; + channel->layers[0]->channel = channel; + channel->seqPlayer = &gAudioCtx.seqPlayers[seqPlayer]; + channel->layers[0]->note = &gAudioCtx.notes[i + updateIndex]; + channel->layers[0]->note->playbackState.parentLayer = channel->layers[0]; + break; + } + } + break; } } @@ -361,9 +378,10 @@ Acmd* AudioSynth_Update(Acmd* abiCmdStart, s32* numAbiCmds, s16* aiBufStart, s32 uint32_t numChannels; uint32_t sampleRate; s16* sampleData; - AudioQueue_GetSeqInfo(AudioQueue_Dequeue(), &numFrames, &numChannels, &sampleRate, &sampleData); + QueuePair pair = AudioQueue_Dequeue(); + AudioQueue_GetSeqInfo(pair.path, &numFrames, &numChannels, &sampleRate, &sampleData); - Audio_LoadCustomBgm(reverseUpdateIndex, numFrames, numChannels, sampleRate, sampleData); + Audio_LoadCustomBgm(reverseUpdateIndex, numFrames, numChannels, sampleRate, sampleData, pair.seqPlayer); } curCmd = AudioSynth_ProcessSamples(curAiBufPos, numSamplesPerUpdate, curCmd, diff --git a/mm/src/audio/sequence.c b/mm/src/audio/sequence.c index daae707df7..0dec934a95 100644 --- a/mm/src/audio/sequence.c +++ b/mm/src/audio/sequence.c @@ -19,6 +19,7 @@ */ #include "global.h" #include "2s2h/Enhancements/Audio/AudioEditor.h" +#include "2s2h/Enhancements/Audio/AudioSeqQueue.h" // Direct audio command (skips the queueing system) #define SEQCMD_SET_SEQPLAYER_VOLUME_NOW(seqPlayerIndex, duration, volume) \ @@ -54,7 +55,6 @@ bool AudioSeq_IsSeqStreamedById(u16 seqId) { } SequenceData* ResourceMgr_LoadSeqPtrByName(const char* path); -void AudioQueue_Enqueue(char* seqId); void AudioSeq_StartSequence(u8 seqPlayerIndex, u8 seqId, u8 seqArgs, u16 fadeInDuration) { u8 channelIndex; @@ -599,10 +599,10 @@ void AudioSeq_UpdateActiveSequences(void) { if (memcmp(sData->fonts, "07151129", sizeof("07151129")) == 0) { // BENTODO Why do we need to add this 4 times? - AudioQueue_Enqueue(sequenceMap[seqId]); - AudioQueue_Enqueue(sequenceMap[seqId]); - AudioQueue_Enqueue(sequenceMap[seqId]); - AudioQueue_Enqueue(sequenceMap[seqId]); + AudioQueue_Enqueue((QueuePair){sequenceMap[seqId], seqPlayerIndex}); + AudioQueue_Enqueue((QueuePair){sequenceMap[seqId], seqPlayerIndex}); + AudioQueue_Enqueue((QueuePair){sequenceMap[seqId], seqPlayerIndex}); + AudioQueue_Enqueue((QueuePair){sequenceMap[seqId], seqPlayerIndex}); } } } diff --git a/mm/src/code/stubs.c b/mm/src/code/stubs.c index dea4cedb92..b5bb0e7d71 100644 --- a/mm/src/code/stubs.c +++ b/mm/src/code/stubs.c @@ -56,7 +56,6 @@ u64 aspMainDataEnd[100]; u8 sNumSeqRequests[5]; u32 sAudioSeqCmds[0x100]; -ActiveSequence gActiveSeqs[5]; u8 sResetAudioHeapTimer; u16 sResetAudioHeapFadeReverbVolume; u16 sResetAudioHeapFadeReverbVolumeStep;