From 566479a3ac4c8c6c9d190d6b4e8ecd104402180c Mon Sep 17 00:00:00 2001 From: David Robillard Date: Tue, 5 Jan 2010 21:55:24 +0000 Subject: JackAudioDriver.[ch]pp -> JackDriver.[ch]pp git-svn-id: http://svn.drobilla.net/lad/trunk/ingen@2344 a436a847-0d15-0410-975c-d299462d15a1 --- src/engine/JackAudioDriver.cpp | 498 ----------------------------------------- src/engine/JackAudioDriver.hpp | 173 -------------- src/engine/JackDriver.cpp | 498 +++++++++++++++++++++++++++++++++++++++++ src/engine/JackDriver.hpp | 173 ++++++++++++++ src/engine/ingen_jack.cpp | 2 +- src/engine/wscript | 2 +- 6 files changed, 673 insertions(+), 673 deletions(-) delete mode 100644 src/engine/JackAudioDriver.cpp delete mode 100644 src/engine/JackAudioDriver.hpp create mode 100644 src/engine/JackDriver.cpp create mode 100644 src/engine/JackDriver.hpp (limited to 'src/engine') diff --git a/src/engine/JackAudioDriver.cpp b/src/engine/JackAudioDriver.cpp deleted file mode 100644 index 44951f27..00000000 --- a/src/engine/JackAudioDriver.cpp +++ /dev/null @@ -1,498 +0,0 @@ -/* This file is part of Ingen. - * Copyright (C) 2007-2009 Dave Robillard - * - * 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 -#include -#include -#include "raul/List.hpp" -#include "shared/LV2Features.hpp" -#include "shared/LV2URIMap.hpp" -#include "AudioBuffer.hpp" -#include "DuplexPort.hpp" -#include "Engine.hpp" -#include "Event.hpp" -#include "EventBuffer.hpp" -#include "EventSource.hpp" -#include "EventSource.hpp" -#include "JackAudioDriver.hpp" -#include "MessageContext.hpp" -#include "PatchImpl.hpp" -#include "PortImpl.hpp" -#include "PostProcessor.hpp" -#include "ProcessSlave.hpp" -#include "QueuedEvent.hpp" -#include "ThreadManager.hpp" -#include "ingen-config.h" -#include "tuning.hpp" -#include "util.hpp" - -using namespace std; -using namespace Raul; - -namespace Ingen { - - -//// JackAudioPort //// - -JackAudioPort::JackAudioPort(JackAudioDriver* driver, DuplexPort* patch_port) - : DriverPort(patch_port) - , Raul::List::Node(this) - , _driver(driver) - , _jack_port(NULL) -{ - assert(patch_port->poly() == 1); - - create(); - - patch_port->buffer(0)->clear(); -} - - -JackAudioPort::~JackAudioPort() -{ - assert(_jack_port == NULL); -} - - -void -JackAudioPort::create() -{ - _jack_port = jack_port_register( - _driver->jack_client(), - ingen_jack_port_name(_patch_port->path()).c_str(), - (_patch_port->type() == PortType::AUDIO) - ? JACK_DEFAULT_AUDIO_TYPE : JACK_DEFAULT_MIDI_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(); - } -} - - -void -JackAudioPort::destroy() -{ - assert(_jack_port); - if (jack_port_unregister(_driver->jack_client(), _jack_port)) - cerr << "[JackMidiPort] ERROR: Unable to unregister port" << endl; - _jack_port = NULL; -} - - -void -JackAudioPort::move(const Raul::Path& path) -{ - jack_port_set_name(_jack_port, ingen_jack_port_name(path).c_str()); -} - - -void -JackAudioPort::pre_process(ProcessContext& context) -{ - if (!is_input()) - return; - - const SampleCount nframes = context.nframes(); - - if (_patch_port->type() == PortType::AUDIO) { - jack_sample_t* jack_buf = (jack_sample_t*)jack_port_get_buffer(_jack_port, nframes); - AudioBuffer* patch_buf = (AudioBuffer*)_patch_port->buffer(0).get(); - - patch_buf->copy(jack_buf, 0, nframes - 1); - - } else if (_patch_port->type() == PortType::EVENTS) { - void* jack_buf = jack_port_get_buffer(_jack_port, nframes); - EventBuffer* patch_buf = (EventBuffer*)_patch_port->buffer(0).get(); - - const jack_nframes_t event_count = jack_midi_get_event_count(jack_buf); - - patch_buf->prepare_write(context); - - // Copy events from Jack port buffer into patch port buffer - for (jack_nframes_t i = 0; i < event_count; ++i) { - jack_midi_event_t ev; - jack_midi_event_get(&ev, jack_buf, i); - - if (!patch_buf->append(ev.time, 0, _driver->_midi_event_type, ev.size, ev.buffer)) - cerr << "WARNING: Failed to write MIDI to port buffer, event(s) lost!" << endl; - } - } -} - - -void -JackAudioPort::post_process(ProcessContext& context) -{ - if (is_input()) - return; - - const SampleCount nframes = context.nframes(); - - if (_patch_port->type() == PortType::AUDIO) { - jack_sample_t* jack_buf = (jack_sample_t*)jack_port_get_buffer(_jack_port, nframes); - AudioBuffer* patch_buf = (AudioBuffer*)_patch_port->buffer(0).get(); - - memcpy(jack_buf, patch_buf->data(), nframes * sizeof(Sample)); - - } else if (_patch_port->type() == PortType::EVENTS) { - void* jack_buf = jack_port_get_buffer(_jack_port, context.nframes()); - EventBuffer* patch_buf = (EventBuffer*)_patch_port->buffer(0).get(); - - patch_buf->prepare_read(context); - jack_midi_clear_buffer(jack_buf); - - uint32_t frames = 0; - uint32_t subframes = 0; - uint16_t type = 0; - uint16_t size = 0; - uint8_t* data = NULL; - - // Copy events from Jack port buffer into patch port buffer - for (patch_buf->rewind(); patch_buf->is_valid(); patch_buf->increment()) { - patch_buf->get_event(&frames, &subframes, &type, &size, &data); - jack_midi_event_write(jack_buf, frames, data, size); - } - } -} - - -//// JackAudioDriver //// - -JackAudioDriver::JackAudioDriver(Engine& engine) - : _engine(engine) - , _jack_thread(NULL) - , _sem(0) - , _flag(0) - , _client(NULL) - , _buffer_size(0) - , _sample_rate(0) - , _is_activated(false) - , _local_client(true) - , _process_context(engine) - , _root_patch(NULL) -{ - SharedPtr map = PtrCast( - _engine.world()->lv2_features->feature(LV2_URI_MAP_URI)); - _midi_event_type = map->uri_to_id(NULL, "http://lv2plug.in/ns/ext/midi#MidiEvent"); -} - - -JackAudioDriver::~JackAudioDriver() -{ - deactivate(); - - if (_local_client) - jack_client_close(_client); -} - - -bool -JackAudioDriver::supports(Shared::PortType port_type, Shared::EventType event_type) -{ - return (port_type == PortType::AUDIO - || (port_type == PortType::EVENTS && event_type == EventType::MIDI)); -} - - -bool -JackAudioDriver::attach(const std::string& server_name, - const std::string& client_name, - void* jack_client) -{ - assert(!_client); - if (!jack_client) { - // Try supplied server name - if (server_name != "") { - _client = jack_client_open(client_name.c_str(), - 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(client_name.c_str(), 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; - return false; - } - } else { - _client = (jack_client_t*)jack_client; - } - - _local_client = (jack_client == NULL); - - _buffer_size = jack_get_buffer_size(_client) * sizeof(Sample); - _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); - - for (Raul::List::iterator i = _ports.begin(); i != _ports.end(); ++i) - (*i)->create(); - - return true; -} - - -void -JackAudioDriver::activate() -{ - if (_is_activated) { - cerr << "[JackAudioDriver] Jack driver already activated." << endl; - return; - } - - if (!_client) - attach(_engine.world()->conf->option("jack-server").get_string(), - _engine.world()->conf->option("jack-client").get_string(), NULL); - - 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; - } -} - - -void -JackAudioDriver::deactivate() -{ - if (_is_activated) { - _flag = 1; - _is_activated = false; - _sem.wait(); - - for (Raul::List::iterator i = _ports.begin(); i != _ports.end(); ++i) - (*i)->destroy(); - - jack_deactivate(_client); - - if (_local_client) { - jack_client_close(_client); - _client = NULL; - } - - _jack_thread->stop(); - cout << "[JackAudioDriver] Deactivated Jack client." << endl; - } -} - - -/** 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(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. - */ -Raul::List::Node* -JackAudioDriver::remove_port(const Path& path) -{ - assert(ThreadManager::current_thread_id() == THREAD_PROCESS); - - for (Raul::List::iterator i = _ports.begin(); i != _ports.end(); ++i) - if ((*i)->patch_port()->path() == path) - return (Raul::List::Node*)(_ports.erase(i)); - - cerr << "[JackAudioDriver::remove_port] WARNING: Unable to find port " << path << endl; - return NULL; -} - - -DriverPort* -JackAudioDriver::port(const Path& path) -{ - for (Raul::List::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::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) { - if (_flag == 1) - _sem.post(); - 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 / sizeof(Sample)); - - // Note that Jack can not call this function for a cycle, if overloaded - 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; - - _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); - - // Read input - for (Raul::List::iterator i = _ports.begin(); i != _ports.end(); ++i) - (*i)->pre_process(_process_context); - - // Run root patch - if (_root_patch) - _root_patch->process(_process_context); - - // Signal message context to run if necessary - if (_engine.message_context()->has_requests()) - _engine.message_context()->signal(_process_context); - - // Write output - for (Raul::List::iterator i = _ports.begin(); i != _ports.end(); ++i) - (*i)->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; - _is_activated = false; - delete _jack_thread; - _jack_thread = NULL; - _client = 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) { - _buffer_size = nframes * sizeof(Sample); - _root_patch->set_buffer_size(*_engine.buffer_factory(), _buffer_size); - } - return 0; -} - - -} // namespace Ingen diff --git a/src/engine/JackAudioDriver.hpp b/src/engine/JackAudioDriver.hpp deleted file mode 100644 index 5ff6bf64..00000000 --- a/src/engine/JackAudioDriver.hpp +++ /dev/null @@ -1,173 +0,0 @@ -/* This file is part of Ingen. - * Copyright (C) 2007-2009 Dave Robillard - * - * 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 - */ - -#ifndef JACKAUDIODRIVER_H -#define JACKAUDIODRIVER_H - -#include -#include -#include "raul/AtomicInt.hpp" -#include "raul/List.hpp" -#include "raul/Semaphore.hpp" -#include "raul/Thread.hpp" -#include "Driver.hpp" -#include "Buffer.hpp" -#include "ProcessContext.hpp" - -namespace Raul { class Path; } - -namespace Ingen { - -class Engine; -class PatchImpl; -class PortImpl; -class DuplexPort; -class JackAudioDriver; -typedef jack_default_audio_sample_t jack_sample_t; - - -/** Used internally by JackAudioDriver to represent a Jack port. - * - * A Jack port always has a one-to-one association with a Patch port. - */ -class JackAudioPort : public DriverPort, public Raul::List::Node -{ -public: - JackAudioPort(JackAudioDriver* driver, DuplexPort* patch_port); - ~JackAudioPort(); - - void create(); - void destroy(); - - void move(const Raul::Path& path); - - void pre_process(ProcessContext& context); - void post_process(ProcessContext& context); - - jack_port_t* jack_port() const { return _jack_port; } - -private: - JackAudioDriver* _driver; - jack_port_t* _jack_port; -}; - - - -/** The Jack Driver. - * - * The process callback here drives the entire audio thread by "pulling" - * events from queues, processing them, running the patches, and passing - * events along to the PostProcessor. - * - * \ingroup engine - */ -class JackAudioDriver : public Driver -{ -public: - JackAudioDriver(Engine& engine); - ~JackAudioDriver(); - - bool supports(Shared::PortType port_type, Shared::EventType event_type); - - bool attach(const std::string& server_name, - const std::string& client_name, - void* jack_client); - - void activate(); - void deactivate(); - void enable(); - void disable(); - - DriverPort* port(const Raul::Path& path); - DriverPort* create_port(DuplexPort* patch_port); - - void add_port(DriverPort* port); - DriverPort* driver_port(const Raul::Path& path); - - Raul::List::Node* remove_port(const Raul::Path& path); - - PatchImpl* root_patch() { return _root_patch; } - void set_root_patch(PatchImpl* patch) { _root_patch = patch; } - - ProcessContext& context() { return _process_context; } - - /** Transport state for this frame. - * Intended to only be called from the audio thread. */ - inline const jack_position_t* position() { return &_position; } - inline jack_transport_state_t transport_state() { return _transport_state; } - - bool is_realtime() const { return jack_is_realtime(_client); } - - jack_client_t* jack_client() const { return _client; } - SampleCount buffer_size() const { return _buffer_size; } - SampleCount sample_rate() const { return _sample_rate; } - bool is_activated() const { return _is_activated; } - - inline SampleCount frame_time() const { return _client ? jack_frame_time(_client) : 0; } - - class PortRegistrationFailedException : public std::exception {}; - -private: - friend class JackAudioPort; - - // Static JACK callbacks which call the non-static callbacks (methods) - inline static void thread_init_cb(void* const jack_driver) { - return ((JackAudioDriver*)jack_driver)->_thread_init_cb(); - } - inline static void shutdown_cb(void* const jack_driver) { - return ((JackAudioDriver*)jack_driver)->_shutdown_cb(); - } - inline static int process_cb(jack_nframes_t nframes, void* const jack_driver) { - return ((JackAudioDriver*)jack_driver)->_process_cb(nframes); - } - inline static int buffer_size_cb(jack_nframes_t nframes, void* const jack_driver) { - return ((JackAudioDriver*)jack_driver)->_buffer_size_cb(nframes); - } - inline static int sample_rate_cb(jack_nframes_t nframes, void* const jack_driver) { - return ((JackAudioDriver*)jack_driver)->_sample_rate_cb(nframes); - } - - // Non static callbacks (methods) - void _thread_init_cb(); - void _shutdown_cb(); - int _process_cb(jack_nframes_t nframes); - int _buffer_size_cb(jack_nframes_t nframes); - int _sample_rate_cb(jack_nframes_t nframes); - - Engine& _engine; - Raul::Thread* _jack_thread; - Raul::Semaphore _sem; - Raul::AtomicInt _flag; - jack_client_t* _client; - jack_nframes_t _buffer_size; - jack_nframes_t _sample_rate; - uint32_t _midi_event_type; - bool _is_activated; - bool _local_client; ///< Whether _client should be closed on destruction - jack_position_t _position; - jack_transport_state_t _transport_state; - - Raul::List _ports; - ProcessContext _process_context; - - PatchImpl* _root_patch; -}; - - -} // namespace Ingen - -#endif // JACKAUDIODRIVER_H diff --git a/src/engine/JackDriver.cpp b/src/engine/JackDriver.cpp new file mode 100644 index 00000000..68a7712b --- /dev/null +++ b/src/engine/JackDriver.cpp @@ -0,0 +1,498 @@ +/* This file is part of Ingen. + * Copyright (C) 2007-2009 Dave Robillard + * + * 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 +#include +#include +#include "raul/List.hpp" +#include "shared/LV2Features.hpp" +#include "shared/LV2URIMap.hpp" +#include "AudioBuffer.hpp" +#include "DuplexPort.hpp" +#include "Engine.hpp" +#include "Event.hpp" +#include "EventBuffer.hpp" +#include "EventSource.hpp" +#include "EventSource.hpp" +#include "JackDriver.hpp" +#include "MessageContext.hpp" +#include "PatchImpl.hpp" +#include "PortImpl.hpp" +#include "PostProcessor.hpp" +#include "ProcessSlave.hpp" +#include "QueuedEvent.hpp" +#include "ThreadManager.hpp" +#include "ingen-config.h" +#include "tuning.hpp" +#include "util.hpp" + +using namespace std; +using namespace Raul; + +namespace Ingen { + + +//// JackAudioPort //// + +JackAudioPort::JackAudioPort(JackAudioDriver* driver, DuplexPort* patch_port) + : DriverPort(patch_port) + , Raul::List::Node(this) + , _driver(driver) + , _jack_port(NULL) +{ + assert(patch_port->poly() == 1); + + create(); + + patch_port->buffer(0)->clear(); +} + + +JackAudioPort::~JackAudioPort() +{ + assert(_jack_port == NULL); +} + + +void +JackAudioPort::create() +{ + _jack_port = jack_port_register( + _driver->jack_client(), + ingen_jack_port_name(_patch_port->path()).c_str(), + (_patch_port->type() == PortType::AUDIO) + ? JACK_DEFAULT_AUDIO_TYPE : JACK_DEFAULT_MIDI_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(); + } +} + + +void +JackAudioPort::destroy() +{ + assert(_jack_port); + if (jack_port_unregister(_driver->jack_client(), _jack_port)) + cerr << "[JackMidiPort] ERROR: Unable to unregister port" << endl; + _jack_port = NULL; +} + + +void +JackAudioPort::move(const Raul::Path& path) +{ + jack_port_set_name(_jack_port, ingen_jack_port_name(path).c_str()); +} + + +void +JackAudioPort::pre_process(ProcessContext& context) +{ + if (!is_input()) + return; + + const SampleCount nframes = context.nframes(); + + if (_patch_port->type() == PortType::AUDIO) { + jack_sample_t* jack_buf = (jack_sample_t*)jack_port_get_buffer(_jack_port, nframes); + AudioBuffer* patch_buf = (AudioBuffer*)_patch_port->buffer(0).get(); + + patch_buf->copy(jack_buf, 0, nframes - 1); + + } else if (_patch_port->type() == PortType::EVENTS) { + void* jack_buf = jack_port_get_buffer(_jack_port, nframes); + EventBuffer* patch_buf = (EventBuffer*)_patch_port->buffer(0).get(); + + const jack_nframes_t event_count = jack_midi_get_event_count(jack_buf); + + patch_buf->prepare_write(context); + + // Copy events from Jack port buffer into patch port buffer + for (jack_nframes_t i = 0; i < event_count; ++i) { + jack_midi_event_t ev; + jack_midi_event_get(&ev, jack_buf, i); + + if (!patch_buf->append(ev.time, 0, _driver->_midi_event_type, ev.size, ev.buffer)) + cerr << "WARNING: Failed to write MIDI to port buffer, event(s) lost!" << endl; + } + } +} + + +void +JackAudioPort::post_process(ProcessContext& context) +{ + if (is_input()) + return; + + const SampleCount nframes = context.nframes(); + + if (_patch_port->type() == PortType::AUDIO) { + jack_sample_t* jack_buf = (jack_sample_t*)jack_port_get_buffer(_jack_port, nframes); + AudioBuffer* patch_buf = (AudioBuffer*)_patch_port->buffer(0).get(); + + memcpy(jack_buf, patch_buf->data(), nframes * sizeof(Sample)); + + } else if (_patch_port->type() == PortType::EVENTS) { + void* jack_buf = jack_port_get_buffer(_jack_port, context.nframes()); + EventBuffer* patch_buf = (EventBuffer*)_patch_port->buffer(0).get(); + + patch_buf->prepare_read(context); + jack_midi_clear_buffer(jack_buf); + + uint32_t frames = 0; + uint32_t subframes = 0; + uint16_t type = 0; + uint16_t size = 0; + uint8_t* data = NULL; + + // Copy events from Jack port buffer into patch port buffer + for (patch_buf->rewind(); patch_buf->is_valid(); patch_buf->increment()) { + patch_buf->get_event(&frames, &subframes, &type, &size, &data); + jack_midi_event_write(jack_buf, frames, data, size); + } + } +} + + +//// JackAudioDriver //// + +JackAudioDriver::JackAudioDriver(Engine& engine) + : _engine(engine) + , _jack_thread(NULL) + , _sem(0) + , _flag(0) + , _client(NULL) + , _buffer_size(0) + , _sample_rate(0) + , _is_activated(false) + , _local_client(true) + , _process_context(engine) + , _root_patch(NULL) +{ + SharedPtr map = PtrCast( + _engine.world()->lv2_features->feature(LV2_URI_MAP_URI)); + _midi_event_type = map->uri_to_id(NULL, "http://lv2plug.in/ns/ext/midi#MidiEvent"); +} + + +JackAudioDriver::~JackAudioDriver() +{ + deactivate(); + + if (_local_client) + jack_client_close(_client); +} + + +bool +JackAudioDriver::supports(Shared::PortType port_type, Shared::EventType event_type) +{ + return (port_type == PortType::AUDIO + || (port_type == PortType::EVENTS && event_type == EventType::MIDI)); +} + + +bool +JackAudioDriver::attach(const std::string& server_name, + const std::string& client_name, + void* jack_client) +{ + assert(!_client); + if (!jack_client) { + // Try supplied server name + if (server_name != "") { + _client = jack_client_open(client_name.c_str(), + 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(client_name.c_str(), 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; + return false; + } + } else { + _client = (jack_client_t*)jack_client; + } + + _local_client = (jack_client == NULL); + + _buffer_size = jack_get_buffer_size(_client) * sizeof(Sample); + _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); + + for (Raul::List::iterator i = _ports.begin(); i != _ports.end(); ++i) + (*i)->create(); + + return true; +} + + +void +JackAudioDriver::activate() +{ + if (_is_activated) { + cerr << "[JackAudioDriver] Jack driver already activated." << endl; + return; + } + + if (!_client) + attach(_engine.world()->conf->option("jack-server").get_string(), + _engine.world()->conf->option("jack-client").get_string(), NULL); + + 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; + } +} + + +void +JackAudioDriver::deactivate() +{ + if (_is_activated) { + _flag = 1; + _is_activated = false; + _sem.wait(); + + for (Raul::List::iterator i = _ports.begin(); i != _ports.end(); ++i) + (*i)->destroy(); + + jack_deactivate(_client); + + if (_local_client) { + jack_client_close(_client); + _client = NULL; + } + + _jack_thread->stop(); + cout << "[JackAudioDriver] Deactivated Jack client." << endl; + } +} + + +/** 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(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. + */ +Raul::List::Node* +JackAudioDriver::remove_port(const Path& path) +{ + assert(ThreadManager::current_thread_id() == THREAD_PROCESS); + + for (Raul::List::iterator i = _ports.begin(); i != _ports.end(); ++i) + if ((*i)->patch_port()->path() == path) + return (Raul::List::Node*)(_ports.erase(i)); + + cerr << "[JackAudioDriver::remove_port] WARNING: Unable to find port " << path << endl; + return NULL; +} + + +DriverPort* +JackAudioDriver::port(const Path& path) +{ + for (Raul::List::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::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) { + if (_flag == 1) + _sem.post(); + 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 / sizeof(Sample)); + + // Note that Jack can not call this function for a cycle, if overloaded + 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; + + _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); + + // Read input + for (Raul::List::iterator i = _ports.begin(); i != _ports.end(); ++i) + (*i)->pre_process(_process_context); + + // Run root patch + if (_root_patch) + _root_patch->process(_process_context); + + // Signal message context to run if necessary + if (_engine.message_context()->has_requests()) + _engine.message_context()->signal(_process_context); + + // Write output + for (Raul::List::iterator i = _ports.begin(); i != _ports.end(); ++i) + (*i)->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; + _is_activated = false; + delete _jack_thread; + _jack_thread = NULL; + _client = 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) { + _buffer_size = nframes * sizeof(Sample); + _root_patch->set_buffer_size(*_engine.buffer_factory(), _buffer_size); + } + return 0; +} + + +} // namespace Ingen diff --git a/src/engine/JackDriver.hpp b/src/engine/JackDriver.hpp new file mode 100644 index 00000000..5ff6bf64 --- /dev/null +++ b/src/engine/JackDriver.hpp @@ -0,0 +1,173 @@ +/* This file is part of Ingen. + * Copyright (C) 2007-2009 Dave Robillard + * + * 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 + */ + +#ifndef JACKAUDIODRIVER_H +#define JACKAUDIODRIVER_H + +#include +#include +#include "raul/AtomicInt.hpp" +#include "raul/List.hpp" +#include "raul/Semaphore.hpp" +#include "raul/Thread.hpp" +#include "Driver.hpp" +#include "Buffer.hpp" +#include "ProcessContext.hpp" + +namespace Raul { class Path; } + +namespace Ingen { + +class Engine; +class PatchImpl; +class PortImpl; +class DuplexPort; +class JackAudioDriver; +typedef jack_default_audio_sample_t jack_sample_t; + + +/** Used internally by JackAudioDriver to represent a Jack port. + * + * A Jack port always has a one-to-one association with a Patch port. + */ +class JackAudioPort : public DriverPort, public Raul::List::Node +{ +public: + JackAudioPort(JackAudioDriver* driver, DuplexPort* patch_port); + ~JackAudioPort(); + + void create(); + void destroy(); + + void move(const Raul::Path& path); + + void pre_process(ProcessContext& context); + void post_process(ProcessContext& context); + + jack_port_t* jack_port() const { return _jack_port; } + +private: + JackAudioDriver* _driver; + jack_port_t* _jack_port; +}; + + + +/** The Jack Driver. + * + * The process callback here drives the entire audio thread by "pulling" + * events from queues, processing them, running the patches, and passing + * events along to the PostProcessor. + * + * \ingroup engine + */ +class JackAudioDriver : public Driver +{ +public: + JackAudioDriver(Engine& engine); + ~JackAudioDriver(); + + bool supports(Shared::PortType port_type, Shared::EventType event_type); + + bool attach(const std::string& server_name, + const std::string& client_name, + void* jack_client); + + void activate(); + void deactivate(); + void enable(); + void disable(); + + DriverPort* port(const Raul::Path& path); + DriverPort* create_port(DuplexPort* patch_port); + + void add_port(DriverPort* port); + DriverPort* driver_port(const Raul::Path& path); + + Raul::List::Node* remove_port(const Raul::Path& path); + + PatchImpl* root_patch() { return _root_patch; } + void set_root_patch(PatchImpl* patch) { _root_patch = patch; } + + ProcessContext& context() { return _process_context; } + + /** Transport state for this frame. + * Intended to only be called from the audio thread. */ + inline const jack_position_t* position() { return &_position; } + inline jack_transport_state_t transport_state() { return _transport_state; } + + bool is_realtime() const { return jack_is_realtime(_client); } + + jack_client_t* jack_client() const { return _client; } + SampleCount buffer_size() const { return _buffer_size; } + SampleCount sample_rate() const { return _sample_rate; } + bool is_activated() const { return _is_activated; } + + inline SampleCount frame_time() const { return _client ? jack_frame_time(_client) : 0; } + + class PortRegistrationFailedException : public std::exception {}; + +private: + friend class JackAudioPort; + + // Static JACK callbacks which call the non-static callbacks (methods) + inline static void thread_init_cb(void* const jack_driver) { + return ((JackAudioDriver*)jack_driver)->_thread_init_cb(); + } + inline static void shutdown_cb(void* const jack_driver) { + return ((JackAudioDriver*)jack_driver)->_shutdown_cb(); + } + inline static int process_cb(jack_nframes_t nframes, void* const jack_driver) { + return ((JackAudioDriver*)jack_driver)->_process_cb(nframes); + } + inline static int buffer_size_cb(jack_nframes_t nframes, void* const jack_driver) { + return ((JackAudioDriver*)jack_driver)->_buffer_size_cb(nframes); + } + inline static int sample_rate_cb(jack_nframes_t nframes, void* const jack_driver) { + return ((JackAudioDriver*)jack_driver)->_sample_rate_cb(nframes); + } + + // Non static callbacks (methods) + void _thread_init_cb(); + void _shutdown_cb(); + int _process_cb(jack_nframes_t nframes); + int _buffer_size_cb(jack_nframes_t nframes); + int _sample_rate_cb(jack_nframes_t nframes); + + Engine& _engine; + Raul::Thread* _jack_thread; + Raul::Semaphore _sem; + Raul::AtomicInt _flag; + jack_client_t* _client; + jack_nframes_t _buffer_size; + jack_nframes_t _sample_rate; + uint32_t _midi_event_type; + bool _is_activated; + bool _local_client; ///< Whether _client should be closed on destruction + jack_position_t _position; + jack_transport_state_t _transport_state; + + Raul::List _ports; + ProcessContext _process_context; + + PatchImpl* _root_patch; +}; + + +} // namespace Ingen + +#endif // JACKAUDIODRIVER_H diff --git a/src/engine/ingen_jack.cpp b/src/engine/ingen_jack.cpp index d9020ec8..4bc88281 100644 --- a/src/engine/ingen_jack.cpp +++ b/src/engine/ingen_jack.cpp @@ -18,7 +18,7 @@ #include #include "module/Module.hpp" #include "module/World.hpp" -#include "JackAudioDriver.hpp" +#include "JackDriver.hpp" #include "Engine.hpp" using namespace std; diff --git a/src/engine/wscript b/src/engine/wscript index 752fd316..6e146d3c 100644 --- a/src/engine/wscript +++ b/src/engine/wscript @@ -109,7 +109,7 @@ def build(bld): if bld.env['HAVE_JACK'] == 1: obj = bld.new_task_gen('cxx', 'shlib') - obj.source = 'JackAudioDriver.cpp ingen_jack.cpp' + obj.source = 'JackDriver.cpp ingen_jack.cpp' obj.export_incdirs = ['.'] obj.includes = ['.', '..', '../..', '../common', '../engine'] obj.name = 'libingen_jack' -- cgit v1.2.1