Make some musical operations on music-locked regions operate in beats.

- use exact beats to determine frame position.
	- see comments in tempo.cc for more.
	- this hasn't been done for split yet, but dragging and
	  trimming are supported.
This commit is contained in:
nick_m 2016-06-14 03:21:52 +10:00
parent 0d050de94e
commit 2d5238d875
13 changed files with 155 additions and 84 deletions

View File

@ -510,7 +510,7 @@ Drag::show_verbose_cursor_text (string const & text)
}
boost::shared_ptr<Region>
Drag::add_midi_region (MidiTimeAxisView* view, bool commit)
Drag::add_midi_region (MidiTimeAxisView* view, bool commit, const int32_t& sub_num)
{
if (_editor->session()) {
const TempoMap& map (_editor->session()->tempo_map());
@ -519,7 +519,7 @@ Drag::add_midi_region (MidiTimeAxisView* view, bool commit)
might be wrong.
*/
framecnt_t len = map.frame_at_beat (map.beat_at_frame (pos) + 1.0) - pos;
return view->add_region (grab_frame(), len, commit);
return view->add_region (grab_frame(), len, commit, sub_num);
}
return boost::shared_ptr<Region>();
@ -1257,7 +1257,7 @@ RegionMoveDrag::motion (GdkEvent* event, bool first_move)
const boost::shared_ptr<const Region> original = rv->region();
boost::shared_ptr<Region> region_copy = RegionFactory::create (original, true);
region_copy->set_position (original->position());
region_copy->set_position (original->position(), _editor->get_grid_music_divisions (event->button.state));
/* need to set this so that the drop zone code can work. This doesn't
actually put the region into the playlist, but just sets a weak pointer
to it.
@ -1364,7 +1364,8 @@ RegionMoveDrag::finished (GdkEvent* ev, bool movement_occurred)
finished_copy (
changed_position,
changed_tracks,
drag_delta
drag_delta,
ev->button.state
);
} else {
@ -1372,7 +1373,8 @@ RegionMoveDrag::finished (GdkEvent* ev, bool movement_occurred)
finished_no_copy (
changed_position,
changed_tracks,
drag_delta
drag_delta,
ev->button.state
);
}
@ -1419,7 +1421,7 @@ RegionMoveDrag::create_destination_time_axis (boost::shared_ptr<Region> region,
}
void
RegionMoveDrag::finished_copy (bool const changed_position, bool const /*changed_tracks*/, framecnt_t const drag_delta)
RegionMoveDrag::finished_copy (bool const changed_position, bool const /*changed_tracks*/, framecnt_t const drag_delta, int32_t const ev_state)
{
RegionSelection new_views;
PlaylistSet modified_playlists;
@ -1510,7 +1512,8 @@ void
RegionMoveDrag::finished_no_copy (
bool const changed_position,
bool const changed_tracks,
framecnt_t const drag_delta
framecnt_t const drag_delta,
int32_t const ev_state
)
{
RegionSelection new_views;
@ -1631,7 +1634,7 @@ RegionMoveDrag::finished_no_copy (
playlist->freeze ();
}
rv->region()->set_position (where);
rv->region()->set_position (where, _editor->get_grid_music_divisions (ev_state));
_editor->session()->add_command (new StatefulDiffCommand (rv->region()));
}
@ -1876,7 +1879,7 @@ RegionInsertDrag::RegionInsertDrag (Editor* e, boost::shared_ptr<Region> r, Rout
}
void
RegionInsertDrag::finished (GdkEvent *, bool)
RegionInsertDrag::finished (GdkEvent * event, bool)
{
int pos = _views.front().time_axis_view;
assert(pos >= 0 && pos < (int)_time_axis_views.size());
@ -2303,7 +2306,7 @@ RegionCreateDrag::motion (GdkEvent* event, bool first_move)
{
if (first_move) {
_editor->begin_reversible_command (_("create region"));
_region = add_midi_region (_view, false);
_region = add_midi_region (_view, false, _editor->get_grid_music_divisions (event->button.state));
_view->playlist()->freeze ();
} else {
if (_region) {
@ -2326,10 +2329,10 @@ RegionCreateDrag::motion (GdkEvent* event, bool first_move)
}
void
RegionCreateDrag::finished (GdkEvent*, bool movement_occurred)
RegionCreateDrag::finished (GdkEvent* event, bool movement_occurred)
{
if (!movement_occurred) {
add_midi_region (_view, true);
add_midi_region (_view, true, _editor->get_grid_music_divisions (event->button.state));
} else {
_view->playlist()->thaw ();
_editor->commit_reversible_command();
@ -2911,7 +2914,9 @@ TrimDrag::motion (GdkEvent* event, bool first_move)
switch (_operation) {
case StartTrim:
for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
bool changed = i->view->trim_front (i->initial_position + dt, non_overlap_trim);
bool changed = i->view->trim_front (i->initial_position + dt, non_overlap_trim
, _editor->get_grid_music_divisions (event->button.state));
if (changed && _preserve_fade_anchor) {
AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
if (arv) {
@ -3379,8 +3384,7 @@ TempoMarkerDrag::aborted (bool moved)
if (moved) {
TempoMap& map (_editor->session()->tempo_map());
map.set_state (*before_state, Stateful::current_state_version);
// delete the dummy marker we used for visual representation while moving.
// a new visual marker will show up automatically.
// delete the dummy (hidden) marker we used for events while moving.
delete _marker;
}
}
@ -4757,7 +4761,7 @@ RubberbandSelectDrag::finished (GdkEvent* event, bool movement_occurred)
/* MIDI track */
if (_editor->selection->empty() && _editor->mouse_mode == MouseDraw) {
/* nothing selected */
add_midi_region (mtv, true);
add_midi_region (mtv, true, _editor->get_grid_music_divisions(event->button.state));
do_deselect = false;
}
}

View File

@ -248,7 +248,7 @@ protected:
/* sets snap delta from unsnapped pos */
void setup_snap_delta (framepos_t pos);
boost::shared_ptr<ARDOUR::Region> add_midi_region (MidiTimeAxisView*, bool commit);
boost::shared_ptr<ARDOUR::Region> add_midi_region (MidiTimeAxisView*, bool commit, const int32_t& sub_num);
void show_verbose_cursor_time (framepos_t);
void show_verbose_cursor_duration (framepos_t, framepos_t, double xoffset = 0);
@ -407,13 +407,15 @@ private:
void finished_no_copy (
bool const,
bool const,
ARDOUR::framecnt_t const
ARDOUR::framecnt_t const,
int32_t const ev_state
);
void finished_copy (
bool const,
bool const,
ARDOUR::framecnt_t const
ARDOUR::framecnt_t const,
int32_t const ev_state
);
RegionView* insert_region_into_playlist (

View File

@ -1508,7 +1508,7 @@ MidiTimeAxisView::automation_child_menu_item (Evoral::Parameter param)
}
boost::shared_ptr<MidiRegion>
MidiTimeAxisView::add_region (framepos_t pos, framecnt_t length, bool commit)
MidiTimeAxisView::add_region (framepos_t pos, framecnt_t length, bool commit, const int32_t& sub_num)
{
Editor* real_editor = dynamic_cast<Editor*> (&_editor);
if (commit) {
@ -1526,7 +1526,7 @@ MidiTimeAxisView::add_region (framepos_t pos, framecnt_t length, bool commit)
plist.add (ARDOUR::Properties::name, PBD::basename_nosuffix(src->name()));
boost::shared_ptr<Region> region = (RegionFactory::create (src, plist));
region->set_position (pos, sub_num);
playlist()->add_region (region, pos);
_session->add_command (new StatefulDiffCommand (playlist()));

View File

@ -82,7 +82,7 @@ public:
void set_height (uint32_t, TrackHeightMode m = OnlySelf);
boost::shared_ptr<ARDOUR::MidiRegion> add_region (ARDOUR::framepos_t, ARDOUR::framecnt_t, bool);
boost::shared_ptr<ARDOUR::MidiRegion> add_region (ARDOUR::framepos_t, ARDOUR::framecnt_t, bool, const int32_t& sub_num);
void show_all_automation (bool apply_to_selection = false);
void show_existing_automation (bool apply_to_selection = false);

View File

@ -819,7 +819,7 @@ RegionView::update_coverage_frames (LayerDisplay d)
}
bool
RegionView::trim_front (framepos_t new_bound, bool no_overlap)
RegionView::trim_front (framepos_t new_bound, bool no_overlap, const int32_t& sub_num)
{
if (_region->locked()) {
return false;
@ -836,7 +836,7 @@ RegionView::trim_front (framepos_t new_bound, bool no_overlap)
return false;
}
_region->trim_front (speed_bound);
_region->trim_front (speed_bound, sub_num);
if (no_overlap) {
// Get the next region on the left of this region and shrink/expand it.

View File

@ -102,7 +102,7 @@ class RegionView : public TimeAxisViewItem
/** Called when a front trim is about to begin */
virtual void trim_front_starting () {}
bool trim_front (framepos_t, bool);
bool trim_front (framepos_t, bool, const int32_t& sub_num);
/** Called when a start trim has finished */
virtual void trim_front_ending () {}

View File

@ -122,7 +122,7 @@ StepEditor::prepare_step_edit_region ()
framecnt_t next_bar_pos = _mtv.session()->tempo_map().frame_at_beat (next_bar_in_beats);
framecnt_t len = next_bar_pos - step_edit_insert_position;
step_edit_region = _mtv.add_region (step_edit_insert_position, len, true);
step_edit_region = _mtv.add_region (step_edit_insert_position, len, true, _editor.get_grid_music_divisions (0));
RegionView* rv = _mtv.midi_view()->find_view (step_edit_region);
step_edit_region_view = dynamic_cast<MidiRegionView*>(rv);

View File

@ -133,8 +133,8 @@ class LIBARDOUR_API MidiRegion : public Region
void set_position_internal (framepos_t pos, bool allow_bbt_recompute);
void set_length_internal (framecnt_t len);
void set_start_internal (framecnt_t);
void trim_to_internal (framepos_t position, framecnt_t length);
void set_start_internal (framecnt_t, const int32_t& sub_num);
void trim_to_internal (framepos_t position, framecnt_t length, const int32_t& sub_num);
void update_length_beats ();
void model_changed ();

View File

@ -207,7 +207,7 @@ class LIBARDOUR_API Region
void set_length (framecnt_t);
void set_start (framepos_t);
void set_position (framepos_t);
void set_position (framepos_t, int32_t sub_num = 0);
void set_initial_position (framepos_t);
void special_set_position (framepos_t);
virtual void update_after_tempo_map_change (bool send_change = true);
@ -216,15 +216,15 @@ class LIBARDOUR_API Region
bool at_natural_position () const;
void move_to_natural_position ();
void move_start (frameoffset_t distance);
void trim_front (framepos_t new_position);
void trim_end (framepos_t new_position);
void trim_to (framepos_t position, framecnt_t length);
void move_start (frameoffset_t distance, const int32_t& sub_num = 0);
void trim_front (framepos_t new_position, const int32_t& sub_num = 0);
void trim_end (framepos_t new_position, const int32_t& sub_num = 0);
void trim_to (framepos_t position, framecnt_t length, const int32_t& sub_num = 0);
virtual void fade_range (framepos_t, framepos_t) {}
void cut_front (framepos_t new_position);
void cut_end (framepos_t new_position);
void cut_front (framepos_t new_position, const int32_t& sub_num = 0);
void cut_end (framepos_t new_position, const int32_t& sub_num = 0);
void set_layer (layer_t l); /* ONLY Playlist can call this */
void raise ();
@ -358,7 +358,7 @@ class LIBARDOUR_API Region
void post_set (const PBD::PropertyChange&);
virtual void set_position_internal (framepos_t pos, bool allow_bbt_recompute);
virtual void set_length_internal (framecnt_t);
virtual void set_start_internal (framecnt_t);
virtual void set_start_internal (framecnt_t, const int32_t& sub_num = 0);
bool verify_start_and_length (framepos_t, framecnt_t&);
void first_edit ();
@ -396,9 +396,9 @@ class LIBARDOUR_API Region
private:
void mid_thaw (const PBD::PropertyChange&);
virtual void trim_to_internal (framepos_t position, framecnt_t length);
void modify_front (framepos_t new_position, bool reset_fade);
void modify_end (framepos_t new_position, bool reset_fade);
virtual void trim_to_internal (framepos_t position, framecnt_t length, const int32_t& sub_num);
void modify_front (framepos_t new_position, bool reset_fade, const int32_t& sub_num);
void modify_end (framepos_t new_position, bool reset_fade, const int32_t& sub_num);
void maybe_uncopy ();

View File

@ -450,6 +450,8 @@ class LIBARDOUR_API TempoMap : public PBD::StatefulDestructible
bool gui_change_tempo (TempoSection*, const Tempo& bpm);
void gui_dilate_tempo (TempoSection* tempo, const framepos_t& frame, const framepos_t& end_frame, const double& pulse);
double exact_beat_at_frame (const framepos_t& frame, const int32_t& sub_num);
std::pair<double, framepos_t> predict_tempo_position (TempoSection* section, const Timecode::BBT_Time& bbt);
bool can_solve_bbt (TempoSection* section, const Timecode::BBT_Time& bbt);
@ -493,6 +495,8 @@ private:
bool solve_map_frame (Metrics& metrics, MeterSection* section, const framepos_t& frame);
bool solve_map_bbt (Metrics& metrics, MeterSection* section, const Timecode::BBT_Time& bbt);
double exact_beat_at_frame_locked (const Metrics& metrics, const framepos_t& frame, const int32_t& sub_num);
friend class ::BBTTest;
friend class ::FrameposPlusBeatsTest;
friend class ::TempoTest;

View File

@ -458,14 +458,17 @@ MidiRegion::fix_negative_start ()
}
void
MidiRegion::set_start_internal (framecnt_t s)
MidiRegion::set_start_internal (framecnt_t s, const int32_t& sub_num)
{
Region::set_start_internal (s);
set_start_beats_from_start_frames ();
Region::set_start_internal (s, sub_num);
if (position_lock_style() == AudioTime) {
set_start_beats_from_start_frames ();
}
}
void
MidiRegion::trim_to_internal (framepos_t position, framecnt_t length)
MidiRegion::trim_to_internal (framepos_t position, framecnt_t length, const int32_t& sub_num)
{
framepos_t new_start;
@ -476,7 +479,7 @@ MidiRegion::trim_to_internal (framepos_t position, framecnt_t length)
PropertyChange what_changed;
/* beat has not been set by set_position_internal */
const double beat_delta = _session.tempo_map().beat_at_frame (position) - beat();
const double beat_delta = _session.tempo_map().exact_beat_at_frame (position, sub_num) - beat();
/* Set position before length, otherwise for MIDI regions this bad thing happens:
* 1. we call set_length_internal; length in beats is computed using the region's current
@ -504,7 +507,7 @@ MidiRegion::trim_to_internal (framepos_t position, framecnt_t length)
_start_beats = Evoral::Beats (new_start_beat);
what_changed.add (Properties::start_beats);
set_start_internal (new_start);
set_start_internal (new_start, sub_num);
what_changed.add (Properties::start);
}

View File

@ -570,13 +570,19 @@ Region::update_after_tempo_map_change (bool send)
}
void
Region::set_position (framepos_t pos)
Region::set_position (framepos_t pos, int32_t sub_num)
{
if (!can_move()) {
return;
}
set_position_internal (pos, true);
if (sub_num == 0) {
set_position_internal (pos, true);
} else {
double beat = _session.tempo_map().exact_beat_at_frame (pos, sub_num);
_beat = beat;
set_position_internal (pos, false);
}
/* do this even if the position is the same. this helps out
a GUI that has moved its representation already.
@ -738,7 +744,7 @@ Region::set_start (framepos_t pos)
}
void
Region::move_start (frameoffset_t distance)
Region::move_start (frameoffset_t distance, const int32_t& sub_num)
{
if (locked() || position_locked() || video_locked()) {
return;
@ -774,7 +780,7 @@ Region::move_start (frameoffset_t distance)
return;
}
set_start_internal (new_start);
set_start_internal (new_start, sub_num);
_whole_file = false;
first_edit ();
@ -783,25 +789,25 @@ Region::move_start (frameoffset_t distance)
}
void
Region::trim_front (framepos_t new_position)
Region::trim_front (framepos_t new_position, const int32_t& sub_num)
{
modify_front (new_position, false);
modify_front (new_position, false, sub_num);
}
void
Region::cut_front (framepos_t new_position)
Region::cut_front (framepos_t new_position, const int32_t& sub_num)
{
modify_front (new_position, true);
modify_front (new_position, true, sub_num);
}
void
Region::cut_end (framepos_t new_endpoint)
Region::cut_end (framepos_t new_endpoint, const int32_t& sub_num)
{
modify_end (new_endpoint, true);
modify_end (new_endpoint, true, sub_num);
}
void
Region::modify_front (framepos_t new_position, bool reset_fade)
Region::modify_front (framepos_t new_position, bool reset_fade, const int32_t& sub_num)
{
if (locked()) {
return;
@ -831,7 +837,7 @@ Region::modify_front (framepos_t new_position, bool reset_fade)
newlen = _length + (_position - new_position);
}
trim_to_internal (new_position, newlen);
trim_to_internal (new_position, newlen, sub_num);
if (reset_fade) {
_right_of_split = true;
@ -846,14 +852,14 @@ Region::modify_front (framepos_t new_position, bool reset_fade)
}
void
Region::modify_end (framepos_t new_endpoint, bool reset_fade)
Region::modify_end (framepos_t new_endpoint, bool reset_fade, const int32_t& sub_num)
{
if (locked()) {
return;
}
if (new_endpoint > _position) {
trim_to_internal (_position, new_endpoint - _position);
trim_to_internal (_position, new_endpoint - _position, sub_num);
if (reset_fade) {
_left_of_split = true;
}
@ -868,19 +874,19 @@ Region::modify_end (framepos_t new_endpoint, bool reset_fade)
*/
void
Region::trim_end (framepos_t new_endpoint)
Region::trim_end (framepos_t new_endpoint, const int32_t& sub_num)
{
modify_end (new_endpoint, false);
modify_end (new_endpoint, false, sub_num);
}
void
Region::trim_to (framepos_t position, framecnt_t length)
Region::trim_to (framepos_t position, framecnt_t length, const int32_t& sub_num)
{
if (locked()) {
return;
}
trim_to_internal (position, length);
trim_to_internal (position, length, sub_num);
if (!property_changes_suspended()) {
recompute_at_start ();
@ -889,7 +895,7 @@ Region::trim_to (framepos_t position, framecnt_t length)
}
void
Region::trim_to_internal (framepos_t position, framecnt_t length)
Region::trim_to_internal (framepos_t position, framecnt_t length, const int32_t& sub_num)
{
framepos_t new_start;
@ -926,7 +932,7 @@ Region::trim_to_internal (framepos_t position, framecnt_t length)
PropertyChange what_changed;
if (_start != new_start) {
set_start_internal (new_start);
set_start_internal (new_start, sub_num);
what_changed.add (Properties::start);
}
@ -1845,7 +1851,7 @@ Region::post_set (const PropertyChange& pc)
}
void
Region::set_start_internal (framecnt_t s)
Region::set_start_internal (framecnt_t s, const int32_t& sub_num)
{
_start = s;
}

View File

@ -586,12 +586,16 @@ MeterSection::get_state() const
/*
Tempo Map Overview
The Shaggs - Things I Wonder
https://www.youtube.com/watch?v=9wQK6zMJOoQ
Tempo is the rate of the musical pulse.
Meters divide the pulses into measures and beats.
TempoSections - provide pulses in the form of beats_per_minute() and note_type() where note_type is the division of a whole pulse,
and beats_per_minute is the number of note_types in one minute (unlike what its name suggests).
Note that Tempo::beats_per_minute() has nothing to do with musical beats.
Note that Tempo::beats_per_minute() has nothing to do with musical beats. It has been left that way because
a shorter one hasn't been found yet (pulse_divisions_per_minute()?).
MeterSecions - divide pulses into measures (via divisions_per_bar) and beats (via note_divisor).
@ -629,6 +633,43 @@ MeterSection::get_state() const
Because ramped MusicTime and AudioTime tempos can interact with each other,
reordering is frequent. Care must be taken to keep _metrics in a solved state.
Solved means ordered by frame or pulse with frame-accurate precision (see check_solved()).
Music and Audio
Music and audio-locked objects may seem interchangeable on the surface, but when translating
between audio samples and beats, keep in mind that a sample is only a quantised approximation
of the actual time (in minutes) of a beat.
Thus if a gui user points to the frame occupying the start of a music-locked object on 1|3|0, it does not
mean that this frame is the actual location in time of 1|3|0.
You cannot use a frame measurement to determine beat distance except under special circumstances
(e.g. where the user has requested that a beat lie on a SMPTE frame or if the tempo is known to be constant over the duration).
This means is that a user operating on a musical grid must supply the desired beat position and/or current beat quantization in order for the
sample space the user is operating at to be translated correctly to the object.
The current approach is to interpret the supplied frame using the grid division the user has currently selected.
If the user has no musical grid set, they are actually operating in sample space (even SMPTE frames are rounded to audio frame), so
the supplied audio frame is interpreted as the desired musical location (beat_at_frame()).
tldr: Beat, being a function of time, has nothing to do with sample rate, but time quantization can get in the way of precision.
When frame_at_beat() is called, the position calculation is performed in pulses and minutes.
The result is rounded to audio frames.
When beat_at_frame() is called, the frame is converted to minutes, with no rounding performed on the result.
So :
frame_at_beat (beat_at_frame (frame)) == frame
but :
beat_at_frame (frame_at_beat (beat)) != beat due to the time quantization of frame_at_beat().
Doing the second one will result in a beat distance error of up to 0.5 audio samples.
So instead work in pulses and/or beats and only use beat position to caclulate frame position (e.g. after tempo change).
For audio-locked objects, use frame position to calculate beat position.
The above pointless example would then do:
beat_at_pulse (pulse_at_beat (beat)) to avoid rounding.
*/
struct MetricSectionSorter {
bool operator() (const MetricSection* a, const MetricSection* b) {
@ -2545,25 +2586,9 @@ TempoMap::gui_move_tempo (TempoSection* ts, const framepos_t& frame, const int&
/* if we're snapping to a musical grid, set the pulse exactly instead of via the supplied frame. */
Glib::Threads::RWLock::WriterLock lm (lock);
TempoSection* tempo_copy = copy_metrics_and_point (_metrics, future_map, ts);
double beat = beat_at_frame_locked (future_map, frame);
if (sub_num > 1) {
beat = floor (beat) + (floor (((beat - floor (beat)) * (double) sub_num) + 0.5) / sub_num);
} else if (sub_num == 1) {
/* snap to beat */
beat = floor (beat + 0.5);
}
const double beat = exact_beat_at_frame_locked (future_map, frame, sub_num);
double pulse = pulse_at_beat_locked (future_map, beat);
if (sub_num == -1) {
/* snap to bar */
BBT_Time bbt = bbt_at_beat_locked (future_map, beat);
bbt.beats = 1;
bbt.ticks = 0;
pulse = pulse_at_bbt_locked (future_map, bbt);
}
if (solve_map_pulse (future_map, tempo_copy, pulse)) {
solve_map_pulse (_metrics, ts, pulse);
recompute_meters (_metrics);
@ -2811,6 +2836,33 @@ TempoMap::gui_dilate_tempo (TempoSection* ts, const framepos_t& frame, const fra
MetricPositionChanged (); // Emit Signal
}
double
TempoMap::exact_beat_at_frame (const framepos_t& frame, const int32_t& sub_num)
{
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& sub_num)
{
double beat = beat_at_frame_locked (metrics, frame);
if (sub_num > 1) {
beat = floor (beat) + (floor (((beat - floor (beat)) * (double) sub_num) + 0.5) / sub_num);
} else if (sub_num == 1) {
/* snap to beat */
beat = floor (beat + 0.5);
} else if (sub_num == -1) {
/* snap to bar */
Timecode::BBT_Time bbt = bbt_at_beat_locked (metrics, beat);
bbt.beats = 1;
bbt.ticks = 0;
beat = beat_at_bbt_locked (metrics, bbt);
}
return beat;
}
framecnt_t
TempoMap::bbt_duration_at (framepos_t pos, const BBT_Time& bbt, int dir)
{