diff options
Diffstat (limited to 'src/engine/JackAudioDriver.cpp')
-rw-r--r-- | src/engine/JackAudioDriver.cpp | 384 |
1 files changed, 384 insertions, 0 deletions
diff --git a/src/engine/JackAudioDriver.cpp b/src/engine/JackAudioDriver.cpp new file mode 100644 index 00000000..eff04653 --- /dev/null +++ b/src/engine/JackAudioDriver.cpp @@ -0,0 +1,384 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "JackAudioDriver.hpp" +#include CONFIG_H_PATH +#include "tuning.hpp" +#include <iostream> +#include <cstdlib> +#include <raul/List.hpp> +#include "Engine.hpp" +#include "util.hpp" +#include "Event.hpp" +#include "ThreadManager.hpp" +#include "QueuedEvent.hpp" +#include "EventSource.hpp" +#include "PostProcessor.hpp" +#include "NodeImpl.hpp" +#include "PatchImpl.hpp" +#include "PortImpl.hpp" +#include "MidiDriver.hpp" +#include "DuplexPort.hpp" +#include "EventSource.hpp" +#include "AudioBuffer.hpp" +#include "ProcessSlave.hpp" + +using namespace std; + +namespace Ingen { + + +//// JackAudioPort //// + +JackAudioPort::JackAudioPort(JackAudioDriver* driver, DuplexPort* patch_port) + : DriverPort(patch_port) + , Raul::List<JackAudioPort*>::Node(this) + , _driver(driver) + , _jack_port(NULL) + , _jack_buffer(NULL) +{ + assert(patch_port->poly() == 1); + + _jack_port = jack_port_register(_driver->jack_client(), + patch_port->path().c_str(), JACK_DEFAULT_AUDIO_TYPE, + (patch_port->is_input()) ? JackPortIsInput : JackPortIsOutput, + 0); + + if (_jack_port == NULL) { + cerr << "[JackAudioPort] ERROR: Failed to register port " << patch_port->path() << endl; + throw JackAudioDriver::PortRegistrationFailedException(); + } + + patch_port->buffer(0)->clear(); + patch_port->fixed_buffers(true); +} + + +JackAudioPort::~JackAudioPort() +{ + jack_port_unregister(_driver->jack_client(), _jack_port); +} + + +void +JackAudioPort::prepare_buffer(jack_nframes_t nframes) +{ + jack_sample_t* jack_buf = (jack_sample_t*)jack_port_get_buffer(_jack_port, nframes); + + AudioBuffer* patch_buf = (AudioBuffer*)_patch_port->buffer(0); + + //cerr << "[JACK] " << _patch_port->path() << " buffer: " << patch_buf << endl; + + if (jack_buf != _jack_buffer) { + patch_buf->set_data(jack_buf); + _jack_buffer = jack_buf; + } + + assert(patch_buf->data() == jack_buf); +} + + +//// JackAudioDriver //// + +JackAudioDriver::JackAudioDriver(Engine& engine, + std::string server_name, + jack_client_t* jack_client) + : _engine(engine) + , _jack_thread(NULL) + , _client(jack_client) + , _buffer_size(jack_client ? jack_get_buffer_size(jack_client) : 0) + , _sample_rate(jack_client ? jack_get_sample_rate(jack_client) : 0) + , _is_activated(false) + , _local_client(true) // FIXME + , _process_context(engine) + , _root_patch(NULL) +{ + if (!_client) { + // Try supplied server name + if (server_name != "") { + _client = jack_client_open("Ingen", JackServerName, NULL, server_name.c_str()); + if (_client) + cerr << "[JackAudioDriver] Connected to JACK server '" << + server_name << "'" << endl; + } + + // Either server name not specified, or supplied server name does not exist + // Connect to default server + if (!_client) { + _client = jack_client_open("Ingen", JackNullOption, NULL); + + if (_client) + cerr << "[JackAudioDriver] Connected to default JACK server." << endl; + } + + // Still failed + if (!_client) { + cerr << "[JackAudioDriver] Unable to connect to Jack. Exiting." << endl; + exit(EXIT_FAILURE); + } + + _buffer_size = jack_get_buffer_size(_client); + _sample_rate = jack_get_sample_rate(_client); + } + + jack_on_shutdown(_client, shutdown_cb, this); + + jack_set_thread_init_callback(_client, thread_init_cb, this); + jack_set_sample_rate_callback(_client, sample_rate_cb, this); + jack_set_buffer_size_callback(_client, buffer_size_cb, this); +} + + +JackAudioDriver::~JackAudioDriver() +{ + deactivate(); + + if (_local_client) + jack_client_close(_client); +} + + +void +JackAudioDriver::activate() +{ + if (_is_activated) { + cerr << "[JackAudioDriver] Jack driver already activated." << endl; + return; + } + + jack_set_process_callback(_client, process_cb, this); + + _is_activated = true; + + if (jack_activate(_client)) { + cerr << "[JackAudioDriver] Could not activate Jack client, aborting." << endl; + exit(EXIT_FAILURE); + } else { + cout << "[JackAudioDriver] Activated Jack client." << endl; +/*#ifdef HAVE_LASH + _engine.lash_driver()->set_jack_client_name(jack_client_get_name(_client)); +#endif*/ + } +} + + +void +JackAudioDriver::deactivate() +{ + if (_is_activated) { + + //for (Raul::List<JackAudioPort*>::iterator i = _ports.begin(); i != _ports.end(); ++i) + // jack_port_unregister(_client, (*i)->jack_port()); + + jack_deactivate(_client); + _is_activated = false; + + _ports.clear(); + + cout << "[JackAudioDriver] Deactivated Jack client." << endl; + + //_engine.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(DriverPort* port) +{ + assert(ThreadManager::current_thread_id() == THREAD_PROCESS); + + assert(dynamic_cast<JackAudioPort*>(port)); + _ports.push_back((JackAudioPort*)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. + */ +DriverPort* +JackAudioDriver::remove_port(const Path& path) +{ + assert(ThreadManager::current_thread_id() == THREAD_PROCESS); + + for (Raul::List<JackAudioPort*>::iterator i = _ports.begin(); i != _ports.end(); ++i) + if ((*i)->patch_port()->path() == path) + return _ports.erase(i)->elem(); // FIXME: LEAK + + cerr << "[JackAudioDriver::remove_port] WARNING: Unable to find Jack port " << path << endl; + return NULL; +} + + +DriverPort* +JackAudioDriver::port(const Path& path) +{ + for (Raul::List<JackAudioPort*>::iterator i = _ports.begin(); i != _ports.end(); ++i) + if ((*i)->patch_port()->path() == path) + return (*i); + + return NULL; +} + + +DriverPort* +JackAudioDriver::create_port(DuplexPort* patch_port) +{ + try { + if (patch_port->buffer_size() == _buffer_size) + return new JackAudioPort(this, patch_port); + else + return NULL; + } catch (...) { + return NULL; + } +} + + +DriverPort* +JackAudioDriver::driver_port(const Path& path) +{ + assert(ThreadManager::current_thread_id() == THREAD_PROCESS); + + for (Raul::List<JackAudioPort*>::iterator i = _ports.begin(); i != _ports.end(); ++i) + if ((*i)->patch_port()->path() == path) + return (*i); + + return NULL; +} + + +/**** Jack Callbacks ****/ + + + +/** Jack process callback, drives entire audio thread. + * + * \callgraph + */ +int +JackAudioDriver::_process_cb(jack_nframes_t nframes) +{ + if (nframes == 0 || ! _is_activated) + return 0; + + // FIXME: all of this time stuff is screwy + + // FIXME: support nframes != buffer_size, even though that never damn well happens + assert(nframes == _buffer_size); + + // Jack can elect to not call this function for a cycle, if overloaded + // FIXME: this doesn't make sense, and the start time isn't used anyway + const jack_nframes_t start_of_current_cycle = jack_last_frame_time(_client); + + const jack_nframes_t end_of_current_cycle = start_of_current_cycle + nframes; +#ifndef NDEBUG + // FIXME: support changing cycle length + const jack_nframes_t start_of_last_cycle = start_of_current_cycle - nframes; + assert(start_of_current_cycle - start_of_last_cycle == nframes); +#endif + + _transport_state = jack_transport_query(_client, &_position); + + _process_context.set_time_slice(nframes, start_of_current_cycle, end_of_current_cycle); + + for (Engine::ProcessSlaves::iterator i = _engine.process_slaves().begin(); + i != _engine.process_slaves().end(); ++i) { + (*i)->context().set_time_slice(nframes, start_of_current_cycle, end_of_current_cycle); + } + + // Process events that came in during the last cycle + // (Aiming for jitter-free 1 block event latency, ideally) + _engine.process_events(_process_context); + + // Set buffers of patch ports to Jack port buffers (zero-copy processing) + for (Raul::List<JackAudioPort*>::iterator i = _ports.begin(); i != _ports.end(); ++i) { + assert(*i); + (*i)->prepare_buffer(nframes); + } + + assert(_engine.midi_driver()); + _engine.midi_driver()->pre_process(_process_context); + + // Run root patch + if (_root_patch) + _root_patch->process(_process_context); + + _engine.midi_driver()->post_process(_process_context); + + _engine.post_processor()->set_end_time(_process_context.end()); + + return 0; +} + + +void +JackAudioDriver::_thread_init_cb() +{ + // Initialize thread specific data + _jack_thread = Thread::create_for_this_thread("Jack"); + assert(&Thread::get() == _jack_thread); + _jack_thread->set_context(THREAD_PROCESS); + assert(ThreadManager::current_thread_id() == THREAD_PROCESS); +} + +void +JackAudioDriver::_shutdown_cb() +{ + cout << "[JackAudioDriver] Jack shutdown. Exiting." << endl; + _engine.quit(); + _jack_thread = NULL; +} + + +int +JackAudioDriver::_sample_rate_cb(jack_nframes_t nframes) +{ + if (_is_activated) { + cerr << "[JackAudioDriver] On-the-fly sample rate changing not supported (yet). Aborting." << endl; + exit(EXIT_FAILURE); + } else { + _sample_rate = nframes; + } + return 0; +} + + +int +JackAudioDriver::_buffer_size_cb(jack_nframes_t nframes) +{ + if (_root_patch) { + _root_patch->set_buffer_size(nframes); + _buffer_size = nframes; + } + return 0; +} + + +} // namespace Ingen + |