Remove unnecessary 0 checks before delete; see http://www.parashift.com/c++-faq-lite...
[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
6 #include <ardour/transient_detector.h>
7 #include <ardour/onset_detector.h>
8 #include <ardour/audiosource.h>
9 #include <ardour/audioregion.h>
10 #include <ardour/playlist.h>
11 #include <ardour/region_factory.h>
12 #include <ardour/session.h>
13
14 #include "rhythm_ferret.h"
15 #include "audio_region_view.h"
16 #include "public_editor.h"
17 #include "utils.h"
18
19 #include "i18n.h"
20
21 using namespace std;
22 using namespace Gtk;
23 using namespace Gdk;
24 using namespace PBD;
25 using namespace ARDOUR;
26
27 /* order of these must match the AnalysisMode enums
28    in rhythm_ferret.h
29 */
30 static const gchar * _analysis_mode_strings[] = {
31         N_("Percussive Onset"),
32         N_("Note Onset"),
33         0
34 };
35
36 static const gchar * _onset_function_strings[] = {
37         N_("Energy Based"),
38         N_("Spectral Difference"),
39         N_("High-Frequency Content"),
40         N_("Complex Domain"),
41         N_("Phase Deviation"),
42         N_("Kullback-Liebler"),
43         N_("Modified Kullback-Liebler"),
44         0
45 };
46
47 RhythmFerret::RhythmFerret (PublicEditor& e)
48         : ArdourDialog (_("Rhythm Ferret"))
49         , editor (e)
50         , operation_frame (_("Operation"))
51         , selection_frame (_("Selection"))
52         , ferret_frame (_("Analysis"))
53         , logo (0)
54         , region_split_button (operation_button_group, _("Split Region"))
55         , tempo_button (operation_button_group, _("Set Tempo Map"))
56         , region_conform_button (operation_button_group, _("Conform Region"))
57         , analysis_mode_label (_("Mode"))
58         , detection_threshold_adjustment (3, 0, 20, 1, 4)
59         , detection_threshold_scale (detection_threshold_adjustment)
60         , detection_threshold_label (_("Threshold"))
61         , sensitivity_adjustment (40, 0, 100, 1, 10)
62         , sensitivity_scale (sensitivity_adjustment)
63         , sensitivity_label (_("Sensitivity"))
64         , analyze_button (_("Analyze"))
65         , onset_function_label (_("Detection function"))
66         , peak_picker_threshold_adjustment (0.3, 0.0, 1.0, 0.01, 0.1)
67         , peak_picker_threshold_scale (peak_picker_threshold_adjustment)
68         , peak_picker_label (_("Peak Threshold"))
69         , silence_threshold_adjustment (-90.0, -120.0, 0.0, 1, 10)
70         , silence_threshold_scale (silence_threshold_adjustment)
71         , silence_label (_("Silent Threshold (dB)"))
72         , trigger_gap_adjustment (3, 0, 100, 1, 10)
73         , trigger_gap_spinner (trigger_gap_adjustment)
74         , trigger_gap_label (_("Trigger gap (msecs)"))
75         , action_button (Stock::APPLY)
76
77 {
78         upper_hpacker.set_spacing (6);
79
80         upper_hpacker.pack_start (ferret_frame, true, true);
81         upper_hpacker.pack_start (selection_frame, true, true);
82         upper_hpacker.pack_start (operation_frame, true, true);
83
84         op_packer.pack_start (region_split_button, false, false);
85         op_packer.pack_start (tempo_button, false, false);
86         op_packer.pack_start (region_conform_button, false, false);
87
88         operation_frame.add (op_packer);
89
90         HBox* box;
91
92         ferret_packer.set_spacing (6);
93         ferret_packer.set_border_width (6);
94         
95         vector<string> strings;
96
97         analysis_mode_strings = I18N (_analysis_mode_strings);
98         Gtkmm2ext::set_popdown_strings (analysis_mode_selector, analysis_mode_strings);
99         analysis_mode_selector.set_active_text (analysis_mode_strings.front());
100         analysis_mode_selector.signal_changed().connect (mem_fun (*this, &RhythmFerret::analysis_mode_changed));
101
102         onset_function_strings = I18N (_onset_function_strings);
103         Gtkmm2ext::set_popdown_strings (onset_detection_function_selector, onset_function_strings);
104         /* Onset plugin uses complex domain as default function 
105            XXX there should be a non-hacky way to set this
106          */
107         onset_detection_function_selector.set_active_text (onset_function_strings[3]);
108
109         box = manage (new HBox);
110         box->set_spacing (6);
111         box->pack_start (analysis_mode_label, false, false);
112         box->pack_start (analysis_mode_selector, true, true);
113         ferret_packer.pack_start (*box, false, false);
114
115         ferret_packer.pack_start (analysis_packer, false, false);
116
117         box = manage (new HBox);
118         box->set_spacing (6);
119         box->pack_start (trigger_gap_label, false, false);
120         box->pack_start (trigger_gap_spinner, false, false);
121         ferret_packer.pack_start (*box, false, false);
122
123         ferret_packer.pack_start (analyze_button, false, false);
124
125         analyze_button.signal_clicked().connect (mem_fun (*this, &RhythmFerret::run_analysis));
126         
127         box = manage (new HBox);
128         box->set_spacing (6);
129         box->pack_start (detection_threshold_label, false, false);
130         box->pack_start (detection_threshold_scale, true, true);
131         perc_onset_packer.pack_start (*box, false, false);
132                 
133         box = manage (new HBox);
134         box->set_spacing (6);
135         box->pack_start (sensitivity_label, false, false);
136         box->pack_start (sensitivity_scale, true, true);
137         perc_onset_packer.pack_start (*box, false, false);
138
139         box = manage (new HBox);
140         box->set_spacing (6);
141         box->pack_start (onset_function_label, false, false);
142         box->pack_start (onset_detection_function_selector, true, true);
143         note_onset_packer.pack_start (*box, false, false);
144                 
145         box = manage (new HBox);
146         box->set_spacing (6);
147         box->pack_start (peak_picker_label, false, false);
148         box->pack_start (peak_picker_threshold_scale, true, true);
149         note_onset_packer.pack_start (*box, false, false);
150         
151         box = manage (new HBox);
152         box->set_spacing (6);
153         box->pack_start (silence_label, false, false);
154         box->pack_start (silence_threshold_scale, true, true);
155         note_onset_packer.pack_start (*box, false, false);
156
157         analysis_mode_changed ();
158
159         ferret_frame.add (ferret_packer);
160         
161         logo = manage (new Gtk::Image (::get_icon (X_("ferret_02"))));
162
163         if (logo) {
164                 lower_hpacker.pack_start (*logo, false, false);
165         }
166
167         lower_hpacker.pack_start (operation_clarification_label, true, true);
168         lower_hpacker.pack_start (action_button, false, false);
169         lower_hpacker.set_border_width (6);
170         lower_hpacker.set_spacing (6);
171
172         action_button.signal_clicked().connect (mem_fun (*this, &RhythmFerret::do_action));
173         
174         get_vbox()->set_border_width (6);
175         get_vbox()->set_spacing (6);
176         get_vbox()->pack_start (upper_hpacker, true, true);
177         get_vbox()->pack_start (lower_hpacker, false, false);
178
179         show_all ();
180 }
181
182 RhythmFerret::~RhythmFerret()
183 {
184         delete logo;
185 }
186
187 void
188 RhythmFerret::analysis_mode_changed ()
189 {
190         analysis_packer.children().clear ();
191
192         switch (get_analysis_mode()) {
193         case PercussionOnset:
194                 analysis_packer.pack_start (perc_onset_packer);
195                 break;
196
197         case NoteOnset:
198                 analysis_packer.pack_start (note_onset_packer);
199                 break;
200         }
201
202         analysis_packer.show_all ();
203 }
204
205 RhythmFerret::AnalysisMode
206 RhythmFerret::get_analysis_mode () const
207 {
208         string str = analysis_mode_selector.get_active_text ();
209
210         if (str == analysis_mode_strings[(int) NoteOnset]) {
211                 return NoteOnset;
212         } 
213
214         return PercussionOnset;
215 }
216
217 RhythmFerret::Action
218 RhythmFerret::get_action () const
219 {
220         if (tempo_button.get_active()) {
221                 return DefineTempoMap;
222         } else if (region_conform_button.get_active()) {
223                 return ConformRegion;
224         }
225
226         return SplitRegion;
227 }
228
229 void
230 RhythmFerret::run_analysis ()
231 {
232         if (!session) {
233                 return;
234         }
235
236         RegionSelection& regions (editor.get_selection().regions);
237
238         current_results.clear ();
239
240         if (regions.empty()) {
241                 return;
242         }
243
244         for (RegionSelection::iterator i = regions.begin(); i != regions.end(); ++i) {
245
246                 boost::shared_ptr<Readable> rd = boost::static_pointer_cast<AudioRegion> ((*i)->region());
247
248                 switch (get_analysis_mode()) {
249                 case PercussionOnset:
250                         run_percussion_onset_analysis (rd, (*i)->region()->position(), current_results);
251                         break;
252                 case NoteOnset:
253                         run_note_onset_analysis (rd, (*i)->region()->position(), current_results);
254                         break;
255                 default:
256                         break;
257                 }
258
259         }
260
261         for (RegionSelection::iterator i = regions.begin(); i != regions.end(); ++i) {
262                 (*i)->get_time_axis_view().show_feature_lines (current_results);
263         }
264
265 }
266
267 int
268 RhythmFerret::run_percussion_onset_analysis (boost::shared_ptr<Readable> readable, nframes64_t offset, AnalysisFeatureList& results)
269 {
270         TransientDetector t (session->frame_rate());
271
272         for (uint32_t i = 0; i < readable->n_channels(); ++i) {
273
274                 AnalysisFeatureList these_results;
275
276                 t.reset ();
277                 t.set_threshold (detection_threshold_adjustment.get_value());
278                 t.set_sensitivity (sensitivity_adjustment.get_value());
279
280                 if (t.run ("", readable.get(), i, these_results)) {
281                         continue;
282                 }
283
284                 /* translate all transients to give absolute position */
285
286                 for (AnalysisFeatureList::iterator x = these_results.begin(); x != these_results.end(); ++x) {
287                         (*x) += offset;
288                 }
289
290                 /* merge */
291                 
292                 results.insert (results.end(), these_results.begin(), these_results.end());
293                 these_results.clear ();
294         }
295
296         if (!results.empty()) {
297                 TransientDetector::cleanup_transients (results, session->frame_rate(), trigger_gap_adjustment.get_value());
298         }
299
300         return 0;
301 }
302
303 int
304 RhythmFerret::get_note_onset_function ()
305 {
306         string txt = onset_detection_function_selector.get_active_text();
307
308         for (int n = 0; _onset_function_strings[n]; ++n) {
309                 /* compare translated versions */
310                 if (txt == onset_function_strings[n]) {
311                         return n;
312                 }
313         }
314         fatal << string_compose (_("programming error: %1 (%2)"), X_("illegal note onset function string"), txt)
315               << endmsg;
316         /*NOTREACHED*/
317         return -1;
318 }
319
320 int
321 RhythmFerret::run_note_onset_analysis (boost::shared_ptr<Readable> readable, nframes64_t offset, AnalysisFeatureList& results)
322 {
323         try {
324                 OnsetDetector t (session->frame_rate());
325                 
326                 for (uint32_t i = 0; i < readable->n_channels(); ++i) {
327                         
328                         AnalysisFeatureList these_results;
329                         
330                         t.reset ();
331                         
332                         t.set_function (get_note_onset_function());
333                         t.set_silence_threshold (silence_threshold_adjustment.get_value());
334                         t.set_peak_threshold (peak_picker_threshold_adjustment.get_value());
335                         
336                         if (t.run ("", readable.get(), i, these_results)) {
337                                 continue;
338                         }
339                         
340                         /* translate all transients to give absolute position */
341                         
342                         for (AnalysisFeatureList::iterator x = these_results.begin(); x != these_results.end(); ++x) {
343                                 (*x) += offset;
344                         }
345                         
346                         /* merge */
347                         
348                         results.insert (results.end(), these_results.begin(), these_results.end());
349                         these_results.clear ();
350                 }
351
352         } catch (failed_constructor& err) {
353                 error << "Could not load note onset detection plugin" << endmsg;
354                 return -1;
355         }
356
357         if (!results.empty()) {
358                 OnsetDetector::cleanup_onsets (results, session->frame_rate(), trigger_gap_adjustment.get_value());
359         }
360
361         return 0;
362 }
363
364 void
365 RhythmFerret::do_action ()
366 {
367         if (!session || current_results.empty()) {
368                 return;
369         }
370
371         switch (get_action()) {
372         case SplitRegion:
373                 do_split_action ();
374                 break;
375
376         default:
377                 break;
378         }
379 }
380
381 void
382 RhythmFerret::do_split_action ()
383 {
384         /* this can/will change the current selection, so work with a copy */
385
386         RegionSelection& regions (editor.get_selection().regions);
387
388         if (regions.empty()) {
389                 return;
390         }
391
392         session->begin_reversible_command (_("split regions (rhythm ferret)"));
393
394         for (RegionSelection::iterator i = regions.begin(); i != regions.end(); ) {
395
396                 RegionSelection::iterator tmp;
397
398                 tmp = i;
399                 ++tmp;
400
401                 (*i)->get_time_axis_view().hide_feature_lines ();
402
403                 editor.split_region_at_points ((*i)->region(), current_results, false);
404
405                 /* i is invalid at this point */
406
407                 i = tmp;
408         }
409         session->commit_reversible_command ();
410
411 }
412
413 void
414 RhythmFerret::set_session (Session* s)
415 {
416         ArdourDialog::set_session (s);
417         current_results.clear ();
418 }
419
420 static void hide_time_axis_features (TimeAxisView& tav)
421 {
422         tav.hide_feature_lines ();
423 }
424
425 void
426 RhythmFerret::on_hide ()
427 {
428         editor.foreach_time_axis_view (sigc::ptr_fun (hide_time_axis_features));
429         ArdourDialog::on_hide ();
430 }
431