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