diff options
author | David Robillard <d@drobilla.net> | 2010-01-05 21:55:24 +0000 |
---|---|---|
committer | David Robillard <d@drobilla.net> | 2010-01-05 21:55:24 +0000 |
commit | 566479a3ac4c8c6c9d190d6b4e8ecd104402180c (patch) | |
tree | 345297b440e7ba20f03d2cca70810c0e653f665c /src/engine/JackDriver.cpp | |
parent | 534b3d2248884d5a8352c2239c668e632ab16b41 (diff) | |
download | ingen-566479a3ac4c8c6c9d190d6b4e8ecd104402180c.tar.gz ingen-566479a3ac4c8c6c9d190d6b4e8ecd104402180c.tar.bz2 ingen-566479a3ac4c8c6c9d190d6b4e8ecd104402180c.zip |
JackAudioDriver.[ch]pp -> JackDriver.[ch]pp
git-svn-id: http://svn.drobilla.net/lad/trunk/ingen@2344 a436a847-0d15-0410-975c-d299462d15a1
Diffstat (limited to 'src/engine/JackDriver.cpp')
-rw-r--r-- | src/engine/JackDriver.cpp | 498 |
1 files changed, 498 insertions, 0 deletions
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 <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 <iostream> +#include <cstdlib> +#include <jack/midiport.h> +#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<JackAudioPort*>::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<Shared::LV2URIMap> map = PtrCast<Shared::LV2URIMap>( + _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<JackAudioPort*>::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<JackAudioPort*>::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<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. + */ +Raul::List<DriverPort*>::Node* +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 (Raul::List<DriverPort*>::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<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) { + 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<JackAudioPort*>::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<JackAudioPort*>::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 |