merge from 2.0-ongoing @ 3581
[ardour.git] / gtk2_ardour / editor_timefx.cc
1 /*
2     Copyright (C) 2000 Paul Davis 
3
4     This program is free software; you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation; either version 2 of the License, or
7     (at your option) any later version.
8
9     This program is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12     GNU General Public License for more details.
13
14     You should have received a copy of the GNU General Public License
15     along with this program; if not, write to the Free Software
16     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17
18 */
19
20 #include <iostream>
21 #include <cstdlib>
22 #include <cmath>
23
24 #include <string>
25
26 #include <pbd/error.h>
27 #include <pbd/pthread_utils.h>
28 #include <pbd/memento_command.h>
29
30 #include <gtkmm2ext/window_title.h>
31 #include <gtkmm2ext/utils.h>
32
33 #include "editor.h"
34 #include "audio_time_axis.h"
35 #include "audio_region_view.h"
36 #include "region_selection.h"
37
38 #include <ardour/session.h>
39 #include <ardour/region.h>
40 #include <ardour/audioplaylist.h>
41 #include <ardour/audio_track.h>
42 #include <ardour/audioregion.h>
43 #include <ardour/audio_diskstream.h>
44 #include <ardour/stretch.h>
45 #include <ardour/midi_stretch.h>
46 #include <ardour/pitch.h>
47
48 #ifdef USE_RUBBERBAND
49 #include <rubberband/RubberBandStretcher.h>
50 using namespace RubberBand;
51 #endif
52
53 #include "i18n.h"
54
55 using namespace std;
56 using namespace ARDOUR;
57 using namespace PBD;
58 using namespace sigc;
59 using namespace Gtk;
60 using namespace Gtkmm2ext;
61
62 Editor::TimeFXDialog::TimeFXDialog (Editor& e, bool pitch)
63         : ArdourDialog (X_("time fx dialog")),
64           editor (e),
65           pitching (pitch),
66           pitch_octave_adjustment (0.0, -4.0, 4.0, 1, 2.0),
67           pitch_semitone_adjustment (0.0, -12.0, 12.0, 1.0, 4.0),
68           pitch_cent_adjustment (0.0, -499.0, 500.0, 5.0, 15.0),
69           pitch_octave_spinner (pitch_octave_adjustment),
70           pitch_semitone_spinner (pitch_semitone_adjustment),
71           pitch_cent_spinner (pitch_cent_adjustment),
72           quick_button (_("Quick but Ugly")),
73           antialias_button (_("Skip Anti-aliasing")),
74           stretch_opts_label (_("Contents:")),
75           precise_button (_("Strict Linear"))
76 {
77         set_modal (true);
78         set_position (Gtk::WIN_POS_MOUSE);
79         set_name (N_("TimeFXDialog"));
80
81         WindowTitle title(Glib::get_application_name());
82         if (pitching) {
83                 title += _("Pitch Shift");
84         } else {
85                 title += _("Time Stretch");
86         }
87         set_title(title.get_string());
88
89         cancel_button = add_button (_("Cancel"), Gtk::RESPONSE_CANCEL);
90
91         get_vbox()->set_spacing (5);
92         get_vbox()->set_border_width (12);
93
94         if (pitching) {
95
96                 upper_button_box.set_spacing (5);
97                 upper_button_box.set_border_width (5);
98                 
99                 Gtk::Label* l;
100
101                 l = manage (new Label (_("Octaves")));
102                 upper_button_box.pack_start (*l, false, false);
103                 upper_button_box.pack_start (pitch_octave_spinner, false, false);
104
105                 l = manage (new Label (_("Semitones (12TET)")));
106                 upper_button_box.pack_start (*l, false, false);
107                 upper_button_box.pack_start (pitch_semitone_spinner, false, false);
108
109                 l = manage (new Label (_("Cents")));
110                 upper_button_box.pack_start (*l, false, false);
111                 upper_button_box.pack_start (pitch_cent_spinner, false, false);
112
113                 pitch_cent_spinner.set_digits (1);
114
115                 add_button (_("Shift"), Gtk::RESPONSE_ACCEPT);
116
117                 get_vbox()->pack_start (upper_button_box, false, false);
118
119         } else {
120
121 #ifdef USE_RUBBERBAND
122                 opts_box.set_spacing (5);
123                 opts_box.set_border_width (5);
124                 vector<string> strings;
125
126                 set_popdown_strings (stretch_opts_selector, editor.rb_opt_strings);
127                 /* set default */
128                 stretch_opts_selector.set_active_text (editor.rb_opt_strings[4]);
129
130                 opts_box.pack_start (precise_button, false, false);
131                 opts_box.pack_start (stretch_opts_label, false, false);
132                 opts_box.pack_start (stretch_opts_selector, false, false);
133
134                 get_vbox()->pack_start (opts_box, false, false);
135
136 #else
137                 upper_button_box.set_homogeneous (true);
138                 upper_button_box.set_spacing (5);
139                 upper_button_box.set_border_width (5);
140
141                 upper_button_box.pack_start (quick_button, true, true);
142                 upper_button_box.pack_start (antialias_button, true, true);
143
144                 quick_button.set_name (N_("TimeFXButton"));
145                 antialias_button.set_name (N_("TimeFXButton"));
146
147                 get_vbox()->pack_start (upper_button_box, false, false);
148
149 #endif  
150                 add_button (_("Stretch/Shrink"), Gtk::RESPONSE_ACCEPT);
151         }
152
153         get_vbox()->pack_start (progress_bar);
154
155         progress_bar.set_name (N_("TimeFXProgress"));
156
157         show_all_children ();
158 }
159
160 gint
161 Editor::TimeFXDialog::update_progress ()
162 {
163         progress_bar.set_fraction (request.progress);
164         return !request.done;
165 }
166
167 void
168 Editor::TimeFXDialog::cancel_in_progress ()
169 {
170         status = -2;
171         request.cancel = true;
172         first_cancel.disconnect();
173 }
174
175 gint
176 Editor::TimeFXDialog::delete_in_progress (GdkEventAny* ev)
177 {
178         status = -2;
179         request.cancel = true;
180         first_delete.disconnect();
181         return TRUE;
182 }
183
184 int
185 Editor::time_stretch (RegionSelection& regions, float fraction)
186 {
187         // FIXME: kludge, implement stretching of selection of both types
188         
189         if (regions.front()->region()->data_type() == DataType::AUDIO) {
190                 // Audio, pop up timefx dialog
191                 return time_fx (regions, fraction, false);
192         } else {
193                 // MIDI, just stretch
194                 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (&regions.front()->get_time_axis_view());
195                 if (!rtv)
196                         return -1;
197                 
198                 boost::shared_ptr<Playlist> playlist
199                         = rtv->track()->diskstream()->playlist();
200
201             ARDOUR::TimeFXRequest request;
202                 request.time_fraction = fraction;
203                 MidiStretch stretch(*session, request);
204                 begin_reversible_command ("midi stretch");
205                 stretch.run(regions.front()->region());
206                 XMLNode &before = playlist->get_state();
207                 playlist->replace_region (regions.front()->region(), stretch.results[0],
208                                 regions.front()->region()->position());
209                 XMLNode &after = playlist->get_state();
210                 session->add_command (new MementoCommand<Playlist>(*playlist, &before, &after));
211                 commit_reversible_command ();
212         }
213
214         return 0;
215 }
216
217 int
218 Editor::pitch_shift (RegionSelection& regions, float fraction)
219 {
220         return time_fx (regions, fraction, true);
221 }
222
223 int
224 Editor::time_fx (RegionSelection& regions, float val, bool pitching)
225 {
226         if (current_timefx != 0) {
227                 delete current_timefx;
228         }
229
230         current_timefx = new TimeFXDialog (*this, pitching);
231
232         current_timefx->progress_bar.set_fraction (0.0f);
233
234         switch (current_timefx->run ()) {
235         case RESPONSE_ACCEPT:
236                 break;
237         default:
238                 current_timefx->hide ();
239                 return 1;
240         }
241
242         current_timefx->status = 0;
243         current_timefx->regions = regions;
244
245         if (pitching) {
246
247                 float cents = current_timefx->pitch_octave_adjustment.get_value() * 1200.0;
248                 float pitch_fraction;
249                 cents += current_timefx->pitch_semitone_adjustment.get_value() * 100.0;
250                 cents += current_timefx->pitch_cent_adjustment.get_value();
251
252                 if (cents == 0.0) {
253                         // user didn't change anything
254                         current_timefx->hide ();
255                         return 0;
256                 }
257
258                 // one octave == 1200 cents
259                 // adding one octave doubles the frequency
260                 // ratio is 2^^octaves
261                                 
262                 pitch_fraction = pow(2, cents/1200);
263
264                 current_timefx->request.time_fraction = 1.0;
265                 current_timefx->request.pitch_fraction = pitch_fraction;
266                 
267         } else {
268
269                 current_timefx->request.time_fraction = val;
270                 current_timefx->request.pitch_fraction = 1.0;
271
272         }
273
274 #ifdef USE_RUBBERBAND
275         /* parse options */
276
277         RubberBandStretcher::Options options = 0;
278
279         bool realtime = false;
280         bool precise = false;
281         bool peaklock = true;
282         bool longwin = false;
283         bool shortwin = false;
284         string txt;
285
286         enum {
287                 NoTransients,
288                 BandLimitedTransients,
289                 Transients
290         } transients = Transients;
291         
292         precise = current_timefx->precise_button.get_active();
293         
294         txt = current_timefx->stretch_opts_selector.get_active_text ();
295
296         if (txt == rb_opt_strings[0]) {
297                 transients = NoTransients; peaklock = false; longwin = true; shortwin = false; 
298         } else if (txt == rb_opt_strings[1]) {
299                 transients = NoTransients; peaklock = false; longwin = false; shortwin = false; 
300         } else if (txt == rb_opt_strings[2]) {
301                 transients = NoTransients; peaklock = true; longwin = false; shortwin = false; 
302         } else if (txt == rb_opt_strings[3]) {
303                 transients = BandLimitedTransients; peaklock = true; longwin = false; shortwin = false; 
304         } else if (txt == rb_opt_strings[5]) {
305                 transients = Transients; peaklock = false; longwin = false; shortwin = true; 
306         } else {
307                 /* default/4 */
308
309                 transients = Transients; peaklock = true; longwin = false; shortwin = false; 
310         };
311
312
313         if (realtime)    options |= RubberBandStretcher::OptionProcessRealTime;
314         if (precise)     options |= RubberBandStretcher::OptionStretchPrecise;
315         if (!peaklock)   options |= RubberBandStretcher::OptionPhaseIndependent;
316         if (longwin)     options |= RubberBandStretcher::OptionWindowLong;
317         if (shortwin)    options |= RubberBandStretcher::OptionWindowShort;
318                 
319         switch (transients) {
320         case NoTransients:
321                 options |= RubberBandStretcher::OptionTransientsSmooth;
322                 break;
323         case BandLimitedTransients:
324                 options |= RubberBandStretcher::OptionTransientsMixed;
325                 break;
326         case Transients:
327                 options |= RubberBandStretcher::OptionTransientsCrisp;
328                 break;
329         }
330
331         current_timefx->request.opts = (int) options;
332 #else
333         current_timefx->request.quick_seek = current_timefx->quick_button.get_active();
334         current_timefx->request.antialias = !current_timefx->antialias_button.get_active();
335 #endif
336         current_timefx->request.progress = 0.0f;
337         current_timefx->request.done = false;
338         current_timefx->request.cancel = false;
339         
340         /* re-connect the cancel button and delete events */
341         
342         current_timefx->first_cancel.disconnect();
343         current_timefx->first_delete.disconnect();
344         
345         current_timefx->first_cancel = current_timefx->cancel_button->signal_clicked().connect 
346                 (mem_fun (current_timefx, &TimeFXDialog::cancel_in_progress));
347         current_timefx->first_delete = current_timefx->signal_delete_event().connect 
348                 (mem_fun (current_timefx, &TimeFXDialog::delete_in_progress));
349
350         if (pthread_create_and_store ("timefx", &current_timefx->request.thread, 0, timefx_thread, current_timefx)) {
351                 current_timefx->hide ();
352                 error << _("timefx cannot be started - thread creation error") << endmsg;
353                 return -1;
354         }
355
356         pthread_detach (current_timefx->request.thread);
357
358         sigc::connection c = Glib::signal_timeout().connect (mem_fun (current_timefx, &TimeFXDialog::update_progress), 100);
359
360         while (!current_timefx->request.done && !current_timefx->request.cancel) {
361                 gtk_main_iteration ();
362         }
363
364         c.disconnect ();
365         
366         current_timefx->hide ();
367         return current_timefx->status;
368 }
369
370 void
371 Editor::do_timefx (TimeFXDialog& dialog)
372 {
373         Track*    t;
374         boost::shared_ptr<Playlist> playlist;
375         boost::shared_ptr<Region>   new_region;
376         bool in_command = false;
377         
378         for (RegionSelection::iterator i = dialog.regions.begin(); i != dialog.regions.end(); ) {
379                 AudioRegionView* arv = dynamic_cast<AudioRegionView*>(*i);
380
381                 if (!arv) {
382                         continue;
383                 }
384
385                 boost::shared_ptr<AudioRegion> region (arv->audio_region());
386                 TimeAxisView* tv = &(arv->get_time_axis_view());
387                 RouteTimeAxisView* rtv;
388                 RegionSelection::iterator tmp;
389                 
390                 tmp = i;
391                 ++tmp;
392
393                 if ((rtv = dynamic_cast<RouteTimeAxisView*> (tv)) == 0) {
394                         i = tmp;
395                         continue;
396                 }
397
398                 if ((t = dynamic_cast<Track*> (rtv->route().get())) == 0) {
399                         i = tmp;
400                         continue;
401                 }
402         
403                 if ((playlist = t->diskstream()->playlist()) == 0) {
404                         i = tmp;
405                         continue;
406                 }
407
408                 if (dialog.request.cancel) {
409                         /* we were cancelled */
410                         dialog.status = 1;
411                         return;
412                 }
413
414                 Filter* fx;
415
416                 if (dialog.pitching) {
417                         fx = new Pitch (*session, dialog.request);
418                 } else {
419                         fx = new Stretch (*session, dialog.request);
420                 }
421
422                 if (fx->run (region)) {
423                         dialog.status = -1;
424                         dialog.request.done = true;
425                         delete fx;
426                         return;
427                 }
428
429                 if (!fx->results.empty()) {
430                         new_region = fx->results.front();
431
432                         if (!in_command) {
433                                 begin_reversible_command (dialog.pitching ? _("pitch shift") : _("time stretch"));
434                                 in_command = true;
435                         }
436
437                         XMLNode &before = playlist->get_state();
438                         playlist->replace_region (region, new_region, region->position());
439                         XMLNode &after = playlist->get_state();
440                         session->add_command (new MementoCommand<Playlist>(*playlist, &before, &after));
441                 }
442
443                 i = tmp;
444                 delete fx;
445         }
446
447         if (in_command) {
448                 commit_reversible_command ();
449         }
450
451         dialog.status = 0;
452         dialog.request.done = true;
453 }
454
455 void*
456 Editor::timefx_thread (void *arg)
457 {
458         PBD::ThreadCreated (pthread_self(), X_("TimeFX"));
459
460         TimeFXDialog* tsd = static_cast<TimeFXDialog*>(arg);
461
462         pthread_setcanceltype (PTHREAD_CANCEL_ASYNCHRONOUS, 0);
463
464         tsd->editor.do_timefx (*tsd);
465
466         return 0;
467 }
468