skeleton framework for LTC-slave

git-svn-id: svn://localhost/ardour2/branches/3.0@13256 d708f5d6-7413-0410-9779-e7cbd77b26cf
This commit is contained in:
Robin Gareus 2012-10-12 09:45:22 +00:00
parent 8f55ca6a50
commit bce3184ff5
12 changed files with 265 additions and 1 deletions

View File

@ -1093,6 +1093,9 @@ AudioClock::set_timecode (framepos_t when, bool /*force*/)
case MIDIClock:
_left_layout->set_text ("M-Clock");
break;
case LTC:
_left_layout->set_text ("LTC");
break;
}
} else {
_left_layout->set_text ("INT");

View File

@ -445,6 +445,7 @@ PortGroupList::gather (ARDOUR::Session* session, ARDOUR::DataType type, bool inp
boost::shared_ptr<Bundle> sync (new Bundle (_("Sync"), inputs));
MIDI::MachineControl* mmc = midi_manager->mmc ();
AudioEngine& ae = session->engine ();
if (inputs) {
sync->add_channel (
_("MTC in"), DataType::MIDI, ae.make_port_name_non_relative (midi_manager->mtc_input_port()->name())
@ -458,6 +459,11 @@ PortGroupList::gather (ARDOUR::Session* session, ARDOUR::DataType type, bool inp
sync->add_channel (
_("MMC in"), DataType::MIDI, ae.make_port_name_non_relative (mmc->input_port()->name())
);
#ifdef HAVE_LIBLTC
sync->add_channel (
_("LTC in"), DataType::AUDIO, ae.make_port_name_non_relative (ae->ltc_input_port()->name())
);
#endif
} else {
sync->add_channel (
_("MTC out"), DataType::MIDI, ae.make_port_name_non_relative (midi_manager->mtc_output_port()->name())

View File

@ -260,6 +260,10 @@ _ the regular process() call to session->process() is not made.
int create_process_thread (boost::function<void()>, pthread_t*, size_t stacksize);
#ifdef HAVE_LTC
Port *ltc_input_port() const { return _ltc_input; }
#endif
private:
static AudioEngine* _instance;
@ -289,6 +293,10 @@ private:
Glib::Threads::Thread* m_meter_thread;
ProcessThread* _main_thread;
#ifdef HAVE_LTC
Port* _ltc_input;
#endif
SerializedRCUManager<Ports> ports;
boost::shared_ptr<Port> register_port (DataType type, const std::string& portname, bool input);

View File

@ -32,6 +32,10 @@
#include "midi++/parser.h"
#include "midi++/types.h"
#ifdef HAVE_LTC
#include <ltc.h>
#endif
namespace MIDI {
class Port;
}
@ -293,6 +297,39 @@ class MTC_Slave : public Slave {
void init_engine_dll (framepos_t, framepos_t);
};
#ifdef HAVE_LTC
class LTC_Slave : public Slave {
public:
LTC_Slave (Session&);
~LTC_Slave ();
bool speed_and_position (double&, framepos_t&);
bool locked() const;
bool ok() const;
framecnt_t resolution () const;
bool requires_seekahead () const { return true; }
framecnt_t seekahead_distance() const;
bool give_slave_full_control_over_transport_speed() const;
private:
int parse_ltc(const jack_nframes_t nframes, const jack_default_audio_sample_t * const in, const framecnt_t posinfo);
void process_ltc();
Session& session;
bool did_reset_tc_format;
Timecode::TimecodeFormat saved_tc_format;
LTCDecoder *decoder;
framecnt_t current_frames_per_ltc_frame;
framecnt_t monotonic_fcnt;
framepos_t ltc_transport_pos;
double ltc_speed;
};
#endif
class MIDIClock_Slave : public Slave {
public:
MIDIClock_Slave (Session&, MIDI::Port&, int ppqn = 24);

View File

@ -462,7 +462,8 @@ namespace ARDOUR {
enum SyncSource {
JACK,
MTC,
MIDIClock
MIDIClock,
LTC
};
enum ShuttleBehaviour {

View File

@ -89,6 +89,9 @@ AudioEngine::AudioEngine (string client_name, string session_uuid)
}
Port::set_engine (this);
#ifdef HAVE_LTC
_ltc_input = new AudioPort ("LTC in", Port::IsInput);
#endif
}
AudioEngine::~AudioEngine ()

View File

@ -307,6 +307,7 @@ setup_enum_writer ()
REGISTER_ENUM (MTC);
REGISTER_ENUM (JACK);
REGISTER_ENUM (MIDIClock);
REGISTER_ENUM (LTC);
REGISTER (_SyncSource);
REGISTER_ENUM (Sprung);

View File

@ -485,6 +485,9 @@ ARDOUR::get_available_sync_options ()
ret.push_back (JACK);
ret.push_back (MTC);
ret.push_back (MIDIClock);
#ifdef HAVE_LTC
ret.push_back (LTC);
#endif
return ret;
}

175
libs/ardour/ltc_slave.cc Normal file
View File

@ -0,0 +1,175 @@
/*
Copyright (C) 2012 Paul Davis
Witten by 2012 Robin Gareus <robin@gareus.org>
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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <iostream>
#include <errno.h>
#include <poll.h>
#include <sys/types.h>
#include <unistd.h>
#include "pbd/error.h"
#include "ardour/debug.h"
#include "ardour/slave.h"
#include "ardour/session.h"
#include "ardour/audioengine.h"
#include "ardour/audio_port.h"
#include "i18n.h"
using namespace std;
using namespace ARDOUR;
using namespace MIDI;
using namespace PBD;
using namespace Timecode;
LTC_Slave::LTC_Slave (Session& s)
: session (s)
{
current_frames_per_ltc_frame = 1920; // samplerate / framerate
ltc_transport_pos = 0;
ltc_speed = 1.0;
monotonic_fcnt = 0;
decoder = ltc_decoder_create(current_frames_per_ltc_frame, 128 /*queue size*/);
}
LTC_Slave::~LTC_Slave()
{
if (did_reset_tc_format) {
session.config.set_timecode_format (saved_tc_format);
}
ltc_decoder_free(decoder);
}
bool
LTC_Slave::give_slave_full_control_over_transport_speed() const
{
return true; // DLL align to engine transport
// return false; // for Session-level computed varispeed
}
ARDOUR::framecnt_t
LTC_Slave::resolution () const
{
return current_frames_per_ltc_frame;
}
ARDOUR::framecnt_t
LTC_Slave::seekahead_distance () const
{
return current_frames_per_ltc_frame * 2;
}
bool
LTC_Slave::locked () const
{
return true;
}
bool
LTC_Slave::ok() const
{
return true;
}
int
LTC_Slave::parse_ltc(const jack_nframes_t nframes, const jack_default_audio_sample_t * const in, const framecnt_t posinfo)
{
jack_nframes_t i;
unsigned char sound[8192];
if (nframes > 8192) return 1;
for (i = 0; i < nframes; i++) {
const int snd=(int)rint((127.0*in[i])+128.0);
sound[i] = (unsigned char) (snd&0xff);
}
ltc_decoder_write(decoder, sound, nframes, posinfo);
return 0;
}
void
LTC_Slave::process_ltc()
{
LTCFrameExt frame;
while (ltc_decoder_read(decoder,&frame)) {
SMPTETimecode stime;
ltc_frame_to_time(&stime, &frame.ltc, 0);
fprintf(stdout, "%02d:%02d:%02d%c%02d | %8lld %8lld%s\n",
stime.hours,
stime.mins,
stime.secs,
(frame.ltc.dfbit) ? '.' : ':',
stime.frame,
frame.off_start,
frame.off_end,
frame.reverse ? " R" : " "
);
/* when a full LTC frame is decoded, the timecode the LTC frame
* is referring has just passed.
* So we send the _next_ timecode which
* is expected to start at the end of the current frame
*/
int detected_fps = 25; // XXX
if (!frame.reverse) {
ltc_frame_increment(&frame.ltc, detected_fps , 0);
ltc_frame_to_time(&stime, &frame.ltc, 0);
} else {
ltc_frame_decrement(&frame.ltc, detected_fps , 0);
int off = frame.off_end - frame.off_start;
frame.off_start += off;
frame.off_end += off;
}
}
}
/* main entry point from session_process.cc
* called from jack_process callback context
*/
bool
LTC_Slave::speed_and_position (double& speed, framepos_t& pos)
{
DEBUG_TRACE (DEBUG::MTC, string_compose ("LTC_Slave::speed_and_position - TID:%1\n", ::pthread_self()));
framepos_t now = session.engine().frame_time_at_cycle_start();
framepos_t sess_pos = session.transport_frame(); // corresponds to now
framecnt_t nframes = session.engine().frames_per_cycle();
jack_port_t *ltc_port = session.engine().ltc_input_port()->jack_port();
jack_default_audio_sample_t *in;
in = (jack_default_audio_sample_t*) jack_port_get_buffer (ltc_port, nframes);
//in = session.engine().ltc_input_port()->engine_get_whole_audio_buffer()
// TODO: get capture latency for ltc_port.
if (in) {
parse_ltc(nframes, in, monotonic_fcnt /* + jltc_latency*/ );
process_ltc();
}
/* fake for testing */
ltc_transport_pos += nframes * ltc_speed;
pos = ltc_transport_pos;
speed = ltc_speed;
monotonic_fcnt += nframes;
}

View File

@ -1372,6 +1372,24 @@ Session::switch_to_sync_source (SyncSource src)
}
break;
case LTC:
#ifdef HAVE_LTC
if (_slave && dynamic_cast<LTC_Slave*>(_slave)) {
return;
}
try {
new_slave = new LTC_Slave (*this);
}
catch (failed_constructor& err) {
return;
}
#else
return;
#endif
break;
case MIDIClock:
if (_slave && dynamic_cast<MIDIClock_Slave*>(_slave)) {
return;

View File

@ -452,6 +452,9 @@ sync_source_to_string (SyncSource src, bool sh)
case MIDIClock:
return _("MIDI Clock");
case LTC:
return _("LTC");
}
/* GRRRR .... stupid, stupid gcc - you can't get here from there, all enum values are handled */
return _("JACK");

View File

@ -281,6 +281,8 @@ def configure(conf):
atleast_version='1.2.1')
autowaf.check_pkg(conf, 'libcurl', uselib_store='CURL',
atleast_version='7.0.0')
autowaf.check_pkg(conf, 'ltc', uselib_store='LTC',
atleast_version='0.3.2', mandatory=False)
# we don't try to detect this, since its part of our source tree
@ -381,6 +383,10 @@ def build(bld):
#obj.uselib += ' SOUNDTOUCH '
#obj.add_objects = 'default/libs/surfaces/control_protocol/smpte_1.o'
if bld.is_defined('HAVE_LTC') :
obj.source += ['ltc_slave.cc']
obj.uselib += ['LTC']
if bld.is_defined('HAVE_LILV') :
obj.source += ['lv2_plugin.cc', 'lv2_evbuf.c', 'uri_map.cc']
obj.uselib += ['LILV']