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