clean up port insert port count/config mess, maybe
[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/pitch.h>
46
47 #ifdef USE_RUBBERBAND
48 #include <rubberband/RubberBandStretcher.h>
49 using namespace RubberBand;
50 #endif
51
52 #include "i18n.h"
53
54 using namespace std;
55 using namespace ARDOUR;
56 using namespace PBD;
57 using namespace sigc;
58 using namespace Gtk;
59 using namespace Gtkmm2ext;
60
61 Editor::TimeFXDialog::TimeFXDialog (Editor& e, bool pitch)
62         : ArdourDialog (X_("time fx dialog")),
63           editor (e),
64           pitching (pitch),
65           pitch_octave_adjustment (0.0, -4.0, 4.0, 1, 2.0),
66           pitch_semitone_adjustment (0.0, -12.0, 12.0, 1.0, 4.0),
67           pitch_cent_adjustment (0.0, -499.0, 500.0, 5.0, 15.0),
68           pitch_octave_spinner (pitch_octave_adjustment),
69           pitch_semitone_spinner (pitch_semitone_adjustment),
70           pitch_cent_spinner (pitch_cent_adjustment),
71           quick_button (_("Quick but Ugly")),
72           antialias_button (_("Skip Anti-aliasing")),
73           stretch_opts_label (_("Contents:")),
74           precise_button (_("Strict Linear"))
75 {
76         set_modal (true);
77         set_position (Gtk::WIN_POS_MOUSE);
78         set_name (N_("TimeFXDialog"));
79
80         WindowTitle title(Glib::get_application_name());
81         if (pitching) {
82                 title += _("Pitch Shift");
83         } else {
84                 title += _("Time Stretch");
85         }
86         set_title(title.get_string());
87
88         cancel_button = add_button (_("Cancel"), Gtk::RESPONSE_CANCEL);
89
90         get_vbox()->set_spacing (5);
91         get_vbox()->set_border_width (12);
92
93         if (pitching) {
94
95                 upper_button_box.set_spacing (5);
96                 upper_button_box.set_border_width (5);
97                 
98                 Gtk::Label* l;
99
100                 l = manage (new Label (_("Octaves")));
101                 upper_button_box.pack_start (*l, false, false);
102                 upper_button_box.pack_start (pitch_octave_spinner, false, false);
103
104                 l = manage (new Label (_("Semitones (12TET)")));
105                 upper_button_box.pack_start (*l, false, false);
106                 upper_button_box.pack_start (pitch_semitone_spinner, false, false);
107
108                 l = manage (new Label (_("Cents")));
109                 upper_button_box.pack_start (*l, false, false);
110                 upper_button_box.pack_start (pitch_cent_spinner, false, false);
111
112                 pitch_cent_spinner.set_digits (1);
113
114                 add_button (_("Shift"), Gtk::RESPONSE_ACCEPT);
115
116                 get_vbox()->pack_start (upper_button_box, false, false);
117
118         } else {
119
120 #ifdef USE_RUBBERBAND
121                 opts_box.set_spacing (5);
122                 opts_box.set_border_width (5);
123                 vector<string> strings;
124
125                 set_popdown_strings (stretch_opts_selector, editor.rb_opt_strings);
126                 /* set default */
127                 stretch_opts_selector.set_active_text (editor.rb_opt_strings[4]);
128
129                 opts_box.pack_start (precise_button, false, false);
130                 opts_box.pack_start (stretch_opts_label, false, false);
131                 opts_box.pack_start (stretch_opts_selector, false, false);
132
133                 get_vbox()->pack_start (opts_box, false, false);
134
135 #else
136                 upper_button_box.set_homogeneous (true);
137                 upper_button_box.set_spacing (5);
138                 upper_button_box.set_border_width (5);
139
140                 upper_button_box.pack_start (quick_button, true, true);
141                 upper_button_box.pack_start (antialias_button, true, true);
142
143                 quick_button.set_name (N_("TimeFXButton"));
144                 antialias_button.set_name (N_("TimeFXButton"));
145
146                 get_vbox()->pack_start (upper_button_box, false, false);
147
148 #endif  
149                 add_button (_("Stretch/Shrink"), Gtk::RESPONSE_ACCEPT);
150         }
151
152         get_vbox()->pack_start (progress_bar);
153
154         progress_bar.set_name (N_("TimeFXProgress"));
155
156         show_all_children ();
157 }
158
159 gint
160 Editor::TimeFXDialog::update_progress ()
161 {
162         progress_bar.set_fraction (request.progress);
163         return !request.done;
164 }
165
166 void
167 Editor::TimeFXDialog::cancel_in_progress ()
168 {
169         status = -2;
170         request.cancel = true;
171         first_cancel.disconnect();
172 }
173
174 gint
175 Editor::TimeFXDialog::delete_in_progress (GdkEventAny* ev)
176 {
177         status = -2;
178         request.cancel = true;
179         first_delete.disconnect();
180         return TRUE;
181 }
182
183 int
184 Editor::time_stretch (RegionSelection& regions, float fraction)
185 {
186         return time_fx (regions, fraction, false);
187 }
188
189 int
190 Editor::pitch_shift (RegionSelection& regions, float fraction)
191 {
192         return time_fx (regions, fraction, true);
193 }
194
195 int
196 Editor::time_fx (RegionSelection& regions, float val, bool pitching)
197 {
198         if (current_timefx != 0) {
199                 delete current_timefx;
200         }
201
202         current_timefx = new TimeFXDialog (*this, pitching);
203
204         current_timefx->progress_bar.set_fraction (0.0f);
205
206         switch (current_timefx->run ()) {
207         case RESPONSE_ACCEPT:
208                 break;
209         default:
210                 current_timefx->hide ();
211                 return 1;
212         }
213
214         current_timefx->status = 0;
215         current_timefx->regions = regions;
216
217         if (pitching) {
218
219                 float cents = current_timefx->pitch_octave_adjustment.get_value() * 1200.0;
220                 float pitch_fraction;
221                 cents += current_timefx->pitch_semitone_adjustment.get_value() * 100.0;
222                 cents += current_timefx->pitch_cent_adjustment.get_value();
223
224                 if (cents == 0.0) {
225                         // user didn't change anything
226                         current_timefx->hide ();
227                         return 0;
228                 }
229
230                 // one octave == 1200 cents
231                 // adding one octave doubles the frequency
232                 // ratio is 2^^octaves
233                                 
234                 pitch_fraction = pow(2, cents/1200);
235
236                 current_timefx->request.time_fraction = 1.0;
237                 current_timefx->request.pitch_fraction = pitch_fraction;
238                 
239         } else {
240
241                 current_timefx->request.time_fraction = val;
242                 current_timefx->request.pitch_fraction = 1.0;
243
244         }
245
246 #ifdef USE_RUBBERBAND
247         /* parse options */
248
249         RubberBandStretcher::Options options = 0;
250
251         bool realtime = false;
252         bool precise = false;
253         bool peaklock = true;
254         bool softening = true;
255         bool longwin = false;
256         bool shortwin = false;
257         string txt;
258
259         enum {
260                 NoTransients,
261                 BandLimitedTransients,
262                 Transients
263         } transients = Transients;
264         
265         precise = current_timefx->precise_button.get_active();
266         
267         txt = current_timefx->stretch_opts_selector.get_active_text ();
268
269         if (txt == rb_opt_strings[0]) {
270                 transients = NoTransients; peaklock = false; longwin = true; shortwin = false; 
271         } else if (txt == rb_opt_strings[1]) {
272                 transients = NoTransients; peaklock = false; longwin = false; shortwin = false; 
273         } else if (txt == rb_opt_strings[2]) {
274                 transients = NoTransients; peaklock = true; longwin = false; shortwin = false; 
275         } else if (txt == rb_opt_strings[3]) {
276                 transients = BandLimitedTransients; peaklock = true; longwin = false; shortwin = false; 
277         } else if (txt == rb_opt_strings[5]) {
278                 transients = Transients; peaklock = false; longwin = false; shortwin = true; 
279         } else {
280                 /* default/4 */
281
282                 transients = Transients; peaklock = true; longwin = false; shortwin = false; 
283         };
284
285
286         if (realtime)    options |= RubberBandStretcher::OptionProcessRealTime;
287         if (precise)     options |= RubberBandStretcher::OptionStretchPrecise;
288         if (!peaklock)   options |= RubberBandStretcher::OptionPhaseIndependent;
289         if (!softening)  options |= RubberBandStretcher::OptionPhasePeakLocked;
290         if (longwin)     options |= RubberBandStretcher::OptionWindowLong;
291         if (shortwin)    options |= RubberBandStretcher::OptionWindowShort;
292                 
293         switch (transients) {
294         case NoTransients:
295                 options |= RubberBandStretcher::OptionTransientsSmooth;
296                 break;
297         case BandLimitedTransients:
298                 options |= RubberBandStretcher::OptionTransientsMixed;
299                 break;
300         case Transients:
301                 options |= RubberBandStretcher::OptionTransientsCrisp;
302                 break;
303         }
304
305         current_timefx->request.opts = (int) options;
306 #else
307         current_timefx->request.quick_seek = current_timefx->quick_button.get_active();
308         current_timefx->request.antialias = !current_timefx->antialias_button.get_active();
309 #endif
310         current_timefx->request.progress = 0.0f;
311         current_timefx->request.done = false;
312         current_timefx->request.cancel = false;
313         
314         /* re-connect the cancel button and delete events */
315         
316         current_timefx->first_cancel.disconnect();
317         current_timefx->first_delete.disconnect();
318         
319         current_timefx->first_cancel = current_timefx->cancel_button->signal_clicked().connect 
320                 (mem_fun (current_timefx, &TimeFXDialog::cancel_in_progress));
321         current_timefx->first_delete = current_timefx->signal_delete_event().connect 
322                 (mem_fun (current_timefx, &TimeFXDialog::delete_in_progress));
323
324         if (pthread_create_and_store ("timefx", &current_timefx->request.thread, 0, timefx_thread, current_timefx)) {
325                 current_timefx->hide ();
326                 error << _("timefx cannot be started - thread creation error") << endmsg;
327                 return -1;
328         }
329
330         pthread_detach (current_timefx->request.thread);
331
332         sigc::connection c = Glib::signal_timeout().connect (mem_fun (current_timefx, &TimeFXDialog::update_progress), 100);
333
334         while (!current_timefx->request.done && !current_timefx->request.cancel) {
335                 gtk_main_iteration ();
336         }
337
338         c.disconnect ();
339         
340         current_timefx->hide ();
341         return current_timefx->status;
342 }
343
344 void
345 Editor::do_timefx (TimeFXDialog& dialog)
346 {
347         Track*    t;
348         boost::shared_ptr<Playlist> playlist;
349         boost::shared_ptr<Region>   new_region;
350         bool in_command = false;
351         
352         for (RegionSelection::iterator i = dialog.regions.begin(); i != dialog.regions.end(); ) {
353                 AudioRegionView* arv = dynamic_cast<AudioRegionView*>(*i);
354
355                 if (!arv) {
356                         continue;
357                 }
358
359                 boost::shared_ptr<AudioRegion> region (arv->audio_region());
360                 TimeAxisView* tv = &(arv->get_time_axis_view());
361                 RouteTimeAxisView* rtv;
362                 RegionSelection::iterator tmp;
363                 
364                 tmp = i;
365                 ++tmp;
366
367                 if ((rtv = dynamic_cast<RouteTimeAxisView*> (tv)) == 0) {
368                         i = tmp;
369                         continue;
370                 }
371
372                 if ((t = dynamic_cast<Track*> (rtv->route().get())) == 0) {
373                         i = tmp;
374                         continue;
375                 }
376         
377                 if ((playlist = t->diskstream()->playlist()) == 0) {
378                         i = tmp;
379                         continue;
380                 }
381
382                 if (dialog.request.cancel) {
383                         /* we were cancelled */
384                         dialog.status = 1;
385                         return;
386                 }
387
388                 AudioFilter* fx;
389
390                 if (dialog.pitching) {
391                         fx = new Pitch (*session, dialog.request);
392                 } else {
393                         fx = new Stretch (*session, dialog.request);
394                 }
395
396                 if (fx->run (region)) {
397                         dialog.status = -1;
398                         dialog.request.done = true;
399                         delete fx;
400                         return;
401                 }
402
403                 if (!fx->results.empty()) {
404                         new_region = fx->results.front();
405
406                         if (!in_command) {
407                                 begin_reversible_command (dialog.pitching ? _("pitch shift") : _("time stretch"));
408                                 in_command = true;
409                         }
410
411                         XMLNode &before = playlist->get_state();
412                         playlist->replace_region (region, new_region, region->position());
413                         XMLNode &after = playlist->get_state();
414                         session->add_command (new MementoCommand<Playlist>(*playlist, &before, &after));
415                 }
416
417                 i = tmp;
418                 delete fx;
419         }
420
421         if (in_command) {
422                 commit_reversible_command ();
423         }
424
425         dialog.status = 0;
426         dialog.request.done = true;
427 }
428
429 void*
430 Editor::timefx_thread (void *arg)
431 {
432         PBD::ThreadCreated (pthread_self(), X_("TimeFX"));
433
434         TimeFXDialog* tsd = static_cast<TimeFXDialog*>(arg);
435
436         pthread_setcanceltype (PTHREAD_CANCEL_ASYNCHRONOUS, 0);
437
438         tsd->editor.do_timefx (*tsd);
439
440         return 0;
441 }
442