From e3c67bceb864adc10764e901b87ad8b6677caebd Mon Sep 17 00:00:00 2001 From: Paul Davis Date: Tue, 14 Dec 2010 20:03:40 +0000 Subject: implement methods in TempoMap for walking a given distance along a tempo map and returning the resulting time; add a new property, _length_beats, to MidiRegion; use previously mentioned methods to keep _length_beats up to date as regions are moved AND as tempo map changes occur git-svn-id: svn://localhost/ardour2/branches/3.0@8274 d708f5d6-7413-0410-9779-e7cbd77b26cf --- libs/ardour/ardour/midi_region.h | 5 + libs/ardour/ardour/region.h | 1 + libs/ardour/ardour/tempo.h | 7 +- libs/ardour/beats_frames_converter.cc | 20 ++-- libs/ardour/globals.cc | 2 + libs/ardour/midi_region.cc | 54 +++++++-- libs/ardour/region.cc | 10 +- libs/ardour/tempo.cc | 204 +++++++++++++++++++++++++++++++++- 8 files changed, 273 insertions(+), 30 deletions(-) (limited to 'libs/ardour') diff --git a/libs/ardour/ardour/midi_region.h b/libs/ardour/ardour/midi_region.h index dae984c331..53ffb4497c 100644 --- a/libs/ardour/ardour/midi_region.h +++ b/libs/ardour/ardour/midi_region.h @@ -117,6 +117,7 @@ class MidiRegion : public Region private: friend class RegionFactory; + PBD::Property _length_beats; MidiRegion (const SourceList&); MidiRegion (boost::shared_ptr, frameoffset_t offset = 0, bool offset_relative = true); @@ -134,6 +135,8 @@ class MidiRegion : public Region void recompute_at_end (); void set_position_internal (framepos_t pos, bool allow_bbt_recompute); + void set_length_internal (framecnt_t len); + void update_length_beats (); void model_changed (); void model_automation_state_changed (Evoral::Parameter const &); @@ -143,6 +146,8 @@ class MidiRegion : public Region PBD::ScopedConnection _model_connection; PBD::ScopedConnection _source_connection; PBD::ScopedConnection _model_contents_connection; + + double _last_length_beats; }; } /* namespace ARDOUR */ diff --git a/libs/ardour/ardour/region.h b/libs/ardour/ardour/region.h index 8a3cb1d9f5..e1bba21b52 100644 --- a/libs/ardour/ardour/region.h +++ b/libs/ardour/ardour/region.h @@ -325,6 +325,7 @@ class Region void trim_to_internal (framepos_t position, framecnt_t length, void *src); virtual void set_position_internal (framepos_t pos, bool allow_bbt_recompute); + virtual void set_length_internal (framepos_t pos); void modify_front (framepos_t new_position, bool reset_fade, void* src); void modify_end (framepos_t new_position, bool reset_fade, void* src); diff --git a/libs/ardour/ardour/tempo.h b/libs/ardour/ardour/tempo.h index de45c788f1..90d27ebed0 100644 --- a/libs/ardour/ardour/tempo.h +++ b/libs/ardour/ardour/tempo.h @@ -204,15 +204,13 @@ class TempoMap : public PBD::StatefulDestructible framecnt_t frame_time (const Timecode::BBT_Time&) const; framecnt_t bbt_duration_at (framepos_t, const Timecode::BBT_Time&, int dir) const; - void bbt_time_add (framepos_t origin, Timecode::BBT_Time& start, const Timecode::BBT_Time& shift); - static const Tempo& default_tempo() { return _default_tempo; } static const Meter& default_meter() { return _default_meter; } const Tempo& tempo_at (framepos_t) const; const Meter& meter_at (framepos_t) const; - const TempoSection& tempo_section_at (framepos_t); + const TempoSection& tempo_section_at (framepos_t) const; void add_tempo(const Tempo&, Timecode::BBT_Time where); void add_meter(const Meter&, Timecode::BBT_Time where); @@ -250,6 +248,9 @@ class TempoMap : public PBD::StatefulDestructible Timecode::BBT_Time bbt_add (const Timecode::BBT_Time& a, const Timecode::BBT_Time& b) const; Timecode::BBT_Time bbt_subtract (const Timecode::BBT_Time&, const Timecode::BBT_Time&) const; + framepos_t framepos_plus_bbt (framepos_t pos, Timecode::BBT_Time b) const; + double framewalk_to_beats (framepos_t pos, framecnt_t distance) const; + void change_existing_tempo_at (framepos_t, double bpm, double note_type); void change_initial_tempo (double bpm, double note_type); diff --git a/libs/ardour/beats_frames_converter.cc b/libs/ardour/beats_frames_converter.cc index 180b3e563a..4cda4173b3 100644 --- a/libs/ardour/beats_frames_converter.cc +++ b/libs/ardour/beats_frames_converter.cc @@ -27,25 +27,19 @@ namespace ARDOUR { framecnt_t BeatsFramesConverter::to(double beats) const { - // FIXME: assumes tempo never changes after origin - const Tempo& tempo = _tempo_map.tempo_at (_origin_b); - const double frames_per_beat = tempo.frames_per_beat( - _tempo_map.frame_rate(), - _tempo_map.meter_at (_origin_b)); + Timecode::BBT_Time delta; - return llrint (beats * frames_per_beat); + delta.bars = 0; + delta.beats = rint (floor (beats)); + delta.ticks = rint (floor (Meter::ticks_per_beat * fmod (beats, 1.0))); + + return _tempo_map.framepos_plus_bbt (_origin_b, delta); } double BeatsFramesConverter::from (framecnt_t frames) const { - // FIXME: assumes tempo never changes after origin - const Tempo& tempo = _tempo_map.tempo_at (_origin_b); - const double frames_per_beat = tempo.frames_per_beat( - _tempo_map.frame_rate(), - _tempo_map.meter_at (_origin_b)); - - return frames / frames_per_beat; + return _tempo_map.framewalk_to_beats (_origin_b, frames); } } /* namespace ARDOUR */ diff --git a/libs/ardour/globals.cc b/libs/ardour/globals.cc index 65125370aa..d5579b47da 100644 --- a/libs/ardour/globals.cc +++ b/libs/ardour/globals.cc @@ -67,6 +67,7 @@ #include "ardour/control_protocol_manager.h" #include "ardour/debug.h" #include "ardour/filesystem_paths.h" +#include "ardour/midi_region.h" #include "ardour/mix.h" #include "ardour/audioplaylist.h" #include "ardour/plugin_manager.h" @@ -247,6 +248,7 @@ ARDOUR::init (bool use_vst, bool try_optimization) make_property_quarks (); SessionObject::make_property_quarks (); Region::make_property_quarks (); + MidiRegion::make_property_quarks (); AudioRegion::make_property_quarks (); RouteGroup::make_property_quarks (); Playlist::make_property_quarks (); diff --git a/libs/ardour/midi_region.cc b/libs/ardour/midi_region.cc index ddf582553b..ee5d42dcf9 100644 --- a/libs/ardour/midi_region.cc +++ b/libs/ardour/midi_region.cc @@ -49,7 +49,8 @@ using namespace PBD; namespace ARDOUR { namespace Properties { - PBD::PropertyDescriptor midi_data; + PBD::PropertyDescriptor midi_data; + PBD::PropertyDescriptor length_beats; } } @@ -58,18 +59,22 @@ MidiRegion::make_property_quarks () { Properties::midi_data.property_id = g_quark_from_static_string (X_("midi-data")); DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for midi-data = %1\n", Properties::midi_data.property_id)); + Properties::length_beats.property_id = g_quark_from_static_string (X_("length-beats")); + DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for length-beats = %1\n", Properties::length_beats.property_id)); } void MidiRegion::register_properties () { - /* none yet, but its only a matter of time */ + add_property (_length_beats); } /* Basic MidiRegion constructor (many channels) */ MidiRegion::MidiRegion (const SourceList& srcs) : Region (srcs) + , _length_beats (Properties::length_beats, (Evoral::MusicalTime) 0) { + update_length_beats (); register_properties (); midi_source(0)->ModelChanged.connect_same_thread (_source_connection, boost::bind (&MidiRegion::model_changed, this)); @@ -81,7 +86,9 @@ MidiRegion::MidiRegion (const SourceList& srcs) /** Create a new MidiRegion, that is part of an existing one */ MidiRegion::MidiRegion (boost::shared_ptr other, frameoffset_t offset, bool offset_relative) : Region (other, offset, offset_relative) + , _length_beats (Properties::length_beats, (Evoral::MusicalTime) 0) { + update_length_beats (); register_properties (); assert(_name.val().find("/") == string::npos); @@ -110,22 +117,43 @@ MidiRegion::clone () plist.add (Properties::whole_file, true); plist.add (Properties::start, _start); plist.add (Properties::length, _length); + plist.add (Properties::length_beats, _length_beats); plist.add (Properties::layer, 0); return boost::dynamic_pointer_cast (RegionFactory::create (ms, plist, true)); } void -MidiRegion::set_position_internal (framepos_t pos, bool allow_bbt_recompute) +MidiRegion::set_length_internal (framecnt_t len) { - BeatsFramesConverter old_converter(_session.tempo_map(), _position - _start); - double length_beats = old_converter.from(_length); - - Region::set_position_internal (pos, allow_bbt_recompute); + Region::set_length_internal (len); + update_length_beats (); +} - BeatsFramesConverter new_converter(_session.tempo_map(), pos - _start); +void +MidiRegion::update_length_beats () +{ + cerr << name() << " Updating length beats, currently = " << _length_beats << " w/length = " << _length << endl; + BeatsFramesConverter converter (_session.tempo_map(), _position - _start); + _length_beats = converter.from (_length); + cerr << "\tnew value: " << _length_beats << endl; +} - set_length(new_converter.to(length_beats), 0); +void +MidiRegion::set_position_internal (framepos_t pos, bool allow_bbt_recompute) +{ + Region::set_position_internal (pos, allow_bbt_recompute); + /* zero length regions don't exist - so if _length_beats is zero, this object + is under construction. + */ + if (_length_beats) { + /* leave _length_beats alone, and change _length to reflect the state of things + at the new position (tempo map may dictate a different number of frames + */ + BeatsFramesConverter converter (_session.tempo_map(), _position - _start); + cerr << name() << " change frame length to " << converter.to (_length_beats) << endl; + Region::set_length_internal (converter.to (_length_beats)); + } } framecnt_t @@ -212,7 +240,13 @@ MidiRegion::state () int MidiRegion::set_state (const XMLNode& node, int version) { - return Region::set_state (node, version); + int ret = Region::set_state (node, version); + + if (ret == 0) { + update_length_beats (); + } + + return ret; } void diff --git a/libs/ardour/region.cc b/libs/ardour/region.cc index 585689a656..a0da02be2a 100644 --- a/libs/ardour/region.cc +++ b/libs/ardour/region.cc @@ -462,7 +462,7 @@ Region::set_length (framecnt_t len, void */*src*/) _last_length = _length; - _length = len; + set_length_internal (len); _whole_file = false; first_edit (); maybe_uncopy (); @@ -476,6 +476,12 @@ Region::set_length (framecnt_t len, void */*src*/) } } +void +Region::set_length_internal (framecnt_t len) +{ + _length = len; +} + void Region::maybe_uncopy () { @@ -925,7 +931,7 @@ Region::trim_to_internal (framepos_t position, framecnt_t length, void */*src*/) if (!property_changes_suspended()) { _last_length = _length; } - _length = length; + set_length_internal (length); what_changed.add (Properties::length); } if (_position != position) { diff --git a/libs/ardour/tempo.cc b/libs/ardour/tempo.cc index adb207c752..90a428b9a0 100644 --- a/libs/ardour/tempo.cc +++ b/libs/ardour/tempo.cc @@ -1510,10 +1510,10 @@ TempoMap::get_points (framepos_t lower, framepos_t upper) const } const TempoSection& -TempoMap::tempo_section_at (framepos_t frame) +TempoMap::tempo_section_at (framepos_t frame) const { Glib::RWLock::ReaderLock lm (lock); - Metrics::iterator i; + Metrics::const_iterator i; TempoSection* prev = 0; for (i = metrics->begin(); i != metrics->end(); ++i) { @@ -1904,6 +1904,206 @@ TempoMap::bbt_subtract (const BBT_Time& start, const BBT_Time& decrement) const return result; } +/** + * add the BBT interval @param increment to @param start and return the result + */ +framepos_t +TempoMap::framepos_plus_bbt (framepos_t pos, BBT_Time op) const +{ + Metrics::const_iterator i; + const MeterSection* meter; + const MeterSection* m; + const TempoSection* tempo; + const TempoSection* t; + framecnt_t frames_per_beat; + + meter = &first_meter (); + tempo = &first_tempo (); + + assert (meter); + assert (tempo); + + /* find the starting metrics for tempo & meter */ + + for (i = metrics->begin(); i != metrics->end(); ++i) { + + if ((*i)->frame() > pos) { + break; + } + + if ((t = dynamic_cast(*i)) != 0) { + tempo = t; + } else if ((m = dynamic_cast(*i)) != 0) { + meter = m; + } + } + + /* We now have: + + meter -> the Meter for "pos" + tempo -> the Tempo for "pos" + i -> for first new metric after "pos", possibly metrics->end() + */ + + /* now comes the complicated part. we have to add one beat a time, + checking for a new metric on every beat. + */ + + frames_per_beat = tempo->frames_per_beat (_frame_rate, *meter); + + while (op.bars) { + + pos += llrint (frames_per_beat * meter->beats_per_bar()); + op.bars--; + + /* check if we need to use a new metric section: has adding frames moved us + to or after the start of the next metric section? in which case, use it. + */ + + if (i != metrics->end()) { + if ((*i)->frame() <= pos) { + + if ((t = dynamic_cast(*i)) != 0) { + tempo = t; + } else if ((m = dynamic_cast(*i)) != 0) { + meter = m; + } + ++i; + frames_per_beat = tempo->frames_per_beat (_frame_rate, *meter); + + } + } + + } + + while (op.beats) { + + /* given the current meter, have we gone past the end of the bar ? */ + + pos += frames_per_beat; + op.beats--; + + /* check if we need to use a new metric section: has adding frames moved us + to or after the start of the next metric section? in which case, use it. + */ + + if (i != metrics->end()) { + if ((*i)->frame() <= pos) { + + if ((t = dynamic_cast(*i)) != 0) { + tempo = t; + } else if ((m = dynamic_cast(*i)) != 0) { + meter = m; + } + ++i; + frames_per_beat = tempo->frames_per_beat (_frame_rate, *meter); + } + } + } + + if (op.ticks) { + if (op.ticks >= Meter::ticks_per_beat) { + pos += frames_per_beat; + pos += llrint (frames_per_beat * ((op.ticks % (uint32_t) Meter::ticks_per_beat) / (double) Meter::ticks_per_beat)); + } else { + pos += llrint (frames_per_beat * (op.ticks / (double) Meter::ticks_per_beat)); + } + } + + return pos; +} + +/** + * add the BBT interval @param increment to @param start and return the result + */ +double +TempoMap::framewalk_to_beats (framepos_t pos, framecnt_t distance) const +{ + Metrics::const_iterator i; + double beats = 0; + const MeterSection* meter; + const MeterSection* m; + const TempoSection* tempo; + const TempoSection* t; + double frames_per_beat; + + double ddist = distance; + double dpos = pos; + + meter = &first_meter (); + tempo = &first_tempo (); + + assert (meter); + assert (tempo); + + /* find the starting metrics for tempo & meter */ + + for (i = metrics->begin(); i != metrics->end(); ++i) { + + if ((*i)->frame() > pos) { + break; + } + + if ((t = dynamic_cast(*i)) != 0) { + tempo = t; + } else if ((m = dynamic_cast(*i)) != 0) { + meter = m; + } + } + + /* We now have: + + meter -> the Meter for "pos" + tempo -> the Tempo for "pos" + i -> for first new metric after "pos", possibly metrics->end() + */ + + /* now comes the complicated part. we have to add one beat a time, + checking for a new metric on every beat. + */ + + frames_per_beat = tempo->frames_per_beat (_frame_rate, *meter); + + while (ddist > 0) { + + /* if we're nearly at the end, but have a fractional beat left, + compute the fraction and then its all over + */ + + if (ddist < frames_per_beat) { + beats += Meter::ticks_per_beat * (ddist/frames_per_beat); + break; + } + + /* walk one beat */ + + ddist -= frames_per_beat; + dpos += frames_per_beat; + beats += 1.0; + + /* check if we need to use a new metric section: has adding frames moved us + to or after the start of the next metric section? in which case, use it. + */ + + if (i != metrics->end()) { + if ((*i)->frame() <= (framepos_t) dpos) { + + if ((t = dynamic_cast(*i)) != 0) { + tempo = t; + } else if ((m = dynamic_cast(*i)) != 0) { + meter = m; + } + ++i; + frames_per_beat = tempo->frames_per_beat (_frame_rate, *meter); + } + } + + } + + return beats; +} + + /** Compare the time of this with that of another MetricSection. * @param with_bbt True to compare using ::start(), false to use ::frame(). * @return -1 for less than, 0 for equal, 1 for greater than. -- cgit v1.2.3