|
|
@@ -26,16 +26,25 @@ namespace juce |
|
|
|
#if JUCE_ALSA
|
|
|
|
|
|
|
|
//==============================================================================
|
|
|
|
class AlsaClient : public ReferenceCountedObject
|
|
|
|
class AlsaClient
|
|
|
|
{
|
|
|
|
auto lowerBound (int portId) const
|
|
|
|
{
|
|
|
|
const auto comparator = [] (const auto& port, const auto& id) { return port->getPortId() < id; };
|
|
|
|
return std::lower_bound (ports.begin(), ports.end(), portId, comparator);
|
|
|
|
}
|
|
|
|
|
|
|
|
auto findPortIterator (int portId) const
|
|
|
|
{
|
|
|
|
const auto iter = lowerBound (portId);
|
|
|
|
return (iter == ports.end() || (*iter)->getPortId() != portId) ? ports.end() : iter;
|
|
|
|
}
|
|
|
|
|
|
|
|
public:
|
|
|
|
~AlsaClient()
|
|
|
|
{
|
|
|
|
inputThread.reset();
|
|
|
|
|
|
|
|
jassert (instance != nullptr);
|
|
|
|
instance = nullptr;
|
|
|
|
|
|
|
|
jassert (activeCallbacks.get() == 0);
|
|
|
|
|
|
|
|
if (handle != nullptr)
|
|
|
@@ -57,15 +66,12 @@ public: |
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
using Ptr = ReferenceCountedObjectPtr<AlsaClient>;
|
|
|
|
|
|
|
|
//==============================================================================
|
|
|
|
// represents an input or output port of the supplied AlsaClient
|
|
|
|
struct Port
|
|
|
|
{
|
|
|
|
Port (AlsaClient& c, bool forInput) noexcept
|
|
|
|
: client (c), isInput (forInput)
|
|
|
|
{}
|
|
|
|
explicit Port (bool forInput) noexcept
|
|
|
|
: isInput (forInput) {}
|
|
|
|
|
|
|
|
~Port()
|
|
|
|
{
|
|
|
@@ -76,21 +82,21 @@ public: |
|
|
|
else
|
|
|
|
snd_midi_event_free (midiParser);
|
|
|
|
|
|
|
|
snd_seq_delete_simple_port (client.get(), portId);
|
|
|
|
snd_seq_delete_simple_port (client->get(), portId);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void connectWith (int sourceClient, int sourcePort) const noexcept
|
|
|
|
{
|
|
|
|
if (isInput)
|
|
|
|
snd_seq_connect_from (client.get(), portId, sourceClient, sourcePort);
|
|
|
|
snd_seq_connect_from (client->get(), portId, sourceClient, sourcePort);
|
|
|
|
else
|
|
|
|
snd_seq_connect_to (client.get(), portId, sourceClient, sourcePort);
|
|
|
|
snd_seq_connect_to (client->get(), portId, sourceClient, sourcePort);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool isValid() const noexcept
|
|
|
|
{
|
|
|
|
return client.get() != nullptr && portId >= 0;
|
|
|
|
return client->get() != nullptr && portId >= 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void setupInput (MidiInput* input, MidiInputCallback* cb)
|
|
|
@@ -126,7 +132,7 @@ public: |
|
|
|
auto numBytes = (long) message.getRawDataSize();
|
|
|
|
auto* data = message.getRawData();
|
|
|
|
|
|
|
|
auto seqHandle = client.get();
|
|
|
|
auto seqHandle = client->get();
|
|
|
|
bool success = true;
|
|
|
|
|
|
|
|
while (numBytes > 0)
|
|
|
@@ -165,7 +171,7 @@ public: |
|
|
|
|
|
|
|
void createPort (const String& name, bool enableSubscription)
|
|
|
|
{
|
|
|
|
if (auto seqHandle = client.get())
|
|
|
|
if (auto seqHandle = client->get())
|
|
|
|
{
|
|
|
|
const unsigned int caps =
|
|
|
|
isInput ? (SND_SEQ_PORT_CAP_WRITE | (enableSubscription ? SND_SEQ_PORT_CAP_SUBS_WRITE : 0))
|
|
|
@@ -194,7 +200,7 @@ public: |
|
|
|
const String& getPortName() const { return portName; }
|
|
|
|
|
|
|
|
private:
|
|
|
|
AlsaClient& client;
|
|
|
|
const std::shared_ptr<AlsaClient> client = AlsaClient::getInstance();
|
|
|
|
|
|
|
|
MidiInputCallback* callback = nullptr;
|
|
|
|
snd_midi_event_t* midiParser = nullptr;
|
|
|
@@ -207,19 +213,23 @@ public: |
|
|
|
bool isInput = false;
|
|
|
|
};
|
|
|
|
|
|
|
|
static Ptr getInstance()
|
|
|
|
static std::shared_ptr<AlsaClient> getInstance()
|
|
|
|
{
|
|
|
|
if (instance == nullptr)
|
|
|
|
instance = new AlsaClient();
|
|
|
|
static std::weak_ptr<AlsaClient> ptr;
|
|
|
|
|
|
|
|
return instance;
|
|
|
|
if (auto locked = ptr.lock())
|
|
|
|
return locked;
|
|
|
|
|
|
|
|
std::shared_ptr<AlsaClient> result (new AlsaClient());
|
|
|
|
ptr = result;
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
void handleIncomingMidiMessage (snd_seq_event* event, const MidiMessage& message)
|
|
|
|
{
|
|
|
|
const ScopedLock sl (callbackLock);
|
|
|
|
|
|
|
|
if (auto* port = ports[event->dest.port])
|
|
|
|
if (auto* port = findPort (event->dest.port))
|
|
|
|
port->handleIncomingMidiMessage (message);
|
|
|
|
}
|
|
|
|
|
|
|
@@ -227,7 +237,7 @@ public: |
|
|
|
{
|
|
|
|
const ScopedLock sl (callbackLock);
|
|
|
|
|
|
|
|
if (auto* port = ports[event->dest.port])
|
|
|
|
if (auto* port = findPort (event->dest.port))
|
|
|
|
port->handlePartialSysexMessage (messageData, numBytesSoFar, timeStamp);
|
|
|
|
}
|
|
|
|
|
|
|
@@ -238,10 +248,13 @@ public: |
|
|
|
{
|
|
|
|
const ScopedLock sl (callbackLock);
|
|
|
|
|
|
|
|
auto port = new Port (*this, forInput);
|
|
|
|
auto port = new Port (forInput);
|
|
|
|
port->createPort (name, enableSubscription);
|
|
|
|
ports.set (port->getPortId(), port);
|
|
|
|
incReferenceCount();
|
|
|
|
|
|
|
|
const auto iter = lowerBound (port->getPortId());
|
|
|
|
jassert (iter == ports.end() || port->getPortId() < (*iter)->getPortId());
|
|
|
|
ports.insert (iter, rawToUniquePtr (port));
|
|
|
|
|
|
|
|
return port;
|
|
|
|
}
|
|
|
|
|
|
|
@@ -249,15 +262,13 @@ public: |
|
|
|
{
|
|
|
|
const ScopedLock sl (callbackLock);
|
|
|
|
|
|
|
|
ports.set (port->getPortId(), nullptr);
|
|
|
|
decReferenceCount();
|
|
|
|
if (const auto iter = findPortIterator (port->getPortId()); iter != ports.end())
|
|
|
|
ports.erase (iter);
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
AlsaClient()
|
|
|
|
{
|
|
|
|
jassert (instance == nullptr);
|
|
|
|
|
|
|
|
snd_seq_open (&handle, "default", SND_SEQ_OPEN_DUPLEX, 0);
|
|
|
|
|
|
|
|
if (handle != nullptr)
|
|
|
@@ -267,7 +278,7 @@ private: |
|
|
|
clientId = snd_seq_client_id (handle);
|
|
|
|
|
|
|
|
// It's good idea to pre-allocate a good number of elements
|
|
|
|
ports.ensureStorageAllocated (32);
|
|
|
|
ports.reserve (32);
|
|
|
|
|
|
|
|
announcementsIn = snd_seq_create_simple_port (handle,
|
|
|
|
TRANS ("announcements").toRawUTF8(),
|
|
|
@@ -279,15 +290,21 @@ private: |
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Port* findPort (int portId)
|
|
|
|
{
|
|
|
|
if (const auto iter = findPortIterator (portId); iter != ports.end())
|
|
|
|
return iter->get();
|
|
|
|
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
snd_seq_t* handle = nullptr;
|
|
|
|
int clientId = 0;
|
|
|
|
int announcementsIn = 0;
|
|
|
|
OwnedArray<Port> ports;
|
|
|
|
std::vector<std::unique_ptr<Port>> ports;
|
|
|
|
Atomic<int> activeCallbacks;
|
|
|
|
CriticalSection callbackLock;
|
|
|
|
|
|
|
|
static AlsaClient* instance;
|
|
|
|
|
|
|
|
//==============================================================================
|
|
|
|
class SequencerThread
|
|
|
|
{
|
|
|
@@ -411,15 +428,13 @@ private: |
|
|
|
std::optional<SequencerThread> inputThread;
|
|
|
|
};
|
|
|
|
|
|
|
|
AlsaClient* AlsaClient::instance = nullptr;
|
|
|
|
|
|
|
|
//==============================================================================
|
|
|
|
static String getFormattedPortIdentifier (int clientId, int portId)
|
|
|
|
{
|
|
|
|
return String (clientId) + "-" + String (portId);
|
|
|
|
}
|
|
|
|
|
|
|
|
static AlsaClient::Port* iterateMidiClient (const AlsaClient::Ptr& client,
|
|
|
|
static AlsaClient::Port* iterateMidiClient (AlsaClient& client,
|
|
|
|
snd_seq_client_info_t* clientInfo,
|
|
|
|
bool forInput,
|
|
|
|
Array<MidiDeviceInfo>& devices,
|
|
|
@@ -427,7 +442,7 @@ static AlsaClient::Port* iterateMidiClient (const AlsaClient::Ptr& client, |
|
|
|
{
|
|
|
|
AlsaClient::Port* port = nullptr;
|
|
|
|
|
|
|
|
auto seqHandle = client->get();
|
|
|
|
auto seqHandle = client.get();
|
|
|
|
snd_seq_port_info_t* portInfo = nullptr;
|
|
|
|
|
|
|
|
snd_seq_port_info_alloca (&portInfo);
|
|
|
@@ -454,7 +469,7 @@ static AlsaClient::Port* iterateMidiClient (const AlsaClient::Ptr& client, |
|
|
|
{
|
|
|
|
if (portID != -1)
|
|
|
|
{
|
|
|
|
port = client->createPort (portName, forInput, false);
|
|
|
|
port = client.createPort (portName, forInput, false);
|
|
|
|
jassert (port->isValid());
|
|
|
|
port->connectWith (sourceClient, portID);
|
|
|
|
break;
|
|
|
@@ -492,8 +507,11 @@ static AlsaClient::Port* iterateMidiDevices (bool forInput, |
|
|
|
{
|
|
|
|
if (snd_seq_query_next_client (seqHandle, clientInfo) == 0)
|
|
|
|
{
|
|
|
|
port = iterateMidiClient (client, clientInfo, forInput,
|
|
|
|
devices, deviceIdentifierToOpen);
|
|
|
|
port = iterateMidiClient (*client,
|
|
|
|
clientInfo,
|
|
|
|
forInput,
|
|
|
|
devices,
|
|
|
|
deviceIdentifierToOpen);
|
|
|
|
|
|
|
|
if (port != nullptr)
|
|
|
|
break;
|
|
|
|