diff --git a/cmake/yup_utilities.cmake b/cmake/yup_utilities.cmake index b23d4d17..87a99b47 100644 --- a/cmake/yup_utilities.cmake +++ b/cmake/yup_utilities.cmake @@ -321,7 +321,8 @@ function (_yup_setup_coverage_flags target_name) -fprofile-arcs -fprofile-update=atomic -ftest-coverage - -fno-elide-constructors) + $<$:-fno-elide-constructors> + $<$:-fno-elide-constructors>) target_link_options (${target_name} INTERFACE --coverage) diff --git a/modules/yup_audio_devices/native/yup_CoreMidi_apple.mm b/modules/yup_audio_devices/native/yup_CoreMidi_apple.mm index d67dc803..595f1b4c 100644 --- a/modules/yup_audio_devices/native/yup_CoreMidi_apple.mm +++ b/modules/yup_audio_devices/native/yup_CoreMidi_apple.mm @@ -84,67 +84,67 @@ static bool checkError(OSStatus err, [[maybe_unused]] int lineNum) constexpr auto implementationStrategy = ImplementationStrategy::onlyOld; #endif -struct SenderBase -{ - virtual ~SenderBase() noexcept = default; - - virtual void send(MIDIPortRef port, - MIDIEndpointRef endpoint, - ump::PacketProtocol protocol, - const ump::BytestreamMidiView& m) = 0; - virtual void send(MIDIPortRef port, - MIDIEndpointRef endpoint, - ump::PacketProtocol protocol, - ump::Iterator b, - ump::Iterator e) = 0; -}; +struct SenderBase +{ + virtual ~SenderBase() noexcept = default; + + virtual void send(MIDIPortRef port, + MIDIEndpointRef endpoint, + ump::PacketProtocol protocol, + const ump::BytestreamMidiView& m) = 0; + virtual void send(MIDIPortRef port, + MIDIEndpointRef endpoint, + ump::PacketProtocol protocol, + ump::Iterator b, + ump::Iterator e) = 0; +}; template struct Sender; #if YUP_HAS_NEW_COREMIDI_API template <> -struct API_AVAILABLE(macos(11.0), ios(14.0)) Sender final : public SenderBase -{ - void send(MIDIPortRef port, - MIDIEndpointRef endpoint, - ump::PacketProtocol protocol, - const ump::BytestreamMidiView& m) override - { - newSendImpl(port, endpoint, protocol, m); - } - - void send(MIDIPortRef port, - MIDIEndpointRef endpoint, - ump::PacketProtocol protocol, - ump::Iterator b, - ump::Iterator e) override - { - newSendImpl(port, endpoint, protocol, b, e); - } - - private: - void newSendImpl(MIDIPortRef port, - MIDIEndpointRef endpoint, - ump::PacketProtocol protocol, - const ump::BytestreamMidiView& message) - { -#if YUP_IOS - const MIDITimeStamp timeStamp = mach_absolute_time(); -#else - const MIDITimeStamp timeStamp = AudioGetCurrentHostTime(); -#endif - - MIDIEventList stackList = {}; - MIDIEventPacket* end = nullptr; - - const auto init = [&] - { - const auto protocolId = protocol == ump::PacketProtocol::MIDI_2_0 - ? kMIDIProtocol_2_0 - : kMIDIProtocol_1_0; - end = MIDIEventListInit(&stackList, protocolId); - }; +struct API_AVAILABLE(macos(11.0), ios(14.0)) Sender final : public SenderBase +{ + void send(MIDIPortRef port, + MIDIEndpointRef endpoint, + ump::PacketProtocol protocol, + const ump::BytestreamMidiView& m) override + { + newSendImpl(port, endpoint, protocol, m); + } + + void send(MIDIPortRef port, + MIDIEndpointRef endpoint, + ump::PacketProtocol protocol, + ump::Iterator b, + ump::Iterator e) override + { + newSendImpl(port, endpoint, protocol, b, e); + } + + private: + void newSendImpl(MIDIPortRef port, + MIDIEndpointRef endpoint, + ump::PacketProtocol protocol, + const ump::BytestreamMidiView& message) + { +#if YUP_IOS + const MIDITimeStamp timeStamp = mach_absolute_time(); +#else + const MIDITimeStamp timeStamp = AudioGetCurrentHostTime(); +#endif + + MIDIEventList stackList = {}; + MIDIEventPacket* end = nullptr; + + const auto init = [&] + { + const auto protocolId = protocol == ump::PacketProtocol::MIDI_2_0 + ? kMIDIProtocol_2_0 + : kMIDIProtocol_1_0; + end = MIDIEventListInit(&stackList, protocolId); + }; const auto send = [&] { @@ -164,107 +164,107 @@ void newSendImpl(MIDIPortRef port, reinterpret_cast(view.data())); }; - init(); - - ump::GenericUMPConverter converter { protocol }; - converter.convert (message, [&] (const ump::View& view) - { - add (view); - - if (end != nullptr) - return; - - send(); - init(); - add (view); - }); - - send(); - } - - void newSendImpl(MIDIPortRef port, - MIDIEndpointRef endpoint, - ump::PacketProtocol protocol, - ump::Iterator b, - ump::Iterator e) - { -#if YUP_IOS - const MIDITimeStamp timeStamp = mach_absolute_time(); -#else - const MIDITimeStamp timeStamp = AudioGetCurrentHostTime(); -#endif - - MIDIEventList stackList = {}; - MIDIEventPacket* end = nullptr; - - const auto init = [&] - { - const auto protocolId = protocol == ump::PacketProtocol::MIDI_2_0 - ? kMIDIProtocol_2_0 - : kMIDIProtocol_1_0; - end = MIDIEventListInit(&stackList, protocolId); - }; - - const auto send = [&] - { - CHECK_ERROR(port != 0 ? MIDISendEventList(port, endpoint, &stackList) - : MIDIReceivedEventList(endpoint, &stackList)); - }; - - const auto add = [&](const ump::View& view) - { - static_assert(sizeof(uint32_t) == sizeof(UInt32) && alignof(uint32_t) == alignof(UInt32), - "If this fails, the cast below will be broken too!"); - end = MIDIEventListAdd(&stackList, - sizeof(MIDIEventList::packet), - end, - timeStamp, - view.size(), - reinterpret_cast(view.data())); - }; - - init(); - - ump::GenericUMPConverter converter { protocol }; - converter.convert (b, e, [&] (const ump::View& view) - { - add (view); - - if (end != nullptr) - return; - - send(); - init(); - add (view); - }); - - send(); - } -}; -#endif + init(); + + ump::GenericUMPConverter converter { protocol }; + converter.convert (message, [&] (const ump::View& view) + { + add (view); + + if (end != nullptr) + return; + + send(); + init(); + add (view); + }); + + send(); + } + + void newSendImpl(MIDIPortRef port, + MIDIEndpointRef endpoint, + ump::PacketProtocol protocol, + ump::Iterator b, + ump::Iterator e) + { +#if YUP_IOS + const MIDITimeStamp timeStamp = mach_absolute_time(); +#else + const MIDITimeStamp timeStamp = AudioGetCurrentHostTime(); +#endif + + MIDIEventList stackList = {}; + MIDIEventPacket* end = nullptr; + + const auto init = [&] + { + const auto protocolId = protocol == ump::PacketProtocol::MIDI_2_0 + ? kMIDIProtocol_2_0 + : kMIDIProtocol_1_0; + end = MIDIEventListInit(&stackList, protocolId); + }; + + const auto send = [&] + { + CHECK_ERROR(port != 0 ? MIDISendEventList(port, endpoint, &stackList) + : MIDIReceivedEventList(endpoint, &stackList)); + }; + + const auto add = [&](const ump::View& view) + { + static_assert(sizeof(uint32_t) == sizeof(UInt32) && alignof(uint32_t) == alignof(UInt32), + "If this fails, the cast below will be broken too!"); + end = MIDIEventListAdd(&stackList, + sizeof(MIDIEventList::packet), + end, + timeStamp, + view.size(), + reinterpret_cast(view.data())); + }; + + init(); + + ump::GenericUMPConverter converter { protocol }; + converter.convert (b, e, [&] (const ump::View& view) + { + add (view); + + if (end != nullptr) + return; + + send(); + init(); + add (view); + }); + + send(); + } +}; +#endif #if YUP_HAS_OLD_COREMIDI_API template <> -struct Sender final : public SenderBase -{ - void send(MIDIPortRef port, - MIDIEndpointRef endpoint, - [[maybe_unused]] ump::PacketProtocol protocol, - const ump::BytestreamMidiView& m) override - { - oldSendImpl(port, endpoint, m); - } - - void send(MIDIPortRef port, - MIDIEndpointRef endpoint, - [[maybe_unused]] ump::PacketProtocol protocol, - ump::Iterator b, - ump::Iterator e) override - { - std::for_each(b, e, [&](const ump::View& v) - { bytestreamConverter.convert(v, 0.0, [&](const ump::BytestreamMidiView& m) - { send(port, endpoint, protocol, m); }); }); - } +struct Sender final : public SenderBase +{ + void send(MIDIPortRef port, + MIDIEndpointRef endpoint, + [[maybe_unused]] ump::PacketProtocol protocol, + const ump::BytestreamMidiView& m) override + { + oldSendImpl(port, endpoint, m); + } + + void send(MIDIPortRef port, + MIDIEndpointRef endpoint, + [[maybe_unused]] ump::PacketProtocol protocol, + ump::Iterator b, + ump::Iterator e) override + { + std::for_each(b, e, [&](const ump::View& v) + { bytestreamConverter.convert(v, 0.0, [&](const ump::BytestreamMidiView& m) + { send(port, endpoint, protocol, m); }); }); + } private: ump::ToBytestreamConverter bytestreamConverter{2048}; @@ -335,29 +335,29 @@ void oldSendImpl(MIDIPortRef port, MIDIEndpointRef endpoint, const ump::Bytestre #if YUP_HAS_NEW_COREMIDI_API && YUP_HAS_OLD_COREMIDI_API template <> -struct Sender -{ - Sender() - : sender(makeImpl()) - { - } - - void send(MIDIPortRef port, - MIDIEndpointRef endpoint, - ump::PacketProtocol protocol, - const ump::BytestreamMidiView& m) - { - sender->send(port, endpoint, protocol, m); - } - - void send(MIDIPortRef port, - MIDIEndpointRef endpoint, - ump::PacketProtocol protocol, - ump::Iterator b, - ump::Iterator e) - { - sender->send(port, endpoint, protocol, b, e); - } +struct Sender +{ + Sender() + : sender(makeImpl()) + { + } + + void send(MIDIPortRef port, + MIDIEndpointRef endpoint, + ump::PacketProtocol protocol, + const ump::BytestreamMidiView& m) + { + sender->send(port, endpoint, protocol, m); + } + + void send(MIDIPortRef port, + MIDIEndpointRef endpoint, + ump::PacketProtocol protocol, + ump::Iterator b, + ump::Iterator e) + { + sender->send(port, endpoint, protocol, b, e); + } private: static std::unique_ptr makeImpl() @@ -432,14 +432,14 @@ Resource release() noexcept using ScopedEndpointRef = ScopedMidiResource; //============================================================================== -class MidiPortAndEndpoint -{ - public: - MidiPortAndEndpoint(ScopedPortRef p, ScopedEndpointRef ep, ump::PacketProtocol protocolIn) noexcept - : port(std::move(p)), endpoint(std::move(ep)) - , protocol(protocolIn) - { - } +class MidiPortAndEndpoint +{ + public: + MidiPortAndEndpoint(ScopedPortRef p, ScopedEndpointRef ep, ump::PacketProtocol protocolIn) noexcept + : port(std::move(p)), endpoint(std::move(ep)) + , protocol(protocolIn) + { + } ~MidiPortAndEndpoint() noexcept { @@ -448,61 +448,60 @@ Resource release() noexcept endpoint.release(); } - void send(const ump::BytestreamMidiView& m) - { - sender.send(*port, *endpoint, protocol, m); - } - - void send(ump::Iterator b, ump::Iterator e) - { - sender.send(*port, *endpoint, protocol, b, e); - } + void send(const ump::BytestreamMidiView& m) + { + sender.send(*port, *endpoint, protocol, m); + } + + void send(ump::Iterator b, ump::Iterator e) + { + sender.send(*port, *endpoint, protocol, b, e); + } bool canStop() const noexcept { return *port != 0; } void stop() const { CHECK_ERROR(MIDIPortDisconnectSource(*port, *endpoint)); } private: ScopedPortRef port; - ScopedEndpointRef endpoint; - ump::PacketProtocol protocol; - - SenderToUse sender; -}; - -static void updateProtocolInfo(MIDIObjectRef entity, MidiDeviceInfo& info) -{ -#if YUP_HAS_NEW_COREMIDI_API - SInt32 protocol = 0; - - if (@available(iOS 14.0, macOS 11.0, *)) - { - if (MIDIObjectGetIntegerProperty(entity, kMIDIPropertyProtocolID, &protocol) == noErr) - { - if (protocol == kMIDIProtocol_2_0) - { - info.protocol = ump::PacketProtocol::MIDI_2_0; - info.supportsMidi2 = true; - } - else - { - info.protocol = ump::PacketProtocol::MIDI_1_0; - info.supportsMidi2 = false; - } - } - } - else - { - info.protocol = ump::PacketProtocol::MIDI_1_0; - info.supportsMidi2 = false; - } -#else - ignoreUnused(entity, info); -#endif -} - -static MidiDeviceInfo getMidiObjectInfo(MIDIObjectRef entity) -{ - MidiDeviceInfo info; + ScopedEndpointRef endpoint; + ump::PacketProtocol protocol; + + SenderToUse sender; +}; + +static void updateProtocolInfo(MIDIObjectRef entity, MidiDeviceInfo& info) +{ +#if YUP_HAS_NEW_COREMIDI_API + if (@available(iOS 14.0, macOS 11.0, *)) + { + SInt32 protocol = 0; + + if (MIDIObjectGetIntegerProperty(entity, kMIDIPropertyProtocolID, &protocol) == noErr) + { + if (protocol == kMIDIProtocol_2_0) + { + info.protocol = ump::PacketProtocol::MIDI_2_0; + info.supportsMidi2 = true; + } + else + { + info.protocol = ump::PacketProtocol::MIDI_1_0; + info.supportsMidi2 = false; + } + } + } + else + { + ignoreUnused(entity, info); + } +#else + ignoreUnused(entity, info); +#endif +} + +static MidiDeviceInfo getMidiObjectInfo(MIDIObjectRef entity) +{ + MidiDeviceInfo info; { CFObjectHolder str; @@ -525,9 +524,9 @@ static MidiDeviceInfo getMidiObjectInfo(MIDIObjectRef entity) info.identifier = String::fromCFString(str.object); } - updateProtocolInfo(entity, info); - return info; -} + updateProtocolInfo(entity, info); + return info; +} static MidiDeviceInfo getEndpointInfo(MIDIEndpointRef endpoint, bool isExternal) { @@ -1101,54 +1100,54 @@ static CreatorFunctionPointers getCreatorFunctionPointers() return std::make_unique(midiInput, CoreMidiHelpers::ReceiverToUse(midiInput, *midiInputCallback)); } - static std::unique_ptr makeInput(const String& name, - const String& identifier, - ump::PacketProtocol protocol, - ump::Receiver& receiver) - { - using namespace CoreMidiHelpers; - - if (auto midiInput = rawToUniquePtr(new MidiInput(name, identifier, protocol))) - { - if ((midiInput->internal = makePimpl(*midiInput, protocol, receiver))) - { - const ScopedLock sl(callbackLock); - activeCallbacks.add(midiInput->internal.get()); - - return midiInput; - } - } - - return {}; - } - - static std::unique_ptr makeInput(const String& name, - const String& identifier, - ump::PacketProtocol protocol, - MidiInputCallback* midiInputCallback) - { - using namespace CoreMidiHelpers; - - if (auto midiInput = rawToUniquePtr(new MidiInput(name, identifier, protocol))) - { - if ((midiInput->internal = makePimpl(*midiInput, midiInputCallback))) - { - const ScopedLock sl(callbackLock); - activeCallbacks.add(midiInput->internal.get()); - - return midiInput; - } - } - - return {}; - } - - template - static std::unique_ptr openDevice(ump::PacketProtocol protocol, - const String& deviceIdentifier, - Args&&... args) - { - using namespace CoreMidiHelpers; + static std::unique_ptr makeInput(const String& name, + const String& identifier, + ump::PacketProtocol protocol, + ump::Receiver& receiver) + { + using namespace CoreMidiHelpers; + + if (auto midiInput = rawToUniquePtr(new MidiInput(name, identifier, protocol))) + { + if ((midiInput->internal = makePimpl(*midiInput, protocol, receiver))) + { + const ScopedLock sl(callbackLock); + activeCallbacks.add(midiInput->internal.get()); + + return midiInput; + } + } + + return {}; + } + + static std::unique_ptr makeInput(const String& name, + const String& identifier, + ump::PacketProtocol protocol, + MidiInputCallback* midiInputCallback) + { + using namespace CoreMidiHelpers; + + if (auto midiInput = rawToUniquePtr(new MidiInput(name, identifier, protocol))) + { + if ((midiInput->internal = makePimpl(*midiInput, midiInputCallback))) + { + const ScopedLock sl(callbackLock); + activeCallbacks.add(midiInput->internal.get()); + + return midiInput; + } + } + + return {}; + } + + template + static std::unique_ptr openDevice(ump::PacketProtocol protocol, + const String& deviceIdentifier, + Args&&... args) + { + using namespace CoreMidiHelpers; if (deviceIdentifier.isEmpty()) return {}; @@ -1167,12 +1166,12 @@ static CreatorFunctionPointers getCreatorFunctionPointers() if (!CHECK_ERROR(MIDIObjectGetStringProperty(endpoint, kMIDIPropertyName, &cfName.object))) continue; - if (auto input = makeInput(endpointInfo.name, - endpointInfo.identifier, - protocol, - std::forward(args)...)) - { - MIDIPortRef port; + if (auto input = makeInput(endpointInfo.name, + endpointInfo.identifier, + protocol, + std::forward(args)...)) + { + MIDIPortRef port; if (!CHECK_ERROR(CreatorFunctionsToUse::createInputPort(protocol, client, cfName.object, input->internal.get(), &port))) continue; @@ -1182,34 +1181,34 @@ static CreatorFunctionPointers getCreatorFunctionPointers() if (!CHECK_ERROR(MIDIPortConnectSource(*scopedPort, endpoint, nullptr))) continue; - input->internal->portAndEndpoint = std::make_unique(std::move(scopedPort), - ScopedEndpointRef{endpoint}, - protocol); - return input; - } - } + input->internal->portAndEndpoint = std::make_unique(std::move(scopedPort), + ScopedEndpointRef{endpoint}, + protocol); + return input; + } + } } return {}; } template - static std::unique_ptr createDevice(ump::PacketProtocol protocol, - const String& deviceName, - Args&&... args) - { - using namespace CoreMidiHelpers; + static std::unique_ptr createDevice(ump::PacketProtocol protocol, + const String& deviceName, + Args&&... args) + { + using namespace CoreMidiHelpers; if (auto client = getGlobalMidiClient()) { auto deviceIdentifier = createUniqueIDForMidiPort(deviceName, true); - if (auto input = makeInput(deviceName, - String(deviceIdentifier), - protocol, - std::forward(args)...)) - { - MIDIEndpointRef endpoint; + if (auto input = makeInput(deviceName, + String(deviceIdentifier), + protocol, + std::forward(args)...)) + { + MIDIEndpointRef endpoint; CFUniquePtr name(deviceName.toCFString()); auto err = CreatorFunctionsToUse::createDestination(protocol, client, name.get(), input->internal.get(), &endpoint); @@ -1231,12 +1230,12 @@ static CreatorFunctionPointers getCreatorFunctionPointers() if (!CHECK_ERROR(MIDIObjectSetIntegerProperty(endpoint, kMIDIPropertyUniqueID, (SInt32)deviceIdentifier))) return {}; - input->internal->portAndEndpoint = std::make_unique(ScopedPortRef{}, - std::move(scopedEndpoint), - protocol); - return input; - } - } + input->internal->portAndEndpoint = std::make_unique(ScopedPortRef{}, + std::move(scopedEndpoint), + protocol); + return input; + } + } return {}; } @@ -1253,51 +1252,51 @@ static CreatorFunctionPointers getCreatorFunctionPointers() return getAvailableDevices().getFirst(); } -std::unique_ptr MidiInput::openDevice(const String& deviceIdentifier, MidiInputCallback* callback) -{ - if (callback == nullptr) - return nullptr; - - return Pimpl::openDevice(ump::PacketProtocol::MIDI_1_0, - deviceIdentifier, - callback); -} - -std::unique_ptr MidiInput::openDevice(const String& deviceIdentifier, - ump::PacketProtocol protocol, - ump::Receiver* receiver) -{ - if (receiver == nullptr) - return nullptr; - - return Pimpl::openDevice(protocol, deviceIdentifier, *receiver); -} - -std::unique_ptr MidiInput::createNewDevice(const String& deviceName, MidiInputCallback* callback) -{ - return Pimpl::createDevice(ump::PacketProtocol::MIDI_1_0, - deviceName, - callback); -} - -std::unique_ptr MidiInput::createNewDevice(const String& deviceName, - ump::PacketProtocol protocol, - ump::Receiver* receiver) -{ - if (receiver == nullptr) - return {}; - - return Pimpl::createDevice(protocol, - deviceName, - *receiver); -} - -MidiInput::MidiInput(const String& deviceName, - const String& deviceIdentifier, - ump::PacketProtocol protocol) - : deviceInfo(deviceName, deviceIdentifier, protocol) -{ -} +std::unique_ptr MidiInput::openDevice(const String& deviceIdentifier, MidiInputCallback* callback) +{ + if (callback == nullptr) + return nullptr; + + return Pimpl::openDevice(ump::PacketProtocol::MIDI_1_0, + deviceIdentifier, + callback); +} + +std::unique_ptr MidiInput::openDevice(const String& deviceIdentifier, + ump::PacketProtocol protocol, + ump::Receiver* receiver) +{ + if (receiver == nullptr) + return nullptr; + + return Pimpl::openDevice(protocol, deviceIdentifier, *receiver); +} + +std::unique_ptr MidiInput::createNewDevice(const String& deviceName, MidiInputCallback* callback) +{ + return Pimpl::createDevice(ump::PacketProtocol::MIDI_1_0, + deviceName, + callback); +} + +std::unique_ptr MidiInput::createNewDevice(const String& deviceName, + ump::PacketProtocol protocol, + ump::Receiver* receiver) +{ + if (receiver == nullptr) + return {}; + + return Pimpl::createDevice(protocol, + deviceName, + *receiver); +} + +MidiInput::MidiInput(const String& deviceName, + const String& deviceIdentifier, + ump::PacketProtocol protocol) + : deviceInfo(deviceName, deviceIdentifier, protocol) +{ +} MidiInput::~MidiInput() = default; @@ -1330,70 +1329,70 @@ static CreatorFunctionPointers getCreatorFunctionPointers() return getAvailableDevices().getFirst(); } -std::unique_ptr MidiOutput::openDevice(const String& deviceIdentifier) -{ - return openDevice(deviceIdentifier, ump::PacketProtocol::MIDI_1_0); -} - -std::unique_ptr MidiOutput::openDevice(const String& deviceIdentifier, - ump::PacketProtocol protocol) -{ - if (deviceIdentifier.isEmpty()) - return {}; - - using namespace CoreMidiHelpers; - - if (auto client = getGlobalMidiClient()) - { - for (auto& endpoint : getEndpoints(false)) - { - auto endpointInfo = getConnectedEndpointInfo(endpoint); - - if (deviceIdentifier != endpointInfo.identifier) - continue; - - CFObjectHolder cfName; - - if (!CHECK_ERROR(MIDIObjectGetStringProperty(endpoint, kMIDIPropertyName, &cfName.object))) - continue; - - MIDIPortRef port; - - if (!CHECK_ERROR(MIDIOutputPortCreate(client, cfName.object, &port))) - continue; - - ScopedPortRef scopedPort{port}; - - auto midiOutput = rawToUniquePtr(new MidiOutput(endpointInfo.name, endpointInfo.identifier, protocol)); - midiOutput->internal = std::make_unique(std::move(scopedPort), - ScopedEndpointRef{endpoint}, - protocol); - - return midiOutput; - } - } - - return {}; -} - -std::unique_ptr MidiOutput::createNewDevice(const String& deviceName) -{ - return createNewDevice(deviceName, ump::PacketProtocol::MIDI_1_0); -} - -std::unique_ptr MidiOutput::createNewDevice(const String& deviceName, - ump::PacketProtocol protocol) -{ - using namespace CoreMidiHelpers; - - if (auto client = getGlobalMidiClient()) - { - MIDIEndpointRef endpoint; - - CFUniquePtr name(deviceName.toCFString()); - - auto err = CreatorFunctionsToUse::createSource(protocol, client, name.get(), &endpoint); - ScopedEndpointRef scopedEndpoint{endpoint}; +std::unique_ptr MidiOutput::openDevice(const String& deviceIdentifier) +{ + return openDevice(deviceIdentifier, ump::PacketProtocol::MIDI_1_0); +} + +std::unique_ptr MidiOutput::openDevice(const String& deviceIdentifier, + ump::PacketProtocol protocol) +{ + if (deviceIdentifier.isEmpty()) + return {}; + + using namespace CoreMidiHelpers; + + if (auto client = getGlobalMidiClient()) + { + for (auto& endpoint : getEndpoints(false)) + { + auto endpointInfo = getConnectedEndpointInfo(endpoint); + + if (deviceIdentifier != endpointInfo.identifier) + continue; + + CFObjectHolder cfName; + + if (!CHECK_ERROR(MIDIObjectGetStringProperty(endpoint, kMIDIPropertyName, &cfName.object))) + continue; + + MIDIPortRef port; + + if (!CHECK_ERROR(MIDIOutputPortCreate(client, cfName.object, &port))) + continue; + + ScopedPortRef scopedPort{port}; + + auto midiOutput = rawToUniquePtr(new MidiOutput(endpointInfo.name, endpointInfo.identifier, protocol)); + midiOutput->internal = std::make_unique(std::move(scopedPort), + ScopedEndpointRef{endpoint}, + protocol); + + return midiOutput; + } + } + + return {}; +} + +std::unique_ptr MidiOutput::createNewDevice(const String& deviceName) +{ + return createNewDevice(deviceName, ump::PacketProtocol::MIDI_1_0); +} + +std::unique_ptr MidiOutput::createNewDevice(const String& deviceName, + ump::PacketProtocol protocol) +{ + using namespace CoreMidiHelpers; + + if (auto client = getGlobalMidiClient()) + { + MIDIEndpointRef endpoint; + + CFUniquePtr name(deviceName.toCFString()); + + auto err = CreatorFunctionsToUse::createSource(protocol, client, name.get(), &endpoint); + ScopedEndpointRef scopedEndpoint{endpoint}; #if YUP_IOS if (err == kMIDINotPermitted) @@ -1413,13 +1412,13 @@ static CreatorFunctionPointers getCreatorFunctionPointers() if (!CHECK_ERROR(MIDIObjectSetIntegerProperty(*scopedEndpoint, kMIDIPropertyUniqueID, (SInt32)deviceIdentifier))) return {}; - auto midiOutput = rawToUniquePtr(new MidiOutput(deviceName, String(deviceIdentifier), protocol)); - midiOutput->internal = std::make_unique(ScopedPortRef{}, - std::move(scopedEndpoint), - protocol); - - return midiOutput; - } + auto midiOutput = rawToUniquePtr(new MidiOutput(deviceName, String(deviceIdentifier), protocol)); + midiOutput->internal = std::make_unique(ScopedPortRef{}, + std::move(scopedEndpoint), + protocol); + + return midiOutput; + } return {}; } @@ -1429,22 +1428,22 @@ static CreatorFunctionPointers getCreatorFunctionPointers() stopBackgroundThread(); } -void MidiOutput::sendMessageNow(const MidiMessage& message) -{ - internal->send(ump::BytestreamMidiView(&message)); -} - -void MidiOutput::sendMessageNow(const ump::View& message) -{ - auto begin = ump::Iterator (message.data(), message.size() * sizeof (uint32_t)); - auto end = ump::Iterator (message.data() + message.size(), 0); - internal->send (begin, end); -} - -void MidiOutput::sendMessageNow (const ump::Packets& packets) -{ - internal->send (packets.cbegin(), packets.cend()); -} +void MidiOutput::sendMessageNow(const MidiMessage& message) +{ + internal->send(ump::BytestreamMidiView(&message)); +} + +void MidiOutput::sendMessageNow(const ump::View& message) +{ + auto begin = ump::Iterator (message.data(), message.size() * sizeof (uint32_t)); + auto end = ump::Iterator (message.data() + message.size(), 0); + internal->send (begin, end); +} + +void MidiOutput::sendMessageNow (const ump::Packets& packets) +{ + internal->send (packets.cbegin(), packets.cend()); +} MidiDeviceListConnection MidiDeviceListConnection::make(std::function cb) { diff --git a/modules/yup_audio_devices/native/yup_Midi_linux.cpp b/modules/yup_audio_devices/native/yup_Midi_linux.cpp index 7621b28b..fd5e0495 100644 --- a/modules/yup_audio_devices/native/yup_Midi_linux.cpp +++ b/modules/yup_audio_devices/native/yup_Midi_linux.cpp @@ -1,851 +1,1578 @@ -/* - ============================================================================== - - This file is part of the YUP library. - Copyright (c) 2024 - kunitoki@gmail.com - - YUP is an open source library subject to open-source licensing. - - The code included in this file is provided under the terms of the ISC license - http://www.isc.org/downloads/software-support-policy/isc-license. Permission - to use, copy, modify, and/or distribute this software for any purpose with or - without fee is hereby granted provided that the above copyright notice and - this permission notice appear in all copies. - - YUP IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2022 - Raw Material Software Limited - - JUCE is an open source library subject to commercial or open-source - licensing. - - The code included in this file is provided under the terms of the ISC license - http://www.isc.org/downloads/software-support-policy/isc-license. Permission - To use, copy, modify, and/or distribute this software for any purpose with or - without fee is hereby granted provided that the above copyright notice and - this permission notice appear in all copies. - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -namespace yup -{ - -#if YUP_ALSA - -//============================================================================== -class AlsaClient -{ - auto lowerBound (int portId) const - { - const auto comparator = [] (const auto& port, const auto& id) - { - return port->getPortId() < id; - }; - return std::lower_bound (ports.begin(), ports.end(), portId, comparator); - } - - auto findPortIterator (int portId) const - { - const auto iter = lowerBound (portId); - return (iter == ports.end() || (*iter)->getPortId() != portId) ? ports.end() : iter; - } - -public: - ~AlsaClient() - { - inputThread.reset(); - - jassert (activeCallbacks.get() == 0); - - if (handle != nullptr) - { - snd_seq_delete_simple_port (handle, announcementsIn); - snd_seq_close (handle); - } - } - - static String getAlsaMidiName() - { -#ifdef YUP_ALSA_MIDI_NAME - return YUP_ALSA_MIDI_NAME; -#else - if (auto* app = YUPApplicationBase::getInstance()) - return app->getApplicationName(); - - return "YUP"; -#endif - } - - //============================================================================== - // represents an input or output port of the supplied AlsaClient - struct Port - { - explicit Port (bool forInput) noexcept - : isInput (forInput) - { - } - - ~Port() - { - if (isValid()) - { - if (isInput) - enableCallback (false); - else - snd_midi_event_free (midiParser); - - snd_seq_delete_simple_port (client->get(), portId); - } - } - - void connectWith (int sourceClient, int sourcePort) const noexcept - { - if (isInput) - snd_seq_connect_from (client->get(), portId, sourceClient, sourcePort); - else - snd_seq_connect_to (client->get(), portId, sourceClient, sourcePort); - } - - bool isValid() const noexcept - { - return client->get() != nullptr && portId >= 0; - } - - void setupInput (MidiInput* input, MidiInputCallback* cb) - { - jassert (cb != nullptr && input != nullptr); - callback = cb; - midiInput = input; - } - - void setupOutput() - { - jassert (! isInput); - snd_midi_event_new ((size_t) maxEventSize, &midiParser); - } - - void enableCallback (bool enable) - { - callbackEnabled = enable; - } - - bool sendMessageNow (const MidiMessage& message) - { - if (message.getRawDataSize() > maxEventSize) - { - maxEventSize = message.getRawDataSize(); - snd_midi_event_free (midiParser); - snd_midi_event_new ((size_t) maxEventSize, &midiParser); - } - - snd_seq_event_t event; - snd_seq_ev_clear (&event); - - auto numBytes = (long) message.getRawDataSize(); - auto* data = message.getRawData(); - - auto seqHandle = client->get(); - bool success = true; - - while (numBytes > 0) - { - auto numSent = snd_midi_event_encode (midiParser, data, numBytes, &event); - - if (numSent <= 0) - { - success = numSent == 0; - break; - } - - numBytes -= numSent; - data += numSent; - - snd_seq_ev_set_source (&event, (unsigned char) portId); - snd_seq_ev_set_subs (&event); - snd_seq_ev_set_direct (&event); - - if (snd_seq_event_output_direct (seqHandle, &event) < 0) - { - success = false; - break; - } - } - - snd_midi_event_reset_encode (midiParser); - return success; - } - - bool operator== (const Port& lhs) const noexcept - { - return portId != -1 && portId == lhs.portId; - } - - void createPort (const String& name, bool enableSubscription) - { - if (auto seqHandle = client->get()) - { - const unsigned int caps = - isInput ? (SND_SEQ_PORT_CAP_WRITE | (enableSubscription ? SND_SEQ_PORT_CAP_SUBS_WRITE : 0)) - : (SND_SEQ_PORT_CAP_READ | (enableSubscription ? SND_SEQ_PORT_CAP_SUBS_READ : 0)); - - portName = name; - portId = snd_seq_create_simple_port (seqHandle, portName.toUTF8(), caps, SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION); - } - } - - void handleIncomingMidiMessage (const MidiMessage& message) const - { - if (callbackEnabled) - callback->handleIncomingMidiMessage (midiInput, message); - } - - void handlePartialSysexMessage (const uint8* messageData, int numBytesSoFar, double timeStamp) - { - if (callbackEnabled) - callback->handlePartialSysexMessage (midiInput, messageData, numBytesSoFar, timeStamp); - } - - int getPortId() const { return portId; } - - const String& getPortName() const { return portName; } - - private: - const std::shared_ptr client = AlsaClient::getInstance(); - - MidiInputCallback* callback = nullptr; - snd_midi_event_t* midiParser = nullptr; - MidiInput* midiInput = nullptr; - - String portName; - - int maxEventSize = 4096, portId = -1; - std::atomic callbackEnabled { false }; - bool isInput = false; - }; - - static std::shared_ptr getInstance() - { - static std::weak_ptr ptr; - - if (auto locked = ptr.lock()) - return locked; - - std::shared_ptr result (new AlsaClient()); - ptr = result; - return result; - } - - void handleIncomingMidiMessage (snd_seq_event* event, const MidiMessage& message) - { - const ScopedLock sl (callbackLock); - - if (auto* port = findPort (event->dest.port)) - port->handleIncomingMidiMessage (message); - } - - void handlePartialSysexMessage (snd_seq_event* event, const uint8* messageData, int numBytesSoFar, double timeStamp) - { - const ScopedLock sl (callbackLock); - - if (auto* port = findPort (event->dest.port)) - port->handlePartialSysexMessage (messageData, numBytesSoFar, timeStamp); - } - - snd_seq_t* get() const noexcept { return handle; } - - int getId() const noexcept { return clientId; } - - Port* createPort (const String& name, bool forInput, bool enableSubscription) - { - const ScopedLock sl (callbackLock); - - auto port = new Port (forInput); - port->createPort (name, enableSubscription); - - const auto iter = lowerBound (port->getPortId()); - jassert (iter == ports.end() || port->getPortId() < (*iter)->getPortId()); - ports.insert (iter, rawToUniquePtr (port)); - - return port; - } - - void deletePort (Port* port) - { - const ScopedLock sl (callbackLock); - - if (const auto iter = findPortIterator (port->getPortId()); iter != ports.end()) - ports.erase (iter); - } - -private: - AlsaClient() - { - snd_seq_open (&handle, "default", SND_SEQ_OPEN_DUPLEX, 0); - - if (handle != nullptr) - { - snd_seq_nonblock (handle, SND_SEQ_NONBLOCK); - snd_seq_set_client_name (handle, getAlsaMidiName().toRawUTF8()); - clientId = snd_seq_client_id (handle); - - // It's good idea to pre-allocate a good number of elements - ports.reserve (32); - - announcementsIn = snd_seq_create_simple_port (handle, - TRANS ("announcements").toRawUTF8(), - SND_SEQ_PORT_CAP_WRITE, - SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION); - snd_seq_connect_from (handle, announcementsIn, SND_SEQ_CLIENT_SYSTEM, SND_SEQ_PORT_SYSTEM_ANNOUNCE); - - inputThread.emplace (*this); - } - } - - Port* findPort (int portId) - { - if (const auto iter = findPortIterator (portId); iter != ports.end()) - return iter->get(); - - return nullptr; - } - - snd_seq_t* handle = nullptr; - int clientId = 0; - int announcementsIn = 0; - std::vector> ports; - Atomic activeCallbacks; - CriticalSection callbackLock; - - //============================================================================== - class SequencerThread - { - public: - explicit SequencerThread (AlsaClient& c) - : client (c) - { - } - - ~SequencerThread() noexcept - { - shouldStop = true; - thread.join(); - } - - private: - // If we directly call MidiDeviceListConnectionBroadcaster::get() from the background thread, - // there's a possibility that we'll deadlock in the following scenario: - // - The main thread calls MidiDeviceListConnectionBroadcaster::get() for the first time - // (e.g. to register a listener). The static MidiDeviceListConnectionBroadcaster singleton - // begins construction. During the constructor, an AlsaClient is created to iterate midi - // ins/outs. - // - The AlsaClient starts a new SequencerThread. If connections are updated, the - // SequencerThread may call MidiDeviceListConnectionBroadcaster::get().notify() - // while the MidiDeviceListConnectionBroadcaster singleton is still being created. - // - The SequencerThread blocks until the MidiDeviceListConnectionBroadcaster has been - // created on the main thread, but the MidiDeviceListConnectionBroadcaster's constructor - // can't complete until the AlsaClient's destructor has run, which in turn requires the - // SequencerThread to join. - class UpdateNotifier final : private AsyncUpdater - { - public: - ~UpdateNotifier() override { cancelPendingUpdate(); } - - using AsyncUpdater::triggerAsyncUpdate; - - private: - void handleAsyncUpdate() override { MidiDeviceListConnectionBroadcaster::get().notify(); } - }; - - AlsaClient& client; - MidiDataConcatenator concatenator { 2048 }; - std::atomic shouldStop { false }; - UpdateNotifier notifier; - std::thread thread { [this] - { - Thread::setCurrentThreadName ("YUP MIDI Input"); - - auto seqHandle = client.get(); - - const int maxEventSize = 16 * 1024; - snd_midi_event_t* midiParser; - - if (snd_midi_event_new (maxEventSize, &midiParser) >= 0) - { - const ScopeGuard freeMidiEvent { [&] - { - snd_midi_event_free (midiParser); - } }; - - const auto numPfds = snd_seq_poll_descriptors_count (seqHandle, POLLIN); - std::vector pfd (static_cast (numPfds)); - snd_seq_poll_descriptors (seqHandle, pfd.data(), (unsigned int) numPfds, POLLIN); - - std::vector buffer (maxEventSize); - - while (! shouldStop) - { - // This timeout shouldn't be too long, so that the program can exit in a timely manner - if (poll (pfd.data(), (nfds_t) numPfds, 100) > 0) - { - if (shouldStop) - break; - - do - { - snd_seq_event_t* inputEvent = nullptr; - - if (snd_seq_event_input (seqHandle, &inputEvent) >= 0) - { - const ScopeGuard freeInputEvent { [&] - { - snd_seq_free_event (inputEvent); - } }; - - constexpr int systemEvents[] { - SND_SEQ_EVENT_CLIENT_CHANGE, - SND_SEQ_EVENT_CLIENT_START, - SND_SEQ_EVENT_CLIENT_EXIT, - SND_SEQ_EVENT_PORT_CHANGE, - SND_SEQ_EVENT_PORT_START, - SND_SEQ_EVENT_PORT_EXIT, - SND_SEQ_EVENT_PORT_SUBSCRIBED, - SND_SEQ_EVENT_PORT_UNSUBSCRIBED, - }; - - const auto foundEvent = std::find (std::begin (systemEvents), - std::end (systemEvents), - inputEvent->type); - - if (foundEvent != std::end (systemEvents)) - { - notifier.triggerAsyncUpdate(); - continue; - } - - // xxx what about SYSEXes that are too big for the buffer? - const auto numBytes = snd_midi_event_decode (midiParser, - buffer.data(), - maxEventSize, - inputEvent); - - snd_midi_event_reset_decode (midiParser); - - concatenator.pushMidiData (buffer.data(), (int) numBytes, Time::getMillisecondCounter() * 0.001, inputEvent, client); - } - } while (snd_seq_event_input_pending (seqHandle, 0) > 0); - } - } - } - } }; - }; - - std::optional inputThread; -}; - -//============================================================================== -static String getFormattedPortIdentifier (int clientId, int portId) -{ - return String (clientId) + "-" + String (portId); -} - -static AlsaClient::Port* iterateMidiClient (AlsaClient& client, - snd_seq_client_info_t* clientInfo, - bool forInput, - Array& devices, - const String& deviceIdentifierToOpen) -{ - AlsaClient::Port* port = nullptr; - - auto seqHandle = client.get(); - snd_seq_port_info_t* portInfo = nullptr; - - snd_seq_port_info_alloca (&portInfo); - jassert (portInfo != nullptr); - auto numPorts = snd_seq_client_info_get_num_ports (clientInfo); - auto sourceClient = snd_seq_client_info_get_client (clientInfo); - - snd_seq_port_info_set_client (portInfo, sourceClient); - snd_seq_port_info_set_port (portInfo, -1); - - while (--numPorts >= 0) - { - if (snd_seq_query_next_port (seqHandle, portInfo) == 0 - && (snd_seq_port_info_get_capability (portInfo) - & (forInput ? SND_SEQ_PORT_CAP_SUBS_READ : SND_SEQ_PORT_CAP_SUBS_WRITE)) - != 0) - { - String portName (snd_seq_port_info_get_name (portInfo)); - auto portID = snd_seq_port_info_get_port (portInfo); - - MidiDeviceInfo device (portName, getFormattedPortIdentifier (sourceClient, portID)); - devices.add (device); - - if (deviceIdentifierToOpen.isNotEmpty() && deviceIdentifierToOpen == device.identifier) - { - if (portID != -1) - { - port = client.createPort (portName, forInput, false); - jassert (port->isValid()); - port->connectWith (sourceClient, portID); - break; - } - } - } - } - - return port; -} - -static AlsaClient::Port* iterateMidiDevices (bool forInput, - Array& devices, - const String& deviceIdentifierToOpen) -{ - AlsaClient::Port* port = nullptr; - auto client = AlsaClient::getInstance(); - - if (auto seqHandle = client->get()) - { - snd_seq_system_info_t* systemInfo = nullptr; - snd_seq_client_info_t* clientInfo = nullptr; - - snd_seq_system_info_alloca (&systemInfo); - jassert (systemInfo != nullptr); - - if (snd_seq_system_info (seqHandle, systemInfo) == 0) - { - snd_seq_client_info_alloca (&clientInfo); - jassert (clientInfo != nullptr); - - auto numClients = snd_seq_system_info_get_cur_clients (systemInfo); - - while (--numClients >= 0) - { - if (snd_seq_query_next_client (seqHandle, clientInfo) == 0) - { - port = iterateMidiClient (*client, - clientInfo, - forInput, - devices, - deviceIdentifierToOpen); - - if (port != nullptr) - break; - } - } - } - } - - return port; -} - -struct AlsaPortPtr -{ - explicit AlsaPortPtr (AlsaClient::Port* p) - : ptr (p) - { - } - - virtual ~AlsaPortPtr() noexcept { AlsaClient::getInstance()->deletePort (ptr); } - - AlsaClient::Port* ptr = nullptr; -}; - -//============================================================================== -class MidiInput::Pimpl final : public AlsaPortPtr -{ -public: - using AlsaPortPtr::AlsaPortPtr; -}; - -Array MidiInput::getAvailableDevices() -{ - Array devices; - iterateMidiDevices (true, devices, {}); - - return devices; -} - -MidiDeviceInfo MidiInput::getDefaultDevice() -{ - return getAvailableDevices().getFirst(); -} - -std::unique_ptr MidiInput::openDevice (const String& deviceIdentifier, MidiInputCallback* callback) -{ - if (deviceIdentifier.isEmpty()) - return {}; - - Array devices; - auto* port = iterateMidiDevices (true, devices, deviceIdentifier); - - if (port == nullptr || ! port->isValid()) - return {}; - - jassert (port->isValid()); - - std::unique_ptr midiInput (new MidiInput (port->getPortName(), - deviceIdentifier, - ump::PacketProtocol::MIDI_1_0)); - - port->setupInput (midiInput.get(), callback); - midiInput->internal = std::make_unique (port); - - return midiInput; -} - -std::unique_ptr MidiInput::openDevice (const String&, - ump::PacketProtocol, - ump::Receiver*) -{ - jassertfalse; - return {}; -} - -std::unique_ptr MidiInput::createNewDevice (const String& deviceName, MidiInputCallback* callback) -{ - auto client = AlsaClient::getInstance(); - auto* port = client->createPort (deviceName, true, true); - - if (port == nullptr || ! port->isValid()) - return {}; - - std::unique_ptr midiInput (new MidiInput (deviceName, - getFormattedPortIdentifier (client->getId(), port->getPortId()), - ump::PacketProtocol::MIDI_1_0)); - - port->setupInput (midiInput.get(), callback); - midiInput->internal = std::make_unique (port); - - return midiInput; -} - -std::unique_ptr MidiInput::createNewDevice (const String&, - ump::PacketProtocol, - ump::Receiver*) -{ - jassertfalse; - return {}; -} - -MidiInput::MidiInput (const String& deviceName, - const String& deviceIdentifier, - ump::PacketProtocol protocol) - : deviceInfo (deviceName, deviceIdentifier, protocol) -{ -} - -MidiInput::~MidiInput() -{ - stop(); -} - -void MidiInput::start() -{ - internal->ptr->enableCallback (true); -} - -void MidiInput::stop() -{ - internal->ptr->enableCallback (false); -} - -//============================================================================== -class MidiOutput::Pimpl final : public AlsaPortPtr -{ -public: - using AlsaPortPtr::AlsaPortPtr; -}; - -Array MidiOutput::getAvailableDevices() -{ - Array devices; - iterateMidiDevices (false, devices, {}); - - return devices; -} - -MidiDeviceInfo MidiOutput::getDefaultDevice() -{ - return getAvailableDevices().getFirst(); -} - -std::unique_ptr MidiOutput::openDevice (const String& deviceIdentifier) -{ - if (deviceIdentifier.isEmpty()) - return {}; - - Array devices; - auto* port = iterateMidiDevices (false, devices, deviceIdentifier); - - if (port == nullptr || ! port->isValid()) - return {}; - - std::unique_ptr midiOutput (new MidiOutput (port->getPortName(), - deviceIdentifier, - ump::PacketProtocol::MIDI_1_0)); - - port->setupOutput(); - midiOutput->internal = std::make_unique (port); - - return midiOutput; -} - -std::unique_ptr MidiOutput::openDevice (const String& deviceIdentifier, - ump::PacketProtocol protocol) -{ - if (protocol != ump::PacketProtocol::MIDI_1_0) - return {}; - - return openDevice (deviceIdentifier); -} - -std::unique_ptr MidiOutput::createNewDevice (const String& deviceName) -{ - auto client = AlsaClient::getInstance(); - auto* port = client->createPort (deviceName, false, true); - - if (port == nullptr || ! port->isValid()) - return {}; - - std::unique_ptr midiOutput (new MidiOutput (deviceName, - getFormattedPortIdentifier (client->getId(), port->getPortId()), - ump::PacketProtocol::MIDI_1_0)); - - port->setupOutput(); - midiOutput->internal = std::make_unique (port); - - return midiOutput; -} - -std::unique_ptr MidiOutput::createNewDevice (const String& deviceName, - ump::PacketProtocol protocol) -{ - if (protocol != ump::PacketProtocol::MIDI_1_0) - return {}; - - return createNewDevice (deviceName); -} - -MidiOutput::~MidiOutput() -{ - stopBackgroundThread(); -} - -void MidiOutput::sendMessageNow (const MidiMessage& message) -{ - internal->ptr->sendMessageNow (message); -} - -void MidiOutput::sendMessageNow (const ump::View& message) -{ - ump::ToBytestreamConverter converter { 2048 }; - converter.convert (message, 0.0, [&] (const MidiMessage& midiMessage) - { - sendMessageNow (midiMessage); - }); -} - -void MidiOutput::sendMessageNow (const ump::Packets& packets) -{ - for (auto it = packets.cbegin(); it != packets.cend(); ++it) - sendMessageNow (*it); -} - -MidiDeviceListConnection MidiDeviceListConnection::make (std::function cb) -{ - auto& broadcaster = MidiDeviceListConnectionBroadcaster::get(); - // We capture the AlsaClient instance here to ensure that it remains alive for at least as long - // as the MidiDeviceListConnection. This is necessary because system change messages will only - // be processed when the AlsaClient's SequencerThread is running. - return { &broadcaster, broadcaster.add ([fn = std::move (cb), client = AlsaClient::getInstance()] - { - NullCheckedInvocation::invoke (fn); - }) }; -} - -//============================================================================== -#else - -class MidiInput::Pimpl -{ -}; - -// (These are just stub functions if ALSA is unavailable...) -MidiInput::MidiInput (const String& deviceName, - const String& deviceID, - ump::PacketProtocol protocol) - : deviceInfo (deviceName, deviceID, protocol) -{ -} - -MidiInput::~MidiInput() {} - -void MidiInput::start() {} - -void MidiInput::stop() {} - -Array MidiInput::getAvailableDevices() { return {}; } - -MidiDeviceInfo MidiInput::getDefaultDevice() { return {}; } - -std::unique_ptr MidiInput::openDevice (const String&, MidiInputCallback*) { return {}; } - -std::unique_ptr MidiInput::openDevice (const String&, - ump::PacketProtocol, - ump::Receiver*) -{ - return {}; -} - -std::unique_ptr MidiInput::createNewDevice (const String&, MidiInputCallback*) { return {}; } - -std::unique_ptr MidiInput::createNewDevice (const String&, - ump::PacketProtocol, - ump::Receiver*) -{ - return {}; -} - -class MidiOutput::Pimpl -{ -}; - -MidiOutput::~MidiOutput() {} - -void MidiOutput::sendMessageNow (const MidiMessage&) {} - -void MidiOutput::sendMessageNow (const ump::View&) {} - -void MidiOutput::sendMessageNow (const ump::Packets&) {} - -Array MidiOutput::getAvailableDevices() { return {}; } - -MidiDeviceInfo MidiOutput::getDefaultDevice() { return {}; } - -std::unique_ptr MidiOutput::openDevice (const String&) { return {}; } - -std::unique_ptr MidiOutput::openDevice (const String&, ump::PacketProtocol) { return {}; } - -std::unique_ptr MidiOutput::createNewDevice (const String&) { return {}; } - -std::unique_ptr MidiOutput::createNewDevice (const String&, ump::PacketProtocol) { return {}; } - -MidiDeviceListConnection MidiDeviceListConnection::make (std::function cb) -{ - auto& broadcaster = MidiDeviceListConnectionBroadcaster::get(); - return { &broadcaster, broadcaster.add (std::move (cb)) }; -} - -#endif - -} // namespace yup +/* + ============================================================================== + + This file is part of the YUP library. + Copyright (c) 2024 - kunitoki@gmail.com + + YUP is an open source library subject to open-source licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + to use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + YUP IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2022 - Raw Material Software Limited + + JUCE is an open source library subject to commercial or open-source + licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace yup +{ + +#if YUP_ALSA + +namespace +{ +constexpr const char* umpRawmidiPrefix = "ump-rawmidi:"; +constexpr const char* umpSequencerPrefix = "ump-seq:"; + +bool isUmpRawmidiIdentifier (const String& identifier) +{ + return identifier.startsWith (umpRawmidiPrefix); +} + +bool isUmpSequencerIdentifier (const String& identifier) +{ + return identifier.startsWith (umpSequencerPrefix); +} + +String getUmpRawmidiPath (const String& identifier) +{ + return identifier.fromFirstOccurrenceOf (umpRawmidiPrefix, false, false); +} + +String getUmpRawmidiIdentifier (const String& path) +{ + return String (umpRawmidiPrefix) + path; +} + +String getUmpSequencerIdentifier (const String& identifier) +{ + return String (umpSequencerPrefix) + identifier; +} + +Array findUmpRawmidiNodes() +{ + Array results; + File ("/dev/snd").findChildFiles (results, File::findFiles, false, "umpC*D*"); + return results; +} + +bool parseUmpRawmidiPath (const String& path, int& card, int& device) +{ + const auto name = File (path).getFileName(); + if (! name.startsWith ("umpC")) + return false; + + const auto dIndex = name.indexOfChar ('D'); + if (dIndex <= 4) + return false; + + const auto cardString = name.substring (4, dIndex); + const auto deviceString = name.substring (dIndex + 1); + + if (! cardString.containsOnly ("0123456789") || ! deviceString.containsOnly ("0123456789")) + return false; + + card = cardString.getIntValue(); + device = deviceString.getIntValue(); + return true; +} + +String getUmpAlsaDeviceName (const String& path) +{ + int card = 0; + int device = 0; + if (! parseUmpRawmidiPath (path, card, device)) + return {}; + + return "hw:" + String (card) + "," + String (device); +} +} // namespace + +//============================================================================== +class AlsaClient +{ + auto lowerBound (int portId) const + { + const auto comparator = [] (const auto& port, const auto& id) + { + return port->getPortId() < id; + }; + return std::lower_bound (ports.begin(), ports.end(), portId, comparator); + } + + auto findPortIterator (int portId) const + { + const auto iter = lowerBound (portId); + return (iter == ports.end() || (*iter)->getPortId() != portId) ? ports.end() : iter; + } + +public: + ~AlsaClient() + { + inputThread.reset(); + + jassert (activeCallbacks.get() == 0); + + if (handle != nullptr) + { + snd_seq_delete_simple_port (handle, announcementsIn); + snd_seq_close (handle); + } + } + + static String getAlsaMidiName() + { +#ifdef YUP_ALSA_MIDI_NAME + return YUP_ALSA_MIDI_NAME; +#else + if (auto* app = YUPApplicationBase::getInstance()) + return app->getApplicationName(); + + return "YUP"; +#endif + } + + //============================================================================== + // represents an input or output port of the supplied AlsaClient + struct Port + { + explicit Port (bool forInput) noexcept + : isInput (forInput) + { + } + + ~Port() + { + if (isValid()) + { + if (isInput) + enableCallback (false); + else + snd_midi_event_free (midiParser); + + snd_seq_delete_simple_port (client->get(), portId); + } + } + + void connectWith (int sourceClient, int sourcePort) const noexcept + { + if (isInput) + snd_seq_connect_from (client->get(), portId, sourceClient, sourcePort); + else + snd_seq_connect_to (client->get(), portId, sourceClient, sourcePort); + } + + bool isValid() const noexcept + { + return client->get() != nullptr && portId >= 0; + } + + void setupInput (MidiInput* input, MidiInputCallback* cb) + { + jassert (cb != nullptr && input != nullptr); + callback = cb; + midiInput = input; + umpReceiver = nullptr; + umpConverter.reset(); + umpToBytestream = std::make_unique (2048); + } + + void setupInputUMP (MidiInput* input, ump::Receiver* receiverIn, ump::PacketProtocol protocolIn) + { + jassert (receiverIn != nullptr && input != nullptr); + callback = nullptr; + midiInput = input; + umpReceiver = receiverIn; + umpProtocol = protocolIn; + umpConverter = std::make_unique (protocolIn); + } + + void setupOutput() + { + jassert (! isInput); + snd_midi_event_new ((size_t) maxEventSize, &midiParser); + } + + void enableCallback (bool enable) + { + callbackEnabled = enable; + } + + bool sendMessageNow (const MidiMessage& message) + { + if (message.getRawDataSize() > maxEventSize) + { + maxEventSize = message.getRawDataSize(); + snd_midi_event_free (midiParser); + snd_midi_event_new ((size_t) maxEventSize, &midiParser); + } + + snd_seq_event_t event; + snd_seq_ev_clear (&event); + + auto numBytes = (long) message.getRawDataSize(); + auto* data = message.getRawData(); + + auto seqHandle = client->get(); + bool success = true; + + while (numBytes > 0) + { + auto numSent = snd_midi_event_encode (midiParser, data, numBytes, &event); + + if (numSent <= 0) + { + success = numSent == 0; + break; + } + + numBytes -= numSent; + data += numSent; + + snd_seq_ev_set_source (&event, (unsigned char) portId); + snd_seq_ev_set_subs (&event); + snd_seq_ev_set_direct (&event); + + if (snd_seq_event_output_direct (seqHandle, &event) < 0) + { + success = false; + break; + } + } + + snd_midi_event_reset_encode (midiParser); + return success; + } + + bool sendUmpMessageNow (const uint32_t* words, uint32_t numWords) + { +#if defined(SND_SEQ_EVENT_UMP) + if (words == nullptr || numWords == 0 || numWords > 4) + return false; + + snd_seq_event_t event; + snd_seq_ev_clear (&event); + event.type = 0; + event.flags |= SND_SEQ_EVENT_UMP; + + std::array payload {}; + std::memcpy (payload.data(), words, sizeof (uint32_t) * numWords); + snd_seq_ev_set_variable (&event, (unsigned int) (numWords * sizeof (uint32_t)), payload.data()); + + snd_seq_ev_set_source (&event, (unsigned char) portId); + snd_seq_ev_set_subs (&event); + snd_seq_ev_set_direct (&event); + + return snd_seq_event_output_direct (client->get(), &event) >= 0; +#else + (void) words; + (void) numWords; + return false; +#endif + } + + bool operator== (const Port& lhs) const noexcept + { + return portId != -1 && portId == lhs.portId; + } + + void createPort (const String& name, bool enableSubscription) + { + if (auto seqHandle = client->get()) + { + const unsigned int caps = + isInput ? (SND_SEQ_PORT_CAP_WRITE | (enableSubscription ? SND_SEQ_PORT_CAP_SUBS_WRITE : 0)) + : (SND_SEQ_PORT_CAP_READ | (enableSubscription ? SND_SEQ_PORT_CAP_SUBS_READ : 0)); + + portName = name; + portId = snd_seq_create_simple_port (seqHandle, portName.toUTF8(), caps, SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION); + } + } + + void handleIncomingMidiMessage (const MidiMessage& message) const + { + if (! callbackEnabled) + return; + + if (callback != nullptr) + { + callback->handleIncomingMidiMessage (midiInput, message); + return; + } + + if (umpReceiver != nullptr && umpConverter != nullptr) + { + const auto timestamp = message.getTimeStamp(); + umpConverter->convert (ump::BytestreamMidiView (&message), [&] (const ump::View& view) + { + umpReceiver->packetReceived (view, timestamp); + }); + } + } + + void handlePartialSysexMessage (const uint8* messageData, int numBytesSoFar, double timeStamp) + { + if (callbackEnabled) + callback->handlePartialSysexMessage (midiInput, messageData, numBytesSoFar, timeStamp); + } + + void handleIncomingUmpPacket (const uint32_t* words, uint32_t numWords, double timeStamp) const + { + if (! callbackEnabled || words == nullptr || numWords == 0) + return; + + const ump::View view (words); + + if (umpReceiver != nullptr) + { + umpReceiver->packetReceived (view, timeStamp); + return; + } + + if (callback != nullptr && umpToBytestream != nullptr) + { + umpToBytestream->convert (view, timeStamp, [&] (const MidiMessage& message) + { + callback->handleIncomingMidiMessage (midiInput, message); + }); + } + } + + int getPortId() const { return portId; } + + const String& getPortName() const { return portName; } + + private: + const std::shared_ptr client = AlsaClient::getInstance(); + + MidiInputCallback* callback = nullptr; + snd_midi_event_t* midiParser = nullptr; + MidiInput* midiInput = nullptr; + ump::Receiver* umpReceiver = nullptr; + ump::PacketProtocol umpProtocol = ump::PacketProtocol::MIDI_1_0; + std::unique_ptr umpConverter; + std::unique_ptr umpToBytestream; + + String portName; + + int maxEventSize = 4096, portId = -1; + std::atomic callbackEnabled { false }; + bool isInput = false; + }; + + static std::shared_ptr getInstance() + { + static std::weak_ptr ptr; + + if (auto locked = ptr.lock()) + return locked; + + std::shared_ptr result (new AlsaClient()); + ptr = result; + return result; + } + + void handleIncomingMidiMessage (snd_seq_event* event, const MidiMessage& message) + { + const ScopedLock sl (callbackLock); + + if (auto* port = findPort (event->dest.port)) + port->handleIncomingMidiMessage (message); + } + + void handlePartialSysexMessage (snd_seq_event* event, const uint8* messageData, int numBytesSoFar, double timeStamp) + { + const ScopedLock sl (callbackLock); + + if (auto* port = findPort (event->dest.port)) + port->handlePartialSysexMessage (messageData, numBytesSoFar, timeStamp); + } + + snd_seq_t* get() const noexcept { return handle; } + + int getId() const noexcept { return clientId; } + + void configureForUmp() + { +#if defined(SND_SEQ_EVENT_UMP) + if (! umpConfigured && handle != nullptr) + { + snd_seq_set_client_midi_version (handle, 2); +#if defined(snd_seq_set_client_ump_conversion) + snd_seq_set_client_ump_conversion (handle, 1); +#endif + umpConfigured = true; + } +#endif + } + + Port* createPort (const String& name, bool forInput, bool enableSubscription) + { + const ScopedLock sl (callbackLock); + + auto port = new Port (forInput); + port->createPort (name, enableSubscription); + + const auto iter = lowerBound (port->getPortId()); + jassert (iter == ports.end() || port->getPortId() < (*iter)->getPortId()); + ports.insert (iter, rawToUniquePtr (port)); + + return port; + } + + void deletePort (Port* port) + { + const ScopedLock sl (callbackLock); + + if (const auto iter = findPortIterator (port->getPortId()); iter != ports.end()) + ports.erase (iter); + } + +private: + AlsaClient() + { + snd_seq_open (&handle, "default", SND_SEQ_OPEN_DUPLEX, 0); + + if (handle != nullptr) + { + snd_seq_nonblock (handle, SND_SEQ_NONBLOCK); + snd_seq_set_client_name (handle, getAlsaMidiName().toRawUTF8()); + clientId = snd_seq_client_id (handle); + + // It's good idea to pre-allocate a good number of elements + ports.reserve (32); + + announcementsIn = snd_seq_create_simple_port (handle, + TRANS ("announcements").toRawUTF8(), + SND_SEQ_PORT_CAP_WRITE, + SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION); + snd_seq_connect_from (handle, announcementsIn, SND_SEQ_CLIENT_SYSTEM, SND_SEQ_PORT_SYSTEM_ANNOUNCE); + + inputThread.emplace (*this); + } + } + + Port* findPort (int portId) + { + if (const auto iter = findPortIterator (portId); iter != ports.end()) + return iter->get(); + + return nullptr; + } + + snd_seq_t* handle = nullptr; + int clientId = 0; + int announcementsIn = 0; + bool umpConfigured = false; + std::vector> ports; + Atomic activeCallbacks; + CriticalSection callbackLock; + + //============================================================================== + class SequencerThread + { + public: + explicit SequencerThread (AlsaClient& c) + : client (c) + { + } + + ~SequencerThread() noexcept + { + shouldStop = true; + thread.join(); + } + + private: + // If we directly call MidiDeviceListConnectionBroadcaster::get() from the background thread, + // there's a possibility that we'll deadlock in the following scenario: + // - The main thread calls MidiDeviceListConnectionBroadcaster::get() for the first time + // (e.g. to register a listener). The static MidiDeviceListConnectionBroadcaster singleton + // begins construction. During the constructor, an AlsaClient is created to iterate midi + // ins/outs. + // - The AlsaClient starts a new SequencerThread. If connections are updated, the + // SequencerThread may call MidiDeviceListConnectionBroadcaster::get().notify() + // while the MidiDeviceListConnectionBroadcaster singleton is still being created. + // - The SequencerThread blocks until the MidiDeviceListConnectionBroadcaster has been + // created on the main thread, but the MidiDeviceListConnectionBroadcaster's constructor + // can't complete until the AlsaClient's destructor has run, which in turn requires the + // SequencerThread to join. + class UpdateNotifier final : private AsyncUpdater + { + public: + ~UpdateNotifier() override { cancelPendingUpdate(); } + + using AsyncUpdater::triggerAsyncUpdate; + + private: + void handleAsyncUpdate() override { MidiDeviceListConnectionBroadcaster::get().notify(); } + }; + + AlsaClient& client; + MidiDataConcatenator concatenator { 2048 }; + std::atomic shouldStop { false }; + UpdateNotifier notifier; + std::thread thread { [this] + { + Thread::setCurrentThreadName ("YUP MIDI Input"); + + auto seqHandle = client.get(); + + const int maxEventSize = 16 * 1024; + snd_midi_event_t* midiParser; + + if (snd_midi_event_new (maxEventSize, &midiParser) >= 0) + { + const ScopeGuard freeMidiEvent { [&] + { + snd_midi_event_free (midiParser); + } }; + + const auto numPfds = snd_seq_poll_descriptors_count (seqHandle, POLLIN); + std::vector pfd (static_cast (numPfds)); + snd_seq_poll_descriptors (seqHandle, pfd.data(), (unsigned int) numPfds, POLLIN); + + std::vector buffer (maxEventSize); + + while (! shouldStop) + { + // This timeout shouldn't be too long, so that the program can exit in a timely manner + if (poll (pfd.data(), (nfds_t) numPfds, 100) > 0) + { + if (shouldStop) + break; + + do + { + snd_seq_event_t* inputEvent = nullptr; + + if (snd_seq_event_input (seqHandle, &inputEvent) >= 0) + { + const ScopeGuard freeInputEvent { [&] + { + snd_seq_free_event (inputEvent); + } }; + + constexpr int systemEvents[] { + SND_SEQ_EVENT_CLIENT_CHANGE, + SND_SEQ_EVENT_CLIENT_START, + SND_SEQ_EVENT_CLIENT_EXIT, + SND_SEQ_EVENT_PORT_CHANGE, + SND_SEQ_EVENT_PORT_START, + SND_SEQ_EVENT_PORT_EXIT, + SND_SEQ_EVENT_PORT_SUBSCRIBED, + SND_SEQ_EVENT_PORT_UNSUBSCRIBED, + }; + + const auto foundEvent = std::find (std::begin (systemEvents), + std::end (systemEvents), + inputEvent->type); + + if (foundEvent != std::end (systemEvents)) + { + notifier.triggerAsyncUpdate(); + continue; + } + +#if defined(SND_SEQ_EVENT_UMP) + if ((inputEvent->flags & SND_SEQ_EVENT_UMP) != 0) + { + const auto timeStamp = Time::getMillisecondCounter() * 0.001; + const auto* dataPtr = static_cast (inputEvent->data.ext.ptr); + const auto dataLen = inputEvent->data.ext.len; + + if (dataPtr != nullptr && dataLen >= 4) + { + std::array words {}; + const auto bytesToCopy = jmin ((size_t) dataLen, sizeof (words)); + std::memcpy (words.data(), dataPtr, bytesToCopy); + + if (auto* port = client.findPort (inputEvent->dest.port)) + port->handleIncomingUmpPacket (words.data(), + static_cast (bytesToCopy / sizeof (uint32_t)), + timeStamp); + } + + continue; + } +#endif + + // xxx what about SYSEXes that are too big for the buffer? + const auto numBytes = snd_midi_event_decode (midiParser, + buffer.data(), + maxEventSize, + inputEvent); + + snd_midi_event_reset_decode (midiParser); + + concatenator.pushMidiData (buffer.data(), (int) numBytes, Time::getMillisecondCounter() * 0.001, inputEvent, client); + } + } while (snd_seq_event_input_pending (seqHandle, 0) > 0); + } + } + } + } }; + }; + + std::optional inputThread; +}; + +//============================================================================== +static String getFormattedPortIdentifier (int clientId, int portId) +{ + return String (clientId) + "-" + String (portId); +} + +static AlsaClient::Port* iterateMidiClient (AlsaClient& client, + snd_seq_client_info_t* clientInfo, + bool forInput, + Array& devices, + const String& deviceIdentifierToOpen) +{ + AlsaClient::Port* port = nullptr; + + auto seqHandle = client.get(); + snd_seq_port_info_t* portInfo = nullptr; + + snd_seq_port_info_alloca (&portInfo); + jassert (portInfo != nullptr); + auto numPorts = snd_seq_client_info_get_num_ports (clientInfo); + auto sourceClient = snd_seq_client_info_get_client (clientInfo); + + snd_seq_port_info_set_client (portInfo, sourceClient); + snd_seq_port_info_set_port (portInfo, -1); + + while (--numPorts >= 0) + { + if (snd_seq_query_next_port (seqHandle, portInfo) == 0 + && (snd_seq_port_info_get_capability (portInfo) + & (forInput ? SND_SEQ_PORT_CAP_SUBS_READ : SND_SEQ_PORT_CAP_SUBS_WRITE)) + != 0) + { + String portName (snd_seq_port_info_get_name (portInfo)); + auto portID = snd_seq_port_info_get_port (portInfo); + + const auto baseIdentifier = getFormattedPortIdentifier (sourceClient, portID); + MidiDeviceInfo device (portName, baseIdentifier); + devices.add (device); + +#if defined(SND_SEQ_PORT_TYPE_MIDI_UMP) + if ((snd_seq_port_info_get_type (portInfo) & SND_SEQ_PORT_TYPE_MIDI_UMP) != 0) + { + devices.add (MidiDeviceInfo { portName, + getUmpSequencerIdentifier (baseIdentifier), + ump::PacketProtocol::MIDI_2_0, + true }); + } +#endif + + if (deviceIdentifierToOpen.isNotEmpty() && deviceIdentifierToOpen == device.identifier) + { + if (portID != -1) + { + port = client.createPort (portName, forInput, false); + jassert (port->isValid()); + port->connectWith (sourceClient, portID); + break; + } + } + } + } + + return port; +} + +static AlsaClient::Port* iterateMidiDevices (bool forInput, + Array& devices, + const String& deviceIdentifierToOpen) +{ + AlsaClient::Port* port = nullptr; + auto client = AlsaClient::getInstance(); + + if (auto seqHandle = client->get()) + { + snd_seq_system_info_t* systemInfo = nullptr; + snd_seq_client_info_t* clientInfo = nullptr; + + snd_seq_system_info_alloca (&systemInfo); + jassert (systemInfo != nullptr); + + if (snd_seq_system_info (seqHandle, systemInfo) == 0) + { + snd_seq_client_info_alloca (&clientInfo); + jassert (clientInfo != nullptr); + + auto numClients = snd_seq_system_info_get_cur_clients (systemInfo); + + while (--numClients >= 0) + { + if (snd_seq_query_next_client (seqHandle, clientInfo) == 0) + { + port = iterateMidiClient (*client, + clientInfo, + forInput, + devices, + deviceIdentifierToOpen); + + if (port != nullptr) + break; + } + } + } + } + + return port; +} + +struct AlsaPortPtr +{ + explicit AlsaPortPtr (AlsaClient::Port* p) + : ptr (p) + { + } + + virtual ~AlsaPortPtr() noexcept { AlsaClient::getInstance()->deletePort (ptr); } + + AlsaClient::Port* ptr = nullptr; +}; + +//============================================================================== +class MidiInput::Pimpl +{ +public: + virtual ~Pimpl() = default; + virtual void start() = 0; + virtual void stop() = 0; +}; + +class SequencerInputPimpl final : public MidiInput::Pimpl + , public AlsaPortPtr +{ +public: + explicit SequencerInputPimpl (AlsaClient::Port* p, std::unique_ptr cb) + : AlsaPortPtr (p) + , callback (std::move (cb)) + { + } + + void start() override + { + ptr->enableCallback (true); + } + + void stop() override + { + ptr->enableCallback (false); + } + +private: + std::unique_ptr callback; +}; + +class UmpRawmidiInputPimpl final : public MidiInput::Pimpl +{ +public: + UmpRawmidiInputPimpl (int fdIn, std::unique_ptr handlerIn) + : fd (fdIn) + , handler (std::move (handlerIn)) + { + startThread(); + } + +#if YUP_HAS_ALSA_UMP + UmpRawmidiInputPimpl (snd_ump_t* handleIn, std::unique_ptr handlerIn) + : umpHandle (handleIn) + , handler (std::move (handlerIn)) + { + startThread(); + } +#endif + + ~UmpRawmidiInputPimpl() override + { + shouldStop = true; + if (thread.joinable()) + thread.join(); + + if (fd >= 0) + ::close (fd); +#if YUP_HAS_ALSA_UMP + if (umpHandle != nullptr) + snd_ump_close (umpHandle); +#endif + } + + void start() override + { + callbackEnabled = true; + handler->reset(); + } + + void stop() override + { + callbackEnabled = false; + } + +private: + void startThread() + { + thread = std::thread ([this] + { + Thread::setCurrentThreadName ("YUP UMP RawMIDI Input"); + + std::array buffer {}; + +#if YUP_HAS_ALSA_UMP + if (umpHandle != nullptr) + { + const auto numPfds = snd_ump_poll_descriptors_count (umpHandle); + std::vector pfds ((size_t) numPfds); + snd_ump_poll_descriptors (umpHandle, pfds.data(), (unsigned int) numPfds); + + while (! shouldStop) + { + if (poll (pfds.data(), (nfds_t) pfds.size(), 100) <= 0) + continue; + + unsigned short revents = 0; + snd_ump_poll_descriptors_revents (umpHandle, pfds.data(), (unsigned int) pfds.size(), &revents); + if ((revents & POLLIN) == 0) + continue; + + const auto bytesRead = snd_ump_read (umpHandle, buffer.data(), buffer.size() * sizeof (uint32_t)); + if (bytesRead <= 0) + continue; + + const auto words = static_cast (bytesRead / sizeof (uint32_t)); + if (words == 0 || ! callbackEnabled) + continue; + + const auto now = Time::getMillisecondCounterHiRes() * 0.001; + handler->pushMidiData (buffer.data(), buffer.data() + words, now); + } + + return; + } +#endif + + pollfd pfd { fd, POLLIN, 0 }; + + while (! shouldStop) + { + if (poll (&pfd, 1, 100) <= 0) + continue; + + if ((pfd.revents & POLLIN) == 0) + continue; + + const auto bytesRead = ::read (fd, buffer.data(), buffer.size() * sizeof (uint32_t)); + if (bytesRead <= 0) + continue; + + const auto words = static_cast (bytesRead / sizeof (uint32_t)); + if (words == 0 || ! callbackEnabled) + continue; + + const auto now = Time::getMillisecondCounterHiRes() * 0.001; + handler->pushMidiData (buffer.data(), buffer.data() + words, now); + } + }); + } + + int fd = -1; +#if YUP_HAS_ALSA_UMP + snd_ump_t* umpHandle = nullptr; +#endif + std::unique_ptr handler; + std::atomic shouldStop { false }; + std::atomic callbackEnabled { false }; + std::thread thread; +}; + +class UmpReceiverCallback final : public MidiInputCallback +{ +public: + UmpReceiverCallback (ump::PacketProtocol protocolIn, ump::Receiver& receiverIn) + : receiver (receiverIn) + , converter (protocolIn) + { + } + + void handleIncomingMidiMessage (MidiInput*, const MidiMessage& message) override + { + const auto timestamp = message.getTimeStamp(); + converter.convert (ump::BytestreamMidiView (&message), [&] (const ump::View& view) + { + receiver.packetReceived (view, timestamp); + }); + } + + void handlePartialSysexMessage (MidiInput*, const uint8*, int, double) override {} + +private: + ump::Receiver& receiver; + ump::GenericUMPConverter converter; +}; + +Array MidiInput::getAvailableDevices() +{ + Array devices; + for (const auto& node : findUmpRawmidiNodes()) + { + const auto path = node.getFullPathName(); + devices.add (MidiDeviceInfo { "UMP " + node.getFileName(), + getUmpRawmidiIdentifier (path), + ump::PacketProtocol::MIDI_2_0, + true }); + } + iterateMidiDevices (true, devices, {}); + + return devices; +} + +MidiDeviceInfo MidiInput::getDefaultDevice() +{ + return getAvailableDevices().getFirst(); +} + +std::unique_ptr MidiInput::openDevice (const String& deviceIdentifier, MidiInputCallback* callback) +{ + if (deviceIdentifier.isEmpty()) + return {}; + + String identifierToOpen = deviceIdentifier; + if (isUmpSequencerIdentifier (identifierToOpen)) + identifierToOpen = identifierToOpen.fromFirstOccurrenceOf (umpSequencerPrefix, false, false); + + if (isUmpRawmidiIdentifier (identifierToOpen)) + { + if (callback == nullptr) + return {}; + + const auto path = getUmpRawmidiPath (identifierToOpen); + int fd = -1; +#if YUP_HAS_ALSA_UMP + snd_ump_t* umpHandle = nullptr; + const auto umpName = getUmpAlsaDeviceName (path); + if (umpName.isNotEmpty() + && snd_ump_open (&umpHandle, nullptr, umpName.toRawUTF8(), SND_RAWMIDI_NONBLOCK) >= 0) + { + snd_ump_nonblock (umpHandle, 1); + } + else + { + umpHandle = nullptr; + } +#endif + +#if YUP_HAS_ALSA_UMP + if (umpHandle == nullptr) + fd = ::open (path.toRawUTF8(), O_RDONLY | O_NONBLOCK); +#else + fd = ::open (path.toRawUTF8(), O_RDONLY | O_NONBLOCK); +#endif + + if (fd < 0 +#if YUP_HAS_ALSA_UMP + && umpHandle == nullptr +#endif + ) + return {}; + + std::unique_ptr midiInput (new MidiInput (File (path).getFileName(), + deviceIdentifier, + ump::PacketProtocol::MIDI_1_0)); + + auto handler = std::make_unique (*midiInput, *callback); +#if YUP_HAS_ALSA_UMP + if (umpHandle != nullptr) + midiInput->internal.reset (new UmpRawmidiInputPimpl (umpHandle, std::move (handler))); + else +#endif + midiInput->internal.reset (new UmpRawmidiInputPimpl (fd, std::move (handler))); + return midiInput; + } + + Array devices; + auto* port = iterateMidiDevices (true, devices, identifierToOpen); + + if (port == nullptr || ! port->isValid()) + return {}; + + jassert (port->isValid()); + + std::unique_ptr midiInput (new MidiInput (port->getPortName(), + deviceIdentifier, + ump::PacketProtocol::MIDI_1_0)); + + port->setupInput (midiInput.get(), callback); + midiInput->internal.reset (new SequencerInputPimpl (port, nullptr)); + + return midiInput; +} + +std::unique_ptr MidiInput::openDevice (const String& deviceIdentifier, + ump::PacketProtocol protocol, + ump::Receiver* receiver) +{ + if (deviceIdentifier.isEmpty() || receiver == nullptr) + return {}; + + String identifierToOpen = deviceIdentifier; + if (isUmpSequencerIdentifier (identifierToOpen)) + identifierToOpen = identifierToOpen.fromFirstOccurrenceOf (umpSequencerPrefix, false, false); + + if (isUmpRawmidiIdentifier (identifierToOpen)) + { + const auto path = getUmpRawmidiPath (identifierToOpen); + int fd = -1; +#if YUP_HAS_ALSA_UMP + snd_ump_t* umpHandle = nullptr; + const auto umpName = getUmpAlsaDeviceName (path); + if (umpName.isNotEmpty() + && snd_ump_open (&umpHandle, nullptr, umpName.toRawUTF8(), SND_RAWMIDI_NONBLOCK) >= 0) + { + snd_ump_nonblock (umpHandle, 1); + } + else + { + umpHandle = nullptr; + } +#endif + +#if YUP_HAS_ALSA_UMP + if (umpHandle == nullptr) + fd = ::open (path.toRawUTF8(), O_RDONLY | O_NONBLOCK); +#else + fd = ::open (path.toRawUTF8(), O_RDONLY | O_NONBLOCK); +#endif + + if (fd < 0 +#if YUP_HAS_ALSA_UMP + && umpHandle == nullptr +#endif + ) + return {}; + + std::unique_ptr midiInput (new MidiInput (File (path).getFileName(), + deviceIdentifier, + protocol)); + + auto handler = std::make_unique (protocol, *receiver); +#if YUP_HAS_ALSA_UMP + if (umpHandle != nullptr) + midiInput->internal.reset (new UmpRawmidiInputPimpl (umpHandle, std::move (handler))); + else +#endif + midiInput->internal.reset (new UmpRawmidiInputPimpl (fd, std::move (handler))); + return midiInput; + } + + Array devices; + auto* port = iterateMidiDevices (true, devices, identifierToOpen); + + if (port == nullptr || ! port->isValid()) + return {}; + + if (deviceIdentifier.startsWith (umpSequencerPrefix)) + AlsaClient::getInstance()->configureForUmp(); + + std::unique_ptr midiInput (new MidiInput (port->getPortName(), + deviceIdentifier, + protocol)); + + if (deviceIdentifier.startsWith (umpSequencerPrefix)) + { + port->setupInputUMP (midiInput.get(), receiver, protocol); + midiInput->internal.reset (new SequencerInputPimpl (port, nullptr)); + } + else + { + auto callback = std::make_unique (protocol, *receiver); + port->setupInput (midiInput.get(), callback.get()); + midiInput->internal.reset (new SequencerInputPimpl (port, std::move (callback))); + } + return midiInput; +} + +std::unique_ptr MidiInput::createNewDevice (const String& deviceName, MidiInputCallback* callback) +{ + auto client = AlsaClient::getInstance(); + auto* port = client->createPort (deviceName, true, true); + + if (port == nullptr || ! port->isValid()) + return {}; + + std::unique_ptr midiInput (new MidiInput (deviceName, + getFormattedPortIdentifier (client->getId(), port->getPortId()), + ump::PacketProtocol::MIDI_1_0)); + + port->setupInput (midiInput.get(), callback); + midiInput->internal.reset (new SequencerInputPimpl (port, nullptr)); + + return midiInput; +} + +std::unique_ptr MidiInput::createNewDevice (const String&, + ump::PacketProtocol, + ump::Receiver*) +{ + jassertfalse; + return {}; +} + +MidiInput::MidiInput (const String& deviceName, + const String& deviceIdentifier, + ump::PacketProtocol protocol) + : deviceInfo (deviceName, deviceIdentifier, protocol) +{ +} + +MidiInput::~MidiInput() +{ + stop(); +} + +void MidiInput::start() +{ + internal->start(); +} + +void MidiInput::stop() +{ + internal->stop(); +} + +//============================================================================== +class MidiOutput::Pimpl +{ +public: + virtual ~Pimpl() = default; + virtual void sendMessageNow (const MidiMessage& message) = 0; + virtual void sendMessageNow (const ump::View& message) = 0; +}; + +class SequencerOutputPimpl final : public AlsaPortPtr + , public MidiOutput::Pimpl +{ +public: + explicit SequencerOutputPimpl (AlsaClient::Port* p) + : AlsaPortPtr (p) + { + } + + void sendMessageNow (const MidiMessage& message) override + { + ptr->sendMessageNow (message); + } + + void sendMessageNow (const ump::View& message) override + { + ump::ToBytestreamConverter converter { 2048 }; + converter.convert (message, 0.0, [&] (const MidiMessage& midiMessage) + { + ptr->sendMessageNow (midiMessage); + }); + } +}; + +class UmpSequencerOutputPimpl final : public AlsaPortPtr + , public MidiOutput::Pimpl +{ +public: + explicit UmpSequencerOutputPimpl (AlsaClient::Port* p, ump::PacketProtocol protocolIn) + : AlsaPortPtr (p) + , converter (protocolIn) + { + } + + void sendMessageNow (const MidiMessage& message) override + { + converter.convert (ump::BytestreamMidiView (&message), [&] (const ump::View& view) + { + ptr->sendUmpMessageNow (view.data(), view.size()); + }); + } + + void sendMessageNow (const ump::View& message) override + { + ptr->sendUmpMessageNow (message.data(), message.size()); + } + +private: + ump::GenericUMPConverter converter; +}; + +class UmpRawmidiOutputPimpl final : public MidiOutput::Pimpl +{ +public: + UmpRawmidiOutputPimpl (int fdIn, ump::PacketProtocol protocolIn) + : fd (fdIn) + , converter (protocolIn) + { + } + +#if YUP_HAS_ALSA_UMP + UmpRawmidiOutputPimpl (snd_ump_t* handleIn, ump::PacketProtocol protocolIn) + : umpHandle (handleIn) + , converter (protocolIn) + { + } +#endif + + ~UmpRawmidiOutputPimpl() override + { + if (fd >= 0) + ::close (fd); +#if YUP_HAS_ALSA_UMP + if (umpHandle != nullptr) + snd_ump_close (umpHandle); +#endif + } + + void sendMessageNow (const MidiMessage& message) override + { + converter.convert (ump::BytestreamMidiView (&message), [&] (const ump::View& view) + { + writeWords (view.data(), view.size()); + }); + } + + void sendMessageNow (const ump::View& message) override + { + writeWords (message.data(), message.size()); + } + +private: + void writeWords (const uint32_t* data, uint32_t numWords) const + { + if (fd < 0 || data == nullptr || numWords == 0) + { +#if YUP_HAS_ALSA_UMP + if (umpHandle == nullptr) + return; +#else + return; +#endif + } + +#if YUP_HAS_ALSA_UMP + if (umpHandle != nullptr) + { + const auto totalBytes = static_cast (numWords) * sizeof (uint32_t); + snd_ump_write (umpHandle, data, totalBytes); + return; + } +#endif + + const auto totalBytes = static_cast (numWords) * sizeof (uint32_t); + const auto* bytes = reinterpret_cast (data); + size_t offset = 0; + + while (offset < totalBytes) + { + const auto written = ::write (fd, bytes + offset, totalBytes - offset); + if (written <= 0) + break; + + offset += static_cast (written); + } + } + + int fd = -1; +#if YUP_HAS_ALSA_UMP + snd_ump_t* umpHandle = nullptr; +#endif + ump::GenericUMPConverter converter; +}; + +Array MidiOutput::getAvailableDevices() +{ + Array devices; + for (const auto& node : findUmpRawmidiNodes()) + { + const auto path = node.getFullPathName(); + devices.add (MidiDeviceInfo { "UMP " + node.getFileName(), + getUmpRawmidiIdentifier (path), + ump::PacketProtocol::MIDI_2_0, + true }); + } + iterateMidiDevices (false, devices, {}); + + return devices; +} + +MidiDeviceInfo MidiOutput::getDefaultDevice() +{ + return getAvailableDevices().getFirst(); +} + +std::unique_ptr MidiOutput::openDevice (const String& deviceIdentifier) +{ + if (deviceIdentifier.isEmpty()) + return {}; + + if (isUmpRawmidiIdentifier (deviceIdentifier)) + return openDevice (deviceIdentifier, ump::PacketProtocol::MIDI_2_0); + if (isUmpSequencerIdentifier (deviceIdentifier)) + return openDevice (deviceIdentifier, ump::PacketProtocol::MIDI_2_0); + + Array devices; + auto* port = iterateMidiDevices (false, devices, deviceIdentifier); + + if (port == nullptr || ! port->isValid()) + return {}; + + std::unique_ptr midiOutput (new MidiOutput (port->getPortName(), + deviceIdentifier, + ump::PacketProtocol::MIDI_1_0)); + + port->setupOutput(); + midiOutput->internal.reset (new SequencerOutputPimpl (port)); + + return midiOutput; +} + +std::unique_ptr MidiOutput::openDevice (const String& deviceIdentifier, + ump::PacketProtocol protocol) +{ + if (deviceIdentifier.isEmpty()) + return {}; + + String identifierToOpen = deviceIdentifier; + if (isUmpSequencerIdentifier (identifierToOpen)) + identifierToOpen = identifierToOpen.fromFirstOccurrenceOf (umpSequencerPrefix, false, false); + + if (isUmpRawmidiIdentifier (identifierToOpen)) + { + const auto path = getUmpRawmidiPath (identifierToOpen); + int fd = -1; +#if YUP_HAS_ALSA_UMP + snd_ump_t* umpHandle = nullptr; + const auto umpName = getUmpAlsaDeviceName (path); + if (umpName.isNotEmpty() + && snd_ump_open (nullptr, &umpHandle, umpName.toRawUTF8(), SND_RAWMIDI_NONBLOCK) >= 0) + { + snd_ump_nonblock (umpHandle, 1); + } + else + { + umpHandle = nullptr; + } +#endif + +#if YUP_HAS_ALSA_UMP + if (umpHandle == nullptr) + fd = ::open (path.toRawUTF8(), O_WRONLY | O_NONBLOCK); +#else + fd = ::open (path.toRawUTF8(), O_WRONLY | O_NONBLOCK); +#endif + + if (fd < 0 +#if YUP_HAS_ALSA_UMP + && umpHandle == nullptr +#endif + ) + return {}; + + std::unique_ptr midiOutput (new MidiOutput (File (path).getFileName(), + deviceIdentifier, + protocol)); + +#if YUP_HAS_ALSA_UMP + if (umpHandle != nullptr) + midiOutput->internal.reset (new UmpRawmidiOutputPimpl (umpHandle, protocol)); + else +#endif + midiOutput->internal.reset (new UmpRawmidiOutputPimpl (fd, protocol)); + return midiOutput; + } + + if (deviceIdentifier.startsWith (umpSequencerPrefix)) + { +#if defined(SND_SEQ_EVENT_UMP) + Array devices; + auto* port = iterateMidiDevices (false, devices, identifierToOpen); + + if (port == nullptr || ! port->isValid()) + return {}; + + AlsaClient::getInstance()->configureForUmp(); + + std::unique_ptr midiOutput (new MidiOutput (port->getPortName(), + deviceIdentifier, + protocol)); + + port->setupOutput(); + midiOutput->internal.reset (new UmpSequencerOutputPimpl (port, protocol)); + return midiOutput; +#else + return {}; +#endif + } + + if (protocol != ump::PacketProtocol::MIDI_1_0) + return {}; + + return openDevice (deviceIdentifier); +} + +std::unique_ptr MidiOutput::createNewDevice (const String& deviceName) +{ + auto client = AlsaClient::getInstance(); + auto* port = client->createPort (deviceName, false, true); + + if (port == nullptr || ! port->isValid()) + return {}; + + std::unique_ptr midiOutput (new MidiOutput (deviceName, + getFormattedPortIdentifier (client->getId(), port->getPortId()), + ump::PacketProtocol::MIDI_1_0)); + + port->setupOutput(); + midiOutput->internal.reset (new SequencerOutputPimpl (port)); + + return midiOutput; +} + +std::unique_ptr MidiOutput::createNewDevice (const String& deviceName, + ump::PacketProtocol protocol) +{ + if (protocol != ump::PacketProtocol::MIDI_1_0) + return {}; + + return createNewDevice (deviceName); +} + +MidiOutput::~MidiOutput() +{ + stopBackgroundThread(); +} + +void MidiOutput::sendMessageNow (const MidiMessage& message) +{ + internal->sendMessageNow (message); +} + +void MidiOutput::sendMessageNow (const ump::View& message) +{ + internal->sendMessageNow (message); +} + +void MidiOutput::sendMessageNow (const ump::Packets& packets) +{ + for (auto it = packets.cbegin(); it != packets.cend(); ++it) + sendMessageNow (*it); +} + +MidiDeviceListConnection MidiDeviceListConnection::make (std::function cb) +{ + auto& broadcaster = MidiDeviceListConnectionBroadcaster::get(); + // We capture the AlsaClient instance here to ensure that it remains alive for at least as long + // as the MidiDeviceListConnection. This is necessary because system change messages will only + // be processed when the AlsaClient's SequencerThread is running. + return { &broadcaster, broadcaster.add ([fn = std::move (cb), client = AlsaClient::getInstance()] + { + NullCheckedInvocation::invoke (fn); + }) }; +} + +//============================================================================== +#else + +class MidiInput::Pimpl +{ +}; + +// (These are just stub functions if ALSA is unavailable...) +MidiInput::MidiInput (const String& deviceName, + const String& deviceID, + ump::PacketProtocol protocol) + : deviceInfo (deviceName, deviceID, protocol) +{ +} + +MidiInput::~MidiInput() {} + +void MidiInput::start() {} + +void MidiInput::stop() {} + +Array MidiInput::getAvailableDevices() { return {}; } + +MidiDeviceInfo MidiInput::getDefaultDevice() { return {}; } + +std::unique_ptr MidiInput::openDevice (const String&, MidiInputCallback*) { return {}; } + +std::unique_ptr MidiInput::openDevice (const String&, + ump::PacketProtocol, + ump::Receiver*) +{ + return {}; +} + +std::unique_ptr MidiInput::createNewDevice (const String&, MidiInputCallback*) { return {}; } + +std::unique_ptr MidiInput::createNewDevice (const String&, + ump::PacketProtocol, + ump::Receiver*) +{ + return {}; +} + +class MidiOutput::Pimpl +{ +}; + +MidiOutput::~MidiOutput() {} + +void MidiOutput::sendMessageNow (const MidiMessage&) {} + +void MidiOutput::sendMessageNow (const ump::View&) {} + +void MidiOutput::sendMessageNow (const ump::Packets&) {} + +Array MidiOutput::getAvailableDevices() { return {}; } + +MidiDeviceInfo MidiOutput::getDefaultDevice() { return {}; } + +std::unique_ptr MidiOutput::openDevice (const String&) { return {}; } + +std::unique_ptr MidiOutput::openDevice (const String&, ump::PacketProtocol) { return {}; } + +std::unique_ptr MidiOutput::createNewDevice (const String&) { return {}; } + +std::unique_ptr MidiOutput::createNewDevice (const String&, ump::PacketProtocol) { return {}; } + +MidiDeviceListConnection MidiDeviceListConnection::make (std::function cb) +{ + auto& broadcaster = MidiDeviceListConnectionBroadcaster::get(); + return { &broadcaster, broadcaster.add (std::move (cb)) }; +} + +#endif + +} // namespace yup diff --git a/modules/yup_audio_devices/yup_audio_devices.cpp b/modules/yup_audio_devices/yup_audio_devices.cpp index ad1bed94..92745335 100644 --- a/modules/yup_audio_devices/yup_audio_devices.cpp +++ b/modules/yup_audio_devices/yup_audio_devices.cpp @@ -62,10 +62,8 @@ #include "yup_audio_devices.h" -#include "audio_io/yup_SampleRateHelpers.cpp" #include "midi_io/yup_MidiDevices.cpp" -#include "midi_io/ump/yup_UMPBytestreamInputHandler.h" -#include "midi_io/ump/yup_UMPU32InputHandler.h" +#include "audio_io/yup_SampleRateHelpers.cpp" //============================================================================== #if YUP_MAC || YUP_IOS @@ -183,6 +181,15 @@ YUP_END_IGNORE_WARNINGS_GCC_LIKE #include "native/yup_ALSA_linux.cpp" #endif +#if defined(__has_include) && __has_include() +#include +#define YUP_HAS_ALSA_UMP 1 +#endif + +#ifndef YUP_HAS_ALSA_UMP +#define YUP_HAS_ALSA_UMP 0 +#endif + #if (YUP_LINUX && YUP_BELA) /* Got an include error here? If so, you've either not got the bela headers installed, or you've not got your paths set up correctly to find its header @@ -197,6 +204,12 @@ YUP_END_IGNORE_WARNINGS_GCC_LIKE #undef SIZEOF #if ! YUP_BELA +#include +#include +#include +#include +#include + #include "native/yup_Midi_linux.cpp" #endif @@ -210,7 +223,6 @@ RealtimeThreadFactory getAndroidRealtimeThreadFactory(); } // namespace yup #include "native/yup_Audio_android.cpp" - #include "native/yup_Midi_android.cpp" #if YUP_USE_ANDROID_OPENSLES || YUP_USE_ANDROID_OBOE @@ -248,6 +260,7 @@ RealtimeThreadFactory getAndroidRealtimeThreadFactory() { return nullptr; } } // namespace yup #endif +//============================================================================== #elif YUP_WASM #if YUP_EMSCRIPTEN #include @@ -260,6 +273,7 @@ RealtimeThreadFactory getAndroidRealtimeThreadFactory() { return nullptr; } #endif +//============================================================================== #if (YUP_LINUX || YUP_BSD || YUP_MAC || YUP_WINDOWS) && YUP_JACK /* Got an include error here? If so, you've either not got jack-audio-connection-kit installed, or you've not got your paths set up correctly to find its header files. @@ -276,6 +290,7 @@ RealtimeThreadFactory getAndroidRealtimeThreadFactory() { return nullptr; } #include "native/yup_JackAudio.cpp" #endif +//============================================================================== #if ! YUP_SYSTEMAUDIOVOL_IMPLEMENTED namespace yup { @@ -308,6 +323,7 @@ bool YUP_CALLTYPE SystemAudioVolume::setMuted (bool) } // namespace yup #endif +//============================================================================== #include "audio_io/yup_AudioDeviceManager.cpp" #include "audio_io/yup_AudioIODevice.cpp" #include "audio_io/yup_AudioIODeviceType.cpp" diff --git a/modules/yup_audio_devices/yup_audio_devices.h b/modules/yup_audio_devices/yup_audio_devices.h index c4ae9e9b..8628cb96 100644 --- a/modules/yup_audio_devices/yup_audio_devices.h +++ b/modules/yup_audio_devices/yup_audio_devices.h @@ -175,6 +175,8 @@ #include "midi_io/yup_MidiDevices.h" #include "midi_io/yup_MidiMessageCollector.h" #include "midi_io/ump/yup_UMPPacketCollector.h" +#include "midi_io/ump/yup_UMPBytestreamInputHandler.h" +#include "midi_io/ump/yup_UMPU32InputHandler.h" namespace yup { diff --git a/modules/yup_python/yup_python.h b/modules/yup_python/yup_python.h index 2ee178c4..2e0788cf 100644 --- a/modules/yup_python/yup_python.h +++ b/modules/yup_python/yup_python.h @@ -108,6 +108,8 @@ static inline constexpr const char* const PythonModuleName = YUP_PYTHON_STRINGIF //============================================================================== +YUP_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wattributes") + #include "scripting/yup_ScriptException.h" #include "scripting/yup_ScriptEngine.h" #include "scripting/yup_ScriptBindings.h" @@ -115,3 +117,5 @@ static inline constexpr const char* const PythonModuleName = YUP_PYTHON_STRINGIF #include "utilities/yup_ClassDemangling.h" #include "utilities/yup_CrashHandling.h" #include "utilities/yup_PythonInterop.h" + +YUP_END_IGNORE_WARNINGS_GCC_LIKE diff --git a/tests/yup_audio_basics.cpp b/tests/yup_audio_basics.cpp index 103c8f77..ced4367a 100644 --- a/tests/yup_audio_basics.cpp +++ b/tests/yup_audio_basics.cpp @@ -1,3 +1,9 @@ +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunknown-warning-option" +#pragma GCC diagnostic ignored "-Wsubobject-linkage" +#endif + #include "yup_audio_basics/yup_ADSR.cpp" #include "yup_audio_basics/yup_AudioChannelSet.cpp" #include "yup_audio_basics/yup_AudioDataConverters.cpp" @@ -49,4 +55,8 @@ #include "yup_audio_basics/yup_UMPSysExCollectors.cpp" #include "yup_audio_basics/yup_UMPTypes.cpp" #include "yup_audio_basics/yup_UMPUniversalPacket.cpp" -#include "yup_audio_basics/yup_UMPUniversalSysEx.cpp" \ No newline at end of file +#include "yup_audio_basics/yup_UMPUniversalSysEx.cpp" + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic pop +#endif diff --git a/tests/yup_audio_basics/yup_AudioPlayHead.cpp b/tests/yup_audio_basics/yup_AudioPlayHead.cpp index e31ff742..ea27fcbd 100644 --- a/tests/yup_audio_basics/yup_AudioPlayHead.cpp +++ b/tests/yup_audio_basics/yup_AudioPlayHead.cpp @@ -49,7 +49,6 @@ class TestAudioPlayHead : public AudioPlayHead private: std::optional testPosition; }; -} // namespace class AudioPlayHeadTests : public ::testing::Test { @@ -62,6 +61,8 @@ class AudioPlayHeadTests : public ::testing::Test std::unique_ptr playHead; }; +} // namespace + TEST_F (AudioPlayHeadTests, DefaultTransportControlMethodsExist) { // Test that default implementations exist and don't crash diff --git a/tests/yup_audio_basics/yup_BufferingAudioSource.cpp b/tests/yup_audio_basics/yup_BufferingAudioSource.cpp index 05eb1671..d4f67ac8 100644 --- a/tests/yup_audio_basics/yup_BufferingAudioSource.cpp +++ b/tests/yup_audio_basics/yup_BufferingAudioSource.cpp @@ -104,7 +104,6 @@ class MockPositionableAudioSource : public PositionableAudioSource int64 currentPosition; bool looping; }; -} // namespace //============================================================================== class BufferingAudioSourceTests : public ::testing::Test @@ -131,6 +130,8 @@ class BufferingAudioSourceTests : public ::testing::Test std::unique_ptr buffering; }; +} // namespace + //============================================================================== TEST_F (BufferingAudioSourceTests, Constructor) { diff --git a/tests/yup_audio_basics/yup_ChannelRemappingAudioSource.cpp b/tests/yup_audio_basics/yup_ChannelRemappingAudioSource.cpp index 13620ec9..43b07a08 100644 --- a/tests/yup_audio_basics/yup_ChannelRemappingAudioSource.cpp +++ b/tests/yup_audio_basics/yup_ChannelRemappingAudioSource.cpp @@ -67,7 +67,6 @@ class MockAudioSource : public AudioSource int lastSamplesPerBlock = 0; double lastSampleRate = 0.0; }; -} // namespace //============================================================================== class ChannelRemappingAudioSourceTests : public ::testing::Test @@ -88,6 +87,8 @@ class ChannelRemappingAudioSourceTests : public ::testing::Test std::unique_ptr remapper; }; +} // namespace + //============================================================================== TEST_F (ChannelRemappingAudioSourceTests, Constructor) { diff --git a/tests/yup_audio_basics/yup_MidiKeyboardState.cpp b/tests/yup_audio_basics/yup_MidiKeyboardState.cpp index 0e45092b..dfaa70e4 100644 --- a/tests/yup_audio_basics/yup_MidiKeyboardState.cpp +++ b/tests/yup_audio_basics/yup_MidiKeyboardState.cpp @@ -57,7 +57,6 @@ class TestListener : public MidiKeyboardState::Listener std::vector noteOnCalls; std::vector noteOffCalls; }; -} // namespace //============================================================================== class MidiKeyboardStateTests : public ::testing::Test @@ -79,6 +78,8 @@ class MidiKeyboardStateTests : public ::testing::Test std::unique_ptr listener; }; +} // namespace + //============================================================================== // Constructor and Reset Tests //============================================================================== diff --git a/tests/yup_audio_basics/yup_ResamplingAudioSource.cpp b/tests/yup_audio_basics/yup_ResamplingAudioSource.cpp index de260094..4513c06b 100644 --- a/tests/yup_audio_basics/yup_ResamplingAudioSource.cpp +++ b/tests/yup_audio_basics/yup_ResamplingAudioSource.cpp @@ -67,7 +67,6 @@ class MockResamplingAudioSource : public AudioSource int lastSamplesPerBlock = 0; double lastSampleRate = 0.0; }; -} // namespace //============================================================================== class ResamplingAudioSourceTests : public ::testing::Test @@ -88,6 +87,8 @@ class ResamplingAudioSourceTests : public ::testing::Test std::unique_ptr resampler; }; +} // namespace + //============================================================================== TEST_F (ResamplingAudioSourceTests, Constructor) { diff --git a/tests/yup_audio_basics/yup_ReverbAudioSource.cpp b/tests/yup_audio_basics/yup_ReverbAudioSource.cpp index 6acfc2de..cc5fc36d 100644 --- a/tests/yup_audio_basics/yup_ReverbAudioSource.cpp +++ b/tests/yup_audio_basics/yup_ReverbAudioSource.cpp @@ -67,7 +67,6 @@ class MockReverbAudioSource : public AudioSource double lastSampleRate = 0.0; float fillValue = 0.5f; }; -} // namespace //============================================================================== class ReverbAudioSourceTests : public ::testing::Test @@ -88,6 +87,8 @@ class ReverbAudioSourceTests : public ::testing::Test std::unique_ptr reverbSource; }; +} // namespace + //============================================================================== TEST_F (ReverbAudioSourceTests, ConstructorWithDeleteInput) { diff --git a/tests/yup_audio_basics/yup_Synthesiser.cpp b/tests/yup_audio_basics/yup_Synthesiser.cpp index 61b43c7f..2e2b96bb 100644 --- a/tests/yup_audio_basics/yup_Synthesiser.cpp +++ b/tests/yup_audio_basics/yup_Synthesiser.cpp @@ -204,8 +204,6 @@ class TestVoice : public SynthesiserVoice SynthesiserSound* currentSound = nullptr; }; -} // namespace - class SynthesiserTest : public ::testing::Test { protected: @@ -223,6 +221,8 @@ class SynthesiserTest : public ::testing::Test std::unique_ptr synth; }; +} // namespace + TEST_F (SynthesiserTest, DefaultConstruction) { Synthesiser synthesiser; diff --git a/tests/yup_core.cpp b/tests/yup_core.cpp index f9d4b5fb..d17dad03 100644 --- a/tests/yup_core.cpp +++ b/tests/yup_core.cpp @@ -1,3 +1,9 @@ +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunknown-warning-option" +#pragma GCC diagnostic ignored "-Wsubobject-linkage" +#endif + #include "yup_core/yup_AbstractFifo.cpp" #include "yup_core/yup_ArrayBase.cpp" #include "yup_core/yup_Atomic.cpp" @@ -83,4 +89,8 @@ #include "yup_core/yup_WebInputStream.cpp" #include "yup_core/yup_XmlDocument.cpp" #include "yup_core/yup_XmlElement.cpp" -#include "yup_core/yup_ZipFile.cpp" \ No newline at end of file +#include "yup_core/yup_ZipFile.cpp" + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic pop +#endif diff --git a/tests/yup_core/yup_ListenerList.cpp b/tests/yup_core/yup_ListenerList.cpp index c0b3fb7a..9a7328f1 100644 --- a/tests/yup_core/yup_ListenerList.cpp +++ b/tests/yup_core/yup_ListenerList.cpp @@ -46,6 +46,7 @@ using namespace yup; namespace { + class MockListener { public: diff --git a/tests/yup_gui.cpp b/tests/yup_gui.cpp index 2903d730..8f945650 100644 --- a/tests/yup_gui.cpp +++ b/tests/yup_gui.cpp @@ -1,3 +1,9 @@ +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunknown-warning-option" +#pragma GCC diagnostic ignored "-Wsubobject-linkage" +#endif + #include "yup_gui/yup_ComboBox.cpp" #include "yup_gui/yup_Component.cpp" #include "yup_gui/yup_FileChooser.cpp" @@ -7,4 +13,8 @@ #include "yup_gui/yup_SwitchButton.cpp" #include "yup_gui/yup_TextButton.cpp" #include "yup_gui/yup_TextEditor.cpp" -#include "yup_gui/yup_ToggleButton.cpp" \ No newline at end of file +#include "yup_gui/yup_ToggleButton.cpp" + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic pop +#endif diff --git a/tests/yup_gui/yup_Component.cpp b/tests/yup_gui/yup_Component.cpp index 374ebb6e..63a0ae7b 100644 --- a/tests/yup_gui/yup_Component.cpp +++ b/tests/yup_gui/yup_Component.cpp @@ -996,6 +996,9 @@ TEST_F (ComponentTest, OpaqueStateWithMultipleChildren) // Tests for missing Component methods using ComponentMock // ============================================================================= +namespace +{ + class ComponentMockTest : public ::testing::Test { protected: @@ -1008,6 +1011,8 @@ class ComponentMockTest : public ::testing::Test std::unique_ptr mockComponent; }; +} // namespace + // ============================================================================= TEST_F (ComponentMockTest, VirtualMethodCallbacks) diff --git a/thirdparty/harfbuzz/harfbuzz.h b/thirdparty/harfbuzz/harfbuzz.h index 303c744b..cdbb4753 100644 --- a/thirdparty/harfbuzz/harfbuzz.h +++ b/thirdparty/harfbuzz/harfbuzz.h @@ -44,5 +44,16 @@ #pragma once +#if defined(__GNUC__) || defined(__clang__) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wempty-body" + #pragma GCC diagnostic ignored "-Wunused-function" + #pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + #include "upstream/hb.h" #include "upstream/hb-ot.h" + +#if defined(__GNUC__) || defined(__clang__) + #pragma GCC diagnostic pop +#endif diff --git a/thirdparty/rive/rive.cpp b/thirdparty/rive/rive.cpp index 3d95828a..af820f40 100644 --- a/thirdparty/rive/rive.cpp +++ b/thirdparty/rive/rive.cpp @@ -26,6 +26,7 @@ #elif __GNUC__ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" + #pragma GCC diagnostic ignored "-Wempty-body" #elif _MSC_VER #pragma warning (push) #pragma warning (disable : 4244)