TestMIDIPlatformService.cpp (9725B)
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 "TestMIDIPlatformService.h" 6 7 #include "mozilla/dom/MIDIPlatformRunnables.h" 8 #include "mozilla/dom/MIDIPort.h" 9 #include "mozilla/dom/MIDIPortInterface.h" 10 #include "mozilla/dom/MIDIPortParent.h" 11 #include "mozilla/dom/MIDITypes.h" 12 #include "mozilla/dom/MIDIUtils.h" 13 #include "mozilla/ipc/BackgroundParent.h" 14 #include "nsIThread.h" 15 16 using namespace mozilla; 17 using namespace mozilla::dom; 18 using namespace mozilla::ipc; 19 20 /** 21 * Runnable used for making sure ProcessMessages only happens on the IO thread. 22 * 23 */ 24 class ProcessMessagesRunnable : public mozilla::Runnable { 25 public: 26 explicit ProcessMessagesRunnable(const nsAString& aPortID) 27 : Runnable("ProcessMessagesRunnable"), mPortID(aPortID) {} 28 ~ProcessMessagesRunnable() = default; 29 NS_IMETHOD Run() override { 30 // If service is no longer running, just exist without processing. 31 if (!MIDIPlatformService::IsRunning()) { 32 return NS_OK; 33 } 34 TestMIDIPlatformService* srv = 35 static_cast<TestMIDIPlatformService*>(MIDIPlatformService::Get()); 36 srv->ProcessMessages(mPortID); 37 return NS_OK; 38 } 39 40 private: 41 nsString mPortID; 42 }; 43 44 /** 45 * Runnable used for allowing IO thread to queue more messages for processing, 46 * since it can't access the service object directly. 47 * 48 */ 49 class QueueMessagesRunnable : public MIDIBackgroundRunnable { 50 public: 51 QueueMessagesRunnable(const nsAString& aPortID, 52 const nsTArray<MIDIMessage>& aMsgs) 53 : MIDIBackgroundRunnable("QueueMessagesRunnable"), 54 mPortID(aPortID), 55 mMsgs(aMsgs.Clone()) {} 56 ~QueueMessagesRunnable() = default; 57 virtual void RunInternal() { 58 MIDIPlatformService::AssertThread(); 59 MIDIPlatformService::Get()->QueueMessages(mPortID, mMsgs); 60 } 61 62 private: 63 nsString mPortID; 64 nsTArray<MIDIMessage> mMsgs; 65 }; 66 67 TestMIDIPlatformService::TestMIDIPlatformService() 68 : mControlInputPort(u"b744eebe-f7d8-499b-872b-958f63c8f522"_ns, 69 u"Test Control MIDI Device Input Port"_ns, 70 u"Test Manufacturer"_ns, u"1.0.0"_ns, 71 static_cast<uint32_t>(MIDIPortType::Input)), 72 mControlOutputPort(u"ab8e7fe8-c4de-436a-a960-30898a7c9a3d"_ns, 73 u"Test Control MIDI Device Output Port"_ns, 74 u"Test Manufacturer"_ns, u"1.0.0"_ns, 75 static_cast<uint32_t>(MIDIPortType::Output)), 76 mStateTestInputPort(u"a9329677-8588-4460-a091-9d4a7f629a48"_ns, 77 u"Test State MIDI Device Input Port"_ns, 78 u"Test Manufacturer"_ns, u"1.0.0"_ns, 79 static_cast<uint32_t>(MIDIPortType::Input)), 80 mStateTestOutputPort(u"478fa225-b5fc-4fa6-a543-d32d9cb651e7"_ns, 81 u"Test State MIDI Device Output Port"_ns, 82 u"Test Manufacturer"_ns, u"1.0.0"_ns, 83 static_cast<uint32_t>(MIDIPortType::Output)), 84 mAlwaysClosedTestOutputPort(u"f87d0c76-3c68-49a9-a44f-700f1125c07a"_ns, 85 u"Always Closed MIDI Device Output Port"_ns, 86 u"Test Manufacturer"_ns, u"1.0.0"_ns, 87 static_cast<uint32_t>(MIDIPortType::Output)), 88 mDoRefresh(false), 89 mIsInitialized(false) { 90 MIDIPlatformService::AssertThread(); 91 } 92 93 TestMIDIPlatformService::~TestMIDIPlatformService() { 94 MIDIPlatformService::AssertThread(); 95 } 96 97 void TestMIDIPlatformService::Init() { 98 MIDIPlatformService::AssertThread(); 99 100 if (mIsInitialized) { 101 return; 102 } 103 mIsInitialized = true; 104 105 // Treat all of our special ports as always connected. When the service comes 106 // up, prepopulate the port list with them. 107 MIDIPlatformService::Get()->AddPortInfo(mControlInputPort); 108 MIDIPlatformService::Get()->AddPortInfo(mControlOutputPort); 109 MIDIPlatformService::Get()->AddPortInfo(mAlwaysClosedTestOutputPort); 110 MIDIPlatformService::Get()->AddPortInfo(mStateTestOutputPort); 111 nsCOMPtr<nsIRunnable> r(new SendPortListRunnable()); 112 113 // Start the IO Thread. 114 OwnerThread()->Dispatch(r.forget()); 115 } 116 117 void TestMIDIPlatformService::Refresh() { 118 if (mDoRefresh) { 119 AddPortInfo(mStateTestInputPort); 120 mDoRefresh = false; 121 } 122 } 123 124 void TestMIDIPlatformService::Open(MIDIPortParent* aPort) { 125 MOZ_ASSERT(aPort); 126 MIDIPortConnectionState s = MIDIPortConnectionState::Open; 127 if (aPort->MIDIPortInterface::Id() == mAlwaysClosedTestOutputPort.id()) { 128 // If it's the always closed testing port, act like it's already opened 129 // exclusively elsewhere. 130 s = MIDIPortConnectionState::Closed; 131 } 132 // Connection events are just simulated on the background thread, no need to 133 // push to IO thread. 134 nsCOMPtr<nsIRunnable> r( 135 new SetStatusRunnable(aPort, aPort->DeviceState(), s)); 136 OwnerThread()->Dispatch(r.forget()); 137 } 138 139 void TestMIDIPlatformService::ScheduleClose(MIDIPortParent* aPort) { 140 AssertThread(); 141 MOZ_ASSERT(aPort); 142 if (aPort->ConnectionState() == MIDIPortConnectionState::Open) { 143 // Connection events are just simulated on the background thread, no need to 144 // push to IO thread. 145 nsCOMPtr<nsIRunnable> r(new SetStatusRunnable( 146 aPort, aPort->DeviceState(), MIDIPortConnectionState::Closed)); 147 OwnerThread()->Dispatch(r.forget()); 148 } 149 } 150 151 void TestMIDIPlatformService::Stop() { MIDIPlatformService::AssertThread(); } 152 153 void TestMIDIPlatformService::ScheduleSend(const nsAString& aPortId) { 154 AssertThread(); 155 nsCOMPtr<nsIRunnable> r(new ProcessMessagesRunnable(aPortId)); 156 OwnerThread()->Dispatch(r.forget()); 157 } 158 159 void TestMIDIPlatformService::ProcessMessages(const nsAString& aPortId) { 160 nsTArray<MIDIMessage> msgs; 161 GetMessagesBefore(aPortId, TimeStamp::Now(), msgs); 162 163 for (MIDIMessage msg : msgs) { 164 // receiving message from test control port 165 if (aPortId == mControlOutputPort.id()) { 166 switch (msg.data()[0]) { 167 // Hit a note, get a test! 168 case 0x90: 169 switch (msg.data()[1]) { 170 // Echo data/timestamp back through output port 171 case 0x00: { 172 nsCOMPtr<nsIRunnable> r( 173 new ReceiveRunnable(mControlInputPort.id(), msg)); 174 OwnerThread()->Dispatch(r, NS_DISPATCH_NORMAL); 175 break; 176 } 177 // Cause control test ports to connect 178 case 0x01: { 179 nsCOMPtr<nsIRunnable> r1( 180 new AddPortRunnable(mStateTestInputPort)); 181 OwnerThread()->Dispatch(r1, NS_DISPATCH_NORMAL); 182 break; 183 } 184 // Cause control test ports to disconnect 185 case 0x02: { 186 nsCOMPtr<nsIRunnable> r1( 187 new RemovePortRunnable(mStateTestInputPort)); 188 OwnerThread()->Dispatch(r1, NS_DISPATCH_NORMAL); 189 break; 190 } 191 // Test for packet timing 192 case 0x03: { 193 // Append a few echo command packets in reverse timing order, 194 // should come out in correct order on other end. 195 nsTArray<MIDIMessage> newMsgs; 196 nsTArray<uint8_t> msg; 197 msg.AppendElement(0x90); 198 msg.AppendElement(0x00); 199 msg.AppendElement(0x00); 200 // PR_Now() returns nanosecods, and we need a double with 201 // fractional milliseconds. 202 TimeStamp currentTime = TimeStamp::Now(); 203 for (int i = 0; i <= 5; ++i) { 204 // Insert messages with timestamps in reverse order, to make 205 // sure we're sorting correctly. 206 newMsgs.AppendElement(MIDIMessage( 207 msg, currentTime - TimeDuration::FromMilliseconds(i * 2))); 208 } 209 nsCOMPtr<nsIRunnable> r( 210 new QueueMessagesRunnable(aPortId, newMsgs)); 211 OwnerThread()->Dispatch(r, NS_DISPATCH_NORMAL); 212 break; 213 } 214 // Causes the next refresh to add new ports to the list 215 case 0x04: { 216 mDoRefresh = true; 217 break; 218 } 219 default: 220 NS_WARNING("Unknown Test MIDI message received!"); 221 } 222 break; 223 // Sysex tests 224 case 0xF0: 225 switch (msg.data()[1]) { 226 // Echo data/timestamp back through output port 227 case 0x00: { 228 nsCOMPtr<nsIRunnable> r( 229 new ReceiveRunnable(mControlInputPort.id(), msg)); 230 OwnerThread()->Dispatch(r, NS_DISPATCH_NORMAL); 231 break; 232 } 233 // Test for system real time messages in the middle of sysex 234 // messages. 235 case 0x01: { 236 nsTArray<uint8_t> msgs; 237 const uint8_t msg[] = {0xF0, 0x01, 0xFA, 0x02, 0x03, 238 0x04, 0xF8, 0x05, 0xF7}; 239 // Can't use AppendElements on an array here, so just do range 240 // based loading. 241 for (const auto& s : msg) { 242 msgs.AppendElement(s); 243 } 244 nsTArray<MIDIMessage> newMsgs; 245 MIDIUtils::ParseMessages(msgs, TimeStamp::Now(), newMsgs); 246 nsCOMPtr<nsIRunnable> r( 247 new ReceiveRunnable(mControlInputPort.id(), newMsgs)); 248 OwnerThread()->Dispatch(r, NS_DISPATCH_NORMAL); 249 break; 250 } 251 default: 252 NS_WARNING("Unknown Test Sysex MIDI message received!"); 253 } 254 break; 255 } 256 } 257 } 258 }