enough with umpteen "i18n.h" files. Consolidate on pbd/i18n.h
[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 "pbd/i18n.h"
40
41 using namespace ARDOUR;
42 using namespace PBD;
43
44 AnalysisWindow::AnalysisWindow()
45         : source_selection_label       (_("Signal source"))
46         , source_selection_ranges_rb   (_("Selected ranges"))
47         , source_selection_regions_rb  (_("Selected regions"))
48         , show_minmax_button     (_("Show frequency power range"))
49         , show_normalized_button (_("Fit dB range"))
50         , show_proportional_button (_("Proportional Spectum, -18dB"))
51         , fft_graph (16384)
52 {
53         set_name(_("FFT analysis window"));
54         set_title (_("Spectral Analysis"));
55
56         track_list_ready = false;
57
58         // Left side: track list + controls
59         tlmodel = Gtk::ListStore::create(tlcols);
60         track_list.set_model (tlmodel);
61         track_list.append_column(_("Track"), tlcols.trackname);
62         track_list.append_column_editable(_("Show"), tlcols.visible);
63         track_list.set_headers_visible(true);
64         track_list.set_reorderable(false);
65         track_list.get_selection()->set_mode (Gtk::SELECTION_NONE);
66
67
68         Gtk::TreeViewColumn* track_col = track_list.get_column(0);
69         Gtk::CellRendererText* renderer = dynamic_cast<Gtk::CellRendererText*>(track_list.get_column_cell_renderer (0));
70
71         track_col->add_attribute(renderer->property_foreground_gdk(), tlcols.color);
72         track_col->set_expand(true);
73
74
75         tlmodel->signal_row_changed().connect (
76                         sigc::mem_fun(*this, &AnalysisWindow::track_list_row_changed) );
77
78         fft_graph.set_analysis_window(this);
79
80         vbox.pack_start(track_list);
81
82
83         // "Signal source"
84         vbox.pack_start(source_selection_label, false, false);
85
86         {
87                 Gtk::RadioButtonGroup group = source_selection_ranges_rb.get_group();
88                 source_selection_regions_rb.set_group(group);
89
90                 source_selection_ranges_rb.set_active();
91
92                 vbox.pack_start (source_selection_ranges_rb,  false, false);
93                 vbox.pack_start (source_selection_regions_rb, false, false);
94
95                 // "Selected ranges" radio
96                 source_selection_ranges_rb.signal_toggled().connect (
97                                 sigc::bind ( sigc::mem_fun(*this, &AnalysisWindow::source_selection_changed), &source_selection_ranges_rb));
98
99                 // "Selected regions" radio
100                 source_selection_regions_rb.signal_toggled().connect (
101                                 sigc::bind ( sigc::mem_fun(*this, &AnalysisWindow::source_selection_changed), &source_selection_regions_rb));
102         }
103
104         // Analyze button
105
106         refresh_button.set_name("EditorGTKButton");
107         refresh_button.set_label(_("Re-analyze data"));
108
109
110         refresh_button.signal_clicked().connect ( sigc::bind ( sigc::mem_fun(*this, &AnalysisWindow::analyze_data), &refresh_button));
111
112         vbox.pack_start(refresh_button, false, false, 10);
113
114         vbox.pack_start(hseparator1, false, false);
115
116         // Feature checkboxes
117
118         // normalize, fit y-range
119         show_normalized_button.signal_toggled().connect( sigc::mem_fun(*this, &AnalysisWindow::show_normalized_changed));
120         vbox.pack_start(show_normalized_button, false, false);
121
122         // minmax
123         show_minmax_button.signal_toggled().connect( sigc::mem_fun(*this, &AnalysisWindow::show_minmax_changed));
124         vbox.pack_start(show_minmax_button, false, false);
125
126         // pink-noise / proportional spectrum
127         show_proportional_button.signal_toggled().connect( sigc::mem_fun(*this, &AnalysisWindow::show_proportional_changed));
128         vbox.pack_start(show_proportional_button, false, false);
129
130
131
132         hbox.pack_start(vbox, Gtk::PACK_SHRINK);
133
134         // Analysis window on the right
135         fft_graph.ensure_style();
136
137         hbox.add(fft_graph);
138
139
140
141         // And last we pack the hbox
142         add(hbox);
143         show_all();
144         track_list.show_all();
145 }
146
147 AnalysisWindow::~AnalysisWindow()
148 {
149
150 }
151
152 void
153 AnalysisWindow::show_minmax_changed()
154 {
155         fft_graph.set_show_minmax(show_minmax_button.get_active());
156 }
157
158 void
159 AnalysisWindow::show_normalized_changed()
160 {
161         fft_graph.set_show_normalized(show_normalized_button.get_active());
162 }
163
164 void
165 AnalysisWindow::show_proportional_changed()
166 {
167         fft_graph.set_show_proportioanl(show_proportional_button.get_active());
168 }
169
170 void
171 AnalysisWindow::set_rangemode()
172 {
173         source_selection_ranges_rb.set_active(true);
174 }
175
176 void
177 AnalysisWindow::set_regionmode()
178 {
179         source_selection_regions_rb.set_active(true);
180 }
181
182 void
183 AnalysisWindow::track_list_row_changed(const Gtk::TreeModel::Path& /*path*/, const Gtk::TreeModel::iterator& /*iter*/)
184 {
185         if (track_list_ready) {
186                 fft_graph.redraw();
187         }
188 }
189
190
191 void
192 AnalysisWindow::clear_tracklist()
193 {
194         // Empty track list & free old graphs
195         Gtk::TreeNodeChildren children = track_list.get_model()->children();
196
197         for (Gtk::TreeIter i = children.begin(); i != children.end(); i++) {
198                 Gtk::TreeModel::Row row = *i;
199
200                 FFTResult *delete_me = row[tlcols.graph];
201                 if (delete_me == 0)
202                         continue;
203
204                 // Make sure it's not drawn
205                 row[tlcols.graph] = 0;
206
207                 delete delete_me;
208         }
209
210         tlmodel->clear();
211 }
212
213 void
214 AnalysisWindow::analyze()
215 {
216         analyze_data(&refresh_button);
217 }
218
219 void
220 AnalysisWindow::analyze_data (Gtk::Button * /*button*/)
221 {
222         track_list_ready = false;
223         {
224                 Glib::Threads::Mutex::Lock lm  (track_list_lock);
225
226                 // Empty track list & free old graphs
227                 clear_tracklist();
228
229                 // first we gather the FFTResults of all tracks
230
231                 Sample *buf    = (Sample *) malloc(sizeof(Sample) * fft_graph.windowSize());
232                 Sample *mixbuf = (Sample *) malloc(sizeof(Sample) * fft_graph.windowSize());
233                 float  *gain   = (float *)  malloc(sizeof(float) * fft_graph.windowSize());
234
235                 Selection& s (PublicEditor::instance().get_selection());
236
237
238                 // if timeSelection
239                 if (source_selection_ranges_rb.get_active()) {
240                         TimeSelection ts = s.time;
241
242                         for (TrackSelection::iterator i = s.tracks.begin(); i != s.tracks.end(); ++i) {
243                                 boost::shared_ptr<AudioPlaylist> pl
244                                         = boost::dynamic_pointer_cast<AudioPlaylist>((*i)->playlist());
245
246                                 if (!pl)
247                                         continue;
248
249                                 RouteUI *rui = dynamic_cast<RouteUI *>(*i);
250                                 int n_inputs = rui->route()->n_inputs().n_audio(); // FFT is audio only
251
252                                 // Busses don't have playlists, so we need to check that we actually are working with a playlist
253                                 if (!pl || !rui)
254                                         continue;
255
256                                 // std::cerr << "Analyzing ranges on track " << rui->route()->name() << std::endl;
257
258                                 FFTResult *res = fft_graph.prepareResult(rui->route_color(), rui->route()->name());
259                                 for (std::list<AudioRange>::iterator j = ts.begin(); j != ts.end(); ++j) {
260
261                                         int n;
262                                         for (int channel = 0; channel < n_inputs; channel++) {
263                                                 framecnt_t x = 0;
264
265                                                 while (x < j->length()) {
266                                                         // TODO: What about stereo+ channels? composite all to one, I guess
267
268                                                         n = fft_graph.windowSize();
269
270                                                         if (x + n >= (*j).length() ) {
271                                                                 n = (*j).length() - x;
272                                                         }
273
274                                                         n = pl->read(buf, mixbuf, gain, (*j).start + x, n, channel);
275
276                                                         if ( n < fft_graph.windowSize()) {
277                                                                 for (int j = n; j < fft_graph.windowSize(); j++) {
278                                                                         buf[j] = 0.0;
279                                                                 }
280                                                         }
281
282                                                         res->analyzeWindow(buf);
283
284                                                         x += n;
285                                                 }
286                                         }
287                                 }
288                                 res->finalize();
289
290                                 Gtk::TreeModel::Row newrow = *(tlmodel)->append();
291                                 newrow[tlcols.trackname]   = rui->route()->name();
292                                 newrow[tlcols.visible]     = true;
293                                 newrow[tlcols.color]       = rui->route_color ();
294                                 newrow[tlcols.graph]       = res;
295                         }
296                 } else if (source_selection_regions_rb.get_active()) {
297                         RegionSelection ars = s.regions;
298                         // std::cerr << "Analyzing selected regions" << std::endl;
299
300                         for (RegionSelection::iterator j = ars.begin(); j != ars.end(); ++j) {
301                                 // Check that the region is actually audio (so we can analyze it)
302                                 AudioRegionView* arv = dynamic_cast<AudioRegionView*>(*j);
303                                 if (!arv)
304                                         continue;
305
306                                 // std::cerr << " - " << (*j)->region().name() << ": " << (*j)->region().length() << " samples starting at " << (*j)->region().position() << std::endl;
307                                 RouteTimeAxisView *rtav = dynamic_cast<RouteTimeAxisView *>(&arv->get_time_axis_view());
308                                 if (!rtav) {
309                                         /* shouldn't happen... */
310                                         continue;
311                                 }
312                                 FFTResult *res = fft_graph.prepareResult(rtav->color(), arv->get_item_name());
313                                 int n;
314                                 for (unsigned int channel = 0; channel < arv->region()->n_channels(); channel++) {
315
316                                         framecnt_t x = 0;
317                                         framecnt_t length = arv->region()->length();
318
319                                         while (x < length) {
320                                                 // TODO: What about stereo+ channels? composite all to one, I guess
321
322                                                 n = fft_graph.windowSize();
323                                                 if (x + n >= length ) {
324                                                         n = length - x;
325                                                 }
326
327                                                 memset (buf, 0, n * sizeof (Sample));
328                                                 n = arv->audio_region()->read_at(buf, mixbuf, gain, arv->region()->position() + x, n, channel);
329
330                                                 if (n == 0)
331                                                         break;
332
333                                                 if ( n < fft_graph.windowSize()) {
334                                                         for (int j = n; j < fft_graph.windowSize(); j++) {
335                                                                 buf[j] = 0.0;
336                                                         }
337                                                 }
338
339                                                 res->analyzeWindow(buf);
340                                                 x += n;
341                                         }
342                                 }
343                                 // std::cerr << "Found: " << (*j)->get_item_name() << std::endl;
344                                 res->finalize();
345
346                                 Gtk::TreeModel::Row newrow = *(tlmodel)->append();
347                                 newrow[tlcols.trackname]   = arv->get_item_name();
348                                 newrow[tlcols.visible]     = true;
349                                 newrow[tlcols.color]       = rtav->color();
350                                 newrow[tlcols.graph]       = res;
351
352                         }
353
354                 }
355
356
357                 free(buf);
358                 free(mixbuf);
359                 free(gain);
360
361                 track_list_ready = true;
362         } /* end lock */
363
364         fft_graph.redraw();
365 }
366
367 void
368 AnalysisWindow::source_selection_changed (Gtk::RadioButton *button)
369 {
370         // We are only interested in activation signals, not deactivation signals
371         if (!button->get_active())
372                 return;
373
374         /*
375         cerr << "AnalysisWindow: signal source = ";
376
377         if (button == &source_selection_ranges_rb) {
378                 cerr << "selected ranges" << endl;
379
380         } else if (button == &source_selection_regions_rb) {
381                 cerr << "selected regions" << endl;
382
383         } else {
384                 cerr << "unknown?" << endl;
385         }
386         */
387 }
388
389 void
390 AnalysisWindow::display_model_changed (Gtk::RadioButton *button)
391 {
392         // We are only interested in activation signals, not deactivation signals
393         if (!button->get_active())
394                 return;
395
396         /*
397         cerr << "AnalysisWindow: display model = ";
398
399         if (button == &display_model_composite_separate_rb) {
400                 cerr << "separate composites of tracks" << endl;
401         } else if (button == &display_model_composite_all_tracks_rb) {
402                 cerr << "composite of all tracks" << endl;
403         } else {
404                 cerr << "unknown?" << endl;
405         }
406         */
407 }
408
409