X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=libs%2Fardour%2Ftempo.cc;h=5d3be149644012c923ff209a69b1eaa85ef7ea2d;hb=refs%2Ftags%2F6.0-pre0;hp=0279007a215196f848f2c402bdb6e45e01609b29;hpb=a432f6585ce04e3500df1d64c7897158cc26d3a9;p=ardour.git diff --git a/libs/ardour/tempo.cc b/libs/ardour/tempo.cc index 0279007a21..5d3be14964 100644 --- a/libs/ardour/tempo.cc +++ b/libs/ardour/tempo.cc @@ -32,6 +32,7 @@ #include "ardour/debug.h" #include "ardour/lmath.h" #include "ardour/tempo.h" +#include "ardour/types_convert.h" #include "pbd/i18n.h" #include @@ -45,7 +46,7 @@ using Timecode::BBT_Time; /* _default tempo is 4/4 qtr=120 */ Meter TempoMap::_default_meter (4.0, 4.0); -Tempo TempoMap::_default_tempo (120.0); +Tempo TempoMap::_default_tempo (120.0, 4.0, 120.0); framepos_t MetricSection::frame_at_minute (const double& time) const @@ -54,11 +55,39 @@ MetricSection::frame_at_minute (const double& time) const } double -MetricSection::minute_at_frame (const framepos_t& frame) const +MetricSection::minute_at_frame (const framepos_t frame) const { return (frame / (double) _sample_rate) / 60.0; } +/***********************************************************************/ + +bool +ARDOUR::bbt_time_to_string (const BBT_Time& bbt, std::string& str) +{ + char buf[256]; + int retval = snprintf (buf, sizeof(buf), "%" PRIu32 "|%" PRIu32 "|%" PRIu32, bbt.bars, bbt.beats, + bbt.ticks); + + if (retval <= 0 || retval >= (int)sizeof(buf)) { + return false; + } + + str = buf; + return true; +} + +bool +ARDOUR::string_to_bbt_time (const std::string& str, BBT_Time& bbt) +{ + if (sscanf (str.c_str (), "%" PRIu32 "|%" PRIu32 "|%" PRIu32, &bbt.bars, &bbt.beats, + &bbt.ticks) == 3) { + return true; + } + return false; +} + + /***********************************************************************/ double @@ -71,7 +100,7 @@ Meter::frames_per_grid (const Tempo& tempo, framecnt_t sr) const The return value IS NOT interpretable in terms of "beats". */ - return (60.0 * sr) / (tempo.note_types_per_minute() * (_note_type/tempo.note_type())); + return (60.0 * sr) / (tempo.note_types_per_minute() * (_note_type / tempo.note_type())); } double @@ -82,125 +111,126 @@ Meter::frames_per_bar (const Tempo& tempo, framecnt_t sr) const /***********************************************************************/ +void +MetricSection::add_state_to_node(XMLNode& node) const +{ + node.set_property ("pulse", _pulse); + node.set_property ("frame", frame()); + node.set_property ("movable", !_initial); + node.set_property ("lock-style", _position_lock_style); +} + +int +MetricSection::set_state (const XMLNode& node, int /*version*/) +{ + node.get_property ("pulse", _pulse); + + framepos_t frame; + if (node.get_property ("frame", frame)) { + set_minute (minute_at_frame (frame)); + } + + bool tmp; + if (!node.get_property ("movable", tmp)) { + error << _("TempoSection XML node has no \"movable\" property") << endmsg; + throw failed_constructor(); + } + _initial = !tmp; + + if (!node.get_property ("lock-style", _position_lock_style)) { + if (!initial()) { + _position_lock_style = MusicTime; + } else { + _position_lock_style = AudioTime; + } + } + return 0; +} + +/***********************************************************************/ + const string TempoSection::xml_state_node_name = "Tempo"; TempoSection::TempoSection (const XMLNode& node, framecnt_t sample_rate) : MetricSection (0.0, 0, MusicTime, true, sample_rate) , Tempo (TempoMap::default_tempo()) - , _c_func (0.0) + , _c (0.0) , _active (true) , _locked_to_meter (false) + , _clamped (false) { - XMLProperty const * prop; - LocaleGuard lg; BBT_Time bbt; - double pulse; - uint32_t frame; - double minute; - bool had_beats_per_minute = false; - - _legacy_bbt = BBT_Time (0, 0, 0); - - if ((prop = node.property ("start")) != 0) { - if (sscanf (prop->value().c_str(), "%" PRIu32 "|%" PRIu32 "|%" PRIu32, - &bbt.bars, - &bbt.beats, - &bbt.ticks) == 3) { + std::string start_bbt; + if (node.get_property ("start", start_bbt)) { + if (string_to_bbt_time (start_bbt, bbt)) { /* legacy session - start used to be in bbt*/ _legacy_bbt = bbt; - pulse = -1.0; + set_pulse(-1.0); info << _("Legacy session detected. TempoSection XML node will be altered.") << endmsg; } } - if ((prop = node.property ("pulse")) != 0) { - if (sscanf (prop->value().c_str(), "%lf", &pulse) != 1) { - error << _("TempoSection XML node has an illegal \"pulse\" value") << endmsg; - } - } - - set_pulse (pulse); - - if ((prop = node.property ("frame")) != 0) { - if (sscanf (prop->value().c_str(), "%" PRIu32, &frame) != 1) { - error << _("TempoSection XML node has an illegal \"frame\" value") << endmsg; - } else { - set_minute (minute_at_frame (frame)); - } - } - - if ((prop = node.property ("minute")) != 0) { - if (sscanf (prop->value().c_str(), "%lf", &minute) != 1) { - error << _("TempoSection XML node has an illegal \"minute\" value") << endmsg; - } else { - set_minute (minute); - } - } + // Don't worry about return value, exception will be thrown on error + MetricSection::set_state (node, Stateful::loading_state_version); - /* replace old beats-per-minute with note-types-per-minute */ - if ((prop = node.property ("beats-per-minute")) != 0) { - info << _("Renaming legacy \"beats-per-minute\" XML node to note-types-per-minute") << endmsg; - if (sscanf (prop->value().c_str(), "%lf", &_note_types_per_minute) != 1 || _note_types_per_minute < 0.0) { - error << _("TempoSection XML node has an illegal \"beats-per-minutee\" value") << endmsg; + if (node.get_property ("beats-per-minute", _note_types_per_minute)) { + if (_note_types_per_minute < 0.0) { + error << _("TempoSection XML node has an illegal \"beats_per_minute\" value") << endmsg; throw failed_constructor(); } - had_beats_per_minute = true; } - if ((prop = node.property ("note-types-per-minute")) != 0) { - if (sscanf (prop->value().c_str(), "%lf", &_note_types_per_minute) != 1 || _note_types_per_minute < 0.0) { - error << _("TempoSection XML node has an illegal \"note-types-per-minute\" value") << endmsg; + if (node.get_property ("note-type", _note_type)) { + if (_note_type < 1.0) { + error << _("TempoSection XML node has an illegal \"note-type\" value") << endmsg; throw failed_constructor(); } - } else if (!had_beats_per_minute) { - error << _("TempoSection XML node has no \"note-types-per-minute\" or \"beats-per-minute\" property") << endmsg; - throw failed_constructor(); - } - - if ((prop = node.property ("note-type")) == 0) { + } else { /* older session, make note type be quarter by default */ _note_type = 4.0; - } else { - if (sscanf (prop->value().c_str(), "%lf", &_note_type) != 1 || _note_type < 1.0) { - error << _("TempoSection XML node has an illegal \"note-type\" value") << endmsg; - throw failed_constructor(); } + + if (!node.get_property ("clamped", _clamped)) { + _clamped = false; } - if ((prop = node.property ("movable")) == 0) { - error << _("TempoSection XML node has no \"movable\" property") << endmsg; - throw failed_constructor(); + if (node.get_property ("end-beats-per-minute", _end_note_types_per_minute)) { + if (_end_note_types_per_minute < 0.0) { + info << _("TempoSection XML node has an illegal \"end-beats-per-minute\" value") << endmsg; + throw failed_constructor(); + } } - set_movable (string_is_affirmative (prop->value())); + TempoSection::Type old_type; + if (node.get_property ("tempo-type", old_type)) { + /* sessions with a tempo-type node contain no end-beats-per-minute. + if the legacy node indicates a constant tempo, simply fill this in with the + start tempo. otherwise we need the next neighbour to know what it will be. + */ - if ((prop = node.property ("active")) == 0) { - warning << _("TempoSection XML node has no \"active\" property") << endmsg; - set_active(true); - } else { - set_active (string_is_affirmative (prop->value())); + if (old_type == TempoSection::Constant) { + _end_note_types_per_minute = _note_types_per_minute; + } else { + _end_note_types_per_minute = -1.0; + } } - if ((prop = node.property ("tempo-type")) == 0) { - _type = Constant; - } else { - _type = Type (string_2_enum (prop->value(), _type)); + if (!node.get_property ("active", _active)) { + warning << _("TempoSection XML node has no \"active\" property") << endmsg; + _active = true; } - if ((prop = node.property ("lock-style")) == 0) { - if (movable()) { - set_position_lock_style (MusicTime); + if (!node.get_property ("locked-to-meter", _locked_to_meter)) { + if (initial()) { + set_locked_to_meter (true); } else { - set_position_lock_style (AudioTime); + set_locked_to_meter (false); } - } else { - set_position_lock_style (PositionLockStyle (string_2_enum (prop->value(), position_lock_style()))); } - if ((prop = node.property ("locked-to-meter")) == 0) { - set_locked_to_meter (false); - } else { - set_locked_to_meter (string_is_affirmative (prop->value())); + /* 5.5 marked initial tempo as not locked to meter. this should always be true anyway */ + if (initial()) { + set_locked_to_meter (true); } } @@ -208,34 +238,17 @@ XMLNode& TempoSection::get_state() const { XMLNode *root = new XMLNode (xml_state_node_name); - char buf[256]; - LocaleGuard lg; - - snprintf (buf, sizeof (buf), "%lf", pulse()); - root->add_property ("pulse", buf); - snprintf (buf, sizeof (buf), "%li", frame()); - root->add_property ("frame", buf); - snprintf (buf, sizeof (buf), "%lf", minute()); - root->add_property ("minute", buf); - snprintf (buf, sizeof (buf), "%lf", _note_types_per_minute); - root->add_property ("note-types-per-minute", buf); - snprintf (buf, sizeof (buf), "%lf", _note_type); - root->add_property ("note-type", buf); - snprintf (buf, sizeof (buf), "%s", movable()?"yes":"no"); - root->add_property ("movable", buf); - snprintf (buf, sizeof (buf), "%s", active()?"yes":"no"); - root->add_property ("active", buf); - root->add_property ("tempo-type", enum_2_string (_type)); - root->add_property ("lock-style", enum_2_string (position_lock_style())); - root->add_property ("locked-to-meter", locked_to_meter()?"yes":"no"); - return *root; -} + MetricSection::add_state_to_node (*root); -void -TempoSection::set_type (Type type) -{ - _type = type; + root->set_property ("beats-per-minute", _note_types_per_minute); + root->set_property ("note-type", _note_type); + root->set_property ("clamped", _clamped); + root->set_property ("end-beats-per-minute", _end_note_types_per_minute); + root->set_property ("active", _active); + root->set_property ("locked-to-meter", _locked_to_meter); + + return *root; } /** returns the Tempo at the session-relative minute. @@ -243,12 +256,12 @@ TempoSection::set_type (Type type) Tempo TempoSection::tempo_at_minute (const double& m) const { - - if (_type == Constant || _c_func == 0.0) { + const bool constant = type() == Constant || _c == 0.0 || (initial() && m < minute()); + if (constant) { return Tempo (note_types_per_minute(), note_type()); } - return Tempo (_tempo_at_time (m - minute()), _note_type); + return Tempo (_tempo_at_time (m - minute()), _note_type, _end_note_types_per_minute); } /** returns the session relative minute where the supplied tempo in note types per minute occurs. @@ -260,17 +273,17 @@ TempoSection::tempo_at_minute (const double& m) const * */ -/** user feedback dictates that if tempoA (120, 4.0) precedes tempoB (120, 8.0), - * there will be no ramp between the two even if set to ramped. +/** if tempoA (120, 4.0) precedes tempoB (120, 8.0), + * there should be no ramp between the two even if we are ramped. * in other words a ramp should only place a curve on note_types_per_minute. * we should be able to use Tempo note type here, but the above * complicates things a bit. - * we would ideally like to use arbitrary Tempo structs here. */ double TempoSection::minute_at_ntpm (const double& ntpm, const double& p) const { - if (_type == Constant || _c_func == 0.0) { + const bool constant = type() == Constant || _c == 0.0 || (initial() && p < pulse()); + if (constant) { return ((p - pulse()) / pulses_per_minute()) + minute(); } @@ -282,12 +295,13 @@ TempoSection::minute_at_ntpm (const double& ntpm, const double& p) const Tempo TempoSection::tempo_at_pulse (const double& p) const { + const bool constant = type() == Constant || _c == 0.0 || (initial() && p < pulse()); - if (_type == Constant || _c_func == 0.0) { + if (constant) { return Tempo (note_types_per_minute(), note_type()); } - return Tempo (_tempo_at_pulse (p - pulse()), _note_type); + return Tempo (_tempo_at_pulse (p - pulse()), _note_type, _end_note_types_per_minute); } /** returns the whole-note pulse where a tempo in note types per minute occurs. @@ -303,7 +317,8 @@ TempoSection::tempo_at_pulse (const double& p) const double TempoSection::pulse_at_ntpm (const double& ntpm, const double& m) const { - if (_type == Constant || _c_func == 0.0) { + const bool constant = type() == Constant || _c == 0.0 || (initial() && m < minute()); + if (constant) { return ((m - minute()) * pulses_per_minute()) + pulse(); } @@ -315,7 +330,8 @@ TempoSection::pulse_at_ntpm (const double& ntpm, const double& m) const double TempoSection::pulse_at_minute (const double& m) const { - if (_type == Constant || _c_func == 0.0) { + const bool constant = type() == Constant || _c == 0.0 || (initial() && m < minute()); + if (constant) { return ((m - minute()) * pulses_per_minute()) + pulse(); } @@ -327,13 +343,42 @@ TempoSection::pulse_at_minute (const double& m) const double TempoSection::minute_at_pulse (const double& p) const { - if (_type == Constant || _c_func == 0.0) { + const bool constant = type() == Constant || _c == 0.0 || (initial() && p < pulse()); + if (constant) { return ((p - pulse()) / pulses_per_minute()) + minute(); } return _time_at_pulse (p - pulse()) + minute(); } +/** returns thw whole-note pulse at session frame position f. + * @param f the frame position. + * @return the position in whole-note pulses corresponding to f + * + * for use with musical units whose granularity is coarser than frames (e.g. ticks) +*/ +double +TempoSection::pulse_at_frame (const framepos_t f) const +{ + const bool constant = type() == Constant || _c == 0.0 || (initial() && f < frame()); + if (constant) { + return (minute_at_frame (f - frame()) * pulses_per_minute()) + pulse(); + } + + return _pulse_at_time (minute_at_frame (f - frame())) + pulse(); +} + +framepos_t +TempoSection::frame_at_pulse (const double& p) const +{ + const bool constant = type() == Constant || _c == 0.0 || (initial() && p < pulse()); + if (constant) { + return frame_at_minute (((p - pulse()) / pulses_per_minute()) + minute()); + } + + return frame_at_minute (_time_at_pulse (p - pulse()) + minute()); +} + /* Ramp Overview @@ -415,8 +460,12 @@ https://www.zhdk.ch/fileadmin/data_subsites/data_icst/Downloads/Timegrid/ICST_Te * @return the calculated function constant */ double -TempoSection::compute_c_func_pulse (const double& end_npm, const double& end_pulse) const +TempoSection::compute_c_pulse (const double& end_npm, const double& end_pulse) const { + if (note_types_per_minute() == end_npm || type() == Constant) { + return 0.0; + } + double const log_tempo_ratio = log (end_npm / note_types_per_minute()); return (note_types_per_minute() * expm1 (log_tempo_ratio)) / ((end_pulse - pulse()) * _note_type); } @@ -427,16 +476,20 @@ TempoSection::compute_c_func_pulse (const double& end_npm, const double& end_pul * @return the calculated function constant */ double -TempoSection::compute_c_func_minute (const double& end_npm, const double& end_minute) const +TempoSection::compute_c_minute (const double& end_npm, const double& end_minute) const { + if (note_types_per_minute() == end_npm || type() == Constant) { + return 0.0; + } + return c_func (end_npm, end_minute - minute()); } /* position function */ double -TempoSection::a_func (double end_npm, double c_func) const +TempoSection::a_func (double end_npm, double c) const { - return log (end_npm / note_types_per_minute()) / c_func; + return log (end_npm / note_types_per_minute()) / c; } /*function constant*/ @@ -450,42 +503,42 @@ TempoSection::c_func (double end_npm, double end_time) const double TempoSection::_tempo_at_time (const double& time) const { - return exp (_c_func * time) * note_types_per_minute(); + return exp (_c * time) * note_types_per_minute(); } /* time in minutes at tempo in note types per minute */ double TempoSection::_time_at_tempo (const double& npm) const { - return log (npm / note_types_per_minute()) / _c_func; + return log (npm / note_types_per_minute()) / _c; } /* pulse at tempo in note types per minute */ double TempoSection::_pulse_at_tempo (const double& npm) const { - return ((npm - note_types_per_minute()) / _c_func) / _note_type; + return ((npm - note_types_per_minute()) / _c) / _note_type; } /* tempo in note types per minute at pulse */ double TempoSection::_tempo_at_pulse (const double& pulse) const { - return (pulse * _note_type * _c_func) + note_types_per_minute(); + return (pulse * _note_type * _c) + note_types_per_minute(); } /* pulse at time in minutes */ double TempoSection::_pulse_at_time (const double& time) const { - return (expm1 (_c_func * time) * (note_types_per_minute() / _c_func)) / _note_type; + return (expm1 (_c * time) * (note_types_per_minute() / _c)) / _note_type; } /* time in minutes at pulse */ double TempoSection::_time_at_pulse (const double& pulse) const { - return log1p ((_c_func * pulse * _note_type) / note_types_per_minute()) / _c_func; + return log1p ((_c * pulse * _note_type) / note_types_per_minute()) / _c; } /***********************************************************************/ @@ -495,139 +548,73 @@ const string MeterSection::xml_state_node_name = "Meter"; MeterSection::MeterSection (const XMLNode& node, const framecnt_t sample_rate) : MetricSection (0.0, 0, MusicTime, false, sample_rate), Meter (TempoMap::default_meter()) { - XMLProperty const * prop; - LocaleGuard lg; - BBT_Time bbt; - double pulse = 0.0; - double beat = 0.0; - framepos_t frame = 0; pair start; - double minute = 0.0; + start.first = 0.0; - if ((prop = node.property ("start")) != 0) { - if (sscanf (prop->value().c_str(), "%" PRIu32 "|%" PRIu32 "|%" PRIu32, - &bbt.bars, - &bbt.beats, - &bbt.ticks) < 3) { - error << _("MeterSection XML node has an illegal \"start\" value") << endmsg; - } else { + std::string bbt_str; + if (node.get_property ("start", bbt_str)) { + if (string_to_bbt_time (bbt_str, start.second)) { /* legacy session - start used to be in bbt*/ info << _("Legacy session detected - MeterSection XML node will be altered.") << endmsg; - pulse = -1.0; - } - } - - if ((prop = node.property ("pulse")) != 0) { - if (sscanf (prop->value().c_str(), "%lf", &pulse) != 1) { - error << _("MeterSection XML node has an illegal \"pulse\" value") << endmsg; + set_pulse (-1.0); + } else { + error << _("MeterSection XML node has an illegal \"start\" value") << endmsg; } } - set_pulse (pulse); - if ((prop = node.property ("beat")) != 0) { - if (sscanf (prop->value().c_str(), "%lf", &beat) != 1) { - error << _("MeterSection XML node has an illegal \"beat\" value") << endmsg; - } - } + MetricSection::set_state (node, Stateful::loading_state_version); - start.first = beat; + node.get_property ("beat", start.first); - if ((prop = node.property ("bbt")) == 0) { + if (node.get_property ("bbt", bbt_str)) { + if (!string_to_bbt_time (bbt_str, start.second)) { + error << _("MeterSection XML node has an illegal \"bbt\" value") << endmsg; + throw failed_constructor(); + } + } else { warning << _("MeterSection XML node has no \"bbt\" property") << endmsg; - } else if (sscanf (prop->value().c_str(), "%" PRIu32 "|%" PRIu32 "|%" PRIu32, - &bbt.bars, - &bbt.beats, - &bbt.ticks) < 3) { - error << _("MeterSection XML node has an illegal \"bbt\" value") << endmsg; - throw failed_constructor(); } - start.second = bbt; set_beat (start); - if ((prop = node.property ("frame")) != 0) { - if (sscanf (prop->value().c_str(), "%li", &frame) != 1) { - error << _("MeterSection XML node has an illegal \"frame\" value") << endmsg; - } else { - set_minute (minute_at_frame (frame)); - } - } - if ((prop = node.property ("minute")) != 0) { - if (sscanf (prop->value().c_str(), "%lf", &minute) != 1) { - error << _("MeterSection XML node has an illegal \"frame\" value") << endmsg; - } else { - set_minute (minute); - } - } - /* beats-per-bar is old; divisions-per-bar is new */ - if ((prop = node.property ("divisions-per-bar")) == 0) { - if ((prop = node.property ("beats-per-bar")) == 0) { + if (!node.get_property ("divisions-per-bar", _divisions_per_bar)) { + if (!node.get_property ("beats-per-bar", _divisions_per_bar)) { error << _("MeterSection XML node has no \"beats-per-bar\" or \"divisions-per-bar\" property") << endmsg; throw failed_constructor(); } } - if (sscanf (prop->value().c_str(), "%lf", &_divisions_per_bar) != 1 || _divisions_per_bar < 0.0) { + + if (_divisions_per_bar < 0.0) { error << _("MeterSection XML node has an illegal \"divisions-per-bar\" value") << endmsg; throw failed_constructor(); } - if ((prop = node.property ("note-type")) == 0) { + if (!node.get_property ("note-type", _note_type)) { error << _("MeterSection XML node has no \"note-type\" property") << endmsg; throw failed_constructor(); } - if (sscanf (prop->value().c_str(), "%lf", &_note_type) != 1 || _note_type < 0.0) { - error << _("MeterSection XML node has an illegal \"note-type\" value") << endmsg; - throw failed_constructor(); - } - if ((prop = node.property ("movable")) == 0) { - error << _("MeterSection XML node has no \"movable\" property") << endmsg; + if (_note_type < 0.0) { + error << _("MeterSection XML node has an illegal \"note-type\" value") << endmsg; throw failed_constructor(); } - - set_movable (string_is_affirmative (prop->value())); - - if ((prop = node.property ("lock-style")) == 0) { - warning << _("MeterSection XML node has no \"lock-style\" property") << endmsg; - if (movable()) { - set_position_lock_style (MusicTime); - } else { - set_position_lock_style (AudioTime); - } - } else { - set_position_lock_style (PositionLockStyle (string_2_enum (prop->value(), position_lock_style()))); - } } XMLNode& MeterSection::get_state() const { XMLNode *root = new XMLNode (xml_state_node_name); - char buf[256]; - LocaleGuard lg; - - snprintf (buf, sizeof (buf), "%lf", pulse()); - root->add_property ("pulse", buf); - snprintf (buf, sizeof (buf), "%" PRIu32 "|%" PRIu32 "|%" PRIu32, - bbt().bars, - bbt().beats, - bbt().ticks); - root->add_property ("bbt", buf); - snprintf (buf, sizeof (buf), "%lf", beat()); - root->add_property ("beat", buf); - snprintf (buf, sizeof (buf), "%lf", _note_type); - root->add_property ("note-type", buf); - snprintf (buf, sizeof (buf), "%li", frame()); - root->add_property ("frame", buf); - snprintf (buf, sizeof (buf), "%lf", minute()); - root->add_property ("minute", buf); - root->add_property ("lock-style", enum_2_string (position_lock_style())); - snprintf (buf, sizeof (buf), "%lf", _divisions_per_bar); - root->add_property ("divisions-per-bar", buf); - snprintf (buf, sizeof (buf), "%s", movable()?"yes":"no"); - root->add_property ("movable", buf); + + MetricSection::add_state_to_node (*root); + + std::string bbt_str; + bbt_time_to_string (_bbt, bbt_str); + root->set_property ("bbt", bbt_str); + root->set_property ("beat", beat()); + root->set_property ("note-type", _note_type); + root->set_property ("divisions-per-bar", _divisions_per_bar); return *root; } @@ -731,11 +718,13 @@ TempoMap::TempoMap (framecnt_t fr) _frame_rate = fr; BBT_Time start (1, 1, 0); - TempoSection *t = new TempoSection (0.0, 0.0, _default_tempo.note_types_per_minute(), _default_tempo.note_type(), TempoSection::Ramp, AudioTime, fr); + TempoSection *t = new TempoSection (0.0, 0.0, _default_tempo, AudioTime, fr); MeterSection *m = new MeterSection (0.0, 0.0, 0.0, start, _default_meter.divisions_per_bar(), _default_meter.note_divisor(), AudioTime, fr); - t->set_movable (false); - m->set_movable (false); + t->set_initial (true); + t->set_locked_to_meter (true); + + m->set_initial (true); /* note: frame time is correct (zero) for both of these */ @@ -744,6 +733,40 @@ TempoMap::TempoMap (framecnt_t fr) } +TempoMap& +TempoMap::operator= (TempoMap const & other) +{ + if (&other != this) { + Glib::Threads::RWLock::ReaderLock lr (other.lock); + Glib::Threads::RWLock::WriterLock lm (lock); + _frame_rate = other._frame_rate; + + Metrics::const_iterator d = _metrics.begin(); + while (d != _metrics.end()) { + delete (*d); + ++d; + } + _metrics.clear(); + + for (Metrics::const_iterator m = other._metrics.begin(); m != other._metrics.end(); ++m) { + TempoSection const * const ts = dynamic_cast (*m); + MeterSection const * const ms = dynamic_cast (*m); + + if (ts) { + TempoSection* new_section = new TempoSection (*ts); + _metrics.push_back (new_section); + } else { + MeterSection* new_section = new MeterSection (*ms); + _metrics.push_back (new_section); + } + } + } + + PropertyChanged (PropertyChange()); + + return *this; +} + TempoMap::~TempoMap () { Metrics::const_iterator d = _metrics.begin(); @@ -793,7 +816,7 @@ TempoMap::remove_tempo_locked (const TempoSection& tempo) for (i = _metrics.begin(); i != _metrics.end(); ++i) { if (dynamic_cast (*i) != 0) { if (tempo.frame() == (*i)->frame()) { - if ((*i)->movable()) { + if (!(*i)->initial()) { delete (*i); _metrics.erase (i); return true; @@ -845,7 +868,7 @@ TempoMap::remove_meter_locked (const MeterSection& meter) for (Metrics::iterator i = _metrics.begin(); i != _metrics.end(); ++i) { if (dynamic_cast (*i) != 0) { if (meter.frame() == (*i)->frame()) { - if ((*i)->movable()) { + if (!(*i)->initial()) { delete (*i); _metrics.erase (i); return true; @@ -899,7 +922,7 @@ TempoMap::do_insert (MetricSection* section) bool const ipm = insert_tempo->position_lock_style() == MusicTime; if ((ipm && tempo->pulse() == insert_tempo->pulse()) || (!ipm && tempo->frame() == insert_tempo->frame())) { - if (!tempo->movable()) { + if (tempo->initial()) { /* can't (re)move this section, so overwrite * its data content (but not its properties as @@ -908,10 +931,6 @@ TempoMap::do_insert (MetricSection* section) *(dynamic_cast(*i)) = *(dynamic_cast(insert_tempo)); (*i)->set_position_lock_style (AudioTime); - TempoSection* t; - if ((t = dynamic_cast(*i)) != 0) { - t->set_type (insert_tempo->type()); - } need_add = false; } else { delete (*i); @@ -928,7 +947,7 @@ TempoMap::do_insert (MetricSection* section) if ((ipm && meter->beat() == insert_meter->beat()) || (!ipm && meter->frame() == insert_meter->frame())) { - if (!meter->movable()) { + if (meter->initial()) { /* can't (re)move this section, so overwrite * its data content (but not its properties as @@ -958,15 +977,24 @@ TempoMap::do_insert (MetricSection* section) MeterSection* const insert_meter = dynamic_cast (section); TempoSection* const insert_tempo = dynamic_cast (section); Metrics::iterator i; + if (insert_meter) { + TempoSection* prev_t = 0; + for (i = _metrics.begin(); i != _metrics.end(); ++i) { MeterSection* const meter = dynamic_cast (*i); + bool const ipm = insert_meter->position_lock_style() == MusicTime; if (meter) { - bool const ipm = insert_meter->position_lock_style() == MusicTime; if ((ipm && meter->beat() > insert_meter->beat()) || (!ipm && meter->frame() > insert_meter->frame())) { break; } + } else { + if (prev_t && prev_t->locked_to_meter() && (!ipm && prev_t->frame() == insert_meter->frame())) { + break; + } + + prev_t = dynamic_cast (*i); } } } else if (insert_tempo) { @@ -975,7 +1003,9 @@ TempoMap::do_insert (MetricSection* section) if (tempo) { bool const ipm = insert_tempo->position_lock_style() == MusicTime; - if ((ipm && tempo->pulse() > insert_tempo->pulse()) || (!ipm && tempo->frame() > insert_tempo->frame())) { + const bool lm = insert_tempo->locked_to_meter(); + if ((ipm && tempo->pulse() > insert_tempo->pulse()) || (!ipm && tempo->frame() > insert_tempo->frame()) + || (lm && tempo->pulse() > insert_tempo->pulse())) { break; } } @@ -983,19 +1013,26 @@ TempoMap::do_insert (MetricSection* section) } _metrics.insert (i, section); - //dump (_metrics, std::cout); + //dump (std::cout); } } /* user supplies the exact pulse if pls == MusicTime */ TempoSection* -TempoMap::add_tempo (const Tempo& tempo, const double& pulse, const framepos_t& frame, ARDOUR::TempoSection::Type type, PositionLockStyle pls) +TempoMap::add_tempo (const Tempo& tempo, const double& pulse, const framepos_t frame, PositionLockStyle pls) { + if (tempo.note_types_per_minute() <= 0.0) { + warning << "Cannot add tempo. note types per minute must be greater than zero." << endmsg; + return 0; + } + TempoSection* ts = 0; { Glib::Threads::RWLock::WriterLock lm (lock); - ts = add_tempo_locked (tempo, pulse, minute_at_frame (frame), type, pls, true); - } + /* here we default to not clamped for a new tempo section. preference? */ + ts = add_tempo_locked (tempo, pulse, minute_at_frame (frame), pls, true, false, false); + recompute_map (_metrics); + } PropertyChanged (PropertyChange ()); @@ -1003,27 +1040,49 @@ TempoMap::add_tempo (const Tempo& tempo, const double& pulse, const framepos_t& } void -TempoMap::replace_tempo (const TempoSection& ts, const Tempo& tempo, const double& pulse, const framepos_t& frame, TempoSection::Type type, PositionLockStyle pls) +TempoMap::replace_tempo (TempoSection& ts, const Tempo& tempo, const double& pulse, const framepos_t frame, PositionLockStyle pls) { - const bool locked_to_meter = ts.locked_to_meter(); + if (tempo.note_types_per_minute() <= 0.0) { + warning << "Cannot replace tempo. note types per minute must be greater than zero." << endmsg; + return; + } + + bool const locked_to_meter = ts.locked_to_meter(); + bool const ts_clamped = ts.clamped(); + TempoSection* new_ts = 0; { Glib::Threads::RWLock::WriterLock lm (lock); TempoSection& first (first_tempo()); - if (ts.frame() != first.frame()) { - remove_tempo_locked (ts); - add_tempo_locked (tempo, pulse, minute_at_frame (frame), type, pls, true, locked_to_meter); + if (!ts.initial()) { + if (locked_to_meter) { + { + /* cannot move a meter-locked tempo section */ + *static_cast(&ts) = tempo; + recompute_map (_metrics); + } + } else { + remove_tempo_locked (ts); + new_ts = add_tempo_locked (tempo, pulse, minute_at_frame (frame), pls, true, locked_to_meter, ts_clamped); + /* enforce clampedness of next tempo section */ + TempoSection* next_t = next_tempo_section_locked (_metrics, new_ts); + if (next_t && next_t->clamped()) { + next_t->set_note_types_per_minute (new_ts->end_note_types_per_minute()); + } + } + } else { - first.set_type (type); first.set_pulse (0.0); first.set_minute (minute_at_frame (frame)); first.set_position_lock_style (AudioTime); + first.set_locked_to_meter (true); + first.set_clamped (ts_clamped); { /* cannot move the first tempo section */ *static_cast(&first) = tempo; - recompute_map (_metrics); } } + recompute_map (_metrics); } PropertyChanged (PropertyChange ()); @@ -1031,43 +1090,53 @@ TempoMap::replace_tempo (const TempoSection& ts, const Tempo& tempo, const doubl TempoSection* TempoMap::add_tempo_locked (const Tempo& tempo, double pulse, double minute - , TempoSection::Type type, PositionLockStyle pls, bool recompute, bool locked_to_meter) + , PositionLockStyle pls, bool recompute, bool locked_to_meter, bool clamped) { - TempoSection* t = new TempoSection (pulse, minute, tempo.note_types_per_minute(), tempo.note_type(), type, pls, _frame_rate); + TempoSection* t = new TempoSection (pulse, minute, tempo, pls, _frame_rate); t->set_locked_to_meter (locked_to_meter); - bool solved = false; + t->set_clamped (clamped); do_insert (t); + TempoSection* prev_tempo = 0; + for (Metrics::iterator i = _metrics.begin(); i != _metrics.end(); ++i) { + TempoSection* const this_t = dynamic_cast(*i); + if (this_t) { + if (this_t == t) { + if (prev_tempo && prev_tempo->type() == TempoSection::Ramp) { + prev_tempo->set_end_note_types_per_minute (t->note_types_per_minute()); + } + break; + } + prev_tempo = this_t; + } + } + if (recompute) { if (pls == AudioTime) { - solved = solve_map_minute (_metrics, t, t->minute()); + solve_map_minute (_metrics, t, t->minute()); } else { - solved = solve_map_pulse (_metrics, t, t->pulse()); + solve_map_pulse (_metrics, t, t->pulse()); } recompute_meters (_metrics); } - if (!solved && recompute) { - recompute_map (_metrics); - } - return t; } MeterSection* -TempoMap::add_meter (const Meter& meter, const double& beat, const Timecode::BBT_Time& where, PositionLockStyle pls) +TempoMap::add_meter (const Meter& meter, const Timecode::BBT_Time& where, framepos_t frame, PositionLockStyle pls) { MeterSection* m = 0; { Glib::Threads::RWLock::WriterLock lm (lock); - m = add_meter_locked (meter, beat, where, pls, true); + m = add_meter_locked (meter, where, frame, pls, true); } #ifndef NDEBUG if (DEBUG_ENABLED(DEBUG::TempoMap)) { - dump (_metrics, std::cerr); + dump (std::cerr); } #endif @@ -1076,15 +1145,14 @@ TempoMap::add_meter (const Meter& meter, const double& beat, const Timecode::BBT } void -TempoMap::replace_meter (const MeterSection& ms, const Meter& meter, const BBT_Time& where, PositionLockStyle pls) +TempoMap::replace_meter (const MeterSection& ms, const Meter& meter, const BBT_Time& where, framepos_t frame, PositionLockStyle pls) { { Glib::Threads::RWLock::WriterLock lm (lock); - const double beat = beat_at_bbt_locked (_metrics, where); - if (ms.movable()) { + if (!ms.initial()) { remove_meter_locked (ms); - add_meter_locked (meter, beat, where, pls, true); + add_meter_locked (meter, where, frame, pls, true); } else { MeterSection& first (first_meter()); TempoSection& first_t (first_tempo()); @@ -1092,10 +1160,11 @@ TempoMap::replace_meter (const MeterSection& ms, const Meter& meter, const BBT_T *static_cast(&first) = meter; first.set_position_lock_style (AudioTime); first.set_pulse (0.0); - //first.set_minute (minute_at_frame (frame)); + first.set_minute (minute_at_frame (frame)); pair beat = make_pair (0.0, BBT_Time (1, 1, 0)); first.set_beat (beat); first_t.set_minute (first.minute()); + first_t.set_locked_to_meter (true); first_t.set_pulse (0.0); first_t.set_position_lock_style (AudioTime); recompute_map (_metrics); @@ -1106,24 +1175,26 @@ TempoMap::replace_meter (const MeterSection& ms, const Meter& meter, const BBT_T } MeterSection* -TempoMap::add_meter_locked (const Meter& meter, double beat, const BBT_Time& where, PositionLockStyle pls, bool recompute) +TempoMap::add_meter_locked (const Meter& meter, const BBT_Time& bbt, framepos_t frame, PositionLockStyle pls, bool recompute) { - const MeterSection& prev_m = meter_section_at_minute_locked (_metrics, minute_at_beat_locked (_metrics, beat) - minute_at_frame (1)); - const double pulse = ((where.bars - prev_m.bbt().bars) * (prev_m.divisions_per_bar() / prev_m.note_divisor())) + prev_m.pulse(); - const double time_minutes = minute_at_pulse_locked (_metrics, pulse); - TempoSection* mlt = 0; + double const minute_at_bbt = minute_at_bbt_locked (_metrics, bbt); + const MeterSection& prev_m = meter_section_at_minute_locked (_metrics, minute_at_bbt - minute_at_frame (1)); + double const pulse = ((bbt.bars - prev_m.bbt().bars) * (prev_m.divisions_per_bar() / prev_m.note_divisor())) + prev_m.pulse(); + /* the natural time of the BBT position */ + double const time_minutes = minute_at_pulse_locked (_metrics, pulse); if (pls == AudioTime) { - /* add meter-locked tempo */ - mlt = add_tempo_locked (tempo_at_minute_locked (_metrics, time_minutes), pulse, time_minutes, TempoSection::Ramp, AudioTime, true, true); + /* add meter-locked tempo at the natural time in the current map (frame may differ). */ + Tempo const tempo_at_time = tempo_at_minute_locked (_metrics, time_minutes); + TempoSection* mlt = add_tempo_locked (tempo_at_time, pulse, time_minutes, AudioTime, true, true, false); if (!mlt) { return 0; } - } + /* still using natural time for the position, ignoring lock style. */ + MeterSection* new_meter = new MeterSection (pulse, time_minutes, beat_at_bbt_locked (_metrics, bbt), bbt, meter.divisions_per_bar(), meter.note_divisor(), pls, _frame_rate); - MeterSection* new_meter = new MeterSection (pulse, time_minutes, beat, where, meter.divisions_per_bar(), meter.note_divisor(), pls, _frame_rate); bool solved = false; do_insert (new_meter); @@ -1131,9 +1202,16 @@ TempoMap::add_meter_locked (const Meter& meter, double beat, const BBT_Time& whe if (recompute) { if (pls == AudioTime) { - solved = solve_map_minute (_metrics, new_meter, time_minutes); + /* now set the audio locked meter's position to frame */ + solved = solve_map_minute (_metrics, new_meter, minute_at_frame (frame)); + /* we failed, most likely due to some impossible frame requirement wrt audio-locked tempi. + fudge frame so that the meter ends up at its BBT position instead. + */ + if (!solved) { + solved = solve_map_minute (_metrics, new_meter, minute_at_frame (prev_m.frame() + 1)); + } } else { - solved = solve_map_bbt (_metrics, new_meter, where); + solved = solve_map_bbt (_metrics, new_meter, bbt); /* required due to resetting the pulse of meter-locked tempi above. Arguably solve_map_bbt() should use solve_map_pulse (_metrics, TempoSection) instead, but afaict this cannot cause the map to be left unsolved (these tempi are all audio locked). @@ -1154,9 +1232,9 @@ TempoMap::add_meter_locked (const Meter& meter, double beat, const BBT_Time& whe } void -TempoMap::change_initial_tempo (double note_types_per_minute, double note_type) +TempoMap::change_initial_tempo (double note_types_per_minute, double note_type, double end_note_types_per_minute) { - Tempo newtempo (note_types_per_minute, note_type); + Tempo newtempo (note_types_per_minute, note_type, end_note_types_per_minute); TempoSection* t; for (Metrics::iterator i = _metrics.begin(); i != _metrics.end(); ++i) { @@ -1176,9 +1254,9 @@ TempoMap::change_initial_tempo (double note_types_per_minute, double note_type) } void -TempoMap::change_existing_tempo_at (framepos_t where, double note_types_per_minute, double note_type) +TempoMap::change_existing_tempo_at (framepos_t where, double note_types_per_minute, double note_type, double end_ntpm) { - Tempo newtempo (note_types_per_minute, note_type); + Tempo newtempo (note_types_per_minute, note_type, end_ntpm); TempoSection* prev; TempoSection* first; @@ -1273,7 +1351,7 @@ TempoMap::first_tempo () const if (!t->active()) { continue; } - if (!t->movable()) { + if (t->initial()) { return *t; } } @@ -1294,7 +1372,7 @@ TempoMap::first_tempo () if (!t->active()) { continue; } - if (!t->movable()) { + if (t->initial()) { return *t; } } @@ -1317,30 +1395,32 @@ TempoMap::recompute_tempi (Metrics& metrics) if (!t->active()) { continue; } - if (!t->movable()) { + if (t->initial()) { if (!prev_t) { t->set_pulse (0.0); prev_t = t; continue; } } + if (prev_t) { if (t->position_lock_style() == AudioTime) { - prev_t->set_c_func (prev_t->compute_c_func_minute (t->note_types_per_minute(), t->minute())); + prev_t->set_c (prev_t->compute_c_minute (prev_t->end_note_types_per_minute(), t->minute())); if (!t->locked_to_meter()) { - t->set_pulse (prev_t->pulse_at_ntpm (t->note_types_per_minute(), t->minute())); + t->set_pulse (prev_t->pulse_at_ntpm (prev_t->end_note_types_per_minute(), t->minute())); } } else { - prev_t->set_c_func (prev_t->compute_c_func_pulse (t->note_types_per_minute(), t->pulse())); - t->set_minute (prev_t->minute_at_ntpm (t->note_types_per_minute(), t->pulse())); + prev_t->set_c (prev_t->compute_c_pulse (prev_t->end_note_types_per_minute(), t->pulse())); + t->set_minute (prev_t->minute_at_ntpm (prev_t->end_note_types_per_minute(), t->pulse())); } } prev_t = t; } } - prev_t->set_c_func (0.0); + assert (prev_t); + prev_t->set_c (0.0); } /* tempos must be positioned correctly. @@ -1365,7 +1445,7 @@ TempoMap::recompute_meters (Metrics& metrics) TempoSection* t; if ((*ii)->is_tempo()) { t = static_cast (*ii); - if ((t->locked_to_meter() || !t->movable()) && t->frame() == meter->frame()) { + if (t->locked_to_meter() && t->frame() == meter->frame()) { meter_locked_tempo = t; break; } @@ -1373,14 +1453,16 @@ TempoMap::recompute_meters (Metrics& metrics) } if (prev_m) { - const double beats = (meter->bbt().bars - prev_m->bbt().bars) * prev_m->divisions_per_bar(); + double beats = (meter->bbt().bars - prev_m->bbt().bars) * prev_m->divisions_per_bar(); if (beats + prev_m->beat() != meter->beat()) { /* reordering caused a bbt change */ + + beats = meter->beat() - prev_m->beat(); b_bbt = make_pair (beats + prev_m->beat() , BBT_Time ((beats / prev_m->divisions_per_bar()) + prev_m->bbt().bars, 1, 0)); pulse = prev_m->pulse() + (beats / prev_m->note_divisor()); - } else if (meter->movable()) { + } else if (!meter->initial()) { b_bbt = make_pair (meter->beat(), meter->bbt()); pulse = prev_m->pulse() + (beats / prev_m->note_divisor()); } @@ -1446,6 +1528,10 @@ TempoMap::metric_at (framepos_t frame, Metrics::const_iterator* last) const Glib::Threads::RWLock::ReaderLock lm (lock); TempoMetric m (first_meter(), first_tempo()); + if (last) { + *last = ++_metrics.begin(); + } + /* at this point, we are *guaranteed* to have m.meter and m.tempo pointing at something, because we insert the default tempo and meter during TempoMap construction. @@ -1510,7 +1596,7 @@ TempoMap::metric_at (BBT_Time bbt) const * This function uses both tempo and meter. */ double -TempoMap::beat_at_frame (const framecnt_t& frame) const +TempoMap::beat_at_frame (const framecnt_t frame) const { Glib::Threads::RWLock::ReaderLock lm (lock); @@ -1577,12 +1663,18 @@ TempoMap::minute_at_beat_locked (const Metrics& metrics, const double& beat) con prev_m = m; } } + assert (prev_m); TempoSection* t; for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) { if ((*i)->is_tempo()) { t = static_cast (*i); + + if (!t->active()) { + continue; + } + if (prev_t && ((t->pulse() - prev_m->pulse()) * prev_m->note_divisor()) + prev_m->beat() > beat) { break; } @@ -1590,6 +1682,7 @@ TempoMap::minute_at_beat_locked (const Metrics& metrics, const double& beat) con } } + assert (prev_t); return prev_t->minute_at_pulse (((beat - prev_m->beat()) / prev_m->note_divisor()) + prev_m->pulse()); } @@ -1600,7 +1693,7 @@ TempoMap::minute_at_beat_locked (const Metrics& metrics, const double& beat) con * */ Tempo -TempoMap::tempo_at_frame (const framepos_t& frame) const +TempoMap::tempo_at_frame (const framepos_t frame) const { Glib::Threads::RWLock::ReaderLock lm (lock); @@ -1628,7 +1721,7 @@ TempoMap::tempo_at_minute_locked (const Metrics& metrics, const double& minute) } } - return Tempo (prev_t->note_types_per_minute(), prev_t->note_type()); + return Tempo (prev_t->note_types_per_minute(), prev_t->note_type(), prev_t->end_note_types_per_minute()); } /** returns the frame at which the supplied tempo occurs, or @@ -1661,17 +1754,20 @@ TempoMap::minute_at_tempo_locked (const Metrics& metrics, const Tempo& tempo) co continue; } - const double t_bpm = t->note_types_per_minute(); - if (t_bpm == tempo_bpm) { + + if (t->note_types_per_minute() == tempo_bpm) { return t->minute(); } if (prev_t) { const double prev_t_bpm = prev_t->note_types_per_minute(); + const double prev_t_end_bpm = prev_t->end_note_types_per_minute(); + if ((prev_t_bpm > tempo_bpm && prev_t_end_bpm < tempo_bpm) + || (prev_t_bpm < tempo_bpm && prev_t_end_bpm > tempo_bpm) + || (prev_t_end_bpm == tempo_bpm)) { - if ((t_bpm > tempo_bpm && prev_t_bpm < tempo_bpm) || (t_bpm < tempo_bpm && prev_t_bpm > tempo_bpm)) { - return prev_t->minute_at_ntpm (prev_t->note_types_per_minute(), prev_t->pulse()); + return prev_t->minute_at_ntpm (tempo_bpm, t->pulse()); } } prev_t = t; @@ -1702,7 +1798,7 @@ TempoMap::tempo_at_pulse_locked (const Metrics& metrics, const double& pulse) co } } - return Tempo (prev_t->note_types_per_minute(), prev_t->note_type()); + return Tempo (prev_t->note_types_per_minute(), prev_t->note_type(), prev_t->end_note_types_per_minute()); } double @@ -1764,7 +1860,7 @@ TempoMap::quarter_note_at_tempo (const Tempo& tempo) const { Glib::Threads::RWLock::ReaderLock lm (lock); - return pulse_at_tempo_locked (_metrics, tempo) * 4.0;; + return pulse_at_tempo_locked (_metrics, tempo) * 4.0; } /** Returns the whole-note pulse corresponding to the supplied BBT (meter-based) beat. @@ -1807,6 +1903,7 @@ TempoMap::beat_at_pulse_locked (const Metrics& metrics, const double& pulse) con prev_m = m; } } + assert (prev_m); double const ret = ((pulse - prev_m->pulse()) * prev_m->note_divisor()) + prev_m->beat(); return ret; @@ -1953,6 +2050,7 @@ TempoMap::bbt_at_beat_locked (const Metrics& metrics, const double& b) const prev_m = m; } } + assert (prev_m); const double beats_in_ms = beats - prev_m->beat(); const uint32_t bars_in_ms = (uint32_t) floor (beats_in_ms / prev_m->divisions_per_bar()); @@ -2090,6 +2188,8 @@ TempoMap::bbt_at_pulse_locked (const Metrics& metrics, const double& pulse) cons } } + assert (prev_m); + const double beats_in_ms = (pulse - prev_m->pulse()) * prev_m->note_divisor(); const uint32_t bars_in_ms = (uint32_t) floor (beats_in_ms / prev_m->divisions_per_bar()); const uint32_t total_bars = bars_in_ms + (prev_m->bbt().bars - 1); @@ -2132,24 +2232,31 @@ TempoMap::bbt_at_frame (framepos_t frame) bbt.bars = 1; bbt.beats = 1; bbt.ticks = 0; - warning << string_compose (_("tempo map asked for BBT time at frame %1\n"), frame) << endmsg; +#ifndef NDEBUG + warning << string_compose (_("tempo map was asked for BBT time at frame %1\n"), frame) << endmsg; +#endif return bbt; } + + const double minute = minute_at_frame (frame); + Glib::Threads::RWLock::ReaderLock lm (lock); - return bbt_at_minute_locked (_metrics, minute_at_frame (frame)); + return bbt_at_minute_locked (_metrics, minute); } BBT_Time TempoMap::bbt_at_frame_rt (framepos_t frame) { + const double minute = minute_at_frame (frame); + Glib::Threads::RWLock::ReaderLock lm (lock, Glib::Threads::TRY_LOCK); if (!lm.locked()) { throw std::logic_error ("TempoMap::bbt_at_frame_rt() could not lock tempo map"); } - return bbt_at_minute_locked (_metrics, minute_at_frame (frame)); + return bbt_at_minute_locked (_metrics, minute); } Timecode::BBT_Time @@ -2231,16 +2338,23 @@ framepos_t TempoMap::frame_at_bbt (const BBT_Time& bbt) { if (bbt.bars < 1) { +#ifndef NDEBUG warning << string_compose (_("tempo map asked for frame time at bar < 1 (%1)\n"), bbt) << endmsg; +#endif return 0; } if (bbt.beats < 1) { throw std::logic_error ("beats are counted from one"); } - Glib::Threads::RWLock::ReaderLock lm (lock); - return frame_at_minute (minute_at_bbt_locked (_metrics, bbt)); + double minute; + { + Glib::Threads::RWLock::ReaderLock lm (lock); + minute = minute_at_bbt_locked (_metrics, bbt); + } + + return frame_at_minute (minute); } /* meter & tempo section based */ @@ -2263,33 +2377,25 @@ TempoMap::minute_at_bbt_locked (const Metrics& metrics, const BBT_Time& bbt) con double TempoMap::quarter_note_at_frame (const framepos_t frame) const { - Glib::Threads::RWLock::ReaderLock lm (lock); - - const double ret = quarter_note_at_minute_locked (_metrics, minute_at_frame (frame)); - - return ret; -} + const double minute = minute_at_frame (frame); -double -TempoMap::quarter_note_at_minute_locked (const Metrics& metrics, const double minute) const -{ - const double ret = pulse_at_minute_locked (metrics, minute) * 4.0; + Glib::Threads::RWLock::ReaderLock lm (lock); - return ret; + return pulse_at_minute_locked (_metrics, minute) * 4.0; } double TempoMap::quarter_note_at_frame_rt (const framepos_t frame) const { + const double minute = minute_at_frame (frame); + Glib::Threads::RWLock::ReaderLock lm (lock, Glib::Threads::TRY_LOCK); if (!lm.locked()) { throw std::logic_error ("TempoMap::quarter_note_at_frame_rt() could not lock tempo map"); } - const double ret = pulse_at_minute_locked (_metrics, minute_at_frame (frame)) * 4.0; - - return ret; + return pulse_at_minute_locked (_metrics, minute) * 4.0; } /** @@ -2303,19 +2409,14 @@ TempoMap::quarter_note_at_frame_rt (const framepos_t frame) const framepos_t TempoMap::frame_at_quarter_note (const double quarter_note) const { - Glib::Threads::RWLock::ReaderLock lm (lock); - - const framepos_t ret = frame_at_minute (minute_at_quarter_note_locked (_metrics, quarter_note)); - - return ret; -} + double minute; + { + Glib::Threads::RWLock::ReaderLock lm (lock); -double -TempoMap::minute_at_quarter_note_locked (const Metrics& metrics, const double quarter_note) const -{ - const double ret = minute_at_pulse_locked (metrics, quarter_note / 4.0); + minute = minute_at_pulse_locked (_metrics, quarter_note / 4.0); + } - return ret; + return frame_at_minute (minute); } /** Returns the quarter-note beats corresponding to the supplied BBT (meter-based) beat. @@ -2326,21 +2427,11 @@ TempoMap::minute_at_quarter_note_locked (const Metrics& metrics, const double qu * */ double -TempoMap::quarter_note_at_beat (const double beat) +TempoMap::quarter_note_at_beat (const double beat) const { Glib::Threads::RWLock::ReaderLock lm (lock); - const double ret = quarter_note_at_beat_locked (_metrics, beat); - - return ret; -} - -double -TempoMap::quarter_note_at_beat_locked (const Metrics& metrics, const double beat) const -{ - const double ret = pulse_at_beat_locked (metrics, beat) * 4.0; - - return ret; + return pulse_at_beat_locked (_metrics, beat) * 4.0; } /** Returns the BBT (meter-based) beat position corresponding to the supplied quarter-note beats. @@ -2351,20 +2442,11 @@ TempoMap::quarter_note_at_beat_locked (const Metrics& metrics, const double beat * */ double -TempoMap::beat_at_quarter_note (const double quarter_note) +TempoMap::beat_at_quarter_note (const double quarter_note) const { Glib::Threads::RWLock::ReaderLock lm (lock); - const double ret = beat_at_quarter_note_locked (_metrics, quarter_note); - - return ret; -} - -double -TempoMap::beat_at_quarter_note_locked (const Metrics& metrics, const double quarter_note) const -{ - - return beat_at_pulse_locked (metrics, quarter_note / 4.0); + return beat_at_pulse_locked (_metrics, quarter_note / 4.0); } /** Returns the duration in frames between two supplied quarter-note beat positions. @@ -2380,9 +2462,14 @@ TempoMap::beat_at_quarter_note_locked (const Metrics& metrics, const double quar framecnt_t TempoMap::frames_between_quarter_notes (const double start, const double end) const { - Glib::Threads::RWLock::ReaderLock lm (lock); + double minutes; - return frame_at_minute (minutes_between_quarter_notes_locked (_metrics, start, end)); + { + Glib::Threads::RWLock::ReaderLock lm (lock); + minutes = minutes_between_quarter_notes_locked (_metrics, start, end); + } + + return frame_at_minute (minutes); } double @@ -2392,6 +2479,55 @@ TempoMap::minutes_between_quarter_notes_locked (const Metrics& metrics, const do return minute_at_pulse_locked (metrics, end / 4.0) - minute_at_pulse_locked (metrics, start / 4.0); } +double +TempoMap::quarter_notes_between_frames (const framecnt_t start, const framecnt_t end) const +{ + Glib::Threads::RWLock::ReaderLock lm (lock); + + return quarter_notes_between_frames_locked (_metrics, start, end); +} + +double +TempoMap::quarter_notes_between_frames_locked (const Metrics& metrics, const framecnt_t start, const framecnt_t end) const +{ + const TempoSection* prev_t = 0; + + for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) { + TempoSection* t; + + if ((*i)->is_tempo()) { + t = static_cast (*i); + if (!t->active()) { + continue; + } + if (prev_t && t->frame() > start) { + break; + } + prev_t = t; + } + } + assert (prev_t); + const double start_qn = prev_t->pulse_at_frame (start); + + for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) { + TempoSection* t; + + if ((*i)->is_tempo()) { + t = static_cast (*i); + if (!t->active()) { + continue; + } + if (prev_t && t->frame() > end) { + break; + } + prev_t = t; + } + } + const double end_qn = prev_t->pulse_at_frame (end); + + return (end_qn - start_qn) * 4.0; +} + bool TempoMap::check_solved (const Metrics& metrics) const { @@ -2413,7 +2549,7 @@ TempoMap::check_solved (const Metrics& metrics) const } /* precision check ensures tempo and frames align.*/ - if (t->frame() != frame_at_minute (prev_t->minute_at_ntpm (t->note_types_per_minute(), t->pulse()))) { + if (t->frame() != frame_at_minute (prev_t->minute_at_ntpm (prev_t->end_note_types_per_minute(), t->pulse()))) { if (!t->locked_to_meter()) { return false; } @@ -2422,8 +2558,8 @@ TempoMap::check_solved (const Metrics& metrics) const /* gradient limit - who knows what it should be? things are also ok (if a little chaotic) without this */ - if (fabs (prev_t->c_func()) > 1000.0) { - //std::cout << "c : " << prev_t->c_func() << std::endl; + if (fabs (prev_t->c()) > 1000.0) { + //std::cout << "c : " << prev_t->c() << std::endl; return false; } } @@ -2434,10 +2570,10 @@ TempoMap::check_solved (const Metrics& metrics) const m = static_cast (*i); if (prev_m && m->position_lock_style() == AudioTime) { const TempoSection* t = &tempo_section_at_minute_locked (metrics, minute_at_frame (m->frame() - 1)); - const double nascent_m_minute = t->minute_at_pulse (m->pulse()); + const framepos_t nascent_m_frame = frame_at_minute (t->minute_at_pulse (m->pulse())); /* Here we check that a preceding section of music doesn't overlap a subsequent one. */ - if (t && (nascent_m_minute > m->minute() || nascent_m_minute < 0.0)) { + if (t && (nascent_m_frame > m->frame() || nascent_m_frame < 0)) { return false; } } @@ -2451,23 +2587,23 @@ TempoMap::check_solved (const Metrics& metrics) const } bool -TempoMap::set_active_tempos (const Metrics& metrics, const framepos_t& frame) +TempoMap::set_active_tempi (const Metrics& metrics, const framepos_t frame) { for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) { TempoSection* t; if ((*i)->is_tempo()) { t = static_cast (*i); - if (!t->movable()) { - t->set_active (true); - continue; - } - if (t->movable() && t->active () && t->position_lock_style() == AudioTime && t->frame() < frame) { - t->set_active (false); - t->set_pulse (0.0); - } else if (t->movable() && t->position_lock_style() == AudioTime && t->frame() > frame) { + if (t->locked_to_meter()) { t->set_active (true); - } else if (t->movable() && t->position_lock_style() == AudioTime && t->frame() == frame) { - return false; + } else if (t->position_lock_style() == AudioTime) { + if (t->frame() < frame) { + t->set_active (false); + t->set_pulse (-1.0); + } else if (t->frame() > frame) { + t->set_active (true); + } else if (t->frame() == frame) { + return false; + } } } } @@ -2480,19 +2616,20 @@ TempoMap::solve_map_minute (Metrics& imaginary, TempoSection* section, const dou TempoSection* prev_t = 0; TempoSection* section_prev = 0; double first_m_minute = 0.0; + const bool sml = section->locked_to_meter(); /* can't move a tempo before the first meter */ for (Metrics::iterator i = imaginary.begin(); i != imaginary.end(); ++i) { MeterSection* m; if (!(*i)->is_tempo()) { m = static_cast (*i); - if (!m->movable()) { + if (m->initial()) { first_m_minute = m->minute(); break; } } } - if (section->movable() && minute <= first_m_minute) { + if (!section->initial() && minute <= first_m_minute) { return false; } @@ -2507,21 +2644,36 @@ TempoMap::solve_map_minute (Metrics& imaginary, TempoSection* section, const dou if (!t->active()) { continue; } + if (prev_t) { + if (t == section) { + continue; + } + + if (t->frame() == frame_at_minute (minute)) { + return false; + } + + const bool tlm = t->position_lock_style() == MusicTime; + + if (prev_t && !section_prev && ((sml && tlm && t->pulse() > section->pulse()) || (!tlm && t->minute() > minute))) { section_prev = prev_t; - if (t->locked_to_meter()) { - prev_t = t; + + section_prev->set_c (section_prev->compute_c_minute (section_prev->end_note_types_per_minute(), minute)); + if (!section->locked_to_meter()) { + section->set_pulse (section_prev->pulse_at_ntpm (section_prev->end_note_types_per_minute(), minute)); } - continue; + prev_t = section; } + if (t->position_lock_style() == MusicTime) { - prev_t->set_c_func (prev_t->compute_c_func_pulse (t->note_types_per_minute(), t->pulse())); - t->set_minute (prev_t->minute_at_ntpm (t->note_types_per_minute(), t->pulse())); + prev_t->set_c (prev_t->compute_c_pulse (prev_t->end_note_types_per_minute(), t->pulse())); + t->set_minute (prev_t->minute_at_ntpm (prev_t->end_note_types_per_minute(), t->pulse())); } else { - prev_t->set_c_func (prev_t->compute_c_func_minute (t->note_types_per_minute(), t->minute())); + prev_t->set_c (prev_t->compute_c_minute (prev_t->end_note_types_per_minute(), t->minute())); if (!t->locked_to_meter()) { - t->set_pulse (prev_t->pulse_at_ntpm (t->note_types_per_minute(), t->minute())); + t->set_pulse (prev_t->pulse_at_ntpm (prev_t->end_note_types_per_minute(), t->minute())); } } } @@ -2529,23 +2681,6 @@ TempoMap::solve_map_minute (Metrics& imaginary, TempoSection* section, const dou } } - if (section_prev) { - section_prev->set_c_func (section_prev->compute_c_func_minute (section->note_types_per_minute(), minute)); - if (!section->locked_to_meter()) { - section->set_pulse (section_prev->pulse_at_ntpm (section->note_types_per_minute(), minute)); - } - } - -#if (0) - recompute_tempi (imaginary); - - if (check_solved (imaginary)) { - return true; - } else { - dunp (imaginary, std::cout); - } -#endif - MetricSectionFrameSorter fcmp; imaginary.sort (fcmp); @@ -2573,7 +2708,7 @@ TempoMap::solve_map_pulse (Metrics& imaginary, TempoSection* section, const doub if (!t->active()) { continue; } - if (!t->movable()) { + if (t->initial()) { t->set_pulse (0.0); prev_t = t; continue; @@ -2583,13 +2718,14 @@ TempoMap::solve_map_pulse (Metrics& imaginary, TempoSection* section, const doub section_prev = prev_t; continue; } + if (t->position_lock_style() == MusicTime) { - prev_t->set_c_func (prev_t->compute_c_func_pulse (t->note_types_per_minute(), t->pulse())); - t->set_minute (prev_t->minute_at_ntpm (t->note_types_per_minute(), t->pulse())); + prev_t->set_c (prev_t->compute_c_pulse (prev_t->end_note_types_per_minute(), t->pulse())); + t->set_minute (prev_t->minute_at_ntpm (prev_t->end_note_types_per_minute(), t->pulse())); } else { - prev_t->set_c_func (prev_t->compute_c_func_minute (t->note_types_per_minute(), t->minute())); + prev_t->set_c (prev_t->compute_c_minute (prev_t->end_note_types_per_minute(), t->minute())); if (!t->locked_to_meter()) { - t->set_pulse (prev_t->pulse_at_ntpm (t->note_types_per_minute(), t->minute())); + t->set_pulse (prev_t->pulse_at_ntpm (prev_t->end_note_types_per_minute(), t->minute())); } } } @@ -2598,19 +2734,9 @@ TempoMap::solve_map_pulse (Metrics& imaginary, TempoSection* section, const doub } if (section_prev) { - section_prev->set_c_func (section_prev->compute_c_func_pulse (section->note_types_per_minute(), pulse)); - section->set_minute (section_prev->minute_at_ntpm (section->note_types_per_minute(), pulse)); - } - -#if (0) - recompute_tempi (imaginary); - - if (check_solved (imaginary)) { - return true; - } else { - dunp (imaginary, std::cout); + section_prev->set_c (section_prev->compute_c_pulse (section_prev->end_note_types_per_minute(), pulse)); + section->set_minute (section_prev->minute_at_ntpm (section_prev->end_note_types_per_minute(), pulse)); } -#endif MetricSectionSorter cmp; imaginary.sort (cmp); @@ -2636,15 +2762,15 @@ TempoMap::solve_map_pulse (Metrics& imaginary, TempoSection* section, const doub bool TempoMap::solve_map_minute (Metrics& imaginary, MeterSection* section, const double& minute) { - /* disallow moving first meter past any subsequent one, and any movable meter before the first one */ + /* disallow moving first meter past any subsequent one, and any initial meter before the first one */ const MeterSection* other = &meter_section_at_minute_locked (imaginary, minute); - if ((!section->movable() && other->movable()) || (!other->movable() && section->movable() && other->minute() >= minute)) { + if ((section->initial() && !other->initial()) || (other->initial() && !section->initial() && other->minute() >= minute)) { return false; } - if (!section->movable()) { + if (section->initial()) { /* lock the first tempo to our first meter */ - if (!set_active_tempos (imaginary, section->frame_at_minute (minute))) { + if (!set_active_tempi (imaginary, frame_at_minute (minute))) { return false; } } @@ -2655,7 +2781,7 @@ TempoMap::solve_map_minute (Metrics& imaginary, MeterSection* section, const dou TempoSection* t; if ((*ii)->is_tempo()) { t = static_cast (*ii); - if ((t->locked_to_meter() || !t->movable()) && t->minute() == section->minute()) { + if (t->locked_to_meter() && t->frame() == section->frame()) { meter_locked_tempo = t; break; } @@ -2676,7 +2802,7 @@ TempoMap::solve_map_minute (Metrics& imaginary, MeterSection* section, const dou if (!(*i)->is_tempo()) { m = static_cast (*i); if (m == section){ - if (prev_m && section->movable()) { + if (prev_m && !section->initial()) { const double beats = (pulse_at_minute_locked (imaginary, minute) - prev_m->pulse()) * prev_m->note_divisor(); if (beats + prev_m->beat() < section->beat()) { /* set the section pulse according to its musical position, @@ -2710,7 +2836,7 @@ TempoMap::solve_map_minute (Metrics& imaginary, MeterSection* section, const dou } } else { /* all is ok. set section's locked tempo if allowed. - possibly disallowed if there is an adjacent audio-locked tempo. + possibly disallow if there is an adjacent audio-locked tempo. XX this check could possibly go. its never actually happened here. */ MeterSection* meter_copy = const_cast @@ -2738,7 +2864,7 @@ TempoMap::solve_map_minute (Metrics& imaginary, MeterSection* section, const dou } } } else { - /* not movable (first meter atm) */ + /* initial (first meter atm) */ tempo_copy->set_minute (minute); tempo_copy->set_pulse (0.0); @@ -2803,11 +2929,17 @@ TempoMap::solve_map_bbt (Metrics& imaginary, MeterSection* section, const BBT_Ti MeterSection* m; if (!(*i)->is_tempo()) { m = static_cast (*i); + + if (m == section) { + continue; + } + pair b_bbt; double new_pulse = 0.0; if (prev_m && m->bbt().bars > when.bars && !section_prev){ section_prev = prev_m; + const double beats = (when.bars - section_prev->bbt().bars) * section_prev->divisions_per_bar(); const double pulse = (beats / section_prev->note_divisor()) + section_prev->pulse(); pair b_bbt = make_pair (beats + section_prev->beat(), when); @@ -2816,7 +2948,6 @@ TempoMap::solve_map_bbt (Metrics& imaginary, MeterSection* section, const BBT_Ti section->set_pulse (pulse); section->set_minute (minute_at_pulse_locked (imaginary, pulse)); prev_m = section; - continue; } if (m->position_lock_style() == AudioTime) { @@ -2826,7 +2957,7 @@ TempoMap::solve_map_bbt (Metrics& imaginary, MeterSection* section, const BBT_Ti TempoSection* t; if ((*ii)->is_tempo()) { t = static_cast (*ii); - if ((t->locked_to_meter() || !t->movable()) && t->frame() == m->frame()) { + if (t->locked_to_meter() && t->frame() == m->frame()) { meter_locked_tempo = t; break; } @@ -2838,14 +2969,21 @@ TempoMap::solve_map_bbt (Metrics& imaginary, MeterSection* section, const BBT_Ti } if (prev_m) { - const double beats = ((m->bbt().bars - prev_m->bbt().bars) * prev_m->divisions_per_bar()); + double beats = ((m->bbt().bars - prev_m->bbt().bars) * prev_m->divisions_per_bar()); if (beats + prev_m->beat() != m->beat()) { /* tempo/ meter change caused a change in beat (bar). */ + + /* the user has requested that the previous section of music overlaps this one. + we have no choice but to change the bar number here, as being locked to audio means + we must stay where we are on the timeline. + */ + beats = m->beat() - prev_m->beat(); b_bbt = make_pair (beats + prev_m->beat() , BBT_Time ((beats / prev_m->divisions_per_bar()) + prev_m->bbt().bars, 1, 0)); new_pulse = prev_m->pulse() + (beats / prev_m->note_divisor()); - } else if (m->movable()) { + + } else if (!m->initial()) { b_bbt = make_pair (m->beat(), m->bbt()); new_pulse = prev_m->pulse() + (beats / prev_m->note_divisor()); } @@ -2901,15 +3039,13 @@ TempoMap::solve_map_bbt (Metrics& imaginary, MeterSection* section, const BBT_Ti * to section's equivalent in copy. */ TempoSection* -TempoMap::copy_metrics_and_point (const Metrics& metrics, Metrics& copy, TempoSection* section) +TempoMap::copy_metrics_and_point (const Metrics& metrics, Metrics& copy, TempoSection* section) const { TempoSection* ret = 0; for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) { - TempoSection* t; - MeterSection* m; if ((*i)->is_tempo()) { - t = static_cast (*i); + TempoSection const * const t = dynamic_cast (*i); if (t == section) { ret = new TempoSection (*t); copy.push_back (ret); @@ -2918,9 +3054,8 @@ TempoMap::copy_metrics_and_point (const Metrics& metrics, Metrics& copy, TempoSe TempoSection* cp = new TempoSection (*t); copy.push_back (cp); - } - if (!(*i)->is_tempo()) { - m = static_cast (*i); + } else { + MeterSection const * const m = dynamic_cast (*i); MeterSection* cp = new MeterSection (*m); copy.push_back (cp); } @@ -2930,21 +3065,17 @@ TempoMap::copy_metrics_and_point (const Metrics& metrics, Metrics& copy, TempoSe } MeterSection* -TempoMap::copy_metrics_and_point (const Metrics& metrics, Metrics& copy, MeterSection* section) +TempoMap::copy_metrics_and_point (const Metrics& metrics, Metrics& copy, MeterSection* section) const { MeterSection* ret = 0; for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) { - TempoSection* t; - MeterSection* m; if ((*i)->is_tempo()) { - t = static_cast (*i); + TempoSection const * const t = dynamic_cast (*i); TempoSection* cp = new TempoSection (*t); copy.push_back (cp); - } - - if (!(*i)->is_tempo()) { - m = static_cast (*i); + } else { + MeterSection const * const m = dynamic_cast (*i); if (m == section) { ret = new MeterSection (*m); copy.push_back (ret); @@ -3009,6 +3140,10 @@ TempoMap::predict_tempo_position (TempoSection* section, const BBT_Time& bbt) const double beat = beat_at_bbt_locked (future_map, bbt); + if (section->position_lock_style() == AudioTime) { + tempo_copy->set_position_lock_style (MusicTime); + } + if (solve_map_pulse (future_map, tempo_copy, pulse_at_beat_locked (future_map, beat))) { ret.first = tempo_copy->pulse(); ret.second = tempo_copy->frame(); @@ -3042,7 +3177,7 @@ TempoMap::predict_tempo_position (TempoSection* section, const BBT_Time& bbt) * if sub_num is zero, the musical position will be taken from the supplied frame. */ void -TempoMap::gui_move_tempo (TempoSection* ts, const framepos_t& frame, const int& sub_num) +TempoMap::gui_set_tempo_position (TempoSection* ts, const framepos_t frame, const int& sub_num) { Metrics future_map; @@ -3071,29 +3206,24 @@ TempoMap::gui_move_tempo (TempoSection* ts, const framepos_t& frame, const int& Glib::Threads::RWLock::WriterLock lm (lock); TempoSection* tempo_copy = copy_metrics_and_point (_metrics, future_map, ts); - if (solve_map_minute (future_map, tempo_copy, minute_at_frame (frame))) { - if (sub_num != 0) { - /* We're moving the object that defines the grid while snapping to it... - * Placing the ts at the beat corresponding to the requested frame may shift the - * grid in such a way that the mouse is left hovering over a completerly different division, - * causing jittering when the mouse next moves (esp. large tempo deltas). - * To avoid this, place the ts at the requested frame in a dummy map - * then find the closest beat subdivision to that frame in the dummy. - * This alters the snap behaviour slightly in that we snap to beat divisions - * in the future map rather than the existing one. - */ - const double beat = exact_beat_at_frame_locked (future_map, frame, sub_num); - const double pulse = pulse_at_beat_locked (future_map, beat); - - if (solve_map_pulse (future_map, tempo_copy, pulse)) { - /* snapping to a grid. force MusicTime temporarily. */ - ts->set_position_lock_style (MusicTime); - solve_map_pulse (_metrics, ts, pulse); - ts->set_position_lock_style (AudioTime); - recompute_meters (_metrics); - } - } else { + if (sub_num != 0) { + /* We're moving the object that defines the grid while snapping to it... + * Placing the ts at the beat corresponding to the requested frame may shift the + * grid in such a way that the mouse is left hovering over a completerly different division, + * causing jittering when the mouse next moves (esp. large tempo deltas). + * We fudge around this by doing this in the musical domain and then swapping back for the recompute. + */ + const double qn = exact_qn_at_frame_locked (_metrics, frame, sub_num); + tempo_copy->set_position_lock_style (MusicTime); + if (solve_map_pulse (future_map, tempo_copy, qn / 4.0)) { + ts->set_position_lock_style (MusicTime); + solve_map_pulse (_metrics, ts, qn / 4.0); + ts->set_position_lock_style (AudioTime); + recompute_meters (_metrics); + } + } else { + if (solve_map_minute (future_map, tempo_copy, minute_at_frame (frame))) { solve_map_minute (_metrics, ts, minute_at_frame (frame)); recompute_meters (_metrics); } @@ -3107,7 +3237,7 @@ TempoMap::gui_move_tempo (TempoSection* ts, const framepos_t& frame, const int& ++d; } - MetricPositionChanged (); // Emit Signal + MetricPositionChanged (PropertyChange ()); // Emit Signal } /** moves a MeterSection to a specified position. @@ -3119,7 +3249,7 @@ TempoMap::gui_move_tempo (TempoSection* ts, const framepos_t& frame, const int& * leaving the meter position unchanged. */ void -TempoMap::gui_move_meter (MeterSection* ms, const framepos_t& frame) +TempoMap::gui_set_meter_position (MeterSection* ms, const framepos_t frame) { Metrics future_map; @@ -3155,7 +3285,7 @@ TempoMap::gui_move_meter (MeterSection* ms, const framepos_t& frame) ++d; } - MetricPositionChanged (); // Emit Signal + MetricPositionChanged (PropertyChange ()); // Emit Signal } bool @@ -3166,11 +3296,40 @@ TempoMap::gui_change_tempo (TempoSection* ts, const Tempo& bpm) { Glib::Threads::RWLock::WriterLock lm (lock); TempoSection* tempo_copy = copy_metrics_and_point (_metrics, future_map, ts); - tempo_copy->set_note_types_per_minute (bpm.note_types_per_minute()); + + if (tempo_copy->type() == TempoSection::Constant) { + tempo_copy->set_end_note_types_per_minute (bpm.note_types_per_minute()); + tempo_copy->set_note_types_per_minute (bpm.note_types_per_minute()); + } else { + tempo_copy->set_note_types_per_minute (bpm.note_types_per_minute()); + tempo_copy->set_end_note_types_per_minute (bpm.end_note_types_per_minute()); + } + + if (ts->clamped()) { + TempoSection* prev = 0; + if ((prev = previous_tempo_section_locked (future_map, tempo_copy)) != 0) { + prev->set_end_note_types_per_minute (tempo_copy->note_types_per_minute()); + } + } + recompute_tempi (future_map); if (check_solved (future_map)) { - ts->set_note_types_per_minute (bpm.note_types_per_minute()); + if (ts->type() == TempoSection::Constant) { + ts->set_end_note_types_per_minute (bpm.note_types_per_minute()); + ts->set_note_types_per_minute (bpm.note_types_per_minute()); + } else { + ts->set_end_note_types_per_minute (bpm.end_note_types_per_minute()); + ts->set_note_types_per_minute (bpm.note_types_per_minute()); + } + + if (ts->clamped()) { + TempoSection* prev = 0; + if ((prev = previous_tempo_section_locked (_metrics, ts)) != 0) { + prev->set_end_note_types_per_minute (ts->note_types_per_minute()); + } + } + recompute_map (_metrics); can_solve = true; } @@ -3182,13 +3341,14 @@ TempoMap::gui_change_tempo (TempoSection* ts, const Tempo& bpm) ++d; } if (can_solve) { - MetricPositionChanged (); // Emit Signal + MetricPositionChanged (PropertyChange ()); // Emit Signal } + return can_solve; } void -TempoMap::gui_dilate_tempo (TempoSection* ts, const framepos_t& frame, const framepos_t& end_frame) +TempoMap::gui_stretch_tempo (TempoSection* ts, const framepos_t frame, const framepos_t end_frame, const double start_qnote, const double end_qnote) { /* Ts (future prev_t) Tnext @@ -3207,194 +3367,437 @@ TempoMap::gui_dilate_tempo (TempoSection* ts, const framepos_t& frame, const fra return; } - TempoSection* prev_t = copy_metrics_and_point (_metrics, future_map, ts); - TempoSection* prev_to_prev_t = 0; - const frameoffset_t fr_off = end_frame - frame; + TempoSection* ts_copy = copy_metrics_and_point (_metrics, future_map, ts); - if (prev_t && prev_t->pulse() > 0.0) { - prev_to_prev_t = const_cast(&tempo_section_at_minute_locked (future_map, minute_at_frame (prev_t->frame() - 1))); + if (!ts_copy) { + return; } - TempoSection* next_t = 0; - for (Metrics::iterator i = future_map.begin(); i != future_map.end(); ++i) { - TempoSection* t = 0; - if ((*i)->is_tempo()) { - t = static_cast (*i); - if (t->frame() > ts->frame()) { - next_t = t; - break; - } - } - } /* minimum allowed measurement distance in frames */ - const framepos_t min_dframe = 2; - - /* the change in frames is the result of changing the slope of at most 2 previous tempo sections. - constant to constant is straightforward, as the tempo prev to prev_t has constant slope. - */ - double contribution = 0.0; - - if (next_t && prev_to_prev_t && prev_to_prev_t->type() == TempoSection::Ramp) { - contribution = (prev_t->frame() - prev_to_prev_t->frame()) / (double) (next_t->frame() - prev_to_prev_t->frame()); - } - - const frameoffset_t prev_t_frame_contribution = fr_off - (contribution * (double) fr_off); - - const double start_pulse = prev_t->pulse_at_minute (minute_at_frame (frame)); - const double end_pulse = prev_t->pulse_at_minute (minute_at_frame (end_frame)); + framepos_t const min_dframe = 2; double new_bpm; + if (ts_copy->clamped()) { + TempoSection* next_t = next_tempo_section_locked (future_map, ts_copy); + TempoSection* prev_to_ts_copy = previous_tempo_section_locked (future_map, ts_copy); + /* the change in frames is the result of changing the slope of at most 2 previous tempo sections. + constant to constant is straightforward, as the tempo prev to ts_copy has constant slope. + */ double contribution = 0.0; + if (next_t && prev_to_ts_copy && prev_to_ts_copy->type() == TempoSection::Ramp) { + contribution = (ts_copy->pulse() - prev_to_ts_copy->pulse()) / (double) (next_t->pulse() - prev_to_ts_copy->pulse()); + } + framepos_t const fr_off = end_frame - frame; + frameoffset_t const ts_copy_frame_contribution = fr_off - (contribution * (double) fr_off); - if (prev_t->type() == TempoSection::Constant || prev_t->c_func() == 0.0) { - - if (prev_t->position_lock_style() == MusicTime) { - if (prev_to_prev_t && prev_to_prev_t->type() == TempoSection::Ramp) { - if (frame > prev_to_prev_t->frame() + min_dframe && (frame + prev_t_frame_contribution) > prev_to_prev_t->frame() + min_dframe) { - - new_bpm = prev_t->note_types_per_minute() * ((frame - prev_to_prev_t->frame()) - / (double) ((frame + prev_t_frame_contribution) - prev_to_prev_t->frame())); - } else { - new_bpm = prev_t->note_types_per_minute(); - } - } else { - /* prev to prev is irrelevant */ - - if (start_pulse > prev_t->pulse() && end_pulse > prev_t->pulse()) { - new_bpm = prev_t->note_types_per_minute() * ((start_pulse - prev_t->pulse()) / (end_pulse - prev_t->pulse())); - } else { - new_bpm = prev_t->note_types_per_minute(); - } - } + if (frame > prev_to_ts_copy->frame() + min_dframe && (frame + ts_copy_frame_contribution) > prev_to_ts_copy->frame() + min_dframe) { + new_bpm = ts_copy->note_types_per_minute() * ((start_qnote - (prev_to_ts_copy->pulse() * 4.0)) + / (end_qnote - (prev_to_ts_copy->pulse() * 4.0))); } else { - /* AudioTime */ - if (prev_to_prev_t && prev_to_prev_t->type() == TempoSection::Ramp) { - if (frame > prev_to_prev_t->frame() + min_dframe && end_frame > prev_to_prev_t->frame() + min_dframe) { - - new_bpm = prev_t->note_types_per_minute() * ((frame - prev_to_prev_t->frame()) - / (double) ((end_frame) - prev_to_prev_t->frame())); - } else { - new_bpm = prev_t->note_types_per_minute(); - } - } else { - /* prev_to_prev_t is irrelevant */ - - if (frame > prev_t->frame() + min_dframe && end_frame > prev_t->frame() + min_dframe) { - new_bpm = prev_t->note_types_per_minute() * ((frame - prev_t->frame()) / (double) (end_frame - prev_t->frame())); - } else { - new_bpm = prev_t->note_types_per_minute(); - } - } + new_bpm = ts_copy->note_types_per_minute(); } } else { + if (frame > ts_copy->frame() + min_dframe && end_frame > ts_copy->frame() + min_dframe) { - double frame_ratio = 1.0; - double pulse_ratio = 1.0; - const double pulse_pos = frame; - - if (prev_to_prev_t) { - if (pulse_pos > prev_to_prev_t->frame() + min_dframe && (pulse_pos - fr_off) > prev_to_prev_t->frame() + min_dframe) { - frame_ratio = (((pulse_pos - fr_off) - prev_to_prev_t->frame()) / (double) ((pulse_pos) - prev_to_prev_t->frame())); - } - if (end_pulse > prev_to_prev_t->pulse() && start_pulse > prev_to_prev_t->pulse()) { - pulse_ratio = ((start_pulse - prev_to_prev_t->pulse()) / (end_pulse - prev_to_prev_t->pulse())); - } + new_bpm = ts_copy->note_types_per_minute() * ((frame - ts_copy->frame()) + / (double) (end_frame - ts_copy->frame())); } else { - if (pulse_pos > prev_t->frame() + min_dframe && (pulse_pos - fr_off) > prev_t->frame() + min_dframe) { - frame_ratio = (((pulse_pos - fr_off) - prev_t->frame()) / (double) ((pulse_pos) - prev_t->frame())); - } - pulse_ratio = (start_pulse / end_pulse); + new_bpm = ts_copy->note_types_per_minute(); } - new_bpm = prev_t->note_types_per_minute() * (pulse_ratio * frame_ratio); - } + new_bpm = min (new_bpm, (double) 1000.0); + } /* don't clamp and proceed here. testing has revealed that this can go negative, which is an entirely different thing to just being too low. */ + if (new_bpm < 0.5) { - return; + goto out; } - new_bpm = min (new_bpm, (double) 1000.0); - prev_t->set_note_types_per_minute (new_bpm); + + ts_copy->set_note_types_per_minute (new_bpm); + + if (ts_copy->clamped()) { + TempoSection* prev = 0; + if ((prev = previous_tempo_section_locked (future_map, ts_copy)) != 0) { + prev->set_end_note_types_per_minute (ts_copy->note_types_per_minute()); + } + } + recompute_tempi (future_map); recompute_meters (future_map); if (check_solved (future_map)) { ts->set_note_types_per_minute (new_bpm); + + if (ts->clamped()) { + TempoSection* prev = 0; + if ((prev = previous_tempo_section_locked (_metrics, ts)) != 0) { + prev->set_end_note_types_per_minute (ts->note_types_per_minute()); + } + } + recompute_tempi (_metrics); recompute_meters (_metrics); } } + +out: Metrics::const_iterator d = future_map.begin(); while (d != future_map.end()) { delete (*d); ++d; } + MetricPositionChanged (PropertyChange ()); // Emit Signal - MetricPositionChanged (); // Emit Signal -} -/** Returns the exact bbt-based beat corresponding to the bar, beat or quarter note subdivision nearest to - * the supplied frame, possibly returning a negative value. - * - * @param frame The session frame position. - * @param sub_num The subdivision to use when rounding the beat. - * A value of -1 indicates rounding to BBT bar. 1 indicates rounding to BBT beats. - * Positive integers indicate quarter note (non BBT) divisions. - * 0 indicates that the returned beat should not be rounded (equivalent to quarter_note_at_frame()). - * @return The beat position of the supplied frame. - * - * when working to a musical grid, the use of sub_nom indicates that - * the position should be interpreted musically. - * - * it effectively snaps to meter bars, meter beats or quarter note divisions - * (as per current gui convention) and returns a musical position independent of frame rate. - * - * If the supplied frame lies before the first meter, the return will be negative, - * in which case the returned beat uses the first meter (for BBT subdivisions) and - * the continuation of the tempo curve (backwards). - * - * This function is sensitive to tempo and meter. - */ -double -TempoMap::exact_beat_at_frame (const framepos_t& frame, const int32_t sub_num) +} +void +TempoMap::gui_stretch_tempo_end (TempoSection* ts, const framepos_t frame, const framepos_t end_frame) { - Glib::Threads::RWLock::ReaderLock lm (lock); + /* + Ts (future prev_t) Tnext + | | + | [drag^] | + |----------|---------- + e_f qn_beats(frame) + */ - return exact_beat_at_frame_locked (_metrics, frame, sub_num); -} + Metrics future_map; -double -TempoMap::exact_beat_at_frame_locked (const Metrics& metrics, const framepos_t& frame, const int32_t divisions) -{ - return beat_at_pulse_locked (_metrics, exact_qn_at_frame_locked (metrics, frame, divisions) / 4.0); -} + { + Glib::Threads::RWLock::WriterLock lm (lock); -/** Returns the exact quarter note corresponding to the bar, beat or quarter note subdivision nearest to - * the supplied frame, possibly returning a negative value. - * - * @param frame The session frame position. - * @param sub_num The subdivision to use when rounding the quarter note. - * A value of -1 indicates rounding to BBT bar. 1 indicates rounding to BBT beats. - * Positive integers indicate quarter note (non BBT) divisions. - * 0 indicates that the returned quarter note should not be rounded (equivalent to quarter_note_at_frame()). - * @return The quarter note position of the supplied frame. - * - * When working to a musical grid, the use of sub_nom indicates that - * the frame position should be interpreted musically. - * - * it effectively snaps to meter bars, meter beats or quarter note divisions - * (as per current gui convention) and returns a musical position independent of frame rate. - * - * If the supplied frame lies before the first meter, the return will be negative, - * in which case the returned quarter note uses the first meter (for BBT subdivisions) and - * the continuation of the tempo curve (backwards). - * - * This function is tempo-sensitive. + if (!ts) { + return; + } + + TempoSection* prev_t = copy_metrics_and_point (_metrics, future_map, ts); + + if (!prev_t) { + return; + } + + /* minimum allowed measurement distance in frames */ + framepos_t const min_dframe = 2; + double new_bpm; + + if (frame > prev_t->frame() + min_dframe && end_frame > prev_t->frame() + min_dframe) { + new_bpm = prev_t->end_note_types_per_minute() * ((prev_t->frame() - frame) + / (double) (prev_t->frame() - end_frame)); + } else { + new_bpm = prev_t->end_note_types_per_minute(); + } + + new_bpm = min (new_bpm, (double) 1000.0); + + if (new_bpm < 0.5) { + goto out; + } + + prev_t->set_end_note_types_per_minute (new_bpm); + + TempoSection* next = 0; + if ((next = next_tempo_section_locked (future_map, prev_t)) != 0) { + if (next->clamped()) { + next->set_note_types_per_minute (prev_t->end_note_types_per_minute()); + } + } + + recompute_tempi (future_map); + recompute_meters (future_map); + + if (check_solved (future_map)) { + ts->set_end_note_types_per_minute (new_bpm); + + TempoSection* true_next = 0; + if ((true_next = next_tempo_section_locked (_metrics, ts)) != 0) { + if (true_next->clamped()) { + true_next->set_note_types_per_minute (ts->end_note_types_per_minute()); + } + } + + recompute_tempi (_metrics); + recompute_meters (_metrics); + } + } + + +out: + Metrics::const_iterator d = future_map.begin(); + while (d != future_map.end()) { + delete (*d); + ++d; + } + + MetricPositionChanged (PropertyChange ()); // Emit Signal +} + +bool +TempoMap::gui_twist_tempi (TempoSection* ts, const Tempo& bpm, const framepos_t frame, const framepos_t end_frame) +{ + TempoSection* next_t = 0; + TempoSection* next_to_next_t = 0; + Metrics future_map; + bool can_solve = false; + + /* minimum allowed measurement distance in frames */ + framepos_t const min_dframe = 2; + + { + Glib::Threads::RWLock::WriterLock lm (lock); + if (!ts) { + return false; + } + + TempoSection* tempo_copy = copy_metrics_and_point (_metrics, future_map, ts); + TempoSection* prev_to_prev_t = 0; + const frameoffset_t fr_off = end_frame - frame; + + if (!tempo_copy) { + return false; + } + + if (tempo_copy->pulse() > 0.0) { + prev_to_prev_t = const_cast(&tempo_section_at_minute_locked (future_map, minute_at_frame (tempo_copy->frame() - 1))); + } + + for (Metrics::const_iterator i = future_map.begin(); i != future_map.end(); ++i) { + if ((*i)->is_tempo() && (*i)->minute() > tempo_copy->minute()) { + next_t = static_cast (*i); + break; + } + } + + if (!next_t) { + return false; + } + + for (Metrics::const_iterator i = future_map.begin(); i != future_map.end(); ++i) { + if ((*i)->is_tempo() && (*i)->minute() > next_t->minute()) { + next_to_next_t = static_cast (*i); + break; + } + } + + if (!next_to_next_t) { + return false; + } + + double prev_contribution = 0.0; + + if (next_t && prev_to_prev_t && prev_to_prev_t->type() == TempoSection::Ramp) { + prev_contribution = (tempo_copy->frame() - prev_to_prev_t->frame()) / (double) (next_t->frame() - prev_to_prev_t->frame()); + } + + const frameoffset_t tempo_copy_frame_contribution = fr_off - (prev_contribution * (double) fr_off); + + + framepos_t old_tc_minute = tempo_copy->minute(); + double old_next_minute = next_t->minute(); + double old_next_to_next_minute = next_to_next_t->minute(); + + double new_bpm; + double new_next_bpm; + double new_copy_end_bpm; + + if (frame > tempo_copy->frame() + min_dframe && (frame + tempo_copy_frame_contribution) > tempo_copy->frame() + min_dframe) { + new_bpm = tempo_copy->note_types_per_minute() * ((frame - tempo_copy->frame()) + / (double) (end_frame - tempo_copy->frame())); + } else { + new_bpm = tempo_copy->note_types_per_minute(); + } + + /* don't clamp and proceed here. + testing has revealed that this can go negative, + which is an entirely different thing to just being too low. + */ + if (new_bpm < 0.5) { + return false; + } + + new_bpm = min (new_bpm, (double) 1000.0); + + tempo_copy->set_note_types_per_minute (new_bpm); + if (tempo_copy->type() == TempoSection::Constant) { + tempo_copy->set_end_note_types_per_minute (new_bpm); + } + + recompute_tempi (future_map); + + if (check_solved (future_map)) { + + if (!next_t) { + return false; + } + + ts->set_note_types_per_minute (new_bpm); + if (ts->type() == TempoSection::Constant) { + ts->set_end_note_types_per_minute (new_bpm); + } + + recompute_map (_metrics); + + can_solve = true; + } + + if (next_t->type() == TempoSection::Constant || next_t->c() == 0.0) { + if (frame > tempo_copy->frame() + min_dframe && end_frame > tempo_copy->frame() + min_dframe) { + + new_next_bpm = next_t->note_types_per_minute() * ((next_to_next_t->minute() - old_next_minute) + / (double) ((old_next_to_next_minute) - old_next_minute)); + + } else { + new_next_bpm = next_t->note_types_per_minute(); + } + + next_t->set_note_types_per_minute (new_next_bpm); + recompute_tempi (future_map); + + if (check_solved (future_map)) { + for (Metrics::const_iterator i = _metrics.begin(); i != _metrics.end(); ++i) { + if ((*i)->is_tempo() && (*i)->minute() > ts->minute()) { + next_t = static_cast (*i); + break; + } + } + + if (!next_t) { + return false; + } + next_t->set_note_types_per_minute (new_next_bpm); + recompute_map (_metrics); + can_solve = true; + } + } else { + double next_frame_ratio = 1.0; + double copy_frame_ratio = 1.0; + + if (next_to_next_t) { + next_frame_ratio = (next_to_next_t->minute() - old_next_minute) / (old_next_to_next_minute - old_next_minute); + + copy_frame_ratio = ((old_tc_minute - next_t->minute()) / (double) (old_tc_minute - old_next_minute)); + } + + new_next_bpm = next_t->note_types_per_minute() * next_frame_ratio; + new_copy_end_bpm = tempo_copy->end_note_types_per_minute() * copy_frame_ratio; + + tempo_copy->set_end_note_types_per_minute (new_copy_end_bpm); + + if (next_t->clamped()) { + next_t->set_note_types_per_minute (new_copy_end_bpm); + } else { + next_t->set_note_types_per_minute (new_next_bpm); + } + + recompute_tempi (future_map); + + if (check_solved (future_map)) { + for (Metrics::const_iterator i = _metrics.begin(); i != _metrics.end(); ++i) { + if ((*i)->is_tempo() && (*i)->minute() > ts->minute()) { + next_t = static_cast (*i); + break; + } + } + + if (!next_t) { + return false; + } + + if (next_t->clamped()) { + next_t->set_note_types_per_minute (new_copy_end_bpm); + } else { + next_t->set_note_types_per_minute (new_next_bpm); + } + + ts->set_end_note_types_per_minute (new_copy_end_bpm); + recompute_map (_metrics); + can_solve = true; + } + } + } + + Metrics::const_iterator d = future_map.begin(); + while (d != future_map.end()) { + delete (*d); + ++d; + } + + MetricPositionChanged (PropertyChange ()); // Emit Signal + + return can_solve; +} + +/** Returns the frame position of the musical position zero */ +framepos_t +TempoMap::music_origin () +{ + Glib::Threads::RWLock::ReaderLock lm (lock); + + return first_tempo().frame(); +} + +/** Returns the exact bbt-based beat corresponding to the bar, beat or quarter note subdivision nearest to + * the supplied frame, possibly returning a negative value. + * + * @param frame The session frame position. + * @param sub_num The subdivision to use when rounding the beat. + * A value of -1 indicates rounding to BBT bar. 1 indicates rounding to BBT beats. + * Positive integers indicate quarter note (non BBT) divisions. + * 0 indicates that the returned beat should not be rounded (equivalent to quarter_note_at_frame()). + * @return The beat position of the supplied frame. + * + * when working to a musical grid, the use of sub_nom indicates that + * the position should be interpreted musically. + * + * it effectively snaps to meter bars, meter beats or quarter note divisions + * (as per current gui convention) and returns a musical position independent of frame rate. + * + * If the supplied frame lies before the first meter, the return will be negative, + * in which case the returned beat uses the first meter (for BBT subdivisions) and + * the continuation of the tempo curve (backwards). + * + * This function is sensitive to tempo and meter. */ double -TempoMap::exact_qn_at_frame (const framepos_t& frame, const int32_t sub_num) +TempoMap::exact_beat_at_frame (const framepos_t frame, const int32_t sub_num) const +{ + Glib::Threads::RWLock::ReaderLock lm (lock); + + return exact_beat_at_frame_locked (_metrics, frame, sub_num); +} + +double +TempoMap::exact_beat_at_frame_locked (const Metrics& metrics, const framepos_t frame, const int32_t divisions) const +{ + return beat_at_pulse_locked (_metrics, exact_qn_at_frame_locked (metrics, frame, divisions) / 4.0); +} + +/** Returns the exact quarter note corresponding to the bar, beat or quarter note subdivision nearest to + * the supplied frame, possibly returning a negative value. + * + * @param frame The session frame position. + * @param sub_num The subdivision to use when rounding the quarter note. + * A value of -1 indicates rounding to BBT bar. 1 indicates rounding to BBT beats. + * Positive integers indicate quarter note (non BBT) divisions. + * 0 indicates that the returned quarter note should not be rounded (equivalent to quarter_note_at_frame()). + * @return The quarter note position of the supplied frame. + * + * When working to a musical grid, the use of sub_nom indicates that + * the frame position should be interpreted musically. + * + * it effectively snaps to meter bars, meter beats or quarter note divisions + * (as per current gui convention) and returns a musical position independent of frame rate. + * + * If the supplied frame lies before the first meter, the return will be negative, + * in which case the returned quarter note uses the first meter (for BBT subdivisions) and + * the continuation of the tempo curve (backwards). + * + * This function is tempo-sensitive. + */ +double +TempoMap::exact_qn_at_frame (const framepos_t frame, const int32_t sub_num) const { Glib::Threads::RWLock::ReaderLock lm (lock); @@ -3402,15 +3805,15 @@ TempoMap::exact_qn_at_frame (const framepos_t& frame, const int32_t sub_num) } double -TempoMap::exact_qn_at_frame_locked (const Metrics& metrics, const framepos_t& frame, const int32_t sub_num) +TempoMap::exact_qn_at_frame_locked (const Metrics& metrics, const framepos_t frame, const int32_t sub_num) const { - double qn = quarter_note_at_minute_locked (metrics, minute_at_frame (frame)); + double qn = pulse_at_minute_locked (metrics, minute_at_frame (frame)) * 4.0; if (sub_num > 1) { qn = floor (qn) + (floor (((qn - floor (qn)) * (double) sub_num) + 0.5) / sub_num); } else if (sub_num == 1) { /* the gui requested exact musical (BBT) beat */ - qn = quarter_note_at_beat_locked (metrics, floor (beat_at_minute_locked (metrics, minute_at_frame (frame)) + 0.5)); + qn = pulse_at_beat_locked (metrics, (floor (beat_at_minute_locked (metrics, minute_at_frame (frame)) + 0.5))) * 4.0; } else if (sub_num == -1) { /* snap to bar */ Timecode::BBT_Time bbt = bbt_at_pulse_locked (metrics, qn / 4.0); @@ -3443,7 +3846,7 @@ TempoMap::bbt_duration_at (framepos_t pos, const BBT_Time& bbt, int dir) Glib::Threads::RWLock::ReaderLock lm (lock); BBT_Time pos_bbt = bbt_at_minute_locked (_metrics, minute_at_frame (pos)); - const framecnt_t offset = frame_at_minute (minute_at_bbt_locked (_metrics, pos_bbt)); + const double divisions = meter_section_at_minute_locked (_metrics, minute_at_frame (pos)).divisions_per_bar(); if (dir > 0) { @@ -3460,149 +3863,69 @@ TempoMap::bbt_duration_at (framepos_t pos, const BBT_Time& bbt, int dir) pos_bbt.bars += 1; pos_bbt.beats -= divisions; } + const framecnt_t pos_bbt_frame = frame_at_minute (minute_at_bbt_locked (_metrics, pos_bbt)); + + return pos_bbt_frame - pos; - return frame_at_minute (minute_at_bbt_locked (_metrics, pos_bbt)) - offset; } else { - pos_bbt.bars -= bbt.bars; + + if (pos_bbt.bars <= bbt.bars) { + pos_bbt.bars = 1; + } else { + pos_bbt.bars -= bbt.bars; + } if (pos_bbt.ticks < bbt.ticks) { - if (pos_bbt.beats == 1) { - pos_bbt.bars--; - pos_bbt.beats = divisions; + if (pos_bbt.bars > 1) { + if (pos_bbt.beats == 1) { + pos_bbt.bars--; + pos_bbt.beats = divisions; + } else { + pos_bbt.beats--; + } + pos_bbt.ticks = BBT_Time::ticks_per_beat - (bbt.ticks - pos_bbt.ticks); } else { - pos_bbt.beats--; + pos_bbt.beats = 1; + pos_bbt.ticks = 0; } - pos_bbt.ticks = BBT_Time::ticks_per_beat - (bbt.ticks - pos_bbt.ticks); } else { pos_bbt.ticks -= bbt.ticks; } if (pos_bbt.beats <= bbt.beats) { - pos_bbt.bars--; - pos_bbt.beats = divisions - (bbt.beats - pos_bbt.beats); + if (pos_bbt.bars > 1) { + pos_bbt.bars--; + pos_bbt.beats = divisions - (bbt.beats - pos_bbt.beats); + } else { + pos_bbt.beats = 1; + } } else { pos_bbt.beats -= bbt.beats; } - return offset - frame_at_minute (minute_at_bbt_locked (_metrics, pos_bbt)); + return pos - frame_at_minute (minute_at_bbt_locked (_metrics, pos_bbt)); } return 0; } -framepos_t +MusicFrame TempoMap::round_to_bar (framepos_t fr, RoundMode dir) { return round_to_type (fr, dir, Bar); } -framepos_t +MusicFrame TempoMap::round_to_beat (framepos_t fr, RoundMode dir) { return round_to_type (fr, dir, Beat); } -framepos_t -TempoMap::round_to_beat_subdivision (framepos_t fr, int sub_num, RoundMode dir) -{ - Glib::Threads::RWLock::ReaderLock lm (lock); - uint32_t ticks = (uint32_t) floor (max (0.0, beat_at_minute_locked (_metrics, minute_at_frame (fr))) * BBT_Time::ticks_per_beat); - uint32_t beats = (uint32_t) floor (ticks / BBT_Time::ticks_per_beat); - uint32_t ticks_one_subdivisions_worth = (uint32_t) BBT_Time::ticks_per_beat / sub_num; - - ticks -= beats * BBT_Time::ticks_per_beat; - - if (dir > 0) { - /* round to next (or same iff dir == RoundUpMaybe) */ - - uint32_t mod = ticks % ticks_one_subdivisions_worth; - - if (mod == 0 && dir == RoundUpMaybe) { - /* right on the subdivision, which is fine, so do nothing */ - - } else if (mod == 0) { - /* right on the subdivision, so the difference is just the subdivision ticks */ - ticks += ticks_one_subdivisions_worth; - - } else { - /* not on subdivision, compute distance to next subdivision */ - - ticks += ticks_one_subdivisions_worth - mod; - } - - if (ticks >= BBT_Time::ticks_per_beat) { - ticks -= BBT_Time::ticks_per_beat; - } - } else if (dir < 0) { - - /* round to previous (or same iff dir == RoundDownMaybe) */ - - uint32_t difference = ticks % ticks_one_subdivisions_worth; - - if (difference == 0 && dir == RoundDownAlways) { - /* right on the subdivision, but force-rounding down, - so the difference is just the subdivision ticks */ - difference = ticks_one_subdivisions_worth; - } - - if (ticks < difference) { - ticks = BBT_Time::ticks_per_beat - ticks; - } else { - ticks -= difference; - } - - } else { - /* round to nearest */ - double rem; - - /* compute the distance to the previous and next subdivision */ - - if ((rem = fmod ((double) ticks, (double) ticks_one_subdivisions_worth)) > ticks_one_subdivisions_worth/2.0) { - - /* closer to the next subdivision, so shift forward */ - - ticks = lrint (ticks + (ticks_one_subdivisions_worth - rem)); - - DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("moved forward to %1\n", ticks)); - - if (ticks > BBT_Time::ticks_per_beat) { - ++beats; - ticks -= BBT_Time::ticks_per_beat; - DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("fold beat to %1\n", beats)); - } - - } else if (rem > 0) { - - /* closer to previous subdivision, so shift backward */ - - if (rem > ticks) { - if (beats == 0) { - /* can't go backwards past zero, so ... */ - return 0; - } - /* step back to previous beat */ - --beats; - ticks = lrint (BBT_Time::ticks_per_beat - rem); - DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("step back beat to %1\n", beats)); - } else { - ticks = lrint (ticks - rem); - DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("moved backward to %1\n", ticks)); - } - } else { - /* on the subdivision, do nothing */ - } - } - - const framepos_t ret_frame = frame_at_minute (minute_at_beat_locked (_metrics, beats + (ticks / BBT_Time::ticks_per_beat))); - - return ret_frame; -} - -framepos_t +MusicFrame TempoMap::round_to_quarter_note_subdivision (framepos_t fr, int sub_num, RoundMode dir) { Glib::Threads::RWLock::ReaderLock lm (lock); - uint32_t ticks = (uint32_t) floor (max (0.0, quarter_note_at_minute_locked (_metrics, minute_at_frame (fr))) * BBT_Time::ticks_per_beat); + uint32_t ticks = (uint32_t) floor (max (0.0, pulse_at_minute_locked (_metrics, minute_at_frame (fr))) * BBT_Time::ticks_per_beat * 4.0); uint32_t beats = (uint32_t) floor (ticks / BBT_Time::ticks_per_beat); uint32_t ticks_one_subdivisions_worth = (uint32_t) BBT_Time::ticks_per_beat / sub_num; @@ -3626,9 +3949,14 @@ TempoMap::round_to_quarter_note_subdivision (framepos_t fr, int sub_num, RoundMo ticks += ticks_one_subdivisions_worth - mod; } - if (ticks >= BBT_Time::ticks_per_beat) { - ticks -= BBT_Time::ticks_per_beat; - } +//NOTE: this code intentionally limits the rounding so we don't advance to the next beat. +// For the purposes of "jump-to-next-subdivision", we DO want to advance to the next beat. +// And since the "prev" direction DOES move beats, I assume this code is unintended. +// But I'm keeping it around, until we determine there are no terrible consequences. +// if (ticks >= BBT_Time::ticks_per_beat) { +// ticks -= BBT_Time::ticks_per_beat; +// } + } else if (dir < 0) { /* round to previous (or same iff dir == RoundDownMaybe) */ @@ -3674,7 +4002,7 @@ TempoMap::round_to_quarter_note_subdivision (framepos_t fr, int sub_num, RoundMo if (rem > ticks) { if (beats == 0) { /* can't go backwards past zero, so ... */ - return 0; + return MusicFrame (0, 0); } /* step back to previous beat */ --beats; @@ -3689,33 +4017,46 @@ TempoMap::round_to_quarter_note_subdivision (framepos_t fr, int sub_num, RoundMo } } - const framepos_t ret_frame = frame_at_minute (minute_at_quarter_note_locked (_metrics, beats + (ticks / BBT_Time::ticks_per_beat))); + MusicFrame ret (0, 0); + ret.frame = frame_at_minute (minute_at_pulse_locked (_metrics, (beats + (ticks / BBT_Time::ticks_per_beat)) / 4.0)); + ret.division = sub_num; - return ret_frame; + return ret; } -framepos_t +MusicFrame TempoMap::round_to_type (framepos_t frame, RoundMode dir, BBTPointType type) { Glib::Threads::RWLock::ReaderLock lm (lock); - - const double beat_at_framepos = max (0.0, beat_at_minute_locked (_metrics, minute_at_frame (frame))); + const double minute = minute_at_frame (frame); + const double beat_at_framepos = max (0.0, beat_at_minute_locked (_metrics, minute)); BBT_Time bbt (bbt_at_beat_locked (_metrics, beat_at_framepos)); + MusicFrame ret (0, 0); switch (type) { case Bar: + ret.division = -1; + if (dir < 0) { /* find bar previous to 'frame' */ + if (bbt.bars > 0) + --bbt.bars; bbt.beats = 1; bbt.ticks = 0; - return frame_at_minute (minute_at_bbt_locked (_metrics, bbt)); + + ret.frame = frame_at_minute (minute_at_bbt_locked (_metrics, bbt)); + + return ret; } else if (dir > 0) { /* find bar following 'frame' */ ++bbt.bars; bbt.beats = 1; bbt.ticks = 0; - return frame_at_minute (minute_at_bbt_locked (_metrics, bbt)); + + ret.frame = frame_at_minute (minute_at_bbt_locked (_metrics, bbt)); + + return ret; } else { /* true rounding: find nearest bar */ framepos_t raw_ft = frame_at_minute (minute_at_bbt_locked (_metrics, bbt)); @@ -3725,27 +4066,40 @@ TempoMap::round_to_type (framepos_t frame, RoundMode dir, BBTPointType type) ++bbt.bars; framepos_t next_ft = frame_at_minute (minute_at_bbt_locked (_metrics, bbt)); - if ((raw_ft - prev_ft) > (next_ft - prev_ft) / 2) { - return next_ft; + if ((raw_ft - prev_ft) > (next_ft - prev_ft) / 2) { + ret.frame = next_ft; + + return ret; } else { - return prev_ft; + --bbt.bars; + ret.frame = prev_ft; + + return ret; } } break; case Beat: + ret.division = 1; + if (dir < 0) { - return frame_at_minute (minute_at_beat_locked (_metrics, floor (beat_at_framepos))); + ret.frame = frame_at_minute (minute_at_beat_locked (_metrics, floor (beat_at_framepos))); + + return ret; } else if (dir > 0) { - return frame_at_minute (minute_at_beat_locked (_metrics, ceil (beat_at_framepos))); + ret.frame = frame_at_minute (minute_at_beat_locked (_metrics, ceil (beat_at_framepos))); + + return ret; } else { - return frame_at_minute (minute_at_beat_locked (_metrics, floor (beat_at_framepos + 0.5))); + ret.frame = frame_at_minute (minute_at_beat_locked (_metrics, floor (beat_at_framepos + 0.5))); + + return ret; } break; } - return 0; + return MusicFrame (0, 0); } void @@ -3766,10 +4120,11 @@ TempoMap::get_grid (vector& points, if (bar_mod == 0) { while (pos >= 0 && pos < upper) { pos = frame_at_minute (minute_at_beat_locked (_metrics, cnt)); - const TempoSection tempo = tempo_section_at_minute_locked (_metrics, minute_at_frame (pos)); const MeterSection meter = meter_section_at_minute_locked (_metrics, minute_at_frame (pos)); const BBT_Time bbt = bbt_at_beat_locked (_metrics, cnt); - points.push_back (BBTPoint (meter, tempo_at_minute_locked (_metrics, minute_at_frame (pos)), pos, bbt.bars, bbt.beats, tempo.c_func())); + const double qn = pulse_at_beat_locked (_metrics, cnt) * 4.0; + + points.push_back (BBTPoint (meter, tempo_at_minute_locked (_metrics, minute_at_frame (pos)), pos, bbt.bars, bbt.beats, qn)); ++cnt; } } else { @@ -3784,9 +4139,10 @@ TempoMap::get_grid (vector& points, while (pos >= 0 && pos < upper) { pos = frame_at_minute (minute_at_bbt_locked (_metrics, bbt)); - const TempoSection tempo = tempo_section_at_minute_locked (_metrics, minute_at_frame (pos)); const MeterSection meter = meter_section_at_minute_locked (_metrics, minute_at_frame (pos)); - points.push_back (BBTPoint (meter, tempo_at_minute_locked (_metrics, minute_at_frame (pos)), pos, bbt.bars, bbt.beats, tempo.c_func())); + const double qn = pulse_at_bbt_locked (_metrics, bbt) * 4.0; + + points.push_back (BBTPoint (meter, tempo_at_minute_locked (_metrics, minute_at_frame (pos)), pos, bbt.bars, bbt.beats, qn)); bbt.bars += bar_mod; } } @@ -3800,6 +4156,14 @@ TempoMap::tempo_section_at_frame (framepos_t frame) const return tempo_section_at_minute_locked (_metrics, minute_at_frame (frame)); } +TempoSection& +TempoMap::tempo_section_at_frame (framepos_t frame) +{ + Glib::Threads::RWLock::ReaderLock lm (lock); + + return tempo_section_at_minute_locked (_metrics, minute_at_frame (frame)); +} + const TempoSection& TempoMap::tempo_section_at_minute_locked (const Metrics& metrics, double minute) const { @@ -3829,7 +4193,35 @@ TempoMap::tempo_section_at_minute_locked (const Metrics& metrics, double minute) return *prev; } +TempoSection& +TempoMap::tempo_section_at_minute_locked (const Metrics& metrics, double minute) +{ + TempoSection* prev = 0; + + TempoSection* t; + + for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) { + + if ((*i)->is_tempo()) { + t = static_cast (*i); + if (!t->active()) { + continue; + } + if (prev && t->minute() > minute) { + break; + } + + prev = t; + } + } + + if (prev == 0) { + fatal << endmsg; + abort(); /*NOTREACHED*/ + } + return *prev; +} const TempoSection& TempoMap::tempo_section_at_beat_locked (const Metrics& metrics, const double& beat) const { @@ -3841,6 +4233,11 @@ TempoMap::tempo_section_at_beat_locked (const Metrics& metrics, const double& be for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) { if ((*i)->is_tempo()) { t = static_cast (*i); + + if (!t->active()) { + continue; + } + if (prev_t && ((t->pulse() - prev_m->pulse()) * prev_m->note_divisor()) + prev_m->beat() > beat) { break; } @@ -3851,11 +4248,97 @@ TempoMap::tempo_section_at_beat_locked (const Metrics& metrics, const double& be return *prev_t; } +TempoSection* +TempoMap::previous_tempo_section (TempoSection* ts) const +{ + Glib::Threads::RWLock::ReaderLock lm (lock); + + return previous_tempo_section_locked (_metrics, ts); + +} + +TempoSection* +TempoMap::previous_tempo_section_locked (const Metrics& metrics, TempoSection* ts) const +{ + if (!ts) { + return 0; + } + + TempoSection* prev = 0; + + for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) { + + if ((*i)->is_tempo()) { + TempoSection* t = static_cast (*i); + + if (!t->active()) { + continue; + } + + if (prev && t == ts) { + + return prev; + } + + prev = t; + } + } + + if (prev == 0) { + fatal << endmsg; + abort(); /*NOTREACHED*/ + } + + return 0; +} + +TempoSection* +TempoMap::next_tempo_section (TempoSection* ts) const +{ + Glib::Threads::RWLock::ReaderLock lm (lock); + + return next_tempo_section_locked (_metrics, ts); +} + +TempoSection* +TempoMap::next_tempo_section_locked (const Metrics& metrics, TempoSection* ts) const +{ + if (!ts) { + return 0; + } + + TempoSection* prev = 0; + + for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) { + + if ((*i)->is_tempo()) { + TempoSection* t = static_cast (*i); + + if (!t->active()) { + continue; + } + + if (prev && prev == ts) { + + return t; + } + + prev = t; + } + } + + if (prev == 0) { + fatal << endmsg; + abort(); /*NOTREACHED*/ + } + + return 0; +} /* don't use this to calculate length (the tempo is only correct for this frame). do that stuff based on the beat_at_frame and frame_at_beat api */ double -TempoMap::frames_per_quarter_note_at (const framepos_t& frame, const framecnt_t& sr) const +TempoMap::frames_per_quarter_note_at (const framepos_t frame, const framecnt_t sr) const { Glib::Threads::RWLock::ReaderLock lm (lock); @@ -3878,6 +4361,7 @@ TempoMap::frames_per_quarter_note_at (const framepos_t& frame, const framecnt_t& ts_at = t; } } + assert (ts_at); if (ts_after) { return (60.0 * _frame_rate) / ts_at->tempo_at_minute (minute_at_frame (frame)).quarter_notes_per_minute(); @@ -3960,13 +4444,14 @@ TempoMap::fix_legacy_session () { MeterSection* prev_m = 0; TempoSection* prev_t = 0; + bool have_initial_t = false; for (Metrics::iterator i = _metrics.begin(); i != _metrics.end(); ++i) { MeterSection* m; TempoSection* t; if ((m = dynamic_cast(*i)) != 0) { - if (!m->movable()) { + if (m->initial()) { pair bbt = make_pair (0.0, BBT_Time (1, 1, 0)); m->set_beat (bbt); m->set_pulse (0.0); @@ -3992,16 +4477,29 @@ TempoMap::fix_legacy_session () if (!t->active()) { continue; } + /* Ramp type never existed in the era of this tempo section */ + t->set_end_note_types_per_minute (t->note_types_per_minute()); - if (!t->movable()) { + if (t->initial()) { t->set_pulse (0.0); t->set_minute (0.0); t->set_position_lock_style (AudioTime); prev_t = t; + have_initial_t = true; continue; } if (prev_t) { + /* some 4.x sessions have no initial (non-movable) tempo. */ + if (!have_initial_t) { + prev_t->set_pulse (0.0); + prev_t->set_minute (0.0); + prev_t->set_position_lock_style (AudioTime); + prev_t->set_initial (true); + prev_t->set_locked_to_meter (true); + have_initial_t = true; + } + const double beat = ((t->legacy_bbt().bars - 1) * ((prev_m) ? prev_m->note_divisor() : 4.0)) + (t->legacy_bbt().beats - 1) + (t->legacy_bbt().ticks / BBT_Time::ticks_per_beat); @@ -4016,6 +4514,34 @@ TempoMap::fix_legacy_session () } } } +void +TempoMap::fix_legacy_end_session () +{ + TempoSection* prev_t = 0; + + for (Metrics::iterator i = _metrics.begin(); i != _metrics.end(); ++i) { + TempoSection* t; + + if ((t = dynamic_cast(*i)) != 0) { + + if (!t->active()) { + continue; + } + + if (prev_t) { + if (prev_t->end_note_types_per_minute() < 0.0) { + prev_t->set_end_note_types_per_minute (t->note_types_per_minute()); + } + } + + prev_t = t; + } + } + + if (prev_t) { + prev_t->set_end_note_types_per_minute (prev_t->note_types_per_minute()); + } +} XMLNode& TempoMap::get_state () @@ -4079,11 +4605,6 @@ TempoMap::set_state (const XMLNode& node, int /*version*/) } } - if (niter == nlist.end()) { - MetricSectionSorter cmp; - _metrics.sort (cmp); - } - /* check for legacy sessions where bbt was the base musical unit for tempo */ for (Metrics::const_iterator i = _metrics.begin(); i != _metrics.end(); ++i) { TempoSection* t; @@ -4092,10 +4613,19 @@ TempoMap::set_state (const XMLNode& node, int /*version*/) fix_legacy_session(); break; } - break; + + if (t->end_note_types_per_minute() < 0.0) { + fix_legacy_end_session(); + break; + } } } + if (niter == nlist.end()) { + MetricSectionSorter cmp; + _metrics.sort (cmp); + } + /* check for multiple tempo/meters at the same location, which ardour2 somehow allowed. */ @@ -4108,9 +4638,9 @@ TempoMap::set_state (const XMLNode& node, int /*version*/) TempoSection* ts; TempoSection* prev_t; if ((prev_m = dynamic_cast(*prev)) != 0 && (ms = dynamic_cast(*i)) != 0) { - if (prev_m->pulse() == ms->pulse()) { - cerr << string_compose (_("Multiple meter definitions found at %1"), prev_m->pulse()) << endmsg; - error << string_compose (_("Multiple meter definitions found at %1"), prev_m->pulse()) << endmsg; + if (prev_m->beat() == ms->beat()) { + cerr << string_compose (_("Multiple meter definitions found at %1"), prev_m->beat()) << endmsg; + error << string_compose (_("Multiple meter definitions found at %1"), prev_m->beat()) << endmsg; return -1; } } else if ((prev_t = dynamic_cast(*prev)) != 0 && (ts = dynamic_cast(*i)) != 0) { @@ -4140,35 +4670,36 @@ TempoMap::set_state (const XMLNode& node, int /*version*/) } void -TempoMap::dump (const Metrics& metrics, std::ostream& o) const +TempoMap::dump (std::ostream& o) const { Glib::Threads::RWLock::ReaderLock lm (lock, Glib::Threads::TRY_LOCK); const MeterSection* m; const TempoSection* t; const TempoSection* prev_t = 0; - for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) { + for (Metrics::const_iterator i = _metrics.begin(); i != _metrics.end(); ++i) { if ((t = dynamic_cast(*i)) != 0) { - o << "Tempo @ " << *i << t->note_types_per_minute() << " BPM (pulse = 1/" << t->note_type() + o << "Tempo @ " << *i << " start : " << t->note_types_per_minute() << " end : " << t->end_note_types_per_minute() << " BPM (pulse = 1/" << t->note_type() << " type= " << enum_2_string (t->type()) << ") " << " at pulse= " << t->pulse() - << " minute= " << t->minute() << " frame= " << t->frame() << " (movable? " << t->movable() << ')' + << " minute= " << t->minute() << " frame= " << t->frame() << " (initial? " << t->initial() << ')' << " pos lock: " << enum_2_string (t->position_lock_style()) << std::endl; if (prev_t) { - o << std::setprecision (17) << " current : " << t->note_types_per_minute() + o << " current start : " << t->note_types_per_minute() + << " current end : " << t->end_note_types_per_minute() << " | " << t->pulse() << " | " << t->frame() << " | " << t->minute() << std::endl; o << " previous : " << prev_t->note_types_per_minute() << " | " << prev_t->pulse() << " | " << prev_t->frame() << " | " << prev_t->minute() << std::endl; o << " calculated : " << prev_t->tempo_at_pulse (t->pulse()) - << " | " << prev_t->pulse_at_ntpm (t->note_types_per_minute(), t->minute()) - << " | " << frame_at_minute (prev_t->minute_at_ntpm (t->note_types_per_minute(), t->pulse())) - << " | " << prev_t->minute_at_ntpm (t->note_types_per_minute(), t->pulse()) << std::endl; + << " | " << prev_t->pulse_at_ntpm (prev_t->end_note_types_per_minute(), t->minute()) + << " | " << frame_at_minute (prev_t->minute_at_ntpm (prev_t->end_note_types_per_minute(), t->pulse())) + << " | " << prev_t->minute_at_ntpm (prev_t->end_note_types_per_minute(), t->pulse()) << std::endl; } prev_t = t; } else if ((m = dynamic_cast(*i)) != 0) { o << "Meter @ " << *i << ' ' << m->divisions_per_bar() << '/' << m->note_divisor() << " at " << m->bbt() << " frame= " << m->frame() << " pulse: " << m->pulse() << " beat : " << m->beat() - << " pos lock: " << enum_2_string (m->position_lock_style()) << " (movable? " << m->movable() << ')' << endl; + << " pos lock: " << enum_2_string (m->position_lock_style()) << " (initial? " << m->initial() << ')' << endl; } } o << "------" << std::endl; @@ -4208,16 +4739,16 @@ void TempoMap::insert_time (framepos_t where, framecnt_t amount) { for (Metrics::reverse_iterator i = _metrics.rbegin(); i != _metrics.rend(); ++i) { - if ((*i)->frame() >= where && (*i)->movable ()) { + if ((*i)->frame() >= where && !(*i)->initial ()) { MeterSection* ms; TempoSection* ts; if ((ms = dynamic_cast (*i)) != 0) { - gui_move_meter (ms, (*i)->frame() + amount); + gui_set_meter_position (ms, (*i)->frame() + amount); } if ((ts = dynamic_cast (*i)) != 0) { - gui_move_tempo (ts, (*i)->frame() + amount, 0); + gui_set_tempo_position (ts, (*i)->frame() + amount, 0); } } } @@ -4290,11 +4821,12 @@ TempoMap::remove_time (framepos_t where, framecnt_t amount) * pos can be -ve, if required. */ framepos_t -TempoMap::framepos_plus_qn (framepos_t frame, Evoral::Beats quarter_note) const +TempoMap::framepos_plus_qn (framepos_t frame, Evoral::Beats beats) const { Glib::Threads::RWLock::ReaderLock lm (lock); + const double frame_qn = pulse_at_minute_locked (_metrics, minute_at_frame (frame)) * 4.0; - return frame_at_minute (minute_at_quarter_note_locked (_metrics, quarter_note_at_minute_locked (_metrics, minute_at_frame (frame)) + quarter_note.to_double())); + return frame_at_minute (minute_at_pulse_locked (_metrics, (frame_qn + beats.to_double()) / 4.0)); } framepos_t @@ -4329,7 +4861,7 @@ TempoMap::framewalk_to_qn (framepos_t pos, framecnt_t distance) const { Glib::Threads::RWLock::ReaderLock lm (lock); - return Evoral::Beats (quarter_note_at_minute_locked (_metrics, minute_at_frame (pos + distance)) - quarter_note_at_minute_locked (_metrics, minute_at_frame (pos))); + return Evoral::Beats (quarter_notes_between_frames_locked (_metrics, pos, pos + distance)); } struct bbtcmp {