summaryrefslogtreecommitdiffstats
path: root/src/libs/engine/JackAudioDriver.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/libs/engine/JackAudioDriver.cpp')
-rw-r--r--src/libs/engine/JackAudioDriver.cpp373
1 files changed, 373 insertions, 0 deletions
diff --git a/src/libs/engine/JackAudioDriver.cpp b/src/libs/engine/JackAudioDriver.cpp
new file mode 100644
index 00000000..0f5e3b1a
--- /dev/null
+++ b/src/libs/engine/JackAudioDriver.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 "JackAudioDriver.h"
+#include "config.h"
+#include "tuning.h"
+#include <iostream>
+#include <cstdlib>
+#include "Om.h"
+#include "OmApp.h"
+#include "util.h"
+#include "Event.h"
+#include "QueuedEvent.h"
+#include "EventSource.h"
+#include "PostProcessor.h"
+#include "util/Queue.h"
+#include "Node.h"
+#include "Patch.h"
+#include "Port.h"
+#include "PortInfo.h"
+#include "MidiDriver.h"
+#include "List.h"
+#include "PortBase.h"
+#ifdef HAVE_LASH
+#include "LashDriver.h"
+#endif
+
+using std::cout; using std::cerr; using std::endl;
+
+
+namespace Om {
+
+
+//// JackAudioPort ////
+
+JackAudioPort::JackAudioPort(JackAudioDriver* driver, PortBase<sample>* patch_port)
+: DriverPort(),
+ ListNode<JackAudioPort*>(this),
+ m_driver(driver),
+ m_jack_port(NULL),
+ m_jack_buffer(NULL),
+ m_patch_port(patch_port)
+{
+ assert(patch_port->tied_port() != NULL);
+ assert(patch_port->poly() == 1);
+
+ m_jack_port = jack_port_register(m_driver->jack_client(),
+ patch_port->path().c_str(), JACK_DEFAULT_AUDIO_TYPE,
+ (patch_port->port_info()->is_input()) ? JackPortIsInput : JackPortIsOutput,
+ 0);
+
+ m_jack_buffer = new DriverBuffer<jack_sample_t>(driver->buffer_size());
+
+ patch_port->fixed_buffers(true);
+}
+
+
+JackAudioPort::~JackAudioPort()
+{
+ jack_port_unregister(m_driver->jack_client(), m_jack_port);
+}
+
+
+void
+JackAudioPort::add_to_driver()
+{
+ m_driver->add_port(this);
+}
+
+
+void
+JackAudioPort::remove_from_driver()
+{
+ m_driver->remove_port(this);
+}
+
+void
+JackAudioPort::prepare_buffer(jack_nframes_t nframes)
+{
+ // Technically this doesn't need to be done every time for output ports
+ m_jack_buffer->set_data((jack_default_audio_sample_t*)
+ jack_port_get_buffer(m_jack_port, nframes));
+
+ assert(m_patch_port->tied_port() != NULL);
+
+ // FIXME: fixed_buffers switch on/off thing can be removed once this shit
+ // gets figured out and assertions can go away
+ m_patch_port->fixed_buffers(false);
+ m_patch_port->buffer(0)->join(m_jack_buffer);
+ m_patch_port->tied_port()->buffer(0)->join(m_jack_buffer);
+
+ m_patch_port->fixed_buffers(true);
+
+ assert(m_patch_port->buffer(0)->data() == m_patch_port->tied_port()->buffer(0)->data());
+ assert(m_patch_port->buffer(0)->data() == m_jack_buffer->data());
+}
+
+
+//// JackAudioDriver ////
+
+JackAudioDriver::JackAudioDriver()
+: m_client(NULL),
+ m_buffer_size(0),
+ m_sample_rate(0),
+ m_is_activated(false),
+ m_local_client(true),
+ m_root_patch(NULL),
+ m_start_of_current_cycle(0),
+ m_start_of_last_cycle(0)
+{
+ m_client = jack_client_new("Om");
+ if (m_client == NULL) {
+ cerr << "[JackAudioDriver] Unable to connect to Jack. Exiting." << endl;
+ exit(EXIT_FAILURE);
+ }
+
+ jack_on_shutdown(m_client, shutdown_cb, this);
+
+ m_buffer_size = jack_get_buffer_size(m_client);
+ m_sample_rate = jack_get_sample_rate(m_client);
+
+ jack_set_sample_rate_callback(m_client, sample_rate_cb, this);
+ jack_set_buffer_size_callback(m_client, buffer_size_cb, this);
+}
+
+JackAudioDriver::JackAudioDriver(jack_client_t* jack_client)
+: m_client(jack_client),
+ m_buffer_size(jack_get_buffer_size(jack_client)),
+ m_sample_rate(jack_get_sample_rate(jack_client)),
+ m_is_activated(false),
+ m_local_client(false),
+ m_start_of_current_cycle(0),
+ m_start_of_last_cycle(0)
+{
+ jack_on_shutdown(m_client, shutdown_cb, this);
+
+ jack_set_sample_rate_callback(m_client, sample_rate_cb, this);
+ jack_set_buffer_size_callback(m_client, buffer_size_cb, this);
+}
+
+
+JackAudioDriver::~JackAudioDriver()
+{
+ deactivate();
+
+ if (m_local_client)
+ jack_client_close(m_client);
+}
+
+
+void
+JackAudioDriver::activate()
+{
+ if (m_is_activated) {
+ cerr << "[JackAudioDriver] Jack driver already activated." << endl;
+ return;
+ }
+
+ jack_set_process_callback(m_client, process_cb, this);
+
+ m_is_activated = true;
+
+ if (jack_activate(m_client)) {
+ cerr << "[JackAudioDriver] Could not activate Jack client, aborting." << endl;
+ exit(EXIT_FAILURE);
+ } else {
+ cout << "[JackAudioDriver] Activated Jack client." << endl;
+#ifdef HAVE_LASH
+ lash_driver->set_jack_client_name("Om"); // FIXME: unique name
+#endif
+ }
+}
+
+
+void
+JackAudioDriver::deactivate()
+{
+ if (m_is_activated) {
+ // FIXME
+ reinterpret_cast<EventSource*>(om->osc_receiver())->stop();
+
+ jack_deactivate(m_client);
+ m_is_activated = false;
+
+ for (List<JackAudioPort*>::iterator i = m_ports.begin(); i != m_ports.end(); ++i)
+ jack_port_unregister(m_client, (*i)->jack_port());
+
+ m_ports.clear();
+
+ cout << "[JackAudioDriver] Deactivated Jack client." << endl;
+
+ om->post_processor()->stop();
+ }
+}
+
+
+/** Add a Jack 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
+JackAudioDriver::add_port(JackAudioPort* port)
+{
+ m_ports.push_back(port);
+}
+
+
+/** Remove a Jack 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.
+ */
+JackAudioPort*
+JackAudioDriver::remove_port(JackAudioPort* port)
+{
+ for (List<JackAudioPort*>::iterator i = m_ports.begin(); i != m_ports.end(); ++i)
+ if ((*i) == port)
+ return m_ports.remove(i)->elem();
+
+ cerr << "[JackAudioDriver::remove_port] WARNING: Failed to find Jack port to remove!" << endl;
+ return NULL;
+}
+
+
+DriverPort*
+JackAudioDriver::create_port(PortBase<sample>* patch_port)
+{
+ if (patch_port->buffer_size() == m_buffer_size)
+ return new JackAudioPort(this, patch_port);
+ else
+ return NULL;
+}
+
+
+/** Process all the pending events for this cycle.
+ *
+ * Called from the realtime thread once every process cycle.
+ */
+void
+JackAudioDriver::process_events(jack_nframes_t block_start, jack_nframes_t block_end)
+{
+ Event* ev = NULL;
+
+ /* Limit the maximum number of queued events to process per cycle. This
+ * makes the process callback truly realtime-safe by preventing being
+ * choked by events coming in faster than they can be processed.
+ * FIXME: run the math on this and figure out a good value */
+ const int MAX_SLOW_EVENTS = m_buffer_size / 100;
+
+ int num_events_processed = 0;
+ int offset = 0;
+
+ // Process the "slow" events first, because it's possible some of the
+ // RT events depend on them
+
+ // FIXME
+ while ((ev = reinterpret_cast<EventSource*>(om->osc_receiver())
+ ->pop_earliest_event_before(block_end)) != NULL) {
+ ev->execute(0); // QueuedEvents are not sample accurate
+ om->post_processor()->push(ev);
+ if (++num_events_processed > MAX_SLOW_EVENTS)
+ break;
+ }
+
+ while (!om->event_queue()->is_empty()
+ && om->event_queue()->front()->time_stamp() < block_end) {
+ ev = om->event_queue()->pop();
+ offset = ev->time_stamp() - block_start;
+ if (offset < 0) offset = 0; // this can happen if we miss a cycle
+ ev->execute(offset);
+ om->post_processor()->push(ev);
+ ++num_events_processed;
+ }
+
+ if (num_events_processed > 0)
+ om->post_processor()->signal();
+}
+
+
+
+/**** Jack Callbacks ****/
+
+
+
+/** Jack process callback, drives entire audio thread.
+ *
+ * \callgraph
+ */
+int
+JackAudioDriver::m_process_cb(jack_nframes_t nframes)
+{
+ // FIXME: support nframes != buffer_size, even though that never damn well happens
+ assert(nframes == m_buffer_size);
+
+ // Note that jack can elect to not call this function for a cycle, if things aren't
+ // keeping up
+ m_start_of_current_cycle = jack_last_frame_time(m_client);
+ m_start_of_last_cycle = m_start_of_current_cycle - nframes;
+
+ assert(m_start_of_current_cycle - m_start_of_last_cycle == nframes);
+
+ m_transport_state = jack_transport_query(m_client, &m_position);
+
+ process_events(m_start_of_last_cycle, m_start_of_current_cycle);
+ om->midi_driver()->prepare_block(m_start_of_last_cycle, m_start_of_current_cycle);
+
+ // Set buffers of patch ports to Jack port buffers (zero-copy processing)
+ for (List<JackAudioPort*>::iterator i = m_ports.begin(); i != m_ports.end(); ++i)
+ (*i)->prepare_buffer(nframes);
+
+ // Run root patch
+ assert(m_root_patch != NULL);
+ m_root_patch->run(nframes);
+
+ return 0;
+}
+
+
+void
+JackAudioDriver::m_shutdown_cb()
+{
+ cout << "[JackAudioDriver] Jack shutdown. Exiting." << endl;
+ om->quit();
+}
+
+
+int
+JackAudioDriver::m_sample_rate_cb(jack_nframes_t nframes)
+{
+ if (m_is_activated) {
+ cerr << "[JackAudioDriver] Om does not support changing sample rate on the fly (yet). Aborting." << endl;
+ exit(EXIT_FAILURE);
+ } else {
+ m_sample_rate = nframes;
+ }
+ return 0;
+}
+
+
+int
+JackAudioDriver::m_buffer_size_cb(jack_nframes_t nframes)
+{
+ if (m_is_activated) {
+ cerr << "[JackAudioDriver] Om does not support chanding buffer size on the fly (yet). Aborting." << endl;
+ exit(EXIT_FAILURE);
+ } else {
+ m_buffer_size = nframes;
+ }
+ return 0;
+}
+
+
+} // namespace Om
+