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