ardour
editor_timefx.cc
Go to the documentation of this file.
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 #include <ctime>
24 #include <string>
25 #include <set>
26 
27 #include "pbd/error.h"
28 #include "pbd/pthread_utils.h"
29 #include "pbd/memento_command.h"
31 
32 #include <gtkmm2ext/utils.h>
33 
34 #include "audio_region_view.h"
35 #include "audio_time_axis.h"
36 #include "editor.h"
37 #include "region_selection.h"
38 #include "time_fx_dialog.h"
39 
40 #include "ardour/audioregion.h"
41 #include "ardour/midi_stretch.h"
42 #include "ardour/pitch.h"
43 #include "ardour/region.h"
44 #include "ardour/session.h"
45 #include "ardour/stretch.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 Gtk;
58 using namespace Gtkmm2ext;
59 
61 int
63 {
64  RegionList audio;
65  RegionList midi;
66  int aret;
67 
68  begin_reversible_command (_("stretch/shrink"));
69 
70  for (RegionSelection::iterator i = regions.begin(); i != regions.end(); ++i) {
71  if ((*i)->region()->data_type() == DataType::AUDIO) {
72  audio.push_back ((*i)->region());
73  } else if ((*i)->region()->data_type() == DataType::MIDI) {
74  midi.push_back ((*i)->region());
75  }
76  }
77 
78  if ((aret = time_fx (audio, fraction, false)) != 0) {
79  commit_reversible_command ();
80  return aret;
81  }
82 
83  set<boost::shared_ptr<Playlist> > midi_playlists_affected;
84 
85  for (RegionList::iterator i = midi.begin(); i != midi.end(); ++i) {
86  boost::shared_ptr<Playlist> playlist = (*i)->playlist();
87 
88  if (playlist) {
89  playlist->clear_changes ();
90  }
91 
92  }
93 
94  ARDOUR::TimeFXRequest request;
95  request.time_fraction = fraction;
96 
97  for (RegionList::iterator i = midi.begin(); i != midi.end(); ++i) {
98  boost::shared_ptr<Playlist> playlist = (*i)->playlist();
99 
100  if (!playlist) {
101  continue;
102  }
103 
104  MidiStretch stretch (*_session, request);
105  stretch.run (*i);
106 
107  playlist->replace_region (regions.front()->region(), stretch.results[0],
108  regions.front()->region()->position());
109  midi_playlists_affected.insert (playlist);
110  }
111 
112  for (set<boost::shared_ptr<Playlist> >::iterator p = midi_playlists_affected.begin(); p != midi_playlists_affected.end(); ++p) {
113  _session->add_command (new StatefulDiffCommand (*p));
114  }
115 
116  commit_reversible_command ();
117 
118  return 0;
119 }
120 
121 int
123 {
124  RegionList rl;
125 
126  for (RegionSelection::iterator i = regions.begin(); i != regions.end(); ++i) {
127  rl.push_back ((*i)->region());
128  }
129 
130  begin_reversible_command (_("pitch shift"));
131 
132  int ret = time_fx (rl, fraction, true);
133 
134  if (ret == 0) {
135  commit_reversible_command ();
136  }
137 
138  return ret;
139 }
140 
144 int
145 Editor::time_fx (RegionList& regions, float val, bool pitching)
146 {
147  delete current_timefx;
148  current_timefx = new TimeFXDialog (*this, pitching);
149  current_timefx->regions = regions;
150 
151  /* See if we have any audio regions on our list */
152  RegionList::iterator i = regions.begin ();
153  while (i != regions.end() && boost::dynamic_pointer_cast<AudioRegion> (*i) == 0) {
154  ++i;
155  }
156 
157  if (i == regions.end ()) {
158  /* No audio regions; we can just do the timefx without a dialogue */
159  do_timefx ();
160  return 0;
161  }
162 
163  switch (current_timefx->run ()) {
164  case RESPONSE_ACCEPT:
165  break;
166  default:
167  current_timefx->hide ();
168  return 1;
169  }
170 
171  current_timefx->status = 0;
172 
173  if (pitching) {
174 
175  float cents = current_timefx->pitch_octave_adjustment.get_value() * 1200.0;
176  float pitch_fraction;
177  cents += current_timefx->pitch_semitone_adjustment.get_value() * 100.0;
178  cents += current_timefx->pitch_cent_adjustment.get_value();
179 
180  if (cents == 0.0) {
181  // user didn't change anything
182  current_timefx->hide ();
183  return 0;
184  }
185 
186  // one octave == 1200 cents
187  // adding one octave doubles the frequency
188  // ratio is 2^^octaves
189 
190  pitch_fraction = pow(2, cents/1200);
191 
192  current_timefx->request.time_fraction = 1.0;
193  current_timefx->request.pitch_fraction = pitch_fraction;
194 
195  } else {
196 
197  current_timefx->request.time_fraction = val;
198  current_timefx->request.pitch_fraction = 1.0;
199 
200  }
201 
202 #ifdef USE_RUBBERBAND
203  /* parse options */
204 
205  RubberBandStretcher::Options options = 0;
206 
207  bool realtime = false;
208  bool precise = false;
209  bool peaklock = true;
210  bool longwin = false;
211  bool shortwin = false;
212  bool preserve_formants = false;
213  string txt;
214 
215  enum {
216  NoTransients,
217  BandLimitedTransients,
218  Transients
219  } transients = Transients;
220 
221  precise = current_timefx->precise_button.get_active();
222  preserve_formants = current_timefx->preserve_formants_button.get_active();
223 
224  txt = current_timefx->stretch_opts_selector.get_active_text ();
225 
226  for (int i = 0; i <= 6; i++) {
227  if (txt == rb_opt_strings[i]) {
228  rb_current_opt = i;
229  break;
230  }
231  }
232 
233  switch (rb_current_opt) {
234  case 0:
235  transients = NoTransients; peaklock = false; longwin = true; shortwin = false;
236  break;
237  case 1:
238  transients = NoTransients; peaklock = false; longwin = false; shortwin = false;
239  break;
240  case 2:
241  transients = NoTransients; peaklock = true; longwin = false; shortwin = false;
242  break;
243  case 3:
244  transients = BandLimitedTransients; peaklock = true; longwin = false; shortwin = false;
245  break;
246  case 5:
247  transients = Transients; peaklock = false; longwin = false; shortwin = true;
248  break;
249  case 6:
250  transients = NoTransients;
251  precise = true;
252  preserve_formants = false;
253  current_timefx->request.pitch_fraction = 1/val;
254  shortwin = true;
255  // peaklock = false;
256  break;
257  default:
258  /* default/4 */
259  transients = Transients; peaklock = true; longwin = false; shortwin = false;
260  break;
261  };
262 
263  if (realtime) options |= RubberBandStretcher::OptionProcessRealTime;
264  if (precise) options |= RubberBandStretcher::OptionStretchPrecise;
265  if (preserve_formants) options |= RubberBandStretcher::OptionFormantPreserved;
266  if (!peaklock) options |= RubberBandStretcher::OptionPhaseIndependent;
267  if (longwin) options |= RubberBandStretcher::OptionWindowLong;
268  if (shortwin) options |= RubberBandStretcher::OptionWindowShort;
269 
270  switch (transients) {
271  case NoTransients:
272  options |= RubberBandStretcher::OptionTransientsSmooth;
273  break;
274  case BandLimitedTransients:
275  options |= RubberBandStretcher::OptionTransientsMixed;
276  break;
277  case Transients:
278  options |= RubberBandStretcher::OptionTransientsCrisp;
279  break;
280  }
281 
282  current_timefx->request.opts = (int) options;
283 #else
284  current_timefx->request.quick_seek = current_timefx->quick_button.get_active();
285  current_timefx->request.antialias = !current_timefx->antialias_button.get_active();
286 #endif
287  current_timefx->request.done = false;
288  current_timefx->request.cancel = false;
289 
290  /* re-connect the cancel button and delete events */
291 
292  current_timefx->first_cancel.disconnect();
293  current_timefx->first_delete.disconnect();
294 
295  current_timefx->first_cancel = current_timefx->cancel_button->signal_clicked().connect
296  (sigc::mem_fun (current_timefx, &TimeFXDialog::cancel_in_progress));
297  current_timefx->first_delete = current_timefx->signal_delete_event().connect
298  (sigc::mem_fun (current_timefx, &TimeFXDialog::delete_in_progress));
299 
300  if (pthread_create_and_store ("timefx", &current_timefx->request.thread, timefx_thread, current_timefx)) {
301  current_timefx->hide ();
302  error << _("timefx cannot be started - thread creation error") << endmsg;
303  return -1;
304  }
305 
306  pthread_detach (current_timefx->request.thread);
307 
308  while (!current_timefx->request.done && !current_timefx->request.cancel) {
309  gtk_main_iteration ();
310  }
311 
312  pthread_join (current_timefx->request.thread, 0);
313 
314  current_timefx->hide ();
315  return current_timefx->status;
316 }
317 
318 void
320 {
322  boost::shared_ptr<Region> new_region;
323  set<boost::shared_ptr<Playlist> > playlists_affected;
324 
325  uint32_t const N = current_timefx->regions.size ();
326 
327  for (RegionList::iterator i = current_timefx->regions.begin(); i != current_timefx->regions.end(); ++i) {
328  boost::shared_ptr<Playlist> playlist = (*i)->playlist();
329 
330  if (playlist) {
331  playlist->clear_changes ();
332  }
333  }
334 
335  for (RegionList::iterator i = current_timefx->regions.begin(); i != current_timefx->regions.end(); ++i) {
336 
338 
339  if (!region || (playlist = region->playlist()) == 0) {
340  continue;
341  }
342 
343  if (current_timefx->request.cancel) {
344  /* we were cancelled */
345  /* XXX what to do about playlists already affected ? */
346  current_timefx->status = 1;
347  return;
348  }
349 
350  Filter* fx;
351 
352  if (current_timefx->pitching) {
353  fx = new Pitch (*_session, current_timefx->request);
354  } else {
355 #ifdef USE_RUBBERBAND
356  fx = new RBStretch (*_session, current_timefx->request);
357 #else
358  fx = new STStretch (*_session, current_timefx->request);
359 #endif
360  }
361 
362  current_timefx->descend (1.0 / N);
363 
364  if (fx->run (region, current_timefx)) {
365  current_timefx->status = -1;
366  current_timefx->request.done = true;
367  delete fx;
368  return;
369  }
370 
371  if (!fx->results.empty()) {
372  new_region = fx->results.front();
373 
374  playlist->replace_region (region, new_region, region->position());
375  playlists_affected.insert (playlist);
376  }
377 
378  current_timefx->ascend ();
379  delete fx;
380  }
381 
382  for (set<boost::shared_ptr<Playlist> >::iterator p = playlists_affected.begin(); p != playlists_affected.end(); ++p) {
383  _session->add_command (new StatefulDiffCommand (*p));
384  }
385 
386  current_timefx->status = 0;
387  current_timefx->request.done = true;
388 }
389 
390 void*
392 {
393  SessionEvent::create_per_thread_pool ("timefx events", 64);
394 
395  TimeFXDialog* tsd = static_cast<TimeFXDialog*>(arg);
396 
397  pthread_setcanceltype (PTHREAD_CANCEL_ASYNCHRONOUS, 0);
398 
399  tsd->editor.do_timefx ();
400 
401  /* GACK! HACK! sleep for a bit so that our request buffer for the GUI
402  event loop doesn't die before any changes we made are processed
403  by the GUI ...
404  */
405 
406 #ifdef PLATFORM_WINDOWS
407  Glib::usleep(2 * G_USEC_PER_SEC);
408 #else
409  struct timespec t = { 2, 0 };
410  nanosleep (&t, 0);
411 #endif
412  return 0;
413 }
414 
Definition: ardour_ui.h:130
shared_ptr< T > dynamic_pointer_cast(shared_ptr< U > const &r)
Definition: shared_ptr.hpp:396
static int N
Definition: signals_test.cc:27
virtual int run(boost::shared_ptr< ARDOUR::Region >, Progress *progress=0)=0
Definition: Beats.hpp:239
LIBPBD_API Transmitter error
std::ostream & endmsg(std::ostream &ostr)
Definition: transmitter.h:71
LIBARDOUR_API PBD::PropertyDescriptor< float > stretch
Definition: region.cc:70
gint delete_in_progress(GdkEventAny *)
#define _(Text)
Definition: i18n.h:11
int time_stretch(RegionSelection &, float fraction)
void replace_region(boost::shared_ptr< Region > old, boost::shared_ptr< Region > newr, framepos_t pos)
Definition: playlist.cc:772
Definition: amp.h:29
int time_fx(ARDOUR::RegionList &, float val, bool pitching)
void cancel_in_progress()
Editor & editor
LIBARDOUR_API PBD::PropertyDescriptor< bool > regions
Definition: playlist.cc:51
std::list< boost::shared_ptr< Region > > RegionList
Definition: types.h:87
framepos_t position() const
Definition: region.h:112
std::vector< boost::shared_ptr< ARDOUR::Region > > results
Definition: filter.h:41
static void * timefx_thread(void *arg)
Definition: debug.h:30
void clear_changes()
Definition: stateful.cc:184
int run(boost::shared_ptr< ARDOUR::Region >, Progress *progress=0)
Definition: midi_stretch.cc:46
LIBPBD_API int pthread_create_and_store(std::string name, pthread_t *thread, void *(*start_routine)(void *), void *arg)
int pitch_shift(RegionSelection &, float cents)
void do_timefx()
boost::shared_ptr< ARDOUR::Playlist > playlist() const
Definition: region.h:251