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