X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=gtk2_ardour%2Frhythm_ferret.cc;h=8eb64840f6a721c4d7aaf7535882d982a92001b5;hb=0268489c7853780a91f289a409ec00c6e561526c;hp=980b36e1d13274a2b3994e2b10babe754f44a8c5;hpb=9f63ab9931e6478472853bdda58da47ea29ac125;p=ardour.git diff --git a/gtk2_ardour/rhythm_ferret.cc b/gtk2_ardour/rhythm_ferret.cc index 980b36e1d1..8eb64840f6 100644 --- a/gtk2_ardour/rhythm_ferret.cc +++ b/gtk2_ardour/rhythm_ferret.cc @@ -1,20 +1,39 @@ +/* + Copyright (C) 2012 Paul Davis + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + #include #include -#include +#include "pbd/memento_command.h" +#include "pbd/convert.h" -#include -#include -#include -#include -#include -#include +#include "ardour/audioregion.h" +#include "ardour/onset_detector.h" +#include "ardour/session.h" +#include "ardour/transient_detector.h" #include "rhythm_ferret.h" #include "audio_region_view.h" -#include "public_editor.h" +#include "editor.h" +#include "time_axis_view.h" -#include "i18n.h" +#include "pbd/i18n.h" using namespace std; using namespace Gtk; @@ -31,107 +50,149 @@ static const gchar * _analysis_mode_strings[] = { 0 }; -RhythmFerret::RhythmFerret (PublicEditor& e) +static const gchar * _onset_function_strings[] = { + N_("Energy Based"), + N_("Spectral Difference"), + N_("High-Frequency Content"), + N_("Complex Domain"), + N_("Phase Deviation"), + N_("Kullback-Liebler"), + N_("Modified Kullback-Liebler"), +#ifdef HAVE_AUBIO4 + N_("Spectral Flux"), +#endif + 0 +}; + +static const gchar * _operation_strings[] = { + N_("Split region"), +#if 0 // these don't do what a user expects + N_("Snap regions"), + N_("Conform regions"), +#endif + 0 +}; + +RhythmFerret::RhythmFerret (Editor& e) : ArdourDialog (_("Rhythm Ferret")) , editor (e) - , operation_frame (_("Operation")) - , selection_frame (_("Selection")) - , ferret_frame (_("Analysis")) - , logo (0) - , region_split_button (operation_button_group, _("Split Region")) - , tempo_button (operation_button_group, _("Set Tempo Map")) - , region_conform_button (operation_button_group, _("Conform Region")) - , analysis_mode_label (_("Mode")) - , detection_threshold_adjustment (3, 0, 20, 1, 4) + , detection_threshold_adjustment (-35, -80, -6, 1, 6) , detection_threshold_scale (detection_threshold_adjustment) - , detection_threshold_label (_("Threshold")) , sensitivity_adjustment (40, 0, 100, 1, 10) , sensitivity_scale (sensitivity_adjustment) - , sensitivity_label (_("Sensitivity")) , analyze_button (_("Analyze")) + , peak_picker_threshold_adjustment (0.3, 0.0, 1.0, 0.01, 0.1) + , peak_picker_threshold_scale (peak_picker_threshold_adjustment) + , silence_threshold_adjustment (-90.0, -120.0, 0.0, 1, 10) + , silence_threshold_scale (silence_threshold_adjustment) +#ifdef HAVE_AUBIO4 + , minioi_adjustment (4, 0, 40, 1, 5) + , minioi_scale (minioi_adjustment) +#endif , trigger_gap_adjustment (3, 0, 100, 1, 10) , trigger_gap_spinner (trigger_gap_adjustment) - , trigger_gap_label (_("Trigger gap (msecs)")) , action_button (Stock::APPLY) - { - upper_hpacker.set_spacing (6); + operation_strings = I18N (_operation_strings); + Gtkmm2ext::set_popdown_strings (operation_selector, operation_strings); + operation_selector.set_active (0); - upper_hpacker.pack_start (operation_frame, true, true); - upper_hpacker.pack_start (selection_frame, true, true); - upper_hpacker.pack_start (ferret_frame, true, true); + analysis_mode_strings = I18N (_analysis_mode_strings); + Gtkmm2ext::set_popdown_strings (analysis_mode_selector, analysis_mode_strings); + analysis_mode_selector.set_active_text (analysis_mode_strings.front()); + analysis_mode_selector.signal_changed().connect (sigc::mem_fun (*this, &RhythmFerret::analysis_mode_changed)); - op_packer.pack_start (region_split_button, false, false); - op_packer.pack_start (tempo_button, false, false); - op_packer.pack_start (region_conform_button, false, false); + onset_function_strings = I18N (_onset_function_strings); + Gtkmm2ext::set_popdown_strings (onset_detection_function_selector, onset_function_strings); + /* Onset plugin uses complex domain as default function + XXX there should be a non-hacky way to set this + */ + onset_detection_function_selector.set_active_text (onset_function_strings[3]); + detection_threshold_scale.set_digits (3); - operation_frame.add (op_packer); + Table* t = manage (new Table (7, 3)); + t->set_spacings (12); - HBox* box; + int n = 0; - ferret_packer.set_spacing (6); - ferret_packer.set_border_width (6); - - vector strings; + t->attach (*manage (new Label (_("Mode"), 1, 0.5)), 0, 1, n, n + 1, FILL); + t->attach (analysis_mode_selector, 1, 2, n, n + 1, FILL); + ++n; - analysis_mode_strings = I18N (_analysis_mode_strings); - Gtkmm2ext::set_popdown_strings (analysis_mode_selector, analysis_mode_strings); - analysis_mode_selector.set_active_text (analysis_mode_strings.front()); + t->attach (*manage (new Label (_("Detection function"), 1, 0.5)), 0, 1, n, n + 1, FILL); + t->attach (onset_detection_function_selector, 1, 2, n, n + 1, FILL); + ++n; + + t->attach (*manage (new Label (_("Trigger gap (postproc)"), 1, 0.5)), 0, 1, n, n + 1, FILL); + t->attach (trigger_gap_spinner, 1, 2, n, n + 1, FILL); + t->attach (*manage (new Label (_("ms"))), 2, 3, n, n + 1, FILL); + ++n; + + t->attach (*manage (new Label (_("Peak threshold"), 1, 0.5)), 0, 1, n, n + 1, FILL); + t->attach (peak_picker_threshold_scale, 1, 2, n, n + 1, FILL); + ++n; + + t->attach (*manage (new Label (_("Silence threshold"), 1, 0.5)), 0, 1, n, n + 1, FILL); + t->attach (silence_threshold_scale, 1, 2, n, n + 1, FILL); + t->attach (*manage (new Label (_("dB"))), 2, 3, n, n + 1, FILL); + ++n; + +#ifdef HAVE_AUBIO4 + t->attach (*manage (new Label (_("Min Inter-Onset Time"), 1, 0.5)), 0, 1, n, n + 1, FILL); + t->attach (minioi_scale, 1, 2, n, n + 1, FILL); + t->attach (*manage (new Label (_("ms"))), 2, 3, n, n + 1, FILL); + ++n; +#endif - box = manage (new HBox); - box->set_spacing (6); - box->pack_start (analysis_mode_label, false, false); - box->pack_start (analysis_mode_selector, true, true); - ferret_packer.pack_start (*box, false, false); - - box = manage (new HBox); - box->set_spacing (6); - box->pack_start (detection_threshold_label, false, false); - box->pack_start (detection_threshold_scale, true, true); - ferret_packer.pack_start (*box, false, false); - - box = manage (new HBox); - box->set_spacing (6); - box->pack_start (sensitivity_label, false, false); - box->pack_start (sensitivity_scale, true, true); - ferret_packer.pack_start (*box, false, false); - - box = manage (new HBox); - box->set_spacing (6); - box->pack_start (trigger_gap_label, false, false); - box->pack_start (trigger_gap_spinner, false, false); - ferret_packer.pack_start (*box, false, false); - - ferret_packer.pack_start (analyze_button, false, false); - - analyze_button.signal_clicked().connect (mem_fun (*this, &RhythmFerret::run_analysis)); - - ferret_frame.add (ferret_packer); - - // Glib::RefPtr logo_pixbuf ("somefile"); - - if (logo) { - lower_hpacker.pack_start (*logo, false, false); - } - lower_hpacker.pack_start (operation_clarification_label, false, false); - lower_hpacker.pack_start (action_button, false, false); + t->attach (*manage (new Label (_("Sensitivity"), 1, 0.5)), 0, 1, n, n + 1, FILL); + t->attach (sensitivity_scale, 1, 2, n, n + 1, FILL); + ++n; + + t->attach (*manage (new Label (_("Cut Pos Threshold"), 1, 0.5)), 0, 1, n, n + 1, FILL); + t->attach (detection_threshold_scale, 1, 2, n, n + 1, FILL); + t->attach (*manage (new Label (_("dB"))), 2, 3, n, n + 1, FILL); + ++n; + + t->attach (*manage (new Label (_("Operation"), 1, 0.5)), 0, 1, n, n + 1, FILL); + t->attach (operation_selector, 1, 2, n, n + 1, FILL); + ++n; + + analyze_button.signal_clicked().connect (sigc::mem_fun (*this, &RhythmFerret::run_analysis)); + action_button.signal_clicked().connect (sigc::mem_fun (*this, &RhythmFerret::do_action)); - action_button.signal_clicked().connect (mem_fun (*this, &RhythmFerret::do_action)); - get_vbox()->set_border_width (6); get_vbox()->set_spacing (6); - get_vbox()->pack_start (upper_hpacker, true, true); - get_vbox()->pack_start (lower_hpacker, false, false); + get_vbox()->pack_start (*t); + + add_action_widget (analyze_button, 1); + add_action_widget (action_button, 0); show_all (); + analysis_mode_changed (); } -RhythmFerret::~RhythmFerret() +void +RhythmFerret::on_response (int response_id) { - if (logo) { - delete logo; - } + Gtk::Dialog::on_response (response_id); +} + +void +RhythmFerret::analysis_mode_changed () +{ + bool const perc = get_analysis_mode() == PercussionOnset; + + // would be nice to actually hide/show the rows. + detection_threshold_scale.set_sensitive (perc); + sensitivity_scale.set_sensitive (perc); + trigger_gap_spinner.set_sensitive (!perc); + onset_detection_function_selector.set_sensitive (!perc); + peak_picker_threshold_scale.set_sensitive (!perc); + silence_threshold_scale.set_sensitive (!perc); +#ifdef HAVE_AUBIO4 + minioi_scale.set_sensitive (!perc); +#endif } RhythmFerret::AnalysisMode @@ -139,9 +200,9 @@ RhythmFerret::get_analysis_mode () const { string str = analysis_mode_selector.get_active_text (); - if (str == _(_analysis_mode_strings[(int) NoteOnset])) { + if (str == analysis_mode_strings[(int) NoteOnset]) { return NoteOnset; - } + } return PercussionOnset; } @@ -149,9 +210,9 @@ RhythmFerret::get_analysis_mode () const RhythmFerret::Action RhythmFerret::get_action () const { - if (tempo_button.get_active()) { - return DefineTempoMap; - } else if (region_conform_button.get_active()) { + if (operation_selector.get_active_row_number() == 1) { + return SnapRegionsToGrid; + } else if (operation_selector.get_active_row_number() == 2) { return ConformRegion; } @@ -161,19 +222,21 @@ RhythmFerret::get_action () const void RhythmFerret::run_analysis () { - if (!session) { + if (!_session) { return; } - RegionSelection& regions (editor.get_selection().regions); + clear_transients (); + + regions_with_transients = editor.get_selection().regions; current_results.clear (); - if (regions.empty()) { + if (regions_with_transients.empty()) { return; } - for (RegionSelection::iterator i = regions.begin(); i != regions.end(); ++i) { + for (RegionSelection::iterator i = regions_with_transients.begin(); i != regions_with_transients.end(); ++i) { boost::shared_ptr rd = boost::static_pointer_cast ((*i)->region()); @@ -181,69 +244,110 @@ RhythmFerret::run_analysis () case PercussionOnset: run_percussion_onset_analysis (rd, (*i)->region()->position(), current_results); break; + case NoteOnset: + run_note_onset_analysis (rd, (*i)->region()->position(), current_results); + break; default: break; } + (*i)->region()->set_onsets (current_results); + current_results.clear(); } - - for (RegionSelection::iterator i = regions.begin(); i != regions.end(); ++i) { - (*i)->get_time_axis_view().show_temporary_lines (current_results); - } - } int -RhythmFerret::run_percussion_onset_analysis (boost::shared_ptr readable, nframes64_t offset, vector& results) +RhythmFerret::run_percussion_onset_analysis (boost::shared_ptr readable, sampleoffset_t /*offset*/, AnalysisFeatureList& results) { - TransientDetector t (session->frame_rate()); + try { + TransientDetector t (_session->sample_rate()); - for (uint32_t i = 0; i < readable->n_channels(); ++i) { + for (uint32_t i = 0; i < readable->n_channels(); ++i) { - vector these_results; + AnalysisFeatureList these_results; - t.reset (); - t.set_threshold (detection_threshold_adjustment.get_value()); - t.set_sensitivity (sensitivity_adjustment.get_value()); + t.reset (); + float dB = detection_threshold_adjustment.get_value(); + float coeff = dB > -80.0f ? pow (10.0f, dB * 0.05f) : 0.0f; + t.set_threshold (coeff); + t.set_sensitivity (4, sensitivity_adjustment.get_value()); - if (t.run ("", readable.get(), i, these_results)) { - continue; - } + if (t.run ("", readable.get(), i, these_results)) { + continue; + } - /* translate all transients to give absolute position */ + /* merge */ - for (vector::iterator i = these_results.begin(); i != these_results.end(); ++i) { - (*i) += offset; + results.insert (results.end(), these_results.begin(), these_results.end()); + these_results.clear (); + + t.update_positions (readable.get(), i, results); } - /* merge */ - - results.insert (results.end(), these_results.begin(), these_results.end()); + } catch (failed_constructor& err) { + error << "Could not load percussion onset detection plugin" << endmsg; + return -1; } - - if (!results.empty()) { - - /* now resort to bring transients from different channels together */ - - sort (results.begin(), results.end()); - /* remove duplicates or other things that are too close */ + return 0; +} + +int +RhythmFerret::get_note_onset_function () +{ + string txt = onset_detection_function_selector.get_active_text(); + + for (int n = 0; _onset_function_strings[n]; ++n) { + /* compare translated versions */ + if (txt == onset_function_strings[n]) { + return n; + } + } + + fatal << string_compose (_("programming error: %1 (%2)"), X_("illegal note onset function string"), txt) + << endmsg; + + abort(); /*NOTREACHED*/ + return -1; +} + +int +RhythmFerret::run_note_onset_analysis (boost::shared_ptr readable, sampleoffset_t /*offset*/, AnalysisFeatureList& results) +{ + try { + OnsetDetector t (_session->sample_rate()); + + for (uint32_t i = 0; i < readable->n_channels(); ++i) { + + AnalysisFeatureList these_results; - vector::iterator i = results.begin(); - nframes64_t curr = (*i); - nframes64_t gap_frames = (nframes64_t) floor (trigger_gap_adjustment.get_value() * (session->frame_rate() / 1000.0)); + t.set_function (get_note_onset_function()); + t.set_silence_threshold (silence_threshold_adjustment.get_value()); + t.set_peak_threshold (peak_picker_threshold_adjustment.get_value()); +#ifdef HAVE_AUBIO4 + t.set_minioi (minioi_adjustment.get_value()); +#endif - ++i; + // aubio-vamp only picks up new settings on reset. + t.reset (); - while (i != results.end()) { - if (((*i) == curr) || (((*i) - curr) < gap_frames)) { - i = results.erase (i); - } else { - ++i; - curr = *i; + if (t.run ("", readable.get(), i, these_results)) { + continue; } + + /* merge */ + + results.insert (results.end(), these_results.begin(), these_results.end()); + these_results.clear (); } + } catch (failed_constructor& err) { + error << "Could not load note onset detection plugin" << endmsg; + return -1; + } + + if (!results.empty()) { + OnsetDetector::cleanup_onsets (results, _session->sample_rate(), trigger_gap_adjustment.get_value()); } return 0; @@ -252,7 +356,7 @@ RhythmFerret::run_percussion_onset_analysis (boost::shared_ptr readabl void RhythmFerret::do_action () { - if (!session || current_results.empty()) { + if (!_session) { return; } @@ -260,7 +364,13 @@ RhythmFerret::do_action () case SplitRegion: do_split_action (); break; - + case SnapRegionsToGrid: + // split first, select all.. ?! + editor.snap_regions_to_grid(); + break; + case ConformRegion: + editor.close_region_gaps(); + break; default: break; } @@ -269,15 +379,33 @@ RhythmFerret::do_action () void RhythmFerret::do_split_action () { - /* this can/will change the current selection, so work with a copy */ - - RegionSelection& regions (editor.get_selection().regions); + /* XXX: this is quite a special-case; (currently) the only operation which is + performed on the selection only (without entered_regionview or the edit point + being considered) + */ + RegionSelection regions = editor.selection->regions; if (regions.empty()) { return; } - session->begin_reversible_command (_("split regions (rhythm ferret)")); + editor.EditorFreeze(); /* Emit signal */ + + editor.begin_reversible_command (_("split regions (rhythm ferret)")); + + /* Merge the transient positions for regions in consideration */ + AnalysisFeatureList merged_features; + + for (RegionSelection::iterator i = regions.begin(); i != regions.end(); ++i) { + + AnalysisFeatureList features; + (*i)->region()->transients(features); + + merged_features.insert (merged_features.end(), features.begin(), features.end()); + } + + merged_features.sort(); + merged_features.unique(); for (RegionSelection::iterator i = regions.begin(); i != regions.end(); ) { @@ -286,15 +414,15 @@ RhythmFerret::do_split_action () tmp = i; ++tmp; - (*i)->get_time_axis_view().hide_temporary_lines (); - - editor.split_region_at_points ((*i)->region(), current_results); + editor.split_region_at_points ((*i)->region(), merged_features, false, false); /* i is invalid at this point */ - i = tmp; } + editor.commit_reversible_command (); + + editor.EditorThaw(); /* Emit signal */ } void @@ -303,3 +431,24 @@ RhythmFerret::set_session (Session* s) ArdourDialog::set_session (s); current_results.clear (); } + +void +RhythmFerret::on_hide () +{ + ArdourDialog::on_hide (); + clear_transients (); +} + +/* Clear any transients that we have added */ +void +RhythmFerret::clear_transients () +{ + current_results.clear (); + + for (RegionSelection::iterator i = regions_with_transients.begin(); i != regions_with_transients.end(); ++i) { + (*i)->region()->set_onsets (current_results); + } + + regions_with_transients.clear (); +} +