Source code

Revision control

Copy as Markdown

Other Tools

/* 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;
}
}
}
}