df0a73c9652b119f52f218dafe3738cee223b544
[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 <cstdlib>
21 #include <cmath>
22
23 #include <string>
24
25 #include <pbd/error.h>
26 #include <pbd/pthread_utils.h>
27 #include <pbd/memento_command.h>
28
29 #include <gtkmm2ext/window_title.h>
30
31 #include "editor.h"
32 #include "audio_time_axis.h"
33 #include "audio_region_view.h"
34 #include "region_selection.h"
35
36 #include <ardour/session.h>
37 #include <ardour/region.h>
38 #include <ardour/audioplaylist.h>
39 #include <ardour/audio_track.h>
40 #include <ardour/audioregion.h>
41 #include <ardour/audio_diskstream.h>
42 #include <ardour/stretch.h>
43
44 #include "i18n.h"
45
46 using namespace ARDOUR;
47 using namespace PBD;
48 using namespace sigc;
49 using namespace Gtk;
50 using namespace Gtkmm2ext;
51
52 Editor::TimeStretchDialog::TimeStretchDialog (Editor& e)
53         : ArdourDialog ("time stretch dialog"),
54           editor (e),
55           quick_button (_("Quick but Ugly")),
56           antialias_button (_("Skip Anti-aliasing"))
57 {
58         set_modal (true);
59         set_position (Gtk::WIN_POS_MOUSE);
60         set_name (N_("TimeStretchDialog"));
61
62         WindowTitle title(Glib::get_application_name());
63         title += _("Timestretch");
64         set_title(title.get_string());
65
66         get_vbox()->set_spacing (5);
67         get_vbox()->set_border_width (5);
68         get_vbox()->pack_start (upper_button_box);
69         get_vbox()->pack_start (progress_bar);
70
71         upper_button_box.set_homogeneous (true);
72         upper_button_box.set_spacing (5);
73         upper_button_box.set_border_width (5);
74         upper_button_box.pack_start (quick_button, true, true);
75         upper_button_box.pack_start (antialias_button, true, true);
76
77         action_button = add_button (_("Stretch/Shrink it"), Gtk::RESPONSE_ACCEPT);
78         cancel_button = add_button (_("Cancel"), Gtk::RESPONSE_CANCEL);
79
80         quick_button.set_name (N_("TimeStretchButton"));
81         antialias_button.set_name (N_("TimeStretchButton"));
82         progress_bar.set_name (N_("TimeStretchProgress"));
83
84         show_all_children ();
85 }
86
87 gint
88 Editor::TimeStretchDialog::update_progress ()
89 {
90         progress_bar.set_fraction (request.progress);
91         return !request.done;
92 }
93
94 void
95 Editor::TimeStretchDialog::cancel_timestretch_in_progress ()
96 {
97         status = -2;
98         request.cancel = true;
99         first_cancel.disconnect();
100 }
101
102 gint
103 Editor::TimeStretchDialog::delete_timestretch_in_progress (GdkEventAny* ev)
104 {
105         status = -2;
106         request.cancel = true;
107         first_delete.disconnect();
108         return TRUE;
109 }
110
111 int
112 Editor::run_timestretch (RegionSelection& regions, float fraction)
113 {
114         if (current_timestretch == 0) {
115                 current_timestretch = new TimeStretchDialog (*this);
116         }
117
118         current_timestretch->progress_bar.set_fraction (0.0f);
119
120         switch (current_timestretch->run ()) {
121         case RESPONSE_ACCEPT:
122                 break;
123         default:
124                 current_timestretch->hide ();
125                 return 1;
126         }
127
128         current_timestretch->status = 0;
129         current_timestretch->regions = regions;
130         current_timestretch->request.fraction = fraction;
131         current_timestretch->request.quick_seek = current_timestretch->quick_button.get_active();
132         current_timestretch->request.antialias = !current_timestretch->antialias_button.get_active();
133         current_timestretch->request.progress = 0.0f;
134         current_timestretch->request.done = false;
135         current_timestretch->request.cancel = false;
136         
137         /* re-connect the cancel button and delete events */
138         
139         current_timestretch->first_cancel.disconnect();
140         current_timestretch->first_delete.disconnect();
141         
142         current_timestretch->first_cancel = current_timestretch->cancel_button->signal_clicked().connect 
143                 (mem_fun (current_timestretch, &TimeStretchDialog::cancel_timestretch_in_progress));
144         current_timestretch->first_delete = current_timestretch->signal_delete_event().connect 
145                 (mem_fun (current_timestretch, &TimeStretchDialog::delete_timestretch_in_progress));
146
147         if (pthread_create_and_store ("timestretch", &current_timestretch->request.thread, 0, timestretch_thread, current_timestretch)) {
148                 current_timestretch->hide ();
149                 error << _("timestretch cannot be started - thread creation error") << endmsg;
150                 return -1;
151         }
152
153         pthread_detach (current_timestretch->request.thread);
154
155         sigc::connection c = Glib::signal_timeout().connect (mem_fun (current_timestretch, &TimeStretchDialog::update_progress), 100);
156
157         while (!current_timestretch->request.done) {
158                 gtk_main_iteration ();
159         }
160
161         c.disconnect ();
162         
163         current_timestretch->hide ();
164         return current_timestretch->status;
165 }
166
167 void
168 Editor::do_timestretch (TimeStretchDialog& dialog)
169 {
170         Track*    t;
171         boost::shared_ptr<Playlist> playlist;
172         boost::shared_ptr<Region>   new_region;
173
174         for (RegionSelection::iterator i = dialog.regions.begin(); i != dialog.regions.end(); ) {
175                 AudioRegionView* arv = dynamic_cast<AudioRegionView*>(*i);
176                 if (!arv)
177                         continue;
178
179                 boost::shared_ptr<AudioRegion> region (arv->audio_region());
180                 TimeAxisView* tv = &(arv->get_time_axis_view());
181                 RouteTimeAxisView* rtv;
182                 RegionSelection::iterator tmp;
183                 
184                 tmp = i;
185                 ++tmp;
186
187                 if ((rtv = dynamic_cast<RouteTimeAxisView*> (tv)) == 0) {
188                         i = tmp;
189                         continue;
190                 }
191
192                 if ((t = dynamic_cast<Track*> (rtv->route().get())) == 0) {
193                         i = tmp;
194                         continue;
195                 }
196         
197                 if ((playlist = t->diskstream()->playlist()) == 0) {
198                         i = tmp;
199                         continue;
200                 }
201
202                 if (dialog.request.cancel) {
203                         /* we were cancelled */
204                         dialog.status = 1;
205                         return;
206                 }
207
208                 Stretch stretch (*session, dialog.request);
209
210                 if (stretch.run (region)) {
211                         dialog.status = -1;
212                         dialog.request.done = true;
213                         return;
214                 }
215
216                 if (!stretch.results.empty()) {
217                         new_region = stretch.results.front();
218
219                         XMLNode &before = playlist->get_state();
220                         playlist->replace_region (region, new_region, region->position());
221                         XMLNode &after = playlist->get_state();
222                         session->add_command (new MementoCommand<Playlist>(*playlist, &before, &after));
223                 }
224
225                 i = tmp;
226         }
227
228         dialog.status = 0;
229         dialog.request.done = true;
230 }
231
232 void*
233 Editor::timestretch_thread (void *arg)
234 {
235         PBD::ThreadCreated (pthread_self(), X_("TimeFX"));
236
237         TimeStretchDialog* tsd = static_cast<TimeStretchDialog*>(arg);
238
239         pthread_setcanceltype (PTHREAD_CANCEL_ASYNCHRONOUS, 0);
240
241         tsd->editor.do_timestretch (*tsd);
242
243         return 0;
244 }
245