From aeaee6733fec4d3a3bd996a8073a83fc9caf21b7 Mon Sep 17 00:00:00 2001 From: Leah Anderson Date: Thu, 2 Apr 2026 14:51:20 -0600 Subject: [PATCH 01/15] attribution fixes --- licenses/bsd.txt | 2 +- source/mididevices/music_adlmidi_mididevice.cpp | 2 +- source/mididevices/music_alsa_mididevice.cpp | 2 +- source/mididevices/music_alsa_state.h | 2 +- source/mididevices/music_base_mididevice.cpp | 2 +- source/mididevices/music_fluidsynth_mididevice.cpp | 2 +- source/mididevices/music_opl_mididevice.cpp | 2 +- source/mididevices/music_opnmidi_mididevice.cpp | 2 +- source/mididevices/music_softsynth_mididevice.cpp | 2 +- source/mididevices/music_timidity_mididevice.cpp | 2 +- source/mididevices/music_timiditypp_mididevice.cpp | 2 +- source/mididevices/music_wavewriter_mididevice.cpp | 2 +- source/mididevices/music_wildmidi_mididevice.cpp | 2 +- source/mididevices/music_win_mididevice.cpp | 2 +- source/midisources/midisource.cpp | 2 +- source/midisources/midisource_hmi.cpp | 2 +- source/midisources/midisource_mus.cpp | 2 +- source/midisources/midisource_smf.cpp | 2 +- source/midisources/midisource_xmi.cpp | 2 +- source/musicformats/music_cd.cpp | 2 +- source/musicformats/music_midi.cpp | 2 +- source/musicformats/music_stream.cpp | 2 +- source/musicformats/win32/helperthread.cpp | 2 +- source/musicformats/win32/helperthread.h | 2 +- source/musicformats/win32/i_cd.cpp | 2 +- source/musicformats/win32/i_cd.h | 2 +- source/streamsources/music_dumb.cpp | 2 +- source/streamsources/music_gme.cpp | 2 +- source/streamsources/music_opl.cpp | 2 +- source/zmusic/critsec.cpp | 2 +- source/zmusic/file_zip.cpp | 2 +- source/zmusic/mus2midi.h | 2 +- source/zmusic/zmusic.cpp | 2 +- thirdparty/oplsynth/musicblock.cpp | 2 +- thirdparty/oplsynth/opl_mus_player.cpp | 2 +- thirdparty/oplsynth/oplio.cpp | 2 +- 36 files changed, 36 insertions(+), 36 deletions(-) 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..f7a7953e 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. ** 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_fluidsynth_mididevice.cpp b/source/mididevices/music_fluidsynth_mididevice.cpp index eb50148a..c867ebcc 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 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..68499973 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 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..4ec7f272 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 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/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/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 // From 7604b572f55af6fc4f6a19eb976eb6bba22359f9 Mon Sep 17 00:00:00 2001 From: Charles the Thobe Date: Wed, 29 Apr 2026 04:16:43 +0300 Subject: [PATCH 02/15] Add MEVENT_SHORTMSG enum member --- source/mididevices/music_alsa_mididevice.cpp | 2 +- source/mididevices/music_coremidi_mididevice.mm | 2 +- source/mididevices/music_softsynth_mididevice.cpp | 4 ++-- source/midisources/midisource.cpp | 4 ++-- source/zmusic/mididefs.h | 1 + 5 files changed, 7 insertions(+), 6 deletions(-) diff --git a/source/mididevices/music_alsa_mididevice.cpp b/source/mididevices/music_alsa_mididevice.cpp index f7a7953e..03e9e2ba 100644 --- a/source/mididevices/music_alsa_mididevice.cpp +++ b/source/mididevices/music_alsa_mididevice.cpp @@ -346,7 +346,7 @@ bool AlsaMIDIDevice::PullEvent() break; } } - case 0: // Short MIDI event + case MEVENT_SHORTMSG: { uint8_t status = event[2] & 0xFF; uint8_t param1 = (event[2] >> 8) & 0x7f; diff --git a/source/mididevices/music_coremidi_mididevice.mm b/source/mididevices/music_coremidi_mididevice.mm index 5a78ed43..3d84965b 100644 --- a/source/mididevices/music_coremidi_mididevice.mm +++ b/source/mididevices/music_coremidi_mididevice.mm @@ -625,7 +625,7 @@ break; } } - case 0: // Short MIDI message (note on/off, control change, etc.) + case MEVENT_SHORTMSG: { // midi_event_type_param contains the 1, 2, or 3 byte MIDI message ShortMsgBuffer = { (uint8_t)(midi_event_type_param & 0xff), // Status diff --git a/source/mididevices/music_softsynth_mididevice.cpp b/source/mididevices/music_softsynth_mididevice.cpp index 68499973..d8ed105f 100644 --- a/source/mididevices/music_softsynth_mididevice.cpp +++ b/source/mididevices/music_softsynth_mididevice.cpp @@ -293,8 +293,8 @@ int SoftSynthMIDIDevice::PlayTick() { HandleLongEvent((uint8_t *)&event[3], MEVENT_EVENTPARM(event[2])); } - else if (MEVENT_EVENTTYPE(event[2]) == 0) - { // Short MIDI event + else if (MEVENT_EVENTTYPE(event[2]) == MEVENT_SHORTMSG) + { int status = event[2] & 0xff; int parm1 = (event[2] >> 8) & 0x7f; int parm2 = (event[2] >> 16) & 0x7f; diff --git a/source/midisources/midisource.cpp b/source/midisources/midisource.cpp index 4ec7f272..19d3059e 100644 --- a/source/midisources/midisource.cpp +++ b/source/midisources/midisource.cpp @@ -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/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, From 904f6d5de71d3871a98c9a47b2536db45f715581 Mon Sep 17 00:00:00 2001 From: Charles the Thobe Date: Wed, 29 Apr 2026 13:43:31 +0300 Subject: [PATCH 03/15] Ensure correct sysex messages before sending to "softsynth" backends --- .../music_fluidsynth_mididevice.cpp | 5 +- .../music_softsynth_mididevice.cpp | 70 +++++++++++-------- 2 files changed, 42 insertions(+), 33 deletions(-) diff --git a/source/mididevices/music_fluidsynth_mididevice.cpp b/source/mididevices/music_fluidsynth_mididevice.cpp index c867ebcc..c896afd7 100644 --- a/source/mididevices/music_fluidsynth_mididevice.cpp +++ b/source/mididevices/music_fluidsynth_mididevice.cpp @@ -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_softsynth_mididevice.cpp b/source/mididevices/music_softsynth_mididevice.cpp index d8ed105f..4653203e 100644 --- a/source/mididevices/music_softsynth_mididevice.cpp +++ b/source/mididevices/music_softsynth_mididevice.cpp @@ -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]) == MEVENT_SHORTMSG) - { - 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. From c18d586ac7af0f842d2fc23507a7e6fec1e00f1c Mon Sep 17 00:00:00 2001 From: Charles the Thobe Date: Wed, 25 Mar 2026 15:37:07 +0200 Subject: [PATCH 04/15] AlsaMIDIDevice and CoreMIDIDevice: small refactor --- source/mididevices/music_alsa_mididevice.cpp | 349 ++++++----- .../mididevices/music_coremidi_mididevice.mm | 578 +++++++++--------- 2 files changed, 458 insertions(+), 469 deletions(-) diff --git a/source/mididevices/music_alsa_mididevice.cpp b/source/mididevices/music_alsa_mididevice.cpp index 03e9e2ba..2cf35344 100644 --- a/source/mididevices/music_alsa_mididevice.cpp +++ b/source/mididevices/music_alsa_mididevice.cpp @@ -34,7 +34,7 @@ #if defined __linux__ && defined HAVE_SYSTEM_MIDI -#include +#include #include #include #include @@ -57,30 +57,36 @@ 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 PulledEvent + { + snd_seq_event_t Event; + uint32_t TickDelta; + }; + PulledEvent PulledEvent; + + // Alsa sequencer handles + AlsaSequencer &sequencer; const static int IntendedPortId = 0; bool Connected = false; int PortId = -1; @@ -89,16 +95,22 @@ 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; }; AlsaMIDIDevice::AlsaMIDIDevice(int dev_id, bool precache) : sequencer(AlsaSequencer::Get()) @@ -125,7 +137,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 +150,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 +203,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 +216,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 +231,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 +242,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 +270,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 +280,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 +363,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 +378,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); + PulledEvent.TickDelta = 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 +395,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); + 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(&PulledEvent.Event, long_msg_len, (void*)long_msg_data); + } + else + { + PulledEvent.Event.type = SND_SEQ_EVENT_NONE; + } break; } - } case MEVENT_SHORTMSG: - { - 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; - } + { + 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; + } 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; } @@ -374,22 +439,24 @@ 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); + snd_seq_start_queue(sequencer.handle, QueueId, nullptr); snd_seq_drain_output(sequencer.handle); 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) + 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; + int pulled_event_tick = buffered_ticks + PulledEvent.TickDelta; 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_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); + 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_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) @@ -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) -{ - 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) +// For use with PrecacheInstruments and Stop messages. +void AlsaMIDIDevice::SendImmediateShortMsg(uint8_t command, uint8_t data1, uint8_t data2) { - 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_coremidi_mididevice.mm b/source/mididevices/music_coremidi_mididevice.mm index 3d84965b..7a04490f 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,62 @@ 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 + void PrepareTempo(uint32_t tempo); + void PrepareMidiMsg(uint8_t* msg, uint32_t length); + void SendMIDIData(const uint8_t* data, size_t length, MIDITimeStamp timestamp); + void SendImmediateShortMsg(uint8_t command, uint8_t data1 = 0, uint8_t data2 = 0); + int GetShortMsgLength(uint8_t* msg); + std::array ShortMsgBuffer; + + // PulledEvent structure to hold the next event to be processed + enum EventType_t { TempoEv, MidiMsgEv, NOP }; + union EventData_t { - uint32_t Tempo; - uint8_t* MidiMsg; + uint32_t tempo; + uint8_t* msg; }; - struct CurrentEvent + struct PulledEvent { - EventType EventType; - EventMsg EventMsg; + EventType_t EventType; + EventData_t EventData; uint32_t length; + uint32_t TickDelta; }; - 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(); }; //========================================================================== @@ -142,11 +141,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 +170,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 +184,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 +206,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 +216,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 +227,7 @@ void CoreMIDIDevice::Close() { - if (!isOpen) + if (!midiDestination) return; // Stop player thread @@ -263,7 +247,6 @@ } midiDestination = 0; - isOpen = false; } //========================================================================== @@ -274,7 +257,7 @@ bool CoreMIDIDevice::IsOpen() const { - return isOpen; + return midiDestination; } //========================================================================== @@ -297,16 +280,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 +315,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] = {0}; + 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 +414,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 +433,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() -{ - CurrentEvTimeStamp = AudioConvertHostTimeToNanos(AudioGetCurrentHostTime()); // Initialize with current host time - Position = 0; - Events = nullptr; - Tempo = InitialTempo; - CalcTickRate(); -} - -//========================================================================== -// -// CoreMIDIDevice :: PrecacheInstruments -// -// This is meant to mirror WinMIDIDevice::PrecacheInstruments -// -//========================================================================== -void CoreMIDIDevice::PrecacheInstruments(const uint16_t* instruments, int count) +int CoreMIDIDevice::StreamOutSync(MidiHeader* header) { - // 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); - } - } + return StreamOut(header); } //========================================================================== // -// CoreMIDIDevice :: PlayTick +// CoreMIDIDevice :: PullEvent // -// Plays all events up to the current tick. +// Pulls next event from MidiHeader buffer // //========================================================================== @@ -574,12 +520,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,151 +535,140 @@ 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 midi_event_type_param = event_ptr[2]; // This is the actual MIDI event or meta-event info + uint32_t* event = (uint32_t*)(Events->lpData + Position); + PulledEvent.TickDelta = event[0]; // First 4 bytes of event - 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] + 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) + { + PrepareMidiMsg(long_msg_data, long_msg_len); + } + else + { + PulledEvent.EventType = NOP; + } break; } - } case MEVENT_SHORTMSG: - { - // 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; + // 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 = GetShortMsgLength(ShortMsgBuffer.data()); + PrepareMidiMsg(ShortMsgBuffer.data(), msgLen); + break; } - PrepareMidiMsg(ShortMsgBuffer.data(), msgLen); - break; - } default: - CurrentEvent.EventType = NoEvent; + PulledEvent.EventType = 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); + 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 + PulledEvent.TickDelta * 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_DEBUG, "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.EventData.tempo; + break; + case MidiMsgEv: + SendMIDIData(PulledEvent.EventData.msg, PulledEvent.length, AudioConvertNanosToHostTime(pulled_ev_timestamp)); + break; + case NOP: + 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.EventData.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.EventData.msg = msg; + PulledEvent.length = length; } //========================================================================== @@ -746,15 +681,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 +696,7 @@ if (requiredSize > sizeof(small_buffer)) { + ZMusic_Printf(ZMUSIC_MSG_DEBUG, "CoreMIDI: Required MIDIPacketList size \"%zu\" exceeds small_buffer size \"%zu\"\n", requiredSize, sizeof(small_buffer)); try { large_buffer.resize(requiredSize); @@ -801,7 +736,50 @@ } } +//========================================================================== +// +// CoreMIDIDevice :: SendImmediateShortMsg +// +// For use with PrecacheInstruments and Stop messages. +// +//========================================================================== +void CoreMIDIDevice::SendImmediateShortMsg(uint8_t command, uint8_t data1, uint8_t data2) +{ + uint8_t msg[3] = { command, data1, data2 }; + int msgLen = GetShortMsgLength(msg); + SendMIDIData(msg, msgLen, 0); +} + +//========================================================================== +// +// CoreMIDIDevice :: GetShortMsgLength +// +// Determines the length of a short MIDI message +// The actual correct length is necessary for CoreMIDI to work. +// +//========================================================================== + +int CoreMIDIDevice::GetShortMsgLength(uint8_t* msg) +{ + int msgLen; + if (msg[0] >= 0xF0) // System messages + { + if (msg[0] == 0xF0 || msg[0] == 0xF7) msgLen = 1; // Start/Stop/Continue/Timing/Active Sensing/Reset (1 byte) + else if (msg[0] == 0xF1 || msg[0] == 0xF3) msgLen = 2; // Time Code Quarter Frame, Song Select (2 bytes) + else if (msg[0] == 0xF2) msgLen = 3; // Song Position Pointer (3 bytes) + else msgLen = 1; // Default to 1 for other unknown system messages + } + else if (msg[0] >= 0xC0 && msg[0] <= 0xDF) // Program Change or Channel Aftertouch (2 bytes) + { + msgLen = 2; + } + else // Note On/Off, Poly Aftertouch, Control Change, Pitch Bend (3 bytes) + { + msgLen = 3; + } + return msgLen; +} //========================================================================== // From 0d1870afd4fe7333ac8fd916f83985705bb1eecb Mon Sep 17 00:00:00 2001 From: Charles the Thobe Date: Sun, 5 Jul 2026 20:06:58 +0300 Subject: [PATCH 05/15] AlsaMIDIDevice and CoreMIDIDevice: declare "PulledEvent" from unnamed struct and change the naming convention of its members to be more fitting --- source/mididevices/music_alsa_mididevice.cpp | 27 +++++----- .../mididevices/music_coremidi_mididevice.mm | 50 +++++++++---------- 2 files changed, 37 insertions(+), 40 deletions(-) diff --git a/source/mididevices/music_alsa_mididevice.cpp b/source/mididevices/music_alsa_mididevice.cpp index 2cf35344..25708a6e 100644 --- a/source/mididevices/music_alsa_mididevice.cpp +++ b/source/mididevices/music_alsa_mididevice.cpp @@ -78,12 +78,11 @@ class AlsaMIDIDevice : public MIDIDevice snd_midi_event_t* Coder = nullptr; // PulledEvent structure to hold the next event to be processed - struct PulledEvent + struct { - snd_seq_event_t Event; - uint32_t TickDelta; - }; - PulledEvent PulledEvent; + snd_seq_event_t event; + uint32_t tick_delta; + } PulledEvent; // Alsa sequencer handles AlsaSequencer &sequencer; @@ -379,7 +378,7 @@ bool AlsaMIDIDevice::PullEvent() } uint32_t* event = (uint32_t*)(Events->lpData + Position); - PulledEvent.TickDelta = event[0]; // First 4 bytes of event + PulledEvent.tick_delta = event[0]; // First 4 bytes of event // Get event size to advance Position if (event[2] < 0x80000000) // Short message (event[2] is the combined status/data bytes) @@ -395,7 +394,7 @@ bool AlsaMIDIDevice::PullEvent() switch (MEVENT_EVENTTYPE(event[2])) { case MEVENT_TEMPO: - snd_seq_ev_set_queue_tempo(&PulledEvent.Event, QueueId, MEVENT_EVENTPARM(event[2])); + snd_seq_ev_set_queue_tempo(&PulledEvent.event, QueueId, MEVENT_EVENTPARM(event[2])); break; case MEVENT_LONGMSG: // SysEx message... { @@ -404,11 +403,11 @@ bool AlsaMIDIDevice::PullEvent() // 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); + snd_seq_ev_set_sysex(&PulledEvent.event, long_msg_len, (void*)long_msg_data); } else { - PulledEvent.Event.type = SND_SEQ_EVENT_NONE; + PulledEvent.event.type = SND_SEQ_EVENT_NONE; } break; } @@ -419,11 +418,11 @@ bool AlsaMIDIDevice::PullEvent() (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); + snd_midi_event_encode(Coder, msg, 3, &PulledEvent.event); break; } default: // We didn't really recognize the event, treat it as a NOP - PulledEvent.Event.type = SND_SEQ_EVENT_NONE; + PulledEvent.event.type = SND_SEQ_EVENT_NONE; } return true; } @@ -454,7 +453,7 @@ void AlsaMIDIDevice::PlayerLoop() snd_seq_queue_status_t* status; snd_seq_queue_status_malloc(&status); - snd_seq_ev_clear(&PulledEvent.Event); + snd_seq_ev_clear(&PulledEvent.event); while (!Exit.load(std::memory_order_relaxed)) { @@ -466,7 +465,7 @@ 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 pulled_event_tick = buffered_ticks + PulledEvent.TickDelta; + int pulled_event_tick = buffered_ticks + PulledEvent.tick_delta; snd_seq_get_queue_status(sequencer.handle, QueueId, status); int queue_tick = snd_seq_queue_status_get_tick_time(status); int ticks_until_pulled_ev = pulled_event_tick - queue_tick; @@ -486,7 +485,7 @@ void AlsaMIDIDevice::PlayerLoop() } // We found an event worthy of sending to the sequencer - HandleEvent(PulledEvent.Event, pulled_event_tick); + HandleEvent(PulledEvent.event, pulled_event_tick); buffered_ticks = pulled_event_tick; Position += PositionOffset; } diff --git a/source/mididevices/music_coremidi_mididevice.mm b/source/mididevices/music_coremidi_mididevice.mm index 7a04490f..3a2aead4 100644 --- a/source/mididevices/music_coremidi_mididevice.mm +++ b/source/mididevices/music_coremidi_mididevice.mm @@ -92,20 +92,18 @@ std::array ShortMsgBuffer; // PulledEvent structure to hold the next event to be processed - enum EventType_t { TempoEv, MidiMsgEv, NOP }; - union EventData_t + enum EventType { EVENT_TEMPO, EVENT_MESSAGE, EVENT_NOP }; + struct { - uint32_t tempo; - uint8_t* msg; - }; - struct PulledEvent - { - EventType_t EventType; - EventData_t EventData; + union + { + uint32_t tempo; + uint8_t* msg; + } data; + EventType type; uint32_t length; - uint32_t TickDelta; - }; - PulledEvent PulledEvent; + uint32_t tick_delta; + } PulledEvent; // CoreMIDI handles MIDIClientRef midiClient; @@ -536,7 +534,7 @@ } uint32_t* event = (uint32_t*)(Events->lpData + Position); - PulledEvent.TickDelta = event[0]; // First 4 bytes of event + PulledEvent.tick_delta = event[0]; // First 4 bytes of event // Get event size to advance Position if (event[2] < 0x80000000) // Short message (event[2] is the combined status/data bytes) @@ -566,7 +564,7 @@ } else { - PulledEvent.EventType = NOP; + PulledEvent.type = EVENT_NOP; } break; } @@ -582,7 +580,7 @@ break; } default: - PulledEvent.EventType = NOP; + PulledEvent.type = EVENT_NOP; } // Indicate that an event was processed. @@ -616,7 +614,7 @@ } // CoreAudio and CoreMidi work in nano seconds so multiply by 1000. - MIDITimeStamp pulled_ev_timestamp = buffer_timestamp + PulledEvent.TickDelta * Tempo / Division * 1000; + MIDITimeStamp pulled_ev_timestamp = buffer_timestamp + PulledEvent.tick_delta * 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; @@ -634,15 +632,15 @@ } // Handle PulledEvent - switch (PulledEvent.EventType) + switch (PulledEvent.type) { - case TempoEv: - Tempo = PulledEvent.EventData.tempo; + case EVENT_TEMPO: + Tempo = PulledEvent.data.tempo; break; - case MidiMsgEv: - SendMIDIData(PulledEvent.EventData.msg, PulledEvent.length, AudioConvertNanosToHostTime(pulled_ev_timestamp)); + case EVENT_MESSAGE: + SendMIDIData(PulledEvent.data.msg, PulledEvent.length, AudioConvertNanosToHostTime(pulled_ev_timestamp)); break; - case NOP: + case EVENT_NOP: default: ; } @@ -661,13 +659,13 @@ void CoreMIDIDevice::PrepareTempo(const uint32_t tempo) { - PulledEvent.EventType = TempoEv; - PulledEvent.EventData.tempo = tempo; + PulledEvent.type = EVENT_TEMPO; + PulledEvent.data.tempo = tempo; } void CoreMIDIDevice::PrepareMidiMsg(uint8_t* msg, uint32_t length) { - PulledEvent.EventType = MidiMsgEv; - PulledEvent.EventData.msg = msg; + PulledEvent.type = EVENT_MESSAGE; + PulledEvent.data.msg = msg; PulledEvent.length = length; } From be719ad3e77065b65c6346abbe4a5e5b7a449bdc Mon Sep 17 00:00:00 2001 From: Charles the Thobe Date: Sun, 5 Jul 2026 20:24:31 +0300 Subject: [PATCH 06/15] CoreMIDIDevice: unify var naming conventions, change SendMIDIData() to HandleEvent() --- .../mididevices/music_coremidi_mididevice.mm | 96 +++++++++---------- source/zmusic/configuration.cpp | 16 ++-- 2 files changed, 56 insertions(+), 56 deletions(-) diff --git a/source/mididevices/music_coremidi_mididevice.mm b/source/mididevices/music_coremidi_mididevice.mm index 3a2aead4..fb5e7918 100644 --- a/source/mididevices/music_coremidi_mididevice.mm +++ b/source/mididevices/music_coremidi_mididevice.mm @@ -59,7 +59,7 @@ class CoreMIDIDevice : public MIDIDevice { public: - CoreMIDIDevice(int deviceID, bool precache); + CoreMIDIDevice(int dev_id, bool precache); ~CoreMIDIDevice(); int Open() override; @@ -86,7 +86,7 @@ // 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); + void HandleEvent(const uint8_t* data, size_t length, MIDITimeStamp timestamp); void SendImmediateShortMsg(uint8_t command, uint8_t data1 = 0, uint8_t data2 = 0); int GetShortMsgLength(uint8_t* msg); std::array ShortMsgBuffer; @@ -106,10 +106,10 @@ } PulledEvent; // CoreMIDI handles - MIDIClientRef midiClient; - MIDIPortRef midiOutPort; - MIDIEndpointRef midiDestination; - int deviceID; + MIDIClientRef MidiClient; + MIDIPortRef MidiOutPort; + MIDIEndpointRef MidiDestination; + int DeviceID; // Threading std::thread PlayerThread; @@ -134,11 +134,11 @@ // //========================================================================== -CoreMIDIDevice::CoreMIDIDevice(int deviceID, bool precache) - : deviceID(deviceID) - , midiClient(0) - , midiOutPort(0) - , midiDestination(0) +CoreMIDIDevice::CoreMIDIDevice(int dev_id, bool precache) + : DeviceID(dev_id) + , MidiClient(0) + , MidiOutPort(0) + , MidiDestination(0) , InitialTempo(500000) // Default: 120 BPM (500,000 µs per quarter note) , Division(100) // Default PPQN , Events(nullptr) @@ -168,13 +168,13 @@ int CoreMIDIDevice::Open() { - if (midiDestination) + if (MidiDestination) return 0; OSStatus status; // Create MIDI client - status = MIDIClientCreate(CFSTR("ZMusic"), 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); @@ -182,35 +182,35 @@ } // Create output port - status = MIDIOutputPortCreate(midiClient, CFSTR("ZMusic Program Music"), &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); - MIDIClientDispose(midiClient); - midiClient = 0; + MIDIClientDispose(MidiClient); + MidiClient = 0; 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); + MIDIPortDispose(MidiOutPort); + MIDIClientDispose(MidiClient); + MidiOutPort = 0; + MidiClient = 0; return -1; } - midiDestination = MIDIGetDestination(deviceID); - if (!midiDestination) + 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); + MIDIPortDispose(MidiOutPort); + MIDIClientDispose(MidiClient); + MidiOutPort = 0; + MidiClient = 0; return -1; } @@ -225,26 +225,26 @@ void CoreMIDIDevice::Close() { - if (!midiDestination) + if (!MidiDestination) return; // Stop player thread Stop(); // Dispose CoreMIDI objects - if (midiOutPort != 0) + if (MidiOutPort != 0) { - MIDIPortDispose(midiOutPort); - midiOutPort = 0; + MIDIPortDispose(MidiOutPort); + MidiOutPort = 0; } - if (midiClient != 0) + if (MidiClient != 0) { - MIDIClientDispose(midiClient); - midiClient = 0; + MIDIClientDispose(MidiClient); + MidiClient = 0; } - midiDestination = 0; + MidiDestination = 0; } //========================================================================== @@ -255,7 +255,7 @@ bool CoreMIDIDevice::IsOpen() const { - return midiDestination; + return MidiDestination; } //========================================================================== @@ -267,10 +267,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; @@ -412,7 +412,7 @@ int CoreMIDIDevice::Resume() { - if (!midiDestination || PlayerThread.joinable()) + if (!MidiDestination || PlayerThread.joinable()) { return -1; } @@ -437,7 +437,7 @@ { PlayerThread.join(); } - MIDIFlushOutput(midiDestination); // Drop pending events. + MIDIFlushOutput(MidiDestination); // Drop pending events. // Reset all channels to prevent hanging notes for (int channel = 0; channel < 16; ++channel) @@ -638,7 +638,7 @@ Tempo = PulledEvent.data.tempo; break; case EVENT_MESSAGE: - SendMIDIData(PulledEvent.data.msg, PulledEvent.length, AudioConvertNanosToHostTime(pulled_ev_timestamp)); + HandleEvent(PulledEvent.data.msg, PulledEvent.length, AudioConvertNanosToHostTime(pulled_ev_timestamp)); break; case EVENT_NOP: default: @@ -671,13 +671,13 @@ //========================================================================== // -// CoreMIDIDevice :: SendMIDIData +// CoreMIDIDevice :: HandleEvent // // Send raw MIDI data to the CoreMIDI output port // //========================================================================== -void CoreMIDIDevice::SendMIDIData(const uint8_t* data, size_t length, MIDITimeStamp timestamp) +void CoreMIDIDevice::HandleEvent(const uint8_t* data, size_t length, MIDITimeStamp timestamp) { // 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. @@ -721,7 +721,7 @@ if (packet != nullptr) { - OSStatus status = MIDISend(midiOutPort, midiDestination, packetList); + OSStatus status = MIDISend(MidiOutPort, MidiDestination, packetList); if (status != noErr) { ZMusic_Printf(ZMUSIC_MSG_ERROR, "CoreMIDI: MIDISend failed (error %d)\n", (int)status); @@ -746,7 +746,7 @@ { uint8_t msg[3] = { command, data1, data2 }; int msgLen = GetShortMsgLength(msg); - SendMIDIData(msg, msgLen, 0); + HandleEvent(msg, msgLen, 0); } //========================================================================== 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 }); } From ebbb0ac312fa73b3fc593a77fe92afb64bb9006e Mon Sep 17 00:00:00 2001 From: Charles the Thobe Date: Sun, 5 Jul 2026 20:24:31 +0300 Subject: [PATCH 07/15] AlsaMIDIDevice: correct "buffer_tick" var name and move reference '&' next to data types --- source/mididevices/music_alsa_mididevice.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/source/mididevices/music_alsa_mididevice.cpp b/source/mididevices/music_alsa_mididevice.cpp index 25708a6e..c1643ccc 100644 --- a/source/mididevices/music_alsa_mididevice.cpp +++ b/source/mididevices/music_alsa_mididevice.cpp @@ -73,7 +73,7 @@ class AlsaMIDIDevice : public MIDIDevice void PlayerLoop(); // Event handling - void HandleEvent(snd_seq_event_t &event, uint32_t tick); + 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; @@ -85,7 +85,7 @@ class AlsaMIDIDevice : public MIDIDevice } PulledEvent; // Alsa sequencer handles - AlsaSequencer &sequencer; + AlsaSequencer& sequencer; const static int IntendedPortId = 0; bool Connected = false; int PortId = -1; @@ -114,8 +114,8 @@ class AlsaMIDIDevice : public MIDIDevice 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; @@ -448,7 +448,7 @@ void AlsaMIDIDevice::PlayerLoop() snd_seq_drain_output(sequencer.handle); Tempo = InitialTempo; - int buffered_ticks = 0; + int buffer_tick = 0; snd_seq_queue_status_t* status; snd_seq_queue_status_malloc(&status); @@ -465,7 +465,7 @@ 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 pulled_event_tick = buffered_ticks + PulledEvent.tick_delta; + int 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 ticks_until_pulled_ev = pulled_event_tick - queue_tick; @@ -486,7 +486,7 @@ void AlsaMIDIDevice::PlayerLoop() // We found an event worthy of sending to the sequencer HandleEvent(PulledEvent.event, pulled_event_tick); - buffered_ticks = pulled_event_tick; + buffer_tick = pulled_event_tick; Position += PositionOffset; } @@ -494,7 +494,7 @@ void AlsaMIDIDevice::PlayerLoop() } // Requires QueueId to be started first for non-zero tick positioned events. -void AlsaMIDIDevice::HandleEvent(snd_seq_event_t &event, uint32_t tick) +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. From fd06a8bd7656a0e48f8bc03dcdf4b3c60fa06c71 Mon Sep 17 00:00:00 2001 From: Charles the Thobe Date: Sun, 5 Jul 2026 23:22:30 +0300 Subject: [PATCH 08/15] AlsaMIDIDevice and CoreMIDIDevice: correct some data types --- source/mididevices/music_alsa_mididevice.cpp | 8 ++++---- source/mididevices/music_coremidi_mididevice.mm | 14 +++++++------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/source/mididevices/music_alsa_mididevice.cpp b/source/mididevices/music_alsa_mididevice.cpp index c1643ccc..c22b9621 100644 --- a/source/mididevices/music_alsa_mididevice.cpp +++ b/source/mididevices/music_alsa_mididevice.cpp @@ -377,7 +377,7 @@ bool AlsaMIDIDevice::PullEvent() return false; } - uint32_t* event = (uint32_t*)(Events->lpData + Position); + 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 @@ -398,8 +398,8 @@ bool AlsaMIDIDevice::PullEvent() break; case MEVENT_LONGMSG: // SysEx message... { - int long_msg_len = MEVENT_EVENTPARM(event[2]); - uint8_t* long_msg_data = (uint8_t*)&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) { @@ -510,7 +510,7 @@ void AlsaMIDIDevice::HandleEvent(snd_seq_event_t& event, uint32_t 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); diff --git a/source/mididevices/music_coremidi_mididevice.mm b/source/mididevices/music_coremidi_mididevice.mm index fb5e7918..c44201d7 100644 --- a/source/mididevices/music_coremidi_mididevice.mm +++ b/source/mididevices/music_coremidi_mididevice.mm @@ -85,7 +85,7 @@ // Event handling void PrepareTempo(uint32_t tempo); - void PrepareMidiMsg(uint8_t* msg, uint32_t length); + void PrepareMidiMsg(const uint8_t* msg, uint32_t length); void HandleEvent(const uint8_t* data, size_t length, MIDITimeStamp timestamp); void SendImmediateShortMsg(uint8_t command, uint8_t data1 = 0, uint8_t data2 = 0); int GetShortMsgLength(uint8_t* msg); @@ -98,7 +98,7 @@ union { uint32_t tempo; - uint8_t* msg; + const uint8_t* msg; } data; EventType type; uint32_t length; @@ -533,7 +533,7 @@ return false; } - uint32_t* event = (uint32_t*)(Events->lpData + Position); + 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 @@ -555,8 +555,8 @@ break; case MEVENT_LONGMSG: { // 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]; + 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) { @@ -598,7 +598,7 @@ void CoreMIDIDevice::PlayerLoop() { std::unique_lock lock(Mutex); - std::chrono::nanoseconds buffer_step(40000000); + const std::chrono::nanoseconds buffer_step(40000000); Tempo = InitialTempo; // Initialize midi clock with current host time @@ -662,7 +662,7 @@ PulledEvent.type = EVENT_TEMPO; PulledEvent.data.tempo = tempo; } -void CoreMIDIDevice::PrepareMidiMsg(uint8_t* msg, uint32_t length) +void CoreMIDIDevice::PrepareMidiMsg(const uint8_t* msg, uint32_t length) { PulledEvent.type = EVENT_MESSAGE; PulledEvent.data.msg = msg; From 8e750288dd10e6d69eb64112e4aa1cb70edf8244 Mon Sep 17 00:00:00 2001 From: Charles the Thobe Date: Sun, 5 Jul 2026 21:57:33 +0300 Subject: [PATCH 09/15] AlsaMIDIDevice and CoreMIDIDevice: curly braces initializations --- source/mididevices/music_alsa_mididevice.cpp | 10 +++---- .../mididevices/music_coremidi_mididevice.mm | 26 +++++++++---------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/source/mididevices/music_alsa_mididevice.cpp b/source/mididevices/music_alsa_mididevice.cpp index c22b9621..45c97350 100644 --- a/source/mididevices/music_alsa_mididevice.cpp +++ b/source/mididevices/music_alsa_mididevice.cpp @@ -112,7 +112,7 @@ class AlsaMIDIDevice : public MIDIDevice 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); @@ -228,7 +228,7 @@ void AlsaMIDIDevice::PrecacheInstruments(const uint16_t* instruments, int count) { return; } - uint8_t bank[16] = {0}; + uint8_t bank[16] = {}; uint8_t i, chan; for (i = 0, chan = 0; i < count; ++i) @@ -434,8 +434,8 @@ bool AlsaMIDIDevice::PullEvent() */ void AlsaMIDIDevice::PlayerLoop() { - std::unique_lock lock(Mutex); - const std::chrono::microseconds buffer_step(40000); + std::unique_lock lock{Mutex}; + const std::chrono::microseconds buffer_step{40000}; // TODO: fill in error handling throughout this. snd_seq_queue_tempo_t* tempo; @@ -469,7 +469,7 @@ void AlsaMIDIDevice::PlayerLoop() snd_seq_get_queue_status(sequencer.handle, QueueId, status); int queue_tick = snd_seq_queue_status_get_tick_time(status); int ticks_until_pulled_ev = pulled_event_tick - queue_tick; - auto time_until_pulled_ev = std::chrono::microseconds(ticks_until_pulled_ev * Tempo / Division); + 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) { diff --git a/source/mididevices/music_coremidi_mididevice.mm b/source/mididevices/music_coremidi_mididevice.mm index c44201d7..27c4f81e 100644 --- a/source/mididevices/music_coremidi_mididevice.mm +++ b/source/mididevices/music_coremidi_mididevice.mm @@ -135,15 +135,15 @@ //========================================================================== CoreMIDIDevice::CoreMIDIDevice(int dev_id, bool precache) - : DeviceID(dev_id) - , MidiClient(0) - , MidiOutPort(0) - , MidiDestination(0) - , InitialTempo(500000) // Default: 120 BPM (500,000 µs per quarter note) - , Division(100) // Default PPQN - , Events(nullptr) - , Position(0) - , Precache(precache) + : DeviceID{dev_id} + , MidiClient{0} + , MidiOutPort{0} + , MidiDestination{0} + , InitialTempo{500000} // Default: 120 BPM (500,000 µs per quarter note) + , Division{100} // Default PPQN + , Events{nullptr} + , Position{0} + , Precache{precache} { } @@ -333,7 +333,7 @@ { return; } - uint8_t bank[16] = {0}; + uint8_t bank[16] = {}; uint8_t i, chan; for (i = 0, chan = 0; i < count; ++i) @@ -597,8 +597,8 @@ void CoreMIDIDevice::PlayerLoop() { - std::unique_lock lock(Mutex); - const std::chrono::nanoseconds buffer_step(40000000); + std::unique_lock lock{Mutex}; + const std::chrono::nanoseconds buffer_step{40000000}; Tempo = InitialTempo; // Initialize midi clock with current host time @@ -616,7 +616,7 @@ // CoreAudio and CoreMidi work in nano seconds so multiply by 1000. MIDITimeStamp pulled_ev_timestamp = buffer_timestamp + PulledEvent.tick_delta * Tempo / Division * 1000; - auto time_until_pulled_ev = std::chrono::nanoseconds(pulled_ev_timestamp - AudioConvertHostTimeToNanos(AudioGetCurrentHostTime())); + std::chrono::nanoseconds time_until_pulled_ev{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 From a76bac23c45886ae70d145a818b4fd2ffa10fa89 Mon Sep 17 00:00:00 2001 From: Charles the Thobe Date: Sun, 5 Jul 2026 20:48:43 +0300 Subject: [PATCH 10/15] CoreMIDIDevice: follow documentation and do not dispose of client or port --- .../mididevices/music_coremidi_mididevice.mm | 59 +++++++------------ 1 file changed, 20 insertions(+), 39 deletions(-) diff --git a/source/mididevices/music_coremidi_mididevice.mm b/source/mididevices/music_coremidi_mididevice.mm index 27c4f81e..83778ad0 100644 --- a/source/mididevices/music_coremidi_mididevice.mm +++ b/source/mididevices/music_coremidi_mididevice.mm @@ -106,8 +106,8 @@ } PulledEvent; // CoreMIDI handles - MIDIClientRef MidiClient; - MIDIPortRef MidiOutPort; + inline static MIDIClientRef MidiClient = 0; + inline static MIDIPortRef MidiOutPort = 0; MIDIEndpointRef MidiDestination; int DeviceID; @@ -136,8 +136,6 @@ CoreMIDIDevice::CoreMIDIDevice(int dev_id, bool precache) : DeviceID{dev_id} - , MidiClient{0} - , MidiOutPort{0} , MidiDestination{0} , InitialTempo{500000} // Default: 120 BPM (500,000 µs per quarter note) , Division{100} // Default PPQN @@ -173,22 +171,26 @@ OSStatus status; - // Create MIDI client - status = MIDIClientCreate(CFSTR("ZMusic"), 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("ZMusic Program Music"), &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 @@ -196,10 +198,6 @@ if (DeviceID < 0 || DeviceID >= (int)midiout_device_count) { ZMusic_Printf(ZMUSIC_MSG_ERROR, "CoreMIDI: Invalid device ID %d (available: %d)\n", DeviceID, (int)midiout_device_count); - MIDIPortDispose(MidiOutPort); - MIDIClientDispose(MidiClient); - MidiOutPort = 0; - MidiClient = 0; return -1; } @@ -207,10 +205,6 @@ 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; return -1; } @@ -226,24 +220,11 @@ void CoreMIDIDevice::Close() { 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; } From 96b51b0a87ee325a2a64675150620794e549d64b Mon Sep 17 00:00:00 2001 From: Charles the Thobe Date: Sun, 5 Jul 2026 21:21:48 +0300 Subject: [PATCH 11/15] CoreMIDIDevice: ensure accurate pulled_ev_timestamp assignment --- source/mididevices/music_coremidi_mididevice.mm | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/source/mididevices/music_coremidi_mididevice.mm b/source/mididevices/music_coremidi_mididevice.mm index 83778ad0..bad82f0a 100644 --- a/source/mididevices/music_coremidi_mididevice.mm +++ b/source/mididevices/music_coremidi_mididevice.mm @@ -118,9 +118,9 @@ std::condition_variable ExitCond; // Timing - int InitialTempo; - int Tempo; - int Division; + int64_t InitialTempo; + int64_t Tempo; + int64_t Division; // ZMusic MidiHeader data MidiHeader* Events; // Linked list of MIDI headers akin to win32 MIDIHDR @@ -595,8 +595,8 @@ } // CoreAudio and CoreMidi work in nano seconds so multiply by 1000. - MIDITimeStamp pulled_ev_timestamp = buffer_timestamp + PulledEvent.tick_delta * Tempo / Division * 1000; - + auto pulled_ev_time_delta = 1000 * PulledEvent.tick_delta * Tempo / Division; + MIDITimeStamp pulled_ev_timestamp = buffer_timestamp + pulled_ev_time_delta; std::chrono::nanoseconds time_until_pulled_ev{pulled_ev_timestamp - AudioConvertHostTimeToNanos(AudioGetCurrentHostTime())}; auto schedule_time = time_until_pulled_ev - buffer_step; if (schedule_time >= buffer_step) From 34e89eef3ddc29ccb32c4d23779e95c515e31f51 Mon Sep 17 00:00:00 2001 From: Charles the Thobe Date: Sun, 5 Jul 2026 21:15:24 +0300 Subject: [PATCH 12/15] AlsaMIDIDevice: avoid bad sign conversion in "ticks_until_pulled_ev" This also makes sure the constructor for "time_until_pulled_ev" is the correct value via promotion of the operands in the constructor because even in cases where all values of the operands were correct any intermediary result or the final result could overflow/underflow. --- source/mididevices/music_alsa_mididevice.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/source/mididevices/music_alsa_mididevice.cpp b/source/mididevices/music_alsa_mididevice.cpp index 45c97350..c74b11db 100644 --- a/source/mididevices/music_alsa_mididevice.cpp +++ b/source/mididevices/music_alsa_mididevice.cpp @@ -102,9 +102,9 @@ class AlsaMIDIDevice : public MIDIDevice std::condition_variable ExitCond; // Timing - int InitialTempo = 500000; - int Tempo; - int Division = 100; // PPQN + int64_t InitialTempo = 500000; + int64_t Tempo; + int64_t Division = 100; // PPQN // ZMusic MidiHeader data MidiHeader* Events = nullptr; @@ -448,7 +448,7 @@ void AlsaMIDIDevice::PlayerLoop() snd_seq_drain_output(sequencer.handle); Tempo = InitialTempo; - int buffer_tick = 0; + uint32_t buffer_tick = 0; snd_seq_queue_status_t* status; snd_seq_queue_status_malloc(&status); @@ -465,10 +465,10 @@ 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 pulled_event_tick = buffer_tick + PulledEvent.tick_delta; + 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 ticks_until_pulled_ev = pulled_event_tick - queue_tick; + 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) From c170702f1e1620758babd628447284d6d5795829 Mon Sep 17 00:00:00 2001 From: Charles the Thobe Date: Sun, 28 Jun 2026 00:56:09 +0300 Subject: [PATCH 13/15] CoreMIDIDevice: substitute deprecated functions --- CMakeLists.txt | 9 +- .../mididevices/music_coremidi_mididevice.mm | 205 +++++++++--------- 2 files changed, 110 insertions(+), 104 deletions(-) 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/source/mididevices/music_coremidi_mididevice.mm b/source/mididevices/music_coremidi_mididevice.mm index bad82f0a..bc34c1e4 100644 --- a/source/mididevices/music_coremidi_mididevice.mm +++ b/source/mididevices/music_coremidi_mididevice.mm @@ -37,7 +37,6 @@ #include #include -#include #include #include #include @@ -85,11 +84,10 @@ // Event handling void PrepareTempo(uint32_t tempo); - void PrepareMidiMsg(const uint8_t* msg, uint32_t length); - void HandleEvent(const uint8_t* data, size_t length, MIDITimeStamp timestamp); - void SendImmediateShortMsg(uint8_t command, uint8_t data1 = 0, uint8_t data2 = 0); - int GetShortMsgLength(uint8_t* msg); - std::array ShortMsgBuffer; + 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 }; @@ -98,10 +96,10 @@ union { uint32_t tempo; - const uint8_t* msg; + uint32_t msg_buffer[64]; } data; EventType type; - uint32_t length; + ByteCount word_count; uint32_t tick_delta; } PulledEvent; @@ -540,8 +538,8 @@ 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) - { - PrepareMidiMsg(long_msg_data, long_msg_len); + { // Strip sysex start (0xF0) and end (0xF7) bytes + PrepareLongMsg(long_msg_data + 1, long_msg_len - 2); } else { @@ -550,14 +548,8 @@ break; } case MEVENT_SHORTMSG: - { - // 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 = GetShortMsgLength(ShortMsgBuffer.data()); - PrepareMidiMsg(ShortMsgBuffer.data(), msgLen); + { // 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; } default: @@ -600,7 +592,7 @@ std::chrono::nanoseconds time_until_pulled_ev{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 + { // Try to keep buffered events under 2x buffer_step if (ExitCond.wait_for(lock, schedule_time) == std::cv_status::no_timeout) { continue; @@ -619,7 +611,7 @@ Tempo = PulledEvent.data.tempo; break; case EVENT_MESSAGE: - HandleEvent(PulledEvent.data.msg, PulledEvent.length, AudioConvertNanosToHostTime(pulled_ev_timestamp)); + HandleEvent(PulledEvent.data.msg_buffer, PulledEvent.word_count, AudioConvertNanosToHostTime(pulled_ev_timestamp)); break; case EVENT_NOP: default: @@ -632,7 +624,7 @@ //========================================================================== // -// CoreMIDIDevice :: PrepareTempo and PrepareMidiMsg +// CoreMIDIDevice :: PrepareTempo and PrepareShortMsg // // Prepare pulled event to be handled later // @@ -643,75 +635,125 @@ PulledEvent.type = EVENT_TEMPO; PulledEvent.data.tempo = tempo; } -void CoreMIDIDevice::PrepareMidiMsg(const uint8_t* msg, uint32_t length) +void CoreMIDIDevice::PrepareShortMsg(uint32_t msg) { PulledEvent.type = EVENT_MESSAGE; - PulledEvent.data.msg = msg; - PulledEvent.length = length; + PulledEvent.data.msg_buffer[0] = msg; + PulledEvent.word_count = 1; } //========================================================================== // -// CoreMIDIDevice :: HandleEvent +// 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::HandleEvent(const uint8_t* data, size_t length, MIDITimeStamp timestamp) +void CoreMIDIDevice::PrepareLongMsg(const uint8_t* long_msg, uint32_t length) { - // 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). - // 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; - std::vector large_buffer; // Will be used only if needed. - - if (requiredSize > sizeof(small_buffer)) + uint ump_count = (length / 6) * 2; + if (length % 6) { - ZMusic_Printf(ZMUSIC_MSG_DEBUG, "CoreMIDI: Required MIDIPacketList size \"%zu\" exceeds small_buffer size \"%zu\"\n", requiredSize, sizeof(small_buffer)); - try + 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; + } + auto remaining_bytes = length; + auto index_ptr = long_msg; + size_t msg_buffer_index = 0; + while (remaining_bytes > 0) + { + // 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 +// +//========================================================================== - if (packet != nullptr) +void CoreMIDIDevice::HandleEvent(const uint32_t* data, ByteCount word_count, MIDITimeStamp timestamp) +{ + MIDIEventList event_list = {}; + MIDIEventPacket* event_packet = MIDIEventListInit(&event_list, kMIDIProtocol_1_0); + + // 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"); } } @@ -723,41 +765,10 @@ // //========================================================================== -void CoreMIDIDevice::SendImmediateShortMsg(uint8_t command, uint8_t data1, uint8_t data2) +void CoreMIDIDevice::SendImmediateShortMsg(uint32_t command, uint32_t data1, uint32_t data2) { - uint8_t msg[3] = { command, data1, data2 }; - int msgLen = GetShortMsgLength(msg); - HandleEvent(msg, msgLen, 0); -} - -//========================================================================== -// -// CoreMIDIDevice :: GetShortMsgLength -// -// Determines the length of a short MIDI message -// The actual correct length is necessary for CoreMIDI to work. -// -//========================================================================== - -int CoreMIDIDevice::GetShortMsgLength(uint8_t* msg) -{ - int msgLen; - if (msg[0] >= 0xF0) // System messages - { - if (msg[0] == 0xF0 || msg[0] == 0xF7) msgLen = 1; // Start/Stop/Continue/Timing/Active Sensing/Reset (1 byte) - else if (msg[0] == 0xF1 || msg[0] == 0xF3) msgLen = 2; // Time Code Quarter Frame, Song Select (2 bytes) - else if (msg[0] == 0xF2) msgLen = 3; // Song Position Pointer (3 bytes) - else msgLen = 1; // Default to 1 for other unknown system messages - } - else if (msg[0] >= 0xC0 && msg[0] <= 0xDF) // Program Change or Channel Aftertouch (2 bytes) - { - msgLen = 2; - } - else // Note On/Off, Poly Aftertouch, Control Change, Pitch Bend (3 bytes) - { - msgLen = 3; - } - return msgLen; + const uint32_t msg = 0x20 << 24 | command << 16 | data1 << 8 | data2; + HandleEvent(&msg, 1, 0); } //========================================================================== From e12b3f019a55f60c4d2db9b345867396cabf8202 Mon Sep 17 00:00:00 2001 From: Charles the Thobe Date: Sat, 11 Jul 2026 21:54:06 +0300 Subject: [PATCH 14/15] CoreMIDIDevice::PlayerLoop(): use std::chrono where applicable --- source/mididevices/music_coremidi_mididevice.mm | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/source/mididevices/music_coremidi_mididevice.mm b/source/mididevices/music_coremidi_mididevice.mm index bc34c1e4..7d5b896b 100644 --- a/source/mididevices/music_coremidi_mididevice.mm +++ b/source/mididevices/music_coremidi_mididevice.mm @@ -574,8 +574,8 @@ const std::chrono::nanoseconds buffer_step{40000000}; Tempo = InitialTempo; - // Initialize midi clock with current host time - MIDITimeStamp buffer_timestamp = AudioConvertHostTimeToNanos(AudioGetCurrentHostTime()); + // 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 (!Exit.load(std::memory_order_relaxed)) @@ -586,10 +586,11 @@ continue; } - // CoreAudio and CoreMidi work in nano seconds so multiply by 1000. - auto pulled_ev_time_delta = 1000 * PulledEvent.tick_delta * Tempo / Division; - MIDITimeStamp pulled_ev_timestamp = buffer_timestamp + pulled_ev_time_delta; - std::chrono::nanoseconds time_until_pulled_ev{pulled_ev_timestamp - AudioConvertHostTimeToNanos(AudioGetCurrentHostTime())}; + // 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 @@ -611,7 +612,7 @@ Tempo = PulledEvent.data.tempo; break; case EVENT_MESSAGE: - HandleEvent(PulledEvent.data.msg_buffer, PulledEvent.word_count, AudioConvertNanosToHostTime(pulled_ev_timestamp)); + HandleEvent(PulledEvent.data.msg_buffer, PulledEvent.word_count, AudioConvertNanosToHostTime(pulled_ev_timestamp.count())); break; case EVENT_NOP: default: From e1e8c6cfefee37cf647c09d8592343b34ece9799 Mon Sep 17 00:00:00 2001 From: Charles the Thobe Date: Fri, 7 Aug 2026 17:40:10 +0300 Subject: [PATCH 15/15] AlsaMIDIDevice and CoreMIDIDevice: make buffer_step value clearer now it's set in milliseconds while avoiding runtime convertion --- source/mididevices/music_alsa_mididevice.cpp | 3 ++- source/mididevices/music_coremidi_mididevice.mm | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/source/mididevices/music_alsa_mididevice.cpp b/source/mididevices/music_alsa_mididevice.cpp index c74b11db..2848bbb5 100644 --- a/source/mididevices/music_alsa_mididevice.cpp +++ b/source/mididevices/music_alsa_mididevice.cpp @@ -435,7 +435,8 @@ bool AlsaMIDIDevice::PullEvent() void AlsaMIDIDevice::PlayerLoop() { std::unique_lock lock{Mutex}; - const std::chrono::microseconds buffer_step{40000}; + 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; diff --git a/source/mididevices/music_coremidi_mididevice.mm b/source/mididevices/music_coremidi_mididevice.mm index 7d5b896b..169410e0 100644 --- a/source/mididevices/music_coremidi_mididevice.mm +++ b/source/mididevices/music_coremidi_mididevice.mm @@ -571,7 +571,8 @@ void CoreMIDIDevice::PlayerLoop() { std::unique_lock lock{Mutex}; - const std::chrono::nanoseconds buffer_step{40000000}; + 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.