summaryrefslogtreecommitdiffstats
path: root/dom/midi/TestMIDIPlatformService.cpp
blob: cbc11a747462b9c4a5f353abbe3bc61a21a2760e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "TestMIDIPlatformService.h"
#include "mozilla/dom/MIDIPort.h"
#include "mozilla/dom/MIDITypes.h"
#include "mozilla/dom/MIDIPortInterface.h"
#include "mozilla/dom/MIDIPortParent.h"
#include "mozilla/dom/MIDIPlatformRunnables.h"
#include "mozilla/dom/MIDIUtils.h"
#include "mozilla/ipc/BackgroundParent.h"
#include "mozilla/Unused.h"
#include "nsIThread.h"

using namespace mozilla;
using namespace mozilla::dom;
using namespace mozilla::ipc;

/**
 * Runnable used for making sure ProcessMessages only happens on the IO thread.
 *
 */
class ProcessMessagesRunnable : public mozilla::Runnable {
 public:
  explicit ProcessMessagesRunnable(const nsAString& aPortID)
      : Runnable("ProcessMessagesRunnable"), mPortID(aPortID) {}
  ~ProcessMessagesRunnable() = default;
  NS_IMETHOD Run() override {
    // If service is no longer running, just exist without processing.
    if (!MIDIPlatformService::IsRunning()) {
      return NS_OK;
    }
    TestMIDIPlatformService* srv =
        static_cast<TestMIDIPlatformService*>(MIDIPlatformService::Get());
    srv->ProcessMessages(mPortID);
    return NS_OK;
  }

 private:
  nsString mPortID;
};

/**
 * Runnable used for allowing IO thread to queue more messages for processing,
 * since it can't access the service object directly.
 *
 */
class QueueMessagesRunnable : public MIDIBackgroundRunnable {
 public:
  QueueMessagesRunnable(const nsAString& aPortID,
                        const nsTArray<MIDIMessage>& aMsgs)
      : MIDIBackgroundRunnable("QueueMessagesRunnable"),
        mPortID(aPortID),
        mMsgs(aMsgs.Clone()) {}
  ~QueueMessagesRunnable() = default;
  virtual void RunInternal() {
    MIDIPlatformService::AssertThread();
    MIDIPlatformService::Get()->QueueMessages(mPortID, mMsgs);
  }

 private:
  nsString mPortID;
  nsTArray<MIDIMessage> mMsgs;
};

TestMIDIPlatformService::TestMIDIPlatformService()
    : mControlInputPort(u"b744eebe-f7d8-499b-872b-958f63c8f522"_ns,
                        u"Test Control MIDI Device Input Port"_ns,
                        u"Test Manufacturer"_ns, u"1.0.0"_ns,
                        static_cast<uint32_t>(MIDIPortType::Input)),
      mControlOutputPort(u"ab8e7fe8-c4de-436a-a960-30898a7c9a3d"_ns,
                         u"Test Control MIDI Device Output Port"_ns,
                         u"Test Manufacturer"_ns, u"1.0.0"_ns,
                         static_cast<uint32_t>(MIDIPortType::Output)),
      mStateTestInputPort(u"a9329677-8588-4460-a091-9d4a7f629a48"_ns,
                          u"Test State MIDI Device Input Port"_ns,
                          u"Test Manufacturer"_ns, u"1.0.0"_ns,
                          static_cast<uint32_t>(MIDIPortType::Input)),
      mStateTestOutputPort(u"478fa225-b5fc-4fa6-a543-d32d9cb651e7"_ns,
                           u"Test State MIDI Device Output Port"_ns,
                           u"Test Manufacturer"_ns, u"1.0.0"_ns,
                           static_cast<uint32_t>(MIDIPortType::Output)),
      mAlwaysClosedTestOutputPort(u"f87d0c76-3c68-49a9-a44f-700f1125c07a"_ns,
                                  u"Always Closed MIDI Device Output Port"_ns,
                                  u"Test Manufacturer"_ns, u"1.0.0"_ns,
                                  static_cast<uint32_t>(MIDIPortType::Output)),
      mDoRefresh(false),
      mIsInitialized(false) {
  MIDIPlatformService::AssertThread();
}

TestMIDIPlatformService::~TestMIDIPlatformService() {
  MIDIPlatformService::AssertThread();
}

void TestMIDIPlatformService::Init() {
  MIDIPlatformService::AssertThread();

  if (mIsInitialized) {
    return;
  }
  mIsInitialized = true;

  // Treat all of our special ports as always connected. When the service comes
  // up, prepopulate the port list with them.
  MIDIPlatformService::Get()->AddPortInfo(mControlInputPort);
  MIDIPlatformService::Get()->AddPortInfo(mControlOutputPort);
  MIDIPlatformService::Get()->AddPortInfo(mAlwaysClosedTestOutputPort);
  MIDIPlatformService::Get()->AddPortInfo(mStateTestOutputPort);
  nsCOMPtr<nsIRunnable> r(new SendPortListRunnable());

  // Start the IO Thread.
  OwnerThread()->Dispatch(r.forget());
}

void TestMIDIPlatformService::Refresh() {
  if (mDoRefresh) {
    AddPortInfo(mStateTestInputPort);
    mDoRefresh = false;
  }
}

void TestMIDIPlatformService::Open(MIDIPortParent* aPort) {
  MOZ_ASSERT(aPort);
  MIDIPortConnectionState s = MIDIPortConnectionState::Open;
  if (aPort->MIDIPortInterface::Id() == mAlwaysClosedTestOutputPort.id()) {
    // If it's the always closed testing port, act like it's already opened
    // exclusively elsewhere.
    s = MIDIPortConnectionState::Closed;
  }
  // Connection events are just simulated on the background thread, no need to
  // push to IO thread.
  nsCOMPtr<nsIRunnable> r(
      new SetStatusRunnable(aPort, aPort->DeviceState(), s));
  OwnerThread()->Dispatch(r.forget());
}

void TestMIDIPlatformService::ScheduleClose(MIDIPortParent* aPort) {
  AssertThread();
  MOZ_ASSERT(aPort);
  if (aPort->ConnectionState() == MIDIPortConnectionState::Open) {
    // Connection events are just simulated on the background thread, no need to
    // push to IO thread.
    nsCOMPtr<nsIRunnable> r(new SetStatusRunnable(
        aPort, aPort->DeviceState(), MIDIPortConnectionState::Closed));
    OwnerThread()->Dispatch(r.forget());
  }
}

void TestMIDIPlatformService::Stop() { MIDIPlatformService::AssertThread(); }

void TestMIDIPlatformService::ScheduleSend(const nsAString& aPortId) {
  AssertThread();
  nsCOMPtr<nsIRunnable> r(new ProcessMessagesRunnable(aPortId));
  OwnerThread()->Dispatch(r.forget());
}

void TestMIDIPlatformService::ProcessMessages(const nsAString& aPortId) {
  nsTArray<MIDIMessage> msgs;
  GetMessagesBefore(aPortId, TimeStamp::Now(), msgs);

  for (MIDIMessage msg : msgs) {
    // receiving message from test control port
    if (aPortId == mControlOutputPort.id()) {
      switch (msg.data()[0]) {
        // Hit a note, get a test!
        case 0x90:
          switch (msg.data()[1]) {
            // Echo data/timestamp back through output port
            case 0x00: {
              nsCOMPtr<nsIRunnable> r(
                  new ReceiveRunnable(mControlInputPort.id(), msg));
              OwnerThread()->Dispatch(r, NS_DISPATCH_NORMAL);
              break;
            }
            // Cause control test ports to connect
            case 0x01: {
              nsCOMPtr<nsIRunnable> r1(
                  new AddPortRunnable(mStateTestInputPort));
              OwnerThread()->Dispatch(r1, NS_DISPATCH_NORMAL);
              break;
            }
            // Cause control test ports to disconnect
            case 0x02: {
              nsCOMPtr<nsIRunnable> r1(
                  new RemovePortRunnable(mStateTestInputPort));
              OwnerThread()->Dispatch(r1, NS_DISPATCH_NORMAL);
              break;
            }
            // Test for packet timing
            case 0x03: {
              // Append a few echo command packets in reverse timing order,
              // should come out in correct order on other end.
              nsTArray<MIDIMessage> newMsgs;
              nsTArray<uint8_t> msg;
              msg.AppendElement(0x90);
              msg.AppendElement(0x00);
              msg.AppendElement(0x00);
              // PR_Now() returns nanosecods, and we need a double with
              // fractional milliseconds.
              TimeStamp currentTime = TimeStamp::Now();
              for (int i = 0; i <= 5; ++i) {
                // Insert messages with timestamps in reverse order, to make
                // sure we're sorting correctly.
                newMsgs.AppendElement(MIDIMessage(
                    msg, currentTime - TimeDuration::FromMilliseconds(i * 2)));
              }
              nsCOMPtr<nsIRunnable> r(
                  new QueueMessagesRunnable(aPortId, newMsgs));
              OwnerThread()->Dispatch(r, NS_DISPATCH_NORMAL);
              break;
            }
            // Causes the next refresh to add new ports to the list
            case 0x04: {
              mDoRefresh = true;
              break;
            }
            default:
              NS_WARNING("Unknown Test MIDI message received!");
          }
          break;
          // Sysex tests
        case 0xF0:
          switch (msg.data()[1]) {
              // Echo data/timestamp back through output port
            case 0x00: {
              nsCOMPtr<nsIRunnable> r(
                  new ReceiveRunnable(mControlInputPort.id(), msg));
              OwnerThread()->Dispatch(r, NS_DISPATCH_NORMAL);
              break;
            }
            // Test for system real time messages in the middle of sysex
            // messages.
            case 0x01: {
              nsTArray<uint8_t> msgs;
              const uint8_t msg[] = {0xF0, 0x01, 0xFA, 0x02, 0x03,
                                     0x04, 0xF8, 0x05, 0xF7};
              // Can't use AppendElements on an array here, so just do range
              // based loading.
              for (const auto& s : msg) {
                msgs.AppendElement(s);
              }
              nsTArray<MIDIMessage> newMsgs;
              MIDIUtils::ParseMessages(msgs, TimeStamp::Now(), newMsgs);
              nsCOMPtr<nsIRunnable> r(
                  new ReceiveRunnable(mControlInputPort.id(), newMsgs));
              OwnerThread()->Dispatch(r, NS_DISPATCH_NORMAL);
              break;
            }
            default:
              NS_WARNING("Unknown Test Sysex MIDI message received!");
          }
          break;
      }
    }
  }
}