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>
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.
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.
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.
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>
30 #include "ardour/audioregion.h"
31 #include "ardour/audioplaylist.h"
32 #include "ardour/types.h"
34 #include "analysis_window.h"
37 #include "time_axis_view.h"
38 #include "public_editor.h"
39 #include "selection.h"
40 #include "audio_region_view.h"
44 using namespace ARDOUR;
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"))
56 set_name (_("FFT analysis window"));
57 set_title (_("Spectral Analysis"));
59 track_list_ready = false;
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);
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));
74 track_col->add_attribute(renderer->property_foreground_gdk(), tlcols.color);
75 track_col->set_expand(true);
78 tlmodel->signal_row_changed().connect (
79 sigc::mem_fun(*this, &AnalysisWindow::track_list_row_changed) );
81 fft_graph.set_analysis_window(this);
83 vbox.pack_start(track_list);
87 vbox.pack_start(source_selection_label, false, false);
90 Gtk::RadioButtonGroup group = source_selection_ranges_rb.get_group();
91 source_selection_regions_rb.set_group(group);
93 source_selection_ranges_rb.set_active();
95 vbox.pack_start (source_selection_ranges_rb, false, false);
96 vbox.pack_start (source_selection_regions_rb, false, false);
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));
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));
109 refresh_button.set_name("EditorGTKButton");
110 refresh_button.set_label(_("Re-analyze data"));
113 refresh_button.signal_clicked().connect ( sigc::bind ( sigc::mem_fun(*this, &AnalysisWindow::analyze_data), &refresh_button));
115 vbox.pack_start(refresh_button, false, false, 10);
117 vbox.pack_start(hseparator1, false, false);
119 // Feature checkboxes
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);
126 show_minmax_button.signal_toggled().connect( sigc::mem_fun(*this, &AnalysisWindow::show_minmax_changed));
127 vbox.pack_start(show_minmax_button, false, false);
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);
135 hbox.pack_start(vbox, Gtk::PACK_SHRINK);
137 // Analysis window on the right
138 fft_graph.ensure_style();
144 // And last we pack the hbox
147 track_list.show_all();
150 AnalysisWindow::~AnalysisWindow()
156 AnalysisWindow::show_minmax_changed()
158 fft_graph.set_show_minmax(show_minmax_button.get_active());
162 AnalysisWindow::show_normalized_changed()
164 fft_graph.set_show_normalized(show_normalized_button.get_active());
168 AnalysisWindow::show_proportional_changed()
170 fft_graph.set_show_proportioanl(show_proportional_button.get_active());
174 AnalysisWindow::set_rangemode()
176 source_selection_ranges_rb.set_active(true);
180 AnalysisWindow::set_regionmode()
182 source_selection_regions_rb.set_active(true);
186 AnalysisWindow::track_list_row_changed(const Gtk::TreeModel::Path& /*path*/, const Gtk::TreeModel::iterator& /*iter*/)
188 if (track_list_ready) {
195 AnalysisWindow::clear_tracklist()
197 // Empty track list & free old graphs
198 Gtk::TreeNodeChildren children = track_list.get_model()->children();
200 for (Gtk::TreeIter i = children.begin(); i != children.end(); i++) {
201 Gtk::TreeModel::Row row = *i;
203 FFTResult *delete_me = row[tlcols.graph];
207 // Make sure it's not drawn
208 row[tlcols.graph] = 0;
217 AnalysisWindow::analyze()
219 analyze_data(&refresh_button);
223 AnalysisWindow::analyze_data (Gtk::Button * /*button*/)
225 track_list_ready = false;
227 Glib::Threads::Mutex::Lock lm (track_list_lock);
229 // Empty track list & free old graphs
232 // first we gather the FFTResults of all tracks
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());
238 Selection& s (PublicEditor::instance().get_selection());
242 if (source_selection_ranges_rb.get_active()) {
243 TimeSelection ts = s.time;
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());
252 RouteUI *rui = dynamic_cast<RouteUI *>(*i);
253 int n_inputs = rui->route()->n_inputs().n_audio(); // FFT is audio only
255 // Busses don't have playlists, so we need to check that we actually are working with a playlist
259 // std::cerr << "Analyzing ranges on track " << rui->route()->name() << std::endl;
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) {
265 for (int channel = 0; channel < n_inputs; channel++) {
268 while (x < j->length()) {
269 // TODO: What about stereo+ channels? composite all to one, I guess
271 n = fft_graph.windowSize();
273 if (x + n >= (*j).length() ) {
274 n = (*j).length() - x;
277 n = pl->read(buf, mixbuf, gain, (*j).start + x, n, channel);
279 if ( n < fft_graph.windowSize()) {
280 for (int j = n; j < fft_graph.windowSize(); j++) {
285 res->analyzeWindow(buf);
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;
299 } else if (source_selection_regions_rb.get_active()) {
300 RegionSelection ars = s.regions;
301 // std::cerr << "Analyzing selected regions" << std::endl;
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);
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());
312 /* shouldn't happen... */
315 FFTResult *res = fft_graph.prepareResult(rtav->color(), arv->get_item_name());
317 for (unsigned int channel = 0; channel < arv->region()->n_channels(); channel++) {
320 samplecnt_t length = arv->region()->length();
323 // TODO: What about stereo+ channels? composite all to one, I guess
325 n = fft_graph.windowSize();
326 if (x + n >= length ) {
330 memset (buf, 0, n * sizeof (Sample));
331 n = arv->audio_region()->read_at(buf, mixbuf, gain, arv->region()->position() + x, n, channel);
336 if ( n < fft_graph.windowSize()) {
337 for (int j = n; j < fft_graph.windowSize(); j++) {
342 res->analyzeWindow(buf);
346 // std::cerr << "Found: " << (*j)->get_item_name() << std::endl;
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;
364 track_list_ready = true;
371 AnalysisWindow::source_selection_changed (Gtk::RadioButton *button)
373 // We are only interested in activation signals, not deactivation signals
374 if (!button->get_active())
378 cerr << "AnalysisWindow: signal source = ";
380 if (button == &source_selection_ranges_rb) {
381 cerr << "selected ranges" << endl;
383 } else if (button == &source_selection_regions_rb) {
384 cerr << "selected regions" << endl;
387 cerr << "unknown?" << endl;
393 AnalysisWindow::display_model_changed (Gtk::RadioButton *button)
395 // We are only interested in activation signals, not deactivation signals
396 if (!button->get_active())
400 cerr << "AnalysisWindow: display model = ";
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;
407 cerr << "unknown?" << endl;