#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 #include #include #include #include #include static logvisor::Module Log("amuseplay"); #if __GNUC__ __attribute__((__format__ (__printf__, 3, 4))) #endif static inline void SNPrintf(boo::SystemChar* str, size_t maxlen, const boo::SystemChar* format, ...) { va_list va; va_start(va, format); #if _WIN32 _vsnwprintf(str, maxlen, format, va); #else vsnprintf(str, maxlen, format, va); #endif va_end(va); } struct AppCallback; struct EventCallback : boo::IWindowCallback { AppCallback& m_app; bool m_tracking = false; 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&) {} void mouseDown(const boo::SWindowCoord&, boo::EMouseButton, boo::EModifierKey); void mouseUp(const boo::SWindowCoord&, boo::EMouseButton, boo::EModifierKey); void mouseMove(const boo::SWindowCoord& coord); 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 = -1; bool m_sfxGroup; /* Song playback selection */ int m_setupId = -1; int m_chanId = 0; int8_t m_octave = 4; int8_t m_velocity = 64; std::shared_ptr m_seq; std::unique_ptr m_arrData; /* SFX playback selection */ int m_sfxId = -1; std::shared_ptr m_vox; size_t m_lastVoxCount = 0; int8_t m_lastChanProg = -1; /* Control state */ float m_volume = 0.8f; float m_modulation = 0.f; float m_pitchBend = 0.f; bool m_updateDisp = false; bool m_running = true; bool m_wantsNext = false; bool m_wantsPrev = false; int m_panicCount = 0; void UpdateSongDisplay() { size_t voxCount = 0; int program = 0; if (m_seq) { voxCount = m_seq->getVoiceCount(); program = m_seq->getChanProgram(m_chanId); } printf("\r " "\r %" PRISize " Setup %d, Chan %d, Prog %d, Octave: %d, Vel: %d, VOL: %d%%\r", voxCount, m_setupId, m_chanId, program, m_octave, m_velocity, int(std::rint(m_volume * 100))); fflush(stdout); } void SelectSong(int setupId) { m_setupId = setupId; if (m_seq) { m_seq->stopSong(true); m_seq->kill(); } m_seq = m_engine->seqPlay(m_groupId, setupId, nullptr); m_seq->setVolume(m_volume); if (m_arrData) m_seq->playSong(m_arrData.get(), false); UpdateSongDisplay(); } void SongLoop(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, : PANIC\n" ": sustain pedal, : pitch wheel, : mod wheel\n" ": octave, : velocity, : channel, <,/.>: program, : quit\n"); std::map*> sortEntries (index.m_midiSetups.cbegin(), index.m_midiSetups.cend()); auto setupIt = sortEntries.cbegin(); if (setupIt != sortEntries.cend()) { if (m_setupId == -1) SelectSong(setupIt->first); else { while (setupIt != sortEntries.cend() && setupIt->first != m_setupId) ++setupIt; if (setupIt == sortEntries.cend()) setupIt = sortEntries.cbegin(); SelectSong(setupIt->first); } } while (m_running) { m_events.dispatchEvents(); if (m_wantsNext) { m_wantsNext = false; auto nextIt = setupIt; ++nextIt; if (nextIt != sortEntries.cend()) { ++setupIt; SelectSong(setupIt->first); m_updateDisp = false; } } if (m_wantsPrev) { m_wantsPrev = false; if (setupIt != sortEntries.cbegin()) { --setupIt; SelectSong(setupIt->first); m_updateDisp = false; } } if (m_seq && m_panicCount) { if (m_panicCount > 1) { m_seq->allOff(true); m_panicCount = 0; } else m_seq->allOff(false); m_updateDisp = true; } if (m_updateDisp) { m_updateDisp = false; UpdateSongDisplay(); } m_engine->pumpEngine(); size_t voxCount; int8_t progId; if (m_seq) { voxCount = m_seq->getVoiceCount(); progId = m_seq->getChanProgram(m_chanId); } else { voxCount = 0; progId = -1; } if (m_lastVoxCount != voxCount || m_lastChanProg != progId) { m_lastVoxCount = voxCount; m_lastChanProg = progId; UpdateSongDisplay(); } m_win->waitForRetrace(); } } void UpdateSFXDisplay() { bool playing = m_vox && m_vox->state() == amuse::VoiceState::Playing; printf("\r " "\r %c SFX %d, VOL: %d%%\r", playing ? '>' : ' ', m_sfxId, int(std::rint(m_volume * 100))); fflush(stdout); } void SelectSFX(int sfxId) { m_sfxId = sfxId; bool playing = m_vox && m_vox->state() == amuse::VoiceState::Playing; if (playing) { m_vox->keyOff(); m_vox = m_engine->fxStart(m_sfxId, m_volume, 0.f); } UpdateSFXDisplay(); } void SFXLoop(const amuse::SFXGroupIndex& index) { printf(": keyon/keyoff, : cycle SFX, : volume, : quit\n"); std::map sortEntries (index.m_sfxEntries.cbegin(), index.m_sfxEntries.cend()); auto sfxIt = sortEntries.cbegin(); if (sfxIt != sortEntries.cend()) SelectSFX(sfxIt->first); while (m_running) { m_events.dispatchEvents(); if (m_wantsNext) { m_wantsNext = false; auto nextIt = sfxIt; ++nextIt; if (nextIt != sortEntries.cend()) { ++sfxIt; SelectSFX(sfxIt->first); m_updateDisp = false; } } if (m_wantsPrev) { m_wantsPrev = false; if (sfxIt != sortEntries.cbegin()) { --sfxIt; SelectSFX(sfxIt->first); m_updateDisp = false; } } if (m_updateDisp) { m_updateDisp = false; UpdateSFXDisplay(); } m_engine->pumpEngine(); if (m_vox && m_vox->state() == amuse::VoiceState::Dead) { m_vox.reset(); UpdateSFXDisplay(); } m_win->waitForRetrace(); } } void charKeyDownRepeat(unsigned long charCode) { charCode = tolower(charCode); if (m_seq && m_chanId != -1) { switch (charCode) { case 'z': m_octave = amuse::clamp(-1, m_octave - 1, 8); m_updateDisp = true; break; case 'x': m_octave = amuse::clamp(-1, m_octave + 1, 8); m_updateDisp = true; break; case 'c': m_velocity = amuse::clamp(0, m_velocity - 1, 127); m_updateDisp = true; break; case 'v': m_velocity = amuse::clamp(0, m_velocity + 1, 127); m_updateDisp = true; break; case 'b': m_chanId = amuse::clamp(0, m_chanId - 1, 15); m_updateDisp = true; break; case 'n': m_chanId = amuse::clamp(0, m_chanId + 1, 15); m_updateDisp = true; break; case ',': m_seq->prevChanProgram(m_chanId); m_updateDisp = true; break; case '.': m_seq->nextChanProgram(m_chanId); m_updateDisp = true; break; default: break; } } } void charKeyDown(unsigned long charCode) { charCode = tolower(charCode); if (charCode == 'q') { m_running = false; return; } 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, m_volume, 0.f); m_updateDisp = true; default: break; } } else if (m_seq && m_chanId != -1) { bool setPanic = false; switch (charCode) { case ' ': ++m_panicCount; setPanic = true; break; case 'z': m_octave = amuse::clamp(-1, m_octave - 1, 8); m_updateDisp = true; break; case 'x': m_octave = amuse::clamp(-1, m_octave + 1, 8); m_updateDisp = true; break; case 'c': m_velocity = amuse::clamp(0, m_velocity - 1, 127); m_updateDisp = true; break; case 'v': m_velocity = amuse::clamp(0, m_velocity + 1, 127); m_updateDisp = true; break; case 'b': m_chanId = amuse::clamp(0, m_chanId - 1, 15); m_updateDisp = true; break; case 'n': m_chanId = amuse::clamp(0, m_chanId + 1, 15); m_updateDisp = true; break; case ',': m_seq->prevChanProgram(m_chanId); m_updateDisp = true; break; case '.': m_seq->nextChanProgram(m_chanId); m_updateDisp = true; break; case '\t': m_seq->setCtrlValue(m_chanId, 64, 127); break; case 'a': m_seq->keyOn(m_chanId, (m_octave + 1) * 12, m_velocity); break; case 'w': m_seq->keyOn(m_chanId, (m_octave + 1) * 12 + 1, m_velocity); break; case 's': m_seq->keyOn(m_chanId, (m_octave + 1) * 12 + 2, m_velocity); break; case 'e': m_seq->keyOn(m_chanId, (m_octave + 1) * 12 + 3, m_velocity); break; case 'd': m_seq->keyOn(m_chanId, (m_octave + 1) * 12 + 4, m_velocity); break; case 'f': m_seq->keyOn(m_chanId, (m_octave + 1) * 12 + 5, m_velocity); break; case 't': m_seq->keyOn(m_chanId, (m_octave + 1) * 12 + 6, m_velocity); break; case 'g': m_seq->keyOn(m_chanId, (m_octave + 1) * 12 + 7, m_velocity); break; case 'y': m_seq->keyOn(m_chanId, (m_octave + 1) * 12 + 8, m_velocity); break; case 'h': m_seq->keyOn(m_chanId, (m_octave + 1) * 12 + 9, m_velocity); break; case 'u': m_seq->keyOn(m_chanId, (m_octave + 1) * 12 + 10, m_velocity); break; case 'j': m_seq->keyOn(m_chanId, (m_octave + 1) * 12 + 11, m_velocity); break; case 'k': m_seq->keyOn(m_chanId, (m_octave + 1) * 12 + 12, m_velocity); break; case 'o': m_seq->keyOn(m_chanId, (m_octave + 1) * 12 + 13, m_velocity); break; case 'l': m_seq->keyOn(m_chanId, (m_octave + 1) * 12 + 14, m_velocity); break; case 'p': m_seq->keyOn(m_chanId, (m_octave + 1) * 12 + 15, m_velocity); break; case ';': case ':': m_seq->keyOn(m_chanId, (m_octave + 1) * 12 + 16, m_velocity); break; default: break; } if (!setPanic) m_panicCount = 0; } } void charKeyUp(unsigned long charCode) { charCode = tolower(charCode); if (!m_sfxGroup && m_chanId != -1) { switch (charCode) { case '\t': m_seq->setCtrlValue(m_chanId, 64, 0); break; case 'a': m_seq->keyOff(m_chanId, (m_octave + 1) * 12, m_velocity); break; case 'w': m_seq->keyOff(m_chanId, (m_octave + 1) * 12 + 1, m_velocity); break; case 's': m_seq->keyOff(m_chanId, (m_octave + 1) * 12 + 2, m_velocity); break; case 'e': m_seq->keyOff(m_chanId, (m_octave + 1) * 12 + 3, m_velocity); break; case 'd': m_seq->keyOff(m_chanId, (m_octave + 1) * 12 + 4, m_velocity); break; case 'f': m_seq->keyOff(m_chanId, (m_octave + 1) * 12 + 5, m_velocity); break; case 't': m_seq->keyOff(m_chanId, (m_octave + 1) * 12 + 6, m_velocity); break; case 'g': m_seq->keyOff(m_chanId, (m_octave + 1) * 12 + 7, m_velocity); break; case 'y': m_seq->keyOff(m_chanId, (m_octave + 1) * 12 + 8, m_velocity); break; case 'h': m_seq->keyOff(m_chanId, (m_octave + 1) * 12 + 9, m_velocity); break; case 'u': m_seq->keyOff(m_chanId, (m_octave + 1) * 12 + 10, m_velocity); break; case 'j': m_seq->keyOff(m_chanId, (m_octave + 1) * 12 + 11, m_velocity); break; case 'k': m_seq->keyOff(m_chanId, (m_octave + 1) * 12 + 12, m_velocity); break; case 'o': m_seq->keyOff(m_chanId, (m_octave + 1) * 12 + 13, m_velocity); break; case 'l': m_seq->keyOff(m_chanId, (m_octave + 1) * 12 + 14, m_velocity); break; case 'p': m_seq->keyOff(m_chanId, (m_octave + 1) * 12 + 15, m_velocity); break; case ';': case ':': m_seq->keyOff(m_chanId, (m_octave + 1) * 12 + 16, m_velocity); break; default: break; } } } int appMain(boo::IApplication* app) { /* Event window */ m_win = app->newWindow(_S("amuseplay"), 1); m_win->setCallback(&m_events); m_win->setWindowFrame(100, 100, 100, 100); m_win->setStyle(~boo::EWindowStyle::Resize); m_win->showWindow(); boo::ITextureR* tex = nullptr; boo::GraphicsDataToken gfxToken = m_win->getMainContextDataFactory()->commitTransaction( [&](boo::IGraphicsDataFactory::Context& ctx) -> bool { tex = ctx.newRenderTexture(100, 100, false, false); return true; }); boo::IGraphicsCommandQueue* q = m_win->getCommandQueue(); q->setRenderTarget(tex); q->clearTarget(); q->resolveDisplay(tex); q->execute(); /* Load data */ if (m_argc < 2) Log.report(logvisor::Fatal, "needs group path argument"); amuse::ContainerRegistry::Type cType = amuse::ContainerRegistry::DetectContainerType(m_argv[1]); if (cType == amuse::ContainerRegistry::Type::Invalid) Log.report(logvisor::Fatal, "invalid/no data at path argument"); Log.report(logvisor::Info, "Found '%s' Audio Group data", amuse::ContainerRegistry::TypeToName(cType)); std::vector> data = amuse::ContainerRegistry::LoadContainer(m_argv[1]); if (data.empty()) Log.report(logvisor::Fatal, "invalid/no data at path argument"); std::list m_projs; std::map*, const amuse::SongGroupIndex*>> allSongGroups; std::map*, const amuse::SFXGroupIndex*>> allSFXGroups; size_t totalGroups = 0; for (auto& grp : data) { /* Load project to assemble group list */ m_projs.push_back(amuse::AudioGroupProject::CreateAudioGroupProject(grp.second)); amuse::AudioGroupProject& proj = m_projs.back(); totalGroups += proj.sfxGroups().size() + proj.songGroups().size(); for (auto it = proj.songGroups().begin() ; it != proj.songGroups().end() ; ++it) allSongGroups[it->first] = std::make_pair(&grp, &it->second); for (auto it = proj.sfxGroups().begin() ; it != proj.sfxGroups().end() ; ++it) allSFXGroups[it->first] = std::make_pair(&grp, &it->second); } /* Get group selection from user */ if (m_groupId != -1) { if (allSongGroups.find(m_groupId) != allSongGroups.end()) m_sfxGroup = false; else if (allSFXGroups.find(m_groupId) != allSFXGroups.end()) m_sfxGroup = true; else Log.report(logvisor::Fatal, "unable to find Group %d", m_groupId); } else if (totalGroups > 1) { /* Ask user to specify which group in project */ printf("Multiple Audio Groups discovered:\n"); for (const auto& pair :allSFXGroups) { printf(" %d %s (SFXGroup) %" PRISize " sfx-entries\n", pair.first, pair.second.first->first.c_str(), pair.second.second->m_sfxEntries.size()); } for (const auto& pair : allSongGroups) { printf(" %d %s (SongGroup) %" PRISize " normal-pages, %" PRISize " drum-pages\n", pair.first, pair.second.first->first.c_str(), pair.second.second->m_normPages.size(), pair.second.second->m_drumPages.size()); } int userSel = 0; printf("Enter Group Number: "); if (scanf("%d", &userSel) <= 0) Log.report(logvisor::Fatal, "unable to parse prompt"); if (allSongGroups.find(userSel) != allSongGroups.end()) { m_groupId = userSel; m_sfxGroup = false; } else if (allSFXGroups.find(userSel) != allSFXGroups.end()) { 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 (allSongGroups.size()) { const auto& pair = *allSongGroups.cbegin(); m_groupId = pair.first; m_sfxGroup = false; } else { const auto& pair = *allSFXGroups.cbegin(); m_groupId = pair.first; m_sfxGroup = true; } } else Log.report(logvisor::Fatal, "empty project"); /* Make final group selection */ amuse::IntrusiveAudioGroupData* selData = nullptr; const amuse::SongGroupIndex* songIndex = nullptr; const amuse::SFXGroupIndex* sfxIndex = nullptr; auto songSearch = allSongGroups.find(m_groupId); if (songSearch != allSongGroups.end()) { selData = &songSearch->second.first->second; songIndex = songSearch->second.second; } else { auto sfxSearch = allSFXGroups.find(m_groupId); if (sfxSearch != allSFXGroups.end()) { selData = &sfxSearch->second.first->second; sfxIndex = sfxSearch->second.second; } } if (!selData) Log.report(logvisor::Fatal, "unable to select audio group data"); /* Attempt loading song */ if (m_argc > 2) { std::experimental::optional r; r.emplace(m_argv[m_argc-1], 32 * 1024, false); if (!r->hasError()) { uint32_t version = r->readUint32Big(); if (version == 0x18) { /* Raw SON data */ r->seek(0, athena::SeekOrigin::Begin); m_arrData = r->readUBytes(r->length()); } else if (version == 0x2) { /* Retro CSNG data */ m_setupId = r->readUint32Big(); m_groupId = r->readUint32Big(); r->readUint32Big(); uint32_t sonLength = r->readUint32Big(); m_arrData = r->readUBytes(sonLength); } } } /* Build voice engine */ std::unique_ptr voxEngine = boo::NewAudioVoiceEngine(); amuse::BooBackendVoiceAllocator booBackend(*voxEngine); m_engine.emplace(booBackend, amuse::AmplitudeMode::BlockLinearized); /* Load group into engine */ const amuse::AudioGroup* group = m_engine->addAudioGroup(*selData); if (!group) Log.report(logvisor::Fatal, "unable to add audio group"); /* Enter playback loop */ if (m_sfxGroup) SFXLoop(*sfxIndex); else SongLoop(*songIndex); 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) m_app.charKeyDownRepeat(charCode); else 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; case boo::ESpecialKey::Up: if (m_app.m_volume < 1.f) m_app.m_volume = amuse::clamp(0.f, m_app.m_volume + 0.05f, 1.f); if (m_app.m_vox) m_app.m_vox->setVolume(m_app.m_volume); if (m_app.m_seq) m_app.m_seq->setVolume(m_app.m_volume); m_app.m_updateDisp = true; break; case boo::ESpecialKey::Down: if (m_app.m_volume > 0.f) m_app.m_volume = amuse::clamp(0.f, m_app.m_volume - 0.05f, 1.f); if (m_app.m_vox) m_app.m_vox->setVolume(m_app.m_volume); if (m_app.m_seq) m_app.m_seq->setVolume(m_app.m_volume); m_app.m_updateDisp = true; break; default: break; } } void EventCallback::specialKeyUp(boo::ESpecialKey key, boo::EModifierKey mods) { } void EventCallback::mouseDown(const boo::SWindowCoord& coord, boo::EMouseButton, boo::EModifierKey) { m_tracking = true; m_app.m_pitchBend = amuse::clamp(-1.f, coord.norm[1] * 2.f - 1.f, 1.f); if (m_app.m_vox) m_app.m_vox->setPitchWheel(m_app.m_pitchBend); if (m_app.m_seq && m_app.m_chanId != -1) m_app.m_seq->setPitchWheel(m_app.m_chanId, m_app.m_pitchBend); } void EventCallback::mouseUp(const boo::SWindowCoord&, boo::EMouseButton, boo::EModifierKey) { m_tracking = false; m_app.m_pitchBend = 0.f; if (m_app.m_vox) m_app.m_vox->setPitchWheel(m_app.m_pitchBend); if (m_app.m_seq && m_app.m_chanId != -1) m_app.m_seq->setPitchWheel(m_app.m_chanId, m_app.m_pitchBend); } void EventCallback::mouseMove(const boo::SWindowCoord& coord) { if (m_tracking) { m_app.m_modulation = amuse::clamp(0.f, coord.norm[0], 1.f); if (m_app.m_vox) m_app.m_vox->setCtrlValue(1, m_app.m_modulation * 127.f); if (m_app.m_seq && m_app.m_chanId != -1) m_app.m_seq->setCtrlValue(m_app.m_chanId, 1, m_app.m_modulation * 127.f); m_app.m_pitchBend = amuse::clamp(-1.f, coord.norm[1] * 2.f - 1.f, 1.f); if (m_app.m_vox) m_app.m_vox->setPitchWheel(m_app.m_pitchBend); if (m_app.m_seq && m_app.m_chanId != -1) m_app.m_seq->setPitchWheel(m_app.m_chanId, m_app.m_pitchBend); } } #if _WIN32 int wmain(int argc, const boo::SystemChar** argv) #else int main(int argc, const boo::SystemChar** argv) #endif { logvisor::RegisterConsoleLogger(); AppCallback app(argc, argv); int ret = boo::ApplicationRun(boo::IApplication::EPlatformType::Auto, app, _S("amuseplay"), _S("Amuse Player"), argc, argv, false); 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