diff --git a/TheForceEngine/TFE_Audio/midiPlayer.cpp b/TheForceEngine/TFE_Audio/midiPlayer.cpp index 378513746..0f04270b3 100644 --- a/TheForceEngine/TFE_Audio/midiPlayer.cpp +++ b/TheForceEngine/TFE_Audio/midiPlayer.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN 1 @@ -39,6 +40,26 @@ namespace TFE_MidiPlayer { MidiPlayerCmd cmd; f32 newVolume; + + static inline MidiCmd pause() + { + return { MIDI_PAUSE }; + } + + static inline MidiCmd resume() + { + return { MIDI_RESUME }; + } + + static inline MidiCmd stop() + { + return { MIDI_STOP_NOTES }; + } + + static inline MidiCmd changeVol(f32 vol) + { + return { MIDI_CHANGE_VOL, vol }; + } }; enum { MAX_MIDI_CMD = 256 }; @@ -59,9 +80,10 @@ namespace TFE_MidiPlayer static SDL_Thread* s_thread = nullptr; static bool s_tPaused = false; - static atomic_bool s_runMusicThread; + static bool s_threadAlive = true; static u8 s_channelSrcVolume[MIDI_CHANNEL_COUNT] = { 0 }; static SDL_mutex* s_mutex; + static SDL_cond* s_midiCmdCond; static MidiDevice* s_midiDevice = nullptr; static MidiCallback s_midiCallback = {}; @@ -107,6 +129,13 @@ namespace TFE_MidiPlayer TFE_System::logWrite(LOG_ERROR, "Midi", "cannot initialize SDL mutex"); return false; } + s_midiCmdCond = SDL_CreateCond(); + if (!s_midiCmdCond) + { + SDL_DestroyMutex(s_mutex); + TFE_System::logWrite(LOG_ERROR, "Midi", "cannot initialize SDL condition"); + return false; + } allocateMidiDevice(type); @@ -123,9 +152,6 @@ namespace TFE_MidiPlayer } } - s_runMusicThread.store(true); - - s_thread = SDL_CreateThread(midiUpdateFunc, "TFE_MidiThread", nullptr); if (!s_thread) { @@ -149,11 +175,15 @@ namespace TFE_MidiPlayer TFE_System::logWrite(LOG_MSG, "MidiPlayer", "Shutdown"); // Destroy the thread before shutting down the Midi Device. - s_runMusicThread.store(false); + SDL_LockMutex(s_mutex); + s_threadAlive = false; + SDL_CondSignal(s_midiCmdCond); + SDL_UnlockMutex(s_mutex); SDL_WaitThread(s_thread, &i); delete s_midiDevice; + SDL_DestroyCond(s_midiCmdCond); SDL_DestroyMutex(s_mutex); } @@ -191,12 +221,17 @@ namespace TFE_MidiPlayer ////////////////////////////////////////////////// // Command Buffer ////////////////////////////////////////////////// - MidiCmd* midiAllocCmd() + static void tryQueueMidiCmd(MidiCmd cmd) { - if (s_midiCmdCount >= MAX_MIDI_CMD) { return nullptr; } - MidiCmd* cmd = &s_midiCmdBuffer[s_midiCmdCount]; - s_midiCmdCount++; - return cmd; + SDL_LockMutex(s_mutex); + if (s_midiCmdCount >= MAX_MIDI_CMD) + { + SDL_UnlockMutex(s_mutex); + return; + } + s_midiCmdBuffer[s_midiCmdCount++] = cmd; + SDL_CondSignal(s_midiCmdCond); + SDL_UnlockMutex(s_mutex); } void midiClearCmdBuffer() @@ -209,14 +244,7 @@ namespace TFE_MidiPlayer ////////////////////////////////////////////////// void setVolume(f32 volume) { - SDL_LockMutex(s_mutex); - MidiCmd* midiCmd = midiAllocCmd(); - if (midiCmd) - { - midiCmd->cmd = MIDI_CHANGE_VOL; - midiCmd->newVolume = volume; - } - SDL_UnlockMutex(s_mutex); + tryQueueMidiCmd(MidiCmd::changeVol(volume)); } // Set the length in seconds that a note is allowed to play for in seconds. @@ -245,35 +273,17 @@ namespace TFE_MidiPlayer void pause() { - SDL_LockMutex(s_mutex); - MidiCmd* midiCmd = midiAllocCmd(); - if (midiCmd) - { - midiCmd->cmd = MIDI_PAUSE; - } - SDL_UnlockMutex(s_mutex); + tryQueueMidiCmd(MidiCmd::pause()); } void resume() { - SDL_LockMutex(s_mutex); - MidiCmd* midiCmd = midiAllocCmd(); - if (midiCmd) - { - midiCmd->cmd = MIDI_RESUME; - } - SDL_UnlockMutex(s_mutex); + tryQueueMidiCmd(MidiCmd::resume()); } void stopMidiSound() { - SDL_LockMutex(s_mutex); - MidiCmd* midiCmd = midiAllocCmd(); - if (midiCmd) - { - midiCmd->cmd = MIDI_STOP_NOTES; - } - SDL_UnlockMutex(s_mutex); + tryQueueMidiCmd(MidiCmd::stop()); } void synthesizeMidi(f32* buffer, u32 stereoSampleCount, bool updateBuffer) @@ -326,6 +336,8 @@ namespace TFE_MidiPlayer { s_channelSrcVolume[i] = CHANNEL_MAX_VOLUME; } + // Changing the volume signals the command condition and + // wakes up the thread. changeVolume(); SDL_UnlockMutex(s_mutex); } @@ -336,6 +348,9 @@ namespace TFE_MidiPlayer s_midiCallback.callback = nullptr; s_midiCallback.timeStep = 0.0; s_midiCallback.accumulator = 0.0; + // If the thread is waiting on the condition, nothing changes. + // Otherwise the thread will discover that we cleared the + // callback and will go to sleep. SDL_UnlockMutex(s_mutex); } @@ -447,18 +462,19 @@ namespace TFE_MidiPlayer // Thread Function int midiUpdateFunc(void* userData) { - bool runThread = true; - bool wasPlaying = false; - bool isPlaying = false; bool isPaused = false; - s32 loopStart = -1; u64 localTime = 0; - u64 localTimeCallback = 0; f64 dt = 0.0; - while (runThread) + while (true) { SDL_LockMutex(s_mutex); - + + if (!s_threadAlive) + { + SDL_UnlockMutex(s_mutex); + break; + } + // Read from the command buffer. MidiCmd* midiCmd = s_midiCmdBuffer; for (u32 i = 0; i < s_midiCmdCount; i++, midiCmd++) @@ -467,7 +483,7 @@ namespace TFE_MidiPlayer { case MIDI_PAUSE: { - localTimeCallback = 0; + localTime = 0; isPaused = true; stopAllNotes(); } break; @@ -484,8 +500,8 @@ namespace TFE_MidiPlayer case MIDI_STOP_NOTES: { stopAllNotes(); - // Reset callback time. - localTimeCallback = 0; + // Reset local time. + localTime = 0; s_midiCallback.accumulator = 0.0; } break; } @@ -495,8 +511,8 @@ namespace TFE_MidiPlayer // Process the midi callback, if it exists. if (s_midiCallback.callback && !isPaused) { - s_midiCallback.accumulator += TFE_System::updateThreadLocal(&localTimeCallback); - while (s_midiCallback.callback && s_midiCallback.accumulator >= s_midiCallback.timeStep) + s_midiCallback.accumulator += TFE_System::updateThreadLocal(&localTime); + while (s_midiCallback.accumulator >= s_midiCallback.timeStep) { s_midiCallback.callback(); s_midiCallback.accumulator -= s_midiCallback.timeStep; @@ -505,10 +521,23 @@ namespace TFE_MidiPlayer // Check for hanging notes. detectHangingNotes(); + + // Sleep until the next playback window to + // avoid a busy waiting scenario. + auto timeout = (s_midiCallback.timeStep - s_midiCallback.accumulator) * 1000; + // TODO: In the future move to std::clamp + timeout = std::max({0, timeout}); + timeout = std::min({std::numeric_limits::max(), + timeout}); + + SDL_CondWaitTimeout(s_midiCmdCond, s_mutex, timeout); + } + else + { + SDL_CondWait(s_midiCmdCond, s_mutex); } SDL_UnlockMutex(s_mutex); - runThread = s_runMusicThread.load(); }; return 0;