diff --git a/CMakeLists.txt b/CMakeLists.txt index f35dadb2..cd1d397d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -129,13 +129,8 @@ else() add_compile_options("-ffp-contract=off") if(APPLE) - set(CMAKE_OSX_DEPLOYMENT_TARGET "10.9") - if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") - # With standard Apple tools -stdlib=libc++ needs to be specified in order to get - # C++11 support using SDKs 10.7 and 10.8. - add_compile_options("-stdlib=libc++") - add_link_options("-stdlib=libc++") - elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + set(CMAKE_OSX_DEPLOYMENT_TARGET "11.0") + if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") # If we're compiling with a custom GCC on the Mac (which we know since g++-4.2 doesn't support C++11) statically link libgcc. add_compile_options("-static-libgcc") endif() diff --git a/licenses/bsd.txt b/licenses/bsd.txt index aa48716a..f78cd277 100644 --- a/licenses/bsd.txt +++ b/licenses/bsd.txt @@ -1,5 +1,5 @@ **--------------------------------------------------------------------------- -** Copyright 1998-2009 Randy Heit, Christoph Oelckers, et al. +** Copyright 1998-2009 Marisa Heit, Christoph Oelckers, et al. ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without diff --git a/source/mididevices/music_adlmidi_mididevice.cpp b/source/mididevices/music_adlmidi_mididevice.cpp index 1f850e89..e4d60645 100644 --- a/source/mididevices/music_adlmidi_mididevice.cpp +++ b/source/mididevices/music_adlmidi_mididevice.cpp @@ -3,7 +3,7 @@ ** Provides access to TiMidity as a generic MIDI device. ** **--------------------------------------------------------------------------- -** Copyright 2008 Randy Heit +** Copyright 2008 Marisa Heit ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without diff --git a/source/mididevices/music_alsa_mididevice.cpp b/source/mididevices/music_alsa_mididevice.cpp index 142c9038..2848bbb5 100644 --- a/source/mididevices/music_alsa_mididevice.cpp +++ b/source/mididevices/music_alsa_mididevice.cpp @@ -2,7 +2,7 @@ ** Provides an ALSA implementation of a MIDI output device. ** **--------------------------------------------------------------------------- -** Copyright 2008-2010 Randy Heit +** Copyright 2008-2010 Marisa Heit ** Copyright 2020 Petr Mrazek ** All rights reserved. ** @@ -34,7 +34,7 @@ #if defined __linux__ && defined HAVE_SYSTEM_MIDI -#include +#include #include #include #include @@ -57,30 +57,35 @@ class AlsaMIDIDevice : public MIDIDevice int GetTechnology() const override; int SetTempo(int tempo) override; int SetTimeDiv(int timediv) override; - int StreamOut(MidiHeader *data) override; - int StreamOutSync(MidiHeader *data) override; + int StreamOut(MidiHeader* data) override; + int StreamOutSync(MidiHeader* data) override; int Resume() override; void Stop() override; - bool FakeVolume() override { return true; }; //Not sure if we even can control the volume this way with Alsa, so make it fake. + bool FakeVolume() override; bool Pause(bool paused) override; void InitPlayback() override; - bool Update() override; - bool CanHandleSysex() const override { return true; } //Assume we can, let Alsa sort it out. - void PrecacheInstruments(const uint16_t *instruments, int count) override; + void PrecacheInstruments(const uint16_t* instruments, int count) override; protected: + bool Precache; + bool PullEvent(); void PlayerLoop(); - void HandleEvent(snd_seq_event_t &event, uint tick); - AlsaSequencer &sequencer; - MidiHeader *Events = nullptr; - snd_seq_event_t Event; + // Event handling + void HandleEvent(snd_seq_event_t& event, uint32_t tick); + void SendImmediateShortMsg(uint8_t command, uint8_t data1 = 0, uint8_t data2 = 0); snd_midi_event_t* Coder = nullptr; - uint32_t Position = 0; - uint32_t PositionOffset; - uint NextEventTickDelta; + // PulledEvent structure to hold the next event to be processed + struct + { + snd_seq_event_t event; + uint32_t tick_delta; + } PulledEvent; + + // Alsa sequencer handles + AlsaSequencer& sequencer; const static int IntendedPortId = 0; bool Connected = false; int PortId = -1; @@ -89,22 +94,28 @@ class AlsaMIDIDevice : public MIDIDevice int DestinationClientId; int DestinationPortId; int Technology; - bool Precache; - - int InitialTempo = 480000; - int Tempo; - int TimeDiv = 480; + // Threading std::thread PlayerThread; - volatile bool Exit = false; + std::atomic Exit; std::mutex Mutex; std::condition_variable ExitCond; + + // Timing + int64_t InitialTempo = 500000; + int64_t Tempo; + int64_t Division = 100; // PPQN + + // ZMusic MidiHeader data + MidiHeader* Events = nullptr; + uint32_t Position = 0; + uint32_t PositionOffset; }; -AlsaMIDIDevice::AlsaMIDIDevice(int dev_id, bool precache) : sequencer(AlsaSequencer::Get()) +AlsaMIDIDevice::AlsaMIDIDevice(int dev_id, bool precache) : sequencer{AlsaSequencer::Get()} { - auto & internalDevices = sequencer.GetInternalDevices(); - auto & device = internalDevices.at(dev_id); + auto& internalDevices = sequencer.GetInternalDevices(); + auto& device = internalDevices.at(dev_id); DestinationClientId = device.ClientID; DestinationPortId = device.PortNumber; Precache = precache; @@ -125,7 +136,7 @@ int AlsaMIDIDevice::Open() if (PortId < 0) { - snd_seq_port_info_t *pinfo; + snd_seq_port_info_t* pinfo; snd_seq_port_info_alloca(&pinfo); snd_seq_port_info_set_port(pinfo, IntendedPortId); @@ -138,7 +149,6 @@ int AlsaMIDIDevice::Open() snd_midi_event_new(3, &Coder); // 3 Bytes for short messages. snd_midi_event_init(Coder); - snd_seq_ev_clear(&Event); int err = 0; err = snd_seq_create_port(sequencer.handle, pinfo); @@ -192,6 +202,11 @@ int AlsaMIDIDevice::GetTechnology() const return Technology; } +bool AlsaMIDIDevice::FakeVolume() +{ + return true; // No true volume control support, so fake volume +} + int AlsaMIDIDevice::SetTempo(int tempo) { InitialTempo = tempo; @@ -200,12 +215,12 @@ int AlsaMIDIDevice::SetTempo(int tempo) int AlsaMIDIDevice::SetTimeDiv(int timediv) { - TimeDiv = timediv; + Division = timediv; return 0; } // This is meant to mirror WinMIDIDevice::PrecacheInstruments -void AlsaMIDIDevice::PrecacheInstruments(const uint16_t *instruments, int count) +void AlsaMIDIDevice::PrecacheInstruments(const uint16_t* instruments, int count) { // Setting snd_midiprecache to false disables this precaching, since it // does involve sleeping for more than a miniscule amount of time. @@ -213,9 +228,8 @@ void AlsaMIDIDevice::PrecacheInstruments(const uint16_t *instruments, int count) { return; } - uint8_t bank[16] = {0}; + uint8_t bank[16] = {}; uint8_t i, chan; - std::array message; for (i = 0, chan = 0; i < count; ++i) { @@ -227,30 +241,20 @@ void AlsaMIDIDevice::PrecacheInstruments(const uint16_t *instruments, int count) { if (bank[9] != banknum) { - message = { MIDI_CTRLCHANGE | 9, 0, banknum }; - snd_midi_event_encode(Coder, message.data(), 3, &Event); - HandleEvent(Event, 0); + SendImmediateShortMsg(MIDI_CTRLCHANGE | 9, 0, banknum); bank[9] = banknum; } - message = { MIDI_NOTEON | 9, instr, 1 }; - snd_midi_event_encode(Coder, message.data(), 3, &Event); - HandleEvent(Event, 0); + SendImmediateShortMsg(MIDI_NOTEON | 9, instr, 1); } else { // Melodic if (bank[chan] != banknum) { - message = { MIDI_CTRLCHANGE | 9, 0, banknum }; - snd_midi_event_encode(Coder, message.data(), 3, &Event); - HandleEvent(Event, 0); + SendImmediateShortMsg(MIDI_CTRLCHANGE | 9, 0, banknum); bank[chan] = banknum; } - message = { (uint8_t)(MIDI_PRGMCHANGE | chan), (uint8_t)instruments[i] }; - snd_midi_event_encode(Coder, message.data(), 2, &Event); - HandleEvent(Event, 0); - message = { (uint8_t)(MIDI_NOTEON | chan), 60, 1 }; - snd_midi_event_encode(Coder, message.data(), 3, &Event); - HandleEvent(Event, 0); + SendImmediateShortMsg(MIDI_PRGMCHANGE | chan, instruments[i]); + SendImmediateShortMsg(MIDI_NOTEON | chan, 60, 1); if (++chan == 9) { // Skip the percussion channel chan = 10; @@ -265,9 +269,7 @@ void AlsaMIDIDevice::PrecacheInstruments(const uint16_t *instruments, int count) for (chan = 15; chan-- != 0; ) { // Turn all notes off - message = { (uint8_t)(MIDI_CTRLCHANGE | chan), 123, 0 }; - snd_midi_event_encode(Coder, message.data(), 3, &Event); - HandleEvent(Event, 0); + SendImmediateShortMsg(MIDI_CTRLCHANGE | chan, 123, 0); } // And now chan is back at 0, ready to start the cycle over. } @@ -277,15 +279,76 @@ void AlsaMIDIDevice::PrecacheInstruments(const uint16_t *instruments, int count) { if (bank[i] != 0) { - message = { MIDI_CTRLCHANGE | 9, 0, 0 }; - snd_midi_event_encode(Coder, message.data(), 3, &Event); - HandleEvent(Event, 0); + SendImmediateShortMsg(MIDI_CTRLCHANGE | 9, 0, 0); } } - // Wait until all events are processed +} + +void AlsaMIDIDevice::InitPlayback() +{ + Exit.store(false, std::memory_order_relaxed); +} + +int AlsaMIDIDevice::Resume() +{ + if (!Connected || PlayerThread.joinable()) + { + return 1; + } + Exit.store(false, std::memory_order_relaxed); + PlayerThread = std::thread(&AlsaMIDIDevice::PlayerLoop, this); + return 0; +} + +void AlsaMIDIDevice::Stop() +{ + Exit.store(true, std::memory_order_relaxed); + ExitCond.notify_all(); + if (PlayerThread.joinable()) + { + PlayerThread.join(); + } + snd_seq_drop_output(sequencer.handle); // This drops events in the sequencer, the sequencer is still usable + snd_seq_stop_queue(sequencer.handle, QueueId, nullptr); + snd_seq_drain_output(sequencer.handle); + + // Reset all channels to prevent hanging notes + for (int channel = 0; channel < 16; ++channel) + { + SendImmediateShortMsg(MIDI_CTRLCHANGE | channel, 123, 0); // All Notes Off + SendImmediateShortMsg(MIDI_CTRLCHANGE | channel, 121, 0); // Reset All Controllers + } snd_seq_sync_output_queue(sequencer.handle); } +bool AlsaMIDIDevice::Pause(bool paused) +{ + return false; // Pausing is not supported +} + +int AlsaMIDIDevice::StreamOut(MidiHeader* header) +{ + header->lpNext = nullptr; + if (Events == nullptr) + { + Events = header; + Position = 0; + } + else + { + MidiHeader** p; + for (p = &Events; *p != nullptr; p = &(*p)->lpNext) + { } + *p = header; + } + return 0; +} + +int AlsaMIDIDevice::StreamOutSync(MidiHeader* header) +{ + return StreamOut(header); +} + bool AlsaMIDIDevice::PullEvent() { if (!Events && Callback) @@ -299,12 +362,12 @@ bool AlsaMIDIDevice::PullEvent() } if (Position >= Events->dwBytesRecorded) - { // All events in the "Events" buffer were used, point to next buffer + { // All events in the buffer were used, point to next buffer Events = Events->lpNext; Position = 0; - if (Callback != NULL) - { // This ensures that we always have 2 unused buffers after 1 is used up. - // omit this nested "if" block if you want to use up the 2 buffers before requesting new buffers + if (Callback) + { // This ensures that we always have the maximum number of unused buffers (most likely 2) after 1 is used up. + // omit this nested "if" block if you want to use up all buffers before requesting new buffers Callback(CallbackData); } } @@ -314,16 +377,16 @@ bool AlsaMIDIDevice::PullEvent() return false; } - uint32_t *event = (uint32_t *)(Events->lpData + Position); - NextEventTickDelta = event[0]; + const uint32_t* event = (uint32_t*)(Events->lpData + Position); + PulledEvent.tick_delta = event[0]; // First 4 bytes of event // Get event size to advance Position - if (event[2] < 0x80000000) - { // Short message - PositionOffset = 12; + if (event[2] < 0x80000000) // Short message (event[2] is the combined status/data bytes) + { + PositionOffset = 12; // 4 bytes delta time, 4 bytes reserved, 4 bytes MIDI message (up to 3 bytes + padding) } - else - { // Long message + else // Long message or meta-event (event[2] holds type and parameter length) + { PositionOffset = 12 + ((MEVENT_EVENTPARM(event[2]) + 3) & ~3); } @@ -331,34 +394,35 @@ bool AlsaMIDIDevice::PullEvent() switch (MEVENT_EVENTTYPE(event[2])) { case MEVENT_TEMPO: - { - int tempo = MEVENT_EVENTPARM(event[2]); - snd_seq_ev_set_queue_tempo(&Event, QueueId, tempo); + snd_seq_ev_set_queue_tempo(&PulledEvent.event, QueueId, MEVENT_EVENTPARM(event[2])); break; - } - case MEVENT_LONGMSG: // SysEx messages... - { - uint8_t* data = (uint8_t *)&event[3]; - int len = MEVENT_EVENTPARM(event[2]); - if (len > 2 && data[0] == 0xF0 && data[len - 1] == 0xF7) + case MEVENT_LONGMSG: // SysEx message... { - snd_seq_ev_set_sysex(&Event, len, (void*)data); + uint32_t long_msg_len = MEVENT_EVENTPARM(event[2]); + const uint8_t* long_msg_data = (uint8_t*)&event[3]; + // Ensure valid sysex message + if (long_msg_len > 2 && long_msg_data[0] == 0xF0 && long_msg_data[long_msg_len - 1] == 0xF7) + { + snd_seq_ev_set_sysex(&PulledEvent.event, long_msg_len, (void*)long_msg_data); + } + else + { + PulledEvent.event.type = SND_SEQ_EVENT_NONE; + } + break; + } + case MEVENT_SHORTMSG: + { + uint8_t msg[3] = { (uint8_t)(event[2] & 0xff), // Status + (uint8_t)((event[2] >> 8) & 0xff), // Data 1 + (uint8_t)((event[2] >> 16) & 0xff) }; // Data 2 + + // This silently ignores extra bytes, so no message length logic is needed. + snd_midi_event_encode(Coder, msg, 3, &PulledEvent.event); break; } - } - case 0: // Short MIDI event - { - uint8_t status = event[2] & 0xFF; - uint8_t param1 = (event[2] >> 8) & 0x7f; - uint8_t param2 = (event[2] >> 16) & 0x7f; - uint8_t message[] = {status, param1, param2}; - // This silently ignore extra bytes, so no message length logic is needed. - snd_midi_event_encode(Coder, message, 3, &Event); - break; - } default: // We didn't really recognize the event, treat it as a NOP - Event.type = SND_SEQ_EVENT_NONE; - snd_seq_ev_set_fixed(&Event); + PulledEvent.event.type = SND_SEQ_EVENT_NONE; } return true; } @@ -370,26 +434,29 @@ bool AlsaMIDIDevice::PullEvent() */ void AlsaMIDIDevice::PlayerLoop() { - std::unique_lock lock(Mutex); - const std::chrono::microseconds buffer_step(40000); + std::unique_lock lock{Mutex}; + using namespace std::literals::chrono_literals; + constexpr std::chrono::microseconds buffer_step = 40ms; // TODO: fill in error handling throughout this. - snd_seq_queue_tempo_t *tempo; + snd_seq_queue_tempo_t* tempo; snd_seq_queue_tempo_alloca(&tempo); snd_seq_queue_tempo_set_tempo(tempo, InitialTempo); - snd_seq_queue_tempo_set_ppq(tempo, TimeDiv); + snd_seq_queue_tempo_set_ppq(tempo, Division); snd_seq_set_queue_tempo(sequencer.handle, QueueId, tempo); - snd_seq_start_queue(sequencer.handle, QueueId, NULL); + snd_seq_start_queue(sequencer.handle, QueueId, nullptr); snd_seq_drain_output(sequencer.handle); Tempo = InitialTempo; - int buffered_ticks = 0; + uint32_t buffer_tick = 0; - snd_seq_queue_status_t *status; + snd_seq_queue_status_t* status; snd_seq_queue_status_malloc(&status); - while (!Exit) + snd_seq_ev_clear(&PulledEvent.event); + + while (!Exit.load(std::memory_order_relaxed)) { // if we reach the end of events, await our doom at a steady rate while looking for more events if (!PullEvent()) @@ -399,36 +466,42 @@ void AlsaMIDIDevice::PlayerLoop() } // Figure out if we should sleep (the event is too far in the future for us to care), and for how long - int next_event_tick = buffered_ticks + NextEventTickDelta; + auto pulled_event_tick = buffer_tick + PulledEvent.tick_delta; snd_seq_get_queue_status(sequencer.handle, QueueId, status); - int queue_tick = snd_seq_queue_status_get_tick_time(status); - int tick_delta = next_event_tick - queue_tick; - auto usecs = std::chrono::microseconds(tick_delta * Tempo / TimeDiv); - auto schedule_time = std::max(std::chrono::microseconds(0), usecs - buffer_step); + auto queue_tick = snd_seq_queue_status_get_tick_time(status); + auto ticks_until_pulled_ev = int64_t{pulled_event_tick} - queue_tick; + std::chrono::microseconds time_until_pulled_ev{ticks_until_pulled_ev * Tempo / Division}; + auto schedule_time = time_until_pulled_ev - buffer_step; if (schedule_time >= buffer_step) { - ExitCond.wait_for(lock, schedule_time); - continue; + if (ExitCond.wait_for(lock, schedule_time) == std::cv_status::no_timeout) + { + continue; + } } - if (tick_delta < 0) - { // Can be triggered on rare occasions on playback start. + if (ticks_until_pulled_ev < 0) + { // Can be triggered on playback start. // Message shouldn't be shown by default like other midi backends here. - ZMusic_Printf(ZMUSIC_MSG_NOTIFY, "Alsa sequencer underrun: %d ticks!\n", tick_delta); + ZMusic_Printf(ZMUSIC_MSG_DEBUG, "Alsa sequencer underrun: %d ticks!\n", ticks_until_pulled_ev); } // We found an event worthy of sending to the sequencer - HandleEvent(Event, next_event_tick); - buffered_ticks = next_event_tick; + HandleEvent(PulledEvent.event, pulled_event_tick); + buffer_tick = pulled_event_tick; Position += PositionOffset; } - snd_seq_ev_clear(&Event); // Event is cleared to be used in reset messages in Stop() snd_seq_queue_status_free(status); } -// Requires QueueId to be started first for non-zero tick position -void AlsaMIDIDevice::HandleEvent(snd_seq_event_t &event, uint tick) +// Requires QueueId to be started first for non-zero tick positioned events. +void AlsaMIDIDevice::HandleEvent(snd_seq_event_t& event, uint32_t tick) { + if (event.type == SND_SEQ_EVENT_NONE) + { // NOP event, clear event handle and return. + snd_seq_ev_clear(&event); + return; + } snd_seq_ev_set_source(&event, PortId); snd_seq_ev_set_subs(&event); if (event.type == SND_SEQ_EVENT_TEMPO) @@ -438,7 +511,7 @@ void AlsaMIDIDevice::HandleEvent(snd_seq_event_t &event, uint tick) event.dest.port = SND_SEQ_PORT_SYSTEM_TIMER; } snd_seq_ev_schedule_tick(&event, QueueId, false, tick); - int result = snd_seq_event_output(sequencer.handle, &event); + auto result = snd_seq_event_output(sequencer.handle, &event); if (result < 0) { ZMusic_Printf(ZMUSIC_MSG_ERROR, "Alsa sequencer did not accept event: error %d!\n", result); @@ -447,79 +520,17 @@ void AlsaMIDIDevice::HandleEvent(snd_seq_event_t &event, uint tick) snd_seq_ev_clear(&event); } - -int AlsaMIDIDevice::Resume() -{ - if (!Connected || PlayerThread.joinable()) - { - return 1; - } - Exit = false; - PlayerThread = std::thread(&AlsaMIDIDevice::PlayerLoop, this); - return 0; -} - -void AlsaMIDIDevice::InitPlayback() -{ - Exit = false; -} - -void AlsaMIDIDevice::Stop() -{ - Exit = true; - ExitCond.notify_all(); - PlayerThread.join(); - snd_seq_drop_output(sequencer.handle); // This drops events in the sequencer, the sequencer is still usable - - // Reset all channels to prevent hanging notes - for (int channel = 0; channel < 16; ++channel) - { - snd_seq_ev_set_controller(&Event, channel, MIDI_CTL_ALL_NOTES_OFF, 0); - HandleEvent(Event, 0); - snd_seq_ev_set_controller(&Event, channel, MIDI_CTL_RESET_CONTROLLERS, 0); - HandleEvent(Event, 0); - } - snd_seq_sync_output_queue(sequencer.handle); -} - -bool AlsaMIDIDevice::Pause(bool paused) -{ - // TODO: implement - return false; -} - - -int AlsaMIDIDevice::StreamOut(MidiHeader *header) +// For use with PrecacheInstruments and Stop messages. +void AlsaMIDIDevice::SendImmediateShortMsg(uint8_t command, uint8_t data1, uint8_t data2) { - header->lpNext = NULL; - if (Events == NULL) - { - Events = header; - Position = 0; - } - else - { - MidiHeader **p; - - for (p = &Events; *p != NULL; p = &(*p)->lpNext) - { } - *p = header; - } - return 0; -} - - -int AlsaMIDIDevice::StreamOutSync(MidiHeader *header) -{ - return StreamOut(header); -} - -bool AlsaMIDIDevice::Update() -{ - return true; + uint8_t msg[3] = { command, data1, data2 }; + snd_seq_event_t event; + snd_seq_ev_clear(&event); + snd_midi_event_encode(Coder, msg, 3, &event); + HandleEvent(event, 0); } -MIDIDevice *CreateAlsaMIDIDevice(int mididevice) +MIDIDevice* CreateAlsaMIDIDevice(int mididevice) { return new AlsaMIDIDevice(mididevice, miscConfig.snd_midiprecache); } diff --git a/source/mididevices/music_alsa_state.h b/source/mididevices/music_alsa_state.h index 78e84751..8105a269 100644 --- a/source/mididevices/music_alsa_state.h +++ b/source/mididevices/music_alsa_state.h @@ -2,7 +2,7 @@ ** Provides an implementation of an ALSA sequencer wrapper ** **--------------------------------------------------------------------------- -** Copyright 2008-2010 Randy Heit +** Copyright 2008-2010 Marisa Heit ** Copyright 2020 Petr Mrazek ** All rights reserved. ** diff --git a/source/mididevices/music_base_mididevice.cpp b/source/mididevices/music_base_mididevice.cpp index dcf856ab..711a9fb2 100644 --- a/source/mididevices/music_base_mididevice.cpp +++ b/source/mididevices/music_base_mididevice.cpp @@ -3,7 +3,7 @@ ** Implements base class for MIDI and MUS streaming. ** **--------------------------------------------------------------------------- -** Copyright 2008 Randy Heit +** Copyright 2008 Marisa Heit ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without diff --git a/source/mididevices/music_coremidi_mididevice.mm b/source/mididevices/music_coremidi_mididevice.mm index 5a78ed43..169410e0 100644 --- a/source/mididevices/music_coremidi_mididevice.mm +++ b/source/mididevices/music_coremidi_mididevice.mm @@ -37,15 +37,15 @@ #include #include -#include +#include #include #include #include -#include "../zmusic/zmusic_internal.h" #include "mididevice.h" #include "zmusic/mididefs.h" #include "zmusic/mus2midi.h" +#include "zmusic/zmusic_internal.h" //========================================================================== // @@ -58,7 +58,7 @@ class CoreMIDIDevice : public MIDIDevice { public: - CoreMIDIDevice(int deviceID, bool precache); + CoreMIDIDevice(int dev_id, bool precache); ~CoreMIDIDevice(); int Open() override; @@ -71,64 +71,59 @@ int StreamOutSync(MidiHeader* data) override; int Resume() override; void Stop() override; - bool Pause(bool paused) override; bool FakeVolume() override; + bool Pause(bool paused) override; void InitPlayback() override; void PrecacheInstruments(const uint16_t* instruments, int count) override; protected: - void CalcTickRate(); - bool PullEvent(); + bool Precache; - // CoreMIDI handles - MIDIClientRef midiClient; - MIDIPortRef midiOutPort; - MIDIEndpointRef midiDestination; - int deviceID; + bool PullEvent(); + void PlayerLoop(); // Event handling - enum EventType { TempoEv, MidiMsgEv, NoEvent }; - union EventMsg - { - uint32_t Tempo; - uint8_t* MidiMsg; - }; - struct CurrentEvent - { - EventType EventType; - EventMsg EventMsg; - uint32_t length; - }; - CurrentEvent CurrentEvent; - std::array ShortMsgBuffer; void PrepareTempo(uint32_t tempo); - void PrepareMidiMsg(uint8_t* msg, uint32_t length); - void HandleCurrentEvent(); - void SendMIDIData(const uint8_t* data, size_t length, MIDITimeStamp timestamp); + void PrepareShortMsg(uint32_t msg); + void PrepareLongMsg(const uint8_t* long_msg, uint32_t length); + void HandleEvent(const uint32_t* data, ByteCount word_count, MIDITimeStamp timestamp); + void SendImmediateShortMsg(uint32_t command, uint32_t data1 = 0, uint32_t data2 = 0); + + // PulledEvent structure to hold the next event to be processed + enum EventType { EVENT_TEMPO, EVENT_MESSAGE, EVENT_NOP }; + struct + { + union + { + uint32_t tempo; + uint32_t msg_buffer[64]; + } data; + EventType type; + ByteCount word_count; + uint32_t tick_delta; + } PulledEvent; + + // CoreMIDI handles + inline static MIDIClientRef MidiClient = 0; + inline static MIDIPortRef MidiOutPort = 0; + MIDIEndpointRef MidiDestination; + int DeviceID; // Threading std::thread PlayerThread; - volatile bool ExitRequested; - std::condition_variable EventCV; // Still needed for pause/resume - std::mutex EventMutex; // Still needed for pause/resume - - bool isOpen; - bool Precache; + std::atomic Exit; + std::mutex Mutex; + std::condition_variable ExitCond; // Timing - int Tempo; - int InitialTempo; - int Division; - MIDITimeStamp CurrentEvTimeStamp; // This will track the host time of the current event being processed. - MIDITimeStamp NextEvTimeStamp; - double NanoSecsPerTick; // Conversion factor: Host Time Units per MIDI Tick. - MidiHeader* Events; // Linked list of MIDI headers + int64_t InitialTempo; + int64_t Tempo; + int64_t Division; + + // ZMusic MidiHeader data + MidiHeader* Events; // Linked list of MIDI headers akin to win32 MIDIHDR uint32_t Position; // Current position in the MidiHeader buffer uint32_t PositionOffset; - - // Thread functions - static void PlayerThreadProc(CoreMIDIDevice* device); - void PlayerLoop(); }; //========================================================================== @@ -137,19 +132,14 @@ // //========================================================================== -CoreMIDIDevice::CoreMIDIDevice(int deviceID, bool precache) - : deviceID(deviceID) - , midiClient(0) - , midiOutPort(0) - , midiDestination(0) - , ExitRequested(false) - , isOpen(false) - , Tempo(500000) // Default: 120 BPM (500,000 µs per quarter note) - , Division(96) // Default PPQN - , CurrentEvTimeStamp(0) - , Events(nullptr) - , Position(0) - , Precache(precache) +CoreMIDIDevice::CoreMIDIDevice(int dev_id, bool precache) + : DeviceID{dev_id} + , MidiDestination{0} + , InitialTempo{500000} // Default: 120 BPM (500,000 µs per quarter note) + , Division{100} // Default PPQN + , Events{nullptr} + , Position{0} + , Precache{precache} { } @@ -174,64 +164,48 @@ int CoreMIDIDevice::Open() { - if (isOpen) + if (MidiDestination) return 0; OSStatus status; - // Create MIDI client - status = MIDIClientCreate(CFSTR("GZDoom"), nullptr, nullptr, &midiClient); - if (status != noErr) + if (!MidiClient) { - ZMusic_Printf(ZMUSIC_MSG_ERROR, "CoreMIDI: Failed to create MIDI client (error %d)\n", (int)status); - return -1; + // Create MIDI client + status = MIDIClientCreate(CFSTR("ZMusic"), nullptr, nullptr, &MidiClient); + if (status != noErr) + { + ZMusic_Printf(ZMUSIC_MSG_ERROR, "CoreMIDI: Failed to create MIDI client (error %d)\n", (int)status); + return -1; + } } - // Create output port - status = MIDIOutputPortCreate(midiClient, CFSTR("GZDoom Output"), &midiOutPort); - if (status != noErr) + if (!MidiOutPort) { - ZMusic_Printf(ZMUSIC_MSG_ERROR, "CoreMIDI: Failed to create output port (error %d)\n", (int)status); - MIDIClientDispose(midiClient); - midiClient = 0; - return -1; + // Create output port + status = MIDIOutputPortCreate(MidiClient, CFSTR("ZMusic Program Music"), &MidiOutPort); + if (status != noErr) + { + ZMusic_Printf(ZMUSIC_MSG_ERROR, "CoreMIDI: Failed to create output port (error %d)\n", (int)status); + return -1; + } } // Get destination endpoint by device ID - ItemCount destCount = MIDIGetNumberOfDestinations(); - if (deviceID < 0 || deviceID >= (int)destCount) + ItemCount midiout_device_count = MIDIGetNumberOfDestinations(); + if (DeviceID < 0 || DeviceID >= (int)midiout_device_count) { - ZMusic_Printf(ZMUSIC_MSG_ERROR, "CoreMIDI: Invalid device ID %d (available: %d)\n", deviceID, (int)destCount); - MIDIPortDispose(midiOutPort); - MIDIClientDispose(midiClient); - midiOutPort = 0; - midiClient = 0; + ZMusic_Printf(ZMUSIC_MSG_ERROR, "CoreMIDI: Invalid device ID %d (available: %d)\n", DeviceID, (int)midiout_device_count); return -1; } - midiDestination = MIDIGetDestination(deviceID); - if (midiDestination == 0) + MidiDestination = MIDIGetDestination(DeviceID); + if (!MidiDestination) { - ZMusic_Printf(ZMUSIC_MSG_ERROR, "CoreMIDI: Failed to get destination for device %d\n", deviceID); - MIDIPortDispose(midiOutPort); - MIDIClientDispose(midiClient); - midiOutPort = 0; - midiClient = 0; + ZMusic_Printf(ZMUSIC_MSG_ERROR, "CoreMIDI: Failed to get destination for device %d\n", DeviceID); return -1; } - // Get device name for logging - CFStringRef deviceName = nullptr; - MIDIObjectGetStringProperty(midiDestination, kMIDIPropertyName, &deviceName); - if (deviceName != nullptr) - { - char nameBuf[256]; - CFStringGetCString(deviceName, nameBuf, sizeof(nameBuf), kCFStringEncodingUTF8); - ZMusic_Printf(ZMUSIC_MSG_DEBUG, "CoreMIDI: Opened device %d: %s\n", deviceID, nameBuf); - CFRelease(deviceName); - } - - isOpen = true; return 0; } @@ -243,27 +217,13 @@ void CoreMIDIDevice::Close() { - if (!isOpen) + if (!MidiDestination) + { return; - + } // Stop player thread Stop(); - - // Dispose CoreMIDI objects - if (midiOutPort != 0) - { - MIDIPortDispose(midiOutPort); - midiOutPort = 0; - } - - if (midiClient != 0) - { - MIDIClientDispose(midiClient); - midiClient = 0; - } - - midiDestination = 0; - isOpen = false; + MidiDestination = 0; } //========================================================================== @@ -274,7 +234,7 @@ bool CoreMIDIDevice::IsOpen() const { - return isOpen; + return MidiDestination; } //========================================================================== @@ -286,10 +246,10 @@ int CoreMIDIDevice::GetTechnology() const { // Query if device is offline/virtual - if (midiDestination != 0) + if (MidiDestination != 0) { SInt32 offline = 0; - MIDIObjectGetIntegerProperty(midiDestination, kMIDIPropertyOffline, &offline); + MIDIObjectGetIntegerProperty(MidiDestination, kMIDIPropertyOffline, &offline); return offline ? MIDIDEV_SWSYNTH : MIDIDEV_MIDIPORT; } return MIDIDEV_MIDIPORT; @@ -297,16 +257,15 @@ //========================================================================== // -// CoreMIDIDevice :: CalcTickRate +// CoreMIDIDevice :: FakeVolume +// +// CoreMIDI doesn't support volume control directly // //========================================================================== -void CoreMIDIDevice::CalcTickRate() +bool CoreMIDIDevice::FakeVolume() { - // Tempo is in microseconds per quarter note. Division is PPQN. - // (Tempo / PPQN) what the midi tick time is in microseconds. - // CoreAudio and CoreMidi work in nano seconds so multiply by 1000. - NanoSecsPerTick = Tempo / Division * 1000; + return true; // No true volume control support, so fake volume } //========================================================================== @@ -333,50 +292,93 @@ int CoreMIDIDevice::SetTimeDiv(int timediv) { - Division = timediv > 0 ? timediv : 96; + Division = timediv; return 0; } //========================================================================== // -// CoreMIDIDevice :: StreamOut +// CoreMIDIDevice :: PrecacheInstruments // -// Queue MIDI data for asynchronous playback +// This is meant to mirror WinMIDIDevice::PrecacheInstruments // //========================================================================== -int CoreMIDIDevice::StreamOut(MidiHeader* data) +void CoreMIDIDevice::PrecacheInstruments(const uint16_t* instruments, int count) { - if (!isOpen) { return -1; }; + // Setting snd_midiprecache to false disables this precaching, since it + // does involve sleeping for more than a miniscule amount of time. + if (!Precache) + { + return; + } + uint8_t bank[16] = {}; + uint8_t i, chan; - data->lpNext = nullptr; - if (Events == nullptr) + for (i = 0, chan = 0; i < count; ++i) { - Events = data; - Position = 0; + uint8_t instr = instruments[i] & 127; + uint8_t banknum = (instruments[i] >> 7) & 127; + uint8_t percussion = instruments[i] >> 14; + + if (percussion) + { + if (bank[9] != banknum) + { + SendImmediateShortMsg(MIDI_CTRLCHANGE | 9, 0, banknum); + bank[9] = banknum; + } + SendImmediateShortMsg(MIDI_NOTEON | 9, instr, 1); + } + else + { // Melodic + if (bank[chan] != banknum) + { + SendImmediateShortMsg(MIDI_CTRLCHANGE | 9, 0, banknum); + bank[chan] = banknum; + } + SendImmediateShortMsg(MIDI_PRGMCHANGE | chan, instruments[i]); + SendImmediateShortMsg(MIDI_NOTEON | chan, 60, 1); + if (++chan == 9) + { // Skip the percussion channel + chan = 10; + } + } + // Once we've got an instrument playing on each melodic channel, sleep to give + // the driver time to load the instruments. Also do this for the final batch + // of instruments. + if (chan == 16 || i == count - 1) + { + std::this_thread::sleep_for(std::chrono::milliseconds(250)); + for (chan = 15; chan-- != 0; ) + { + // Turn all notes off + SendImmediateShortMsg(MIDI_CTRLCHANGE | chan, 123, 0); + } + // And now chan is back at 0, ready to start the cycle over. + } } - else + // Make sure all channels are set back to bank 0. + for (i = 0; i < 16; ++i) { - MidiHeader** p; - for (p = &Events; *p != nullptr; p = &(*p)->lpNext) + if (bank[i] != 0) { + SendImmediateShortMsg(MIDI_CTRLCHANGE | 9, 0, 0); } - *p = data; } - return 0; } //========================================================================== // -// CoreMIDIDevice :: StreamOutSync +// CoreMIDIDevice :: InitPlayback // -// Queue MIDI data for synchronous playback +// Initialize playback state // //========================================================================== -int CoreMIDIDevice::StreamOutSync(MidiHeader* data) +void CoreMIDIDevice::InitPlayback() { - return StreamOut(data); + Exit.store(false, std::memory_order_relaxed); } //========================================================================== @@ -389,14 +391,12 @@ int CoreMIDIDevice::Resume() { - if (!isOpen) { return -1; }; - - if (!PlayerThread.joinable()) + if (!MidiDestination || PlayerThread.joinable()) { - ExitRequested = false; - PlayerThread = std::thread(PlayerThreadProc, this); + return -1; } - + Exit.store(false, std::memory_order_relaxed); + PlayerThread = std::thread(&CoreMIDIDevice::PlayerLoop, this); return 0; } @@ -410,154 +410,77 @@ void CoreMIDIDevice::Stop() { - if (!isOpen) { return; } - + Exit.store(true, std::memory_order_relaxed); + ExitCond.notify_all(); if (PlayerThread.joinable()) { - ExitRequested = true; - EventCV.notify_all(); PlayerThread.join(); } + MIDIFlushOutput(MidiDestination); // Drop pending events. - // Send All Notes Off and Reset All Controllers + // Reset all channels to prevent hanging notes for (int channel = 0; channel < 16; ++channel) { - uint8_t msg1[3] = { (uint8_t)(0xB0 | channel), 123, 0 }; - SendMIDIData(msg1, 3, 0); // All Notes Off - uint8_t msg2[3] = { (uint8_t)(0xB0 | channel), 121, 0 }; - SendMIDIData(msg2, 3, 0); // Reset All Controllers + SendImmediateShortMsg(MIDI_CTRLCHANGE | channel, 123, 0); // All Notes Off + SendImmediateShortMsg(MIDI_CTRLCHANGE | channel, 121, 0); // Reset All Controllers } - - // Clear event queue - Events = nullptr; } //========================================================================== // // CoreMIDIDevice :: Pause // -// Pause/resume playback +// We cannot pause so just always return false // //========================================================================== bool CoreMIDIDevice::Pause(bool paused) { - return false; // We don support pausing + return false; } //========================================================================== // -// CoreMIDIDevice :: FakeVolume +// CoreMIDIDevice :: StreamOut // -// CoreMIDI doesn't support volume control directly +// Gets new midi buffers // //========================================================================== -bool CoreMIDIDevice::FakeVolume() +int CoreMIDIDevice::StreamOut(MidiHeader* header) { - return true; // No true volume control support, so fake volume + header->lpNext = nullptr; + if (Events == nullptr) + { + Events = header; + Position = 0; + } + else + { + MidiHeader** p; + for (p = &Events; *p != nullptr; p = &(*p)->lpNext) + { } + *p = header; + } + return 0; } //========================================================================== // -// CoreMIDIDevice :: InitPlayback -// -// Initialize playback state +// CoreMIDIDevice :: StreamOutSync // //========================================================================== -void CoreMIDIDevice::InitPlayback() +int CoreMIDIDevice::StreamOutSync(MidiHeader* header) { - CurrentEvTimeStamp = AudioConvertHostTimeToNanos(AudioGetCurrentHostTime()); // Initialize with current host time - Position = 0; - Events = nullptr; - Tempo = InitialTempo; - CalcTickRate(); + return StreamOut(header); } //========================================================================== // -// CoreMIDIDevice :: PrecacheInstruments +// CoreMIDIDevice :: PullEvent // -// This is meant to mirror WinMIDIDevice::PrecacheInstruments -// -//========================================================================== -void CoreMIDIDevice::PrecacheInstruments(const uint16_t* instruments, int count) -{ - // Setting snd_midiprecache to false disables this precaching, since it - // does involve sleeping for more than a miniscule amount of time. - if (!Precache) - { - return; - } - uint8_t bank[16] = {0}; - uint8_t i, chan; - - for (i = 0, chan = 0; i < count; ++i) - { - uint8_t instr = instruments[i] & 127; - uint8_t banknum = (instruments[i] >> 7) & 127; - uint8_t percussion = instruments[i] >> 14; - - if (percussion) - { - if (bank[9] != banknum) - { - ShortMsgBuffer = { MIDI_CTRLCHANGE | 9, 0, banknum }; - SendMIDIData(ShortMsgBuffer.data(), 3, 0); - bank[9] = banknum; - } - ShortMsgBuffer = { MIDI_NOTEON | 9, instr, 1 }; - SendMIDIData(ShortMsgBuffer.data(), 3, 0); - } - else - { // Melodic - if (bank[chan] != banknum) - { - ShortMsgBuffer = { MIDI_CTRLCHANGE | 9, 0, banknum }; - SendMIDIData(ShortMsgBuffer.data(), 3, 0); - bank[chan] = banknum; - } - ShortMsgBuffer = { (uint8_t)(MIDI_PRGMCHANGE | chan), (uint8_t)instruments[i] }; - SendMIDIData(ShortMsgBuffer.data(), 2, 0); - ShortMsgBuffer = { (uint8_t)(MIDI_NOTEON | chan), 60, 1 }; - SendMIDIData(ShortMsgBuffer.data(), 3, 0); - if (++chan == 9) - { // Skip the percussion channel - chan = 10; - } - } - // Once we've got an instrument playing on each melodic channel, sleep to give - // the driver time to load the instruments. Also do this for the final batch - // of instruments. - if (chan == 16 || i == count - 1) - { - std::this_thread::sleep_for(std::chrono::milliseconds(250)); - for (chan = 15; chan-- != 0; ) - { - // Turn all notes off - ShortMsgBuffer = { (uint8_t)(MIDI_CTRLCHANGE | chan), 123, 0 }; - SendMIDIData(ShortMsgBuffer.data(), 3, 0); - } - // And now chan is back at 0, ready to start the cycle over. - } - } - // Make sure all channels are set back to bank 0. - for (i = 0; i < 16; ++i) - { - if (bank[i] != 0) - { - ShortMsgBuffer = { MIDI_CTRLCHANGE | 9, 0, 0 }; - SendMIDIData(ShortMsgBuffer.data(), 3, 0); - } - } -} - -//========================================================================== -// -// CoreMIDIDevice :: PlayTick -// -// Plays all events up to the current tick. +// Pulls next event from MidiHeader buffer // //========================================================================== @@ -574,12 +497,12 @@ } if (Position >= Events->dwBytesRecorded) - { // All events in the "Events" buffer were used, point to next buffer + { // All events in the buffer were used, point to next buffer Events = Events->lpNext; Position = 0; if (Callback) - { // This ensures that we always have 2 unused buffers after 1 is used up. - // omit this nested "if" block if you want to use up the 2 buffers before requesting new buffers + { // This ensures that we always have the maximum number of unused buffers (most likely 2) after 1 is used up. + // omit this nested "if" block if you want to use up all buffers before requesting new buffers Callback(CallbackData); } } @@ -589,219 +512,266 @@ return false; } - // Read the delta time (first 4 bytes of the event) - uint32_t* event_ptr = (uint32_t*)(Events->lpData + Position); - uint32_t tick_delta = event_ptr[0]; // Assuming delta time is the first uint32_t - - // Advance CurrentEventHostTime based on delta ticks. - // This timestamp will be used for the current event, accurate to the 0.5 millisecond. - NextEvTimeStamp = CurrentEvTimeStamp + tick_delta * NanoSecsPerTick; + const uint32_t* event = (uint32_t*)(Events->lpData + Position); + PulledEvent.tick_delta = event[0]; // First 4 bytes of event - uint32_t midi_event_type_param = event_ptr[2]; // This is the actual MIDI event or meta-event info - - if (midi_event_type_param < 0x80000000) // Short message (midi_event_type_param is the combined status/data bytes) + // Get event size to advance Position + if (event[2] < 0x80000000) // Short message (event[2] is the combined status/data bytes) { PositionOffset = 12; // 4 bytes delta time, 4 bytes reserved, 4 bytes MIDI message (up to 3 bytes + padding) } - else // Long message or meta-event (midi_event_type_param holds type and parameter length) + else // Long message or meta-event (event[2] holds type and parameter length) { - PositionOffset = 12 + ((MEVENT_EVENTPARM(midi_event_type_param) + 3) & ~3); + PositionOffset = 12 + ((MEVENT_EVENTPARM(event[2]) + 3) & ~3); } - switch (MEVENT_EVENTTYPE(midi_event_type_param)) + // Pulling event out of buffer + switch (MEVENT_EVENTTYPE(event[2])) { case MEVENT_TEMPO: // Tempo change event, update our internal calculation for future events - PrepareTempo(MEVENT_EVENTPARM(midi_event_type_param)); + PrepareTempo(MEVENT_EVENTPARM(event[2])); break; case MEVENT_LONGMSG: - { // Long MIDI message (SysEx, etc.), data starts after event_ptr[3] - int long_msg_len = MEVENT_EVENTPARM(midi_event_type_param); - uint8_t* long_msg_data = (uint8_t*)&event_ptr[3]; - // Ensure valid sysex message - if (long_msg_len > 2 && long_msg_data[0] == 0xF0 && long_msg_data[long_msg_len - 1] == 0xF7) - { - PrepareMidiMsg(long_msg_data, long_msg_len); + { // Long MIDI message (SysEx, etc.), data starts after event[3] + uint32_t long_msg_len = MEVENT_EVENTPARM(event[2]); + const uint8_t* long_msg_data = (uint8_t*)&event[3]; + // Ensure valid sysex message + if (long_msg_len > 2 && long_msg_data[0] == 0xF0 && long_msg_data[long_msg_len - 1] == 0xF7) + { // Strip sysex start (0xF0) and end (0xF7) bytes + PrepareLongMsg(long_msg_data + 1, long_msg_len - 2); + } + else + { + PulledEvent.type = EVENT_NOP; + } break; } - } - case 0: // Short MIDI message (note on/off, control change, etc.) - { - // midi_event_type_param contains the 1, 2, or 3 byte MIDI message - ShortMsgBuffer = { (uint8_t)(midi_event_type_param & 0xff), // Status - (uint8_t)((midi_event_type_param >> 8) & 0xff), // Data 1 - (uint8_t)((midi_event_type_param >> 16) & 0xff) }; // Data 2 - - int msgLen = 0; - if (ShortMsgBuffer[0] >= 0xF0) // System messages - { - if (ShortMsgBuffer[0] == 0xF0 || ShortMsgBuffer[0] == 0xF7) msgLen = 1; // Start/Stop/Continue/Timing/Active Sensing/Reset (1 byte) - else if (ShortMsgBuffer[0] == 0xF1 || ShortMsgBuffer[0] == 0xF3) msgLen = 2; // Time Code Quarter Frame, Song Select (2 bytes) - else if (ShortMsgBuffer[0] == 0xF2) msgLen = 3; // Song Position Pointer (3 bytes) - else msgLen = 1; // Default to 1 for other unknown system messages - } - else if (ShortMsgBuffer[0] >= 0xC0 && ShortMsgBuffer[0] < 0xE0) // Program Change or Channel Aftertouch (2 bytes) - { - msgLen = 2; - } - else // Note On/Off, Poly Aftertouch, Control Change, Pitch Bend (3 bytes) - { - msgLen = 3; + case MEVENT_SHORTMSG: + { // MIDI 1.0 voice msg type (0x2) | Group (0x0) | the remaining 24 bits are raw MIDI 1.0 bytes + PrepareShortMsg(0x20 << 24 | CFSwapInt32(event[2]) >> 8); + break; } - PrepareMidiMsg(ShortMsgBuffer.data(), msgLen); - break; - } default: - CurrentEvent.EventType = NoEvent; + PulledEvent.type = EVENT_NOP; } - // Indicate that an event was processed and potentially more are available in the current tick. - // The PlayerLoop will decide when to call PlayTick again. + // Indicate that an event was processed. return true; } -//========================================================================== -// -// CoreMIDIDevice :: PlayerThreadProc -// -// Static thread entry point -// -//========================================================================== - -void CoreMIDIDevice::PlayerThreadProc(CoreMIDIDevice* device) -{ - device->PlayerLoop(); -} - //========================================================================== // // CoreMIDIDevice :: PlayerLoop // -// Main player thread loop - processes MIDI events from queue +// Main player thread loop // //========================================================================== void CoreMIDIDevice::PlayerLoop() { - std::unique_lock lock(EventMutex); - std::chrono::nanoseconds buffer_time_limit(40000000); + std::unique_lock lock{Mutex}; + using namespace std::literals::chrono_literals; + constexpr std::chrono::nanoseconds buffer_step = 40ms; + + Tempo = InitialTempo; + // Initialize midi clock with current host time, CoreAudio and CoreMidi work in nano seconds. + std::chrono::nanoseconds buffer_timestamp{AudioConvertHostTimeToNanos(AudioGetCurrentHostTime())}; + // Process all available events and schedule them with CoreMIDI - while (!ExitRequested) //while (Events != nullptr && !Paused && !ExitRequested) + while (!Exit.load(std::memory_order_relaxed)) { if (!PullEvent()) { - EventCV.wait_for(lock, buffer_time_limit); + ExitCond.wait_for(lock, buffer_step); continue; } - std::chrono::nanoseconds next_ev_time_delta(NextEvTimeStamp - AudioConvertHostTimeToNanos(AudioGetCurrentHostTime())); - std::chrono::nanoseconds schedule_time = next_ev_time_delta - buffer_time_limit; - if (schedule_time >= buffer_time_limit) + // Multiply by 1000 to convert to nanoseconds, multiplication is done before any division to be accurate to the nanosecond. + std::chrono::nanoseconds pulled_ev_time_delta{1000 * PulledEvent.tick_delta * Tempo / Division}; + auto pulled_ev_timestamp = buffer_timestamp + pulled_ev_time_delta; + std::chrono::nanoseconds current_timestamp{AudioConvertHostTimeToNanos(AudioGetCurrentHostTime())}; + auto time_until_pulled_ev = pulled_ev_timestamp - current_timestamp; + auto schedule_time = time_until_pulled_ev - buffer_step; + if (schedule_time >= buffer_step) + { // Try to keep buffered events under 2x buffer_step + if (ExitCond.wait_for(lock, schedule_time) == std::cv_status::no_timeout) + { + continue; + } + } + if (time_until_pulled_ev < std::chrono::nanoseconds::zero()) + { // Can be triggered on playback start. + // Message shouldn't be shown by default like other midi backends here. + ZMusic_Printf(ZMUSIC_MSG_DEBUG, "CoreMidi backend underrun by %d nanoseconds!\n", time_until_pulled_ev.count()); + } + + // Handle PulledEvent + switch (PulledEvent.type) { - // Try to keep events under 2x time limit - EventCV.wait_for(lock, schedule_time); - continue; + case EVENT_TEMPO: + Tempo = PulledEvent.data.tempo; + break; + case EVENT_MESSAGE: + HandleEvent(PulledEvent.data.msg_buffer, PulledEvent.word_count, AudioConvertNanosToHostTime(pulled_ev_timestamp.count())); + break; + case EVENT_NOP: + default: + ; } - CurrentEvTimeStamp = NextEvTimeStamp; + buffer_timestamp = pulled_ev_timestamp; Position += PositionOffset; - HandleCurrentEvent(); } - std::this_thread::sleep_for(buffer_time_limit * 2); } +//========================================================================== +// +// CoreMIDIDevice :: PrepareTempo and PrepareShortMsg +// +// Prepare pulled event to be handled later +// +//========================================================================== + void CoreMIDIDevice::PrepareTempo(const uint32_t tempo) { - CurrentEvent.EventType = TempoEv; - CurrentEvent.EventMsg.Tempo = tempo; + PulledEvent.type = EVENT_TEMPO; + PulledEvent.data.tempo = tempo; } -void CoreMIDIDevice::PrepareMidiMsg(uint8_t* msg, uint32_t length) +void CoreMIDIDevice::PrepareShortMsg(uint32_t msg) { - CurrentEvent.EventType = MidiMsgEv; - CurrentEvent.EventMsg.MidiMsg = msg; - CurrentEvent.length = length; -} - -void CoreMIDIDevice::HandleCurrentEvent() -{ - switch (CurrentEvent.EventType) - { - case TempoEv: - Tempo = CurrentEvent.EventMsg.Tempo; - CalcTickRate(); - break; - case MidiMsgEv: - SendMIDIData(CurrentEvent.EventMsg.MidiMsg, CurrentEvent.length, AudioConvertNanosToHostTime(CurrentEvTimeStamp)); - break; - default: - } + PulledEvent.type = EVENT_MESSAGE; + PulledEvent.data.msg_buffer[0] = msg; + PulledEvent.word_count = 1; } //========================================================================== // -// CoreMIDIDevice :: SendMIDIData +// CoreMIDIDevice :: PrepareLongMsg // -// Send raw MIDI data to the CoreMIDI output port +// Prepares MIDI sysex messages by packing them into UMPs (Universal Midi Packets) +// sysex UMPs must always come in pairs of 32-bit structures called UMPs +// the first 2 bytes of the first UMP contain metadata to identify the UMP +// the last 2 bytes and the entirety of the second UMP (4 bytes) 2 + 4 = 6 bytes +// are for the raw sysex message each UMP packed in the native endianness of the machine // //========================================================================== -void CoreMIDIDevice::SendMIDIData(const uint8_t* data, size_t length, MIDITimeStamp timestamp) +void CoreMIDIDevice::PrepareLongMsg(const uint8_t* long_msg, uint32_t length) { - if (!isOpen || midiOutPort == 0 || midiDestination == 0) + uint ump_count = (length / 6) * 2; + if (length % 6) + { + ump_count += 2; + } + if (ump_count > 64) + { // Max capacity of 1 MIDIEventPacket is 64 32-bit words, thus max size sysex is 64 / 2 x 6 = 192 bytes + // for larger messages a bigger buffer could be allocated and type punned to MIDIEventList for up to 65,536 bytes + // and for even larger sysex messages we could split it over successive invocations of MIDIEventListAdd. + // Nonetheless since sysex messages here typically do not get past 11 bytes, I think neither solution is worth implementing. + ZMusic_Printf(ZMUSIC_MSG_ERROR, "CoreMidi: message needs %u UMPs, exceeding MIDIEventPacket capacity: 64\n", ump_count); + PulledEvent.type = EVENT_NOP; return; - - // The required size for the MIDIPacketList is the size of the list itself - // plus the size of the packet header and the actual MIDI data. - size_t requiredSize = offsetof(MIDIPacketList, packet) + offsetof(MIDIPacket, data) + length; - - // Use a stack buffer for small messages to avoid heap allocation (fast path). - Byte small_buffer[256]; - - // Choose the buffer to use. - Byte* buffer; - std::vector large_buffer; // Will be used only if needed. - - if (requiredSize > sizeof(small_buffer)) + } + auto remaining_bytes = length; + auto index_ptr = long_msg; + size_t msg_buffer_index = 0; + while (remaining_bytes > 0) { - try + // Determine how many bytes go into this UMP pair (up to 6 bytes) + // Note: keep it uint32_t because it will be bitshifted and or'ed to construct the UMP + uint32_t chunk_size = std::min(6, remaining_bytes); + + // Determine UMP Status (third 4 bits) + uint32_t status; + if (length <= 6) { - large_buffer.resize(requiredSize); - buffer = large_buffer.data(); + status = 0x0; // Complete System Exclusive Message fits in one UMP pair } - catch (const std::bad_alloc&) + else if (index_ptr == long_msg) { - ZMusic_Printf(ZMUSIC_MSG_ERROR, "CoreMIDI: Failed to allocate memory for large MIDI message.\n"); - return; + status = 0x1; // Start UMP } - } - else - { - buffer = small_buffer; + else if (remaining_bytes > 6) + { + status = 0x2; // Continue UMP + } + else + { + status = 0x3; // End UMP + } + + // Initialize buffer with 0s + uint32_t buffer[6] = {}; + // Copy bytes from index_ptr and expand them to 32 bits for bitshifting and or'ing later + // when chunk_size is < 6 the extra bytes are left as 0s, this padding is part of the spec; the second UMP is needed even if it's all 0s + for (uint32_t i = 0; i < chunk_size; ++i) + { + buffer[i] = index_ptr[i]; + } + + // (sysex msg type (0x3) | Group (0x0)) = 8 bits | Status = 4 bits | # of bytes = 4 bits | first 2 bytes from buffer + const uint32_t ump_1 = 0x30 << 24 | status << 20 | chunk_size << 16 | buffer[0] << 8 | buffer[1]; + + // last 4 bytes from buffer + const uint32_t ump_2 = buffer[2] << 24 | buffer[3] << 16 | buffer[4] << 8 | buffer[5]; + + PulledEvent.data.msg_buffer[msg_buffer_index] = ump_1; + ++msg_buffer_index; + PulledEvent.data.msg_buffer[msg_buffer_index] = ump_2; + ++msg_buffer_index; + + remaining_bytes -= chunk_size; + index_ptr += chunk_size; } - MIDIPacketList* packetList = (MIDIPacketList*)buffer; - MIDIPacket* packet = MIDIPacketListInit(packetList); + PulledEvent.type = EVENT_MESSAGE; + PulledEvent.word_count = ump_count; +} - // Add the MIDI data to the packet list. The size passed to MIDIPacketListAdd - // is the total size of the buffer we have available. - packet = MIDIPacketListAdd(packetList, (buffer == small_buffer) ? sizeof(small_buffer) : requiredSize, packet, - timestamp, length, data); +//========================================================================== +// +// CoreMIDIDevice :: HandleEvent +// +// Schedules MIDI events to be sent to the output port +// +//========================================================================== + +void CoreMIDIDevice::HandleEvent(const uint32_t* data, ByteCount word_count, MIDITimeStamp timestamp) +{ + MIDIEventList event_list = {}; + MIDIEventPacket* event_packet = MIDIEventListInit(&event_list, kMIDIProtocol_1_0); - if (packet != nullptr) + // Add the event to the event list. + event_packet = MIDIEventListAdd(&event_list, sizeof(MIDIEventList::packet), event_packet, timestamp, word_count, data); + + if (event_packet != nullptr) { - OSStatus status = MIDISend(midiOutPort, midiDestination, packetList); + OSStatus status = MIDISendEventList(MidiOutPort, MidiDestination, &event_list); if (status != noErr) { - ZMusic_Printf(ZMUSIC_MSG_ERROR, "CoreMIDI: MIDISend failed (error %d)\n", (int)status); + ZMusic_Printf(ZMUSIC_MSG_ERROR, "CoreMIDI: \"MIDISendEventList\" failed with error: %d\n", (int)status); } } else { - // This should ideally not happen with dynamic allocation, but we keep the check for safety. - ZMusic_Printf(ZMUSIC_MSG_ERROR, "CoreMIDI: MIDIPacketListAdd failed unexpectedly.\n"); + // Should never happen as long as (word_count <= 64) + ZMusic_Printf(ZMUSIC_MSG_ERROR, "CoreMIDI: \"MIDIEventListAdd\" failed unexpectedly.\n"); } } +//========================================================================== +// +// CoreMIDIDevice :: SendImmediateShortMsg +// +// For use with PrecacheInstruments and Stop messages. +// +//========================================================================== +void CoreMIDIDevice::SendImmediateShortMsg(uint32_t command, uint32_t data1, uint32_t data2) +{ + const uint32_t msg = 0x20 << 24 | command << 16 | data1 << 8 | data2; + HandleEvent(&msg, 1, 0); +} //========================================================================== // diff --git a/source/mididevices/music_fluidsynth_mididevice.cpp b/source/mididevices/music_fluidsynth_mididevice.cpp index eb50148a..c896afd7 100644 --- a/source/mididevices/music_fluidsynth_mididevice.cpp +++ b/source/mididevices/music_fluidsynth_mididevice.cpp @@ -3,7 +3,7 @@ ** Provides access to FluidSynth as a generic MIDI device. ** **--------------------------------------------------------------------------- -** Copyright 2010 Randy Heit +** Copyright 2010 Marisa Heit ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without @@ -241,10 +241,7 @@ void FluidSynthMIDIDevice::HandleEvent(int status, int parm1, int parm2) void FluidSynthMIDIDevice::HandleLongEvent(const uint8_t *data, int len) { constexpr int excludedByteCount = 2; // 0xF0 (first byte) and 0xF7 (last byte) are not given to FluidSynth. - if (len > excludedByteCount && data[0] == 0xF0 && data[len - 1] == 0xF7) - { - fluid_synth_sysex(FluidSynth, (const char *)data + 1, len - excludedByteCount, NULL, NULL, NULL, 0); - } + fluid_synth_sysex(FluidSynth, (const char *)data + 1, len - excludedByteCount, NULL, NULL, NULL, 0); } //========================================================================== diff --git a/source/mididevices/music_opl_mididevice.cpp b/source/mididevices/music_opl_mididevice.cpp index c463579f..55cf3cea 100644 --- a/source/mididevices/music_opl_mididevice.cpp +++ b/source/mididevices/music_opl_mididevice.cpp @@ -3,7 +3,7 @@ ** Provides an emulated OPL implementation of a MIDI output device. ** **--------------------------------------------------------------------------- -** Copyright 2008 Randy Heit +** Copyright 2008 Marisa Heit ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without diff --git a/source/mididevices/music_opnmidi_mididevice.cpp b/source/mididevices/music_opnmidi_mididevice.cpp index f4793cd2..df6a5765 100644 --- a/source/mididevices/music_opnmidi_mididevice.cpp +++ b/source/mididevices/music_opnmidi_mididevice.cpp @@ -3,7 +3,7 @@ ** Provides access to libOPNMIDI as a generic MIDI device. ** **--------------------------------------------------------------------------- -** Copyright 2008 Randy Heit +** Copyright 2008 Marisa Heit ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without diff --git a/source/mididevices/music_softsynth_mididevice.cpp b/source/mididevices/music_softsynth_mididevice.cpp index 8d8d69dd..4653203e 100644 --- a/source/mididevices/music_softsynth_mididevice.cpp +++ b/source/mididevices/music_softsynth_mididevice.cpp @@ -3,7 +3,7 @@ ** Common base class for software synthesis MIDI devices. ** **--------------------------------------------------------------------------- -** Copyright 2008-2010 Randy Heit +** Copyright 2008-2010 Marisa Heit ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without @@ -285,44 +285,56 @@ int SoftSynthMIDIDevice::PlayTick() while (delay == 0 && Events != NULL) { uint32_t *event = (uint32_t *)(Events->lpData + Position); - if (MEVENT_EVENTTYPE(event[2]) == MEVENT_TEMPO) + switch (MEVENT_EVENTTYPE(event[2])) { + case MEVENT_TEMPO: SetTempo(MEVENT_EVENTPARM(event[2])); - } - else if (MEVENT_EVENTTYPE(event[2]) == MEVENT_LONGMSG) - { - HandleLongEvent((uint8_t *)&event[3], MEVENT_EVENTPARM(event[2])); - } - else if (MEVENT_EVENTTYPE(event[2]) == 0) - { // Short MIDI event - int status = event[2] & 0xff; - int parm1 = (event[2] >> 8) & 0x7f; - int parm2 = (event[2] >> 16) & 0x7f; - HandleEvent(status, parm1, parm2); + break; + case MEVENT_LONGMSG: + { + int long_msg_len = MEVENT_EVENTPARM(event[2]); + uint8_t* long_msg_data = (uint8_t*)&event[3]; + // Ensure valid sysex message + if (long_msg_len > 2 && long_msg_data[0] == 0xF0 && long_msg_data[long_msg_len - 1] == 0xF7) + { + HandleLongEvent(long_msg_data, long_msg_len); + } + break; + } + case MEVENT_SHORTMSG: + { + int status = event[2] & 0xff; + int parm1 = (event[2] >> 8) & 0x7f; + int parm2 = (event[2] >> 16) & 0x7f; + HandleEvent(status, parm1, parm2); #if 0 - if (synth_watch) - { - static const char *const commands[8] = + if (synth_watch) { - "Note off", - "Note on", - "Poly press", - "Ctrl change", - "Prgm change", - "Chan press", - "Pitch bend", - "SysEx" - }; - char buffer[128]; - mysnprintf(buffer, countof(buffer), "C%02d: %11s %3d %3d\n", (status & 15) + 1, commands[(status >> 4) & 7], parm1, parm2); + static const char *const commands[8] = + { + "Note off", + "Note on", + "Poly press", + "Ctrl change", + "Prgm change", + "Chan press", + "Pitch bend", + "SysEx" + }; + char buffer[128]; + mysnprintf(buffer, countof(buffer), "C%02d: %11s %3d %3d\n", (status & 15) + 1, commands[(status >> 4) & 7], parm1, parm2); #ifdef _WIN32 - I_DebugPrint(buffer); + I_DebugPrint(buffer); #else - fputs(buffer, stderr); + fputs(buffer, stderr); #endif - } + } #endif + break; + } + default: + ; } // Advance to next event. diff --git a/source/mididevices/music_timidity_mididevice.cpp b/source/mididevices/music_timidity_mididevice.cpp index 49c1e081..93cc87cd 100644 --- a/source/mididevices/music_timidity_mididevice.cpp +++ b/source/mididevices/music_timidity_mididevice.cpp @@ -3,7 +3,7 @@ ** Provides access to TiMidity as a generic MIDI device. ** **--------------------------------------------------------------------------- -** Copyright 2008 Randy Heit +** Copyright 2008 Marisa Heit ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without diff --git a/source/mididevices/music_timiditypp_mididevice.cpp b/source/mididevices/music_timiditypp_mididevice.cpp index ee87ddbd..109a7b51 100644 --- a/source/mididevices/music_timiditypp_mididevice.cpp +++ b/source/mididevices/music_timiditypp_mididevice.cpp @@ -3,7 +3,7 @@ ** Provides access to timidity.exe ** **--------------------------------------------------------------------------- -** Copyright 2001-2017 Randy Heit +** Copyright 2001-2017 Marisa Heit ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without diff --git a/source/mididevices/music_wavewriter_mididevice.cpp b/source/mididevices/music_wavewriter_mididevice.cpp index f9358268..467090fe 100644 --- a/source/mididevices/music_wavewriter_mididevice.cpp +++ b/source/mididevices/music_wavewriter_mididevice.cpp @@ -3,7 +3,7 @@ ** Dumps a MIDI to a wave file by using one of the other software synths. ** **--------------------------------------------------------------------------- -** Copyright 2008 Randy Heit +** Copyright 2008 Marisa Heit ** Copyright 2018 Christoph Oelckers ** All rights reserved. ** diff --git a/source/mididevices/music_wildmidi_mididevice.cpp b/source/mididevices/music_wildmidi_mididevice.cpp index 0c78022f..02832061 100644 --- a/source/mididevices/music_wildmidi_mididevice.cpp +++ b/source/mididevices/music_wildmidi_mididevice.cpp @@ -3,7 +3,7 @@ ** Provides access to WildMidi as a generic MIDI device. ** **--------------------------------------------------------------------------- -** Copyright 2015 Randy Heit +** Copyright 2015 Marisa Heit ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without diff --git a/source/mididevices/music_win_mididevice.cpp b/source/mididevices/music_win_mididevice.cpp index e4d1d512..ac881a51 100644 --- a/source/mididevices/music_win_mididevice.cpp +++ b/source/mididevices/music_win_mididevice.cpp @@ -3,7 +3,7 @@ ** Provides a WinMM implementation of a MIDI output device. ** **--------------------------------------------------------------------------- -** Copyright 2008 Randy Heit +** Copyright 2008 Marisa Heit ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without diff --git a/source/midisources/midisource.cpp b/source/midisources/midisource.cpp index 08abaa84..19d3059e 100644 --- a/source/midisources/midisource.cpp +++ b/source/midisources/midisource.cpp @@ -3,7 +3,7 @@ ** Implements base class for the different MIDI formats ** **--------------------------------------------------------------------------- - ** Copyright 2008-2016 Randy Heit + ** Copyright 2008-2016 Marisa Heit ** Copyright 2017-2018 Christoph Oelckers ** All rights reserved. ** @@ -142,7 +142,7 @@ std::vector MIDISource::PrecacheData() uint32_t *event_end = MakeEvents(Events[0], &Events[0][MAX_MIDI_EVENTS*3], 1000000*600); for (uint32_t *event = Events[0]; event < event_end; ) { - if (MEVENT_EVENTTYPE(event[2]) == 0) + if (MEVENT_EVENTTYPE(event[2]) == MEVENT_SHORTMSG) { int command = (event[2] & 0x70); int channel = (event[2] & 0x0f); @@ -359,7 +359,7 @@ void MIDISource::CreateSMF(std::vector &file, int looplimit) } running_status = 255; } - else if (MEVENT_EVENTTYPE(event[2]) == 0) + else if (MEVENT_EVENTTYPE(event[2]) == MEVENT_SHORTMSG) { WriteVarLen(file, delay); delay = 0; diff --git a/source/midisources/midisource_hmi.cpp b/source/midisources/midisource_hmi.cpp index 791954ed..c3a89fc2 100644 --- a/source/midisources/midisource_hmi.cpp +++ b/source/midisources/midisource_hmi.cpp @@ -3,7 +3,7 @@ ** Code to let ZDoom play HMI MIDI music through the MIDI streaming API. ** **--------------------------------------------------------------------------- -** Copyright 2010 Randy Heit +** Copyright 2010 Marisa Heit ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without diff --git a/source/midisources/midisource_mus.cpp b/source/midisources/midisource_mus.cpp index bac5de9c..e1b9e71f 100644 --- a/source/midisources/midisource_mus.cpp +++ b/source/midisources/midisource_mus.cpp @@ -3,7 +3,7 @@ ** Code to let ZDoom play MUS music through the MIDI streaming API. ** **--------------------------------------------------------------------------- -** Copyright 1998-2008 Randy Heit +** Copyright 1998-2008 Marisa Heit ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without diff --git a/source/midisources/midisource_smf.cpp b/source/midisources/midisource_smf.cpp index e9b4615a..ba296cd5 100644 --- a/source/midisources/midisource_smf.cpp +++ b/source/midisources/midisource_smf.cpp @@ -3,7 +3,7 @@ ** Code to let ZDoom play SMF MIDI music through the MIDI streaming API. ** **--------------------------------------------------------------------------- -** Copyright 1998-2008 Randy Heit +** Copyright 1998-2008 Marisa Heit ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without diff --git a/source/midisources/midisource_xmi.cpp b/source/midisources/midisource_xmi.cpp index bbd282c4..ca34346e 100644 --- a/source/midisources/midisource_xmi.cpp +++ b/source/midisources/midisource_xmi.cpp @@ -3,7 +3,7 @@ ** Code to let ZDoom play XMIDI music through the MIDI streaming API. ** **--------------------------------------------------------------------------- -** Copyright 2010 Randy Heit +** Copyright 2010 Marisa Heit ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without diff --git a/source/musicformats/music_cd.cpp b/source/musicformats/music_cd.cpp index 84fd60bc..8cf3f08e 100644 --- a/source/musicformats/music_cd.cpp +++ b/source/musicformats/music_cd.cpp @@ -2,7 +2,7 @@ ** music_cd.cpp ** **--------------------------------------------------------------------------- -** Copyright 1999-2003 Randy Heit +** Copyright 1999-2003 Marisa Heit ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without diff --git a/source/musicformats/music_midi.cpp b/source/musicformats/music_midi.cpp index e6ce0ecb..03dc4d1a 100644 --- a/source/musicformats/music_midi.cpp +++ b/source/musicformats/music_midi.cpp @@ -3,7 +3,7 @@ ** Implements base class for MIDI and MUS streaming. ** **--------------------------------------------------------------------------- -** Copyright 2008 Randy Heit +** Copyright 2008 Marisa Heit ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without diff --git a/source/musicformats/music_stream.cpp b/source/musicformats/music_stream.cpp index 86299f04..1c7f6078 100644 --- a/source/musicformats/music_stream.cpp +++ b/source/musicformats/music_stream.cpp @@ -3,7 +3,7 @@ ** Plays a streaming song from a StreamSource ** **--------------------------------------------------------------------------- -** Copyright 2008 Randy Heit +** Copyright 2008 Marisa Heit ** Copyright 2019 Christoph Oelckers ** All rights reserved. ** diff --git a/source/musicformats/win32/helperthread.cpp b/source/musicformats/win32/helperthread.cpp index ef6d0d3e..d759504a 100644 --- a/source/musicformats/win32/helperthread.cpp +++ b/source/musicformats/win32/helperthread.cpp @@ -6,7 +6,7 @@ ** helper thread. (Only used by the CD Audio player) ** **--------------------------------------------------------------------------- -** Copyright 1998-2006 Randy Heit +** Copyright 1998-2006 Marisa Heit ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without diff --git a/source/musicformats/win32/helperthread.h b/source/musicformats/win32/helperthread.h index 45ca9a70..1874c474 100644 --- a/source/musicformats/win32/helperthread.h +++ b/source/musicformats/win32/helperthread.h @@ -2,7 +2,7 @@ ** helperthread.h ** **--------------------------------------------------------------------------- -** Copyright 1998-2006 Randy Heit +** Copyright 1998-2006 Marisa Heit ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without diff --git a/source/musicformats/win32/i_cd.cpp b/source/musicformats/win32/i_cd.cpp index 5eb8b41e..5db6ef2b 100644 --- a/source/musicformats/win32/i_cd.cpp +++ b/source/musicformats/win32/i_cd.cpp @@ -3,7 +3,7 @@ ** Functions for controlling CD playback ** **--------------------------------------------------------------------------- -** Copyright 1998-2006 Randy Heit +** Copyright 1998-2006 Marisa Heit ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without diff --git a/source/musicformats/win32/i_cd.h b/source/musicformats/win32/i_cd.h index 6640d104..3e3226c9 100644 --- a/source/musicformats/win32/i_cd.h +++ b/source/musicformats/win32/i_cd.h @@ -3,7 +3,7 @@ ** Defines the CD interface ** **--------------------------------------------------------------------------- -** Copyright 1998-2006 Randy Heit +** Copyright 1998-2006 Marisa Heit ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without diff --git a/source/streamsources/music_dumb.cpp b/source/streamsources/music_dumb.cpp index dced2d8e..3050d5c3 100644 --- a/source/streamsources/music_dumb.cpp +++ b/source/streamsources/music_dumb.cpp @@ -4,7 +4,7 @@ ** Based on the Foobar2000 component foo_dumb, version 0.9.8.4. ** **--------------------------------------------------------------------------- -** Copyright 2008 Randy Heit +** Copyright 2008 Marisa Heit ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without diff --git a/source/streamsources/music_gme.cpp b/source/streamsources/music_gme.cpp index 5f59a884..ef2a60c1 100644 --- a/source/streamsources/music_gme.cpp +++ b/source/streamsources/music_gme.cpp @@ -3,7 +3,7 @@ ** General game music player, using Game Music Emu for decoding. ** **--------------------------------------------------------------------------- -** Copyright 2009 Randy Heit +** Copyright 2009 Marisa Heit ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without diff --git a/source/streamsources/music_opl.cpp b/source/streamsources/music_opl.cpp index 629f0aa1..6548c28c 100644 --- a/source/streamsources/music_opl.cpp +++ b/source/streamsources/music_opl.cpp @@ -3,7 +3,7 @@ ** Plays raw OPL formats ** **--------------------------------------------------------------------------- -** Copyright 1998-2008 Randy Heit +** Copyright 1998-2008 Marisa Heit ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without diff --git a/source/zmusic/configuration.cpp b/source/zmusic/configuration.cpp index 2d13e979..62574696 100644 --- a/source/zmusic/configuration.cpp +++ b/source/zmusic/configuration.cpp @@ -251,25 +251,25 @@ struct MidiDeviceList } } #elif __APPLE__ - CFStringRef cfName; + CFStringRef name; char string_buffer[128]; - auto destCount = MIDIGetNumberOfDestinations(); - for (int i = 0; i < destCount; i++) + auto midiout_device_count = MIDIGetNumberOfDestinations(); + for (int i = 0; i < midiout_device_count; i++) { auto endpoint = MIDIGetDestination(i); if (!endpoint) { continue; } - cfName = nullptr; - MIDIObjectGetStringProperty(endpoint, kMIDIPropertyName, &cfName); - if (!CFStringGetCString(cfName, string_buffer, sizeof(string_buffer), kCFStringEncodingUTF8)) + name = nullptr; + MIDIObjectGetStringProperty(endpoint, kMIDIPropertyName, &name); + if (!CFStringGetCString(name, string_buffer, sizeof(string_buffer), kCFStringEncodingUTF8)) { strcpy(string_buffer, "CoreMidi device"); } - if (cfName != nullptr) + if (name != nullptr) { - CFRelease(cfName); + CFRelease(name); } devices.push_back({ strdup(string_buffer), i, MIDIDEV_MAPPER }); } diff --git a/source/zmusic/critsec.cpp b/source/zmusic/critsec.cpp index 24160eff..91bc1bcd 100644 --- a/source/zmusic/critsec.cpp +++ b/source/zmusic/critsec.cpp @@ -2,7 +2,7 @@ ** ** **--------------------------------------------------------------------------- -** Copyright 2005-2016 Randy Heit +** Copyright 2005-2016 Marisa Heit ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without diff --git a/source/zmusic/file_zip.cpp b/source/zmusic/file_zip.cpp index 91f33adb..72cb201e 100644 --- a/source/zmusic/file_zip.cpp +++ b/source/zmusic/file_zip.cpp @@ -2,7 +2,7 @@ ** file_zip.cpp ** **--------------------------------------------------------------------------- -** Copyright 1998-2009 Randy Heit +** Copyright 1998-2009 Marisa Heit ** Copyright 2005-2023 Christoph Oelckers ** All rights reserved. ** diff --git a/source/zmusic/mididefs.h b/source/zmusic/mididefs.h index 29cda0d2..4f605daa 100644 --- a/source/zmusic/mididefs.h +++ b/source/zmusic/mididefs.h @@ -12,6 +12,7 @@ inline constexpr uint32_t MEVENT_EVENTPARM(uint32_t x) { return ((x) & 0xffffff) enum EMidiEvent : uint8_t { + MEVENT_SHORTMSG = 0, MEVENT_TEMPO = 1, MEVENT_NOP = 2, MEVENT_LONGMSG = 128, diff --git a/source/zmusic/mus2midi.h b/source/zmusic/mus2midi.h index d083fe8c..f110778a 100644 --- a/source/zmusic/mus2midi.h +++ b/source/zmusic/mus2midi.h @@ -2,7 +2,7 @@ ** mus2midi.h ** **--------------------------------------------------------------------------- -** Copyright 1998-2006 Randy Heit +** Copyright 1998-2006 Marisa Heit ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without diff --git a/source/zmusic/zmusic.cpp b/source/zmusic/zmusic.cpp index 3f41abcc..9d803edb 100644 --- a/source/zmusic/zmusic.cpp +++ b/source/zmusic/zmusic.cpp @@ -3,7 +3,7 @@ ** Plays music ** **--------------------------------------------------------------------------- - ** Copyright 1998-2016 Randy Heit + ** Copyright 1998-2016 Marisa Heit ** Copyright 2005-2019 Christoph Oelckers ** All rights reserved. ** diff --git a/thirdparty/oplsynth/musicblock.cpp b/thirdparty/oplsynth/musicblock.cpp index 4d0aeb24..4d17db5e 100644 --- a/thirdparty/oplsynth/musicblock.cpp +++ b/thirdparty/oplsynth/musicblock.cpp @@ -1,6 +1,6 @@ //----------------------------------------------------------------------------- // -// Copyright 2002-2016 Randy Heit +// Copyright 2002-2016 Marisa Heit // Copyright 2005-2014 Simon Howard // Copyright 2017 Christoph Oelckers // diff --git a/thirdparty/oplsynth/opl_mus_player.cpp b/thirdparty/oplsynth/opl_mus_player.cpp index 9f88538a..d250e6f3 100644 --- a/thirdparty/oplsynth/opl_mus_player.cpp +++ b/thirdparty/oplsynth/opl_mus_player.cpp @@ -2,7 +2,7 @@ ** opl_mus_player.cpp ** **--------------------------------------------------------------------------- -** Copyright 1999-2016 Randy Heit +** Copyright 1999-2016 Marisa Heit ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without diff --git a/thirdparty/oplsynth/oplio.cpp b/thirdparty/oplsynth/oplio.cpp index e97d726f..21d9ccee 100644 --- a/thirdparty/oplsynth/oplio.cpp +++ b/thirdparty/oplsynth/oplio.cpp @@ -1,6 +1,6 @@ //----------------------------------------------------------------------------- // -// Copyright 2002-2016 Randy Heit +// Copyright 2002-2016 Marisa Heit // Copyright 2005-2014 Simon Howard // Copyright 2017 Christoph Oelckers //