From 1102d50f8f8c3f1a0fabdcafd657ab1d4564b908 Mon Sep 17 00:00:00 2001 From: Jack Andersen Date: Tue, 10 May 2016 18:48:08 -1000 Subject: [PATCH] Work on Voice state and SurroundProfiles --- CMakeLists.txt | 14 +- driver/main.cpp | 501 ++++++++++++++++---- include/amuse/AudioGroup.hpp | 11 +- include/amuse/AudioGroupProject.hpp | 9 +- include/amuse/AudioGroupSampleDirectory.hpp | 6 +- include/amuse/BooBackend.hpp | 2 + include/amuse/Engine.hpp | 5 +- include/amuse/Entity.hpp | 19 +- include/amuse/IBackendVoice.hpp | 3 + include/amuse/IBackendVoiceAllocator.hpp | 17 +- include/amuse/SurroundProfiles.hpp | 24 + include/amuse/Voice.hpp | 58 ++- include/amuse/dsp.h | 34 ++ lib/AudioGroup.cpp | 11 +- lib/AudioGroupProject.cpp | 18 +- lib/AudioGroupSampleDirectory.cpp | 7 +- lib/BooBackend.cpp | 10 + lib/Engine.cpp | 64 ++- lib/SoundMacroState.cpp | 12 +- lib/SurroundProfiles.cpp | 172 +++++++ lib/Voice.cpp | 154 +++++- lib/dsp.c | 124 +++++ 22 files changed, 1099 insertions(+), 176 deletions(-) create mode 100644 include/amuse/SurroundProfiles.hpp create mode 100644 include/amuse/dsp.h create mode 100644 lib/SurroundProfiles.cpp create mode 100644 lib/dsp.c diff --git a/CMakeLists.txt b/CMakeLists.txt index ec4d5ce..56df182 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,7 +16,9 @@ set(SOURCES lib/EffectReverbHi.cpp lib/EffectReverbStd.cpp lib/EffectChorus.cpp - lib/EffectDelay.cpp) + lib/EffectDelay.cpp + lib/SurroundProfiles.cpp + lib/dsp.c) set(HEADERS include/amuse/AudioGroup.hpp @@ -41,12 +43,14 @@ set(HEADERS include/amuse/EffectReverbStd.hpp include/amuse/EffectChorus.hpp include/amuse/EffectDelay.hpp + include/amuse/SurroundProfiles.hpp include/amuse/Common.hpp - include/amuse/amuse.hpp) + include/amuse/amuse.hpp + include/amuse/dsp.h) unset(EXTRAS) if(TARGET boo) - include_directories(${BOO_INCLUDE_DIR} ${LOGVISOR_INCLUDE_DIR}) + include_directories(${BOO_INCLUDE_DIR} ${LOGVISOR_INCLUDE_DIR} ${ATHENA_INCLUDE_DIR}) list(APPEND EXTRAS lib/BooBackend.cpp include/amuse/BooBackend.hpp) endif() @@ -59,6 +63,6 @@ add_library(amuse ${EXTRAS}) if(TARGET boo) - add_executable(amusetool driver/main.cpp) - target_link_libraries(amusetool amuse boo ${BOO_SYS_LIBS} logvisor) + add_executable(amusetool WIN32 driver/main.cpp) + target_link_libraries(amusetool amuse boo ${BOO_SYS_LIBS} logvisor athena-core) endif() diff --git a/driver/main.cpp b/driver/main.cpp index 51e6232..075a266 100644 --- a/driver/main.cpp +++ b/driver/main.cpp @@ -1,110 +1,423 @@ #include "amuse/amuse.hpp" #include "amuse/BooBackend.hpp" +#include "athena/FileReader.hpp" +#include "boo/boo.hpp" #include "boo/audiodev/IAudioVoiceEngine.hpp" #include "logvisor/logvisor.hpp" +#include "optional.hpp" +#include +#include +#ifndef _WIN32 +#include +#include +#include +#else +#include +#endif #include +#include static logvisor::Module Log("amusetool"); -static amuse::IntrusiveAudioGroupData LoadFromArgs(int argc, char* argv[], +static amuse::IntrusiveAudioGroupData LoadFromArgs(int argc, const boo::SystemChar** argv, std::string& descOut, bool& good) { + if (argc > 1) + { + /* First attempt single-file case */ + std::experimental::optional r; + r.emplace(argv[1], 32 * 1024, false); + if (!r->hasError()) + { + char testBuf[6]; + if (r->readBytesToBuf(testBuf, 6) == 6) + { + if (!memcmp(testBuf, "Audio/", 6)) + { + /* Metroid Prime 1 container */ + r->seek(0, athena::SeekOrigin::Begin); + r->readString(); + descOut = "Metroid Prime (" + r->readString() + ")"; + + std::unique_ptr pool = r->readUBytes(r->readUint32Big()); + std::unique_ptr proj = r->readUBytes(r->readUint32Big()); + std::unique_ptr samp = r->readUBytes(r->readUint32Big()); + std::unique_ptr sdir = r->readUBytes(r->readUint32Big()); + + good = true; + return {proj.release(), pool.release(), sdir.release(), samp.release()}; + } + else if (amuse::SBig(*reinterpret_cast(testBuf)) == 0x1) + { + /* Metroid Prime 2 container */ + r->seek(0, athena::SeekOrigin::Begin); + r->readUint32Big(); + descOut = "Metroid Prime 2 (" + r->readString() + ")"; + + r->readUint16Big(); + uint32_t poolSz = r->readUint32Big(); + uint32_t projSz = r->readUint32Big(); + uint32_t sdirSz = r->readUint32Big(); + uint32_t sampSz = r->readUint32Big(); + + std::unique_ptr pool = r->readUBytes(poolSz); + std::unique_ptr proj = r->readUBytes(projSz); + std::unique_ptr sdir = r->readUBytes(sdirSz); + std::unique_ptr samp = r->readUBytes(sampSz); + + good = true; + return {proj.release(), pool.release(), sdir.release(), samp.release()}; + } + } + } + else + { + /* Now attempt multi-file case */ + char path[1024]; + + /* Project */ + snprintf(path, 1024, "%s.pro", argv[1]); + r.emplace(path, 32 * 1024, false); + if (r->hasError()) + { + snprintf(path, 1024, "%s.proj", argv[1]); + r.emplace(path, 32 * 1024, false); + if (r->hasError()) + return {nullptr, nullptr, nullptr, nullptr}; + } + std::unique_ptr proj = r->readUBytes(r->length()); + + /* Pool */ + snprintf(path, 1024, "%s.poo", argv[1]); + r.emplace(path, 32 * 1024, false); + if (r->hasError()) + { + snprintf(path, 1024, "%s.pool", argv[1]); + r.emplace(path, 32 * 1024, false); + if (r->hasError()) + return {nullptr, nullptr, nullptr, nullptr}; + } + std::unique_ptr pool = r->readUBytes(r->length()); + + /* Sdir */ + snprintf(path, 1024, "%s.sdi", argv[1]); + r.emplace(path, 32 * 1024, false); + if (r->hasError()) + { + snprintf(path, 1024, "%s.sdir", argv[1]); + r.emplace(path, 32 * 1024, false); + if (r->hasError()) + return {nullptr, nullptr, nullptr, nullptr}; + } + std::unique_ptr sdir = r->readUBytes(r->length()); + + /* Samp */ + snprintf(path, 1024, "%s.sam", argv[1]); + r.emplace(path, 32 * 1024, false); + if (r->hasError()) + { + snprintf(path, 1024, "%s.samp", argv[1]); + r.emplace(path, 32 * 1024, false); + if (r->hasError()) + return {nullptr, nullptr, nullptr, nullptr}; + } + std::unique_ptr samp = r->readUBytes(r->length()); + + descOut = "4 RAW Chunk Files"; + good = true; + return {proj.release(), pool.release(), sdir.release(), samp.release()}; + } + } + return {nullptr, nullptr, nullptr, nullptr}; } -int main(int argc, char* argv[]) +struct AppCallback; + +struct EventCallback : boo::IWindowCallback +{ + AppCallback& m_app; +public: + void charKeyDown(unsigned long charCode, boo::EModifierKey mods, bool isRepeat); + void charKeyUp(unsigned long charCode, boo::EModifierKey mods); + void specialKeyDown(boo::ESpecialKey key, boo::EModifierKey mods, bool isRepeat); + void specialKeyUp(boo::ESpecialKey key, boo::EModifierKey mods); + void resized(const boo::SWindowRect&, const boo::SWindowRect&) {} + + EventCallback(AppCallback& app) : m_app(app) {} +}; + +struct AppCallback : boo::IApplicationCallback +{ + int m_argc; + const boo::SystemChar** m_argv; + + /* Boo window and events */ + EventCallback m_eventRec; + boo::DeferredWindowEvents m_events; + boo::IWindow* m_win = nullptr; + + /* Amuse engine */ + std::experimental::optional m_engine; + int m_groupId; + bool m_sfxGroup; + + /* Song playback selection */ + int m_setupId = -1; + int m_chanId = -1; + + /* SFX playback selection */ + int m_sfxId = -1; + amuse::Voice* m_vox = nullptr; + + /* Control state */ + bool m_running = true; + bool m_wantsNext = false; + bool m_wantsPrev = false; + + void SongLoop(const amuse::AudioGroup& group, + const amuse::SongGroupIndex& index) + { + printf("░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░\n" + "░░░ ████ ████ ┃ ████ ████ ████ ┃ ████ ████ ░░░\n" + "░░░ ████ ████ ┃ ████ ████ ████ ┃ ████ ████ ░░░\n" + "░░░ ▌W▐█ ▌E▐█ ┃ ▌T▐█ ▌Y▐█ ▌U▐█ ┃ ▌O▐█ ▌P▐█ ░░░\n" + "░░░ │ │ ┃ │ │ │ ┃ │ │ ░░░\n" + "░░░ A │ S │ D ┃ F │ G │ H │ J ┃ K │ L │ ; ░░░\n" + "░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░\n" + ": cycle MIDI setup / channel, : volume\n" + ": sustain pedal, <1/2>: pitch wheel, <3-8>: mod wheel\n" + ": octave, : velocity\n" + ": quit\n"); + + std::map*> sortEntries + (index.m_midiSetups.cbegin(), index.m_midiSetups.cend()); + auto setupIt = sortEntries.cbegin(); + if (setupIt != sortEntries.cend()) + { + m_setupId = setupIt->first; + m_chanId = 0; + } + + while (m_running) + { + m_events.dispatchEvents(); + + if (m_wantsNext) + { + m_wantsNext = false; + + } + + m_engine->pumpEngine(); + m_win->waitForRetrace(); + } + } + + void SFXLoop(const amuse::AudioGroup& group, + const amuse::SFXGroupIndex& index) + { + printf(": keyon/keyon, : cycle SFX, : volume\n" + ": quit\n"); + + std::map sortEntries + (index.m_sfxEntries.cbegin(), index.m_sfxEntries.cend()); + auto sfxIt = sortEntries.cbegin(); + if (sfxIt != sortEntries.cend()) + { + m_sfxId = sfxIt->first; + m_vox = m_engine->fxStart(m_sfxId, 1.f, 0.f); + } + + while (m_running) + { + m_events.dispatchEvents(); + m_engine->pumpEngine(); + m_win->waitForRetrace(); + } + } + + void charKeyDown(unsigned long charCode) + { + if (m_sfxGroup) + { + switch (charCode) + { + case ' ': + if (m_vox && m_vox->state() == amuse::VoiceState::Playing) + m_vox->keyOff(); + else if (m_sfxId != -1) + m_vox = m_engine->fxStart(m_sfxId, 1.f, 0.f); + default: break; + } + } + else + { + } + } + + void charKeyUp(unsigned long charCode) + { + } + + int appMain(boo::IApplication* app) + { + /* Event window */ + m_win = app->newWindow("amusetool", 1); + m_win->setWindowFrame(100, 100, 100, 100); + m_win->setCallback(&m_events); + m_win->showWindow(); + + /* Load data */ + std::string desc; + bool good = false; + amuse::IntrusiveAudioGroupData data = LoadFromArgs(m_argc, m_argv, desc, good); + if (!good) + Log.report(logvisor::Fatal, "incomplete data in args"); + printf("Found '%s' Audio Group data\n", desc.c_str()); + + /* Load project to assemble group list */ + amuse::AudioGroupProject proj(data.getProj()); + + /* Get group selection from user */ + size_t totalGroups = proj.sfxGroups().size() + proj.songGroups().size(); + if (totalGroups > 1) + { + /* Ask user to specify which group in project */ + printf("Multiple Audio Groups discovered:\n"); + for (const auto& pair : proj.songGroups()) + { + printf(" %d (SongGroup) %" PRISize " normal-pages, %" PRISize " drum-pages\n", + pair.first, pair.second.m_normPages.size(), pair.second.m_drumPages.size()); + } + for (const auto& pair : proj.sfxGroups()) + { + printf(" %d (SFXGroup) %" PRISize " sfx-entries\n", + pair.first, pair.second.m_sfxEntries.size()); + } + + int userSel = 0; + printf("Enter Group Number: "); + if (scanf("%d", &userSel) <= 0) + Log.report(logvisor::Fatal, "unable to parse prompt"); + + if (proj.getSongGroupIndex(userSel)) + { + m_groupId = userSel; + m_sfxGroup = false; + } + else if (proj.getSFXGroupIndex(userSel)) + { + m_groupId = userSel; + m_sfxGroup = true; + } + else + Log.report(logvisor::Fatal, "unable to find Group %d", userSel); + } + else if (totalGroups == 1) + { + /* Load one and only group */ + if (proj.songGroups().size()) + { + const auto& pair = *proj.songGroups().cbegin(); + m_groupId = pair.first; + m_sfxGroup = false; + } + else + { + const auto& pair = *proj.sfxGroups().cbegin(); + m_groupId = pair.first; + m_sfxGroup = true; + } + } + else + Log.report(logvisor::Fatal, "empty project"); + + /* Build voice engine */ + std::unique_ptr voxEngine = boo::NewAudioVoiceEngine(); + amuse::BooBackendVoiceAllocator booBackend(*voxEngine); + m_engine.emplace(booBackend); + + /* Load group into engine */ + const amuse::AudioGroup* group = m_engine->addAudioGroup(m_groupId, data); + if (!group) + Log.report(logvisor::Fatal, "unable to add audio group"); + + /* Enter playback loop */ + if (m_sfxGroup) + SFXLoop(*group, *proj.getSFXGroupIndex(m_groupId)); + else + SongLoop(*group, *proj.getSongGroupIndex(m_groupId)); + + return 0; + } + + void appQuitting(boo::IApplication*) + { + m_running = false; + } + + AppCallback(int argc, const boo::SystemChar** argv) + : m_argc(argc), m_argv(argv), m_eventRec(*this), m_events(m_eventRec) {} +}; + +void EventCallback::charKeyDown(unsigned long charCode, boo::EModifierKey mods, bool isRepeat) +{ + if (isRepeat) + return; + m_app.charKeyDown(charCode); +} + +void EventCallback::charKeyUp(unsigned long charCode, boo::EModifierKey mods) +{ + m_app.charKeyUp(charCode); +} + +void EventCallback::specialKeyDown(boo::ESpecialKey key, boo::EModifierKey mods, bool isRepeat) +{ + switch (key) + { + case boo::ESpecialKey::Left: + m_app.m_wantsPrev = true; + break; + case boo::ESpecialKey::Right: + m_app.m_wantsNext = true; + break; + default: break; + } +} + +void EventCallback::specialKeyUp(boo::ESpecialKey key, boo::EModifierKey mods) +{ +} + +#if _WIN32 +int wmain(int argc, const boo::SystemChar** argv) +#else +int main(int argc, const boo::SystemChar** argv) +#endif { logvisor::RegisterConsoleLogger(); - - /* Load data */ - std::string desc; - bool good = false; - amuse::IntrusiveAudioGroupData data = LoadFromArgs(argc, argv, desc, good); - if (!good) - Log.report(logvisor::Fatal, "incomplete data in args"); - printf("Found '%s' Audio Group data\n", desc.c_str()); - - /* Load project to assemble group list */ - amuse::AudioGroupProject proj(data.getProj()); - - /* Get group selection from user */ - int groupId; - bool sfxGroup; - size_t totalGroups = proj.sfxGroups().size() + proj.songGroups().size(); - if (totalGroups > 1) - { - /* Ask user to specify which group in project */ - printf("Multiple Audio Groups discovered:\n"); - for (const auto& pair : proj.songGroups()) - { - printf(" %d (SongGroup) %" PRISize " normal-pages, %" PRISize " drum-pages\n", - pair.first, pair.second.m_normPages.size(), pair.second.m_drumPages.size()); - } - for (const auto& pair : proj.sfxGroups()) - { - printf(" %d (SFXGroup) %" PRISize " sfx-entries\n", - pair.first, pair.second.m_sfxEntries.size()); - } - - int userSel = 0; - printf("Enter Group Number: "); - if (scanf("%d", &userSel) <= 0) - Log.report(logvisor::Fatal, "unable to parse prompt"); - - if (proj.getSongGroupIndex(userSel)) - { - groupId = userSel; - sfxGroup = false; - } - else if (proj.getSFXGroupIndex(userSel)) - { - groupId = userSel; - sfxGroup = true; - } - else - Log.report(logvisor::Fatal, "unable to find Group %d", userSel); - } - else if (totalGroups == 1) - { - /* Load one and only group */ - if (proj.songGroups().size()) - { - const auto& pair = *proj.songGroups().cbegin(); - groupId = pair.first; - sfxGroup = false; - } - else - { - const auto& pair = *proj.sfxGroups().cbegin(); - groupId = pair.first; - sfxGroup = true; - } - } - else - Log.report(logvisor::Fatal, "empty project"); - - /* Build voice engine */ - std::unique_ptr voxEngine = boo::NewAudioVoiceEngine(); - amuse::BooBackendVoiceAllocator booBackend(*voxEngine); - amuse::Engine engine(booBackend); - - engine.addAudioGroup(groupId, data); - - amuse::Voice* vox = engine.fxStart(1, 1.f, 0.f); - - for (int f=0 ; f<300 ; ++f) - { - engine.pumpEngine(); - std::this_thread::sleep_for(std::chrono::milliseconds(15)); - } - - vox->keyOff(); - - for (int f=0 ; f<120 ; ++f) - { - engine.pumpEngine(); - std::this_thread::sleep_for(std::chrono::milliseconds(15)); - } - - return 0; + AppCallback app(argc, argv); + int ret = boo::ApplicationRun(boo::IApplication::EPlatformType::Auto, + app, _S("amusetool"), _S("Amuse Tool"), argc, argv); + printf("IM DYING!!\n"); + return ret; } + +#if _WIN32 +int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE, LPWSTR lpCmdLine, int) +{ + int argc = 0; + const boo::SystemChar** argv = (const wchar_t**)(CommandLineToArgvW(lpCmdLine, &argc)); + static boo::SystemChar selfPath[1024]; + GetModuleFileNameW(nullptr, selfPath, 1024); + static const boo::SystemChar* booArgv[32] = {}; + booArgv[0] = selfPath; + for (int i=0 ; i; + class AudioGroup { int m_groupId; - AudioGroupPool m_pool; AudioGroupProject m_proj; + AudioGroupPool m_pool; AudioGroupSampleDirectory m_sdir; const unsigned char* m_samp; bool m_valid; @@ -25,8 +28,10 @@ public: bool sfxInGroup(int sfxId) const; bool songInGroup(int songId) const; - const AudioGroupPool& getPool() const; - const AudioGroupSampleDirectory::Entry* getSfxEntry(int sfxId) const; + const Sample* getSample(int sfxId) const; + const unsigned char* getSampleData(uint32_t offset) const; + const AudioGroupProject& getProj() const {return m_proj;} + const AudioGroupPool& getPool() const {return m_pool;} }; } diff --git a/include/amuse/AudioGroupProject.hpp b/include/amuse/AudioGroupProject.hpp index 0cf85d0..3ac8218 100644 --- a/include/amuse/AudioGroupProject.hpp +++ b/include/amuse/AudioGroupProject.hpp @@ -9,7 +9,14 @@ namespace amuse { -struct AudioGroupIndex {}; +/** Common index members of SongGroups and SFXGroups */ +struct AudioGroupIndex +{ + const uint16_t* m_soundMacroIndex; + const uint16_t* m_tablesIndex; + const uint16_t* m_keymapsIndex; + const uint16_t* m_layersIndex; +}; /** Root index of SongGroup */ struct SongGroupIndex : AudioGroupIndex diff --git a/include/amuse/AudioGroupSampleDirectory.hpp b/include/amuse/AudioGroupSampleDirectory.hpp index 17f0d10..292716e 100644 --- a/include/amuse/AudioGroupSampleDirectory.hpp +++ b/include/amuse/AudioGroupSampleDirectory.hpp @@ -1,7 +1,7 @@ #ifndef __AMUSE_AUDIOGROUPSAMPLEDIR_HPP__ #define __AMUSE_AUDIOGROUPSAMPLEDIR_HPP__ -#include +#include #include namespace amuse @@ -31,11 +31,11 @@ public: uint8_t m_lps; int16_t m_hist1; int16_t m_hist2; - int16_t m_coefs[16]; + int16_t m_coefs[8][2]; void swapBig(); }; private: - std::vector> m_entries; + std::unordered_map> m_entries; public: AudioGroupSampleDirectory(const unsigned char* data); }; diff --git a/include/amuse/BooBackend.hpp b/include/amuse/BooBackend.hpp index c81d92c..aff78de 100644 --- a/include/amuse/BooBackend.hpp +++ b/include/amuse/BooBackend.hpp @@ -27,6 +27,7 @@ public: double sampleRate, bool dynamicPitch); BooBackendVoice(boo::IAudioSubmix& submix, Voice& clientVox, double sampleRate, bool dynamicPitch); + void resetSampleRate(double sampleRate); void setMatrixCoefficients(const float coefs[8]); void setPitchRatio(double ratio); void start(); @@ -66,6 +67,7 @@ public: BooBackendVoiceAllocator(boo::IAudioVoiceEngine& booEngine); std::unique_ptr allocateVoice(Voice& clientVox, double sampleRate, bool dynamicPitch); std::unique_ptr allocateSubmix(Submix& clientSmx); + AudioChannelSet getAvailableSet(); }; } diff --git a/include/amuse/Engine.hpp b/include/amuse/Engine.hpp index 29a25cb..113491c 100644 --- a/include/amuse/Engine.hpp +++ b/include/amuse/Engine.hpp @@ -28,6 +28,7 @@ class Engine std::list m_activeEmitters; std::list m_activeSequencers; std::list m_activeSubmixes; + std::unordered_map> m_sfxLookup; std::linear_congruential_engine m_random; int m_nextVid = 0; Voice* _allocateVoice(const AudioGroup& group, double sampleRate, @@ -35,8 +36,6 @@ class Engine Submix* _allocateSubmix(Submix* smx); std::list::iterator _destroyVoice(Voice* voice); std::list::iterator _destroySubmix(Submix* smx); - AudioGroup* _findGroupFromSfxId(int sfxId, const AudioGroupSampleDirectory::Entry*& entOut) const; - AudioGroup* _findGroupFromSongId(int songId) const; public: Engine(IBackendVoiceAllocator& backend); @@ -64,7 +63,7 @@ public: Submix* smx=nullptr); /** Start song playing from loaded audio groups */ - Sequencer* seqPlay(int songId, const unsigned char* arrData); + Sequencer* seqPlay(int groupId, int songId, const unsigned char* arrData); /** Find voice from VoiceId */ Voice* findVoice(int vid); diff --git a/include/amuse/Entity.hpp b/include/amuse/Entity.hpp index 17ada43..778a3c9 100644 --- a/include/amuse/Entity.hpp +++ b/include/amuse/Entity.hpp @@ -21,15 +21,7 @@ enum class ObjectType : uint8_t /** Common ID structure statically tagging * SoundMacros, Tables, Keymaps, Layers */ -struct ObjectId -{ - ObjectType type = ObjectType::Invalid; - uint8_t id = 0xff; - bool operator ==(const ObjectId& other) const - {return *reinterpret_cast(this) == reinterpret_cast(other);} - bool operator !=(const ObjectId& other) const - {return *reinterpret_cast(this) != reinterpret_cast(other);} -}; +using ObjectId = uint16_t; /** Common 'engine child' class */ class Entity @@ -65,13 +57,4 @@ using Curve = uint8_t[128]; } -namespace std -{ -template <> struct hash -{ - size_t operator()(const amuse::ObjectId& val) const noexcept - {return std::hash()(reinterpret_cast(val));} -}; -} - #endif // __AMUSE_ENTITY_HPP__ diff --git a/include/amuse/IBackendVoice.hpp b/include/amuse/IBackendVoice.hpp index f2a68a8..3902d70 100644 --- a/include/amuse/IBackendVoice.hpp +++ b/include/amuse/IBackendVoice.hpp @@ -33,6 +33,9 @@ class IBackendVoice public: virtual ~IBackendVoice() = default; + /** Set new sample rate into platform voice (may result in artifacts while playing) */ + virtual void resetSampleRate(double sampleRate)=0; + /** Set channel-gains for audio source (AudioChannel enum for array index) */ virtual void setMatrixCoefficients(const float coefs[8])=0; diff --git a/include/amuse/IBackendVoiceAllocator.hpp b/include/amuse/IBackendVoiceAllocator.hpp index 38c82f6..de3465d 100644 --- a/include/amuse/IBackendVoiceAllocator.hpp +++ b/include/amuse/IBackendVoiceAllocator.hpp @@ -10,6 +10,16 @@ class IBackendSubmix; class Voice; class Submix; +/** Same enum as boo for describing speaker-configuration */ +enum class AudioChannelSet +{ + Stereo, + Quad, + Surround51, + Surround71, + Unknown = 0xff +}; + /** * @brief Client-implemented voice allocator */ @@ -19,10 +29,15 @@ public: virtual ~IBackendVoiceAllocator() = default; /** Amuse obtains a new voice from the platform this way */ - virtual std::unique_ptr allocateVoice(Voice& clientVox, double sampleRate, bool dynamicPitch)=0; + virtual std::unique_ptr allocateVoice(Voice& clientVox, + double sampleRate, + bool dynamicPitch)=0; /** Amuse obtains a new submix from the platform this way */ virtual std::unique_ptr allocateSubmix(Submix& clientSmx)=0; + + /** Amuse obtains speaker-configuration from the platform this way */ + virtual AudioChannelSet getAvailableSet()=0; }; } diff --git a/include/amuse/SurroundProfiles.hpp b/include/amuse/SurroundProfiles.hpp new file mode 100644 index 0000000..c07db27 --- /dev/null +++ b/include/amuse/SurroundProfiles.hpp @@ -0,0 +1,24 @@ +#ifndef __AMUSE_SURROUNDPROFILES_HPP__ +#define __AMUSE_SURROUNDPROFILES_HPP__ + +#include "IBackendVoice.hpp" +#include "IBackendVoiceAllocator.hpp" +#include "Emitter.hpp" + +namespace amuse +{ +struct ReferenceVector; + +class SurroundProfiles +{ + static void SetupRefs(float matOut[8], const ChannelMap& map, + const Vector3f& listenEmit, const ReferenceVector refs[]); +public: + static void SetupMatrix(float matOut[8], const ChannelMap& map, AudioChannelSet set, + const Vector3f& emitPos, const Vector3f& listenPos, + const Vector3f& listenDir, const Vector3f& listenUp); +}; + +} + +#endif // __AMUSE_SURROUNDPROFILES_HPP__ diff --git a/include/amuse/Voice.hpp b/include/amuse/Voice.hpp index f008046..dcc5e32 100644 --- a/include/amuse/Voice.hpp +++ b/include/amuse/Voice.hpp @@ -7,6 +7,8 @@ #include #include "SoundMacroState.hpp" #include "Entity.hpp" +#include "AudioGroupSampleDirectory.hpp" +#include "AudioGroup.hpp" namespace amuse { @@ -35,7 +37,20 @@ class Voice : public Entity uint8_t m_lastNote = 0; /**< Last MIDI semitone played by voice */ uint8_t m_keygroup = 0; /**< Keygroup voice is a member of */ + const Sample* m_curSample = nullptr; /**< Current sample entry playing */ + const unsigned char* m_curSampleData = nullptr; /**< Current sample data playing */ + uint32_t m_curSamplePos = 0; /**< Current sample position */ + uint32_t m_lastSamplePos = 0; /**< Last sample position (or last loop sample) */ + int16_t m_prev1 = 0; /**< DSPADPCM prev sample */ + int16_t m_prev2 = 0; /**< DSPADPCM prev-prev sample */ + + VoiceState m_voxState = VoiceState::Finished; /**< Current high-level state of voice */ + bool m_sustained = false; /**< Sustain pedal pressed for this voice */ + bool m_sustainKeyOff = false; /**< Keyoff event occured while sustained */ + void _destroy(); + bool _checkSamplePos(); + void _doKeyOff(); public: Voice(Engine& engine, const AudioGroup& group, int vid, bool emitter, Submix* smx); @@ -49,7 +64,7 @@ public: Submix* getSubmix() {return m_submix;} /** Get current state of voice */ - VoiceState state() const; + VoiceState state() const {return m_voxState;} /** Get VoiceId of this voice (unique to all currently-playing voices) */ int vid() const {return m_vid;} @@ -60,34 +75,63 @@ public: /** Load specified SoundMacro ID of within group into voice */ bool loadSoundMacro(ObjectId macroId, int macroStep=0, bool pushPc=false); - /** Signals voice to begin fade-out, eventually reaching silence */ + /** Signals voice to begin fade-out (or defer if sustained), eventually reaching silence */ void keyOff(); /** Sends numeric message to voice and all siblings */ void message(int32_t val); + /** Start playing specified sample from within group, optionally by sample offset */ void startSample(int16_t sampId, int32_t offset); + + /** Stop playing current sample */ void stopSample(); + + /** Set current voice volume immediately */ void setVolume(float vol); + + /** Set current voice panning immediately */ void setPanning(float pan); + + /** Set current voice surround-panning immediately */ void setSurroundPanning(float span); + + /** Set voice relative-pitch in cents */ void setPitchKey(int32_t cents); + + /** Set voice modulation */ void setModulation(float mod); + + /** Set sustain status within voice; clearing will trigger a deferred keyoff */ void setPedal(bool pedal); + + /** Set doppler factor for voice */ void setDoppler(float doppler); + + /** Set reverb mix for voice */ void setReverbVol(float rvol); + + /** Set envelope for voice */ void setAdsr(ObjectId adsrId); + + /** Set pitch in absolute hertz */ void setPitchFrequency(uint32_t hz, uint16_t fine); + + /** Set pitch envelope */ void setPitchAdsr(ObjectId adsrId, int32_t cents); + + /** Set effective pitch range via the pitchwheel controller */ void setPitchWheelRange(int8_t up, int8_t down); + + /** Assign voice to keygroup for coordinated mass-silencing */ void setKeygroup(uint8_t kg) {m_keygroup = kg;} uint8_t getLastNote() const {return m_lastNote;} - int8_t getCtrlValue(uint8_t ctrl) const; - void setCtrlValue(uint8_t ctrl, int8_t val); - int8_t getPitchWheel() const; - int8_t getModWheel() const; - int8_t getAftertouch() const; + int8_t getCtrlValue(uint8_t ctrl) const {} + void setCtrlValue(uint8_t ctrl, int8_t val) {} + int8_t getPitchWheel() const {} + int8_t getModWheel() const {} + int8_t getAftertouch() const {} }; diff --git a/include/amuse/dsp.h b/include/amuse/dsp.h new file mode 100644 index 0000000..96becbe --- /dev/null +++ b/include/amuse/dsp.h @@ -0,0 +1,34 @@ +#ifndef _DSP_h +#define _DSP_h + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +static inline int16_t DSPSampClamp(int32_t val) +{ + if (val < -32768) val = -32768; + else if (val > 32767) val = 32767; + return val; +} + +unsigned DSPDecompressFrameRanged(int16_t* out, const uint8_t* in, + const int16_t coefs[8][2], int16_t* prev1, int16_t* prev2, + unsigned firstSample, unsigned lastSample); +unsigned DSPDecompressFrame(int16_t* out, const uint8_t* in, + const int16_t coefs[8][2], int16_t* prev1, int16_t* prev2, + unsigned lastSample); +unsigned DSPDecompressFrameStereoStride(int16_t* out, const uint8_t* in, + const int16_t coefs[8][2], int16_t* prev1, int16_t* prev2, + unsigned lastSample); +unsigned DSPDecompressFrameStereoDupe(int16_t* out, const uint8_t* in, + const int16_t coefs[8][2], int16_t* prev1, int16_t* prev2, + unsigned lastSample); + +#ifdef __cplusplus +} +#endif + +#endif // _DSP_h diff --git a/lib/AudioGroup.cpp b/lib/AudioGroup.cpp index d2dd9f8..a18607b 100644 --- a/lib/AudioGroup.cpp +++ b/lib/AudioGroup.cpp @@ -20,12 +20,17 @@ bool AudioGroup::songInGroup(int songId) const { } -const AudioGroupSampleDirectory::Entry* AudioGroup::getSfxEntry(int sfxId) const +const Sample* AudioGroup::getSample(int sfxId) const { for (const auto& ent : m_sdir.m_entries) - if (ent.first.m_sfxId == sfxId) - return &ent.first; + if (ent.second.first.m_sfxId == sfxId) + return &ent.second; return nullptr; } +const unsigned char* AudioGroup::getSampleData(uint32_t offset) const +{ + return m_samp + offset; +} + } diff --git a/lib/AudioGroupProject.cpp b/lib/AudioGroupProject.cpp index 55eb292..393079d 100644 --- a/lib/AudioGroupProject.cpp +++ b/lib/AudioGroupProject.cpp @@ -49,14 +49,17 @@ AudioGroupProject::AudioGroupProject(const unsigned char* data) GroupHeader header = *group; header.swapBig(); + AudioGroupIndex* bIdx = nullptr; + if (header.type == GroupType::Song) { SongGroupIndex& idx = m_songGroups[header.groupId]; + bIdx = &idx; /* Normal pages */ const SongGroupIndex::PageEntry* normEntries = reinterpret_cast(data + header.pageTableOff); - while (normEntries->objId.type != ObjectType::Invalid) + while (normEntries->objId != 0xffff) { idx.m_normPages[normEntries->programNo] = normEntries; ++normEntries; @@ -65,7 +68,7 @@ AudioGroupProject::AudioGroupProject(const unsigned char* data) /* Drum pages */ const SongGroupIndex::PageEntry* drumEntries = reinterpret_cast(data + header.drumTableOff); - while (drumEntries->objId.type != ObjectType::Invalid) + while (drumEntries->objId != 0xffff) { idx.m_drumPages[drumEntries->programNo] = drumEntries; ++drumEntries; @@ -85,17 +88,26 @@ AudioGroupProject::AudioGroupProject(const unsigned char* data) else if (header.type == GroupType::SFX) { SFXGroupIndex& idx = m_sfxGroups[header.groupId]; + bIdx = &idx; /* SFX entries */ const SFXGroupIndex::SFXEntry* entries = reinterpret_cast(data + header.pageTableOff); - while (entries->objId.type != ObjectType::Invalid) + while (entries->objId != 0xffff) { idx.m_sfxEntries[SBig(entries->defineId)] = entries; ++entries; } } + if (bIdx) + { + bIdx->m_soundMacroIndex = reinterpret_cast(data + header.soundMacroIdsOff); + bIdx->m_tablesIndex = reinterpret_cast(data + header.tableIdsOff); + bIdx->m_keymapsIndex = reinterpret_cast(data + header.keymapIdsOff); + bIdx->m_layersIndex = reinterpret_cast(data + header.layerIdsOff); + } + group = reinterpret_cast(data + header.groupEndOff); } } diff --git a/lib/AudioGroupSampleDirectory.cpp b/lib/AudioGroupSampleDirectory.cpp index 762d2c1..54e531a 100644 --- a/lib/AudioGroupSampleDirectory.cpp +++ b/lib/AudioGroupSampleDirectory.cpp @@ -21,8 +21,11 @@ void AudioGroupSampleDirectory::ADPCMParms::swapBig() m_bytesPerFrame = SBig(m_bytesPerFrame); m_hist1 = SBig(m_hist1); m_hist2 = SBig(m_hist2); - for (int i=0 ; i<16 ; ++i) - m_coefs[i] = SBig(m_coefs[i]); + for (int i=0 ; i<8 ; ++i) + { + m_coefs[i][0] = SBig(m_coefs[i][0]); + m_coefs[i][1] = SBig(m_coefs[i][1]); + } } AudioGroupSampleDirectory::AudioGroupSampleDirectory(const unsigned char* data) diff --git a/lib/BooBackend.cpp b/lib/BooBackend.cpp index 8be1d31..b42df7c 100644 --- a/lib/BooBackend.cpp +++ b/lib/BooBackend.cpp @@ -23,6 +23,11 @@ BooBackendVoice::BooBackendVoice(boo::IAudioSubmix& submix, Voice& clientVox, m_booVoice(submix.allocateNewMonoVoice(sampleRate, &m_cb, dynamicPitch)) {} +void BooBackendVoice::resetSampleRate(double sampleRate) +{ + m_booVoice->resetSampleRate(sampleRate); +} + void BooBackendVoice::setMatrixCoefficients(const float coefs[8]) { m_booVoice->setMonoMatrixCoefficients(coefs); @@ -100,4 +105,9 @@ std::unique_ptr BooBackendVoiceAllocator::allocateSubmix(Submix& return std::make_unique(m_booEngine, clientSmx); } +AudioChannelSet BooBackendVoiceAllocator::getAvailableSet() +{ + return AudioChannelSet(m_booEngine.getAvailableSet()); +} + } diff --git a/lib/Engine.cpp b/lib/Engine.cpp index a38fd15..b140f46 100644 --- a/lib/Engine.cpp +++ b/lib/Engine.cpp @@ -6,6 +6,7 @@ #include "amuse/IBackendVoiceAllocator.hpp" #include "amuse/AudioGroupData.hpp" #include "amuse/AudioGroup.hpp" +#include "amuse/Common.hpp" namespace amuse { @@ -50,25 +51,6 @@ std::list::iterator Engine::_destroySubmix(Submix* smx) return m_activeSubmixes.erase(smx->m_engineIt); } -AudioGroup* Engine::_findGroupFromSfxId(int sfxId, const AudioGroupSampleDirectory::Entry*& entOut) const -{ - for (const auto& grp : m_audioGroups) - { - entOut = grp.second->getSfxEntry(sfxId); - if (entOut) - return grp.second.get(); - } - return nullptr; -} - -AudioGroup* Engine::_findGroupFromSongId(int songId) const -{ - for (const auto& grp : m_audioGroups) - if (grp.second->songInGroup(songId)) - return grp.second.get(); - return nullptr; -} - /** Update all active audio entities and fill OS audio buffers as needed */ void Engine::pumpEngine() { @@ -88,12 +70,28 @@ const AudioGroup* Engine::addAudioGroup(int groupId, const AudioGroupData& data) return nullptr; AudioGroup* ret = grp.get(); m_audioGroups.emplace(std::make_pair(groupId, std::move(grp))); + + /* setup SFX index for contained objects */ + for (const auto& pair : ret->getProj().sfxGroups()) + { + const SFXGroupIndex& sfxGroup = pair.second; + m_sfxLookup.reserve(m_sfxLookup.size() + sfxGroup.m_sfxEntries.size()); + for (const auto& pair : sfxGroup.m_sfxEntries) + m_sfxLookup[pair.first] = std::make_pair(ret, pair.second->objId); + } + return ret; } /** Remove audio group from engine */ void Engine::removeAudioGroup(int groupId) { + auto search = m_audioGroups.find(groupId); + if (search == m_audioGroups.end()) + return; + AudioGroup* grp = search->second.get(); + + /* Destroy runtime entities within group */ for (auto it = m_activeVoices.begin() ; it != m_activeVoices.end() ;) { if (it->getAudioGroup().groupId() == groupId) @@ -127,6 +125,14 @@ void Engine::removeAudioGroup(int groupId) ++it; } + /* teardown SFX index for contained objects */ + for (const auto& pair : grp->getProj().sfxGroups()) + { + const SFXGroupIndex& sfxGroup = pair.second; + for (const auto& pair : sfxGroup.m_sfxEntries) + m_sfxLookup.erase(pair.first); + } + m_audioGroups.erase(groupId); } @@ -172,12 +178,15 @@ void Engine::removeSubmix(Submix* smx) /** Start soundFX playing from loaded audio groups */ Voice* Engine::fxStart(int sfxId, float vol, float pan, Submix* smx) { - const AudioGroupSampleDirectory::Entry* entry; - AudioGroup* grp = _findGroupFromSfxId(sfxId, entry); + auto search = m_sfxLookup.find(sfxId); + if (search == m_sfxLookup.end()) + return nullptr; + + AudioGroup* grp = search->second.first; if (!grp) return nullptr; - Voice* ret = _allocateVoice(*grp, entry->m_sampleRate, true, false, smx); + Voice* ret = _allocateVoice(*grp, 32000.0, true, false, smx); ret->setVolume(vol); ret->setPanning(pan); return ret; @@ -187,12 +196,15 @@ Voice* Engine::fxStart(int sfxId, float vol, float pan, Submix* smx) Emitter* Engine::addEmitter(const Vector3f& pos, const Vector3f& dir, float maxDist, float falloff, int sfxId, float minVol, float maxVol, Submix* smx) { - const AudioGroupSampleDirectory::Entry* entry; - AudioGroup* grp = _findGroupFromSfxId(sfxId, entry); + auto search = m_sfxLookup.find(sfxId); + if (search == m_sfxLookup.end()) + return nullptr; + + AudioGroup* grp = search->second.first; if (!grp) return nullptr; - Voice* vox = _allocateVoice(*grp, entry->m_sampleRate, true, true, smx); + Voice* vox = _allocateVoice(*grp, 32000.0, true, true, smx); m_activeEmitters.emplace_back(*this, *grp, *vox); Emitter& ret = m_activeEmitters.back(); ret.setPos(pos); @@ -206,7 +218,7 @@ Emitter* Engine::addEmitter(const Vector3f& pos, const Vector3f& dir, float maxD } /** Start song playing from loaded audio groups */ -Sequencer* Engine::seqPlay(int songId, const unsigned char* arrData) +Sequencer* Engine::seqPlay(int groupId, int songId, const unsigned char* arrData) { } diff --git a/lib/SoundMacroState.cpp b/lib/SoundMacroState.cpp index cb19b65..4ed1cc0 100644 --- a/lib/SoundMacroState.cpp +++ b/lib/SoundMacroState.cpp @@ -511,7 +511,7 @@ bool SoundMacroState::advance(Voice& vox, float dt) int32_t eval = int32_t(orgVel ? m_initVel : m_curVel) * scale / 127 + add; eval = std::max(0, std::min(127, eval)); - if (curve.id != 0) + if (curve != 0) { const Curve* curveData = vox.getAudioGroup().getPool().tableAsCurves(curve); if (curveData) @@ -558,7 +558,7 @@ bool SoundMacroState::advance(Voice& vox, float dt) m_envelopeStart = m_curVel; m_envelopeEnd = eval; - if (curve.id != 0) + if (curve != 0) m_envelopeCurve = vox.getAudioGroup().getPool().tableAsCurves(curve); else m_envelopeCurve = nullptr; @@ -632,7 +632,7 @@ bool SoundMacroState::advance(Voice& vox, float dt) m_envelopeStart = 0.f; m_envelopeEnd = eval; - if (curve.id != 0) + if (curve != 0) m_envelopeCurve = vox.getAudioGroup().getPool().tableAsCurves(curve); else m_envelopeCurve = nullptr; @@ -1333,7 +1333,7 @@ void SoundMacroState::keyoffNotify(Voice& vox) if (m_inWait && m_keyoffWait) m_inWait = false; - if (m_keyoffTrap.macroId.id != 0xff) + if (m_keyoffTrap.macroId != 0xffff) { if (m_keyoffTrap.macroId == m_header.m_macroId) m_pc.back().second = m_keyoffTrap.macroStep; @@ -1348,7 +1348,7 @@ void SoundMacroState::sampleEndNotify(Voice& vox) if (m_inWait && m_sampleEndWait) m_inWait = false; - if (m_sampleEndTrap.macroId.id != 0xff) + if (m_sampleEndTrap.macroId != 0xffff) { if (m_sampleEndTrap.macroId == m_header.m_macroId) m_pc.back().second = m_sampleEndTrap.macroStep; @@ -1361,7 +1361,7 @@ void SoundMacroState::messageNotify(Voice& vox, int32_t val) { m_messageQueue.push_back(val); - if (m_messageTrap.macroId.id != 0xff) + if (m_messageTrap.macroId != 0xffff) { if (m_messageTrap.macroId == m_header.m_macroId) m_pc.back().second = m_messageTrap.macroStep; diff --git a/lib/SurroundProfiles.cpp b/lib/SurroundProfiles.cpp new file mode 100644 index 0000000..b03730c --- /dev/null +++ b/lib/SurroundProfiles.cpp @@ -0,0 +1,172 @@ +#include "amuse/SurroundProfiles.hpp" +#include +#include + +namespace amuse +{ + +static float Dot(const Vector3f& a, const Vector3f& b) +{ + return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; +} + +static float Length(const Vector3f& a) +{ + if (a[0] <= FLT_EPSILON && a[1] <= FLT_EPSILON && a[2] <= FLT_EPSILON) + return 0.f; + return std::sqrt(Dot(a, a)); +} + +static float Normalize(Vector3f& out, const Vector3f& in) +{ + out[0] = in[0]; + out[1] = in[1]; + out[2] = in[2]; + float dist = Length(out); + out[0] /= dist; + out[1] /= dist; + out[2] /= dist; + return dist; +} + +static void Cross(Vector3f& out, const Vector3f& a, const Vector3f& b) +{ + out[0] = a[1] * b[2] - a[2] * b[1]; + out[1] = a[2] * b[0] - a[0] * b[2]; + out[2] = a[0] * b[1] - a[1] * b[0]; +} + +class SimpleMatrix +{ + Vector3f m_mat[3]; +public: + SimpleMatrix(const Vector3f& dir, const Vector3f& up) + { + Vector3f temp; + Normalize(temp, dir); + m_mat[0][1] = temp[0]; + m_mat[1][1] = temp[1]; + m_mat[2][1] = temp[2]; + + Normalize(temp, up); + m_mat[0][2] = temp[0]; + m_mat[1][2] = temp[1]; + m_mat[2][2] = temp[2]; + + Cross(temp, dir, up); + m_mat[0][0] = temp[0]; + m_mat[1][0] = temp[1]; + m_mat[2][0] = temp[2]; + } + + void vecMult(Vector3f& out, const Vector3f& in) + { + out[0] = Dot(m_mat[0], in); + out[1] = Dot(m_mat[1], in); + out[2] = Dot(m_mat[2], in); + } +}; + +struct ReferenceVector +{ + Vector3f vec; + float bias; + bool valid = false; + ReferenceVector() = default; + ReferenceVector(float x, float y, float z, float thres) + { + vec[0] = x; + vec[1] = y; + vec[2] = z; + bias = thres; + valid = true; + } +}; + +static const ReferenceVector StereoVectors[8] = +{ + {-0.80901f, 0.58778f, 0.f, 0.3f}, + { 0.80901f, 0.58778f, 0.f, 0.3f}, +}; + +static const ReferenceVector QuadVectors[8] = +{ + {-0.70710f, 0.70710f, 0.f, 0.1f}, + { 0.70710f, 0.70710f, 0.f, 0.1f}, + {-0.70710f, -0.70710f, 0.f, 0.1f}, + { 0.70710f, -0.70710f, 0.f, 0.1f}, +}; + +static const ReferenceVector Sur51Vectors[8] = +{ + {-0.70710f, 0.70710f, 0.f, 0.1f}, + { 0.70710f, 0.70710f, 0.f, 0.1f}, + {-0.70710f, -0.70710f, 0.f, 0.1f}, + { 0.70710f, -0.70710f, 0.f, 0.1f}, + { 0.0f, 1.0f, 0.f, 0.1f}, + { 0.0f, 1.0f, 0.f, 1.0f}, +}; + +static const ReferenceVector Sur71Vectors[8] = +{ + {-0.70710f, 0.70710f, 0.f, 0.1f}, + { 0.70710f, 0.70710f, 0.f, 0.1f}, + {-0.70710f, -0.70710f, 0.f, 0.1f}, + { 0.70710f, -0.70710f, 0.f, 0.1f}, + { 0.0f, 1.0f, 0.f, 0.1f}, + { 0.0f, 1.0f, 0.f, 1.0f}, + {-1.f, 0.0f, 0.f, 0.1f}, + { 1.f, 0.0f, 0.f, 0.1f}, +}; + +void SurroundProfiles::SetupRefs(float matOut[8], const ChannelMap& map, + const Vector3f& listenEmit, const ReferenceVector refs[]) +{ + for (unsigned i=0 ; i namespace amuse { @@ -26,8 +29,83 @@ Voice::Voice(Engine& engine, const AudioGroup& group, ObjectId oid, int vid, boo m_submix->m_activeVoices.insert(this); } -size_t Voice::supplyAudio(size_t frames, int16_t* data) +bool Voice::_checkSamplePos() { + if (m_curSamplePos >= m_lastSamplePos) + { + if (m_curSample->first.m_loopLengthSamples) + { + /* Turn over looped sample */ + m_curSamplePos = m_curSample->first.m_loopStartSample; + m_prev1 = m_curSample->second.m_hist1; + m_prev2 = m_curSample->second.m_hist2; + } + else + { + /* Notify sample end */ + m_state.sampleEndNotify(*this); + m_curSample = nullptr; + return true; + } + } + return false; +} + +void Voice::_doKeyOff() +{ +} + +size_t Voice::supplyAudio(size_t samples, int16_t* data) +{ + uint32_t samplesRem = samples; + if (m_curSample) + { + uint32_t block = m_curSamplePos / 14; + uint32_t rem = m_curSamplePos % 14; + + if (rem) + { + uint32_t decSamples = DSPDecompressFrameRanged(data, m_curSampleData + 8 * block, + m_curSample->second.m_coefs, + &m_prev1, &m_prev2, rem, + std::min(samplesRem, + m_lastSamplePos - block * 14)); + m_curSamplePos += decSamples; + samplesRem -= decSamples; + data += decSamples; + } + + if (_checkSamplePos()) + { + if (samplesRem) + memset(data, 0, sizeof(int16_t) * samplesRem); + return samples; + } + + while (samplesRem) + { + block = m_curSamplePos / 14; + uint32_t decSamples = DSPDecompressFrame(data, m_curSampleData + 8 * block, + m_curSample->second.m_coefs, + &m_prev1, &m_prev2, + std::min(samplesRem, + m_lastSamplePos - block * 14)); + m_curSamplePos += decSamples; + samplesRem -= decSamples; + data += decSamples; + + if (_checkSamplePos()) + { + if (samplesRem) + memset(data, 0, sizeof(int16_t) * samplesRem); + return samples; + } + } + } + else + memset(data, 0, sizeof(int16_t) * samples); + + return samples; } Voice* Voice::startChildMacro(int8_t addNote, ObjectId macroId, int macroStep) @@ -40,12 +118,40 @@ bool Voice::loadSoundMacro(ObjectId macroId, int macroStep, bool pushPc) void Voice::keyOff() { + if (m_sustained) + m_sustainKeyOff = true; + else + _doKeyOff(); } void Voice::message(int32_t val) { } +void Voice::startSample(int16_t sampId, int32_t offset) +{ + m_curSample = m_audioGroup.getSample(sampId); + if (m_curSample) + { + m_backendVoice->stop(); + m_backendVoice->resetSampleRate(m_curSample->first.m_sampleRate); + m_backendVoice->start(); + m_curSamplePos = offset; + m_curSampleData = m_audioGroup.getSampleData(m_curSample->first.m_sampleOff); + m_prev1 = 0; + m_prev2 = 0; + m_lastSamplePos = m_curSample->first.m_loopLengthSamples ? + (m_curSample->first.m_loopStartSample + m_curSample->first.m_loopLengthSamples) : + m_curSample->first.m_numSamples; + } +} + +void Voice::stopSample() +{ + m_backendVoice->stop(); + m_curSample = nullptr; +} + void Voice::setVolume(float vol) { } @@ -54,4 +160,50 @@ void Voice::setPanning(float pan) { } +void Voice::setSurroundPanning(float span) +{ +} + +void Voice::setPitchKey(int32_t cents) +{ +} + +void Voice::setModulation(float mod) +{ +} + +void Voice::setPedal(bool pedal) +{ + if (m_sustained && !pedal && m_sustainKeyOff) + { + m_sustainKeyOff = false; + _doKeyOff(); + } + m_sustained = pedal; +} + +void Voice::setDoppler(float doppler) +{ +} + +void Voice::setReverbVol(float rvol) +{ +} + +void Voice::setAdsr(ObjectId adsrId) +{ +} + +void Voice::setPitchFrequency(uint32_t hz, uint16_t fine) +{ +} + +void Voice::setPitchAdsr(ObjectId adsrId, int32_t cents) +{ +} + +void Voice::setPitchWheelRange(int8_t up, int8_t down) +{ +} + } diff --git a/lib/dsp.c b/lib/dsp.c new file mode 100644 index 0000000..30dec39 --- /dev/null +++ b/lib/dsp.c @@ -0,0 +1,124 @@ +#include "amuse/dsp.h" + +static const int32_t NibbleToInt[16] = {0,1,2,3,4,5,6,7,-8,-7,-6,-5,-4,-3,-2,-1}; + +unsigned DSPDecompressFrameRanged(int16_t* out, const uint8_t* in, + const int16_t coefs[8][2], int16_t* prev1, int16_t* prev2, + unsigned firstSample, unsigned lastSample) +{ + uint8_t cIdx = (in[0]>>4) & 0xf; + int32_t factor1 = coefs[cIdx][0]; + int32_t factor2 = coefs[cIdx][1]; + uint8_t exp = in[0] & 0xf; + unsigned ret = 0; + for (int s=firstSample ; s<14 && s>4)&0xf]; + sampleData <<= exp; + sampleData <<= 11; + sampleData += 1024; + sampleData += + factor1 * ((int32_t)(*prev1)) + + factor2 * ((int32_t)(*prev2)); + sampleData >>= 11; + sampleData = DSPSampClamp(sampleData); + *out++ = sampleData; + *prev2 = *prev1; + *prev1 = sampleData; + ++ret; + } + return ret; +} + +unsigned DSPDecompressFrame(int16_t* out, const uint8_t* in, + const int16_t coefs[8][2], int16_t* prev1, int16_t* prev2, + unsigned lastSample) +{ + uint8_t cIdx = (in[0]>>4) & 0xf; + int32_t factor1 = coefs[cIdx][0]; + int32_t factor2 = coefs[cIdx][1]; + uint8_t exp = in[0] & 0xf; + unsigned ret = 0; + for (int s=0 ; s<14 && s>4)&0xf]; + sampleData <<= exp; + sampleData <<= 11; + sampleData += 1024; + sampleData += + factor1 * ((int32_t)(*prev1)) + + factor2 * ((int32_t)(*prev2)); + sampleData >>= 11; + sampleData = DSPSampClamp(sampleData); + out[s] = sampleData; + *prev2 = *prev1; + *prev1 = sampleData; + ++ret; + } + return ret; +} + +unsigned DSPDecompressFrameStereoStride(int16_t* out, const uint8_t* in, + const int16_t coefs[8][2], int16_t* prev1, int16_t* prev2, + unsigned lastSample) +{ + uint32_t cIdx = (in[0]>>4) & 0xf; + int32_t factor1 = coefs[cIdx][0]; + int32_t factor2 = coefs[cIdx][1]; + uint32_t exp = in[0] & 0xf; + unsigned ret = 0; + for (int s=0 ; s<14 && s>4)&0xf]; + sampleData <<= exp; + sampleData <<= 11; + sampleData += 1024; + sampleData += + factor1 * ((int32_t)(*prev1)) + + factor2 * ((int32_t)(*prev2)); + sampleData >>= 11; + sampleData = DSPSampClamp(sampleData); + out[s*2] = sampleData; + *prev2 = *prev1; + *prev1 = sampleData; + ++ret; + } + return ret; +} + +unsigned DSPDecompressFrameStereoDupe(int16_t* out, const uint8_t* in, + const int16_t coefs[8][2], int16_t* prev1, int16_t* prev2, + unsigned lastSample) +{ + uint8_t cIdx = (in[0]>>4) & 0xf; + int32_t factor1 = coefs[cIdx][0]; + int32_t factor2 = coefs[cIdx][1]; + uint8_t exp = in[0] & 0xf; + unsigned ret = 0; + for (int s=0 ; s<14 && s>4)&0xf]; + sampleData <<= exp; + sampleData <<= 11; + sampleData += 1024; + sampleData += + factor1 * ((int32_t)(*prev1)) + + factor2 * ((int32_t)(*prev2)); + sampleData >>= 11; + sampleData = DSPSampClamp(sampleData); + out[s*2] = sampleData; + out[s*2+1] = sampleData; + *prev2 = *prev1; + *prev1 = sampleData; + ++ret; + } + return ret; +}