Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
133 changes: 81 additions & 52 deletions TheForceEngine/TFE_Audio/midiPlayer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include <TFE_Audio/MidiSynth/fm4Opl3Device.h>
#include <algorithm>
#include <assert.h>
#include <limits>

#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN 1
Expand All @@ -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 };
Expand All @@ -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 = {};
Expand Down Expand Up @@ -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);

Expand All @@ -123,9 +152,6 @@ namespace TFE_MidiPlayer
}
}

s_runMusicThread.store(true);


s_thread = SDL_CreateThread(midiUpdateFunc, "TFE_MidiThread", nullptr);
if (!s_thread)
{
Expand All @@ -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);
}

Expand Down Expand Up @@ -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()
Expand All @@ -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.
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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);
}
Expand All @@ -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);
}

Expand Down Expand Up @@ -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++)
Expand All @@ -467,7 +483,7 @@ namespace TFE_MidiPlayer
{
case MIDI_PAUSE:
{
localTimeCallback = 0;
localTime = 0;
isPaused = true;
stopAllNotes();
} break;
Expand All @@ -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;
}
Expand All @@ -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;
Expand All @@ -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<decltype(timeout)>({0, timeout});
timeout = std::min<decltype(timeout)>({std::numeric_limits<Uint32>::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;
Expand Down