diff options
Diffstat (limited to 'dom/midi/midirMIDIPlatformService.cpp')
-rw-r--r-- | dom/midi/midirMIDIPlatformService.cpp | 193 |
1 files changed, 193 insertions, 0 deletions
diff --git a/dom/midi/midirMIDIPlatformService.cpp b/dom/midi/midirMIDIPlatformService.cpp new file mode 100644 index 0000000000..842eb51dcd --- /dev/null +++ b/dom/midi/midirMIDIPlatformService.cpp @@ -0,0 +1,193 @@ +/* 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 "midirMIDIPlatformService.h" +#include "mozilla/StaticMutex.h" +#include "mozilla/TimeStamp.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/dom/midi/midir_impl_ffi_generated.h" +#include "mozilla/ipc/BackgroundParent.h" +#include "mozilla/Unused.h" +#include "nsIThread.h" +#include "mozilla/Logging.h" +#include "MIDILog.h" + +using namespace mozilla; +using namespace mozilla::dom; +using namespace mozilla::ipc; + +static_assert(sizeof(TimeStamp) == sizeof(GeckoTimeStamp)); + +/** + * Runnable used for to send messages asynchronously on the I/O thread. + */ +class SendRunnable : public MIDIBackgroundRunnable { + public: + explicit SendRunnable(const nsAString& aPortID, const MIDIMessage& aMessage) + : MIDIBackgroundRunnable("SendRunnable"), + mPortID(aPortID), + mMessage(aMessage) {} + ~SendRunnable() = default; + virtual void RunInternal() { + MIDIPlatformService::AssertThread(); + if (!MIDIPlatformService::IsRunning()) { + // Some send operations might outlive the service, bail out and do nothing + return; + } + midirMIDIPlatformService* srv = + static_cast<midirMIDIPlatformService*>(MIDIPlatformService::Get()); + srv->SendMessage(mPortID, mMessage); + } + + private: + nsString mPortID; + MIDIMessage mMessage; +}; + +// static +StaticMutex midirMIDIPlatformService::gOwnerThreadMutex; + +// static +nsCOMPtr<nsISerialEventTarget> midirMIDIPlatformService::gOwnerThread; + +midirMIDIPlatformService::midirMIDIPlatformService() + : mImplementation(nullptr) { + StaticMutexAutoLock lock(gOwnerThreadMutex); + gOwnerThread = OwnerThread(); +} + +midirMIDIPlatformService::~midirMIDIPlatformService() { + LOG("midir_impl_shutdown"); + if (mImplementation) { + midir_impl_shutdown(mImplementation); + } + StaticMutexAutoLock lock(gOwnerThreadMutex); + gOwnerThread = nullptr; +} + +// static +void midirMIDIPlatformService::AddPort(const nsString* aId, + const nsString* aName, bool aInput) { + MIDIPortType type = aInput ? MIDIPortType::Input : MIDIPortType::Output; + MIDIPortInfo port(*aId, *aName, u""_ns, u""_ns, static_cast<uint32_t>(type)); + MIDIPlatformService::Get()->AddPortInfo(port); +} + +// static +void midirMIDIPlatformService::RemovePort(const nsString* aId, + const nsString* aName, bool aInput) { + MIDIPortType type = aInput ? MIDIPortType::Input : MIDIPortType::Output; + MIDIPortInfo port(*aId, *aName, u""_ns, u""_ns, static_cast<uint32_t>(type)); + MIDIPlatformService::Get()->RemovePortInfo(port); +} + +void midirMIDIPlatformService::Init() { + if (mImplementation) { + return; + } + + mImplementation = midir_impl_init(AddPort); + + if (mImplementation) { + MIDIPlatformService::Get()->SendPortList(); + } else { + LOG("midir_impl_init failure"); + } +} + +// static +void midirMIDIPlatformService::CheckAndReceive(const nsString* aId, + const uint8_t* aData, + size_t aLength, + const GeckoTimeStamp* aTimeStamp, + uint64_t aMicros) { + nsTArray<uint8_t> data; + data.AppendElements(aData, aLength); + const TimeStamp* openTime = reinterpret_cast<const TimeStamp*>(aTimeStamp); + TimeStamp timestamp = + *openTime + TimeDuration::FromMicroseconds(static_cast<double>(aMicros)); + MIDIMessage message(data, timestamp); + LogMIDIMessage(message, *aId, MIDIPortType::Input); + nsTArray<MIDIMessage> messages; + messages.AppendElement(message); + + nsCOMPtr<nsIRunnable> r(new ReceiveRunnable(*aId, messages)); + StaticMutexAutoLock lock(gOwnerThreadMutex); + if (gOwnerThread) { + gOwnerThread->Dispatch(r, NS_DISPATCH_NORMAL); + } +} + +void midirMIDIPlatformService::Refresh() { + midir_impl_refresh(mImplementation, AddPort, RemovePort); +} + +void midirMIDIPlatformService::Open(MIDIPortParent* aPort) { + AssertThread(); + MOZ_ASSERT(aPort); + nsString id = aPort->MIDIPortInterface::Id(); + TimeStamp openTimeStamp = TimeStamp::Now(); + if (midir_impl_open_port(mImplementation, &id, + reinterpret_cast<GeckoTimeStamp*>(&openTimeStamp), + CheckAndReceive)) { + LOG("MIDI port open: %s at t=%lf", NS_ConvertUTF16toUTF8(id).get(), + (openTimeStamp - TimeStamp::ProcessCreation()).ToSeconds()); + nsCOMPtr<nsIRunnable> r(new SetStatusRunnable( + aPort, aPort->DeviceState(), MIDIPortConnectionState::Open)); + OwnerThread()->Dispatch(r.forget()); + } else { + LOG("MIDI port open failed: %s", NS_ConvertUTF16toUTF8(id).get()); + } +} + +void midirMIDIPlatformService::Stop() { + // Nothing to do here AFAIK +} + +void midirMIDIPlatformService::ScheduleSend(const nsAString& aPortId) { + AssertThread(); + LOG("MIDI port schedule send %s", NS_ConvertUTF16toUTF8(aPortId).get()); + nsTArray<MIDIMessage> messages; + GetMessages(aPortId, messages); + TimeStamp now = TimeStamp::Now(); + for (const auto& message : messages) { + if (message.timestamp().IsNull()) { + SendMessage(aPortId, message); + } else { + double delay = (message.timestamp() - now).ToMilliseconds(); + if (delay < 1.0) { + SendMessage(aPortId, message); + } else { + nsCOMPtr<nsIRunnable> r(new SendRunnable(aPortId, message)); + OwnerThread()->DelayedDispatch(r.forget(), + static_cast<uint32_t>(delay)); + } + } + } +} + +void midirMIDIPlatformService::ScheduleClose(MIDIPortParent* aPort) { + AssertThread(); + MOZ_ASSERT(aPort); + nsString id = aPort->MIDIPortInterface::Id(); + LOG("MIDI port schedule close %s", NS_ConvertUTF16toUTF8(id).get()); + if (aPort->ConnectionState() == MIDIPortConnectionState::Open) { + midir_impl_close_port(mImplementation, &id); + nsCOMPtr<nsIRunnable> r(new SetStatusRunnable( + aPort, aPort->DeviceState(), MIDIPortConnectionState::Closed)); + OwnerThread()->Dispatch(r.forget()); + } +} + +void midirMIDIPlatformService::SendMessage(const nsAString& aPortId, + const MIDIMessage& aMessage) { + LOG("MIDI send message on %s", NS_ConvertUTF16toUTF8(aPortId).get()); + LogMIDIMessage(aMessage, aPortId, MIDIPortType::Output); + midir_impl_send(mImplementation, &aPortId, &aMessage.data()); +} |