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/. */
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"
16 using namespace mozilla
;
17 using namespace mozilla::dom
;
18 using namespace mozilla::ipc
;
21 * Runnable used for making sure ProcessMessages only happens on the IO thread.
24 class ProcessMessagesRunnable
: public mozilla::Runnable
{
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()) {
34 TestMIDIPlatformService
* srv
=
35 static_cast<TestMIDIPlatformService
*>(MIDIPlatformService::Get());
36 srv
->ProcessMessages(mPortID
);
45 * Runnable used for allowing IO thread to queue more messages for processing,
46 * since it can't access the service object directly.
49 class QueueMessagesRunnable
: public MIDIBackgroundRunnable
{
51 QueueMessagesRunnable(const nsAString
& aPortID
,
52 const nsTArray
<MIDIMessage
>& aMsgs
)
53 : MIDIBackgroundRunnable("QueueMessagesRunnable"),
55 mMsgs(aMsgs
.Clone()) {}
56 ~QueueMessagesRunnable() = default;
57 virtual void RunInternal() {
58 MIDIPlatformService::AssertThread();
59 MIDIPlatformService::Get()->QueueMessages(mPortID
, mMsgs
);
64 nsTArray
<MIDIMessage
> mMsgs
;
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
)),
89 mIsInitialized(false) {
90 MIDIPlatformService::AssertThread();
93 TestMIDIPlatformService::~TestMIDIPlatformService() {
94 MIDIPlatformService::AssertThread();
97 void TestMIDIPlatformService::Init() {
98 MIDIPlatformService::AssertThread();
100 if (mIsInitialized
) {
103 mIsInitialized
= true;
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());
113 // Start the IO Thread.
114 OwnerThread()->Dispatch(r
.forget());
117 void TestMIDIPlatformService::Refresh() {
119 AddPortInfo(mStateTestInputPort
);
124 void TestMIDIPlatformService::Open(MIDIPortParent
* 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
;
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());
139 void TestMIDIPlatformService::ScheduleClose(MIDIPortParent
* 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());
151 void TestMIDIPlatformService::Stop() { MIDIPlatformService::AssertThread(); }
153 void TestMIDIPlatformService::ScheduleSend(const nsAString
& aPortId
) {
155 nsCOMPtr
<nsIRunnable
> r(new ProcessMessagesRunnable(aPortId
));
156 OwnerThread()->Dispatch(r
.forget());
159 void TestMIDIPlatformService::ProcessMessages(const nsAString
& aPortId
) {
160 nsTArray
<MIDIMessage
> msgs
;
161 GetMessagesBefore(aPortId
, TimeStamp::Now(), msgs
);
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!
169 switch (msg
.data()[1]) {
170 // Echo data/timestamp back through output port
172 nsCOMPtr
<nsIRunnable
> r(
173 new ReceiveRunnable(mControlInputPort
.id(), msg
));
174 OwnerThread()->Dispatch(r
, NS_DISPATCH_NORMAL
);
177 // Cause control test ports to connect
179 nsCOMPtr
<nsIRunnable
> r1(
180 new AddPortRunnable(mStateTestInputPort
));
181 OwnerThread()->Dispatch(r1
, NS_DISPATCH_NORMAL
);
184 // Cause control test ports to disconnect
186 nsCOMPtr
<nsIRunnable
> r1(
187 new RemovePortRunnable(mStateTestInputPort
));
188 OwnerThread()->Dispatch(r1
, NS_DISPATCH_NORMAL
);
191 // Test for packet timing
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)));
209 nsCOMPtr
<nsIRunnable
> r(
210 new QueueMessagesRunnable(aPortId
, newMsgs
));
211 OwnerThread()->Dispatch(r
, NS_DISPATCH_NORMAL
);
214 // Causes the next refresh to add new ports to the list
220 NS_WARNING("Unknown Test MIDI message received!");
225 switch (msg
.data()[1]) {
226 // Echo data/timestamp back through output port
228 nsCOMPtr
<nsIRunnable
> r(
229 new ReceiveRunnable(mControlInputPort
.id(), msg
));
230 OwnerThread()->Dispatch(r
, NS_DISPATCH_NORMAL
);
233 // Test for system real time messages in the middle of sysex
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
241 for (const auto& s
: msg
) {
242 msgs
.AppendElement(s
);
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
);
252 NS_WARNING("Unknown Test Sysex MIDI message received!");