diff --git a/licenses/bsd.txt b/licenses/bsd.txt index aa48716..f78cd27 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 1f850e8..e4d6064 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 142c903..729c945 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. ** @@ -35,6 +35,7 @@ #if defined __linux__ && defined HAVE_SYSTEM_MIDI #include +#include #include #include #include @@ -57,30 +58,29 @@ 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, uint tick); + snd_seq_event_t AlsaSeqEvent; snd_midi_event_t* Coder = nullptr; - uint32_t Position = 0; - uint32_t PositionOffset; - uint NextEventTickDelta; + std::array ShortMsgBuffer; + // Alsa sequencer handles + AlsaSequencer &sequencer; const static int IntendedPortId = 0; bool Connected = false; int PortId = -1; @@ -89,16 +89,23 @@ 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 + int InitialTempo = 500000; + int Tempo; + int Division = 100; // PPQN + + // ZMusic MidiHeader data + MidiHeader* Events = nullptr; + uint32_t Position = 0; + uint32_t PositionOffset; + uint32_t PulledEventTickDelta; }; AlsaMIDIDevice::AlsaMIDIDevice(int dev_id, bool precache) : sequencer(AlsaSequencer::Get()) @@ -125,7 +132,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 +145,7 @@ int AlsaMIDIDevice::Open() snd_midi_event_new(3, &Coder); // 3 Bytes for short messages. snd_midi_event_init(Coder); - snd_seq_ev_clear(&Event); + snd_seq_ev_clear(&AlsaSeqEvent); int err = 0; err = snd_seq_create_port(sequencer.handle, pinfo); @@ -192,6 +199,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 +212,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. @@ -215,7 +227,6 @@ void AlsaMIDIDevice::PrecacheInstruments(const uint16_t *instruments, int count) } uint8_t bank[16] = {0}; uint8_t i, chan; - std::array message; for (i = 0, chan = 0; i < count; ++i) { @@ -227,30 +238,30 @@ 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); + ShortMsgBuffer = { MIDI_CTRLCHANGE | 9, 0, banknum }; + snd_midi_event_encode(Coder, ShortMsgBuffer.data(), 3, &AlsaSeqEvent); + HandleEvent(AlsaSeqEvent, 0); bank[9] = banknum; } - message = { MIDI_NOTEON | 9, instr, 1 }; - snd_midi_event_encode(Coder, message.data(), 3, &Event); - HandleEvent(Event, 0); + ShortMsgBuffer = { MIDI_NOTEON | 9, instr, 1 }; + snd_midi_event_encode(Coder, ShortMsgBuffer.data(), 3, &AlsaSeqEvent); + HandleEvent(AlsaSeqEvent, 0); } else { // Melodic if (bank[chan] != banknum) { - message = { MIDI_CTRLCHANGE | 9, 0, banknum }; - snd_midi_event_encode(Coder, message.data(), 3, &Event); - HandleEvent(Event, 0); + ShortMsgBuffer = { MIDI_CTRLCHANGE | 9, 0, banknum }; + snd_midi_event_encode(Coder, ShortMsgBuffer.data(), 3, &AlsaSeqEvent); + HandleEvent(AlsaSeqEvent, 0); 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); + ShortMsgBuffer = { (uint8_t)(MIDI_PRGMCHANGE | chan), (uint8_t)instruments[i] }; + snd_midi_event_encode(Coder, ShortMsgBuffer.data(), 2, &AlsaSeqEvent); + HandleEvent(AlsaSeqEvent, 0); + ShortMsgBuffer = { (uint8_t)(MIDI_NOTEON | chan), 60, 1 }; + snd_midi_event_encode(Coder, ShortMsgBuffer.data(), 3, &AlsaSeqEvent); + HandleEvent(AlsaSeqEvent, 0); if (++chan == 9) { // Skip the percussion channel chan = 10; @@ -265,9 +276,9 @@ 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); + ShortMsgBuffer = { (uint8_t)(MIDI_CTRLCHANGE | chan), 123, 0 }; + snd_midi_event_encode(Coder, ShortMsgBuffer.data(), 3, &AlsaSeqEvent); + HandleEvent(AlsaSeqEvent, 0); } // And now chan is back at 0, ready to start the cycle over. } @@ -277,15 +288,56 @@ 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); + ShortMsgBuffer = { MIDI_CTRLCHANGE | 9, 0, 0 }; + snd_midi_event_encode(Coder, ShortMsgBuffer.data(), 3, &AlsaSeqEvent); + HandleEvent(AlsaSeqEvent, 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 + + // Reset all channels to prevent hanging notes + for (int channel = 0; channel < 16; ++channel) + { + snd_seq_ev_set_controller(&AlsaSeqEvent, channel, MIDI_CTL_ALL_NOTES_OFF, 0); + HandleEvent(AlsaSeqEvent, 0); + snd_seq_ev_set_controller(&AlsaSeqEvent, channel, MIDI_CTL_RESET_CONTROLLERS, 0); + HandleEvent(AlsaSeqEvent, 0); + } snd_seq_sync_output_queue(sequencer.handle); } + +bool AlsaMIDIDevice::Pause(bool paused) +{ + return false; // Pausing is not supported +} + bool AlsaMIDIDevice::PullEvent() { if (!Events && Callback) @@ -299,12 +351,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 +366,16 @@ bool AlsaMIDIDevice::PullEvent() return false; } - uint32_t *event = (uint32_t *)(Events->lpData + Position); - NextEventTickDelta = event[0]; + uint32_t* event = (uint32_t*)(Events->lpData + Position); + PulledEventTickDelta = 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,38 +383,57 @@ 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(&AlsaSeqEvent, QueueId, MEVENT_EVENTPARM(event[2])); break; - } - case MEVENT_LONGMSG: // SysEx messages... + case MEVENT_LONGMSG: // SysEx message... { - uint8_t* data = (uint8_t *)&event[3]; - int len = MEVENT_EVENTPARM(event[2]); - if (len > 2 && data[0] == 0xF0 && data[len - 1] == 0xF7) + 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) { - snd_seq_ev_set_sysex(&Event, len, (void*)data); + snd_seq_ev_set_sysex(&AlsaSeqEvent, long_msg_len, (void*)long_msg_data); 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); + ShortMsgBuffer = { (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, ShortMsgBuffer.data(), 3, &AlsaSeqEvent); 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); + AlsaSeqEvent.type = SND_SEQ_EVENT_NONE; } return true; } +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); +} + /* * Pumps events from the input to the output in a worker thread. * It tries to keep the amount of events (time-wise) in the ALSA sequencer queue to be between 40 and 80ms by sleeping where necessary. @@ -374,10 +445,10 @@ void AlsaMIDIDevice::PlayerLoop() const std::chrono::microseconds buffer_step(40000); // 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); @@ -386,10 +457,10 @@ void AlsaMIDIDevice::PlayerLoop() Tempo = InitialTempo; int buffered_ticks = 0; - snd_seq_queue_status_t *status; + snd_seq_queue_status_t* status; snd_seq_queue_status_malloc(&status); - while (!Exit) + 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 +470,43 @@ 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; + int pulled_event_tick = buffered_ticks + PulledEventTickDelta; 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); + int ticks_until_pulled_ev = pulled_event_tick - queue_tick; + auto time_until_pulled_ev = std::chrono::microseconds(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_NOTIFY, "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(AlsaSeqEvent, pulled_event_tick); + buffered_ticks = pulled_event_tick; Position += PositionOffset; } - snd_seq_ev_clear(&Event); // Event is cleared to be used in reset messages in Stop() + snd_seq_ev_clear(&AlsaSeqEvent); // AlsaSeqEvent 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 +// Requires QueueId to be started first for non-zero tick positioned events. void AlsaMIDIDevice::HandleEvent(snd_seq_event_t &event, uint 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) @@ -447,79 +525,7 @@ 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) -{ - 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; -} - -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 78e8475..8105a26 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 dcf856a..711a9fb 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 5a78ed4..c1a5369 100644 --- a/source/mididevices/music_coremidi_mididevice.mm +++ b/source/mididevices/music_coremidi_mididevice.mm @@ -38,14 +38,15 @@ #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" //========================================================================== // @@ -71,64 +72,60 @@ 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 + void PrepareTempo(uint32_t tempo); + void PrepareMidiMsg(uint8_t* msg, uint32_t length); + void SendMIDIData(const uint8_t* data, size_t length, MIDITimeStamp timestamp); + std::array ShortMsgBuffer; + + // PulledEvent structure to hold the next event to be processed enum EventType { TempoEv, MidiMsgEv, NoEvent }; union EventMsg { - uint32_t Tempo; - uint8_t* MidiMsg; + uint32_t tempo; + uint8_t* data; }; - struct CurrentEvent + struct PulledEvent { 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); + PulledEvent PulledEvent; + + // CoreMIDI handles + MIDIClientRef midiClient; + MIDIPortRef midiOutPort; + 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 Tempo; 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 + + // 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(); + uint32_t PulledEventTickDelta; }; //========================================================================== @@ -142,11 +139,8 @@ , 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) + , InitialTempo(500000) // Default: 120 BPM (500,000 µs per quarter note) + , Division(100) // Default PPQN , Events(nullptr) , Position(0) , Precache(precache) @@ -174,13 +168,13 @@ int CoreMIDIDevice::Open() { - if (isOpen) + if (midiDestination) return 0; OSStatus status; // Create MIDI client - status = MIDIClientCreate(CFSTR("GZDoom"), nullptr, nullptr, &midiClient); + 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); @@ -188,7 +182,7 @@ } // Create output port - status = MIDIOutputPortCreate(midiClient, CFSTR("GZDoom Output"), &midiOutPort); + 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); @@ -210,7 +204,7 @@ } midiDestination = MIDIGetDestination(deviceID); - if (midiDestination == 0) + if (!midiDestination) { ZMusic_Printf(ZMUSIC_MSG_ERROR, "CoreMIDI: Failed to get destination for device %d\n", deviceID); MIDIPortDispose(midiOutPort); @@ -220,18 +214,6 @@ 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,7 +225,7 @@ void CoreMIDIDevice::Close() { - if (!isOpen) + if (!midiDestination) return; // Stop player thread @@ -263,7 +245,6 @@ } midiDestination = 0; - isOpen = false; } //========================================================================== @@ -274,7 +255,7 @@ bool CoreMIDIDevice::IsOpen() const { - return isOpen; + return midiDestination; } //========================================================================== @@ -297,16 +278,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,148 +313,10 @@ int CoreMIDIDevice::SetTimeDiv(int timediv) { - Division = timediv > 0 ? timediv : 96; - return 0; -} - -//========================================================================== -// -// CoreMIDIDevice :: StreamOut -// -// Queue MIDI data for asynchronous playback -// -//========================================================================== - -int CoreMIDIDevice::StreamOut(MidiHeader* data) -{ - if (!isOpen) { return -1; }; - - data->lpNext = nullptr; - if (Events == nullptr) - { - Events = data; - Position = 0; - } - else - { - MidiHeader** p; - for (p = &Events; *p != nullptr; p = &(*p)->lpNext) - { - } - *p = data; - } - return 0; -} - -//========================================================================== -// -// CoreMIDIDevice :: StreamOutSync -// -// Queue MIDI data for synchronous playback -// -//========================================================================== - -int CoreMIDIDevice::StreamOutSync(MidiHeader* data) -{ - return StreamOut(data); -} - -//========================================================================== -// -// CoreMIDIDevice :: Resume -// -// Start or resume playback -// -//========================================================================== - -int CoreMIDIDevice::Resume() -{ - if (!isOpen) { return -1; }; - - if (!PlayerThread.joinable()) - { - ExitRequested = false; - PlayerThread = std::thread(PlayerThreadProc, this); - } - + Division = timediv; return 0; } -//========================================================================== -// -// CoreMIDIDevice :: Stop -// -// Stop playback -// -//========================================================================== - -void CoreMIDIDevice::Stop() -{ - if (!isOpen) { return; } - - if (PlayerThread.joinable()) - { - ExitRequested = true; - EventCV.notify_all(); - PlayerThread.join(); - } - - // Send All Notes Off and Reset All Controllers - 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 - } - - // Clear event queue - Events = nullptr; -} - -//========================================================================== -// -// CoreMIDIDevice :: Pause -// -// Pause/resume playback -// -//========================================================================== - -bool CoreMIDIDevice::Pause(bool paused) -{ - return false; // We don support pausing -} - -//========================================================================== -// -// CoreMIDIDevice :: FakeVolume -// -// CoreMIDI doesn't support volume control directly -// -//========================================================================== - -bool CoreMIDIDevice::FakeVolume() -{ - return true; // No true volume control support, so fake volume -} - -//========================================================================== -// -// CoreMIDIDevice :: InitPlayback -// -// Initialize playback state -// -//========================================================================== - -void CoreMIDIDevice::InitPlayback() -{ - CurrentEvTimeStamp = AudioConvertHostTimeToNanos(AudioGetCurrentHostTime()); // Initialize with current host time - Position = 0; - Events = nullptr; - Tempo = InitialTempo; - CalcTickRate(); -} - //========================================================================== // // CoreMIDIDevice :: PrecacheInstruments @@ -482,6 +324,7 @@ // 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 @@ -555,9 +398,82 @@ //========================================================================== // -// CoreMIDIDevice :: PlayTick +// CoreMIDIDevice :: InitPlayback +// +// Initialize playback state +// +//========================================================================== + +void CoreMIDIDevice::InitPlayback() +{ + Exit.store(false, std::memory_order_relaxed); +} + +//========================================================================== +// +// CoreMIDIDevice :: Resume +// +// Start or resume playback +// +//========================================================================== + +int CoreMIDIDevice::Resume() +{ + if (!midiDestination || PlayerThread.joinable()) + { + return -1; + } + Exit.store(false, std::memory_order_relaxed); + PlayerThread = std::thread(&CoreMIDIDevice::PlayerLoop, this); + return 0; +} + +//========================================================================== +// +// CoreMIDIDevice :: Stop +// +// Stop playback +// +//========================================================================== + +void CoreMIDIDevice::Stop() +{ + Exit.store(true, std::memory_order_relaxed); + ExitCond.notify_all(); + if (PlayerThread.joinable()) + { + PlayerThread.join(); + } + MIDIFlushOutput(midiDestination); // Drop pending events. + + // Send All Notes Off and Reset All Controllers + for (int channel = 0; channel < 16; ++channel) + { + ShortMsgBuffer = { (uint8_t)(0xB0 | channel), 123, 0 }; + SendMIDIData(ShortMsgBuffer.data(), 3, 0); // All Notes Off + ShortMsgBuffer = { (uint8_t)(0xB0 | channel), 121, 0 }; + SendMIDIData(ShortMsgBuffer.data(), 3, 0); // Reset All Controllers + } +} + +//========================================================================== +// +// CoreMIDIDevice :: Pause // -// Plays all events up to the current tick. +// We cannot pause so just always return false +// +//========================================================================== + +bool CoreMIDIDevice::Pause(bool paused) +{ + return false; +} + +//========================================================================== +// +// CoreMIDIDevice :: PullEvent +// +// Pulls next event from MidiHeader buffer // //========================================================================== @@ -574,12 +490,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,35 +505,30 @@ 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; + uint32_t* event = (uint32_t*)(Events->lpData + Position); + PulledEventTickDelta = 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]; + { // Long MIDI message (SysEx, etc.), data starts after event[3] + 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) { @@ -627,10 +538,10 @@ } 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 + // event[2] contains the 1, 2, or 3 byte MIDI message + ShortMsgBuffer = { (uint8_t)(event[2] & 0xff), // Status + (uint8_t)((event[2] >> 8) & 0xff), // Data 1 + (uint8_t)((event[2] >> 16) & 0xff) }; // Data 2 int msgLen = 0; if (ShortMsgBuffer[0] >= 0xF0) // System messages @@ -652,88 +563,130 @@ break; } default: - CurrentEvent.EventType = NoEvent; + PulledEvent.EventType = NoEvent; } - // 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 +// CoreMIDIDevice :: StreamOut +// +// Gets new midi buffers +// +//========================================================================== + +int CoreMIDIDevice::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; +} + +//========================================================================== // -// Static thread entry point +// CoreMIDIDevice :: StreamOutSync // //========================================================================== -void CoreMIDIDevice::PlayerThreadProc(CoreMIDIDevice* device) +int CoreMIDIDevice::StreamOutSync(MidiHeader* header) { - device->PlayerLoop(); + return StreamOut(header); } //========================================================================== // // 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); + std::chrono::nanoseconds buffer_step(40000000); + + Tempo = InitialTempo; + // Initialize midi clock with current host time + MIDITimeStamp 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) + // CoreAudio and CoreMidi work in nano seconds so multiply by 1000. + MIDITimeStamp pulled_ev_timestamp = buffer_timestamp + PulledEventTickDelta * Tempo / Division * 1000; + + auto time_until_pulled_ev = std::chrono::nanoseconds(pulled_ev_timestamp - AudioConvertHostTimeToNanos(AudioGetCurrentHostTime())); + 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_NOTIFY, "CoreMidi backend underrun by %d nanoseconds!\n", time_until_pulled_ev.count()); + } + + // Handle PulledEvent + switch (PulledEvent.EventType) { - // Try to keep events under 2x time limit - EventCV.wait_for(lock, schedule_time); - continue; + case TempoEv: + Tempo = PulledEvent.EventMsg.tempo; + break; + case MidiMsgEv: + SendMIDIData(PulledEvent.EventMsg.data, PulledEvent.length, AudioConvertNanosToHostTime(pulled_ev_timestamp)); + break; + case NoEvent: + default: + ; } - CurrentEvTimeStamp = NextEvTimeStamp; + buffer_timestamp = pulled_ev_timestamp; Position += PositionOffset; - HandleCurrentEvent(); } - std::this_thread::sleep_for(buffer_time_limit * 2); } +//========================================================================== +// +// CoreMIDIDevice :: PrepareTempo and PrepareMidiMsg +// +// Prepare pulled event to be handled later +// +//========================================================================== + void CoreMIDIDevice::PrepareTempo(const uint32_t tempo) { - CurrentEvent.EventType = TempoEv; - CurrentEvent.EventMsg.Tempo = tempo; + PulledEvent.EventType = TempoEv; + PulledEvent.EventMsg.tempo = tempo; } void CoreMIDIDevice::PrepareMidiMsg(uint8_t* msg, uint32_t length) { - 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.EventType = MidiMsgEv; + PulledEvent.EventMsg.data = msg; + PulledEvent.length = length; } //========================================================================== @@ -746,15 +699,14 @@ void CoreMIDIDevice::SendMIDIData(const uint8_t* data, size_t length, MIDITimeStamp timestamp) { - if (!isOpen || midiOutPort == 0 || midiDestination == 0) - 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]; + // Short messages typically need 15-17 bytes (14 offsets + message length) + // and long messages can need up to 25 bytes in my testing, so 64 bytes should be sufficient for most cases. + Byte small_buffer[64]; // Choose the buffer to use. Byte* buffer; @@ -762,6 +714,7 @@ if (requiredSize > sizeof(small_buffer)) { + ZMusic_Printf(ZMUSIC_MSG_NOTIFY, "CoreMIDI: Required MIDIPacketList size \"%zu\" exceeds small_buffer size \"%zu\"\n", requiredSize, sizeof(small_buffer)); try { large_buffer.resize(requiredSize); @@ -801,8 +754,6 @@ } } - - //========================================================================== // // CreateCoreMIDIDevice diff --git a/source/mididevices/music_fluidsynth_mididevice.cpp b/source/mididevices/music_fluidsynth_mididevice.cpp index eb50148..c867ebc 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 diff --git a/source/mididevices/music_opl_mididevice.cpp b/source/mididevices/music_opl_mididevice.cpp index c463579..55cf3ce 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 f4793cd..df6a576 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 8d8d69d..6849997 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 diff --git a/source/mididevices/music_timidity_mididevice.cpp b/source/mididevices/music_timidity_mididevice.cpp index 49c1e08..93cc87c 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 ee87ddb..109a7b5 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 f935826..467090f 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 0c78022..0283206 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 e4d1d51..ac881a5 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 08abaa8..4ec7f27 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. ** diff --git a/source/midisources/midisource_hmi.cpp b/source/midisources/midisource_hmi.cpp index 791954e..c3a89fc 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 bac5de9..e1b9e71 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 e9b4615..ba296cd 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 bbd282c..ca34346 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 84fd60b..8cf3f08 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 e6ce0ec..03dc4d1 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 86299f0..1c7f607 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 ef6d0d3..d759504 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 45ca9a7..1874c47 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 5eb8b41..5db6ef2 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 6640d10..3e3226c 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 dced2d8..3050d5c 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 5f59a88..ef2a60c 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 629f0aa..6548c28 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 de125a7..2d13e97 100644 --- a/source/zmusic/configuration.cpp +++ b/source/zmusic/configuration.cpp @@ -251,16 +251,27 @@ struct MidiDeviceList } } #elif __APPLE__ - for (int i = 0; i < MIDIGetNumberOfDestinations(); i++) + CFStringRef cfName; + char string_buffer[128]; + auto destCount = MIDIGetNumberOfDestinations(); + for (int i = 0; i < destCount; i++) { - uint32_t endpoint = MIDIGetDestination(i); + auto endpoint = MIDIGetDestination(i); if (!endpoint) { continue; } - CFStringRef cfName; + cfName = nullptr; MIDIObjectGetStringProperty(endpoint, kMIDIPropertyName, &cfName); - devices.push_back({ strdup(CFStringGetCStringPtr(cfName, kCFStringEncodingUTF8)), i, MIDIDEV_MAPPER }); + if (!CFStringGetCString(cfName, string_buffer, sizeof(string_buffer), kCFStringEncodingUTF8)) + { + strcpy(string_buffer, "CoreMidi device"); + } + if (cfName != nullptr) + { + CFRelease(cfName); + } + devices.push_back({ strdup(string_buffer), i, MIDIDEV_MAPPER }); } #endif #endif diff --git a/source/zmusic/critsec.cpp b/source/zmusic/critsec.cpp index 24160ef..91bc1bc 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 91f33ad..72cb201 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/mus2midi.h b/source/zmusic/mus2midi.h index d083fe8..f110778 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 3f41abc..9d803ed 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 4d0aeb2..4d17db5 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 9f88538..d250e6f 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 e97d726..21d9cce 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 //