/* ============================================================================== This file is part of the JUCE framework. Copyright (c) Raw Material Software Limited JUCE is an open source framework subject to commercial or open source licensing. By downloading, installing, or using the JUCE framework, or combining the JUCE framework with any other source code, object code, content or any other copyrightable work, you agree to the terms of the JUCE End User Licence Agreement, and all incorporated terms including the JUCE Privacy Policy and the JUCE Website Terms of Service, as applicable, which will bind you. If you do not agree to the terms of these agreements, we will not license the JUCE framework to you, and you must discontinue the installation or download process and cease use of the JUCE framework. JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/ JUCE Privacy Policy: https://juce.com/juce-privacy-policy JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/ Or: You may also use this code under the terms of the AGPLv3: https://www.gnu.org/licenses/agpl-3.0.en.html THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED. ============================================================================== */ #ifndef DRV_QUERYDEVICEINTERFACE #define DRV_RESERVED 0x0800 #define DRV_QUERYDEVICEINTERFACE (DRV_RESERVED + 12) #define DRV_QUERYDEVICEINTERFACESIZE (DRV_RESERVED + 13) #endif namespace juce { #if JUCE_USE_WINDOWS_MIDI_SERVICES namespace wm2 = winrt::Microsoft::Windows::Devices::Midi2; namespace wm2v = wm2::Endpoints::Virtual; namespace mwdmi = Microsoft::Windows::Devices::Midi2::Initialization; class MidiServices { public: [[nodiscard]] static std::unique_ptr makeEndpoints (ump::EndpointsListener& l) { return EndpointsImplNative::make (l); } private: /* For both input and output. It's most resource-efficient to have only one connection to each endpoint. Therefore, we keep track of endpoints we've opened, and share the endpoints between Input and Output instances when possible. */ class SharedConnection : private AsyncUpdater { public: template static std::unique_ptr make (const wm2::MidiSession& session, const winrt::param::hstring& id, Args&&... args) { auto connection = session.CreateEndpointConnection (id); if (connection == nullptr) return {}; setUpConnection (connection, args...); auto result = rawToUniquePtr (new SharedConnection (session, std::move (connection))); result->inputToken = result->connection.MessageReceived ([self = result.get()] (const auto&, const wm2::MidiMessageReceivedEventArgs& args) { std::array words{}; args.FillWordArray (0, words); const ump::Iterator begin { words.data(), words.size() }; const auto end = std::next (begin); const auto elapsedTime = args.Timestamp() - self->startTimeNative; const auto juceTimeMillis = self->startTimeMillis + wm2::MidiClock::ConvertTimestampTicksToMilliseconds (elapsedTime); self->consumers.call ([&] (auto& c) { c.consume (begin, end, juceTimeMillis * 0.001); }); }); result->disconnectToken = result->connection.EndpointDeviceDisconnected ([self = result.get()] (auto&&...) { self->triggerAsyncUpdate(); }); if (! result->connection.Open()) return {}; return result; } ~SharedConnection() { connection.MessageReceived (inputToken); connection.EndpointDeviceDisconnected (disconnectToken); cancelPendingUpdate(); if (session != nullptr && connection != nullptr) session.DisconnectEndpointConnection (connection.ConnectionId()); } ump::EndpointId getEndpointId() const { const auto id = toString (connection.ConnectedEndpointDeviceId()); return ump::EndpointId::makeSrcDst (id, id); } void addConsumer (ump::Consumer& c) { consumers.add (c); } void removeConsumer (ump::Consumer& c) { consumers.remove (c); } void addDisconnectListener (ump::DisconnectionListener& l) { disconnectListeners.add (&l); } void removeDisconnectListener (ump::DisconnectionListener& l) { disconnectListeners.remove (&l); } bool send (ump::Iterator b, ump::Iterator e) { const auto result = connection.SendMultipleMessagesWordArray (0, 0, (uint32_t) std::distance (b->data(), e->data()), { b->data(), e->data() }); return wm2::MidiSendMessageResults::Succeeded == result; } private: SharedConnection (wm2::MidiSession s, wm2::MidiEndpointConnection c) : session (std::move (s)), connection (std::move (c)) {} void handleAsyncUpdate() override { disconnectListeners.call ([] (auto& c) { c.disconnected(); }); } static void setUpConnection (const wm2::MidiEndpointConnection&) { // Do nothing } static void setUpConnection (const wm2::MidiEndpointConnection& connection, const wm2::IMidiEndpointMessageProcessingPlugin& plugin) { connection.AddMessageProcessingPlugin (plugin); } const uint64_t startTimeNative = wm2::MidiClock::Now(); const uint32_t startTimeMillis = Time::getMillisecondCounter(); wm2::MidiSession session; wm2::MidiEndpointConnection connection; WaitFreeListeners consumers; ListenerList disconnectListeners; winrt::event_token inputToken, disconnectToken; }; class InputImplNative : public ump::Input::Impl::Native, private ump::Consumer { public: ~InputImplNative() override { shared->removeDisconnectListener (listener); shared->removeConsumer (*this); } ump::EndpointId getEndpointId() const override { return shared->getEndpointId(); } ump::PacketProtocol getProtocol() const override { return converter.getProtocol(); } static std::unique_ptr make (std::shared_ptr x, ump::DisconnectionListener& l, ump::PacketProtocol protocol, Consumer& consumer) { if (x == nullptr) return {}; return rawToUniquePtr (new InputImplNative { x, l, protocol, consumer }); } private: InputImplNative (std::shared_ptr s, ump::DisconnectionListener& l, ump::PacketProtocol p, Consumer& c) : listener (l), consumer (c), shared (s), converter (p) { shared->addConsumer (*this); shared->addDisconnectListener (listener); } void consume (ump::Iterator b, ump::Iterator e, double time) override { converter.convert (b, e, [&] (ump::View v) { const ump::Iterator b { v.data(), v.size() }; consumer.consume (b, std::next (b), time); }); } ump::DisconnectionListener& listener; Consumer& consumer; std::shared_ptr shared; ump::GenericUMPConverter converter; }; class OutputImplNative : public ump::Output::Impl::Native { public: ~OutputImplNative() override { shared->removeDisconnectListener (listener); } ump::EndpointId getEndpointId() const override { return shared->getEndpointId(); } bool send (ump::Iterator b, ump::Iterator e) override { return shared->send (b, e); } static std::unique_ptr make (std::shared_ptr c, ump::DisconnectionListener& l) { if (c == nullptr) return {}; return rawToUniquePtr (new OutputImplNative { std::move (c), l }); } private: OutputImplNative (std::shared_ptr c, ump::DisconnectionListener& l) : listener (l), shared (std::move (c)) { shared->addDisconnectListener (listener); } ump::DisconnectionListener& listener; std::shared_ptr shared; }; class VirtualEndpoint { public: ump::EndpointId getId() const { const auto id = toString (device.DeviceEndpointDeviceId()); return ump::EndpointId::makeSrcDst (id, id); } ump::Endpoint getEndpoint() const { return endpoint; } ump::StaticDeviceInfo getStaticDeviceInfo() const { return staticInfo; } bool setBlock (uint8_t i, const ump::Block& b) { if (! device.UpdateFunctionBlock (makeBlock (i, b))) return false; endpoint.getBlocks()[i] = b; return true; } bool setName (const String& x) { if (! device.UpdateEndpointName (x.toWideCharPointer())) return false; endpoint = endpoint.withName (x); return true; } static std::unique_ptr make (std::shared_ptr c, wm2v::MidiVirtualDevice d, ump::Endpoint ep, ump::StaticDeviceInfo si) { if (d == nullptr) return {}; return rawToUniquePtr (new VirtualEndpoint { std::move (c), std::move (d), std::move (ep), std::move (si) }); } private: VirtualEndpoint (std::shared_ptr s, wm2v::MidiVirtualDevice d, ump::Endpoint ep, ump::StaticDeviceInfo si) : shared (std::move (s)), device (std::move (d)), endpoint (ep), staticInfo (std::move (si)) { } std::shared_ptr shared; wm2v::MidiVirtualDevice device; ump::Endpoint endpoint; ump::StaticDeviceInfo staticInfo; }; struct VirtualEndpointRegistry { virtual ~VirtualEndpointRegistry() = default; virtual void virtualEndpointAdded (std::shared_ptr) = 0; }; class VirtualEndpointImplNative : public ump::VirtualEndpoint::Impl::Native, public ump::LegacyVirtualInput::Impl::Native, public ump::LegacyVirtualOutput::Impl::Native { public: ump::EndpointId getId() const override { return endpoint->getId(); } bool setBlock (uint8_t i, const ump::Block& b) override { return endpoint->setBlock (i, b); } bool setName (const String& x) override { return endpoint->setName (x); } static std::unique_ptr make (std::shared_ptr ep) { if (ep == nullptr) return {}; return rawToUniquePtr (new VirtualEndpointImplNative { std::move (ep) }); } private: explicit VirtualEndpointImplNative (std::shared_ptr ep) : endpoint (std::move (ep)) {} std::shared_ptr endpoint; }; class SessionImplNative : public ump::Session::Impl::Native { public: ~SessionImplNative() override { session.Close(); } String getName() const override { return toString (session.Name()); } std::unique_ptr connectInput (ump::DisconnectionListener& listener, const ump::EndpointId& id, ump::PacketProtocol p, ump::Consumer& consumer) override { const auto strong = findOrOpenConnection (id.src.toWideCharPointer()); return InputImplNative::make (strong, listener, p, consumer); } std::unique_ptr connectOutput (ump::DisconnectionListener& listener, const ump::EndpointId& id) override { const auto strong = findOrOpenConnection (id.dst.toWideCharPointer()); return OutputImplNative::make (strong, listener); } std::unique_ptr createNativeVirtualEndpoint (const String& name, const ump::DeviceInfo& info, const String& productInstance, ump::PacketProtocol protocol, Span blocks, ump::BlocksAreStatic areStatic) override { return createNativeVirtualEndpointImpl (name, info, productInstance, protocol, blocks, areStatic); } std::unique_ptr createLegacyVirtualInput (const String& name) override { const ump::Block blocks[] { ump::IOHelpers::makeLegacyBlock (true) }; return createNativeVirtualEndpointImpl (name, {}, {}, ump::PacketProtocol::MIDI_1_0, blocks, ump::BlocksAreStatic::yes); } std::unique_ptr createLegacyVirtualOutput (const String& name) override { const ump::Block blocks[] { ump::IOHelpers::makeLegacyBlock (false) }; return createNativeVirtualEndpointImpl (name, {}, {}, ump::PacketProtocol::MIDI_1_0, blocks, ump::BlocksAreStatic::yes); } static std::unique_ptr make (VirtualEndpointRegistry& r, const String& name) { if (auto s = wm2::MidiSession::Create (name.toWideCharPointer())) return rawToUniquePtr (new SessionImplNative (r, s)); return {}; } private: SessionImplNative (VirtualEndpointRegistry& r, const wm2::MidiSession& s) : registry (r), session (s) {} std::unique_ptr createNativeVirtualEndpointImpl (const String& name, const ump::DeviceInfo& info, const String& productInstance, ump::PacketProtocol protocol, Span blocks, ump::BlocksAreStatic areStatic) { wm2::MidiDeclaredEndpointInfo e; e.Name = name.toWideCharPointer(); e.HasStaticFunctionBlocks = areStatic == ump::BlocksAreStatic::yes; e.DeclaredFunctionBlockCount = (uint8_t) blocks.size(); e.ProductInstanceId = productInstance.toWideCharPointer(); e.SupportsMidi10Protocol = protocol == ump::PacketProtocol::MIDI_1_0; e.SupportsMidi20Protocol = protocol == ump::PacketProtocol::MIDI_2_0; e.SpecificationVersionMajor = 1; e.SpecificationVersionMinor = 1; e.SupportsReceivingJitterReductionTimestamps = false; e.SupportsSendingJitterReductionTimestamps = false; wm2v::MidiVirtualDeviceCreationConfig config { e.Name, ump::Endpoints::Impl::getGlobalMidiClientName().toWideCharPointer(), L"", e, makeDeviceInfo (info) }; for (const auto [index, value] : enumerate (blocks, uint8_t{})) config.FunctionBlocks().Append (makeBlock (index, value)); auto device = wm2v::MidiVirtualDeviceManager::CreateVirtualDevice (config); if (device == nullptr) return {}; // In order to function, the device needs a client plugin installed, which in turn // requires opening a connection to the endpoint. auto connection = findOrOpenConnection (device.DeviceEndpointDeviceId(), device); if (connection == nullptr) return {}; auto endpoint = ump::Endpoint{}.withName (name) .withDeviceInfo (info) .withProductInstanceId (productInstance) .withProtocol (protocol) .withMidi1Support (protocol == ump::PacketProtocol::MIDI_1_0) .withMidi2Support (protocol == ump::PacketProtocol::MIDI_2_0) .withStaticBlocks (areStatic == ump::BlocksAreStatic::yes) .withBlocks (blocks); auto staticInfo = ump::StaticDeviceInfo{}.withName (name) .withManufacturer ("") .withProduct ("") .withTransport (ump::Transport::ump) .withHasSource (true) .withHasDestination (true); std::shared_ptr virtualEndpoint = VirtualEndpoint::make (std::move (connection), std::move (device), endpoint, staticInfo); if (virtualEndpoint == nullptr) return {}; registry.virtualEndpointAdded (virtualEndpoint); return VirtualEndpointImplNative::make (std::move (virtualEndpoint)); } template std::shared_ptr findOrOpenConnection (const winrt::hstring& id, Args&&... args) { auto& weak = weakConnections[toString (id)]; if (const auto strong = weak.lock()) return strong; const std::shared_ptr strong = SharedConnection::make (session, id, args...); weak = strong; return strong; } VirtualEndpointRegistry& registry; std::map> weakConnections; wm2::MidiSession session; }; static wm2::MidiFunctionBlock makeBlock (uint8_t index, const ump::Block& b) { const auto direction = std::invoke ([&] { switch (b.getDirection()) { case ump::BlockDirection::bidirectional: return wm2::MidiFunctionBlockDirection::Bidirectional; case ump::BlockDirection::sender: return wm2::MidiFunctionBlockDirection::BlockOutput; case ump::BlockDirection::receiver: return wm2::MidiFunctionBlockDirection::BlockInput; case ump::BlockDirection::unknown: return wm2::MidiFunctionBlockDirection::Undefined; } return wm2::MidiFunctionBlockDirection{}; }); const auto hint = std::invoke ([&] { switch (b.getUiHint()) { case ump::BlockUiHint::bidirectional: return wm2::MidiFunctionBlockUIHint::Bidirectional; case ump::BlockUiHint::sender: return wm2::MidiFunctionBlockUIHint::Sender; case ump::BlockUiHint::receiver: return wm2::MidiFunctionBlockUIHint::Receiver; case ump::BlockUiHint::unknown: return wm2::MidiFunctionBlockUIHint::Unknown; } return wm2::MidiFunctionBlockUIHint{}; }); const auto proxy = std::invoke ([&] { switch (b.getMIDI1ProxyKind()) { case ump::BlockMIDI1ProxyKind::inapplicable: return wm2::MidiFunctionBlockRepresentsMidi10Connection::Not10; case ump::BlockMIDI1ProxyKind::restrictedBandwidth: return wm2::MidiFunctionBlockRepresentsMidi10Connection::YesBandwidthRestricted; case ump::BlockMIDI1ProxyKind::unrestrictedBandwidth: return wm2::MidiFunctionBlockRepresentsMidi10Connection::YesBandwidthUnrestricted; } return wm2::MidiFunctionBlockRepresentsMidi10Connection{}; }); wm2::MidiFunctionBlock result; result.Name (b.getName().toWideCharPointer()); result.Number (index); result.IsActive (b.isEnabled()); result.FirstGroup (wm2::MidiGroup { b.getFirstGroup() }); result.GroupCount (b.getNumGroups()); result.MaxSystemExclusive8Streams (b.getMaxSysex8Streams()); result.Direction (direction); result.UIHint (hint); result.RepresentsMidi10Connection (proxy); return result; } struct IndexedBlock { uint8_t index; ump::Block block; }; static IndexedBlock makeBlock (const wm2::MidiFunctionBlock& b) { const auto index = b.Number(); const auto direction = std::invoke ([&] { switch (b.Direction()) { case wm2::MidiFunctionBlockDirection::Bidirectional: return ump::BlockDirection::bidirectional; case wm2::MidiFunctionBlockDirection::BlockOutput: return ump::BlockDirection::sender; case wm2::MidiFunctionBlockDirection::BlockInput: return ump::BlockDirection::receiver; case wm2::MidiFunctionBlockDirection::Undefined: return ump::BlockDirection::unknown; } return ump::BlockDirection{}; }); const auto hint = std::invoke ([&] { switch (b.UIHint()) { case wm2::MidiFunctionBlockUIHint::Bidirectional: return ump::BlockUiHint::bidirectional; case wm2::MidiFunctionBlockUIHint::Sender: return ump::BlockUiHint::sender; case wm2::MidiFunctionBlockUIHint::Receiver: return ump::BlockUiHint::receiver; case wm2::MidiFunctionBlockUIHint::Unknown: return ump::BlockUiHint::unknown; } return ump::BlockUiHint{}; }); const auto proxy = std::invoke ([&] { switch (b.RepresentsMidi10Connection()) { case wm2::MidiFunctionBlockRepresentsMidi10Connection::Not10: return ump::BlockMIDI1ProxyKind::inapplicable; case wm2::MidiFunctionBlockRepresentsMidi10Connection::YesBandwidthRestricted: return ump::BlockMIDI1ProxyKind::restrictedBandwidth; case wm2::MidiFunctionBlockRepresentsMidi10Connection::YesBandwidthUnrestricted: return ump::BlockMIDI1ProxyKind::unrestrictedBandwidth; case wm2::MidiFunctionBlockRepresentsMidi10Connection::Reserved: break; } return ump::BlockMIDI1ProxyKind{}; }); const auto block = ump::Block{}.withDirection (direction) .withUiHint (hint) .withMIDI1ProxyKind (proxy) .withFirstGroup (b.FirstGroup().Index()) .withNumGroups (b.GroupCount()) .withEnabled (b.IsActive()) .withName (toString (b.Name())) .withMaxSysex8Streams (b.MaxSystemExclusive8Streams()); return { index, block }; } static wm2::MidiDeclaredDeviceIdentity makeDeviceInfo (ump::DeviceInfo x) { wm2::MidiDeclaredDeviceIdentity result{}; result.SystemExclusiveIdByte1 = (uint8_t) x.manufacturer[0]; result.SystemExclusiveIdByte2 = (uint8_t) x.manufacturer[1]; result.SystemExclusiveIdByte3 = (uint8_t) x.manufacturer[2]; result.DeviceFamilyLsb = (uint8_t) x.family[0]; result.DeviceFamilyMsb = (uint8_t) x.family[1]; result.DeviceFamilyModelNumberLsb = (uint8_t) x.modelNumber[0]; result.DeviceFamilyModelNumberMsb = (uint8_t) x.modelNumber[1]; result.SoftwareRevisionLevelByte1 = (uint8_t) x.revision[0]; result.SoftwareRevisionLevelByte2 = (uint8_t) x.revision[1]; result.SoftwareRevisionLevelByte3 = (uint8_t) x.revision[2]; result.SoftwareRevisionLevelByte4 = (uint8_t) x.revision[3]; return result; } static ump::DeviceInfo makeDeviceInfo (const wm2::MidiDeclaredDeviceIdentity& x) { return ump::DeviceInfo { { std::byte (x.SystemExclusiveIdByte1), std::byte (x.SystemExclusiveIdByte2), std::byte (x.SystemExclusiveIdByte3) }, { std::byte (x.DeviceFamilyLsb), std::byte (x.DeviceFamilyMsb) }, { std::byte (x.DeviceFamilyModelNumberLsb), std::byte (x.DeviceFamilyModelNumberMsb) }, { std::byte (x.SoftwareRevisionLevelByte1), std::byte (x.SoftwareRevisionLevelByte2), std::byte (x.SoftwareRevisionLevelByte3), std::byte (x.SoftwareRevisionLevelByte4) }, }; } class EndpointsImplNative : public ump::Endpoints::Impl::Native, private VirtualEndpointRegistry, private AsyncUpdater { public: ~EndpointsImplNative() override { watcher.Stop(); cancelPendingUpdate(); } ump::Backend getBackend() const { return ump::Backend::wms; } bool isVirtualMidiUmpServiceActive() const override { return wm2v::MidiVirtualDeviceManager::IsTransportAvailable(); } bool isVirtualMidiBytestreamServiceActive() const override { return wm2v::MidiVirtualDeviceManager::IsTransportAvailable(); } void getEndpoints (std::vector& buffer) const override { std::transform (cachedEndpoints.begin(), cachedEndpoints.end(), std::back_inserter (buffer), [] (auto& pair) { return pair.first; }); } std::optional getEndpoint (const ump::EndpointId& id) const override { if (const auto iter = virtualEndpoints.find (id); iter != virtualEndpoints.end()) if (auto strong = iter->second.lock()) return strong->getEndpoint(); if (const auto iter = cachedEndpoints.find (id); iter != cachedEndpoints.end()) return iter->second.endpoint; return {}; } std::optional getStaticDeviceInfo (const ump::EndpointId& id) const override { if (const auto iter = virtualEndpoints.find (id); iter != virtualEndpoints.end()) if (auto strong = iter->second.lock()) return strong->getStaticDeviceInfo(); if (const auto iter = cachedEndpoints.find (id); iter != cachedEndpoints.end()) return iter->second.info; return {}; } std::unique_ptr makeSession (const String& name) override { return SessionImplNative::make (*this, name); } static std::unique_ptr make (ump::EndpointsListener& l) { SharedResourcePointer initialiser; if (! initialiser->isValid()) { // If you hit this, you've tried to initialise Windows MIDI Services but the // initialisation failed. Did you forget to install the Windows MIDI Services SDK? jassertfalse; return {}; } auto watcher = wm2::MidiEndpointDeviceWatcher::Create(); if (! watcher) return {}; return rawToUniquePtr (new EndpointsImplNative { watcher, l }); } private: EndpointsImplNative (wm2::MidiEndpointDeviceWatcher w, ump::EndpointsListener& l) : listener (l), watcher (w) { watcher.Added ([this] (auto&, const wm2::MidiEndpointDeviceInformationAddedEventArgs& args) { const auto device = args.AddedDevice(); const auto id = toString (device.EndpointDeviceId()); const auto endpoint = makeEndpoint (device); const std::scoped_lock lock { mutex }; pendingWork.push_back ([this, id, endpoint] { cachedEndpoints.insert_or_assign (ump::EndpointId::makeSrcDst (id, id), endpoint); }); triggerAsyncUpdate(); }); watcher.Updated ([this] (auto&, const wm2::MidiEndpointDeviceInformationUpdatedEventArgs& args) { const auto id = toString (args.EndpointDeviceId()); if (const auto info = wm2::MidiEndpointDeviceInformation::CreateFromEndpointDeviceId (args.EndpointDeviceId())) { const auto endpoint = makeEndpoint (info); const std::scoped_lock lock { mutex }; pendingWork.push_back ([this, id, endpoint] { cachedEndpoints.insert_or_assign (ump::EndpointId::makeSrcDst (id, id), endpoint); }); triggerAsyncUpdate(); } }); watcher.Removed ([this] (auto&, const wm2::MidiEndpointDeviceInformationRemovedEventArgs& args) { const auto id = toString (args.EndpointDeviceId()); const std::scoped_lock lock { mutex }; pendingWork.push_back ([this, id] { cachedEndpoints.erase (ump::EndpointId::makeSrcDst (id, id)); }); triggerAsyncUpdate(); }); watcher.EnumerationCompleted ([this] (auto&, auto&) { const std::scoped_lock lock { mutex }; pendingWork.push_back ([this] { const auto devices = watcher.EnumeratedEndpointDevices(); for (const auto& [id, device] : devices) { auto endpoint = makeEndpoint (device); cachedEndpoints.insert_or_assign (endpoint.id, endpoint); } }); triggerAsyncUpdate(); }); watcher.Start(); } void virtualEndpointAdded (std::shared_ptr ep) override { if (ep != nullptr) virtualEndpoints[ep->getId()] = ep; } std::function popWork() { const std::scoped_lock lock { mutex }; if (pendingWork.empty()) return {}; auto result = pendingWork.front(); pendingWork.pop_front(); return result; } void handleAsyncUpdate() override { while (auto fn = popWork()) fn(); listener.endpointsChanged(); } static ump::EndpointAndStaticInfo makeEndpoint (const wm2::MidiEndpointDeviceInformation& info) { const auto transport = std::invoke ([&] { const auto t = info.GetTransportSuppliedInfo().NativeDataFormat; if (t == wm2::MidiEndpointNativeDataFormat::Midi1ByteFormat) return ump::Transport::bytestream; return ump::Transport::ump; }); const auto itemProtocol = std::invoke ([&] { const auto p = info.GetDeclaredStreamConfiguration().Protocol; if (p == wm2::MidiProtocol::Midi1 || transport == ump::Transport::bytestream) return ump::PacketProtocol::MIDI_1_0; return ump::PacketProtocol::MIDI_2_0; }); const auto deviceInfo = makeDeviceInfo (info.GetDeclaredDeviceIdentity()); const auto id = toString (info.EndpointDeviceId()); std::array legacyIds; for (auto [index, value] : enumerate (legacyIds)) { auto obj = new DynamicObject; obj->setProperty ("endpoint", id); obj->setProperty ("group", index); value = JSON::toString (obj, true); } std::vector blocks; for (const auto& fb : info.GetDeclaredFunctionBlocks()) blocks.push_back (makeBlock (fb).block); if (blocks.empty()) { for (const auto& gtb : info.GetGroupTerminalBlocks()) blocks.push_back (makeBlock (gtb.AsEquivalentFunctionBlock()).block); } const auto e = info.GetDeclaredEndpointInfo(); const auto manufacturer = info.GetContainerDeviceInformation() .Properties() .TryLookup (L"System.Devices.Manufacturer"); const auto product = info.GetContainerDeviceInformation() .Properties() .TryLookup (L"System.Devices.ModelName"); const auto endpoint = ump::Endpoint{}.withName (toString (info.Name())) .withProtocol (itemProtocol) .withBlocks (blocks) .withDeviceInfo (deviceInfo) .withProductInstanceId (toString (info.GetDeclaredEndpointInfo().ProductInstanceId)) .withUMPVersion (e.SpecificationVersionMajor, e.SpecificationVersionMinor) .withMidi1Support (e.SupportsMidi10Protocol) .withMidi2Support (e.SupportsMidi20Protocol) .withStaticBlocks (e.HasStaticFunctionBlocks) .withReceiveJRSupport (e.SupportsReceivingJitterReductionTimestamps) .withTransmitJRSupport (e.SupportsSendingJitterReductionTimestamps); const auto hasBlockDirection = [&] (auto direction) { const auto blockCanUseDirection = [&] (const wm2::MidiFunctionBlock& x) { const auto d = x.Direction(); return d == wm2::MidiFunctionBlockDirection::Bidirectional || d == direction; }; const auto fb = info.GetDeclaredFunctionBlocks(); const auto gt = info.GetGroupTerminalBlocks(); return std::any_of (fb.begin(), fb.end(), blockCanUseDirection) || std::any_of (gt.begin(), gt.end(), [&] (const wm2::MidiGroupTerminalBlock& x) { return blockCanUseDirection (x.AsEquivalentFunctionBlock()); }); }; const auto staticInfo = ump::StaticDeviceInfo{}.withName (toString (info.Name())) .withManufacturer (toString (winrt::unbox_value_or (manufacturer, L""))) .withProduct (toString (winrt::unbox_value_or (product, L""))) .withHasSource (hasBlockDirection (wm2::MidiFunctionBlockDirection::BlockOutput)) .withHasDestination (hasBlockDirection (wm2::MidiFunctionBlockDirection::BlockInput)) .withLegacyIdentifiersSrc (legacyIds) .withLegacyIdentifiersDst (legacyIds) .withTransport (transport); return { endpoint, staticInfo, ump::EndpointId::makeSrcDst (id, id) }; } class SdkInitialiser { public: SdkInitialiser() = default; bool isValid() const { return ptr != nullptr; } private: ComSmartPtr ptr = std::invoke ([]() -> ComSmartPtr { try { winrt::init_apartment (winrt::apartment_type::single_threaded); } catch (...) { // We tried... } ComSmartPtr result; if (FAILED (CoCreateInstance (__uuidof (mwdmi::MidiClientInitializerUuid), nullptr, CLSCTX::CLSCTX_INPROC_SERVER | CLSCTX::CLSCTX_FROM_DEFAULT_CONTEXT, __uuidof (mwdmi::IMidiClientInitializer), (void**) result.resetAndGetPointerAddress()))) return {}; if (result == nullptr) return {}; if (FAILED (result->EnsureServiceAvailable())) return {}; return result; }); }; SharedResourcePointer initialiser; std::mutex mutex; std::deque> pendingWork; ump::EndpointsListener& listener; std::map cachedEndpoints; std::map> virtualEndpoints; wm2::MidiEndpointDeviceWatcher watcher; }; static String toString (const winrt::hstring& str) { return CharPointer_UTF16 { str.data() }; } MidiServices() = delete; }; #endif RTL_OSVERSIONINFOW getWindowsVersionInfo(); struct WindowsMidiHelpers { #if JUCE_USE_WINRT_MIDI #ifndef JUCE_FORCE_WINRT_MIDI #define JUCE_FORCE_WINRT_MIDI 0 #endif #ifndef JUCE_WINRT_MIDI_LOGGING #define JUCE_WINRT_MIDI_LOGGING 0 #endif #if JUCE_WINRT_MIDI_LOGGING #define JUCE_WINRT_MIDI_LOG(x) DBG(x) #else #define JUCE_WINRT_MIDI_LOG(x) #endif struct Winrt { using DeviceInformation = ABI::Windows::Devices::Enumeration::DeviceInformation; using DeviceInformationKind = ABI::Windows::Devices::Enumeration::DeviceInformationKind; using DeviceInformationUpdate = ABI::Windows::Devices::Enumeration::DeviceInformationUpdate; using DeviceWatcher = ABI::Windows::Devices::Enumeration::DeviceWatcher; using IBuffer = ABI::Windows::Storage::Streams::IBuffer; using IBufferFactory = ABI::Windows::Storage::Streams::IBufferFactory; using IBufferByteAccess = Windows::Storage::Streams::IBufferByteAccess; using IDeviceInformation = ABI::Windows::Devices::Enumeration::IDeviceInformation; using IDeviceInformationStatics2 = ABI::Windows::Devices::Enumeration::IDeviceInformationStatics2; using IDeviceInformationUpdate = ABI::Windows::Devices::Enumeration::IDeviceInformationUpdate; using IDevicePicker = ABI::Windows::Devices::Enumeration::IDevicePicker; using IDeviceWatcher = ABI::Windows::Devices::Enumeration::IDeviceWatcher; using IMidiInPort = ABI::Windows::Devices::Midi::IMidiInPort; using IMidiInPortStatics = ABI::Windows::Devices::Midi::IMidiInPortStatics; using IMidiMessage = ABI::Windows::Devices::Midi::IMidiMessage; using IMidiMessageReceivedEventArgs = ABI::Windows::Devices::Midi::IMidiMessageReceivedEventArgs; using IMidiOutPort = ABI::Windows::Devices::Midi::IMidiOutPort; using IMidiOutPortStatics = ABI::Windows::Devices::Midi::IMidiOutPortStatics; using MidiInPort = ABI::Windows::Devices::Midi::MidiInPort; using MidiMessageReceivedEventArgs = ABI::Windows::Devices::Midi::MidiMessageReceivedEventArgs; template using ITypedEventHandler = ABI::Windows::Foundation::ITypedEventHandler; template using IAsyncOperation = ABI::Windows::Foundation::IAsyncOperation; template using IAsyncOperationCompletedHandler = ABI::Windows::Foundation::IAsyncOperationCompletedHandler; template using IIterable = ABI::Windows::Foundation::Collections::IIterable; template using IReference = ABI::Windows::Foundation::IReference; template using IVector = ABI::Windows::Foundation::Collections::IVector; class DeviceCallbackHandler { public: struct Delegate { virtual ~Delegate() = default; virtual HRESULT addDevice (IDeviceInformation*) = 0; virtual HRESULT removeDevice (IDeviceInformationUpdate*) = 0; virtual HRESULT updateDevice (IDeviceInformationUpdate*) = 0; }; static std::unique_ptr make (Delegate* delegate, HSTRING deviceSelector, DeviceInformationKind infoKind) { auto* wrtWrapper = WinRTWrapper::getInstanceWithoutCreating(); if (wrtWrapper == nullptr) { JUCE_WINRT_MIDI_LOG ("Failed to get the WinRTWrapper singleton!"); return {}; } auto deviceInfoFactory = wrtWrapper->getWRLFactory (&RuntimeClass_Windows_Devices_Enumeration_DeviceInformation[0]); if (deviceInfoFactory == nullptr) return {}; // A quick way of getting an IVector... auto requestedProperties = [wrtWrapper] { auto devicePicker = wrtWrapper->activateInstance (&RuntimeClass_Windows_Devices_Enumeration_DevicePicker[0], __uuidof (IDevicePicker)); jassert (devicePicker != nullptr); IVector* result; auto hr = devicePicker->get_RequestedProperties (&result); jassert (SUCCEEDED (hr)); hr = result->Clear(); jassert (SUCCEEDED (hr)); return result; }(); StringArray propertyKeys ("System.Devices.ContainerId", "System.Devices.Aep.ContainerId", "System.Devices.Aep.IsConnected"); for (auto& key : propertyKeys) { WinRTWrapper::ScopedHString hstr (key); auto hr = requestedProperties->Append (hstr.get()); if (FAILED (hr)) { jassertfalse; return {}; } } ComSmartPtr> iter; auto hr = requestedProperties->QueryInterface (__uuidof (IIterable), (void**) iter.resetAndGetPointerAddress()); if (FAILED (hr)) { jassertfalse; return {}; } auto result = rawToUniquePtr (new DeviceCallbackHandler); result->delegate = delegate; hr = deviceInfoFactory->CreateWatcherWithKindAqsFilterAndAdditionalProperties (deviceSelector, iter, infoKind, result->watcher.resetAndGetPointerAddress()); if (FAILED (hr)) { jassertfalse; return {}; } result->thread = std::thread { [r = result.get()] { Thread::setCurrentThreadName (SystemStats::getJUCEVersion() + ": WinRT Device Enumeration Thread"); r->watcher->add_Added ( Microsoft::WRL::Callback> ( [r] (IDeviceWatcher*, IDeviceInformation* info) { return r->delegate->addDevice (info); } ).Get(), &r->deviceAddedToken); r->watcher->add_Removed ( Microsoft::WRL::Callback> ( [r] (IDeviceWatcher*, IDeviceInformationUpdate* infoUpdate) { return r->delegate->removeDevice (infoUpdate); } ).Get(), &r->deviceRemovedToken); r->watcher->add_Updated ( Microsoft::WRL::Callback> ( [r] (IDeviceWatcher*, IDeviceInformationUpdate* infoUpdate) { return r->delegate->updateDevice (infoUpdate); } ).Get(), &r->deviceUpdatedToken); r->watcher->Start(); } }; return result; } ~DeviceCallbackHandler() { if (thread.joinable()) thread.join(); if (watcher == nullptr) return; auto hr = watcher->Stop(); jassert (SUCCEEDED (hr)); if (deviceAddedToken.value != 0) { hr = watcher->remove_Added (deviceAddedToken); jassert (SUCCEEDED (hr)); deviceAddedToken.value = 0; } if (deviceUpdatedToken.value != 0) { hr = watcher->remove_Updated (deviceUpdatedToken); jassert (SUCCEEDED (hr)); deviceUpdatedToken.value = 0; } if (deviceRemovedToken.value != 0) { hr = watcher->remove_Removed (deviceRemovedToken); jassert (SUCCEEDED (hr)); deviceRemovedToken.value = 0; } watcher = nullptr; } template IInspectable* getValueFromDeviceInfo (String key, InfoType* info) { __FIMapView_2_HSTRING_IInspectable* properties; info->get_Properties (&properties); boolean found = false; WinRTWrapper::ScopedHString keyHstr (key); auto hr = properties->HasKey (keyHstr.get(), &found); if (FAILED (hr)) { jassertfalse; return nullptr; } if (! found) return nullptr; IInspectable* inspectable; hr = properties->Lookup (keyHstr.get(), &inspectable); if (FAILED (hr)) { jassertfalse; return nullptr; } return inspectable; } String getGUIDFromInspectable (IInspectable& inspectable) { ComSmartPtr> guidRef; auto hr = inspectable.QueryInterface (__uuidof (IReference), (void**) guidRef.resetAndGetPointerAddress()); if (FAILED (hr)) { jassertfalse; return {}; } GUID result; hr = guidRef->get_Value (&result); if (FAILED (hr)) { jassertfalse; return {}; } OLECHAR* resultString; StringFromCLSID (result, &resultString); return resultString; } bool getBoolFromInspectable (IInspectable& inspectable) { ComSmartPtr> boolRef; auto hr = inspectable.QueryInterface (__uuidof (IReference), (void**) boolRef.resetAndGetPointerAddress()); if (FAILED (hr)) { jassertfalse; return false; } boolean result; hr = boolRef->get_Value (&result); if (FAILED (hr)) { jassertfalse; return false; } return result; } private: DeviceCallbackHandler() = default; Delegate* delegate = nullptr; ComSmartPtr watcher; EventRegistrationToken deviceAddedToken { 0 }, deviceRemovedToken { 0 }, deviceUpdatedToken { 0 }; std::thread thread; }; struct BLEDeviceWatcherListener { virtual ~BLEDeviceWatcherListener() = default; virtual void bleDeviceAdded (const String& containerID) = 0; virtual void bleDeviceDisconnected (const String& containerID) = 0; }; class BLEDeviceWatcher : private DeviceCallbackHandler::Delegate { public: static std::unique_ptr make() { WinRTWrapper::ScopedHString deviceSelector ("System.Devices.Aep.ProtocolId:=\"{bb7bb05e-5972-42b5-94fc-76eaa7084d49}\"" " AND System.Devices.Aep.IsPaired:=System.StructuredQueryType.Boolean#True"); auto result = rawToUniquePtr (new BLEDeviceWatcher); if (auto watcher = DeviceCallbackHandler::make (result.get(), deviceSelector.get(), DeviceInformationKind::DeviceInformationKind_AssociationEndpoint)) { result->watcher = std::move (watcher); return result; } return {}; } bool isBleDevice (const String& containerId) const { const ScopedLock lock (deviceChanges); return bleContainerIds.find (containerId) != bleContainerIds.end(); } void addListener (BLEDeviceWatcherListener& l) { listeners.add (l); } void removeListener (BLEDeviceWatcherListener& l) { listeners.remove (l); } private: HRESULT addDevice (IDeviceInformation* addedDeviceInfo) override { HSTRING deviceIDHst; auto hr = addedDeviceInfo->get_Id (&deviceIDHst); if (FAILED (hr)) { JUCE_WINRT_MIDI_LOG ("Failed to query added BLE device ID!"); return S_OK; } auto* wrtWrapper = WinRTWrapper::getInstanceWithoutCreating(); if (wrtWrapper == nullptr) { JUCE_WINRT_MIDI_LOG ("Failed to get the WinRTWrapper singleton!"); return false; } auto deviceID = wrtWrapper->hStringToString (deviceIDHst); JUCE_WINRT_MIDI_LOG ("Detected paired BLE device: " << deviceID); if (auto* containerIDValue = watcher->getValueFromDeviceInfo ("System.Devices.Aep.ContainerId", addedDeviceInfo)) { auto containerID = watcher->getGUIDFromInspectable (*containerIDValue); if (containerID.isNotEmpty()) { listeners.call ([&containerID] (auto& l) { l.bleDeviceAdded (containerID); }); const ScopedLock lock (deviceChanges); bleContainerIds.insert (containerID); return S_OK; } } JUCE_WINRT_MIDI_LOG ("Failed to get a container ID for BLE device: " << deviceID); return S_OK; } HRESULT removeDevice (IDeviceInformationUpdate* removedDeviceInfo) override { if (auto* containerIDValue = watcher->getValueFromDeviceInfo ("System.Devices.Aep.ContainerId", removedDeviceInfo)) { auto containerID = watcher->getGUIDFromInspectable (*containerIDValue); if (containerID.isNotEmpty()) { listeners.call ([&containerID] (auto& l) { l.bleDeviceDisconnected (containerID); }); const ScopedLock lock (deviceChanges); bleContainerIds.erase (containerID); return S_OK; } } return E_FAIL; } HRESULT updateDevice (IDeviceInformationUpdate*) override { // This shouldn't change the device container return S_OK; } BLEDeviceWatcher() = default; WaitFreeListeners listeners; std::set bleContainerIds; CriticalSection deviceChanges; std::unique_ptr watcher; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BLEDeviceWatcher) }; struct WinRTMIDIDeviceInfo { String deviceID, containerID, name; bool isDefault = false; }; struct MidiIODeviceWatcherListener { virtual ~MidiIODeviceWatcherListener() = default; virtual void deviceAdded (const String&) = 0; virtual void deviceRemoved (const String&) = 0; virtual void deviceUpdated (const String&) = 0; }; template class MidiIODeviceWatcher : private DeviceCallbackHandler::Delegate { public: void getAvailableDevices (std::vector& storage) { { const ScopedLock lock (deviceChanges); lastQueriedConnectedDevices = connectedDevices; } StringArray deviceNames, deviceIDs; for (auto info : lastQueriedConnectedDevices.get()) { deviceNames.add (info.name); deviceIDs .add (info.containerID); } deviceNames.appendNumbersToDuplicates (false, false, CharPointer_UTF8 ("-"), CharPointer_UTF8 ("")); deviceIDs .appendNumbersToDuplicates (false, false, CharPointer_UTF8 ("-"), CharPointer_UTF8 ("")); constexpr auto direction = std::invoke ([] { if constexpr (std::is_same_v) return ump::BlockDirection::sender; else if constexpr (std::is_same_v) return ump::BlockDirection::receiver; }); for (int i = 0; i < deviceNames.size(); ++i) { const auto fullInfo = ump::IOHelpers::makeProxyEndpoint (MidiDeviceInfo { deviceNames[i], deviceIDs[i] }, direction); storage.push_back (fullInfo); } } WinRTMIDIDeviceInfo getWinRTDeviceInfoForDevice (const ump::EndpointId& deviceIdentifier) { std::vector endpoints; getAvailableDevices (endpoints); for (const auto [index, value] : enumerate (endpoints)) if (value.id == deviceIdentifier) return lastQueriedConnectedDevices.get()[(int) index]; return {}; } static std::unique_ptr make (ComSmartPtr f) { HSTRING deviceSelector; auto hr = f->GetDeviceSelector (&deviceSelector); if (FAILED (hr)) { JUCE_WINRT_MIDI_LOG ("Failed to get MIDI device selector!"); return {}; } auto result = rawToUniquePtr (new MidiIODeviceWatcher); result->factory = f; if (auto watcher = DeviceCallbackHandler::make (result.get(), deviceSelector, DeviceInformationKind::DeviceInformationKind_DeviceInterface)) { result->watcher = std::move (watcher); return result; } return {}; } void addListener (MidiIODeviceWatcherListener& l) { listeners.add (l); } void removeListener (MidiIODeviceWatcherListener& l) { listeners.remove (l); } private: MidiIODeviceWatcher() = default; HRESULT addDevice (IDeviceInformation* addedDeviceInfo) override { WinRTMIDIDeviceInfo info; HSTRING deviceID; auto hr = addedDeviceInfo->get_Id (&deviceID); if (FAILED (hr)) { JUCE_WINRT_MIDI_LOG ("Failed to query added MIDI device ID!"); return S_OK; } auto* wrtWrapper = WinRTWrapper::getInstanceWithoutCreating(); if (wrtWrapper == nullptr) { JUCE_WINRT_MIDI_LOG ("Failed to get the WinRTWrapper singleton!"); return false; } info.deviceID = wrtWrapper->hStringToString (deviceID); listeners.call ([&] (auto& l) { l.deviceAdded (info.deviceID); }); JUCE_WINRT_MIDI_LOG ("Detected MIDI device: " << info.deviceID); boolean isEnabled = false; hr = addedDeviceInfo->get_IsEnabled (&isEnabled); if (FAILED (hr) || ! isEnabled) { JUCE_WINRT_MIDI_LOG ("MIDI device not enabled: " << info.deviceID); return S_OK; } // We use the container ID to match a MIDI device with a generic BLE device, if possible if (auto* containerIDValue = watcher->getValueFromDeviceInfo ("System.Devices.ContainerId", addedDeviceInfo)) info.containerID = watcher->getGUIDFromInspectable (*containerIDValue); HSTRING name; hr = addedDeviceInfo->get_Name (&name); if (FAILED (hr)) { JUCE_WINRT_MIDI_LOG ("Failed to query detected MIDI device name for " << info.deviceID); return S_OK; } info.name = wrtWrapper->hStringToString (name); boolean isDefault = false; hr = addedDeviceInfo->get_IsDefault (&isDefault); if (FAILED (hr)) { JUCE_WINRT_MIDI_LOG ("Failed to query detected MIDI device defaultness for " << info.deviceID << " " << info.name); return S_OK; } info.isDefault = isDefault; JUCE_WINRT_MIDI_LOG ("Adding MIDI device: " << info.deviceID << " " << info.containerID << " " << info.name); { const ScopedLock lock (deviceChanges); connectedDevices.add (info); } return S_OK; } HRESULT removeDevice (IDeviceInformationUpdate* removedDeviceInfo) override { HSTRING removedDeviceIdHstr; auto hr = removedDeviceInfo->get_Id (&removedDeviceIdHstr); if (FAILED (hr)) { JUCE_WINRT_MIDI_LOG ("Failed to query removed MIDI device ID!"); return S_OK; } auto* wrtWrapper = WinRTWrapper::getInstanceWithoutCreating(); if (wrtWrapper == nullptr) { JUCE_WINRT_MIDI_LOG ("Failed to get the WinRTWrapper singleton!"); return S_OK; } auto removedDeviceId = wrtWrapper->hStringToString (removedDeviceIdHstr); listeners.call ([&] (auto& l) { l.deviceRemoved (removedDeviceId); }); JUCE_WINRT_MIDI_LOG ("Removing MIDI device: " << removedDeviceId); { const ScopedLock lock (deviceChanges); for (int i = 0; i < connectedDevices.size(); ++i) { if (connectedDevices[i].deviceID == removedDeviceId) { connectedDevices.remove (i); JUCE_WINRT_MIDI_LOG ("Removed MIDI device: " << removedDeviceId); break; } } } return S_OK; } // This is never called HRESULT updateDevice (IDeviceInformationUpdate* c) override { HSTRING removedDeviceIdHstr; auto hr = c->get_Id (&removedDeviceIdHstr); if (FAILED (hr)) { JUCE_WINRT_MIDI_LOG ("Failed to query removed MIDI device ID!"); return S_OK; } auto* wrtWrapper = WinRTWrapper::getInstanceWithoutCreating(); if (wrtWrapper == nullptr) { JUCE_WINRT_MIDI_LOG ("Failed to get the WinRTWrapper singleton!"); return S_OK; } auto id = wrtWrapper->hStringToString (removedDeviceIdHstr); listeners.call ([&] (auto& l) { l.deviceUpdated (id); }); return S_OK; } ComSmartPtr factory; Array connectedDevices; CriticalSection deviceChanges; ThreadLocalValue> lastQueriedConnectedDevices; WaitFreeListeners listeners; std::unique_ptr watcher; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiIODeviceWatcher) }; struct DeviceRemovalListener { virtual ~DeviceRemovalListener() = default; virtual void deviceRemoved (const String&) = 0; }; class Client : private MidiIODeviceWatcherListener, private BLEDeviceWatcherListener, private AsyncUpdater { public: ~Client() override { inputDeviceWatcher->removeListener (*this); outputDeviceWatcher->removeListener (*this); bleDeviceWatcher->removeListener (*this); } WinRTMIDIDeviceInfo getWinRTDeviceInfoForInput (const ump::EndpointId& deviceIdentifier) const { return inputDeviceWatcher->getWinRTDeviceInfoForDevice (deviceIdentifier); } WinRTMIDIDeviceInfo getWinRTDeviceInfoForOutput (const ump::EndpointId& deviceIdentifier) const { return outputDeviceWatcher->getWinRTDeviceInfoForDevice (deviceIdentifier); } bool isBleDevice (const String& containerId) const { return bleDeviceWatcher->isBleDevice (containerId); } void addBleListener (BLEDeviceWatcherListener& l) { bleDeviceWatcher->addListener (l); } void removeBleListener (BLEDeviceWatcherListener& l) { bleDeviceWatcher->removeListener (l); } void getEndpoints (std::vector& x) { std::transform (cachedEndpoints.begin(), cachedEndpoints.end(), std::back_inserter (x), [] (const auto& p) { return p.first; }); } std::optional getEndpoint (const ump::EndpointId& x) const { const auto iter = cachedEndpoints.find (x); if (iter == cachedEndpoints.end()) return {}; return iter->second.endpoint; } std::optional getStaticDeviceInfo (const ump::EndpointId& x) const { const auto iter = cachedEndpoints.find (x); if (iter == cachedEndpoints.end()) return {}; return iter->second.info; } static std::unique_ptr make (ump::EndpointsListener& l) { const auto windowsVersionInfo = getWindowsVersionInfo(); if (windowsVersionInfo.dwMajorVersion < 10 || windowsVersionInfo.dwBuildNumber < 17763) return {}; auto* wrtWrapper = WinRTWrapper::getInstance(); if (! wrtWrapper->isInitialised()) return {}; auto midiInFactory = wrtWrapper->getWRLFactory (&RuntimeClass_Windows_Devices_Midi_MidiInPort[0]); if (midiInFactory == nullptr) return {}; auto midiOutFactory = wrtWrapper->getWRLFactory (&RuntimeClass_Windows_Devices_Midi_MidiOutPort[0]); if (midiOutFactory == nullptr) return {}; // The WinRT BLE MIDI API doesn't provide callbacks when devices become disconnected, // but it does require a disconnection via the API before a device will reconnect again. // We can monitor the BLE connection state of paired devices to get callbacks when // connections are broken. auto bleDeviceWatcher = BLEDeviceWatcher::make(); if (bleDeviceWatcher == nullptr) return {}; auto inputDeviceWatcher = MidiIODeviceWatcher::make (midiInFactory); if (inputDeviceWatcher == nullptr) return {}; auto outputDeviceWatcher = MidiIODeviceWatcher::make (midiOutFactory); if (outputDeviceWatcher == nullptr) return {}; auto result = rawToUniquePtr (new Client { l }); result->midiInFactory = std::move (midiInFactory); result->midiOutFactory = std::move (midiOutFactory); result->inputDeviceWatcher = std::move (inputDeviceWatcher); result->outputDeviceWatcher = std::move (outputDeviceWatcher); result->bleDeviceWatcher = std::move (bleDeviceWatcher); result->inputDeviceWatcher->addListener (*result); result->outputDeviceWatcher->addListener (*result); result->bleDeviceWatcher->addListener (*result); result->updateCachedEndpoints(); return result; } ComSmartPtr getMidiInFactory() const { return midiInFactory; } ComSmartPtr getMidiOutFactory() const { return midiOutFactory; } void addDeviceRemovalListener (DeviceRemovalListener& l) { listeners.add (l); } void removeDeviceRemovalListener (DeviceRemovalListener& l) { listeners.remove (l); } private: explicit Client (ump::EndpointsListener& l) : listener (l) {} void deviceAdded (const String&) override { triggerAsyncUpdate(); } void deviceRemoved (const String& id) override { listeners.call ([&] (auto& c) { c.deviceRemoved (id); }); triggerAsyncUpdate(); } void deviceUpdated (const String&) override { triggerAsyncUpdate(); } void bleDeviceAdded (const String&) override { triggerAsyncUpdate(); } void bleDeviceDisconnected (const String&) override { triggerAsyncUpdate(); } void handleAsyncUpdate() override { updateCachedEndpoints(); listener.endpointsChanged(); } void updateCachedEndpoints() { std::vector buffer; inputDeviceWatcher->getAvailableDevices (buffer); outputDeviceWatcher->getAvailableDevices (buffer); cachedEndpoints.clear(); for (auto& item : buffer) cachedEndpoints.emplace (item.id, item); // If this is hit, we got an identical ID for an input and output device jassert (cachedEndpoints.size() == buffer.size()); } ump::EndpointsListener& listener; std::map cachedEndpoints; ComSmartPtr midiInFactory; ComSmartPtr midiOutFactory; std::unique_ptr> inputDeviceWatcher; std::unique_ptr> outputDeviceWatcher; std::unique_ptr bleDeviceWatcher; WaitFreeListeners listeners; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Client) }; template class WinRTIOWrapper : private BLEDeviceWatcherListener, private DeviceRemovalListener, private AsyncUpdater { public: static std::unique_ptr make (std::shared_ptr c, const ump::EndpointId& deviceIdentifier, ump::DisconnectionListener& listener) { auto result = rawToUniquePtr (new WinRTIOWrapper); result->client = c; result->onDisconnect = &listener; result->deviceInfo = isInput ? c->getWinRTDeviceInfoForInput (deviceIdentifier) : c->getWinRTDeviceInfoForOutput (deviceIdentifier); if (result->deviceInfo.deviceID.isEmpty()) return {}; JUCE_WINRT_MIDI_LOG ("Creating JUCE MIDI IO: " << deviceInfo.deviceID); result->client->addDeviceRemovalListener (*result); if (result->deviceInfo.containerID.isNotEmpty()) { result->isBLEDevice = result->client->isBleDevice (result->deviceInfo.containerID); result->client->addBleListener (*result); } return result; } WinRTMIDIDeviceInfo getDeviceInfo() const { return deviceInfo; } bool isBLE() const { return isBLEDevice; } ump::EndpointId getEndpointId() const { return isInput ? ump::EndpointId::make (ump::IOKind::src, deviceInfo.containerID) : ump::EndpointId::make (ump::IOKind::dst, deviceInfo.containerID); } ~WinRTIOWrapper() override { client->removeDeviceRemovalListener (*this); client->removeBleListener (*this); cancelPendingUpdate(); } private: WinRTIOWrapper() = default; void bleDeviceAdded (const String& containerID) override { if (containerID == deviceInfo.containerID) isBLEDevice = true; } void bleDeviceDisconnected (const String& containerID) override { if (containerID != deviceInfo.containerID) triggerAsyncUpdate(); JUCE_WINRT_MIDI_LOG ("Disconnecting MIDI port from BLE disconnection: " << deviceInfo.deviceID << " " << deviceInfo.containerID << " " << deviceInfo.name); } void deviceRemoved (const String& deviceId) override { if (deviceId == deviceInfo.deviceID) triggerAsyncUpdate(); } void handleAsyncUpdate() override { if (onDisconnect != nullptr) onDisconnect->disconnected(); } std::shared_ptr client; WinRTMIDIDeviceInfo deviceInfo; bool isBLEDevice = false; ump::DisconnectionListener* onDisconnect = nullptr; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WinRTIOWrapper) }; template static void openMidiPortThread (String threadName, String midiDeviceID, const ComSmartPtr& comFactory, ComSmartPtr& comPort) { std::thread { [&] { Thread::setCurrentThreadName (threadName); const WinRTWrapper::ScopedHString hDeviceId { midiDeviceID }; ComSmartPtr> asyncOp; const auto hr = comFactory->FromIdAsync (hDeviceId.get(), asyncOp.resetAndGetPointerAddress()); if (FAILED (hr)) return; std::promise> promise; auto future = promise.get_future(); auto callback = [p = std::move (promise)] (IAsyncOperation* asyncOpPtr, AsyncStatus) mutable { if (asyncOpPtr == nullptr) { p.set_value (nullptr); return E_ABORT; } ComSmartPtr result; const auto hr = asyncOpPtr->GetResults (result.resetAndGetPointerAddress()); if (FAILED (hr)) { p.set_value (nullptr); return hr; } p.set_value (std::move (result)); return S_OK; }; const auto ir = asyncOp->put_Completed (Microsoft::WRL::Callback> (std::move (callback)).Get()); if (FAILED (ir)) return; if (future.wait_for (std::chrono::milliseconds (2000)) == std::future_status::ready) comPort = future.get(); } }.join(); } class InputImplNative : public ump::Input::Impl::Native { public: ~InputImplNative() { disconnect(); } ump::EndpointId getEndpointId() const override { return wrapper->getEndpointId(); } ump::PacketProtocol getProtocol() const override { return dispatcher.getProtocol(); } static std::unique_ptr open (std::shared_ptr c, ump::DisconnectionListener& listener, const ump::EndpointId& identifier, ump::PacketProtocol protocol, ump::Consumer& consumer) { auto result = rawToUniquePtr (new InputImplNative (c, protocol, consumer)); auto w = WinRTIOWrapper::make (c, identifier, listener); if (w == nullptr) return {}; result->wrapper = std::move (w); openMidiPortThread ("Open WinRT MIDI input port", result->wrapper->getDeviceInfo().deviceID, result->client->getMidiInFactory(), result->port); if (result->port == nullptr) { JUCE_WINRT_MIDI_LOG ("Timed out waiting for midi input port creation"); return {}; } result->startTime = Time::getMillisecondCounterHiRes(); result->start(); return result; } private: InputImplNative (std::shared_ptr c, ump::PacketProtocol p, ump::Consumer& cb) : client (c), consumer (cb), dispatcher { 0, p, 4096 } { } void start() { if (midiInMessageToken.value != 0) { JUCE_WINRT_MIDI_LOG ("Input already started"); return; } const auto hr = port->add_MessageReceived ( Microsoft::WRL::Callback> ( [this] (IMidiInPort*, IMidiMessageReceivedEventArgs* args) { return midiInMessageReceived (args); } ).Get(), &midiInMessageToken); if (FAILED (hr)) { JUCE_WINRT_MIDI_LOG ("Failed to set MIDI input callback"); jassertfalse; } } void stop() { if (port != nullptr && midiInMessageToken.value != 0) port->remove_MessageReceived (midiInMessageToken); midiInMessageToken = {}; } void disconnect() { stop(); if (port != nullptr && wrapper->isBLE()) port->Release(); port = nullptr; } HRESULT midiInMessageReceived (IMidiMessageReceivedEventArgs* args) { ComSmartPtr message; auto hr = args->get_Message (message.resetAndGetPointerAddress()); if (FAILED (hr)) return hr; ComSmartPtr buffer; hr = message->get_RawData (buffer.resetAndGetPointerAddress()); if (FAILED (hr)) return hr; ComSmartPtr bufferByteAccess; hr = buffer->QueryInterface (bufferByteAccess.resetAndGetPointerAddress()); if (FAILED (hr)) return hr; uint8_t* bufferData = nullptr; hr = bufferByteAccess->Buffer (&bufferData); if (FAILED (hr)) return hr; uint32_t numBytes = 0; hr = buffer->get_Length (&numBytes); if (FAILED (hr)) return hr; ABI::Windows::Foundation::TimeSpan timespan; hr = message->get_Timestamp (×pan); if (FAILED (hr)) return hr; const Span bytes { unalignedPointerCast (bufferData), numBytes }; const auto time = convertTimeStamp (timespan.Duration); dispatcher.dispatch (bytes, time, [this] (const ump::View& view, double timestamp) { const ump::Iterator b { view.data(), view.size() }; const auto e = std::next (b); consumer.consume (b, e, timestamp); }); return S_OK; } double convertTimeStamp (int64 timestamp) { auto millisecondsSinceStart = static_cast (timestamp) / 10000.0; auto t = startTime + millisecondsSinceStart; auto now = Time::getMillisecondCounterHiRes(); if (t > now) { if (t > now + 2.0) startTime -= 1.0; t = now; } return t * 0.001; } std::shared_ptr client; ComSmartPtr port; ump::Consumer& consumer; EventRegistrationToken midiInMessageToken { 0 }; double startTime = 0; ump::BytestreamToUMPDispatcher dispatcher; std::unique_ptr> wrapper; }; class OutputImplNative : public ump::Output::Impl::Native { public: ~OutputImplNative() { disconnect(); } ump::EndpointId getEndpointId() const override { return wrapper->getEndpointId(); } bool send (ump::Iterator b, ump::Iterator e) override { for (const auto& view : makeRange (b, e)) { toBytestream.convert (view, 0, [&] (ump::BytesOnGroup bytesView, double) { sendBytestream (bytesView.bytes); }); } return true; } static std::unique_ptr open (std::shared_ptr c, ump::DisconnectionListener& listener, const ump::EndpointId& identifier) { auto result = rawToUniquePtr (new OutputImplNative { c }); auto w = WinRTIOWrapper::make (c, identifier, listener); if (w == nullptr) return {}; result->wrapper = std::move (w); openMidiPortThread ("Open WinRT MIDI output port", result->wrapper->getDeviceInfo().deviceID, result->client->getMidiOutFactory(), result->port); if (result->port == nullptr) return {}; auto* wrtWrapper = WinRTWrapper::getInstanceWithoutCreating(); if (wrtWrapper == nullptr) return {}; auto bufferFactory = wrtWrapper->getWRLFactory (&RuntimeClass_Windows_Storage_Streams_Buffer[0]); if (bufferFactory == nullptr) return {}; auto hr = bufferFactory->Create (static_cast (65536), result->buffer.resetAndGetPointerAddress()); if (FAILED (hr)) return {}; hr = result->buffer->QueryInterface (result->bufferByteAccess.resetAndGetPointerAddress()); if (FAILED (hr)) return {}; hr = result->bufferByteAccess->Buffer (&result->bufferData); if (FAILED (hr)) return {}; return result; } private: explicit OutputImplNative (std::shared_ptr c) : client (std::move (c)) { } void disconnect() { if (port != nullptr && wrapper->isBLE()) port->Release(); port = nullptr; } void sendBytestream (Span message) { if (port == nullptr) return; auto numBytes = message.size(); auto hr = buffer->put_Length ((UINT32) numBytes); if (FAILED (hr)) { jassertfalse; return; } memcpy_s (bufferData, numBytes, message.data(), numBytes); port->SendBuffer (buffer); } std::shared_ptr client; ComSmartPtr port; ump::ToBytestreamConverter toBytestream { 4096 }; ComSmartPtr buffer; ComSmartPtr bufferByteAccess; uint8_t* bufferData = nullptr; std::unique_ptr> wrapper; }; class SessionImplNative : public ump::Session::Impl::Native { public: SessionImplNative (std::shared_ptr c, const String& x) : client (c), name (x) {} String getName() const override { return name; } std::unique_ptr connectInput (ump::DisconnectionListener& listener, const ump::EndpointId& id, ump::PacketProtocol protocol, ump::Consumer& consumer) override { return InputImplNative::open (client, listener, id, protocol, consumer); } std::unique_ptr connectOutput (ump::DisconnectionListener& listener, const ump::EndpointId& id) override { return OutputImplNative::open (client, listener, id); } private: std::shared_ptr client; String name; }; class EndpointsImplNative : public ump::Endpoints::Impl::Native { public: ump::Backend getBackend() const override { return ump::Backend::winrt; } bool isVirtualMidiUmpServiceActive() const override { return false; } bool isVirtualMidiBytestreamServiceActive() const override { return false; } void getEndpoints (std::vector& x) const override { client->getEndpoints (x); } std::optional getEndpoint (const ump::EndpointId& x) const override { return client->getEndpoint (x); } std::optional getStaticDeviceInfo (const ump::EndpointId& x) const override { return client->getStaticDeviceInfo (x); } std::unique_ptr makeSession (const String& x) override { return std::make_unique (client, x); } static std::unique_ptr make (ump::EndpointsListener& l) { return rawToUniquePtr (new EndpointsImplNative { l }); } private: explicit EndpointsImplNative (ump::EndpointsListener& l) : client (Client::make (l)) {} std::shared_ptr client; }; }; #endif struct Win32 { struct ITraits { using Ptr = HMIDIIN; using Caps = MIDIINCAPS; static inline const auto getNum = midiInGetNumDevs; static inline const auto getCaps = midiInGetDevCaps; static inline const auto message = midiInMessage; static inline const auto isInput = true; }; struct OTraits { using Ptr = HMIDIOUT; using Caps = MIDIOUTCAPS; static inline const auto getNum = midiOutGetNumDevs; static inline const auto getCaps = midiOutGetDevCaps; static inline const auto message = midiOutMessage; static inline const auto isInput = false; }; template static void getAvailableDevices (const Traits&, std::vector& result) { const auto deviceCaps = [&] { std::vector devices; const auto end = Traits::getNum(); for (auto i = (decltype (end)) 0; i < end; ++i) if (typename Traits::Caps mc{}; Traits::getCaps (i, &mc, sizeof (mc)) == MMSYSERR_NOERROR) devices.push_back (mc); return devices; }(); StringArray deviceIDs, deviceNames; for (const auto [i, device] : enumerate (deviceCaps)) { const auto name = device.szPname; const auto identifier = std::invoke ([index = i]() -> String { ULONG size = 0; if (Traits::message ((typename Traits::Ptr) index, DRV_QUERYDEVICEINTERFACESIZE, (DWORD_PTR) &size, 0) != MMSYSERR_NOERROR) return {}; WCHAR interfaceName[512]{}; if (! isPositiveAndBelow (size, std::size (interfaceName)) || Traits::message ((typename Traits::Ptr) index, DRV_QUERYDEVICEINTERFACE, (DWORD_PTR) interfaceName, sizeof (interfaceName)) != MMSYSERR_NOERROR) { return {}; } return interfaceName; }); deviceNames.add (name); deviceIDs.add (identifier.isNotEmpty() ? identifier : name); } for (auto* list : { &deviceIDs, &deviceNames }) list->appendNumbersToDuplicates (false, false, CharPointer_UTF8 ("-"), CharPointer_UTF8 ("")); constexpr auto direction = Traits::isInput ? ump::BlockDirection::sender : ump::BlockDirection::receiver; for (auto [index, id] : enumerate (deviceIDs, int{})) { const auto fullInfo = ump::IOHelpers::makeProxyEndpoint ({ deviceNames[index], id }, direction); result.push_back (fullInfo); } } class MidiHeader { public: void prepare (HMIDIIN device) { hdr = {}; hdr.lpData = data.data(); hdr.dwBufferLength = (DWORD) data.size(); midiInPrepareHeader (device, &hdr, sizeof (hdr)); } void unprepare (HMIDIIN device) { if ((hdr.dwFlags & WHDR_DONE) != 0) { int c = 10; while (--c >= 0 && midiInUnprepareHeader (device, &hdr, sizeof (hdr)) == MIDIERR_STILLPLAYING) Thread::sleep (20); jassert (c >= 0); } } void write (HMIDIIN device) { hdr.dwBytesRecorded = 0; midiInAddBuffer (device, &hdr, sizeof (hdr)); } void writeIfFinished (HMIDIIN device) { if ((hdr.dwFlags & WHDR_DONE) != 0) write (device); } private: MIDIHDR hdr{}; std::array data{}; }; template class DeviceCache { public: std::shared_ptr open (const ump::EndpointId& id) { const std::scoped_lock lock { mutex }; if (const auto iter = devices.find (id); iter != devices.end()) if (auto strong = iter->second.lock()) return strong; std::unique_ptr uniqueDevice = openInternal (id); std::shared_ptr sharedDevice { std::move (uniqueDevice) }; devices.emplace (id, sharedDevice); return sharedDevice; } private: std::mutex mutex; std::map> devices; }; // A device can only be opened by one client at a time. In order to allow multiple inputs // to open the same device in JUCE, we share the device between all inputs that are // currently using it. class InputDevice : private AsyncUpdater { public: static std::shared_ptr open (const ump::EndpointId& id) { static DeviceCache devices; return devices.open (id); } ~InputDevice() override { allInputs().remove (*this); if (deviceHandle == nullptr) return; unprepareAllHeaders(); midiInReset (deviceHandle); midiInStop (deviceHandle); for (int count = 5; --count >= 0;) { if (midiInClose (deviceHandle) == MMSYSERR_NOERROR) break; Sleep (20); } } ump::EndpointId getEndpointId() const { return endpointId; } void addConsumer (ump::Consumer& c) { consumers.add (c); } void removeConsumer (ump::Consumer& c) { consumers.add (c); } void addDisconnectListener (ump::DisconnectionListener& l) { disconnectListeners.add (&l); } void removeDisconnectListener (ump::DisconnectionListener& l) { disconnectListeners.remove (&l); } private: static std::unique_ptr openInternal (const ump::EndpointId& id) { std::vector endpoints; getAvailableDevices (ITraits{}, endpoints); const auto iter = std::find_if (endpoints.begin(), endpoints.end(), [&] (const auto& x) { return x.id == id; }); if (iter == endpoints.end()) return {}; const auto deviceID = std::distance (endpoints.begin(), iter); auto result = rawToUniquePtr (new InputDevice { id }); HMIDIIN handle{}; const auto err = midiInOpen (&handle, (UINT) deviceID, (DWORD_PTR) &midiInCallback, (DWORD_PTR) result.get(), CALLBACK_FUNCTION); if (err != MMSYSERR_NOERROR) return {}; result->deviceHandle = handle; for (auto& header : result->headers) { header.prepare (handle); header.write (handle); } if (midiInStart (handle) != MMSYSERR_NOERROR) return {}; return result; } explicit InputDevice (const ump::EndpointId& x) : endpointId (x) { allInputs().add (*this); } void handleMessage (const uint8* bytes, uint32 timeStamp) { if (bytes[0] >= 0x80) { auto len = MidiMessage::getMessageLengthFromFirstByte (bytes[0]); auto time = convertTimeStamp (timeStamp); dispatcher.dispatch ({ unalignedPointerCast (bytes), (size_t) len }, time, [this] (const ump::View& view, double timestamp) { const ump::Iterator b { view.data(), view.size() }; const auto e = std::next (b); consumers.call ([&] (ump::Consumer& c) { c.consume (b, e, timestamp); }); }); writeFinishedBlocks(); } } void handleSysEx (MIDIHDR* hdr, uint32 timeStamp) { if (hdr->dwBytesRecorded > 0) { auto time = convertTimeStamp (timeStamp); dispatcher.dispatch ({ unalignedPointerCast (hdr->lpData), (size_t) hdr->dwBytesRecorded }, time, [this] (const ump::View& view, double timestamp) { const ump::Iterator b { view.data(), view.size() }; const auto e = std::next (b); consumers.call ([&] (ump::Consumer& c) { c.consume (b, e, timestamp); }); }); writeFinishedBlocks(); } } void disconnected() { triggerAsyncUpdate(); } void handleAsyncUpdate() override { disconnectListeners.call ([] (auto& x) { x.disconnected(); }); } void writeFinishedBlocks() { for (auto& header : headers) header.writeIfFinished (deviceHandle); } void unprepareAllHeaders() { for (auto& header : headers) header.unprepare (deviceHandle); } double convertTimeStamp (uint32 timeStamp) { auto t = startTime + timeStamp; auto now = Time::getMillisecondCounterHiRes(); if (t > now) { if (t > now + 2.0) startTime -= 1.0; t = now; } return t * 0.001; } static void CALLBACK midiInCallback (HMIDIIN, UINT uMsg, DWORD_PTR dwInstance, DWORD_PTR midiMessage, DWORD_PTR timeStamp) { auto* collector = reinterpret_cast (dwInstance); allInputs().call ([&] (auto& l) { if (collector != &l) return; switch (uMsg) { case MIM_DATA: l.handleMessage ((const uint8*) &midiMessage, (uint32) timeStamp); break; case MIM_LONGDATA: l.handleSysEx ((MIDIHDR*) midiMessage, (uint32) timeStamp); break; case MIM_CLOSE: l.disconnected(); break; } }); } static WaitFreeListeners& allInputs() { static WaitFreeListeners result; return result; } ump::EndpointId endpointId; HMIDIIN deviceHandle = nullptr; std::array headers; double startTime = Time::getMillisecondCounterHiRes(); WaitFreeListeners consumers; ListenerList disconnectListeners; // The shared input always converts to plain MIDI 1.0. Clients that want MIDI 2.0 have // their own converters. ump::BytestreamToUMPDispatcher dispatcher { 0, ump::PacketProtocol::MIDI_1_0, 4096 }; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (InputDevice) }; class OutputDevice : private AsyncUpdater { public: ~OutputDevice() override { allOutputs().remove (*this); cancelPendingUpdate(); if (handle != nullptr) midiOutClose (handle); } static std::shared_ptr open (const ump::EndpointId& id) { static DeviceCache devices; return devices.open (id); } ump::EndpointId getEndpointId() const { return endpointId; } bool send (ump::Iterator b, ump::Iterator e) { for (const auto& view : makeRange (b, e)) { toBytestream.convert (view, 0, [&] (ump::BytesOnGroup bytesView, double) { sendBytestream (bytesView.bytes); }); } return true; } void addDisconnectListener (ump::DisconnectionListener& l) { disconnectListeners.add (&l); } void removeDisconnectListener (ump::DisconnectionListener& l) { disconnectListeners.remove (&l); } private: static std::unique_ptr openInternal (const ump::EndpointId& id) { std::vector endpoints; getAvailableDevices (OTraits{}, endpoints); const auto iter = std::find_if (endpoints.begin(), endpoints.end(), [&] (const auto& x) { return x.id == id; }); if (iter == endpoints.end()) return {}; const auto deviceID = std::distance (endpoints.begin(), iter); for (auto i = 0; i < 4; ++i) { auto result = rawToUniquePtr (new OutputDevice (id)); HMIDIOUT h = nullptr; auto res = midiOutOpen (&h, (UINT) deviceID, (DWORD_PTR) &midiOutCallback, (DWORD_PTR) result.get(), CALLBACK_FUNCTION); switch (res) { case MMSYSERR_NOERROR: result->handle = h; return result; case MMSYSERR_ALLOCATED: Sleep (100); break; default: return {}; } } return {}; } explicit OutputDevice (const ump::EndpointId& x) : endpointId (x) { allOutputs().add (*this); } void sendBytestream (Span message) { if (message.empty()) return; if (message.size() > 3 || message[0] == std::byte { 0xf0 }) { MIDIHDR h = {}; h.lpData = (char*) message.data(); h.dwBytesRecorded = h.dwBufferLength = (DWORD) message.size(); if (midiOutPrepareHeader (handle, &h, sizeof (MIDIHDR)) == MMSYSERR_NOERROR) { auto res = midiOutLongMsg (handle, &h, sizeof (MIDIHDR)); if (res == MMSYSERR_NOERROR) { while ((h.dwFlags & MHDR_DONE) == 0) Sleep (1); int count = 500; // 1 sec timeout while (--count >= 0) { res = midiOutUnprepareHeader (handle, &h, sizeof (MIDIHDR)); if (res == MIDIERR_STILLPLAYING) Sleep (2); else break; } } } } else { const auto msg = ByteOrder::makeInt (0 < message.size() ? (uint8_t) message[0] : 0, 1 < message.size() ? (uint8_t) message[1] : 0, 2 < message.size() ? (uint8_t) message[2] : 0, 0); for (int i = 0; i < 50; ++i) { if (midiOutShortMsg (handle, msg) != MIDIERR_NOTREADY) break; Sleep (1); } } } void disconnected() { triggerAsyncUpdate(); } void handleAsyncUpdate() override { disconnectListeners.call ([] (auto& x) { x.disconnected(); }); } static void CALLBACK midiOutCallback (HMIDIOUT, UINT wMsg, DWORD_PTR dwInstance, DWORD_PTR, DWORD_PTR) { auto* collector = reinterpret_cast (dwInstance); allOutputs().call ([&] (auto& l) { if (collector != &l) return; switch (wMsg) { case MOM_CLOSE: l.disconnected(); break; } }); } static WaitFreeListeners& allOutputs() { static WaitFreeListeners result; return result; } ump::EndpointId endpointId; HMIDIOUT handle = nullptr; ListenerList disconnectListeners; ump::ToBytestreamConverter toBytestream { 4096 }; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OutputDevice) }; class InputImplNative : public ump::Input::Impl::Native, private ump::Consumer { public: InputImplNative (std::shared_ptr d, ump::DisconnectionListener& l, ump::PacketProtocol p, Consumer& c) : device (d), listener (l), consumer (c), converter (p) { device->addConsumer (*this); device->addDisconnectListener (listener); } ~InputImplNative() override { device->removeDisconnectListener (listener); device->removeConsumer (*this); } ump::EndpointId getEndpointId() const override { return device->getEndpointId(); } ump::PacketProtocol getProtocol() const override { return converter.getProtocol(); } private: void consume (ump::Iterator b, ump::Iterator e, double time) override { converter.convert (b, e, [&] (ump::View v) { const ump::Iterator iter { v.data(), v.size() }; consumer.consume (iter, std::next (iter), time); }); } std::shared_ptr device; ump::DisconnectionListener& listener; Consumer& consumer; ump::GenericUMPConverter converter; }; class SessionImplNative : public ump::Session::Impl::Native { public: explicit SessionImplNative (const String& x) : name (x) {} String getName() const override { return name; } std::unique_ptr connectInput (ump::DisconnectionListener& listener, const ump::EndpointId& endpoint, ump::PacketProtocol protocol, ump::Consumer& consumer) override { auto device = InputDevice::open (endpoint); if (device == nullptr) return {}; return std::make_unique (device, listener, protocol, consumer); } std::unique_ptr connectOutput (ump::DisconnectionListener& listener, const ump::EndpointId& endpoint) override { class Result : public ump::Output::Impl::Native { public: Result (std::shared_ptr d, ump::DisconnectionListener& l) : device (d), listener (l) { device->addDisconnectListener (listener); } ~Result() override { device->removeDisconnectListener (listener); } ump::EndpointId getEndpointId() const override { return device->getEndpointId(); } bool send (ump::Iterator b, ump::Iterator e) override { return device->send (b, e); } private: std::shared_ptr device; ump::DisconnectionListener& listener; }; auto device = OutputDevice::open (endpoint); if (device == nullptr) return {}; return std::make_unique (device, listener); } private: String name; }; class EndpointsImplNative : public ump::Endpoints::Impl::Native { public: ump::Backend getBackend() const override { return ump::Backend::winmm; } bool isVirtualMidiUmpServiceActive() const override { return false; } bool isVirtualMidiBytestreamServiceActive() const override { return false; } void getEndpoints (std::vector& storage) const override { std::transform (cachedEndpoints.begin(), cachedEndpoints.end(), std::back_inserter (storage), [] (auto& p) { return p.first; }); } std::optional getEndpoint (const ump::EndpointId& x) const override { const auto iter = cachedEndpoints.find (x); if (iter != cachedEndpoints.end()) return iter->second.endpoint; return {}; } std::optional getStaticDeviceInfo (const ump::EndpointId& x) const override { const auto iter = cachedEndpoints.find (x); if (iter != cachedEndpoints.end()) return iter->second.info; return {}; } std::unique_ptr makeSession (const String& x) override { return std::make_unique (x); } static std::unique_ptr make (ump::EndpointsListener& listener) { return rawToUniquePtr (new EndpointsImplNative { listener }); } private: explicit EndpointsImplNative (ump::EndpointsListener& l) : listener (l) { updateCachedEndpoints(); } void updateCachedEndpoints() { JUCE_ASSERT_MESSAGE_THREAD; std::vector buffer; getAvailableDevices (ITraits{}, buffer); getAvailableDevices (OTraits{}, buffer); cachedEndpoints.clear(); for (auto& item : buffer) cachedEndpoints.emplace (item.id, item); // If this is hit, we got an identical ID for an input and output device jassert (cachedEndpoints.size() == buffer.size()); } ump::EndpointsListener& listener; std::map cachedEndpoints; DeviceChangeDetector detector { L"JuceMidiDeviceDetector_", [&] { updateCachedEndpoints(); listener.endpointsChanged(); } }; }; }; }; auto ump::Endpoints::Impl::Native::make (EndpointsListener& l) -> std::unique_ptr { #if JUCE_USE_WINDOWS_MIDI_SERVICES if (auto ptr = MidiServices::makeEndpoints (l)) return ptr; #endif #if JUCE_USE_WINRT_MIDI if (auto winrtSession = WindowsMidiHelpers::Winrt::EndpointsImplNative::make (l)) return winrtSession; #endif #if JUCE_FORCE_WINRT_MIDI return nullptr; #else return WindowsMidiHelpers::Win32::EndpointsImplNative::make (l); #endif } } // namespace juce