ardour
rhythm_ferret.cc
Go to the documentation of this file.
1 /*
2  Copyright (C) 2012 Paul Davis
3 
4  This program is free software; you can redistribute it and/or modify
5  it under the terms of the GNU General Public License as published by
6  the Free Software Foundation; either version 2 of the License, or
7  (at your option) any later version.
8 
9  This program is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  GNU General Public License for more details.
13 
14  You should have received a copy of the GNU General Public License
15  along with this program; if not, write to the Free Software
16  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17 
18 */
19 
20 #include <gtkmm/stock.h>
21 #include <gtkmm2ext/utils.h>
22 
23 #include "pbd/memento_command.h"
24 #include "pbd/convert.h"
25 
26 #include "ardour/audioregion.h"
27 #include "ardour/onset_detector.h"
28 #include "ardour/session.h"
30 
31 #include "rhythm_ferret.h"
32 #include "audio_region_view.h"
33 #include "editor.h"
34 #include "time_axis_view.h"
35 
36 #include "i18n.h"
37 
38 using namespace std;
39 using namespace Gtk;
40 using namespace Gdk;
41 using namespace PBD;
42 using namespace ARDOUR;
43 
44 /* order of these must match the AnalysisMode enums
45  in rhythm_ferret.h
46 */
47 static const gchar * _analysis_mode_strings[] = {
48  N_("Percussive Onset"),
49  N_("Note Onset"),
50  0
51 };
52 
53 static const gchar * _onset_function_strings[] = {
54  N_("Energy Based"),
55  N_("Spectral Difference"),
56  N_("High-Frequency Content"),
57  N_("Complex Domain"),
58  N_("Phase Deviation"),
59  N_("Kullback-Liebler"),
60  N_("Modified Kullback-Liebler"),
61  0
62 };
63 
64 static const gchar * _operation_strings[] = {
65  N_("Split region"),
66  N_("Snap regions"),
67  N_("Conform regions"),
68  0
69 };
70 
72  : ArdourDialog (_("Rhythm Ferret"))
73  , editor (e)
74  , detection_threshold_adjustment (0.015, 0.0, 0.1, 0.001, 0.1)
75  , detection_threshold_scale (detection_threshold_adjustment)
76  , sensitivity_adjustment (40, 0, 100, 1, 10)
77  , sensitivity_scale (sensitivity_adjustment)
78  , analyze_button (_("Analyze"))
79  , peak_picker_threshold_adjustment (0.3, 0.0, 1.0, 0.01, 0.1)
80  , peak_picker_threshold_scale (peak_picker_threshold_adjustment)
81  , silence_threshold_adjustment (-90.0, -120.0, 0.0, 1, 10)
82  , silence_threshold_scale (silence_threshold_adjustment)
83  , trigger_gap_adjustment (3, 0, 100, 1, 10)
84  , trigger_gap_spinner (trigger_gap_adjustment)
85  , action_button (Stock::APPLY)
86 {
89  operation_selector.set_active (0);
90 
93  analysis_mode_selector.set_active_text (analysis_mode_strings.front());
94  analysis_mode_selector.signal_changed().connect (sigc::mem_fun (*this, &RhythmFerret::analysis_mode_changed));
95 
98  /* Onset plugin uses complex domain as default function
99  XXX there should be a non-hacky way to set this
100  */
102  detection_threshold_scale.set_digits (3);
103 
104  Table* t = manage (new Table (7, 3));
105  t->set_spacings (12);
106 
107  int n = 0;
108 
109  t->attach (*manage (new Label (_("Mode"), 1, 0.5)), 0, 1, n, n + 1, FILL);
110  t->attach (analysis_mode_selector, 1, 2, n, n + 1, FILL);
111  ++n;
112 
113  t->attach (*manage (new Label (_("Detection function"), 1, 0.5)), 0, 1, n, n + 1, FILL);
114  t->attach (onset_detection_function_selector, 1, 2, n, n + 1, FILL);
115  ++n;
116 
117  t->attach (*manage (new Label (_("Trigger gap"), 1, 0.5)), 0, 1, n, n + 1, FILL);
118  t->attach (trigger_gap_spinner, 1, 2, n, n + 1, FILL);
119  t->attach (*manage (new Label (_("ms"))), 2, 3, n, n + 1, FILL);
120  ++n;
121 
122  t->attach (*manage (new Label (_("Threshold"), 1, 0.5)), 0, 1, n, n + 1, FILL);
123  t->attach (detection_threshold_scale, 1, 2, n, n + 1, FILL);
124  t->attach (*manage (new Label (_("dB"))), 2, 3, n, n + 1, FILL);
125  ++n;
126 
127  t->attach (*manage (new Label (_("Peak threshold"), 1, 0.5)), 0, 1, n, n + 1, FILL);
128  t->attach (peak_picker_threshold_scale, 1, 2, n, n + 1, FILL);
129  t->attach (*manage (new Label (_("dB"))), 2, 3, n, n + 1, FILL);
130  ++n;
131 
132  t->attach (*manage (new Label (_("Silence threshold"), 1, 0.5)), 0, 1, n, n + 1, FILL);
133  t->attach (silence_threshold_scale, 1, 2, n, n + 1, FILL);
134  t->attach (*manage (new Label (_("dB"))), 2, 3, n, n + 1, FILL);
135  ++n;
136 
137  t->attach (*manage (new Label (_("Sensitivity"), 1, 0.5)), 0, 1, n, n + 1, FILL);
138  t->attach (sensitivity_scale, 1, 2, n, n + 1, FILL);
139  ++n;
140 
141  t->attach (*manage (new Label (_("Operation"), 1, 0.5)), 0, 1, n, n + 1, FILL);
142  t->attach (operation_selector, 1, 2, n, n + 1, FILL);
143  ++n;
144 
145  analyze_button.signal_clicked().connect (sigc::mem_fun (*this, &RhythmFerret::run_analysis));
146  action_button.signal_clicked().connect (sigc::mem_fun (*this, &RhythmFerret::do_action));
147 
148  get_vbox()->set_border_width (6);
149  get_vbox()->set_spacing (6);
150  get_vbox()->pack_start (*t);
151 
152  add_action_widget (analyze_button, 1);
153  add_action_widget (action_button, 0);
154 
155  show_all ();
157 }
158 
159 void
161 {
162  bool const perc = get_analysis_mode() == PercussionOnset;
163 
164  trigger_gap_spinner.set_sensitive (!perc);
165  detection_threshold_scale.set_sensitive (perc);
166  sensitivity_scale.set_sensitive (perc);
167  onset_detection_function_selector.set_sensitive (!perc);
168  peak_picker_threshold_scale.set_sensitive (!perc);
169  silence_threshold_scale.set_sensitive (!perc);
170 }
171 
174 {
175  string str = analysis_mode_selector.get_active_text ();
176 
177  if (str == analysis_mode_strings[(int) NoteOnset]) {
178  return NoteOnset;
179  }
180 
181  return PercussionOnset;
182 }
183 
186 {
187  if (operation_selector.get_active_row_number() == 1) {
188  return SnapRegionsToGrid;
189  } else if (operation_selector.get_active_row_number() == 2) {
190  return ConformRegion;
191  }
192 
193  return SplitRegion;
194 }
195 
196 void
198 {
199  if (!_session) {
200  return;
201  }
202 
203  clear_transients ();
204 
206 
207  current_results.clear ();
208 
209  if (regions_with_transients.empty()) {
210  return;
211  }
212 
213  for (RegionSelection::iterator i = regions_with_transients.begin(); i != regions_with_transients.end(); ++i) {
214 
216 
217  switch (get_analysis_mode()) {
218  case PercussionOnset:
219  run_percussion_onset_analysis (rd, (*i)->region()->position(), current_results);
220  break;
221  case NoteOnset:
222  run_note_onset_analysis (rd, (*i)->region()->position(), current_results);
223  break;
224  default:
225  break;
226  }
227 
228  (*i)->region()->set_transients (current_results);
229  current_results.clear();
230  }
231 }
232 
233 int
235 {
237 
238  for (uint32_t i = 0; i < readable->n_channels(); ++i) {
239 
240  AnalysisFeatureList these_results;
241 
242  t.reset ();
243  t.set_threshold (detection_threshold_adjustment.get_value());
244  t.set_sensitivity (sensitivity_adjustment.get_value());
245 
246  if (t.run ("", readable.get(), i, these_results)) {
247  continue;
248  }
249 
250  /* merge */
251 
252  results.insert (results.end(), these_results.begin(), these_results.end());
253  these_results.clear ();
254 
255  t.update_positions (readable.get(), i, results);
256  }
257 
258  return 0;
259 }
260 
261 int
263 {
264  string txt = onset_detection_function_selector.get_active_text();
265 
266  for (int n = 0; _onset_function_strings[n]; ++n) {
267  /* compare translated versions */
268  if (txt == onset_function_strings[n]) {
269  return n;
270  }
271  }
272 
273  fatal << string_compose (_("programming error: %1 (%2)"), X_("illegal note onset function string"), txt)
274  << endmsg;
275 
276  abort(); /*NOTREACHED*/
277  return -1;
278 }
279 
280 int
282 {
283  try {
285 
286  for (uint32_t i = 0; i < readable->n_channels(); ++i) {
287 
288  AnalysisFeatureList these_results;
289 
290  t.reset ();
291 
292  t.set_function (get_note_onset_function());
293  t.set_silence_threshold (silence_threshold_adjustment.get_value());
294  t.set_peak_threshold (peak_picker_threshold_adjustment.get_value());
295 
296  if (t.run ("", readable.get(), i, these_results)) {
297  continue;
298  }
299 
300  /* merge */
301 
302  results.insert (results.end(), these_results.begin(), these_results.end());
303  these_results.clear ();
304  }
305 
306  } catch (failed_constructor& err) {
307  error << "Could not load note onset detection plugin" << endmsg;
308  return -1;
309  }
310 
311  if (!results.empty()) {
312  OnsetDetector::cleanup_onsets (results, _session->frame_rate(), trigger_gap_adjustment.get_value());
313  }
314 
315  return 0;
316 }
317 
318 void
320 {
321  if (!_session) {
322  return;
323  }
324 
325  switch (get_action()) {
326  case SplitRegion:
327  do_split_action ();
328  break;
329  case SnapRegionsToGrid:
331  break;
332  case ConformRegion:
334  break;
335  default:
336  break;
337  }
338 }
339 
340 void
342 {
343  /* XXX: this is quite a special-case; (currently) the only operation which is
344  performed on the selection only (without entered_regionview or the edit point
345  being considered)
346  */
348 
349  if (regions.empty()) {
350  return;
351  }
352 
353  editor.EditorFreeze(); /* Emit signal */
354 
355  editor.begin_reversible_command (_("split regions (rhythm ferret)"));
356 
357  /* Merge the transient positions for regions in consideration */
358  AnalysisFeatureList merged_features;
359 
360  for (RegionSelection::iterator i = regions.begin(); i != regions.end(); ++i) {
361 
362  AnalysisFeatureList features;
363  features = (*i)->region()->transients();
364 
365  merged_features.insert (merged_features.end(), features.begin(), features.end());
366  }
367 
368  merged_features.sort();
369  merged_features.unique();
370 
371  for (RegionSelection::iterator i = regions.begin(); i != regions.end(); ) {
372 
373  RegionSelection::iterator tmp;
374 
375  tmp = i;
376  ++tmp;
377 
378  editor.split_region_at_points ((*i)->region(), merged_features, false, false);
379 
380  /* i is invalid at this point */
381  i = tmp;
382  }
383 
385 
386  editor.EditorThaw(); /* Emit signal */
387 }
388 
389 void
391 {
393  current_results.clear ();
394 }
395 
396 void
398 {
399  ArdourDialog::on_hide ();
400  clear_transients ();
401 }
402 
403 /* Clear any transients that we have added */
404 void
406 {
407  current_results.clear ();
408 
409  for (RegionSelection::iterator i = regions_with_transients.begin(); i != regions_with_transients.end(); ++i) {
410  (*i)->region()->set_transients (current_results);
411  }
412 
413  regions_with_transients.clear ();
414 }
415 
Gtk::SpinButton trigger_gap_spinner
Definition: rhythm_ferret.h:92
LIBPBD_API Transmitter fatal
int run_note_onset_analysis(boost::shared_ptr< ARDOUR::Readable > region, ARDOUR::frameoffset_t offset, ARDOUR::AnalysisFeatureList &results)
Selection * selection
Definition: editor.h:1801
Gtk::HScale peak_picker_threshold_scale
Definition: rhythm_ferret.h:85
void begin_reversible_command(std::string cmd_name)
Definition: ardour_ui.h:130
std::vector< std::string > analysis_mode_strings
Definition: rhythm_ferret.h:96
RegionSelection regions_with_transients
Definition: Beats.hpp:239
LIBPBD_API Transmitter error
RhythmFerret(Editor &)
std::ostream & endmsg(std::ostream &ostr)
Definition: transmitter.h:71
static const gchar * _onset_function_strings[]
LIBGTKMM2EXT_API void set_popdown_strings(Gtk::ComboBoxText &, const std::vector< std::string > &)
framecnt_t frame_rate() const
Definition: session.h:365
virtual uint32_t n_channels() const =0
Gtk::Button action_button
Definition: rhythm_ferret.h:94
#define _(Text)
Definition: i18n.h:11
#define X_(Text)
Definition: i18n.h:13
std::list< framepos_t > AnalysisFeatureList
Definition: types.h:530
Definition: amp.h:29
void run_analysis()
Gtk::Adjustment sensitivity_adjustment
Definition: rhythm_ferret.h:77
void split_region_at_points(boost::shared_ptr< ARDOUR::Region >, ARDOUR::AnalysisFeatureList &, bool can_ferret, bool select_new=false)
Definition: editor_ops.cc:6308
void close_region_gaps()
Definition: editor_ops.cc:6555
PBD::Signal0< void > EditorFreeze
Definition: editor.h:1577
RegionSelection regions
Definition: selection.h:82
Gtk::ComboBoxText operation_selector
Definition: rhythm_ferret.h:69
Gtk::Adjustment trigger_gap_adjustment
Definition: rhythm_ferret.h:91
shared_ptr< T > static_pointer_cast(shared_ptr< U > const &r)
Definition: shared_ptr.hpp:386
Editor & editor
Definition: rhythm_ferret.h:67
int run_percussion_onset_analysis(boost::shared_ptr< ARDOUR::Readable > region, ARDOUR::frameoffset_t offset, ARDOUR::AnalysisFeatureList &results)
#define I18N(Array)
Definition: i18n.h:14
int64_t frameoffset_t
Definition: types.h:71
void clear_transients()
LIBARDOUR_API PBD::PropertyDescriptor< bool > regions
Definition: playlist.cc:51
T * get() const
Definition: shared_ptr.hpp:268
void do_split_action()
void snap_regions_to_grid()
Definition: editor_ops.cc:6516
Action get_action() const
std::vector< std::string > operation_strings
Definition: rhythm_ferret.h:98
Definition: editor.h:134
int get_note_onset_function()
PBD::Signal0< void > EditorThaw
Definition: editor.h:1578
Gtk::Adjustment peak_picker_threshold_adjustment
Definition: rhythm_ferret.h:84
Gtk::HScale silence_threshold_scale
Definition: rhythm_ferret.h:87
Definition: debug.h:30
Selection & get_selection() const
Definition: editor.h:244
std::vector< std::string > onset_function_strings
Definition: rhythm_ferret.h:97
Gtk::Adjustment silence_threshold_adjustment
Definition: rhythm_ferret.h:86
virtual void set_session(ARDOUR::Session *)
void set_session(ARDOUR::Session *)
void commit_reversible_command()
Definition: editor.cc:3483
Gtk::ComboBoxText onset_detection_function_selector
Definition: rhythm_ferret.h:83
void analysis_mode_changed()
Gtk::Adjustment detection_threshold_adjustment
Definition: rhythm_ferret.h:75
ARDOUR::AnalysisFeatureList current_results
static const gchar * _operation_strings[]
Gtk::HScale sensitivity_scale
Definition: rhythm_ferret.h:78
ARDOUR::Session * _session
AnalysisMode get_analysis_mode() const
std::string string_compose(const std::string &fmt, const T1 &o1)
Definition: compose.h:208
#define N_(Text)
Definition: i18n.h:12
Gtk::ComboBoxText analysis_mode_selector
Definition: rhythm_ferret.h:71
Gtk::Button analyze_button
Definition: rhythm_ferret.h:79
Gtk::HScale detection_threshold_scale
Definition: rhythm_ferret.h:76
static const gchar * _analysis_mode_strings[]