1 #include <gtkmm/stock.h>
2 #include <gtkmm2ext/utils.h>
4 #include "pbd/memento_command.h"
5 #include "pbd/convert.h"
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"
15 #include "rhythm_ferret.h"
16 #include "audio_region_view.h"
17 #include "public_editor.h"
19 #include "time_axis_view.h"
27 using namespace ARDOUR
;
29 /* order of these must match the AnalysisMode enums
32 static const gchar
* _analysis_mode_strings
[] = {
33 N_("Percussive Onset"),
38 static const gchar
* _onset_function_strings
[] = {
40 N_("Spectral Difference"),
41 N_("High-Frequency Content"),
43 N_("Phase Deviation"),
44 N_("Kullback-Liebler"),
45 N_("Modified Kullback-Liebler"),
49 RhythmFerret::RhythmFerret (PublicEditor
& e
)
50 : ArdourDialog (_("Rhythm Ferret"))
52 , operation_frame (_("Operation"))
53 , selection_frame (_("Selection"))
54 , ferret_frame (_("Analysis"))
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
)
80 upper_hpacker
.set_spacing (6);
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);
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);
90 operation_frame
.add (op_packer
);
94 ferret_packer
.set_spacing (6);
95 ferret_packer
.set_border_width (6);
97 vector
<string
> strings
;
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
));
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
109 onset_detection_function_selector
.set_active_text (onset_function_strings
[3]);
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);
117 ferret_packer
.pack_start (analysis_packer
, false, false);
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);
125 ferret_packer
.pack_start (analyze_button
, false, false);
127 analyze_button
.signal_clicked().connect (sigc::mem_fun (*this, &RhythmFerret::run_analysis
));
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);
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);
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);
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);
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);
159 analysis_mode_changed ();
161 ferret_frame
.add (ferret_packer
);
163 logo
= manage (new Gtk::Image (::get_icon (X_("ferret_02"))));
166 lower_hpacker
.pack_start (*logo
, false, false);
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);
174 action_button
.signal_clicked().connect (sigc::mem_fun (*this, &RhythmFerret::do_action
));
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);
184 RhythmFerret::~RhythmFerret()
190 RhythmFerret::analysis_mode_changed ()
192 analysis_packer
.children().clear ();
194 switch (get_analysis_mode()) {
195 case PercussionOnset
:
196 analysis_packer
.pack_start (perc_onset_packer
);
200 analysis_packer
.pack_start (note_onset_packer
);
204 analysis_packer
.show_all ();
207 RhythmFerret::AnalysisMode
208 RhythmFerret::get_analysis_mode () const
210 string str
= analysis_mode_selector
.get_active_text ();
212 if (str
== analysis_mode_strings
[(int) NoteOnset
]) {
216 return PercussionOnset
;
220 RhythmFerret::get_action () const
222 if (tempo_button
.get_active()) {
223 return DefineTempoMap
;
224 } else if (region_conform_button
.get_active()) {
225 return ConformRegion
;
232 RhythmFerret::run_analysis ()
238 RegionSelection
& regions (editor
.get_selection().regions
);
240 current_results
.clear ();
242 if (regions
.empty()) {
246 for (RegionSelection::iterator i
= regions
.begin(); i
!= regions
.end(); ++i
) {
248 boost::shared_ptr
<Readable
> rd
= boost::static_pointer_cast
<AudioRegion
> ((*i
)->region());
250 switch (get_analysis_mode()) {
251 case PercussionOnset
:
252 run_percussion_onset_analysis (rd
, (*i
)->region()->position(), current_results
);
255 run_note_onset_analysis (rd
, (*i
)->region()->position(), current_results
);
263 for (RegionSelection::iterator i
= regions
.begin(); i
!= regions
.end(); ++i
) {
264 (*i
)->get_time_axis_view().show_feature_lines (current_results
);
270 RhythmFerret::run_percussion_onset_analysis (boost::shared_ptr
<Readable
> readable
, nframes64_t offset
, AnalysisFeatureList
& results
)
272 TransientDetector
t (_session
->frame_rate());
274 for (uint32_t i
= 0; i
< readable
->n_channels(); ++i
) {
276 AnalysisFeatureList these_results
;
279 t
.set_threshold (detection_threshold_adjustment
.get_value());
280 t
.set_sensitivity (sensitivity_adjustment
.get_value());
282 if (t
.run ("", readable
.get(), i
, these_results
)) {
286 /* translate all transients to give absolute position */
288 for (AnalysisFeatureList::iterator x
= these_results
.begin(); x
!= these_results
.end(); ++x
) {
294 results
.insert (results
.end(), these_results
.begin(), these_results
.end());
295 these_results
.clear ();
298 if (!results
.empty()) {
299 TransientDetector::cleanup_transients (results
, _session
->frame_rate(), trigger_gap_adjustment
.get_value());
306 RhythmFerret::get_note_onset_function ()
308 string txt
= onset_detection_function_selector
.get_active_text();
310 for (int n
= 0; _onset_function_strings
[n
]; ++n
) {
311 /* compare translated versions */
312 if (txt
== onset_function_strings
[n
]) {
316 fatal
<< string_compose (_("programming error: %1 (%2)"), X_("illegal note onset function string"), txt
)
323 RhythmFerret::run_note_onset_analysis (boost::shared_ptr
<Readable
> readable
, nframes64_t offset
, AnalysisFeatureList
& results
)
326 OnsetDetector
t (_session
->frame_rate());
328 for (uint32_t i
= 0; i
< readable
->n_channels(); ++i
) {
330 AnalysisFeatureList these_results
;
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());
338 if (t
.run ("", readable
.get(), i
, these_results
)) {
342 /* translate all transients to give absolute position */
344 for (AnalysisFeatureList::iterator x
= these_results
.begin(); x
!= these_results
.end(); ++x
) {
350 results
.insert (results
.end(), these_results
.begin(), these_results
.end());
351 these_results
.clear ();
354 } catch (failed_constructor
& err
) {
355 error
<< "Could not load note onset detection plugin" << endmsg
;
359 if (!results
.empty()) {
360 OnsetDetector::cleanup_onsets (results
, _session
->frame_rate(), trigger_gap_adjustment
.get_value());
367 RhythmFerret::do_action ()
369 if (!_session
|| current_results
.empty()) {
373 switch (get_action()) {
384 RhythmFerret::do_split_action ()
386 /* this can/will change the current selection, so work with a copy */
388 RegionSelection
& regions (editor
.get_selection().regions
);
390 if (regions
.empty()) {
394 _session
->begin_reversible_command (_("split regions (rhythm ferret)"));
396 for (RegionSelection::iterator i
= regions
.begin(); i
!= regions
.end(); ) {
398 RegionSelection::iterator tmp
;
403 (*i
)->get_time_axis_view().hide_feature_lines ();
405 editor
.split_region_at_points ((*i
)->region(), current_results
, false);
407 /* i is invalid at this point */
412 _session
->commit_reversible_command ();
416 RhythmFerret::set_session (Session
* s
)
418 ArdourDialog::set_session (s
);
419 current_results
.clear ();
422 static void hide_time_axis_features (TimeAxisView
& tav
)
424 tav
.hide_feature_lines ();
428 RhythmFerret::on_hide ()
430 editor
.foreach_time_axis_view (sigc::ptr_fun (hide_time_axis_features
));
431 ArdourDialog::on_hide ();