merge with master, including manual merge conflict resolution
[ardour.git] / gtk2_ardour / analysis_window.cc
1 /*
2     Copyright (C) 2006 Paul Davis
3     Written by Sampo Savolainen
4
5     This program is free software; you can redistribute it and/or modify
6     it under the terms of the GNU General Public License as published by
7     the Free Software Foundation; either version 2 of the License, or
8     (at your option) any later version.
9
10     This program is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU General Public License for more details.
14
15     You should have received a copy of the GNU General Public License
16     along with this program; if not, write to the Free Software
17     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18
19 */
20
21 #include <gtkmm2ext/gtk_ui.h>
22 #include <gtkmm/stock.h>
23 #include <gtkmm/label.h>
24 #include <gtkmm/treemodel.h>
25 #include <gtkmm/treeiter.h>
26
27 #include "ardour/audioregion.h"
28 #include "ardour/audioplaylist.h"
29 #include "ardour/types.h"
30
31 #include "analysis_window.h"
32
33 #include "route_ui.h"
34 #include "time_axis_view.h"
35 #include "public_editor.h"
36 #include "selection.h"
37 #include "audio_region_view.h"
38
39 #include "i18n.h"
40
41 using namespace ARDOUR;
42 using namespace PBD;
43
44 AnalysisWindow::AnalysisWindow() :
45
46           source_selection_label       (_("Signal source")),
47           source_selection_ranges_rb   (_("Selected ranges")),
48           source_selection_regions_rb  (_("Selected regions")),
49
50           display_model_label                   (_("Display model")),
51           display_model_composite_separate_rb   (_("Composite graphs for each track")),
52           display_model_composite_all_tracks_rb (_("Composite graph of all tracks")),
53
54           show_minmax_button     (_("Show frequency power range")),
55           show_normalized_button (_("Normalize values")),
56
57           fft_graph (16384)
58 {
59         set_name(_("FFT analysis window"));
60         set_title (_("Spectral Analysis"));
61
62         track_list_ready = false;
63
64         // Left side: track list + controls
65         tlmodel = Gtk::ListStore::create(tlcols);
66         track_list.set_model (tlmodel);
67         track_list.append_column(_("Track"), tlcols.trackname);
68         track_list.append_column_editable(_("Show"), tlcols.visible);
69         track_list.set_headers_visible(true);
70         track_list.set_reorderable(false);
71         track_list.get_selection()->set_mode (Gtk::SELECTION_NONE);
72
73
74         Gtk::TreeViewColumn* track_col = track_list.get_column(0);
75         Gtk::CellRendererText* renderer = dynamic_cast<Gtk::CellRendererText*>(track_list.get_column_cell_renderer (0));
76
77         track_col->add_attribute(renderer->property_foreground_gdk(), tlcols.color);
78         track_col->set_expand(true);
79
80
81         tlmodel->signal_row_changed().connect (
82                         sigc::mem_fun(*this, &AnalysisWindow::track_list_row_changed) );
83
84         fft_graph.set_analysis_window(this);
85
86         vbox.pack_start(track_list);
87
88
89         // "Signal source"
90         vbox.pack_start(source_selection_label, false, false);
91
92         {
93                 Gtk::RadioButtonGroup group = source_selection_ranges_rb.get_group();
94                 source_selection_regions_rb.set_group(group);
95
96                 source_selection_ranges_rb.set_active();
97
98                 vbox.pack_start (source_selection_ranges_rb,  false, false);
99                 vbox.pack_start (source_selection_regions_rb, false, false);
100
101                 // "Selected ranges" radio
102                 source_selection_ranges_rb.signal_toggled().connect (
103                                 sigc::bind ( sigc::mem_fun(*this, &AnalysisWindow::source_selection_changed), &source_selection_ranges_rb));
104
105                 // "Selected regions" radio
106                 source_selection_regions_rb.signal_toggled().connect (
107                                 sigc::bind ( sigc::mem_fun(*this, &AnalysisWindow::source_selection_changed), &source_selection_regions_rb));
108         }
109
110         vbox.pack_start(hseparator1, false, false);
111
112         // "Display model"
113         vbox.pack_start(display_model_label, false, false);
114         {
115                 Gtk::RadioButtonGroup group = display_model_composite_separate_rb.get_group();
116                 display_model_composite_all_tracks_rb.set_group (group);
117
118                 display_model_composite_separate_rb.set_active();
119
120                 vbox.pack_start (display_model_composite_separate_rb,   false, false);
121                 vbox.pack_start (display_model_composite_all_tracks_rb, false, false);
122
123                 // "Composite graphs for all tracks"
124                 display_model_composite_separate_rb.signal_toggled().connect (
125                                 sigc::bind ( sigc::mem_fun(*this, &AnalysisWindow::display_model_changed), &display_model_composite_separate_rb));
126
127                 // "Composite graph of all tracks"
128                 display_model_composite_all_tracks_rb.signal_toggled().connect (
129                                 sigc::bind ( sigc::mem_fun(*this, &AnalysisWindow::display_model_changed), &display_model_composite_all_tracks_rb));
130         }
131
132         // Analyze button
133
134         refresh_button.set_name("EditorGTKButton");
135         refresh_button.set_label(_("Re-analyze data"));
136
137         refresh_button.signal_clicked().connect ( sigc::bind ( sigc::mem_fun(*this, &AnalysisWindow::analyze_data), &refresh_button));
138
139         vbox.pack_start(refresh_button, false, false, 10);
140
141
142         // Feature checkboxes
143
144         // minmax
145         show_minmax_button.signal_toggled().connect( sigc::mem_fun(*this, &AnalysisWindow::show_minmax_changed));
146         vbox.pack_start(show_minmax_button, false, false);
147
148         // normalize
149         show_normalized_button.signal_toggled().connect( sigc::mem_fun(*this, &AnalysisWindow::show_normalized_changed));
150         vbox.pack_start(show_normalized_button, false, false);
151
152
153
154
155
156         hbox.pack_start(vbox, Gtk::PACK_SHRINK);
157
158         // Analysis window on the right
159         fft_graph.ensure_style();
160
161         hbox.add(fft_graph);
162
163
164
165         // And last we pack the hbox
166         add(hbox);
167         show_all();
168         track_list.show_all();
169 }
170
171 AnalysisWindow::~AnalysisWindow()
172 {
173
174 }
175
176 void
177 AnalysisWindow::show_minmax_changed()
178 {
179         fft_graph.set_show_minmax(show_minmax_button.get_active());
180 }
181
182 void
183 AnalysisWindow::show_normalized_changed()
184 {
185         fft_graph.set_show_normalized(show_normalized_button.get_active());
186 }
187
188 void
189 AnalysisWindow::set_rangemode()
190 {
191         source_selection_ranges_rb.set_active(true);
192 }
193
194 void
195 AnalysisWindow::set_regionmode()
196 {
197         source_selection_regions_rb.set_active(true);
198 }
199
200 void
201 AnalysisWindow::track_list_row_changed(const Gtk::TreeModel::Path& /*path*/, const Gtk::TreeModel::iterator& /*iter*/)
202 {
203         if (track_list_ready) {
204                 fft_graph.redraw();
205         }
206 }
207
208
209 void
210 AnalysisWindow::clear_tracklist()
211 {
212         // Empty track list & free old graphs
213         Gtk::TreeNodeChildren children = track_list.get_model()->children();
214
215         for (Gtk::TreeIter i = children.begin(); i != children.end(); i++) {
216                 Gtk::TreeModel::Row row = *i;
217
218                 FFTResult *delete_me = row[tlcols.graph];
219                 if (delete_me == 0)
220                         continue;
221
222                 // Make sure it's not drawn
223                 row[tlcols.graph] = 0;
224
225                 delete delete_me;
226         }
227
228         tlmodel->clear();
229 }
230
231 void
232 AnalysisWindow::analyze()
233 {
234         analyze_data(&refresh_button);
235 }
236
237 void
238 AnalysisWindow::analyze_data (Gtk::Button * /*button*/)
239 {
240         track_list_ready = false;
241         {
242                 Glib::Threads::Mutex::Lock lm  (track_list_lock);
243
244                 // Empty track list & free old graphs
245                 clear_tracklist();
246
247                 // first we gather the FFTResults of all tracks
248
249                 Sample *buf    = (Sample *) malloc(sizeof(Sample) * fft_graph.windowSize());
250                 Sample *mixbuf = (Sample *) malloc(sizeof(Sample) * fft_graph.windowSize());
251                 float  *gain   = (float *)  malloc(sizeof(float) * fft_graph.windowSize());
252
253                 Selection& s (PublicEditor::instance().get_selection());
254
255
256                 // if timeSelection
257                 if (source_selection_ranges_rb.get_active()) {
258                         TimeSelection ts = s.time;
259
260                         for (TrackSelection::iterator i = s.tracks.begin(); i != s.tracks.end(); ++i) {
261                                 boost::shared_ptr<AudioPlaylist> pl
262                                         = boost::dynamic_pointer_cast<AudioPlaylist>((*i)->playlist());
263
264                                 if (!pl)
265                                         continue;
266
267                                 RouteUI *rui = dynamic_cast<RouteUI *>(*i);
268                                 int n_inputs = rui->route()->n_inputs().n_audio(); // FFT is audio only
269
270                                 // Busses don't have playlists, so we need to check that we actually are working with a playlist
271                                 if (!pl || !rui)
272                                         continue;
273
274                                 // std::cerr << "Analyzing ranges on track " << rui->route()->name() << std::endl;
275
276                                 FFTResult *res = fft_graph.prepareResult(rui->color(), rui->route()->name());
277                                 for (std::list<AudioRange>::iterator j = ts.begin(); j != ts.end(); ++j) {
278
279                                         int n;
280                                         for (int channel = 0; channel < n_inputs; channel++) {
281                                                 framecnt_t x = 0;
282
283                                                 while (x < j->length()) {
284                                                         // TODO: What about stereo+ channels? composite all to one, I guess
285
286                                                         n = fft_graph.windowSize();
287
288                                                         if (x + n >= (*j).length() ) {
289                                                                 n = (*j).length() - x;
290                                                         }
291
292                                                         n = pl->read(buf, mixbuf, gain, (*j).start + x, n, channel);
293
294                                                         if ( n < fft_graph.windowSize()) {
295                                                                 for (int j = n; j < fft_graph.windowSize(); j++) {
296                                                                         buf[j] = 0.0;
297                                                                 }
298                                                         }
299
300                                                         res->analyzeWindow(buf);
301
302                                                         x += n;
303                                                 }
304                                         }
305                                 }
306                                 res->finalize();
307
308                                 Gtk::TreeModel::Row newrow = *(tlmodel)->append();
309                                 newrow[tlcols.trackname]   = rui->route()->name();
310                                 newrow[tlcols.visible]     = true;
311                                 newrow[tlcols.color]       = rui->color();
312                                 newrow[tlcols.graph]       = res;
313                         } 
314                 } else if (source_selection_regions_rb.get_active()) {
315                         RegionSelection ars = s.regions;
316                         // std::cerr << "Analyzing selected regions" << std::endl;
317
318                         for (RegionSelection::iterator j = ars.begin(); j != ars.end(); ++j) {
319                                 // Check that the region is actually audio (so we can analyze it)
320                                 AudioRegionView* arv = dynamic_cast<AudioRegionView*>(*j);
321                                 if (!arv)
322                                         continue;
323
324                                 // std::cerr << " - " << (*j)->region().name() << ": " << (*j)->region().length() << " samples starting at " << (*j)->region().position() << std::endl;
325                                 RouteTimeAxisView *rtav = dynamic_cast<RouteTimeAxisView *>(&arv->get_time_axis_view());
326                                 if (!rtav) {
327                                         /* shouldn't happen... */
328                                         continue;
329                                 }
330                                 FFTResult *res = fft_graph.prepareResult(rtav->color(), arv->get_item_name());
331                                 int n;
332                                 for (unsigned int channel = 0; channel < arv->region()->n_channels(); channel++) {
333
334                                         framecnt_t x = 0;
335                                         framecnt_t length = arv->region()->length();
336
337                                         while (x < length) {
338                                                 // TODO: What about stereo+ channels? composite all to one, I guess
339
340                                                 n = fft_graph.windowSize();
341                                                 if (x + n >= length ) {
342                                                         n = length - x;
343                                                 }
344
345                                                 memset (buf, 0, n * sizeof (Sample));
346                                                 n = arv->audio_region()->read_at(buf, mixbuf, gain, arv->region()->position() + x, n, channel);
347
348                                                 if (n == 0)
349                                                         break;
350
351                                                 if ( n < fft_graph.windowSize()) {
352                                                         for (int j = n; j < fft_graph.windowSize(); j++) {
353                                                                 buf[j] = 0.0;
354                                                         }
355                                                 }
356
357                                                 res->analyzeWindow(buf);
358                                                 x += n;
359                                         }
360                                 }
361                                 // std::cerr << "Found: " << (*j)->get_item_name() << std::endl;
362                                 res->finalize();
363
364                                 Gtk::TreeModel::Row newrow = *(tlmodel)->append();
365                                 newrow[tlcols.trackname]   = arv->get_item_name();
366                                 newrow[tlcols.visible]     = true;
367                                 newrow[tlcols.color]       = rtav->color();
368                                 newrow[tlcols.graph]       = res;
369
370                         }
371
372                 }
373
374
375                 free(buf);
376                 free(mixbuf);
377
378                 track_list_ready = true;
379         } /* end lock */
380
381         fft_graph.redraw();
382 }
383
384 void
385 AnalysisWindow::source_selection_changed (Gtk::RadioButton *button)
386 {
387         // We are only interested in activation signals, not deactivation signals
388         if (!button->get_active())
389                 return;
390
391         /*
392         cerr << "AnalysisWindow: signal source = ";
393
394         if (button == &source_selection_ranges_rb) {
395                 cerr << "selected ranges" << endl;
396
397         } else if (button == &source_selection_regions_rb) {
398                 cerr << "selected regions" << endl;
399
400         } else {
401                 cerr << "unknown?" << endl;
402         }
403         */
404 }
405
406 void
407 AnalysisWindow::display_model_changed (Gtk::RadioButton *button)
408 {
409         // We are only interested in activation signals, not deactivation signals
410         if (!button->get_active())
411                 return;
412
413         /*
414         cerr << "AnalysisWindow: display model = ";
415
416         if (button == &display_model_composite_separate_rb) {
417                 cerr << "separate composites of tracks" << endl;
418         } else if (button == &display_model_composite_all_tracks_rb) {
419                 cerr << "composite of all tracks" << endl;
420         } else {
421                 cerr << "unknown?" << endl;
422         }
423         */
424 }
425
426