diff --git a/gtk2_ardour/audio_clock.cc b/gtk2_ardour/audio_clock.cc index 4fa30186a7..9a9fc6d56e 100644 --- a/gtk2_ardour/audio_clock.cc +++ b/gtk2_ardour/audio_clock.cc @@ -915,7 +915,7 @@ AudioClock::end_edit_relative (bool add) } else { framepos_t c = current_time(); - if (c > frames) { + if (c > frames || _negative_allowed) { set (c - frames, true); } else { set (0, true); @@ -932,6 +932,11 @@ AudioClock::end_edit_relative (bool add) void AudioClock::session_configuration_changed (std::string p) { + if (_negative_allowed) { + /* session option editor clock */ + return; + } + if (p == "sync-source" || p == "external-sync") { set (current_time(), true); return; @@ -1897,10 +1902,23 @@ bool AudioClock::timecode_validate_edit (const string&) { Timecode::Time TC; + int hours; char ignored[2]; if (sscanf (_layout->get_text().c_str(), "%" PRId32 ":%" PRId32 ":%" PRId32 "%[:;]%" PRId32, - &TC.hours, &TC.minutes, &TC.seconds, ignored, &TC.frames) != 5) { + &hours, &TC.minutes, &TC.seconds, ignored, &TC.frames) != 5) { + return false; + } + + if (hours < 0) { + TC.hours = hours * -1; + TC.negative = true; + } else { + TC.hours = hours; + TC.negative = false; + } + + if (TC.negative && !_negative_allowed) { return false; } @@ -1947,19 +1965,22 @@ AudioClock::frames_from_timecode_string (const string& str) const Timecode::Time TC; framepos_t sample; char ignored[2]; + int hours; - if (sscanf (str.c_str(), "%d:%d:%d%[:;]%d", &TC.hours, &TC.minutes, &TC.seconds, ignored, &TC.frames) != 5) { + if (sscanf (str.c_str(), "%d:%d:%d%[:;]%d", &hours, &TC.minutes, &TC.seconds, ignored, &TC.frames) != 5) { error << string_compose (_("programming error: %1 %2"), "badly formatted timecode clock string", str) << endmsg; return 0; } - - TC.negative = edit_is_negative; + TC.hours = abs(hours); TC.rate = _session->timecode_frames_per_second(); TC.drop= _session->timecode_drop_frames(); _session->timecode_to_sample (TC, sample, false /* use_offset */, false /* use_subframes */ ); // timecode_tester (); + if (edit_is_negative) { + sample = - sample; + } return sample; } diff --git a/gtk2_ardour/option_editor.cc b/gtk2_ardour/option_editor.cc index 3c3f5baab6..da4594e6da 100644 --- a/gtk2_ardour/option_editor.cc +++ b/gtk2_ardour/option_editor.cc @@ -25,6 +25,7 @@ #include "ardour/rc_configuration.h" #include "ardour/utils.h" #include "ardour/dB.h" +#include "ardour/session.h" #include "option_editor.h" #include "gui_thread.h" @@ -284,7 +285,7 @@ FaderOption::add_to_page (OptionEditorPage* p) add_widgets_to_page (p, &_label, &_box); } -ClockOption::ClockOption (string const & i, string const & n, sigc::slot g, sigc::slot s) +ClockOption::ClockOption (string const & i, string const & n, sigc::slot g, sigc::slot s) : Option (i, n) , _clock (X_("timecode-offset"), false, X_(""), true, false, true, false) , _get (g) @@ -299,13 +300,24 @@ ClockOption::ClockOption (string const & i, string const & n, sigc::slotframes_per_timecode_frame(); + TC.drop = _session->timecode_drop_frames(); + _session->timecode_to_sample(TC, when, false, false); + if (TC.negative) { when=-when; } + _clock.set (when, true); } void ClockOption::save_clock_time () { - _set (_clock.current_time()); + Timecode::Time TC; + _session->sample_to_timecode(_clock.current_time(), TC, false, false); + _set (Timecode::timecode_format_time(TC)); } void @@ -317,6 +329,7 @@ ClockOption::add_to_page (OptionEditorPage* p) void ClockOption::set_session (Session* s) { + _session = s; _clock.set_session (s); } diff --git a/gtk2_ardour/option_editor.h b/gtk2_ardour/option_editor.h index 454b0f2245..5fbed4ea32 100644 --- a/gtk2_ardour/option_editor.h +++ b/gtk2_ardour/option_editor.h @@ -535,7 +535,7 @@ private: class ClockOption : public Option { public: - ClockOption (std::string const &, std::string const &, sigc::slot, sigc::slot); + ClockOption (std::string const &, std::string const &, sigc::slot, sigc::slot); void set_state_from_config (); void add_to_page (OptionEditorPage *); void set_session (ARDOUR::Session *); @@ -547,8 +547,9 @@ private: void save_clock_time (); Gtk::Label _label; AudioClock _clock; - sigc::slot _get; - sigc::slot _set; + sigc::slot _get; + sigc::slot _set; + ARDOUR::Session *_session; }; class DirectoryOption : public Option diff --git a/gtk2_ardour/session_option_editor.cc b/gtk2_ardour/session_option_editor.cc index dd95b7f057..64ccc93de6 100644 --- a/gtk2_ardour/session_option_editor.cc +++ b/gtk2_ardour/session_option_editor.cc @@ -91,24 +91,32 @@ SessionOptionEditor::SessionOptionEditor (Session* s) add_option (_("Timecode"), _vpu); - ClockOption* co = new ClockOption ( - "timecode-offset", - _("Timecode offset"), - sigc::mem_fun (*_session_config, &SessionConfiguration::get_timecode_offset), - sigc::mem_fun (*_session_config, &SessionConfiguration::set_timecode_offset) + + add_option (_("Timecode"), new OptionEditorHeading (_("Ext Timecode Offsets"))); + + ClockOption* sco = new ClockOption ( + "slave-timecode-offset", + _("Slave Timecode offset"), + sigc::mem_fun (*_session_config, &SessionConfiguration::get_slave_timecode_offset), + sigc::mem_fun (*_session_config, &SessionConfiguration::set_slave_timecode_offset) ); - co->set_session (_session); - co->clock().set_negative_allowed (true); + sco->set_session (_session); + sco->clock().set_negative_allowed (true); - add_option (_("Timecode"), co); + add_option (_("Timecode"), sco); - add_option (_("Timecode"), new BoolOption ( - "timecode-offset-negative", - _("Timecode Offset Negative"), - sigc::mem_fun (*_session_config, &SessionConfiguration::get_timecode_offset_negative), - sigc::mem_fun (*_session_config, &SessionConfiguration::set_timecode_offset_negative) - )); + ClockOption* gco = new ClockOption ( + "timecode-generator-offset", + _("Timecode Generator offset"), + sigc::mem_fun (*_session_config, &SessionConfiguration::get_timecode_generator_offset), + sigc::mem_fun (*_session_config, &SessionConfiguration::set_timecode_generator_offset) + ); + + gco->set_session (_session); + gco->clock().set_negative_allowed (true); + + add_option (_("Timecode"), gco); add_option (_("Timecode"), new OptionEditorHeading (_("JACK Transport/Time Settings"))); @@ -300,7 +308,11 @@ SessionOptionEditor::parameter_changed (std::string const & p) _vpu->set_sensitive(true); } } - + if (p == "timecode-format") { + /* update offset clocks */ + parameter_changed("timecode-generator-offset"); + parameter_changed("slave-timecode-offset"); + } } /* the presence of absence of a monitor section is not really a regular session diff --git a/libs/ardour/ardour/session.h b/libs/ardour/ardour/session.h index d86b919111..9b25f29c8f 100644 --- a/libs/ardour/ardour/session.h +++ b/libs/ardour/ardour/session.h @@ -1203,6 +1203,9 @@ class Session : public PBD::StatefulDestructible, public PBD::ScopedConnectionLi framepos_t ltc_enc_off; bool restarting; + framepos_t ltc_timecode_offset; + bool ltc_timecode_negative_offset; + jack_latency_range_t ltc_out_latency; void ltc_tx_initialize(); @@ -1210,6 +1213,7 @@ class Session : public PBD::StatefulDestructible, public PBD::ScopedConnectionLi void ltc_tx_reset(); void ltc_tx_resync_latency(); void ltc_tx_recalculate_position(); + void ltc_tx_parse_offset(); void ltc_tx_send_time_code_for_cycle (framepos_t, framepos_t, double, double, pframes_t nframes); #endif diff --git a/libs/ardour/ardour/session_configuration_vars.h b/libs/ardour/ardour/session_configuration_vars.h index 1bfc254921..1a4a17ce5d 100644 --- a/libs/ardour/ardour/session_configuration_vars.h +++ b/libs/ardour/ardour/session_configuration_vars.h @@ -53,10 +53,8 @@ CONFIG_VARIABLE (bool, external_sync, "external-sync", false) CONFIG_VARIABLE (InsertMergePolicy, insert_merge_policy, "insert-merge-policy", InsertMergeRelax) CONFIG_VARIABLE (framecnt_t, timecode_offset, "timecode-offset", 0) CONFIG_VARIABLE (bool, timecode_offset_negative, "timecode-offset-negative", true) -CONFIG_VARIABLE (framecnt_t, slave_timecode_offset, "slave-timecode-offset", 0) -CONFIG_VARIABLE (bool, slave_timecode_offset_negative, "slave-timecode-offset-negative", true) -CONFIG_VARIABLE (framecnt_t, timecode_generator_offset, "timecode-generator-offset", 0) -CONFIG_VARIABLE (bool, timecode_generator_offset_negative, "timecode-generator-offset-negative", true) +CONFIG_VARIABLE (std::string, slave_timecode_offset, "slave-timecode-offset", " 00:00:00:00") +CONFIG_VARIABLE (std::string, timecode_generator_offset, "timecode-generator-offset", " 00:00:00:00") CONFIG_VARIABLE (bool, glue_new_markers_to_bars_and_beats, "glue-new-markers-to-bars-and-beats", false) CONFIG_VARIABLE (bool, midi_copy_is_fork, "midi-copy-is-fork", false) CONFIG_VARIABLE (bool, glue_new_regions_to_bars_and_beats, "glue-new-regions-to-bars-and-beats", false) diff --git a/libs/ardour/ardour/slave.h b/libs/ardour/ardour/slave.h index 5b424bab6f..d5cb79c522 100644 --- a/libs/ardour/ardour/slave.h +++ b/libs/ardour/ardour/slave.h @@ -247,6 +247,9 @@ class TimecodeSlave : public Slave { of the TC source position. */ virtual std::string approximate_current_position() const = 0; + + framepos_t timecode_offset; + bool timecode_negative_offset; }; class MTC_Slave : public TimecodeSlave { @@ -274,6 +277,7 @@ class MTC_Slave : public TimecodeSlave { Session& session; MIDI::Port* port; PBD::ScopedConnectionList port_connections; + PBD::ScopedConnection config_connection; bool can_notify_on_unknown_rate; static const int frame_tolerance; @@ -327,6 +331,8 @@ class MTC_Slave : public TimecodeSlave { bool outside_window (framepos_t) const; void init_mtc_dll(framepos_t, double); void init_engine_dll (framepos_t, framepos_t); + void parse_timecode_offset(); + void parameter_changed(std::string const & p); }; #ifdef HAVE_LTC @@ -359,6 +365,8 @@ public: void reset(); void resync_xrun(); void resync_latency(); + void parse_timecode_offset(); + void parameter_changed(std::string const & p); Session& session; bool did_reset_tc_format; @@ -384,6 +392,7 @@ public: Timecode::TimecodeFormat a3e_timecode; PBD::ScopedConnectionList port_connections; + PBD::ScopedConnection config_connection; jack_latency_range_t ltc_slave_latency; /* DLL - chase LTC */ diff --git a/libs/ardour/ltc_slave.cc b/libs/ardour/ltc_slave.cc index 166c78ce24..7b182cfa64 100644 --- a/libs/ardour/ltc_slave.cc +++ b/libs/ardour/ltc_slave.cc @@ -60,6 +60,9 @@ LTC_Slave::LTC_Slave (Session& s) memset(&prev_frame, 0, sizeof(LTCFrameExt)); decoder = ltc_decoder_create((int) frames_per_ltc_frame, 128 /*queue size*/); + + session.config.ParameterChanged.connect_same_thread (config_connection, boost::bind (<C_Slave::parameter_changed, this, _1)); + parse_timecode_offset(); reset(); resync_latency(); session.Xrun.connect_same_thread (port_connections, boost::bind (<C_Slave::resync_xrun, this)); @@ -69,6 +72,7 @@ LTC_Slave::LTC_Slave (Session& s) LTC_Slave::~LTC_Slave() { port_connections.drop_connections(); + config_connection.disconnect(); if (did_reset_tc_format) { session.config.set_timecode_format (saved_tc_format); @@ -77,6 +81,27 @@ LTC_Slave::~LTC_Slave() ltc_decoder_free(decoder); } +void +LTC_Slave::parse_timecode_offset() { + Timecode::Time offset_tc; + Timecode::parse_timecode_format(session.config.get_slave_timecode_offset(), offset_tc); + offset_tc.rate = session.timecode_frames_per_second(); + offset_tc.drop = session.timecode_drop_frames(); + session.timecode_to_sample(offset_tc, timecode_offset, false, false); + timecode_negative_offset = offset_tc.negative; +} + +void +LTC_Slave::parameter_changed (std::string const & p) +{ + if (p == "slave-timecode-offset" + || p == "subframes-per-frame" + || p == "timecode-format" + ) { + parse_timecode_offset(); + } +} + ARDOUR::framecnt_t LTC_Slave::resolution () const { @@ -334,7 +359,7 @@ LTC_Slave::process_ltc(framepos_t const now) Timecode::timecode_to_sample (timecode, ltc_frame, true, false, double(session.frame_rate()), session.config.get_subframes_per_frame(), - session.config.get_slave_timecode_offset_negative(), session.config.get_slave_timecode_offset() + timecode_negative_offset, timecode_offset ); framepos_t cur_timestamp = frame.off_end + 1; diff --git a/libs/ardour/mtc_slave.cc b/libs/ardour/mtc_slave.cc index 1228465f8b..dcc51ee5c8 100644 --- a/libs/ardour/mtc_slave.cc +++ b/libs/ardour/mtc_slave.cc @@ -66,6 +66,8 @@ MTC_Slave::MTC_Slave (Session& s, MIDI::Port& p) a3e_timecode = session.config.get_timecode_format(); printed_timecode_warning = false; + session.config.ParameterChanged.connect_same_thread (config_connection, boost::bind (&MTC_Slave::parameter_changed, this, _1)); + parse_timecode_offset(); reset (true); rebind (p); } @@ -73,6 +75,7 @@ MTC_Slave::MTC_Slave (Session& s, MIDI::Port& p) MTC_Slave::~MTC_Slave() { port_connections.drop_connections(); + config_connection.disconnect(); while (busy_guard1 != busy_guard2) { /* make sure MIDI parser is not currently calling any callbacks in here, @@ -101,6 +104,27 @@ MTC_Slave::rebind (MIDI::Port& p) port->parser()->mtc_status.connect_same_thread (port_connections, boost::bind (&MTC_Slave::update_mtc_status, this, _1)); } +void +MTC_Slave::parse_timecode_offset() { + Timecode::Time offset_tc; + Timecode::parse_timecode_format(session.config.get_slave_timecode_offset(), offset_tc); + offset_tc.rate = session.timecode_frames_per_second(); + offset_tc.drop = session.timecode_drop_frames(); + session.timecode_to_sample(offset_tc, timecode_offset, false, false); + timecode_negative_offset = offset_tc.negative; +} + +void +MTC_Slave::parameter_changed (std::string const & p) +{ + if (p == "slave-timecode-offset" + || p == "subframes-per-frame" + || p == "timecode-format" + ) { + parse_timecode_offset(); + } +} + bool MTC_Slave::give_slave_full_control_over_transport_speed() const { @@ -390,7 +414,7 @@ MTC_Slave::update_mtc_time (const byte *msg, bool was_full, framepos_t now) Timecode::timecode_to_sample (timecode, mtc_frame, true, false, double(session.frame_rate()), session.config.get_subframes_per_frame(), - session.config.get_slave_timecode_offset_negative(), session.config.get_slave_timecode_offset() + timecode_negative_offset, timecode_offset ); DEBUG_TRACE (DEBUG::MTC, string_compose ("MTC at %1 TC %2 = mtc_frame %3 (from full message ? %4) tc-ratio %5\n", diff --git a/libs/ardour/session_ltc.cc b/libs/ardour/session_ltc.cc index 25ba783fc7..733ca6877e 100644 --- a/libs/ardour/session_ltc.cc +++ b/libs/ardour/session_ltc.cc @@ -63,6 +63,7 @@ Session::ltc_tx_initialize() { ltc_enc_tcformat = config.get_timecode_format(); + ltc_tx_parse_offset(); DEBUG_TRACE (DEBUG::LTC, string_compose("LTC TX init sr: %1 fps: %2\n", nominal_frame_rate(), timecode_to_frames_per_second(ltc_enc_tcformat))); ltc_encoder = ltc_encoder_create(nominal_frame_rate(), timecode_to_frames_per_second(ltc_enc_tcformat), @@ -118,6 +119,16 @@ Session::ltc_tx_reset() ltc_encoder_reset(ltc_encoder); } +void +Session::ltc_tx_parse_offset() { + Timecode::Time offset_tc; + Timecode::parse_timecode_format(config.get_timecode_generator_offset(), offset_tc); + offset_tc.rate = timecode_frames_per_second(); + offset_tc.drop = timecode_drop_frames(); + timecode_to_sample(offset_tc, ltc_timecode_offset, false, false); + ltc_timecode_negative_offset = !offset_tc.negative; +} + void Session::ltc_tx_recalculate_position() { @@ -135,7 +146,7 @@ Session::ltc_tx_recalculate_position() Timecode::timecode_to_sample (a3tc, ltc_enc_pos, true, false, (double)frame_rate(), config.get_subframes_per_frame(), - config.get_timecode_generator_offset_negative(), config.get_timecode_generator_offset() + ltc_timecode_negative_offset, ltc_timecode_offset ); restarting = false; } @@ -204,6 +215,7 @@ Session::ltc_tx_send_time_code_for_cycle (framepos_t start_frame, framepos_t end } ltc_encoder_set_filter(ltc_encoder, LTC_RISE_TIME(ltc_speed)); ltc_enc_tcformat = cur_timecode; + ltc_tx_parse_offset(); ltc_tx_reset(); } @@ -365,14 +377,14 @@ Session::ltc_tx_send_time_code_for_cycle (framepos_t start_frame, framepos_t end timecode_drop_frames(), (double)frame_rate(), config.get_subframes_per_frame(), - config.get_timecode_generator_offset_negative(), config.get_timecode_generator_offset() + ltc_timecode_negative_offset, ltc_timecode_offset ); /* convert timecode back to sample-position */ Timecode::timecode_to_sample (tc_start, tc_sample_start, true, false, (double)frame_rate(), config.get_subframes_per_frame(), - config.get_timecode_generator_offset_negative(), config.get_timecode_generator_offset() + ltc_timecode_negative_offset, ltc_timecode_offset ); /* difference between current frame and TC frame in samples */ diff --git a/libs/ardour/session_state.cc b/libs/ardour/session_state.cc index 46dee236ae..74257ef482 100644 --- a/libs/ardour/session_state.cc +++ b/libs/ardour/session_state.cc @@ -3565,6 +3565,10 @@ Session::config_changed (std::string p, bool ours) reconnect_ltc_input (); } else if (p == "ltc-sink-port") { reconnect_ltc_output (); +#ifdef HAVE_LTC + } else if (p == "timecode-generator-offset") { + ltc_tx_parse_offset(); +#endif } set_dirty (); diff --git a/libs/ardour/session_time.cc b/libs/ardour/session_time.cc index 5e25aaf508..72d8fb1e89 100644 --- a/libs/ardour/session_time.cc +++ b/libs/ardour/session_time.cc @@ -95,6 +95,9 @@ Session::sync_time_vars () } break; }; +#ifdef HAVE_LTC + ltc_tx_parse_offset(); +#endif } void diff --git a/libs/timecode/src/time.cc b/libs/timecode/src/time.cc index b39da34d6d..9a67e177b7 100644 --- a/libs/timecode/src/time.cc +++ b/libs/timecode/src/time.cc @@ -21,6 +21,7 @@ #include #include +#include #include "timecode/time.h" @@ -761,8 +762,8 @@ sample_to_timecode ( int64_t offset_sample; if (!use_offset) { - offset_sample = sample; - timecode.negative = false; + timecode.negative = (sample < 0); + offset_sample = llabs(sample); } else { if (offset_is_negative) { offset_sample = sample + offset_samples;