Permalink
Fetching contributors…
Cannot retrieve contributors at this time
501 lines (397 sloc) 10.9 KB
/*
** Copyright (C) 2004 Jesse Chappell <jesse@essej.net>
**
** This program 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.
**
** This program 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 more 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 <string>
#include <iostream>
#include <jack/jack.h>
#include "jack_audio_driver.hpp"
#include "engine.hpp"
using namespace SooperLooper;
using namespace PBD;
using namespace std;
JackAudioDriver::JackAudioDriver(string client_name, string serv_name)
: AudioDriver(client_name, serv_name)
{
_timebase_master = false;
}
JackAudioDriver::~JackAudioDriver()
{
if (_jack) {
jack_deactivate (_jack);
jack_client_close (_jack);
}
}
bool
JackAudioDriver::initialize(string client_name)
{
if (!client_name.empty()) {
_client_name = client_name;
}
if (connect_to_jack ()) {
cerr << "cannot connect to jack" << endl;
return false;
}
_samplerate = jack_get_sample_rate (_jack);
_buffersize = jack_get_buffer_size (_jack);
if (jack_set_process_callback (_jack, _process_callback, this) != 0) {
cerr << "cannot set process callback" << endl;
return false;
}
if (jack_set_buffer_size_callback (_jack, _buffersize_callback, this) != 0) {
cerr << "cannot set buffersize callback" << endl;
}
return true;
}
bool
JackAudioDriver::activate()
{
if (_jack) {
if (jack_activate (_jack)) {
cerr << "cannot activate JACK" << endl;
return false;
}
return true;
}
return false;
}
bool
JackAudioDriver::deactivate()
{
if (_jack) {
if (jack_deactivate (_jack)) {
cerr << "cannot deactivate JACK" << endl;
return false;
}
return true;
}
return false;
}
static void
our_jack_error(const char * err)
{
// cerr << "Jack error: " << err << endl;
}
int
JackAudioDriver::connect_to_jack ()
{
jack_set_error_function (our_jack_error);
_jack = 0;
#ifdef HAVE_JACK_CLIENT_OPEN
jack_options_t options = JackNullOption;
jack_status_t status;
const char *server_name = NULL;
// support for server name
if (!_server_name.empty()) {
server_name = _server_name.c_str();
}
if (_client_name.empty()) {
_client_name = "sooperlooper";
}
_jack = jack_client_open (_client_name.c_str(), options, &status, server_name);
if (!_jack) {
return -1;
}
if (status & JackServerStarted) {
cerr << "JACK server started" << endl;
}
if (status & JackNameNotUnique) {
_client_name = jack_get_client_name (_jack);
}
#else
char namebuf[100];
/* try to become a client of the JACK server */
if (_client_name.empty()) {
// find a name predictably
for (int i=1; i < 10; i++) {
snprintf(namebuf, sizeof(namebuf)-1, "sooperlooper_%d", i);
if ((_jack = jack_client_new (namebuf)) != 0) {
_client_name = namebuf;
break;
}
}
}
else {
// try the passed name, or base a predictable name from it
if ((_jack = jack_client_new (_client_name.c_str())) == 0) {
for (int i=1; i < 10; i++) {
snprintf(namebuf, sizeof(namebuf)-1, "%s_%d", _client_name.c_str(), i);
if ((_jack = jack_client_new (namebuf)) != 0) {
_client_name = namebuf;
break;
}
}
}
}
if (!_jack) {
return -1;
}
#endif
jack_set_xrun_callback (_jack, _xrun_callback, this);
jack_on_shutdown (_jack, _shutdown_callback, this);
jack_set_graph_order_callback (_jack, _conn_changed_callback, this);
set_timebase_master(_timebase_master);
return 0;
}
int
JackAudioDriver::_xrun_callback (void* arg)
{
cerr << "got xrun" << endl;
return 0;
}
void
JackAudioDriver::_shutdown_callback (void* arg)
{
cerr << "jack shut us down" << endl;
return;
}
int
JackAudioDriver::_buffersize_callback (jack_nframes_t nframes, void* arg)
{
return static_cast<JackAudioDriver*> (arg)->buffersize_callback (nframes);
}
int
JackAudioDriver::buffersize_callback (jack_nframes_t nframes)
{
if (_engine) {
_buffersize = nframes;
_engine->buffersize_changed (nframes);
}
return 0;
}
int
JackAudioDriver::_process_callback (jack_nframes_t nframes, void* arg)
{
return static_cast<JackAudioDriver*> (arg)->process_callback (nframes);
}
int
JackAudioDriver::process_callback (jack_nframes_t nframes)
{
if (_engine) {
_engine->process (nframes);
}
return 0;
}
int JackAudioDriver::_conn_changed_callback (void* arg)
{
return static_cast<JackAudioDriver*> (arg)->conn_changed_callback ();
}
int JackAudioDriver::conn_changed_callback ()
{
ConnectionsChanged(); // emit
return 0;
}
bool
JackAudioDriver::create_input_port (std::string name, port_id_t & portid)
{
if (!_jack) return false;
jack_port_t * port;
if ((port = jack_port_register (_jack, name.c_str(), JACK_DEFAULT_AUDIO_TYPE,
JackPortIsInput, 0)) == 0) {
cerr << "JackAudioDriver: cannot register input port" << endl;
return false;
}
{
//LockMonitor mon(_port_lock, __LINE__, __FILE__);
_input_ports.push_back (port);
portid = _input_ports.size();
}
return true;
}
bool
JackAudioDriver::create_output_port (std::string name, port_id_t & portid)
{
if (!_jack) return false;
jack_port_t * port;
if ((port = jack_port_register (_jack, name.c_str(), JACK_DEFAULT_AUDIO_TYPE,
JackPortIsOutput, 0)) == 0) {
cerr << "JackAudioDriver: cannot register input port" << endl;
return false;
}
{
//LockMonitor mon(_port_lock, __LINE__, __FILE__);
_output_ports.push_back (port);
portid = _output_ports.size();
}
return true;
}
bool
JackAudioDriver::destroy_output_port (port_id_t portid)
{
jack_port_t * port = 0;
//LockMonitor mon(_port_lock, __LINE__, __FILE__);
if (portid <= _output_ports.size() && portid > 0) {
port = _output_ports[portid-1];
_output_ports[portid-1] = 0;
return (jack_port_unregister (_jack, port) == 0);
}
return false;
}
bool
JackAudioDriver::destroy_input_port (port_id_t portid)
{
jack_port_t * port = 0;
//LockMonitor mon(_port_lock, __LINE__, __FILE__);
if (portid <= _input_ports.size() && portid > 0) {
port = _input_ports[portid-1];
_input_ports[portid-1] = 0;
return (jack_port_unregister (_jack, port) == 0);
}
return false;
}
sample_t *
JackAudioDriver::get_input_port_buffer (port_id_t port, nframes_t nframes)
{
// not locked
if (!_jack || port > _input_ports.size() || port == 0) return 0;
return (sample_t*) jack_port_get_buffer (_input_ports[port-1], nframes);
}
sample_t *
JackAudioDriver::get_output_port_buffer (port_id_t port, nframes_t nframes)
{
// not locked
if (!_jack || port > _output_ports.size() || port == 0) return 0;
return (sample_t*) jack_port_get_buffer (_output_ports[port-1], nframes);
}
nframes_t
JackAudioDriver::get_input_port_latency (port_id_t port)
{
if (!_jack || port > _input_ports.size() || port == 0) return 0;
return jack_port_get_total_latency (_jack, _input_ports[port-1]);
}
nframes_t
JackAudioDriver::get_output_port_latency (port_id_t port)
{
if (!_jack || port > _output_ports.size() || port == 0) return 0;
return jack_port_get_total_latency (_jack, _output_ports[port-1]);
}
void
JackAudioDriver::set_transport_info (const TransportInfo &info)
{
_transport_info = info;
}
bool
JackAudioDriver::set_timebase_master(bool flag)
{
bool ret = true;
if (_jack) {
if (flag) {
ret = (jack_set_timebase_callback (_jack, 0, _timebase_callback, this) == 0);
if (!ret) {
_timebase_master = false;
}
//cerr << "sett jack timebase to " << _timebase_master << " ret is: " << ret << endl;
}
else if (_timebase_master) {
ret = (jack_release_timebase(_jack) == 0);
_timebase_master = false;
}
}
if (ret) {
_timebase_master = flag;
}
return ret;
}
bool
JackAudioDriver::get_transport_info (TransportInfo &info)
{
if (!_jack) return false;
jack_position_t tpos;
jack_transport_state_t tstate;
tstate = jack_transport_query( _jack, &tpos);
switch ( tstate ) {
case JackTransportStopped:
info.state = TransportInfo::STOPPED;
break;
case JackTransportRolling:
info.state = TransportInfo::ROLLING;
break;
case JackTransportStarting:
info.state = TransportInfo::STOPPED;
break;
default:
break;
//errorLog( "[updateTransportInfo] Unknown jack transport state" );
}
if ( tpos.valid & JackPositionBBT ) {
info.bpm = tpos.beats_per_minute;
}
info.last_framepos = info.framepos;
info.framepos = tpos.frame;
return true;
}
void JackAudioDriver::_timebase_callback(jack_transport_state_t state,
jack_nframes_t nframes,
jack_position_t *pos,
int new_pos,
void *arg)
{
static_cast<JackAudioDriver*> (arg)->timebase_callback (state, nframes, pos, new_pos);
}
void JackAudioDriver::timebase_callback(jack_transport_state_t state,
jack_nframes_t nframes,
jack_position_t *pos,
int new_pos)
{
// FIXME!
static const double time_ticks_per_beat = 1920.0;
pos->beats_per_minute = _transport_info.bpm;
//cerr << "timebase callback bpm: " << _transport_info.bpm << endl;
pos->beats_per_bar = _transport_info.beats_per_bar > 0 ? _transport_info.beats_per_bar : 4.0;
pos->beat_type = _transport_info.beat_type;
pos->ticks_per_beat = time_ticks_per_beat;
// we got nothin' special for the other stuff yet, so use example
if (new_pos || !(pos->valid & JackPositionBBT)) {
/* Compute BBT info from frame number. This is relatively
* simple here, but would become complex if we supported tempo
* or time signature changes at specific locations in the
* transport timeline.
*/
double minute = pos->frame / ((double) pos->frame_rate * 60.0);
long abs_tick = (long) (minute * pos->beats_per_minute * pos->ticks_per_beat);
long abs_beat = (long) (abs_tick / pos->ticks_per_beat);
pos->bar = (int) (abs_beat / pos->beats_per_bar);
pos->beat = (int) (abs_beat - (pos->bar * pos->beats_per_bar) + 1);
pos->tick = (int) (abs_tick - (abs_beat * pos->ticks_per_beat));
pos->bar_start_tick = (int) (pos->bar * pos->beats_per_bar *
pos->ticks_per_beat);
pos->bar++; /* adjust start to bar 1 */
} else {
/* Compute BBT info based on previous period. */
pos->tick += (int) (
nframes * pos->ticks_per_beat * pos->beats_per_minute
/ (pos->frame_rate * 60));
while (pos->tick >= pos->ticks_per_beat) {
pos->tick -= (int) pos->ticks_per_beat;
if (++pos->beat > pos->beats_per_bar) {
pos->beat = 1;
++pos->bar;
pos->bar_start_tick +=
pos->beats_per_bar
* pos->ticks_per_beat;
}
}
}
pos->valid = JackPositionBBT;
}
void JackAudioDriver::reposition_transport(nframes_t framepos)
{
if (_jack) {
// this will take effect in two cycles, so lets add two cycles to the time ? (cheating, wrong?)
jack_transport_locate(_jack, framepos + 2*_buffersize);
}
}