From 98fe0e7056e6697396249531785d3899f94d79be Mon Sep 17 00:00:00 2001 From: David Robillard Date: Sat, 10 Jun 2006 01:52:02 +0000 Subject: More juggling git-svn-id: http://svn.drobilla.net/lad/grauph@15 a436a847-0d15-0410-975c-d299462d15a1 --- src/libs/engine/JackAudioDriver.cpp | 373 ++++++++++++++++++++++++++++++++++++ 1 file changed, 373 insertions(+) create mode 100644 src/libs/engine/JackAudioDriver.cpp (limited to 'src/libs/engine/JackAudioDriver.cpp') 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 +#include +#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* patch_port) +: DriverPort(), + ListNode(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(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(om->osc_receiver())->stop(); + + jack_deactivate(m_client); + m_is_activated = false; + + for (List::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::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* 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(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::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 + -- cgit v1.2.1