summaryrefslogtreecommitdiffstats
path: root/src/libs/engine/AlsaMidiDriver.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/libs/engine/AlsaMidiDriver.cpp')
-rw-r--r--src/libs/engine/AlsaMidiDriver.cpp373
1 files changed, 373 insertions, 0 deletions
diff --git a/src/libs/engine/AlsaMidiDriver.cpp b/src/libs/engine/AlsaMidiDriver.cpp
new file mode 100644
index 00000000..decd2471
--- /dev/null
+++ b/src/libs/engine/AlsaMidiDriver.cpp
@@ -0,0 +1,373 @@
+/* This file is part of Om. Copyright (C) 2006 Dave Robillard.
+ *
+ * Om is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Om is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "AlsaMidiDriver.h"
+#include <iostream>
+#include <cstdlib>
+#include <pthread.h>
+#include "Om.h"
+#include "OmApp.h"
+#include "util/types.h"
+#include "OmApp.h"
+#include "Maid.h"
+#include "AudioDriver.h"
+#include "PortInfo.h"
+#include "MidiMessage.h"
+#include "PortBase.h"
+#ifdef HAVE_LASH
+#include "LashDriver.h"
+#endif
+using std::cout; using std::cerr; using std::endl;
+
+namespace Om {
+
+
+//// AlsaMidiPort ////
+
+AlsaMidiPort::AlsaMidiPort(AlsaMidiDriver* driver, PortBase<MidiMessage>* port)
+: DriverPort(),
+ ListNode<AlsaMidiPort*>(this),
+ m_driver(driver),
+ m_patch_port(port),
+ m_port_id(0),
+ m_midi_pool(new unsigned char*[port->buffer_size()]),
+ m_events(1024)
+{
+ assert(port->parent() != NULL);
+ assert(port->poly() == 1);
+
+ if (port->port_info()->is_input()) {
+ if ((m_port_id = snd_seq_create_simple_port(driver->seq_handle(), port->path().c_str(),
+ SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE,
+ SND_SEQ_PORT_TYPE_APPLICATION)) < 0)
+ {
+ cerr << "[AlsaMidiPort] Error creating sequencer port." << endl;
+ exit(EXIT_FAILURE);
+ }
+ } else {
+ if ((m_port_id = snd_seq_create_simple_port(driver->seq_handle(), port->path().c_str(),
+ SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ,
+ SND_SEQ_PORT_TYPE_APPLICATION)) < 0)
+ {
+ cerr << "[AlsaMidiPort] Error creating sequencer port." << endl;
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ /* Allocate event pool. This pool is used when preparing a block from the queue
+ * of Alsa events. The buffer member of the MidiMessage's in the patch port's
+ * buffer will be set directly to an element in this pool, then next cycle they
+ * will be overwritten (eliminating the need for any allocation/freeing). */
+ for (size_t i=0; i < port->buffer_size(); ++i)
+ m_midi_pool[i] = new unsigned char[MAX_MIDI_EVENT_SIZE];
+
+ port->buffer(0)->clear();
+ port->fixed_buffers(true);
+}
+
+
+AlsaMidiPort::~AlsaMidiPort()
+{
+ snd_seq_delete_simple_port(m_driver->seq_handle(), m_port_id);
+
+ // Free event pool
+ for (size_t i=0; i < m_patch_port->buffer_size(); ++i)
+ delete[] m_midi_pool[i];
+
+ delete[] m_midi_pool;
+}
+
+
+void
+AlsaMidiPort::add_to_driver()
+{
+ m_driver->add_port(this);
+}
+
+
+void
+AlsaMidiPort::remove_from_driver()
+{
+ m_driver->remove_port(this);
+}
+
+
+void
+AlsaMidiPort::set_name(const string& name)
+{
+ snd_seq_port_info_t* info = NULL;
+ snd_seq_port_info_malloc(&info);
+ snd_seq_get_port_info(m_driver->seq_handle(), m_port_id, info);
+ snd_seq_port_info_set_name(info, name.c_str());
+ snd_seq_set_port_info(m_driver->seq_handle(), m_port_id, info);
+ snd_seq_port_info_free(info);
+}
+
+
+void
+AlsaMidiPort::event(snd_seq_event_t* const ev)
+{
+ // Abuse the tick field to hold the timestamp
+ ev->time.tick = om->audio_driver()->time_stamp();
+
+ // Fix noteons with velocity 0 (required for DSSI spec)
+ if (ev->type == SND_SEQ_EVENT_NOTEON && ev->data.note.velocity == 0)
+ ev->type = SND_SEQ_EVENT_NOTEOFF;
+
+ m_events.push(*ev);
+}
+
+
+/** Generates a flat array of MIDI events for patching.
+ *
+ * Prepares all events that occurred during the time interval passed
+ * (which ideally are the events from the previous cycle with an exact
+ * 1 cycle delay) and creates a flat port buffer for this cycle.
+ */
+void
+AlsaMidiPort::prepare_block(const samplecount block_start, const samplecount block_end)
+{
+ assert(block_end >= block_start);
+
+ snd_seq_event_t* ev = NULL;
+ MidiMessage* message = NULL;
+ size_t num_events = 0;
+ size_t event_size = 0; // decoded length of Alsa event in bytes
+ int timestamp = 0;
+
+ while (!m_events.is_empty() && m_events.front().time.tick < block_end) {
+ assert(num_events < m_patch_port->buffer_size());
+ ev = &m_events.front();
+ message = &m_patch_port->buffer(0)->data()[num_events];
+
+ timestamp = ev->time.tick - block_start;
+ if (timestamp < 0) {
+ // FIXME: remove this (obviously not realtime safe)
+ cerr << "[AlsaMidiPort] Missed event by " << -timestamp << " samples!" << endl;
+ timestamp = 0;
+ }
+ assert(timestamp < (int)(block_end - block_start));
+
+ // Reset decoder so we don't get running status
+ snd_midi_event_reset_decode(m_driver->event_coder());
+
+ // FIXME: is this realtime safe?
+ if ((event_size = snd_midi_event_decode(m_driver->event_coder(),
+ m_midi_pool[num_events], MAX_MIDI_EVENT_SIZE, ev)) > 0) {
+ message->size = event_size;
+ message->time = timestamp;
+ message->buffer = m_midi_pool[num_events];
+ ++num_events;
+ } else {
+ cerr << "[AlsaMidiPort] Unable to decode MIDI event" << endl;
+ }
+
+ m_events.pop();
+ }
+
+ m_patch_port->buffer(0)->filled_size(num_events);
+ m_patch_port->tied_port()->buffer(0)->filled_size(num_events);
+}
+
+
+
+//// AlsaMidiDriver ////
+
+
+bool AlsaMidiDriver::m_midi_thread_exit_flag = true;
+
+
+AlsaMidiDriver::AlsaMidiDriver()
+: m_seq_handle(NULL),
+ m_event_coder(NULL),
+ m_is_activated(false)
+{
+ if (snd_seq_open(&m_seq_handle, "hw", SND_SEQ_OPEN_INPUT, 0) < 0) {
+ cerr << "[AlsaMidiDriver] Error opening ALSA sequencer." << endl;
+ exit(EXIT_FAILURE);
+ } else {
+ cout << "[AlsaMidiDriver] Successfully opened ALSA sequencer." << endl;
+ }
+
+ if (snd_midi_event_new(3, &m_event_coder)) {
+ cerr << "[AlsaMidiDriver] Failed to initialize ALSA MIDI event coder!";
+ exit(EXIT_FAILURE);
+ } else {
+ snd_midi_event_reset_encode(m_event_coder);
+ snd_midi_event_reset_decode(m_event_coder);
+ }
+
+ snd_seq_set_client_name(m_seq_handle, "Om");
+}
+
+
+AlsaMidiDriver::~AlsaMidiDriver()
+{
+ deactivate();
+ snd_midi_event_free(m_event_coder);
+ snd_seq_close(m_seq_handle);
+}
+
+
+/** Launch and start the MIDI thread.
+ */
+void
+AlsaMidiDriver::activate()
+{
+ // Just exit if already running
+ if (m_midi_thread_exit_flag == false)
+ return;
+
+ bool success = false;
+ m_midi_thread_exit_flag = false;
+
+ //if (om->audio_driver()->is_realtime()) {
+ pthread_attr_t attr;
+ pthread_attr_init(&attr);
+
+ if (pthread_attr_setschedpolicy(&attr, SCHED_FIFO)) {
+ cerr << "[AlsaMidiDriver] Unable to set realtime scheduling for MIDI thread." << endl;
+ }
+
+ sched_param param;
+ param.sched_priority = 10;
+
+ pthread_attr_setstacksize(&attr, 1500000);
+
+ if (pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED)
+ || pthread_attr_setschedparam(&attr, &param))
+ cout << "[AlsaMidiDriver] Unable to set SCHED_FIFO priority "
+ << param.sched_priority << endl;
+
+ if (!pthread_create(&m_process_thread, &attr, process_midi_in, this)) {
+ cout << "[AlsaMidiDriver] Started realtime MIDI thread (SCHED_FIFO, priority "
+ << param.sched_priority << ")" << endl;
+ success = true;
+ } else {
+ cerr << "[AlsaMidiDriver] Unable to start realtime MIDI thread." << endl;
+ }
+ pthread_attr_destroy(&attr);
+ //}
+
+ if (!success) {
+ // FIXME: check for success
+ pthread_create(&m_process_thread, NULL, process_midi_in, this);
+ cout << "[AlsaMidiDriver] Started non-realtime MIDI thread." << endl;
+ }
+
+#ifdef HAVE_LASH
+ lash_driver->set_alsa_client_id(snd_seq_client_id(m_seq_handle));
+#endif
+
+ m_is_activated = true;
+}
+
+
+/** Terminate the MIDI thread.
+ */
+void
+AlsaMidiDriver::deactivate()
+{
+ if (m_is_activated) {
+ m_midi_thread_exit_flag = true;
+ pthread_cancel(m_process_thread);
+ pthread_join(m_process_thread, NULL);
+ m_is_activated = false;
+ }
+}
+
+
+/** Build flat arrays of events for DSSI plugins for each Port.
+ */
+void
+AlsaMidiDriver::prepare_block(const samplecount block_start, const samplecount block_end)
+{
+ for (List<AlsaMidiPort*>::iterator i = m_in_ports.begin(); i != m_in_ports.end(); ++i)
+ (*i)->prepare_block(block_start, block_end);
+}
+
+
+/** Add an Alsa MIDI port.
+ *
+ * Realtime safe, this is to be called at the beginning of a process cycle to
+ * insert (and actually begin using) a new port.
+ *
+ * See create_port() and remove_port().
+ */
+void
+AlsaMidiDriver::add_port(AlsaMidiPort* port)
+{
+ if (port->patch_port()->port_info()->is_input())
+ m_in_ports.push_back(port);
+ else
+ m_out_ports.push_back(port);
+}
+
+
+/** Remove an Alsa MIDI port.
+ *
+ * Realtime safe. This is to be called at the beginning of a process cycle to
+ * remove the port from the lists read by the audio thread, so the port
+ * will no longer be used and can be removed afterwards.
+ *
+ * It is the callers responsibility to delete the returned port.
+ */
+AlsaMidiPort*
+AlsaMidiDriver::remove_port(AlsaMidiPort* port)
+{
+ if (port->patch_port()->port_info()->is_input()) {
+ for (List<AlsaMidiPort*>::iterator i = m_in_ports.begin(); i != m_in_ports.end(); ++i)
+ if ((*i) == (AlsaMidiPort*)port)
+ return m_in_ports.remove(i)->elem();
+ } else {
+ for (List<AlsaMidiPort*>::iterator i = m_out_ports.begin(); i != m_out_ports.end(); ++i)
+ if ((*i) == port)
+ return m_out_ports.remove(i)->elem();
+ }
+
+ cerr << "[AlsaMidiDriver::remove_input] WARNING: Failed to find Jack port to remove!" << endl;
+ return NULL;
+}
+
+
+/** MIDI thread.
+ */
+void*
+AlsaMidiDriver::process_midi_in(void* alsa_driver)
+{
+ AlsaMidiDriver* ad = (AlsaMidiDriver*)alsa_driver;
+
+ snd_seq_event_t* ev;
+
+ int npfd = snd_seq_poll_descriptors_count(ad->m_seq_handle, POLLIN);
+ struct pollfd pfd;
+ snd_seq_poll_descriptors(ad->m_seq_handle, &pfd, npfd, POLLIN);
+
+ while ( ! m_midi_thread_exit_flag)
+ if (poll(&pfd, npfd, 100000) > 0)
+ while (snd_seq_event_input(ad->m_seq_handle, &ev) > 0)
+ for (List<AlsaMidiPort*>::iterator i = ad->m_in_ports.begin(); i != ad->m_in_ports.end(); ++i)
+ if ((*i)->port_id() == ev->dest.port)
+ (*i)->event(ev);
+
+ cout << "[AlsaMidiDriver] Exiting MIDI thread." << endl;
+
+ return NULL;
+}
+
+
+} // namespace Om
+