change MidiPlaylist::dump() into ::render(); change type of initial argument
[ardour.git] / libs / ardour / midi_stretch.cc
1 /*
2  * Copyright (C) 2008-2015 David Robillard <d@drobilla.net>
3  * Copyright (C) 2008-2017 Paul Davis <paul@linuxaudiosystems.com>
4  * Copyright (C) 2010 Carl Hetherington <carl@carlh.net>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License along
17  * with this program; if not, write to the Free Software Foundation, Inc.,
18  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19  */
20
21 #include "pbd/error.h"
22
23 #include "ardour/midi_model.h"
24 #include "ardour/midi_region.h"
25 #include "ardour/midi_source.h"
26 #include "ardour/midi_stretch.h"
27 #include "ardour/session.h"
28 #include "ardour/tempo.h"
29 #include "ardour/types.h"
30
31 #include "pbd/i18n.h"
32
33 using namespace std;
34 using namespace ARDOUR;
35 using namespace PBD;
36
37 MidiStretch::MidiStretch (Session& s, const TimeFXRequest& req)
38         : Filter (s)
39         , _request (req)
40 {
41 }
42
43 MidiStretch::~MidiStretch ()
44 {
45 }
46
47 int
48 MidiStretch::run (boost::shared_ptr<Region> r, Progress*)
49 {
50         SourceList nsrcs;
51         char suffix[32];
52
53         boost::shared_ptr<MidiRegion> region = boost::dynamic_pointer_cast<MidiRegion>(r);
54         if (!region) {
55                 return -1;
56         }
57
58         const TempoMap& tmap (session.tempo_map ());
59
60         /* Fraction is based on sample-time, while MIDI source
61          * is always using music-time. This matters in case there is
62          * a tempo-ramp. So we need to scale the ratio to music-time */
63         const double mtfrac =
64                 tmap.framewalk_to_qn (region->position (), region->length () * _request.time_fraction).to_double ()
65                 /
66                 tmap.framewalk_to_qn (region->position (), region->length ()).to_double ();
67
68         /* the name doesn't need to be super-precise, but allow for 2 fractional
69          * digits just to disambiguate close but not identical stretches.
70          */
71
72         snprintf (suffix, sizeof (suffix), "@%d", (int) floor (mtfrac * 100.0f));
73
74         string new_name = region->name();
75         string::size_type at = new_name.find ('@');
76
77         // remove any existing stretch indicator
78
79         if (at != string::npos && at > 2) {
80                 new_name = new_name.substr (0, at - 1);
81         }
82
83         new_name += suffix;
84
85         /* create new sources */
86
87         if (make_new_sources (region, nsrcs, suffix))
88                 return -1;
89
90         boost::shared_ptr<MidiSource> src = region->midi_source(0);
91         {
92                 Source::Lock lock(src->mutex());
93                 src->load_model(lock);
94         }
95
96         boost::shared_ptr<MidiModel> old_model = src->model();
97
98         boost::shared_ptr<MidiSource> new_src = boost::dynamic_pointer_cast<MidiSource>(nsrcs[0]);
99         if (!new_src) {
100                 error << _("MIDI stretch created non-MIDI source") << endmsg;
101                 return -1;
102         }
103
104         Glib::Threads::Mutex::Lock sl (new_src->mutex ());
105
106         new_src->load_model(sl, true);
107         boost::shared_ptr<MidiModel> new_model = new_src->model();
108         new_model->start_write();
109
110         const double r_start = tmap.quarter_notes_between_samples (region->position () - region->start (), region->position ());
111         const double r_end = r_start + tmap.quarter_notes_between_samples (region->position (), region->position () + region->length ());
112
113 #ifdef DEBUG_MIDI_STRETCH
114         printf ("stretch start: %f end: %f  [* %f] * %f\n", r_start, r_end, _request.time_fraction, mtfrac);
115 #endif
116
117         /* Note: pass true into force_discrete for the begin() iterator so that the model doesn't
118          * do interpolation of controller data when we stretch.
119          */
120         for (Evoral::Sequence<MidiModel::TimeType>::const_iterator i = old_model->begin (MidiModel::TimeType(), true); i != old_model->end(); ++i) {
121
122                 if (i->time() < r_start) {
123 #ifdef DEBUG_MIDI_STRETCH
124                         cout << "SKIP EARLY EVENT " << i->time() << "\n";
125 #endif
126                         continue;
127                 }
128                 if (i->time() > r_end) {
129 #ifdef DEBUG_MIDI_STRETCH
130                         cout << "SKIP LATE EVENT " << i->time() << "\n";
131                         continue;
132 #else
133                         break;
134 #endif
135                 }
136
137 #ifdef DEBUG_MIDI_STRETCH
138                 cout << "STRETCH " << i->time() << " TO " << (i->time() - r_start) * mtfrac << "\n";
139 #endif
140
141                 const MidiModel::TimeType new_time = (i->time() - r_start) * mtfrac;
142
143                 // FIXME: double copy
144                 Evoral::Event<MidiModel::TimeType> ev(*i, true);
145                 ev.set_time(new_time);
146                 new_model->append(ev, Evoral::next_event_id());
147         }
148
149         new_model->end_write (Evoral::Sequence<Temporal::Beats>::ResolveStuckNotes);
150         new_model->set_edited (true);
151
152         new_src->copy_interpolation_from (src);
153
154         const int ret = finish (region, nsrcs, new_name);
155
156         /* non-musical */
157         results[0]->set_start(0);
158         results[0]->set_position (r->position ()); // force updates the region's quarter-note position
159         results[0]->set_length((samplecnt_t) floor (r->length() * _request.time_fraction), 0);
160
161         return ret;
162 }
163