| @@ -34,171 +34,389 @@ | |||||
| #define JUCE_ALSA_MIDI_OUTPUT_NAME "Juce Midi Output" | #define JUCE_ALSA_MIDI_OUTPUT_NAME "Juce Midi Output" | ||||
| #endif | #endif | ||||
| #ifndef JUCE_ALSA_MIDI_INPUT_PORT_NAME | |||||
| #define JUCE_ALSA_MIDI_INPUT_PORT_NAME "Juce Midi In Port" | |||||
| #endif | |||||
| //============================================================================== | |||||
| namespace | |||||
| { | |||||
| #ifndef JUCE_ALSA_MIDI_OUTPUT_PORT_NAME | |||||
| #define JUCE_ALSA_MIDI_OUTPUT_PORT_NAME "Juce Midi Out Port" | |||||
| #endif | |||||
| class AlsaPortAndCallback; | |||||
| //============================================================================== | //============================================================================== | ||||
| namespace | |||||
| class AlsaClient : public ReferenceCountedObject | |||||
| { | { | ||||
| snd_seq_t* iterateMidiClient (snd_seq_t* seqHandle, | |||||
| snd_seq_client_info_t* clientInfo, | |||||
| const bool forInput, | |||||
| StringArray& deviceNamesFound, | |||||
| const int deviceIndexToOpen) | |||||
| public: | |||||
| typedef ReferenceCountedObjectPtr<AlsaClient> Ptr; | |||||
| AlsaClient (bool forInput) | |||||
| : input (forInput), handle (nullptr) | |||||
| { | |||||
| snd_seq_open (&handle, "default", forInput ? SND_SEQ_OPEN_INPUT | |||||
| : SND_SEQ_OPEN_OUTPUT, 0); | |||||
| } | |||||
| ~AlsaClient() | |||||
| { | { | ||||
| snd_seq_t* returnedHandle = nullptr; | |||||
| if (handle != nullptr) | |||||
| { | |||||
| snd_seq_close (handle); | |||||
| handle = nullptr; | |||||
| } | |||||
| jassert (activeCallbacks.size() == 0); | |||||
| snd_seq_port_info_t* portInfo; | |||||
| if (snd_seq_port_info_malloc (&portInfo) == 0) | |||||
| if (inputThread) | |||||
| { | { | ||||
| int numPorts = snd_seq_client_info_get_num_ports (clientInfo); | |||||
| const int client = snd_seq_client_info_get_client (clientInfo); | |||||
| inputThread->stopThread (3000); | |||||
| inputThread = nullptr; | |||||
| } | |||||
| } | |||||
| bool isInput() const noexcept { return input; } | |||||
| snd_seq_port_info_set_client (portInfo, client); | |||||
| snd_seq_port_info_set_port (portInfo, -1); | |||||
| void setName (const String& name) | |||||
| { | |||||
| snd_seq_set_client_name (handle, name.toUTF8()); | |||||
| } | |||||
| while (--numPorts >= 0) | |||||
| void registerCallback (AlsaPortAndCallback* cb) | |||||
| { | |||||
| if (cb != nullptr) | |||||
| { | |||||
| { | { | ||||
| if (snd_seq_query_next_port (seqHandle, portInfo) == 0 | |||||
| && (snd_seq_port_info_get_capability (portInfo) | |||||
| & (forInput ? SND_SEQ_PORT_CAP_READ | |||||
| : SND_SEQ_PORT_CAP_WRITE)) != 0) | |||||
| { | |||||
| deviceNamesFound.add (snd_seq_client_info_get_name (clientInfo)); | |||||
| const ScopedLock sl (callbackLock); | |||||
| activeCallbacks.add (cb); | |||||
| if (inputThread == nullptr) | |||||
| inputThread = new MidiInputThread (*this); | |||||
| } | |||||
| if (deviceNamesFound.size() == deviceIndexToOpen + 1) | |||||
| inputThread->startThread(); | |||||
| } | |||||
| } | |||||
| void unregisterCallback (AlsaPortAndCallback* cb) | |||||
| { | |||||
| const ScopedLock sl (callbackLock); | |||||
| jassert (activeCallbacks.contains (cb)); | |||||
| activeCallbacks.removeAllInstancesOf (cb); | |||||
| if (activeCallbacks.size() == 0 && inputThread->isThreadRunning()) | |||||
| inputThread->signalThreadShouldExit(); | |||||
| } | |||||
| void handleIncomingMidiMessage (const MidiMessage& message, int port); | |||||
| snd_seq_t* get() const noexcept { return handle; } | |||||
| private: | |||||
| bool input; | |||||
| snd_seq_t* handle; | |||||
| Array<AlsaPortAndCallback*> activeCallbacks; | |||||
| CriticalSection callbackLock; | |||||
| //============================================================================== | |||||
| class MidiInputThread : public Thread | |||||
| { | |||||
| public: | |||||
| MidiInputThread (AlsaClient& c) | |||||
| : Thread ("Juce MIDI Input"), client (c) | |||||
| { | |||||
| jassert (client.input && client.get() != nullptr); | |||||
| } | |||||
| void run() | |||||
| { | |||||
| const int maxEventSize = 16 * 1024; | |||||
| snd_midi_event_t* midiParser; | |||||
| snd_seq_t* seqHandle = client.get(); | |||||
| if (snd_midi_event_new (maxEventSize, &midiParser) >= 0) | |||||
| { | |||||
| const int numPfds = snd_seq_poll_descriptors_count (seqHandle, POLLIN); | |||||
| HeapBlock<pollfd> pfd (numPfds); | |||||
| snd_seq_poll_descriptors (seqHandle, pfd, numPfds, POLLIN); | |||||
| HeapBlock <uint8> buffer (maxEventSize); | |||||
| while (! threadShouldExit()) | |||||
| { | |||||
| if (poll (pfd, numPfds, 100) > 0) // there was a "500" here which is a bit long when we exit the program and have to wait for a timeout on this poll call | |||||
| { | { | ||||
| const int sourcePort = snd_seq_port_info_get_port (portInfo); | |||||
| const int sourceClient = snd_seq_client_info_get_client (clientInfo); | |||||
| if (threadShouldExit()) | |||||
| break; | |||||
| if (sourcePort != -1) | |||||
| snd_seq_nonblock (seqHandle, 1); | |||||
| do | |||||
| { | { | ||||
| if (forInput) | |||||
| snd_seq_event_t* inputEvent = nullptr; | |||||
| if (snd_seq_event_input (seqHandle, &inputEvent) >= 0) | |||||
| { | { | ||||
| snd_seq_set_client_name (seqHandle, JUCE_ALSA_MIDI_INPUT_NAME); | |||||
| // xxx what about SYSEXes that are too big for the buffer? | |||||
| const int numBytes = snd_midi_event_decode (midiParser, buffer, | |||||
| maxEventSize, inputEvent); | |||||
| const int portId = snd_seq_create_simple_port (seqHandle, JUCE_ALSA_MIDI_INPUT_PORT_NAME, | |||||
| SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE, | |||||
| SND_SEQ_PORT_TYPE_MIDI_GENERIC); | |||||
| snd_midi_event_reset_decode (midiParser); | |||||
| snd_seq_connect_from (seqHandle, portId, sourceClient, sourcePort); | |||||
| } | |||||
| else | |||||
| { | |||||
| snd_seq_set_client_name (seqHandle, JUCE_ALSA_MIDI_OUTPUT_NAME); | |||||
| if (numBytes > 0) | |||||
| { | |||||
| const MidiMessage message ((const uint8*) buffer, numBytes, | |||||
| Time::getMillisecondCounter() * 0.001); | |||||
| const int portId = snd_seq_create_simple_port (seqHandle, JUCE_ALSA_MIDI_OUTPUT_PORT_NAME, | |||||
| SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ, | |||||
| SND_SEQ_PORT_TYPE_MIDI_GENERIC); | |||||
| client.handleIncomingMidiMessage (message, inputEvent->dest.port); | |||||
| } | |||||
| snd_seq_connect_to (seqHandle, portId, sourceClient, sourcePort); | |||||
| snd_seq_free_event (inputEvent); | |||||
| } | } | ||||
| returnedHandle = seqHandle; | |||||
| } | } | ||||
| while (snd_seq_event_input_pending (seqHandle, 0) > 0); | |||||
| } | } | ||||
| } | } | ||||
| snd_midi_event_free (midiParser); | |||||
| } | } | ||||
| }; | |||||
| private: | |||||
| AlsaClient& client; | |||||
| }; | |||||
| ScopedPointer<MidiInputThread> inputThread; | |||||
| }; | |||||
| static AlsaClient::Ptr globalAlsaSequencerIn() | |||||
| { | |||||
| static AlsaClient::Ptr global (new AlsaClient (true)); | |||||
| return global; | |||||
| } | |||||
| static AlsaClient::Ptr globalAlsaSequencerOut() | |||||
| { | |||||
| static AlsaClient::Ptr global (new AlsaClient (false)); | |||||
| return global; | |||||
| } | |||||
| static AlsaClient::Ptr globalAlsaSequencer (bool input) | |||||
| { | |||||
| return input ? globalAlsaSequencerIn() | |||||
| : globalAlsaSequencerOut(); | |||||
| } | |||||
| //============================================================================== | |||||
| // represents an input or output port of the supplied AlsaClient | |||||
| class AlsaPort | |||||
| { | |||||
| public: | |||||
| AlsaPort() noexcept : portId (-1) {} | |||||
| AlsaPort (const AlsaClient::Ptr& c, int port) noexcept : client (c), portId (port) {} | |||||
| snd_seq_port_info_free (portInfo); | |||||
| void createPort (const AlsaClient::Ptr& c, const String& name, bool forInput) | |||||
| { | |||||
| client = c; | |||||
| if (snd_seq_t* handle = client->get()) | |||||
| portId = snd_seq_create_simple_port (handle, name.toUTF8(), | |||||
| forInput ? (SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE) | |||||
| : (SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ), | |||||
| SND_SEQ_PORT_TYPE_MIDI_GENERIC); | |||||
| } | |||||
| void deletePort() | |||||
| { | |||||
| if (isValid()) | |||||
| { | |||||
| snd_seq_delete_simple_port (client->get(), portId); | |||||
| portId = -1; | |||||
| } | } | ||||
| } | |||||
| void connectWith (int sourceClient, int sourcePort) | |||||
| { | |||||
| if (client->isInput()) | |||||
| snd_seq_connect_from (client->get(), portId, sourceClient, sourcePort); | |||||
| else | |||||
| snd_seq_connect_to (client->get(), portId, sourceClient, sourcePort); | |||||
| } | |||||
| return returnedHandle; | |||||
| bool isValid() const noexcept | |||||
| { | |||||
| return client != nullptr && client->get() != nullptr && portId >= 0; | |||||
| } | } | ||||
| snd_seq_t* iterateMidiDevices (const bool forInput, | |||||
| AlsaClient::Ptr client; | |||||
| int portId; | |||||
| }; | |||||
| //============================================================================== | |||||
| class AlsaPortAndCallback | |||||
| { | |||||
| public: | |||||
| AlsaPortAndCallback (AlsaPort p, MidiInput* in, MidiInputCallback* cb) | |||||
| : port (p), midiInput (in), callback (cb), callbackEnabled (false) | |||||
| { | |||||
| } | |||||
| ~AlsaPortAndCallback() | |||||
| { | |||||
| enableCallback (false); | |||||
| port.deletePort(); | |||||
| } | |||||
| void enableCallback (bool enable) | |||||
| { | |||||
| if (callbackEnabled != enable) | |||||
| { | |||||
| callbackEnabled = enable; | |||||
| if (enable) | |||||
| port.client->registerCallback (this); | |||||
| else | |||||
| port.client->unregisterCallback (this); | |||||
| } | |||||
| } | |||||
| void handleIncomingMidiMessage (const MidiMessage& message) const | |||||
| { | |||||
| callback->handleIncomingMidiMessage (midiInput, message); | |||||
| } | |||||
| private: | |||||
| AlsaPort port; | |||||
| MidiInput* midiInput; | |||||
| MidiInputCallback* callback; | |||||
| bool callbackEnabled; | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AlsaPortAndCallback) | |||||
| }; | |||||
| void AlsaClient::handleIncomingMidiMessage (const MidiMessage& message, int port) | |||||
| { | |||||
| const ScopedLock sl (callbackLock); | |||||
| if (AlsaPortAndCallback* const cb = activeCallbacks[port]) | |||||
| cb->handleIncomingMidiMessage (message); | |||||
| } | |||||
| //============================================================================== | |||||
| static AlsaPort iterateMidiClient (const AlsaClient::Ptr& seq, | |||||
| snd_seq_client_info_t* clientInfo, | |||||
| const bool forInput, | |||||
| StringArray& deviceNamesFound, | StringArray& deviceNamesFound, | ||||
| const int deviceIndexToOpen) | const int deviceIndexToOpen) | ||||
| { | |||||
| AlsaPort port; | |||||
| snd_seq_t* seqHandle = seq->get(); | |||||
| snd_seq_port_info_t* portInfo = nullptr; | |||||
| if (snd_seq_port_info_malloc (&portInfo) == 0) | |||||
| { | { | ||||
| snd_seq_t* returnedHandle = nullptr; | |||||
| snd_seq_t* seqHandle = nullptr; | |||||
| int numPorts = snd_seq_client_info_get_num_ports (clientInfo); | |||||
| const int client = snd_seq_client_info_get_client (clientInfo); | |||||
| if (snd_seq_open (&seqHandle, "default", forInput ? SND_SEQ_OPEN_INPUT | |||||
| : SND_SEQ_OPEN_OUTPUT, 0) == 0) | |||||
| { | |||||
| snd_seq_system_info_t* systemInfo = nullptr; | |||||
| snd_seq_client_info_t* clientInfo = nullptr; | |||||
| snd_seq_port_info_set_client (portInfo, client); | |||||
| snd_seq_port_info_set_port (portInfo, -1); | |||||
| if (snd_seq_system_info_malloc (&systemInfo) == 0) | |||||
| while (--numPorts >= 0) | |||||
| { | |||||
| if (snd_seq_query_next_port (seqHandle, portInfo) == 0 | |||||
| && (snd_seq_port_info_get_capability (portInfo) & (forInput ? SND_SEQ_PORT_CAP_READ | |||||
| : SND_SEQ_PORT_CAP_WRITE)) != 0) | |||||
| { | { | ||||
| if (snd_seq_system_info (seqHandle, systemInfo) == 0 | |||||
| && snd_seq_client_info_malloc (&clientInfo) == 0) | |||||
| { | |||||
| int numClients = snd_seq_system_info_get_cur_clients (systemInfo); | |||||
| deviceNamesFound.add (snd_seq_client_info_get_name (clientInfo)); | |||||
| while (--numClients >= 0 && returnedHandle == 0) | |||||
| if (snd_seq_query_next_client (seqHandle, clientInfo) == 0) | |||||
| returnedHandle = iterateMidiClient (seqHandle, clientInfo, | |||||
| forInput, deviceNamesFound, | |||||
| deviceIndexToOpen); | |||||
| if (deviceNamesFound.size() == deviceIndexToOpen + 1) | |||||
| { | |||||
| const int sourcePort = snd_seq_port_info_get_port (portInfo); | |||||
| const int sourceClient = snd_seq_client_info_get_client (clientInfo); | |||||
| snd_seq_client_info_free (clientInfo); | |||||
| if (sourcePort != -1) | |||||
| { | |||||
| const String name (forInput ? JUCE_ALSA_MIDI_INPUT_NAME | |||||
| : JUCE_ALSA_MIDI_OUTPUT_NAME); | |||||
| seq->setName (name); | |||||
| port.createPort (seq, name, forInput); | |||||
| port.connectWith (sourceClient, sourcePort); | |||||
| } | |||||
| } | } | ||||
| snd_seq_system_info_free (systemInfo); | |||||
| } | } | ||||
| if (returnedHandle == 0) | |||||
| snd_seq_close (seqHandle); | |||||
| } | } | ||||
| deviceNamesFound.appendNumbersToDuplicates (true, true); | |||||
| return returnedHandle; | |||||
| snd_seq_port_info_free (portInfo); | |||||
| } | } | ||||
| snd_seq_t* createMidiDevice (const bool forInput, const String& deviceNameToOpen) | |||||
| return port; | |||||
| } | |||||
| static AlsaPort iterateMidiDevices (const bool forInput, | |||||
| StringArray& deviceNamesFound, | |||||
| const int deviceIndexToOpen) | |||||
| { | |||||
| AlsaPort port; | |||||
| const AlsaClient::Ptr client (globalAlsaSequencer (forInput)); | |||||
| if (snd_seq_t* const seqHandle = client->get()) | |||||
| { | { | ||||
| snd_seq_t* seqHandle = nullptr; | |||||
| snd_seq_system_info_t* systemInfo = nullptr; | |||||
| snd_seq_client_info_t* clientInfo = nullptr; | |||||
| if (snd_seq_open (&seqHandle, "default", forInput ? SND_SEQ_OPEN_INPUT | |||||
| : SND_SEQ_OPEN_OUTPUT, 0) == 0) | |||||
| if (snd_seq_system_info_malloc (&systemInfo) == 0) | |||||
| { | { | ||||
| snd_seq_set_client_name (seqHandle, | |||||
| (deviceNameToOpen + (forInput ? " Input" : " Output")).toUTF8()); | |||||
| const int portId | |||||
| = snd_seq_create_simple_port (seqHandle, | |||||
| forInput ? "in" | |||||
| : "out", | |||||
| forInput ? (SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE) | |||||
| : (SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ), | |||||
| forInput ? SND_SEQ_PORT_TYPE_APPLICATION | |||||
| : SND_SEQ_PORT_TYPE_MIDI_GENERIC); | |||||
| if (portId < 0) | |||||
| if (snd_seq_system_info (seqHandle, systemInfo) == 0 | |||||
| && snd_seq_client_info_malloc (&clientInfo) == 0) | |||||
| { | { | ||||
| snd_seq_close (seqHandle); | |||||
| seqHandle = nullptr; | |||||
| int numClients = snd_seq_system_info_get_cur_clients (systemInfo); | |||||
| while (--numClients >= 0 && ! port.isValid()) | |||||
| if (snd_seq_query_next_client (seqHandle, clientInfo) == 0) | |||||
| port = iterateMidiClient (client, clientInfo, forInput, | |||||
| deviceNamesFound, deviceIndexToOpen); | |||||
| snd_seq_client_info_free (clientInfo); | |||||
| } | } | ||||
| snd_seq_system_info_free (systemInfo); | |||||
| } | } | ||||
| return seqHandle; | |||||
| } | } | ||||
| deviceNamesFound.appendNumbersToDuplicates (true, true); | |||||
| return port; | |||||
| } | } | ||||
| AlsaPort createMidiDevice (const bool forInput, const String& deviceNameToOpen) | |||||
| { | |||||
| AlsaPort port; | |||||
| AlsaClient::Ptr client (new AlsaClient (forInput)); | |||||
| if (client->get()) | |||||
| { | |||||
| client->setName (deviceNameToOpen + (forInput ? " Input" : " Output")); | |||||
| port.createPort (client, forInput ? "in" : "out", forInput); | |||||
| } | |||||
| return port; | |||||
| } | |||||
| //============================================================================== | //============================================================================== | ||||
| class MidiOutputDevice | class MidiOutputDevice | ||||
| { | { | ||||
| public: | public: | ||||
| MidiOutputDevice (MidiOutput* output, snd_seq_t* handle) | |||||
| : midiOutput (output), seqHandle (handle), | |||||
| MidiOutputDevice (MidiOutput* const output, const AlsaPort& p) | |||||
| : midiOutput (output), port (p), | |||||
| maxEventSize (16 * 1024) | maxEventSize (16 * 1024) | ||||
| { | { | ||||
| jassert (seqHandle != 0 && midiOutput != 0); | |||||
| jassert (port.isValid() && midiOutput != nullptr); | |||||
| snd_midi_event_new (maxEventSize, &midiParser); | snd_midi_event_new (maxEventSize, &midiParser); | ||||
| } | } | ||||
| ~MidiOutputDevice() | ~MidiOutputDevice() | ||||
| { | { | ||||
| snd_midi_event_free (midiParser); | snd_midi_event_free (midiParser); | ||||
| snd_seq_close (seqHandle); | |||||
| } | } | ||||
| void sendMessageNow (const MidiMessage& message) | void sendMessageNow (const MidiMessage& message) | ||||
| @@ -216,6 +434,8 @@ public: | |||||
| long numBytes = (long) message.getRawDataSize(); | long numBytes = (long) message.getRawDataSize(); | ||||
| const uint8* data = message.getRawData(); | const uint8* data = message.getRawData(); | ||||
| snd_seq_t* seqHandle = port.client->get(); | |||||
| while (numBytes > 0) | while (numBytes > 0) | ||||
| { | { | ||||
| const long numSent = snd_midi_event_encode (midiParser, data, numBytes, &event); | const long numSent = snd_midi_event_encode (midiParser, data, numBytes, &event); | ||||
| @@ -238,13 +458,15 @@ public: | |||||
| private: | private: | ||||
| MidiOutput* const midiOutput; | MidiOutput* const midiOutput; | ||||
| snd_seq_t* const seqHandle; | |||||
| AlsaPort port; | |||||
| snd_midi_event_t* midiParser; | snd_midi_event_t* midiParser; | ||||
| int maxEventSize; | int maxEventSize; | ||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiOutputDevice) | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiOutputDevice); | |||||
| }; | }; | ||||
| } // namespace | |||||
| StringArray MidiOutput::getDevices() | StringArray MidiOutput::getDevices() | ||||
| { | { | ||||
| StringArray devices; | StringArray devices; | ||||
| @@ -260,12 +482,14 @@ int MidiOutput::getDefaultDeviceIndex() | |||||
| MidiOutput* MidiOutput::openDevice (int deviceIndex) | MidiOutput* MidiOutput::openDevice (int deviceIndex) | ||||
| { | { | ||||
| MidiOutput* newDevice = nullptr; | MidiOutput* newDevice = nullptr; | ||||
| StringArray devices; | StringArray devices; | ||||
| AlsaPort port (iterateMidiDevices (false, devices, deviceIndex)); | |||||
| if (snd_seq_t* const handle = iterateMidiDevices (false, devices, deviceIndex)) | |||||
| if (port.isValid()) | |||||
| { | { | ||||
| newDevice = new MidiOutput(); | newDevice = new MidiOutput(); | ||||
| newDevice->internal = new MidiOutputDevice (newDevice, handle); | |||||
| newDevice->internal = new MidiOutputDevice (newDevice, port); | |||||
| } | } | ||||
| return newDevice; | return newDevice; | ||||
| @@ -275,10 +499,12 @@ MidiOutput* MidiOutput::createNewDevice (const String& deviceName) | |||||
| { | { | ||||
| MidiOutput* newDevice = nullptr; | MidiOutput* newDevice = nullptr; | ||||
| if (snd_seq_t* const handle = createMidiDevice (false, deviceName)) | |||||
| AlsaPort port (createMidiDevice (false, deviceName)); | |||||
| if (port.isValid()) | |||||
| { | { | ||||
| newDevice = new MidiOutput(); | newDevice = new MidiOutput(); | ||||
| newDevice->internal = new MidiOutputDevice (newDevice, handle); | |||||
| newDevice->internal = new MidiOutputDevice (newDevice, port); | |||||
| } | } | ||||
| return newDevice; | return newDevice; | ||||
| @@ -294,79 +520,6 @@ void MidiOutput::sendMessageNow (const MidiMessage& message) | |||||
| static_cast <MidiOutputDevice*> (internal)->sendMessageNow (message); | static_cast <MidiOutputDevice*> (internal)->sendMessageNow (message); | ||||
| } | } | ||||
| //============================================================================== | |||||
| class MidiInputThread : public Thread | |||||
| { | |||||
| public: | |||||
| MidiInputThread (MidiInput* input, snd_seq_t* handle, MidiInputCallback* cb) | |||||
| : Thread ("Juce MIDI Input"), | |||||
| midiInput (input), seqHandle (handle), callback (cb) | |||||
| { | |||||
| jassert (seqHandle != nullptr && callback != nullptr && midiInput != nullptr); | |||||
| } | |||||
| ~MidiInputThread() | |||||
| { | |||||
| snd_seq_close (seqHandle); | |||||
| } | |||||
| void run() | |||||
| { | |||||
| const int maxEventSize = 16 * 1024; | |||||
| snd_midi_event_t* midiParser; | |||||
| if (snd_midi_event_new (maxEventSize, &midiParser) >= 0) | |||||
| { | |||||
| HeapBlock <uint8> buffer (maxEventSize); | |||||
| const int numPfds = snd_seq_poll_descriptors_count (seqHandle, POLLIN); | |||||
| HeapBlock <pollfd> pfd (numPfds); | |||||
| snd_seq_poll_descriptors (seqHandle, pfd, numPfds, POLLIN); | |||||
| while (! threadShouldExit()) | |||||
| { | |||||
| if (poll (pfd, numPfds, 500) > 0) | |||||
| { | |||||
| snd_seq_event_t* inputEvent = nullptr; | |||||
| snd_seq_nonblock (seqHandle, 1); | |||||
| do | |||||
| { | |||||
| if (snd_seq_event_input (seqHandle, &inputEvent) >= 0) | |||||
| { | |||||
| // xxx what about SYSEXes that are too big for the buffer? | |||||
| const int numBytes = snd_midi_event_decode (midiParser, buffer, maxEventSize, inputEvent); | |||||
| snd_midi_event_reset_decode (midiParser); | |||||
| if (numBytes > 0) | |||||
| callback->handleIncomingMidiMessage (midiInput, | |||||
| MidiMessage ((const uint8*) buffer, numBytes, | |||||
| Time::getMillisecondCounter() * 0.001)); | |||||
| snd_seq_free_event (inputEvent); | |||||
| } | |||||
| } | |||||
| while (snd_seq_event_input_pending (seqHandle, 0) > 0); | |||||
| snd_seq_free_event (inputEvent); | |||||
| } | |||||
| } | |||||
| snd_midi_event_free (midiParser); | |||||
| } | |||||
| }; | |||||
| private: | |||||
| MidiInput* const midiInput; | |||||
| snd_seq_t* const seqHandle; | |||||
| MidiInputCallback* const callback; | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiInputThread) | |||||
| }; | |||||
| //============================================================================== | //============================================================================== | ||||
| MidiInput::MidiInput (const String& nm) | MidiInput::MidiInput (const String& nm) | ||||
| : name (nm), internal (nullptr) | : name (nm), internal (nullptr) | ||||
| @@ -376,11 +529,18 @@ MidiInput::MidiInput (const String& nm) | |||||
| MidiInput::~MidiInput() | MidiInput::~MidiInput() | ||||
| { | { | ||||
| stop(); | stop(); | ||||
| delete static_cast <MidiInputThread*> (internal); | |||||
| delete static_cast <AlsaPortAndCallback*> (internal); | |||||
| } | } | ||||
| void MidiInput::start() { static_cast <MidiInputThread*> (internal)->startThread(); } | |||||
| void MidiInput::stop() { static_cast <MidiInputThread*> (internal)->stopThread (3000); } | |||||
| void MidiInput::start() | |||||
| { | |||||
| static_cast<AlsaPortAndCallback*> (internal)->enableCallback (true); | |||||
| } | |||||
| void MidiInput::stop() | |||||
| { | |||||
| static_cast<AlsaPortAndCallback*> (internal)->enableCallback (false); | |||||
| } | |||||
| int MidiInput::getDefaultDeviceIndex() | int MidiInput::getDefaultDeviceIndex() | ||||
| { | { | ||||
| @@ -397,12 +557,14 @@ StringArray MidiInput::getDevices() | |||||
| MidiInput* MidiInput::openDevice (int deviceIndex, MidiInputCallback* callback) | MidiInput* MidiInput::openDevice (int deviceIndex, MidiInputCallback* callback) | ||||
| { | { | ||||
| MidiInput* newDevice = nullptr; | MidiInput* newDevice = nullptr; | ||||
| StringArray devices; | StringArray devices; | ||||
| AlsaPort port (iterateMidiDevices (true, devices, deviceIndex)); | |||||
| if (snd_seq_t* const handle = iterateMidiDevices (true, devices, deviceIndex)) | |||||
| if (port.isValid()) | |||||
| { | { | ||||
| newDevice = new MidiInput (devices [deviceIndex]); | newDevice = new MidiInput (devices [deviceIndex]); | ||||
| newDevice->internal = new MidiInputThread (newDevice, handle, callback); | |||||
| newDevice->internal = new AlsaPortAndCallback (port, newDevice, callback); | |||||
| } | } | ||||
| return newDevice; | return newDevice; | ||||
| @@ -412,10 +574,12 @@ MidiInput* MidiInput::createNewDevice (const String& deviceName, MidiInputCallba | |||||
| { | { | ||||
| MidiInput* newDevice = nullptr; | MidiInput* newDevice = nullptr; | ||||
| if (snd_seq_t* const handle = createMidiDevice (true, deviceName)) | |||||
| AlsaPort port (createMidiDevice (true, deviceName)); | |||||
| if (port.isValid()) | |||||
| { | { | ||||
| newDevice = new MidiInput (deviceName); | newDevice = new MidiInput (deviceName); | ||||
| newDevice->internal = new MidiInputThread (newDevice, handle, callback); | |||||
| newDevice->internal = new AlsaPortAndCallback (port, newDevice, callback); | |||||
| } | } | ||||
| return newDevice; | return newDevice; | ||||
| @@ -434,7 +598,7 @@ MidiOutput* MidiOutput::createNewDevice (const String&) { return nul | |||||
| MidiOutput::~MidiOutput() {} | MidiOutput::~MidiOutput() {} | ||||
| void MidiOutput::sendMessageNow (const MidiMessage&) {} | void MidiOutput::sendMessageNow (const MidiMessage&) {} | ||||
| MidiInput::MidiInput (const String& name_) : name (name_), internal (0) {} | |||||
| MidiInput::MidiInput (const String& nm) : name (nm), internal (nullptr) {} | |||||
| MidiInput::~MidiInput() {} | MidiInput::~MidiInput() {} | ||||
| void MidiInput::start() {} | void MidiInput::start() {} | ||||
| void MidiInput::stop() {} | void MidiInput::stop() {} | ||||