1 #include <gtkmm/stock.h>
2 #include <gtkmm2ext/utils.h>
4 #include "pbd/memento_command.h"
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"
14 #include "rhythm_ferret.h"
15 #include "audio_region_view.h"
16 #include "public_editor.h"
18 #include "time_axis_view.h"
26 using namespace ARDOUR;
28 /* order of these must match the AnalysisMode enums
31 static const gchar * _analysis_mode_strings[] = {
32 N_("Percussive Onset"),
37 static const gchar * _onset_function_strings[] = {
39 N_("Spectral Difference"),
40 N_("High-Frequency Content"),
42 N_("Phase Deviation"),
43 N_("Kullback-Liebler"),
44 N_("Modified Kullback-Liebler"),
48 RhythmFerret::RhythmFerret (PublicEditor& e)
49 : ArdourDialog (_("Rhythm Ferret"))
51 , operation_frame (_("Operation"))
52 , selection_frame (_("Selection"))
53 , ferret_frame (_("Analysis"))
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)
79 upper_hpacker.set_spacing (6);
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);
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);
89 operation_frame.add (op_packer);
93 ferret_packer.set_spacing (6);
94 ferret_packer.set_border_width (6);
96 vector<string> strings;
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));
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
108 onset_detection_function_selector.set_active_text (onset_function_strings[3]);
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);
116 ferret_packer.pack_start (analysis_packer, false, false);
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);
124 ferret_packer.pack_start (analyze_button, false, false);
126 analyze_button.signal_clicked().connect (mem_fun (*this, &RhythmFerret::run_analysis));
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);
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);
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);
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);
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);
158 analysis_mode_changed ();
160 ferret_frame.add (ferret_packer);
162 logo = manage (new Gtk::Image (::get_icon (X_("ferret_02"))));
165 lower_hpacker.pack_start (*logo, false, false);
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);
173 action_button.signal_clicked().connect (mem_fun (*this, &RhythmFerret::do_action));
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);
183 RhythmFerret::~RhythmFerret()
189 RhythmFerret::analysis_mode_changed ()
191 analysis_packer.children().clear ();
193 switch (get_analysis_mode()) {
194 case PercussionOnset:
195 analysis_packer.pack_start (perc_onset_packer);
199 analysis_packer.pack_start (note_onset_packer);
203 analysis_packer.show_all ();
206 RhythmFerret::AnalysisMode
207 RhythmFerret::get_analysis_mode () const
209 string str = analysis_mode_selector.get_active_text ();
211 if (str == analysis_mode_strings[(int) NoteOnset]) {
215 return PercussionOnset;
219 RhythmFerret::get_action () const
221 if (tempo_button.get_active()) {
222 return DefineTempoMap;
223 } else if (region_conform_button.get_active()) {
224 return ConformRegion;
231 RhythmFerret::run_analysis ()
237 RegionSelection& regions (editor.get_selection().regions);
239 current_results.clear ();
241 if (regions.empty()) {
245 for (RegionSelection::iterator i = regions.begin(); i != regions.end(); ++i) {
247 boost::shared_ptr<Readable> rd = boost::static_pointer_cast<AudioRegion> ((*i)->region());
249 switch (get_analysis_mode()) {
250 case PercussionOnset:
251 run_percussion_onset_analysis (rd, (*i)->region()->position(), current_results);
254 run_note_onset_analysis (rd, (*i)->region()->position(), current_results);
262 for (RegionSelection::iterator i = regions.begin(); i != regions.end(); ++i) {
263 (*i)->get_time_axis_view().show_feature_lines (current_results);
269 RhythmFerret::run_percussion_onset_analysis (boost::shared_ptr<Readable> readable, nframes64_t offset, AnalysisFeatureList& results)
271 TransientDetector t (session->frame_rate());
273 for (uint32_t i = 0; i < readable->n_channels(); ++i) {
275 AnalysisFeatureList these_results;
278 t.set_threshold (detection_threshold_adjustment.get_value());
279 t.set_sensitivity (sensitivity_adjustment.get_value());
281 if (t.run ("", readable.get(), i, these_results)) {
285 /* translate all transients to give absolute position */
287 for (AnalysisFeatureList::iterator x = these_results.begin(); x != these_results.end(); ++x) {
293 results.insert (results.end(), these_results.begin(), these_results.end());
294 these_results.clear ();
297 if (!results.empty()) {
298 TransientDetector::cleanup_transients (results, session->frame_rate(), trigger_gap_adjustment.get_value());
305 RhythmFerret::get_note_onset_function ()
307 string txt = onset_detection_function_selector.get_active_text();
309 for (int n = 0; _onset_function_strings[n]; ++n) {
310 /* compare translated versions */
311 if (txt == onset_function_strings[n]) {
315 fatal << string_compose (_("programming error: %1 (%2)"), X_("illegal note onset function string"), txt)
322 RhythmFerret::run_note_onset_analysis (boost::shared_ptr<Readable> readable, nframes64_t offset, AnalysisFeatureList& results)
325 OnsetDetector t (session->frame_rate());
327 for (uint32_t i = 0; i < readable->n_channels(); ++i) {
329 AnalysisFeatureList these_results;
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());
337 if (t.run ("", readable.get(), i, these_results)) {
341 /* translate all transients to give absolute position */
343 for (AnalysisFeatureList::iterator x = these_results.begin(); x != these_results.end(); ++x) {
349 results.insert (results.end(), these_results.begin(), these_results.end());
350 these_results.clear ();
353 } catch (failed_constructor& err) {
354 error << "Could not load note onset detection plugin" << endmsg;
358 if (!results.empty()) {
359 OnsetDetector::cleanup_onsets (results, session->frame_rate(), trigger_gap_adjustment.get_value());
366 RhythmFerret::do_action ()
368 if (!session || current_results.empty()) {
372 switch (get_action()) {
383 RhythmFerret::do_split_action ()
385 /* this can/will change the current selection, so work with a copy */
387 RegionSelection& regions (editor.get_selection().regions);
389 if (regions.empty()) {
393 session->begin_reversible_command (_("split regions (rhythm ferret)"));
395 for (RegionSelection::iterator i = regions.begin(); i != regions.end(); ) {
397 RegionSelection::iterator tmp;
402 (*i)->get_time_axis_view().hide_feature_lines ();
404 editor.split_region_at_points ((*i)->region(), current_results, false);
406 /* i is invalid at this point */
411 session->commit_reversible_command ();
415 RhythmFerret::set_session (Session* s)
417 ArdourDialog::set_session (s);
418 current_results.clear ();
421 static void hide_time_axis_features (TimeAxisView& tav)
423 tav.hide_feature_lines ();
427 RhythmFerret::on_hide ()
429 editor.foreach_time_axis_view (sigc::ptr_fun (hide_time_axis_features));
430 ArdourDialog::on_hide ();