fix invalid casts in prev commit.
[ardour.git] / gtk2_ardour / rhythm_ferret.cc
1 #include <gtkmm/stock.h>
2 #include <gtkmm2ext/utils.h>
3
4 #include "pbd/memento_command.h"
5 #include "pbd/convert.h"
6
7 #include "ardour/audioregion.h"
8 #include "ardour/onset_detector.h"
9 #include "ardour/session.h"
10 #include "ardour/transient_detector.h"
11
12 #include "rhythm_ferret.h"
13 #include "audio_region_view.h"
14 #include "editor.h"
15 #include "utils.h"
16 #include "time_axis_view.h"
17
18 #include "i18n.h"
19
20 using namespace std;
21 using namespace Gtk;
22 using namespace Gdk;
23 using namespace PBD;
24 using namespace ARDOUR;
25
26 /* order of these must match the AnalysisMode enums
27    in rhythm_ferret.h
28 */
29 static const gchar * _analysis_mode_strings[] = {
30         N_("Percussive Onset"),
31         N_("Note Onset"),
32         0
33 };
34
35 static const gchar * _onset_function_strings[] = {
36         N_("Energy Based"),
37         N_("Spectral Difference"),
38         N_("High-Frequency Content"),
39         N_("Complex Domain"),
40         N_("Phase Deviation"),
41         N_("Kullback-Liebler"),
42         N_("Modified Kullback-Liebler"),
43         0
44 };
45
46 static const gchar * _operation_strings[] = {
47         N_("Split region"),
48         N_("Snap regions"),
49         N_("Conform regions"),
50         0
51 };
52
53 RhythmFerret::RhythmFerret (Editor& e)
54         : ArdourDialog (_("Rhythm Ferret"))
55         , editor (e)
56         , detection_threshold_adjustment (0.015, 0.0, 0.1, 0.001, 0.1)
57         , detection_threshold_scale (detection_threshold_adjustment)
58         , sensitivity_adjustment (40, 0, 100, 1, 10)
59         , sensitivity_scale (sensitivity_adjustment)
60         , analyze_button (_("Analyze"))
61         , peak_picker_threshold_adjustment (0.3, 0.0, 1.0, 0.01, 0.1)
62         , peak_picker_threshold_scale (peak_picker_threshold_adjustment)
63         , silence_threshold_adjustment (-90.0, -120.0, 0.0, 1, 10)
64         , silence_threshold_scale (silence_threshold_adjustment)
65         , trigger_gap_adjustment (3, 0, 100, 1, 10)
66         , trigger_gap_spinner (trigger_gap_adjustment)
67         , action_button (Stock::APPLY)
68 {
69         operation_strings = I18N (_operation_strings);
70         Gtkmm2ext::set_popdown_strings (operation_selector, operation_strings);
71         operation_selector.set_active (0);
72
73         analysis_mode_strings = I18N (_analysis_mode_strings);
74         Gtkmm2ext::set_popdown_strings (analysis_mode_selector, analysis_mode_strings);
75         analysis_mode_selector.set_active_text (analysis_mode_strings.front());
76         analysis_mode_selector.signal_changed().connect (sigc::mem_fun (*this, &RhythmFerret::analysis_mode_changed));
77
78         onset_function_strings = I18N (_onset_function_strings);
79         Gtkmm2ext::set_popdown_strings (onset_detection_function_selector, onset_function_strings);
80         /* Onset plugin uses complex domain as default function
81            XXX there should be a non-hacky way to set this
82          */
83         onset_detection_function_selector.set_active_text (onset_function_strings[3]);
84         detection_threshold_scale.set_digits (3);
85
86         Table* t = manage (new Table (7, 3));
87         t->set_spacings (12);
88
89         int n = 0;
90
91         t->attach (*manage (new Label (_("Mode"), 1, 0.5)), 0, 1, n, n + 1, FILL);
92         t->attach (analysis_mode_selector, 1, 2, n, n + 1, FILL);
93         ++n;
94
95         t->attach (*manage (new Label (_("Detection function"), 1, 0.5)), 0, 1, n, n + 1, FILL);
96         t->attach (onset_detection_function_selector, 1, 2, n, n + 1, FILL);
97         ++n;
98
99         t->attach (*manage (new Label (_("Trigger gap"), 1, 0.5)), 0, 1, n, n + 1, FILL);
100         t->attach (trigger_gap_spinner, 1, 2, n, n + 1, FILL);
101         t->attach (*manage (new Label (_("ms"))), 2, 3, n, n + 1, FILL);
102         ++n;
103
104         t->attach (*manage (new Label (_("Threshold"), 1, 0.5)), 0, 1, n, n + 1, FILL);
105         t->attach (detection_threshold_scale, 1, 2, n, n + 1, FILL);
106         t->attach (*manage (new Label (_("dB"))), 2, 3, n, n + 1, FILL);
107         ++n;
108
109         t->attach (*manage (new Label (_("Peak threshold"), 1, 0.5)), 0, 1, n, n + 1, FILL);
110         t->attach (peak_picker_threshold_scale, 1, 2, n, n + 1, FILL);
111         t->attach (*manage (new Label (_("dB"))), 2, 3, n, n + 1, FILL);
112         ++n;
113
114         t->attach (*manage (new Label (_("Silence threshold"), 1, 0.5)), 0, 1, n, n + 1, FILL);
115         t->attach (silence_threshold_scale, 1, 2, n, n + 1, FILL);
116         t->attach (*manage (new Label (_("dB"))), 2, 3, n, n + 1, FILL);
117         ++n;
118
119         t->attach (*manage (new Label (_("Sensitivity"), 1, 0.5)), 0, 1, n, n + 1, FILL);
120         t->attach (sensitivity_scale, 1, 2, n, n + 1, FILL);
121         ++n;
122
123         t->attach (*manage (new Label (_("Operation"), 1, 0.5)), 0, 1, n, n + 1, FILL);
124         t->attach (operation_selector, 1, 2, n, n + 1, FILL);
125         ++n;
126
127         analyze_button.signal_clicked().connect (sigc::mem_fun (*this, &RhythmFerret::run_analysis));
128         action_button.signal_clicked().connect (sigc::mem_fun (*this, &RhythmFerret::do_action));
129
130         get_vbox()->set_border_width (6);
131         get_vbox()->set_spacing (6);
132         get_vbox()->pack_start (*t);
133
134         add_action_widget (analyze_button, 1);
135         add_action_widget (action_button, 0);
136
137         show_all ();
138         analysis_mode_changed ();
139 }
140
141 void
142 RhythmFerret::analysis_mode_changed ()
143 {
144         bool const perc = get_analysis_mode() == PercussionOnset;
145
146         trigger_gap_spinner.set_sensitive (!perc);
147         detection_threshold_scale.set_sensitive (perc);
148         sensitivity_scale.set_sensitive (perc);
149         onset_detection_function_selector.set_sensitive (!perc);
150         peak_picker_threshold_scale.set_sensitive (!perc);
151         silence_threshold_scale.set_sensitive (!perc);
152 }
153
154 RhythmFerret::AnalysisMode
155 RhythmFerret::get_analysis_mode () const
156 {
157         string str = analysis_mode_selector.get_active_text ();
158
159         if (str == analysis_mode_strings[(int) NoteOnset]) {
160                 return NoteOnset;
161         }
162
163         return PercussionOnset;
164 }
165
166 RhythmFerret::Action
167 RhythmFerret::get_action () const
168 {
169         if (operation_selector.get_active_row_number() == 1) {
170                 return SnapRegionsToGrid;
171         } else if (operation_selector.get_active_row_number() == 2) {
172                 return ConformRegion;
173         }
174
175         return SplitRegion;
176 }
177
178 void
179 RhythmFerret::run_analysis ()
180 {
181         if (!_session) {
182                 return;
183         }
184
185         clear_transients ();
186
187         regions_with_transients = editor.get_selection().regions;
188
189         current_results.clear ();
190
191         if (regions_with_transients.empty()) {
192                 return;
193         }
194
195         for (RegionSelection::iterator i = regions_with_transients.begin(); i != regions_with_transients.end(); ++i) {
196
197                 boost::shared_ptr<Readable> rd = boost::static_pointer_cast<AudioRegion> ((*i)->region());
198
199                 switch (get_analysis_mode()) {
200                 case PercussionOnset:
201                         run_percussion_onset_analysis (rd, (*i)->region()->position(), current_results);
202                         break;
203                 case NoteOnset:
204                         run_note_onset_analysis (rd, (*i)->region()->position(), current_results);
205                         break;
206                 default:
207                         break;
208                 }
209
210                 (*i)->region()->set_transients (current_results);
211                 current_results.clear();
212         }
213 }
214
215 int
216 RhythmFerret::run_percussion_onset_analysis (boost::shared_ptr<Readable> readable, frameoffset_t /*offset*/, AnalysisFeatureList& results)
217 {
218         TransientDetector t (_session->frame_rate());
219
220         for (uint32_t i = 0; i < readable->n_channels(); ++i) {
221
222                 AnalysisFeatureList these_results;
223
224                 t.reset ();
225                 t.set_threshold (detection_threshold_adjustment.get_value());
226                 t.set_sensitivity (sensitivity_adjustment.get_value());
227
228                 if (t.run ("", readable.get(), i, these_results)) {
229                         continue;
230                 }
231
232                 /* merge */
233
234                 results.insert (results.end(), these_results.begin(), these_results.end());
235                 these_results.clear ();
236
237                 t.update_positions (readable.get(), i, results);
238         }
239
240         return 0;
241 }
242
243 int
244 RhythmFerret::get_note_onset_function ()
245 {
246         string txt = onset_detection_function_selector.get_active_text();
247
248         for (int n = 0; _onset_function_strings[n]; ++n) {
249                 /* compare translated versions */
250                 if (txt == onset_function_strings[n]) {
251                         return n;
252                 }
253         }
254
255         fatal << string_compose (_("programming error: %1 (%2)"), X_("illegal note onset function string"), txt)
256               << endmsg;
257
258         /*NOTREACHED*/
259         return -1;
260 }
261
262 int
263 RhythmFerret::run_note_onset_analysis (boost::shared_ptr<Readable> readable, frameoffset_t /*offset*/, AnalysisFeatureList& results)
264 {
265         try {
266                 OnsetDetector t (_session->frame_rate());
267
268                 for (uint32_t i = 0; i < readable->n_channels(); ++i) {
269
270                         AnalysisFeatureList these_results;
271
272                         t.reset ();
273
274                         t.set_function (get_note_onset_function());
275                         t.set_silence_threshold (silence_threshold_adjustment.get_value());
276                         t.set_peak_threshold (peak_picker_threshold_adjustment.get_value());
277
278                         if (t.run ("", readable.get(), i, these_results)) {
279                                 continue;
280                         }
281
282                         /* merge */
283
284                         results.insert (results.end(), these_results.begin(), these_results.end());
285                         these_results.clear ();
286                 }
287
288         } catch (failed_constructor& err) {
289                 error << "Could not load note onset detection plugin" << endmsg;
290                 return -1;
291         }
292
293         if (!results.empty()) {
294                 OnsetDetector::cleanup_onsets (results, _session->frame_rate(), trigger_gap_adjustment.get_value());
295         }
296
297         return 0;
298 }
299
300 void
301 RhythmFerret::do_action ()
302 {
303         if (!_session) {
304                 return;
305         }
306
307         switch (get_action()) {
308         case SplitRegion:
309                 do_split_action ();
310                 break;
311         case SnapRegionsToGrid:
312                 editor.snap_regions_to_grid();
313                 break;
314         case ConformRegion:
315                 editor.close_region_gaps();
316                 break;
317         default:
318                 break;
319         }
320 }
321
322 void
323 RhythmFerret::do_split_action ()
324 {
325         /* XXX: this is quite a special-case; (currently) the only operation which is
326            performed on the selection only (without entered_regionview or the edit point
327            being considered)
328         */
329         RegionSelection regions = editor.get_regions_from_selection();
330
331         if (regions.empty()) {
332                 return;
333         }
334
335         editor.EditorFreeze(); /* Emit signal */
336
337         _session->begin_reversible_command (_("split regions (rhythm ferret)"));
338
339         /* Merge the transient positions for regions in consideration */
340         AnalysisFeatureList merged_features;
341
342         for (RegionSelection::iterator i = regions.begin(); i != regions.end(); ++i) {
343
344                 AnalysisFeatureList features;
345                 features = (*i)->region()->transients();
346
347                 merged_features.insert (merged_features.end(), features.begin(), features.end());
348         }
349
350         merged_features.sort();
351         merged_features.unique();
352
353         for (RegionSelection::iterator i = regions.begin(); i != regions.end(); ) {
354
355                 RegionSelection::iterator tmp;
356
357                 tmp = i;
358                 ++tmp;
359
360                 editor.split_region_at_points ((*i)->region(), merged_features, false, false);
361
362                 /* i is invalid at this point */
363                 i = tmp;
364         }
365
366         _session->commit_reversible_command ();
367
368         editor.EditorThaw(); /* Emit signal */
369 }
370
371 void
372 RhythmFerret::set_session (Session* s)
373 {
374         ArdourDialog::set_session (s);
375         current_results.clear ();
376 }
377
378 void
379 RhythmFerret::on_hide ()
380 {
381         ArdourDialog::on_hide ();
382         clear_transients ();
383 }
384
385 /* Clear any transients that we have added */
386 void
387 RhythmFerret::clear_transients ()
388 {
389         current_results.clear ();
390
391         for (RegionSelection::iterator i = regions_with_transients.begin(); i != regions_with_transients.end(); ++i) {
392                 (*i)->region()->set_transients (current_results);
393         }
394
395         regions_with_transients.clear ();
396 }
397