Logo Search packages:      
Sourcecode: ardour version File versions  Download package

rhythm_ferret.cc

#include <gtkmm/stock.h>
#include <gtkmm2ext/utils.h>

#include <pbd/memento_command.h>

#include <ardour/transient_detector.h>
#include <ardour/onset_detector.h>
#include <ardour/audiosource.h>
#include <ardour/audioregion.h>
#include <ardour/playlist.h>
#include <ardour/region_factory.h>
#include <ardour/session.h>

#include "rhythm_ferret.h"
#include "audio_region_view.h"
#include "public_editor.h"
#include "utils.h"

#include "i18n.h"

using namespace std;
using namespace Gtk;
using namespace Gdk;
using namespace PBD;
using namespace ARDOUR;

/* order of these must match the AnalysisMode enums
   in rhythm_ferret.h
*/
static const gchar * _analysis_mode_strings[] = {
      N_("Percussive Onset"),
      N_("Note Onset"),
      0
};

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"),
      0
};

RhythmFerret::RhythmFerret (PublicEditor& 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_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"))
      , onset_function_label (_("Detection function"))
      , peak_picker_threshold_adjustment (0.3, 0.0, 1.0, 0.01, 0.1)
      , peak_picker_threshold_scale (peak_picker_threshold_adjustment)
      , peak_picker_label (_("Peak Threshold"))
      , silence_threshold_adjustment (-90.0, -120.0, 0.0, 1, 10)
      , silence_threshold_scale (silence_threshold_adjustment)
      , silence_label (_("Silent Threshold (dB)"))
      , 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);

      upper_hpacker.pack_start (ferret_frame, true, true);
      upper_hpacker.pack_start (selection_frame, true, true);
      upper_hpacker.pack_start (operation_frame, true, true);

      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);

      operation_frame.add (op_packer);

      HBox* box;

      ferret_packer.set_spacing (6);
      ferret_packer.set_border_width (6);
      
      vector<string> strings;

      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 (mem_fun (*this, &RhythmFerret::analysis_mode_changed));

      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]);

      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);

      ferret_packer.pack_start (analysis_packer, 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));
      
      box = manage (new HBox);
      box->set_spacing (6);
      box->pack_start (detection_threshold_label, false, false);
      box->pack_start (detection_threshold_scale, true, true);
      perc_onset_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);
      perc_onset_packer.pack_start (*box, false, false);

      box = manage (new HBox);
      box->set_spacing (6);
      box->pack_start (onset_function_label, false, false);
      box->pack_start (onset_detection_function_selector, true, true);
      note_onset_packer.pack_start (*box, false, false);
            
      box = manage (new HBox);
      box->set_spacing (6);
      box->pack_start (peak_picker_label, false, false);
      box->pack_start (peak_picker_threshold_scale, true, true);
      note_onset_packer.pack_start (*box, false, false);
      
      box = manage (new HBox);
      box->set_spacing (6);
      box->pack_start (silence_label, false, false);
      box->pack_start (silence_threshold_scale, true, true);
      note_onset_packer.pack_start (*box, false, false);

      analysis_mode_changed ();

      ferret_frame.add (ferret_packer);
      
      logo = manage (new Gtk::Image (::get_icon (X_("ferret_02"))));

      if (logo) {
            lower_hpacker.pack_start (*logo, false, false);
      }

      lower_hpacker.pack_start (operation_clarification_label, true, true);
      lower_hpacker.pack_start (action_button, false, false);
      lower_hpacker.set_border_width (6);
      lower_hpacker.set_spacing (6);

      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);

      show_all ();
}

RhythmFerret::~RhythmFerret()
{
      if (logo) {
            delete logo;
      }
}

void
RhythmFerret::analysis_mode_changed ()
{
      analysis_packer.children().clear ();

      switch (get_analysis_mode()) {
      case PercussionOnset:
            analysis_packer.pack_start (perc_onset_packer);
            break;

      case NoteOnset:
            analysis_packer.pack_start (note_onset_packer);
            break;
      }

      analysis_packer.show_all ();
}

RhythmFerret::AnalysisMode
RhythmFerret::get_analysis_mode () const
{
      string str = analysis_mode_selector.get_active_text ();

      if (str == analysis_mode_strings[(int) NoteOnset]) {
            return NoteOnset;
      } 

      return PercussionOnset;
}

RhythmFerret::Action
RhythmFerret::get_action () const
{
      if (tempo_button.get_active()) {
            return DefineTempoMap;
      } else if (region_conform_button.get_active()) {
            return ConformRegion;
      }

      return SplitRegion;
}

void
RhythmFerret::run_analysis ()
{
      if (!session) {
            return;
      }

      RegionSelection& regions (editor.get_selection().regions);

      current_results.clear ();

      if (regions.empty()) {
            return;
      }

      for (RegionSelection::iterator i = regions.begin(); i != regions.end(); ++i) {

            boost::shared_ptr<Readable> rd = boost::static_pointer_cast<AudioRegion> ((*i)->region());

            switch (get_analysis_mode()) {
            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;
            }

      }

      for (RegionSelection::iterator i = regions.begin(); i != regions.end(); ++i) {
            (*i)->get_time_axis_view().show_feature_lines (current_results);
      }

}

int
RhythmFerret::run_percussion_onset_analysis (boost::shared_ptr<Readable> readable, nframes64_t offset, AnalysisFeatureList& results)
{
      TransientDetector t (session->frame_rate());

      for (uint32_t i = 0; i < readable->n_channels(); ++i) {

            AnalysisFeatureList these_results;

            t.reset ();
            t.set_threshold (detection_threshold_adjustment.get_value());
            t.set_sensitivity (sensitivity_adjustment.get_value());

            if (t.run ("", readable.get(), i, these_results)) {
                  continue;
            }

            /* translate all transients to give absolute position */

            for (AnalysisFeatureList::iterator x = these_results.begin(); x != these_results.end(); ++x) {
                  (*x) += offset;
            }

            /* merge */
            
            results.insert (results.end(), these_results.begin(), these_results.end());
            these_results.clear ();
      }

      if (!results.empty()) {
            TransientDetector::cleanup_transients (results, session->frame_rate(), trigger_gap_adjustment.get_value());
      }

      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;
      /*NOTREACHED*/
      return -1;
}

int
RhythmFerret::run_note_onset_analysis (boost::shared_ptr<Readable> readable, nframes64_t offset, AnalysisFeatureList& results)
{
      try {
            OnsetDetector t (session->frame_rate());
            
            for (uint32_t i = 0; i < readable->n_channels(); ++i) {
                  
                  AnalysisFeatureList these_results;
                  
                  t.reset ();
                  
                  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());
                  
                  if (t.run ("", readable.get(), i, these_results)) {
                        continue;
                  }
                  
                  /* translate all transients to give absolute position */
                  
                  for (AnalysisFeatureList::iterator x = these_results.begin(); x != these_results.end(); ++x) {
                        (*x) += offset;
                  }
                  
                  /* 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->frame_rate(), trigger_gap_adjustment.get_value());
      }

      return 0;
}

void
RhythmFerret::do_action ()
{
      if (!session || current_results.empty()) {
            return;
      }

      switch (get_action()) {
      case SplitRegion:
            do_split_action ();
            break;

      default:
            break;
      }
}

void
RhythmFerret::do_split_action ()
{
      /* this can/will change the current selection, so work with a copy */

      RegionSelection& regions (editor.get_selection().regions);

      if (regions.empty()) {
            return;
      }

      session->begin_reversible_command (_("split regions (rhythm ferret)"));

      for (RegionSelection::iterator i = regions.begin(); i != regions.end(); ) {

            RegionSelection::iterator tmp;

            tmp = i;
            ++tmp;

            (*i)->get_time_axis_view().hide_feature_lines ();

            editor.split_region_at_points ((*i)->region(), current_results, false);

            /* i is invalid at this point */

            i = tmp;
      }
      session->commit_reversible_command ();

}

void
RhythmFerret::set_session (Session* s)
{
      ArdourDialog::set_session (s);
      current_results.clear ();
}

static void hide_time_axis_features (TimeAxisView& tav)
{
      tav.hide_feature_lines ();
}

void
RhythmFerret::on_hide ()
{
      editor.foreach_time_axis_view (sigc::ptr_fun (hide_time_axis_features));
      ArdourDialog::on_hide ();
}


Generated by  Doxygen 1.6.0   Back to index