Merge with 2.0-ongoing R2988
[ardour.git] / gtk2_ardour / rhythm_ferret.cc
1 #include <gtkmm/stock.h>
2 #include <gtkmm2ext/utils.h>
3
4 #include <pbd/memento_command.h>
5
6 #include <ardour/transient_detector.h>
7 #include <ardour/audiosource.h>
8 #include <ardour/audioregion.h>
9 #include <ardour/playlist.h>
10 #include <ardour/region_factory.h>
11 #include <ardour/session.h>
12
13 #include "rhythm_ferret.h"
14 #include "audio_region_view.h"
15 #include "public_editor.h"
16 #include "utils.h"
17
18 #include "i18n.h"
19
20 using namespace std;
21 using namespace Gtk;
22 using namespace Gdk;
23 using namespace PBD;
24 using namespace ARDOUR;
25
26 /* order of these must match the AnalysisMode enums
27    in rhythm_ferret.h
28 */
29 static const gchar * _analysis_mode_strings[] = {
30         N_("Percussive Onset"),
31         N_("Note Onset"),
32         0
33 };
34
35 RhythmFerret::RhythmFerret (PublicEditor& e)
36         : ArdourDialog (_("Rhythm Ferret"))
37         , editor (e)
38         , operation_frame (_("Operation"))
39         , selection_frame (_("Selection"))
40         , ferret_frame (_("Analysis"))
41         , logo (0)
42         , region_split_button (operation_button_group, _("Split Region"))
43         , tempo_button (operation_button_group, _("Set Tempo Map"))
44         , region_conform_button (operation_button_group, _("Conform Region"))
45         , analysis_mode_label (_("Mode"))
46         , detection_threshold_adjustment (3, 0, 20, 1, 4)
47         , detection_threshold_scale (detection_threshold_adjustment)
48         , detection_threshold_label (_("Threshold"))
49         , sensitivity_adjustment (40, 0, 100, 1, 10)
50         , sensitivity_scale (sensitivity_adjustment)
51         , sensitivity_label (_("Sensitivity"))
52         , analyze_button (_("Analyze"))
53         , trigger_gap_adjustment (3, 0, 100, 1, 10)
54         , trigger_gap_spinner (trigger_gap_adjustment)
55         , trigger_gap_label (_("Trigger gap (msecs)"))
56         , action_button (Stock::APPLY)
57
58 {
59         upper_hpacker.set_spacing (6);
60
61         upper_hpacker.pack_start (ferret_frame, true, true);
62         upper_hpacker.pack_start (selection_frame, true, true);
63         upper_hpacker.pack_start (operation_frame, true, true);
64
65         op_packer.pack_start (region_split_button, false, false);
66         op_packer.pack_start (tempo_button, false, false);
67         op_packer.pack_start (region_conform_button, false, false);
68
69         operation_frame.add (op_packer);
70
71         HBox* box;
72
73         ferret_packer.set_spacing (6);
74         ferret_packer.set_border_width (6);
75         
76         vector<string> strings;
77
78         analysis_mode_strings = I18N (_analysis_mode_strings);
79         Gtkmm2ext::set_popdown_strings (analysis_mode_selector, analysis_mode_strings);
80         analysis_mode_selector.set_active_text (analysis_mode_strings.front());
81
82         box = manage (new HBox);
83         box->set_spacing (6);
84         box->pack_start (analysis_mode_label, false, false);
85         box->pack_start (analysis_mode_selector, true, true);
86         ferret_packer.pack_start (*box, false, false);
87
88         box = manage (new HBox);
89         box->set_spacing (6);
90         box->pack_start (detection_threshold_label, false, false);
91         box->pack_start (detection_threshold_scale, true, true);
92         ferret_packer.pack_start (*box, false, false);
93
94         box = manage (new HBox);
95         box->set_spacing (6);
96         box->pack_start (sensitivity_label, false, false);
97         box->pack_start (sensitivity_scale, true, true);
98         ferret_packer.pack_start (*box, false, false);
99
100         box = manage (new HBox);
101         box->set_spacing (6);
102         box->pack_start (trigger_gap_label, false, false);
103         box->pack_start (trigger_gap_spinner, false, false);
104         ferret_packer.pack_start (*box, false, false);
105
106         ferret_packer.pack_start (analyze_button, false, false);
107
108         analyze_button.signal_clicked().connect (mem_fun (*this, &RhythmFerret::run_analysis));
109         
110         ferret_frame.add (ferret_packer);
111         
112         logo = manage (new Gtk::Image (::get_icon (X_("ferret_02"))));
113
114         if (logo) {
115                 lower_hpacker.pack_start (*logo, false, false);
116         }
117
118         lower_hpacker.pack_start (operation_clarification_label, true, true);
119         lower_hpacker.pack_start (action_button, false, false);
120         lower_hpacker.set_border_width (6);
121         lower_hpacker.set_spacing (6);
122
123         action_button.signal_clicked().connect (mem_fun (*this, &RhythmFerret::do_action));
124         
125         get_vbox()->set_border_width (6);
126         get_vbox()->set_spacing (6);
127         get_vbox()->pack_start (upper_hpacker, true, true);
128         get_vbox()->pack_start (lower_hpacker, false, false);
129
130         show_all ();
131 }
132
133 RhythmFerret::~RhythmFerret()
134 {
135         if (logo) {
136                 delete logo;
137         }
138 }
139
140 RhythmFerret::AnalysisMode
141 RhythmFerret::get_analysis_mode () const
142 {
143         string str = analysis_mode_selector.get_active_text ();
144
145         if (str == _(_analysis_mode_strings[(int) NoteOnset])) {
146                 return NoteOnset;
147         } 
148
149         return PercussionOnset;
150 }
151
152 RhythmFerret::Action
153 RhythmFerret::get_action () const
154 {
155         if (tempo_button.get_active()) {
156                 return DefineTempoMap;
157         } else if (region_conform_button.get_active()) {
158                 return ConformRegion;
159         }
160
161         return SplitRegion;
162 }
163
164 void
165 RhythmFerret::run_analysis ()
166 {
167         if (!session) {
168                 return;
169         }
170
171         RegionSelection& regions (editor.get_selection().regions);
172
173         current_results.clear ();
174
175         if (regions.empty()) {
176                 return;
177         }
178
179         for (RegionSelection::iterator i = regions.begin(); i != regions.end(); ++i) {
180
181                 boost::shared_ptr<Readable> rd = boost::static_pointer_cast<AudioRegion> ((*i)->region());
182
183                 switch (get_analysis_mode()) {
184                 case PercussionOnset:
185                         run_percussion_onset_analysis (rd, (*i)->region()->position(), current_results);
186                         break;
187                 default:
188                         break;
189                 }
190
191         }
192
193         for (RegionSelection::iterator i = regions.begin(); i != regions.end(); ++i) {
194                 (*i)->get_time_axis_view().show_temporary_lines (current_results);
195         }
196
197 }
198
199 int
200 RhythmFerret::run_percussion_onset_analysis (boost::shared_ptr<Readable> readable, nframes64_t offset, AnalysisFeatureList& results)
201 {
202         TransientDetector t (session->frame_rate());
203
204         for (uint32_t i = 0; i < readable->n_channels(); ++i) {
205
206                 AnalysisFeatureList these_results;
207
208                 t.reset ();
209                 t.set_threshold (detection_threshold_adjustment.get_value());
210                 t.set_sensitivity (sensitivity_adjustment.get_value());
211
212                 if (t.run ("", readable.get(), i, these_results)) {
213                         continue;
214                 }
215
216                 /* translate all transients to give absolute position */
217
218                 for (AnalysisFeatureList::iterator x = these_results.begin(); x != these_results.end(); ++x) {
219                         (*x) += offset;
220                 }
221
222                 /* merge */
223                 
224                 results.insert (results.end(), these_results.begin(), these_results.end());
225                 these_results.clear ();
226         }
227
228         if (!results.empty()) {
229                 TransientDetector::cleanup_transients (results, session->frame_rate(), trigger_gap_adjustment.get_value());
230         }
231
232         return 0;
233 }
234
235 void
236 RhythmFerret::do_action ()
237 {
238         if (!session || current_results.empty()) {
239                 return;
240         }
241
242         switch (get_action()) {
243         case SplitRegion:
244                 do_split_action ();
245                 break;
246
247         default:
248                 break;
249         }
250 }
251
252 void
253 RhythmFerret::do_split_action ()
254 {
255         /* this can/will change the current selection, so work with a copy */
256
257         RegionSelection& regions (editor.get_selection().regions);
258
259         if (regions.empty()) {
260                 return;
261         }
262
263         session->begin_reversible_command (_("split regions (rhythm ferret)"));
264
265         for (RegionSelection::iterator i = regions.begin(); i != regions.end(); ) {
266
267                 RegionSelection::iterator tmp;
268
269                 tmp = i;
270                 ++tmp;
271
272                 (*i)->get_time_axis_view().hide_temporary_lines ();
273
274                 editor.split_region_at_points ((*i)->region(), current_results);
275
276                 /* i is invalid at this point */
277
278                 i = tmp;
279         }
280
281 }
282
283 void
284 RhythmFerret::set_session (Session* s)
285 {
286         ArdourDialog::set_session (s);
287         current_results.clear ();
288 }