da01387ffdec7731597efb07bea1b5496f287b9a
[ardour.git] / gtk2_ardour / strip_silence_dialog.cc
1 /*
2     Copyright (C) 2009 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
22 #include <gtkmm/table.h>
23 #include <gtkmm/label.h>
24 #include <gtkmm/stock.h>
25 #include "ardour/audioregion.h"
26 #include "ardour/audiosource.h"
27
28 #include "ardour/dB.h"
29 #include "ardour_ui.h"
30 #include "ardour/session.h"
31
32 #include "gui_thread.h"
33 #include "strip_silence_dialog.h"
34 #include "canvas_impl.h"
35 #include "region_view.h"
36 #include "simpleline.h"
37 #include "waveview.h"
38 #include "simplerect.h"
39 #include "rgb_macros.h"
40 #include "i18n.h"
41 #include "logmeter.h"
42
43 using namespace ARDOUR;
44 using namespace std;
45 using namespace ArdourCanvas;
46
47 /** Construct Strip silence dialog box */
48 StripSilenceDialog::StripSilenceDialog (Session* s, list<RegionView*> const & v)
49         : ArdourDialog (_("Strip Silence"))
50         , ProgressReporter ()
51         , _minimum_length (X_("silence duration"), true, "SilenceDurationClock", true, false, true, false)
52         , _fade_length (X_("silence duration"), true, "SilenceDurationClock", true, false, true, false)
53         , _peaks_ready_connection (0)
54         , _destroying (false)
55 {
56         set_session (s);
57
58         for (list<RegionView*>::const_iterator r = v.begin(); r != v.end(); ++r) {
59                 views.push_back (ViewInterval (*r));
60         }
61
62         Gtk::HBox* hbox = Gtk::manage (new Gtk::HBox);
63         
64         Gtk::Table* table = Gtk::manage (new Gtk::Table (3, 3));
65         table->set_spacings (6);
66
67         int n = 0;
68
69         table->attach (*Gtk::manage (new Gtk::Label (_("Threshold"), 1, 0.5)), 0, 1, n, n + 1, Gtk::FILL);
70         table->attach (_threshold, 1, 2, n, n + 1, Gtk::FILL);
71         table->attach (*Gtk::manage (new Gtk::Label (_("dbFS"))), 2, 3, n, n + 1, Gtk::FILL);
72         ++n;
73         
74         _threshold.set_digits (1);
75         _threshold.set_increments (1, 10);
76         _threshold.set_range (-120, 0);
77         _threshold.set_value (-60);
78
79         table->attach (*Gtk::manage (new Gtk::Label (_("Minimum length"), 1, 0.5)), 0, 1, n, n + 1, Gtk::FILL);
80         table->attach (_minimum_length, 1, 2, n, n + 1, Gtk::FILL);
81         ++n;
82         
83         _minimum_length.set_session (s);
84         _minimum_length.set_mode (AudioClock::Frames);
85         _minimum_length.set (1000, true);
86
87         table->attach (*Gtk::manage (new Gtk::Label (_("Fade length"), 1, 0.5)), 0, 1, n, n + 1, Gtk::FILL);
88         table->attach (_fade_length, 1, 2, n, n + 1, Gtk::FILL);
89         ++n;
90
91         _fade_length.set_session (s);
92         _fade_length.set_mode (AudioClock::Frames);
93         _fade_length.set (64, true);
94
95         hbox->pack_start (*table);
96
97         table = Gtk::manage (new Gtk::Table (3, 2));
98         table->set_spacings (6);
99         
100         n = 0;
101         
102         table->attach (*Gtk::manage (new Gtk::Label (_("Silent segments:"), 1, 0.5)), 3, 4, n, n + 1, Gtk::FILL);
103         table->attach (_segment_count_label, 5, 6, n, n + 1, Gtk::FILL);
104         _segment_count_label.set_alignment (0, 0.5);
105         ++n;
106
107         table->attach (*Gtk::manage (new Gtk::Label (_("Shortest silence:"), 1, 0.5)), 3, 4, n, n + 1, Gtk::FILL);
108         table->attach (_shortest_silence_label, 5, 6, n, n + 1, Gtk::FILL);
109         _shortest_silence_label.set_alignment (0, 0.5);
110         ++n;
111
112         table->attach (*Gtk::manage (new Gtk::Label (_("Shortest audible:"), 1, 0.5)), 3, 4, n, n + 1, Gtk::FILL);
113         table->attach (_shortest_audible_label, 5, 6, n, n + 1, Gtk::FILL);
114         _shortest_audible_label.set_alignment (0, 0.5);
115         ++n;
116         
117         hbox->pack_start (*table);
118
119         /* dummy label for padding */
120         hbox->pack_start (*Gtk::manage (new Gtk::Label ("")), true, true);
121
122         get_vbox()->pack_start (*hbox, false, false);
123
124         add_button (Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
125         add_button (Gtk::Stock::APPLY, Gtk::RESPONSE_OK);
126
127         get_vbox()->pack_start (_progress_bar, true, true);
128
129         show_all ();
130
131         _threshold.get_adjustment()->signal_value_changed().connect (sigc::mem_fun (*this, &StripSilenceDialog::threshold_changed));
132         _minimum_length.ValueChanged.connect (sigc::mem_fun (*this, &StripSilenceDialog::restart_thread));
133
134         update_silence_rects ();
135         update_threshold_line ();
136
137         /* Create a thread which runs while the dialogue is open to compute the silence regions */
138         Completed.connect (_completed_connection, MISSING_INVALIDATOR, ui_bind (&StripSilenceDialog::update, this), gui_context ());
139         _thread_should_finish = false;
140         pthread_create (&_thread, 0, StripSilenceDialog::_detection_thread_work, this);
141 }
142
143
144 StripSilenceDialog::~StripSilenceDialog ()
145 {
146         _destroying = true;
147         
148         /* Terminate our thread */
149         
150         _lock.lock ();
151         _interthread_info.cancel = true;
152         _thread_should_finish = true;
153         _lock.unlock ();
154
155         _run_cond.signal ();
156         pthread_join (_thread, 0);
157         
158         for (list<ViewInterval>::iterator v = views.begin(); v != views.end(); ++v) {
159                 (*v).view->drop_silent_frames ();
160         }
161         
162         delete _peaks_ready_connection;
163 }
164
165 void
166 StripSilenceDialog::update_threshold_line ()
167 {
168 #if 0
169         int n = 0;
170
171         /* Don't need to lock here as we're not reading the _waves silence details */
172
173         for (list<Wave*>::iterator i = _waves.begin(); i != _waves.end(); ++i) {
174                 (*i)->threshold_line->property_x1() = 0;
175                 (*i)->threshold_line->property_x2() = _wave_width;
176                 
177                 double const y = alt_log_meter (_threshold.get_value());
178
179                 (*i)->threshold_line->property_y1() = (n + 1 - y) * _wave_height;
180                 (*i)->threshold_line->property_y2() = (n + 1 - y) * _wave_height;
181         }
182
183         ++n;
184 #endif
185 }
186
187 void
188 StripSilenceDialog::update ()
189 {
190         cerr << "UPDATE!\n";
191         update_threshold_line ();
192         /* XXX: first one only?! */
193         // update_stats (_waves.front()->silence);
194
195         update_silence_rects ();
196         cerr << "UPDATE done\n";
197 }
198
199 void
200 StripSilenceDialog::update_silence_rects ()
201 {
202         uint32_t max_segments = 0;
203
204         /* Lock so that we don't contend with the detection thread for access to the silence regions */
205         Glib::Mutex::Lock lm (_lock);
206         
207         for (list<ViewInterval>::iterator v = views.begin(); v != views.end(); ++v) {
208                 (*v).view->set_silent_frames ((*v).intervals);
209                 max_segments = max (max_segments, (uint32_t) (*v).intervals.size());
210         }
211
212 #if 0
213         cerr << "minaudible in update " << min_audible << " minsilence = " << min_silence << endl;
214
215         if (min_audible > 0) {
216                 float ms, ma;
217                 char const * aunits;
218                 char const * sunits;
219
220                 ma = (float) min_audible/_session->frame_rate();
221                 ms = (float) min_silence/_session->frame_rate();
222
223                 /* ma and ms are now in seconds */
224
225                 if (ma >= 60.0) {
226                         aunits = _("minutes");
227                         //ma /= 60.0;
228                 } else if (min_audible < _session->frame_rate()) {
229                         aunits = _("msecs");
230                         ma *= 1000.0;
231                 } else {
232                         aunits = _("seconds");
233                 }
234
235                 if (ms >= 60.0) {
236                         sunits = _("minutes");
237                         //ms /= 60.0;
238                 } else if (min_silence < _session->frame_rate()) {
239                         sunits = _("ms");
240                         ms *= 1000.0;
241                 } else {
242                         sunits = _("s");
243                 }
244
245                 _segment_count_label.set_text (string_compose ("%1", max_segments));
246                 if (max_segments > 0) {
247                         _shortest_silence_label.set_text (string_compose ("%1 %2", ms, sunits));
248                         _shortest_audible_label.set_text (string_compose ("%1 %2", ma, aunits));
249                 } else {
250                         _shortest_silence_label.set_text ("");
251                         _shortest_audible_label.set_text ("");
252                 }
253         } else {
254                 _segment_count_label.set_text (_("Full silence"));
255                 _shortest_silence_label.set_text ("");
256                 _shortest_audible_label.set_text ("");
257         }
258 #endif
259 }
260
261 void *
262 StripSilenceDialog::_detection_thread_work (void* arg)
263 {
264         StripSilenceDialog* d = reinterpret_cast<StripSilenceDialog*> (arg);
265         return d->detection_thread_work ();
266 }
267
268 /** Body of our silence detection thread */
269 void *
270 StripSilenceDialog::detection_thread_work ()
271 {
272         ARDOUR_UI::instance()->register_thread ("gui", pthread_self(), "silence", 32);
273
274         /* Hold this lock when we are doing work */
275         _lock.lock ();
276         
277         while (1) {
278                 for (list<ViewInterval>::iterator i = views.begin(); i != views.end(); ++i) {
279                         boost::shared_ptr<AudioRegion> ar = boost::dynamic_pointer_cast<AudioRegion> ((*i).view->region());
280
281                         if (ar) {
282                                 (*i).intervals = ar->find_silence (dB_to_coefficient (threshold ()), minimum_length (), _interthread_info);
283                         }
284
285                         if (_interthread_info.cancel) {
286                                 break;
287                         }
288                 }
289
290                 if (!_interthread_info.cancel) {
291                         Completed (); /* EMIT SIGNAL */
292                 }
293
294                 /* Our work is done; sleep until there is more to do.
295                  * The lock is released while we are waiting.
296                  */
297                 _run_cond.wait (_lock);
298
299                 if (_thread_should_finish) {
300                         _lock.unlock ();
301                         return 0;
302                 }
303         }
304
305         return 0;
306 }
307
308 void
309 StripSilenceDialog::restart_thread ()
310 {
311         if (_destroying) {
312                 /* I don't know how this happens, but it seems to be possible for this
313                    method to be called after our destructor has finished executing.
314                    If this happens, bad things follow; _lock cannot be locked and
315                    Ardour hangs.  So if we are destroying, just bail early.
316                 */
317                 return;
318         }
319         
320         /* Cancel any current run */
321         _interthread_info.cancel = true;
322
323         /* Block until the thread waits() */
324         _lock.lock ();
325         /* Reset the flag */
326         _interthread_info.cancel = false;
327         _lock.unlock ();
328
329         /* And re-awake the thread */
330         _run_cond.signal ();
331 }
332
333 void
334 StripSilenceDialog::threshold_changed ()
335 {
336         update_threshold_line ();
337         restart_thread ();
338 }
339
340 void
341 StripSilenceDialog::update_stats (const AudioIntervalResult& res)
342 {
343         if (res.empty()) {
344                 return;
345         }
346
347         max_silence = 0;
348         min_silence = max_framepos;
349         max_audible = 0;
350         min_audible = max_framepos;
351         
352         AudioIntervalResult::const_iterator cur;
353         bool saw_silence = false;
354         bool saw_audible = false;
355
356         cur = res.begin();
357
358         framepos_t start = 0;
359         framepos_t end;
360         bool in_silence;
361
362         if (cur->first == 0) {
363                 /* initial segment, starting at zero, is silent */
364                 end = cur->second;
365                 in_silence = true;
366         } else {
367                 /* initial segment, starting at zero, is audible */
368                 end = cur->first;
369                 in_silence = false;
370         }
371
372         while (cur != res.end()) {
373
374                 framecnt_t interval_duration;
375
376                 interval_duration = end - start;
377
378                 if (in_silence) {
379                         saw_silence = true;
380                         cerr << "Silent duration: " << interval_duration << endl;
381
382                         max_silence = max (max_silence, interval_duration);
383                         min_silence = min (min_silence, interval_duration);
384                 } else {
385                         saw_audible = true;
386                         cerr << "Audible duration: " << interval_duration << endl;
387
388                         max_audible = max (max_audible, interval_duration);
389                         min_audible = min (min_audible, interval_duration);
390                 }
391
392                 start = end;
393                 ++cur;
394                 end = cur->first;
395                 in_silence = !in_silence;
396         }
397
398         if (!saw_silence) {
399                 min_silence = 0;
400                 max_silence = 0;
401         }
402
403         if (!saw_audible) {
404                 min_audible = 0;
405                 max_audible = 0;
406         }
407
408         cerr << "max aud: " << max_audible << " min aud: " << min_audible << " max sil: " << max_silence << " min sil: " << min_silence << endl;
409 }
410
411 framecnt_t
412 StripSilenceDialog::minimum_length () const
413 {
414         return _minimum_length.current_duration (views.front().view->region()->position());
415 }
416
417 framecnt_t
418 StripSilenceDialog::fade_length () const
419 {
420         return _minimum_length.current_duration (views.front().view->region()->position());
421 }
422                 
423 void
424 StripSilenceDialog::update_progress_gui (float p)
425 {
426         _progress_bar.set_fraction (p);
427 }