|
@@ -20,7 +20,7 @@
|
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
//
|
|
|
// native_midi.cpp: Native Windows Runtime MIDI player for SDLPal.
|
|
|
-// @author: Lou Yihua, 2017
|
|
|
+// @Author: Lou Yihua, 2017
|
|
|
//
|
|
|
|
|
|
#include "pch.h"
|
|
@@ -34,43 +34,45 @@
|
|
|
#include <mutex>
|
|
|
#include <atomic>
|
|
|
|
|
|
-extern "C" {
|
|
|
#include "native_midi/native_midi.h"
|
|
|
#include "native_midi/native_midi_common.h"
|
|
|
-}
|
|
|
+
|
|
|
+using namespace Windows::Devices::Midi;
|
|
|
|
|
|
struct MidiEvent
|
|
|
{
|
|
|
- Windows::Devices::Midi::IMidiMessage^ message;
|
|
|
- uint32_t deltaTime; // time in ticks
|
|
|
- uint32_t tempo; // microseconds per quarter note
|
|
|
+ IMidiMessage^ message;
|
|
|
+ uint32_t deltaTime; // time in ticks
|
|
|
+ uint32_t tempo; // microseconds per quarter note
|
|
|
|
|
|
std::chrono::system_clock::duration DeltaTimeAsTick(uint16_t ppq)
|
|
|
{
|
|
|
return std::chrono::system_clock::duration((int64_t)deltaTime * tempo * 10 / ppq);
|
|
|
}
|
|
|
+
|
|
|
+ void Send(MidiSynthesizer^ synthesizer) { synthesizer->SendMessage(message); }
|
|
|
};
|
|
|
|
|
|
struct _NativeMidiSong {
|
|
|
- Windows::Devices::Midi::MidiSynthesizer^ Synthesizer;
|
|
|
- MidiEvent* Events;
|
|
|
- int Size;
|
|
|
- int Position;
|
|
|
- Uint16 ppq; // parts (ticks) per quarter note
|
|
|
- volatile bool Playing;
|
|
|
- bool Loaded;
|
|
|
- std::thread Thread;
|
|
|
- std::mutex Mutex;
|
|
|
+ std::vector<MidiEvent> Events;
|
|
|
+ std::thread Thread;
|
|
|
+ std::mutex Mutex;
|
|
|
std::condition_variable Stop;
|
|
|
+ MidiSynthesizer^ Synthesizer;
|
|
|
+ int Size;
|
|
|
+ int Position;
|
|
|
+ Uint16 ppq; // parts (ticks) per quarter note
|
|
|
+ volatile bool Playing;
|
|
|
+ bool Loaded;
|
|
|
+ bool Looping;
|
|
|
|
|
|
_NativeMidiSong()
|
|
|
- : Events(nullptr), Size(0), Position(0)
|
|
|
- , ppq(0), Playing(false), Loaded(false)
|
|
|
- , Synthesizer(nullptr)
|
|
|
+ : Synthesizer(nullptr), Size(0), Position(0)
|
|
|
+ , ppq(0), Playing(false), Loaded(false), Looping(false)
|
|
|
{ }
|
|
|
};
|
|
|
|
|
|
-static std::atomic<NativeMidiSong*> CurrentSong = nullptr;
|
|
|
+static MidiSystemResetMessage^ ResetMessage = ref new MidiSystemResetMessage();
|
|
|
|
|
|
enum class MidiSystemMessage {
|
|
|
Exclusive = 0,
|
|
@@ -89,48 +91,50 @@ enum class MidiSystemMessage {
|
|
|
static void MIDItoStream(NativeMidiSong *song, MIDIEvent *eventlist)
|
|
|
{
|
|
|
int eventcount = 0, prevtime = 0, tempo = 500000;
|
|
|
- MIDIEvent *event = eventlist;
|
|
|
- while (event)
|
|
|
+
|
|
|
+ for (MIDIEvent* event = eventlist; event; event = event->next)
|
|
|
{
|
|
|
- eventcount++;
|
|
|
- event = event->next;
|
|
|
+ if (event->status != 0xFF)
|
|
|
+ eventcount++;
|
|
|
}
|
|
|
|
|
|
- if (!(song->Events = new MidiEvent[eventcount]))
|
|
|
- return;
|
|
|
+ song->Events.resize(song->Size = eventcount);
|
|
|
+ song->Position = 0;
|
|
|
+ song->Loaded = true;
|
|
|
|
|
|
- for (event = eventlist, eventcount = 0; event; event = event->next)
|
|
|
+ eventcount = 0;
|
|
|
+ for (MIDIEvent* event = eventlist; event; event = event->next)
|
|
|
{
|
|
|
- Windows::Devices::Midi::IMidiMessage^ message = nullptr;
|
|
|
+ IMidiMessage^ message = nullptr;
|
|
|
int status = (event->status & 0xF0) >> 4;
|
|
|
switch (status)
|
|
|
{
|
|
|
case MIDI_STATUS_NOTE_OFF:
|
|
|
- message = ref new Windows::Devices::Midi::MidiNoteOffMessage(event->status - (status << 4), event->data[0], event->data[1]);
|
|
|
+ message = ref new MidiNoteOffMessage(event->status - (status << 4), event->data[0], event->data[1]);
|
|
|
break;
|
|
|
|
|
|
case MIDI_STATUS_NOTE_ON:
|
|
|
- message = ref new Windows::Devices::Midi::MidiNoteOnMessage(event->status - (status << 4), event->data[0], event->data[1]);
|
|
|
+ message = ref new MidiNoteOnMessage(event->status - (status << 4), event->data[0], event->data[1]);
|
|
|
break;
|
|
|
|
|
|
case MIDI_STATUS_AFTERTOUCH:
|
|
|
- message = ref new Windows::Devices::Midi::MidiPolyphonicKeyPressureMessage(event->status - (status << 4), event->data[0], event->data[1]);
|
|
|
+ message = ref new MidiPolyphonicKeyPressureMessage(event->status - (status << 4), event->data[0], event->data[1]);
|
|
|
break;
|
|
|
|
|
|
case MIDI_STATUS_CONTROLLER:
|
|
|
- message = ref new Windows::Devices::Midi::MidiControlChangeMessage(event->status - (status << 4), event->data[0], event->data[1]);
|
|
|
+ message = ref new MidiControlChangeMessage(event->status - (status << 4), event->data[0], event->data[1]);
|
|
|
break;
|
|
|
|
|
|
case MIDI_STATUS_PROG_CHANGE:
|
|
|
- message = ref new Windows::Devices::Midi::MidiProgramChangeMessage(event->status - (status << 4), event->data[0]);
|
|
|
+ message = ref new MidiProgramChangeMessage(event->status - (status << 4), event->data[0]);
|
|
|
break;
|
|
|
|
|
|
case MIDI_STATUS_PRESSURE:
|
|
|
- message = ref new Windows::Devices::Midi::MidiChannelPressureMessage(event->status - (status << 4), event->data[0]);
|
|
|
+ message = ref new MidiChannelPressureMessage(event->status - (status << 4), event->data[0]);
|
|
|
break;
|
|
|
|
|
|
case MIDI_STATUS_PITCH_WHEEL:
|
|
|
- message = ref new Windows::Devices::Midi::MidiPitchBendChangeMessage(event->status - (status << 4), event->data[0] | (event->data[1] << 7));
|
|
|
+ message = ref new MidiPitchBendChangeMessage(event->status - (status << 4), event->data[0] | (event->data[1] << 7));
|
|
|
break;
|
|
|
|
|
|
case MIDI_STATUS_SYSEX:
|
|
@@ -141,50 +145,50 @@ static void MIDItoStream(NativeMidiSong *song, MIDIEvent *eventlist)
|
|
|
auto buffer = NativeBuffer::GetIBuffer(event->extraData, event->extraLen);
|
|
|
if (buffer)
|
|
|
{
|
|
|
- message = ref new Windows::Devices::Midi::MidiSystemExclusiveMessage(buffer);
|
|
|
+ message = ref new MidiSystemExclusiveMessage(buffer);
|
|
|
delete buffer;
|
|
|
}
|
|
|
}
|
|
|
break;
|
|
|
|
|
|
case MidiSystemMessage::TimeCode:
|
|
|
- message = ref new Windows::Devices::Midi::MidiTimeCodeMessage(event->extraData[0] >> 4, event->extraData[0] & 0xF);
|
|
|
+ message = ref new MidiTimeCodeMessage(event->extraData[0] >> 4, event->extraData[0] & 0xF);
|
|
|
break;
|
|
|
|
|
|
case MidiSystemMessage::SongPositionPointer:
|
|
|
- message = ref new Windows::Devices::Midi::MidiSongPositionPointerMessage(event->extraData[0] | (event->extraData[1] << 7));
|
|
|
+ message = ref new MidiSongPositionPointerMessage(event->extraData[0] | (event->extraData[1] << 7));
|
|
|
break;
|
|
|
|
|
|
case MidiSystemMessage::SongSelect:
|
|
|
- message = ref new Windows::Devices::Midi::MidiSongSelectMessage(event->extraData[0]);
|
|
|
+ message = ref new MidiSongSelectMessage(event->extraData[0]);
|
|
|
break;
|
|
|
|
|
|
case MidiSystemMessage::TuneRequest:
|
|
|
- message = ref new Windows::Devices::Midi::MidiTuneRequestMessage();
|
|
|
+ message = ref new MidiTuneRequestMessage();
|
|
|
break;
|
|
|
|
|
|
case MidiSystemMessage::TimingClock:
|
|
|
- message = ref new Windows::Devices::Midi::MidiTimingClockMessage();
|
|
|
+ message = ref new MidiTimingClockMessage();
|
|
|
break;
|
|
|
|
|
|
case MidiSystemMessage::Start:
|
|
|
- message = ref new Windows::Devices::Midi::MidiStartMessage();
|
|
|
+ message = ref new MidiStartMessage();
|
|
|
break;
|
|
|
|
|
|
case MidiSystemMessage::Continue:
|
|
|
- message = ref new Windows::Devices::Midi::MidiContinueMessage();
|
|
|
+ message = ref new MidiContinueMessage();
|
|
|
break;
|
|
|
|
|
|
case MidiSystemMessage::Stop:
|
|
|
- message = ref new Windows::Devices::Midi::MidiStopMessage();
|
|
|
+ message = ref new MidiStopMessage();
|
|
|
break;
|
|
|
|
|
|
case MidiSystemMessage::ActiveSensing:
|
|
|
- message = ref new Windows::Devices::Midi::MidiActiveSensingMessage();
|
|
|
+ message = ref new MidiActiveSensingMessage();
|
|
|
break;
|
|
|
|
|
|
case MidiSystemMessage::SystemReset:
|
|
|
- message = ref new Windows::Devices::Midi::MidiSystemResetMessage();
|
|
|
+ // This message is only used as meta-event in MIDI files
|
|
|
if (event->data[0] == 0x51)
|
|
|
tempo = (event->extraData[0] << 16) | (event->extraData[1] << 8) | event->extraData[2];
|
|
|
break;
|
|
@@ -200,15 +204,11 @@ static void MIDItoStream(NativeMidiSong *song, MIDIEvent *eventlist)
|
|
|
prevtime = event->time; eventcount++;
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
- song->Size = eventcount;
|
|
|
- song->Position = 0;
|
|
|
- song->Loaded = 1;
|
|
|
}
|
|
|
|
|
|
int native_midi_detect()
|
|
|
{
|
|
|
- auto synthesizer = AWait(Windows::Devices::Midi::MidiSynthesizer::CreateAsync());
|
|
|
+ auto synthesizer = AWait(MidiSynthesizer::CreateAsync());
|
|
|
if (synthesizer)
|
|
|
{
|
|
|
delete synthesizer;
|
|
@@ -236,7 +236,9 @@ NativeMidiSong *native_midi_loadsong_RW(SDL_RWops *rw)
|
|
|
{
|
|
|
MIDItoStream(newsong.get(), evntlist);
|
|
|
FreeMIDIEventList(evntlist);
|
|
|
- return newsong.release();
|
|
|
+
|
|
|
+ if (newsong->Synthesizer = AWait(MidiSynthesizer::CreateAsync()))
|
|
|
+ return newsong.release();
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -247,83 +249,81 @@ void native_midi_freesong(NativeMidiSong *song)
|
|
|
{
|
|
|
if (song)
|
|
|
{
|
|
|
- native_midi_stop();
|
|
|
-
|
|
|
- if (song->Events)
|
|
|
- delete[] song->Events;
|
|
|
+ native_midi_stop(song);
|
|
|
+ if (song->Synthesizer)
|
|
|
+ delete song->Synthesizer;
|
|
|
delete song;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-void native_midi_start(NativeMidiSong *song)
|
|
|
+void native_midi_start(NativeMidiSong *song, int looping)
|
|
|
{
|
|
|
if (!song) return;
|
|
|
|
|
|
- native_midi_stop();
|
|
|
-
|
|
|
- if (!song->Synthesizer)
|
|
|
- {
|
|
|
- song->Synthesizer = AWait(Windows::Devices::Midi::MidiSynthesizer::CreateAsync());
|
|
|
- if (!song->Synthesizer) return;
|
|
|
- }
|
|
|
+ native_midi_stop(song);
|
|
|
|
|
|
+ song->Playing = true;
|
|
|
+ song->Looping = looping ? true : false;
|
|
|
song->Thread = std::move(std::thread([](NativeMidiSong *song)->void {
|
|
|
auto time = std::chrono::system_clock::now();
|
|
|
- while (song->Position < song->Size)
|
|
|
+ while (song->Playing)
|
|
|
{
|
|
|
do
|
|
|
{
|
|
|
- song->Synthesizer->SendMessage(song->Events[song->Position++].message);
|
|
|
+ song->Events[song->Position++].Send(song->Synthesizer);
|
|
|
} while (song->Position < song->Size && song->Events[song->Position].deltaTime == 0);
|
|
|
- time += std::chrono::system_clock::duration(song->Events[song->Position].DeltaTimeAsTick(song->ppq));
|
|
|
- if (song->Stop.wait_until(std::unique_lock<std::mutex>(song->Mutex), time) == std::cv_status::no_timeout) break;
|
|
|
+ if (song->Position < song->Size)
|
|
|
+ {
|
|
|
+ time += std::chrono::system_clock::duration(song->Events[song->Position].DeltaTimeAsTick(song->ppq));
|
|
|
+ while (song->Playing)
|
|
|
+ {
|
|
|
+ if (song->Stop.wait_until(std::unique_lock<std::mutex>(song->Mutex), time) == std::cv_status::timeout)
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else if (song->Playing = song->Looping)
|
|
|
+ {
|
|
|
+ song->Position = 0;
|
|
|
+ song->Synthesizer->SendMessage(ResetMessage);
|
|
|
+ }
|
|
|
}
|
|
|
- song->Playing = false;
|
|
|
}, song));
|
|
|
-
|
|
|
- song->Playing = true;
|
|
|
-
|
|
|
- CurrentSong.exchange(song);
|
|
|
}
|
|
|
|
|
|
-void native_midi_stop()
|
|
|
+void native_midi_stop(NativeMidiSong *song)
|
|
|
{
|
|
|
- NativeMidiSong* song;
|
|
|
- if (song = CurrentSong.exchange(nullptr))
|
|
|
+ if (song)
|
|
|
{
|
|
|
+ song->Playing = false;
|
|
|
song->Stop.notify_all();
|
|
|
if (song->Thread.joinable())
|
|
|
song->Thread.join();
|
|
|
song->Thread = std::move(std::thread());
|
|
|
- song->Playing = false;
|
|
|
if (song->Synthesizer)
|
|
|
{
|
|
|
- delete song->Synthesizer;
|
|
|
- song->Synthesizer = nullptr;
|
|
|
+ song->Synthesizer->SendMessage(ResetMessage);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-int native_midi_active()
|
|
|
+int native_midi_active(NativeMidiSong *song)
|
|
|
{
|
|
|
- auto song = CurrentSong.load();
|
|
|
return (song && song->Playing) ? 1 : 0;
|
|
|
}
|
|
|
|
|
|
-void native_midi_setvolume(int volume)
|
|
|
+void native_midi_setvolume(NativeMidiSong *song, int volume)
|
|
|
{
|
|
|
- auto song = CurrentSong.load();
|
|
|
if (song && song->Synthesizer)
|
|
|
{
|
|
|
- if (volume > 128)
|
|
|
- volume = 128;
|
|
|
+ if (volume > 127)
|
|
|
+ volume = 127;
|
|
|
else if (volume < 0)
|
|
|
volume = 0;
|
|
|
- song->Synthesizer->Volume = (double)volume / 128.0;
|
|
|
+ song->Synthesizer->Volume = (double)volume / 127.0;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-const char *native_midi_error(void)
|
|
|
+const char *native_midi_error(NativeMidiSong *song)
|
|
|
{
|
|
|
return "";
|
|
|
}
|