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