Browse Source

MIDI: Fix off-by-one bug when accessing MIDI ports on Linux

v7.0.9
reuk 2 years ago
parent
commit
63e80c3908
No known key found for this signature in database GPG Key ID: FCB43929F012EE5C
1 changed files with 58 additions and 40 deletions
  1. +58
    -40
      modules/juce_audio_devices/native/juce_Midi_linux.cpp

+ 58
- 40
modules/juce_audio_devices/native/juce_Midi_linux.cpp View File

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


Loading…
Cancel
Save