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