1+ // SPDX-FileCopyrightText: Copyright (C) Kushview, LLC.
2+ // SPDX-License-Identifier: GPL-3.0-or-later
3+
4+ #include " midioutput.h"
5+ namespace element {
6+ #if JUCE_MAC
7+
8+ MIDIEndpointRef findDestination (const juce::MidiDeviceInfo& deviceInfo)
9+ {
10+ SInt32 targetUID = deviceInfo.identifier .containsChar (' ' )
11+ ? deviceInfo.identifier .fromLastOccurrenceOf (" " , false , false ).getIntValue ()
12+ : deviceInfo.identifier .getIntValue ();
13+ ItemCount count = MIDIGetNumberOfDestinations ();
14+
15+ for (ItemCount i = 0 ; i < count; ++i)
16+ {
17+ MIDIEndpointRef endpoint = MIDIGetDestination (i);
18+ SInt32 uid = 0 ;
19+ MIDIObjectGetIntegerProperty (endpoint, kMIDIPropertyUniqueID , &uid);
20+
21+ if (uid == targetUID)
22+ return endpoint;
23+ }
24+
25+ return 0 ;
26+ }
27+
28+ ElementMidiOutput::ElementMidiOutput (const juce::MidiDeviceInfo& deviceInfo)
29+ {
30+ destination = findDestination (deviceInfo);
31+ if (destination)
32+ {
33+ MIDIClientCreate (CFSTR (" ElementMIDIOutput" ), nullptr , nullptr , &client);
34+ MIDIOutputPortCreate (client, deviceInfo.name .toCFString (), &port);
35+ mach_timebase_info (&timebase);
36+ }
37+ }
38+
39+ juce::Result ElementMidiOutput::openDevice (const juce::MidiDeviceInfo& deviceInfo, std::unique_ptr<ElementMidiOutput>& out)
40+ {
41+ out = std::make_unique<ElementMidiOutput> (deviceInfo);
42+ if (! out->destination )
43+ {
44+ out.reset ();
45+ return juce::Result::fail (" MIDI destination not found: " + deviceInfo.name );
46+ }
47+ return juce::Result::ok ();
48+ }
49+
50+ void ElementMidiOutput::closeDevice ()
51+ {
52+ if (! destination)
53+ return ;
54+ MIDIFlushOutput (destination);
55+ MIDIClientDispose (client);
56+ destination = 0 ;
57+ }
58+
59+ MIDITimeStamp ElementMidiOutput::futureTimestamp (uint64_t nanosFromNow) const
60+ {
61+ MIDITimeStamp ticks = nanosFromNow * timebase.denom / timebase.numer ;
62+ return mach_absolute_time () + ticks;
63+ }
64+
65+ juce::Result ElementMidiOutput::sendBlockOfMessages (const juce::MidiBuffer& midi, double delayMs, double sampleRate) const
66+ {
67+ constexpr size_t BUFFER_SIZE_FOR_PACKET_LIST = 1024 ;
68+ uint8_t buffer[BUFFER_SIZE_FOR_PACKET_LIST ];
69+ auto * packetList = reinterpret_cast <MIDIPacketList*> (buffer);
70+ MIDIPacket* packet = MIDIPacketListInit (packetList);
71+
72+ for (juce::MidiMessageMetadata message : midi)
73+ {
74+ const double nanoSeconds = message.samplePosition / sampleRate * 1'000'000'000.0 + delayMs * 1'000'000.0 ;
75+ const auto ticks = futureTimestamp (static_cast <uint64_t > (std::round (nanoSeconds)));
76+ packet = MIDIPacketListAdd (packetList, BUFFER_SIZE_FOR_PACKET_LIST , packet, ticks, message.numBytes , message.data );
77+ }
78+
79+ OSStatus status = MIDISend (port, destination, packetList);
80+ if (status != noErr)
81+ return juce::Result::fail (" MIDISend failed with OSStatus " + juce::String (status));
82+ return juce::Result::ok ();
83+ }
84+
85+ #else
86+
87+ void ElementMidiOutput::closeDevice ()
88+ {
89+ if (output)
90+ {
91+ output->stopBackgroundThread ();
92+ output->clearAllPendingMessages ();
93+ output.reset ();
94+ }
95+ }
96+
97+ ElementMidiOutput::ElementMidiOutput (const juce::MidiDeviceInfo& deviceInfo)
98+ {
99+ output = juce::MidiOutput::openDevice (deviceInfo.identifier );
100+
101+ if (output)
102+ {
103+ output->clearAllPendingMessages ();
104+ output->startBackgroundThread ();
105+ }
106+ else
107+ {
108+ DBG (" [element] could not open MIDI output: " << deviceInfo.name );
109+ }
110+ }
111+
112+ juce::Result ElementMidiOutput::openDevice (const juce::MidiDeviceInfo& deviceInfo, std::unique_ptr<ElementMidiOutput>& out)
113+ {
114+ out = std::make_unique<ElementMidiOutput> (deviceInfo);
115+ if (! out->output )
116+ {
117+ out.reset ();
118+ return juce::Result::fail (" Could not open MIDI output: " + deviceInfo.name );
119+ }
120+ return juce::Result::ok ();
121+ }
122+
123+ juce::Result ElementMidiOutput::sendBlockOfMessages (const juce::MidiBuffer& midi, double delayMs, double sampleRate) const
124+ {
125+ #if JUCE_WINDOWS
126+ output->sendBlockOfMessagesNow (midi);
127+ #else
128+ output->sendBlockOfMessages (
129+ midi, delayMs + juce::Time::getMillisecondCounterHiRes (), sampleRate);
130+ #endif
131+ return juce::Result::ok ();
132+ }
133+ #endif
134+
135+ } // namespace element
0 commit comments