midirMIDIPlatformService.cpp (6700B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 * You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 #include "midirMIDIPlatformService.h" 6 7 #include "MIDILog.h" 8 #include "mozilla/Logging.h" 9 #include "mozilla/StaticMutex.h" 10 #include "mozilla/TimeStamp.h" 11 #include "mozilla/dom/MIDIPlatformRunnables.h" 12 #include "mozilla/dom/MIDIPort.h" 13 #include "mozilla/dom/MIDIPortInterface.h" 14 #include "mozilla/dom/MIDIPortParent.h" 15 #include "mozilla/dom/MIDITypes.h" 16 #include "mozilla/dom/MIDIUtils.h" 17 #include "mozilla/dom/midi/midir_impl_ffi_generated.h" 18 #include "mozilla/ipc/BackgroundParent.h" 19 #include "nsIThread.h" 20 21 using namespace mozilla; 22 using namespace mozilla::dom; 23 using namespace mozilla::ipc; 24 25 static_assert(sizeof(TimeStamp) == sizeof(GeckoTimeStamp)); 26 27 /** 28 * Runnable used for to send messages asynchronously on the I/O thread. 29 */ 30 class SendRunnable : public MIDIBackgroundRunnable { 31 public: 32 explicit SendRunnable(const nsAString& aPortID, const MIDIMessage& aMessage) 33 : MIDIBackgroundRunnable("SendRunnable"), 34 mPortID(aPortID), 35 mMessage(aMessage) {} 36 ~SendRunnable() = default; 37 virtual void RunInternal() { 38 MIDIPlatformService::AssertThread(); 39 if (!MIDIPlatformService::IsRunning()) { 40 // Some send operations might outlive the service, bail out and do nothing 41 return; 42 } 43 midirMIDIPlatformService* srv = 44 static_cast<midirMIDIPlatformService*>(MIDIPlatformService::Get()); 45 srv->SendMessage(mPortID, mMessage); 46 } 47 48 private: 49 nsString mPortID; 50 MIDIMessage mMessage; 51 }; 52 53 // static 54 StaticMutex midirMIDIPlatformService::gOwnerThreadMutex; 55 56 // static 57 MOZ_RUNINIT nsCOMPtr<nsISerialEventTarget> 58 midirMIDIPlatformService::gOwnerThread; 59 60 midirMIDIPlatformService::midirMIDIPlatformService() 61 : mImplementation(nullptr) { 62 StaticMutexAutoLock lock(gOwnerThreadMutex); 63 gOwnerThread = OwnerThread(); 64 } 65 66 midirMIDIPlatformService::~midirMIDIPlatformService() { 67 LOG("midir_impl_shutdown"); 68 if (mImplementation) { 69 midir_impl_shutdown(mImplementation); 70 } 71 StaticMutexAutoLock lock(gOwnerThreadMutex); 72 gOwnerThread = nullptr; 73 } 74 75 // static 76 void midirMIDIPlatformService::AddPort(const nsString* aId, 77 const nsString* aName, bool aInput) { 78 MIDIPortType type = aInput ? MIDIPortType::Input : MIDIPortType::Output; 79 MIDIPortInfo port(*aId, *aName, u""_ns, u""_ns, static_cast<uint32_t>(type)); 80 MIDIPlatformService::Get()->AddPortInfo(port); 81 } 82 83 // static 84 void midirMIDIPlatformService::RemovePort(const nsString* aId, 85 const nsString* aName, bool aInput) { 86 MIDIPortType type = aInput ? MIDIPortType::Input : MIDIPortType::Output; 87 MIDIPortInfo port(*aId, *aName, u""_ns, u""_ns, static_cast<uint32_t>(type)); 88 MIDIPlatformService::Get()->RemovePortInfo(port); 89 } 90 91 void midirMIDIPlatformService::Init() { 92 if (mImplementation) { 93 return; 94 } 95 96 mImplementation = midir_impl_init(AddPort); 97 98 if (mImplementation) { 99 MIDIPlatformService::Get()->SendPortList(); 100 } else { 101 LOG("midir_impl_init failure"); 102 } 103 } 104 105 // static 106 void midirMIDIPlatformService::CheckAndReceive(const nsString* aId, 107 const uint8_t* aData, 108 size_t aLength, 109 const GeckoTimeStamp* aTimeStamp, 110 uint64_t aMicros) { 111 nsTArray<uint8_t> data; 112 data.AppendElements(aData, aLength); 113 const TimeStamp* openTime = reinterpret_cast<const TimeStamp*>(aTimeStamp); 114 TimeStamp timestamp = 115 *openTime + TimeDuration::FromMicroseconds(static_cast<double>(aMicros)); 116 MIDIMessage message(data, timestamp); 117 LogMIDIMessage(message, *aId, MIDIPortType::Input); 118 nsTArray<MIDIMessage> messages; 119 messages.AppendElement(message); 120 121 nsCOMPtr<nsIRunnable> r(new ReceiveRunnable(*aId, messages)); 122 StaticMutexAutoLock lock(gOwnerThreadMutex); 123 if (gOwnerThread) { 124 gOwnerThread->Dispatch(r, NS_DISPATCH_NORMAL); 125 } 126 } 127 128 void midirMIDIPlatformService::Refresh() { 129 midir_impl_refresh(mImplementation, AddPort, RemovePort); 130 } 131 132 void midirMIDIPlatformService::Open(MIDIPortParent* aPort) { 133 AssertThread(); 134 MOZ_ASSERT(aPort); 135 nsString id = aPort->MIDIPortInterface::Id(); 136 TimeStamp openTimeStamp = TimeStamp::Now(); 137 if (midir_impl_open_port(mImplementation, &id, 138 reinterpret_cast<GeckoTimeStamp*>(&openTimeStamp), 139 CheckAndReceive)) { 140 LOG("MIDI port open: %s at t=%lf", NS_ConvertUTF16toUTF8(id).get(), 141 (openTimeStamp - TimeStamp::ProcessCreation()).ToSeconds()); 142 nsCOMPtr<nsIRunnable> r(new SetStatusRunnable( 143 aPort, aPort->DeviceState(), MIDIPortConnectionState::Open)); 144 OwnerThread()->Dispatch(r.forget()); 145 } else { 146 LOG("MIDI port open failed: %s", NS_ConvertUTF16toUTF8(id).get()); 147 } 148 } 149 150 void midirMIDIPlatformService::Stop() { 151 // Nothing to do here AFAIK 152 } 153 154 void midirMIDIPlatformService::ScheduleSend(const nsAString& aPortId) { 155 AssertThread(); 156 LOG("MIDI port schedule send %s", NS_ConvertUTF16toUTF8(aPortId).get()); 157 nsTArray<MIDIMessage> messages; 158 GetMessages(aPortId, messages); 159 TimeStamp now = TimeStamp::Now(); 160 for (const auto& message : messages) { 161 if (message.timestamp().IsNull()) { 162 SendMessage(aPortId, message); 163 } else { 164 double delay = (message.timestamp() - now).ToMilliseconds(); 165 if (delay < 1.0) { 166 SendMessage(aPortId, message); 167 } else { 168 nsCOMPtr<nsIRunnable> r(new SendRunnable(aPortId, message)); 169 OwnerThread()->DelayedDispatch(r.forget(), 170 static_cast<uint32_t>(delay)); 171 } 172 } 173 } 174 } 175 176 void midirMIDIPlatformService::ScheduleClose(MIDIPortParent* aPort) { 177 AssertThread(); 178 MOZ_ASSERT(aPort); 179 nsString id = aPort->MIDIPortInterface::Id(); 180 LOG("MIDI port schedule close %s", NS_ConvertUTF16toUTF8(id).get()); 181 if (aPort->ConnectionState() == MIDIPortConnectionState::Open) { 182 midir_impl_close_port(mImplementation, &id); 183 nsCOMPtr<nsIRunnable> r(new SetStatusRunnable( 184 aPort, aPort->DeviceState(), MIDIPortConnectionState::Closed)); 185 OwnerThread()->Dispatch(r.forget()); 186 } 187 } 188 189 void midirMIDIPlatformService::SendMessage(const nsAString& aPortId, 190 const MIDIMessage& aMessage) { 191 LOG("MIDI send message on %s", NS_ConvertUTF16toUTF8(aPortId).get()); 192 LogMIDIMessage(aMessage, aPortId, MIDIPortType::Output); 193 midir_impl_send(mImplementation, &aPortId, &aMessage.data()); 194 }