NO-OP: whitespace
[ardour.git] / gtk2_ardour / plugin_eq_gui.cc
1 /*
2  * Copyright (C) 2018 Robin Gareus <robin@gareus.org>
3  * Copyright (C) 2008 Paul Davis
4  * Original Author: Sampo Savolainen
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2
9  * of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19  */
20
21 #include <algorithm>
22 #include <math.h>
23 #include <iomanip>
24 #include <iostream>
25 #include <sstream>
26
27 #ifdef COMPILER_MSVC
28 # include <float.h>
29 /* isinf() & isnan() are C99 standards, which older MSVC doesn't provide */
30 # define ISINF(val) !((bool)_finite((double)val))
31 # define ISNAN(val) (bool)_isnan((double)val)
32 #else
33 # define ISINF(val) std::isinf((val))
34 # define ISNAN(val) std::isnan((val))
35 #endif
36
37 #include <gtkmm/box.h>
38 #include <gtkmm/button.h>
39 #include <gtkmm/checkbutton.h>
40
41 #include "ardour/audio_buffer.h"
42 #include "ardour/data_type.h"
43 #include "ardour/chan_mapping.h"
44 #include "ardour/plugin_insert.h"
45 #include "ardour/session.h"
46
47 #include "plugin_eq_gui.h"
48 #include "fft.h"
49 #include "ardour_ui.h"
50 #include "gui_thread.h"
51
52 #include "pbd/i18n.h"
53
54 using namespace ARDOUR;
55
56 PluginEqGui::PluginEqGui (boost::shared_ptr<ARDOUR::PluginInsert> pluginInsert)
57         : _min_dB (-12.0)
58         , _max_dB (+12.0)
59         , _step_dB (3.0)
60         , _block_size (0)
61         , _buffer_size (0)
62         , _signal_buffer_size (0)
63         , _impulse_fft (0)
64         , _signal_input_fft (0)
65         , _signal_output_fft (0)
66         , _plugin_insert (pluginInsert)
67         , _pointer_in_area_xpos (-1)
68 {
69         _signal_analysis_running = false;
70         _samplerate = ARDOUR_UI::instance()->the_session()->sample_rate();
71
72         _log_coeff = (1.0 - 2.0 * (1000.0 / (_samplerate / 2.0))) / powf (1000.0 / (_samplerate / 2.0), 2.0);
73         _log_max = log10f (1 + _log_coeff);
74
75         // Setup analysis drawing area
76         _analysis_scale_surface = 0;
77
78         _analysis_area = new Gtk::DrawingArea();
79         _analysis_width = 256.0;
80         _analysis_height = 256.0;
81         _analysis_area->set_size_request (_analysis_width, _analysis_height);
82
83         _analysis_area->add_events (Gdk::POINTER_MOTION_MASK | Gdk::LEAVE_NOTIFY_MASK | Gdk::BUTTON_PRESS_MASK);
84
85         _analysis_area->signal_expose_event().connect (sigc::mem_fun (*this, &PluginEqGui::expose_analysis_area));
86         _analysis_area->signal_size_allocate().connect (sigc::mem_fun (*this, &PluginEqGui::resize_analysis_area));
87         _analysis_area->signal_motion_notify_event().connect (sigc::mem_fun (*this, &PluginEqGui::analysis_area_mouseover));
88         _analysis_area->signal_leave_notify_event().connect (sigc::mem_fun (*this, &PluginEqGui::analysis_area_mouseexit));
89
90         // dB selection
91         dBScaleModel = Gtk::ListStore::create (dBColumns);
92
93         dBScaleCombo = new Gtk::ComboBox (dBScaleModel, false);
94         dBScaleCombo->set_title (_("dB scale"));
95
96 #define ADD_DB_ROW(MIN,MAX,STEP,NAME) \
97         { \
98                 Gtk::TreeModel::Row row = *(dBScaleModel->append()); \
99                 row[dBColumns.dBMin]  = (MIN); \
100                 row[dBColumns.dBMax]  = (MAX); \
101                 row[dBColumns.dBStep] = (STEP); \
102                 row[dBColumns.name]   = NAME; \
103         }
104
105         ADD_DB_ROW( -6,  +6, 1, "-6dB .. +6dB");
106         ADD_DB_ROW(-12, +12, 3, "-12dB .. +12dB");
107         ADD_DB_ROW(-24, +24, 5, "-24dB .. +24dB");
108         ADD_DB_ROW(-36, +36, 6, "-36dB .. +36dB");
109         ADD_DB_ROW(-64, +64,12, "-64dB .. +64dB");
110
111 #undef ADD_DB_ROW
112
113         dBScaleCombo -> pack_start(dBColumns.name);
114         dBScaleCombo -> set_active(1);
115
116         dBScaleCombo -> signal_changed().connect (sigc::mem_fun(*this, &PluginEqGui::change_dB_scale));
117
118         Gtk::Label *dBComboLabel = new Gtk::Label (_("dB scale"));
119
120         Gtk::HBox *dBSelectBin = new Gtk::HBox (false, 5);
121         dBSelectBin->add (*manage(dBComboLabel));
122         dBSelectBin->add (*manage(dBScaleCombo));
123
124         // Phase checkbutton
125         _signal_button = new Gtk::CheckButton (_("Plot live signal"));
126         _signal_button->set_active(true);
127
128         // Phase checkbutton
129         _phase_button = new Gtk::CheckButton (_("Show phase"));
130         _phase_button->set_active (true);
131         _phase_button->signal_toggled().connect (sigc::mem_fun(*this, &PluginEqGui::redraw_scales));
132
133         // Freq/dB info for mouse over
134         _pointer_info = new Gtk::Label ("", 1, 0.5);
135         _pointer_info->set_size_request (_analysis_width / 4, -1);
136         _pointer_info->set_name ("PluginAnalysisInfoLabel");
137
138         // populate table
139         attach (*manage(_analysis_area), 0, 4, 0, 1);
140         attach (*manage(dBSelectBin),    0, 1, 1, 2, Gtk::SHRINK, Gtk::SHRINK);
141         attach (*manage(_signal_button), 1, 2, 1, 2, Gtk::SHRINK, Gtk::SHRINK);
142         attach (*manage(_phase_button),  2, 3, 1, 2, Gtk::SHRINK, Gtk::SHRINK);
143         attach (*manage(_pointer_info),  3, 4, 1, 2, Gtk::FILL,   Gtk::SHRINK);
144 }
145
146 PluginEqGui::~PluginEqGui ()
147 {
148         stop_listening ();
149
150         if (_analysis_scale_surface) {
151                 cairo_surface_destroy (_analysis_scale_surface);
152         }
153
154         delete _impulse_fft;
155         _impulse_fft = 0;
156         delete _signal_input_fft;
157         _signal_input_fft = 0;
158         delete _signal_output_fft;
159         _signal_output_fft = 0;
160
161         // all gui objects are *manage'd by the inherited Table object
162 }
163
164 static inline float
165 power_to_dB (float a)
166 {
167         return 10.0 * log10f (a);
168 }
169
170 void
171 PluginEqGui::start_listening ()
172 {
173         if (!_plugin) {
174                 _plugin = _plugin_insert->get_impulse_analysis_plugin ();
175         }
176
177         _plugin->activate ();
178         set_buffer_size (8192, 16384);
179         _block_size = 0; // re-initialize the plugin next time.
180
181         /* Connect the realtime signal collection callback */
182         _plugin_insert->AnalysisDataGathered.connect (analysis_connection, invalidator (*this), boost::bind (&PluginEqGui::signal_collect_callback, this, _1, _2), gui_context());
183 }
184
185 void
186 PluginEqGui::stop_listening ()
187 {
188         analysis_connection.disconnect ();
189         _plugin->deactivate ();
190 }
191
192 void
193 PluginEqGui::on_hide ()
194 {
195         stop_updating ();
196         Gtk::Table::on_hide ();
197 }
198
199 void
200 PluginEqGui::stop_updating ()
201 {
202         if (_update_connection.connected ()) {
203                 _update_connection.disconnect ();
204         }
205 }
206
207 void
208 PluginEqGui::start_updating ()
209 {
210         if (!_update_connection.connected() && is_visible()) {
211                 _update_connection = Glib::signal_timeout().connect (sigc::mem_fun (this, &PluginEqGui::timeout_callback), 250);
212         }
213 }
214
215 void
216 PluginEqGui::on_show ()
217 {
218         Gtk::Table::on_show ();
219
220         start_updating ();
221
222         Gtk::Widget *toplevel = get_toplevel ();
223         if (toplevel) {
224                 if (!_window_unmap_connection.connected ()) {
225                         _window_unmap_connection = toplevel->signal_unmap().connect (sigc::mem_fun (this, &PluginEqGui::stop_updating));
226                 }
227
228                 if (!_window_map_connection.connected ()) {
229                         _window_map_connection = toplevel->signal_map().connect (sigc::mem_fun (this, &PluginEqGui::start_updating));
230                 }
231         }
232 }
233
234 void
235 PluginEqGui::change_dB_scale ()
236 {
237         Gtk::TreeModel::iterator iter = dBScaleCombo -> get_active ();
238
239         Gtk::TreeModel::Row row;
240
241         if (iter && (row = *iter)) {
242                 _min_dB = row[dBColumns.dBMin];
243                 _max_dB = row[dBColumns.dBMax];
244                 _step_dB = row[dBColumns.dBStep];
245
246                 redraw_scales ();
247         }
248 }
249
250 void
251 PluginEqGui::redraw_scales ()
252 {
253
254         if (_analysis_scale_surface) {
255                 cairo_surface_destroy (_analysis_scale_surface);
256                 _analysis_scale_surface = 0;
257         }
258
259         _analysis_area->queue_draw ();
260
261         // TODO: Add graph legend!
262 }
263
264 void
265 PluginEqGui::set_buffer_size (uint32_t size, uint32_t signal_size)
266 {
267         if (_buffer_size == size && _signal_buffer_size == signal_size) {
268                 return;
269         }
270
271         GTKArdour::FFT *tmp1 = _impulse_fft;
272         GTKArdour::FFT *tmp2 = _signal_input_fft;
273         GTKArdour::FFT *tmp3 = _signal_output_fft;
274
275         try {
276                 _impulse_fft       = new GTKArdour::FFT (size);
277                 _signal_input_fft  = new GTKArdour::FFT (signal_size);
278                 _signal_output_fft = new GTKArdour::FFT (signal_size);
279         } catch (...) {
280                 // Don't care about lost memory, we're screwed anyhow
281                 _impulse_fft       = tmp1;
282                 _signal_input_fft  = tmp2;
283                 _signal_output_fft = tmp3;
284                 throw;
285         }
286
287         delete tmp1;
288         delete tmp2;
289         delete tmp3;
290
291         _buffer_size = size;
292         _signal_buffer_size = signal_size;
293
294         /* allocate separate in+out buffers, VST cannot process in-place */
295         ARDOUR::ChanCount acount (_plugin->get_info()->n_inputs + _plugin->get_info()->n_outputs);
296         ARDOUR::ChanCount ccount = ARDOUR::ChanCount::max (_plugin->get_info()->n_inputs, _plugin->get_info()->n_outputs);
297
298         for (ARDOUR::DataType::iterator i = ARDOUR::DataType::begin(); i != ARDOUR::DataType::end(); ++i) {
299                 _bufferset.ensure_buffers (*i, acount.get (*i), _buffer_size);
300                 _collect_bufferset.ensure_buffers (*i, ccount.get (*i), _buffer_size);
301         }
302
303         _bufferset.set_count (acount);
304         _collect_bufferset.set_count (ccount);
305 }
306
307 void
308 PluginEqGui::resize_analysis_area (Gtk::Allocation& size)
309 {
310         _analysis_width  = (float)size.get_width();
311         _analysis_height = (float)size.get_height();
312
313         if (_analysis_scale_surface) {
314                 cairo_surface_destroy (_analysis_scale_surface);
315                 _analysis_scale_surface = 0;
316         }
317
318         _pointer_info->set_size_request (_analysis_width / 4, -1);
319 }
320
321 bool
322 PluginEqGui::timeout_callback ()
323 {
324         if (!_signal_analysis_running) {
325                 _signal_analysis_running = true;
326                 _plugin_insert -> collect_signal_for_analysis (_signal_buffer_size);
327         }
328
329         run_impulse_analysis ();
330         return true;
331 }
332
333 void
334 PluginEqGui::signal_collect_callback (ARDOUR::BufferSet* in, ARDOUR::BufferSet* out)
335 {
336         ENSURE_GUI_THREAD (*this, &PluginEqGui::signal_collect_callback, in, out);
337
338         _signal_input_fft ->reset ();
339         _signal_output_fft->reset ();
340
341         for (uint32_t i = 0; i < _plugin_insert->input_streams().n_audio(); ++i) {
342                 _signal_input_fft ->analyze (in ->get_audio (i).data(), GTKArdour::FFT::HANN);
343         }
344
345         for (uint32_t i = 0; i < _plugin_insert->output_streams().n_audio(); ++i) {
346                 _signal_output_fft->analyze (out->get_audio (i).data(), GTKArdour::FFT::HANN);
347         }
348
349         _signal_input_fft ->calculate ();
350         _signal_output_fft->calculate ();
351
352         _signal_analysis_running = false;
353         _analysis_area->queue_draw ();
354 }
355
356 void
357 PluginEqGui::run_impulse_analysis ()
358 {
359         /* Allocate some thread-local buffers so that Plugin::connect_and_run can use them */
360         ARDOUR_UI::instance()->get_process_buffers ();
361
362         uint32_t inputs  = _plugin->get_info()->n_inputs.n_audio();
363         uint32_t outputs = _plugin->get_info()->n_outputs.n_audio();
364
365         /* Create the impulse, can't use silence() because consecutive calls won't work */
366         for (uint32_t i = 0; i < inputs; ++i) {
367                 ARDOUR::AudioBuffer& buf = _bufferset.get_audio (i);
368                 ARDOUR::Sample* d = buf.data ();
369                 memset (d, 0, sizeof (ARDOUR::Sample) * _buffer_size);
370                 *d = 1.0;
371         }
372
373         /* Silence collect buffers to copy data to */
374         for (uint32_t i = 0; i < outputs; ++i) {
375                 ARDOUR::AudioBuffer &buf = _collect_bufferset.get_audio (i);
376                 ARDOUR::Sample *d = buf.data ();
377                 memset (d, 0, sizeof (ARDOUR::Sample) * _buffer_size);
378         }
379
380         /* create default linear I/O maps */
381         ARDOUR::ChanMapping in_map (_plugin->get_info()->n_inputs);
382         ARDOUR::ChanMapping out_map (_plugin->get_info()->n_outputs);
383         /* map output buffers after input buffers (no inplace for VST) */
384         out_map.offset_to (DataType::AUDIO, inputs);
385
386         /* run at most at session's block size chunks.
387          *
388          * This is important since VSTs may call audioMasterGetBlockSize
389          * or access various other /real/ session paramaters using the
390          * audioMasterCallback
391          */
392         samplecnt_t block_size = ARDOUR_UI::instance()->the_session()->get_block_size();
393         if (_block_size != block_size) {
394                 _block_size = block_size;
395                 _plugin->set_block_size (block_size);
396         }
397
398         samplepos_t sample_pos = 0;
399         samplecnt_t latency = _plugin->signal_latency ();
400         samplecnt_t samples_remain = _buffer_size + latency;
401
402         _impulse_fft->reset ();
403
404         while (samples_remain > 0) {
405
406                 samplecnt_t n_samples = std::min (samples_remain, block_size);
407                 _plugin->connect_and_run (_bufferset, sample_pos, sample_pos + n_samples, 1.0, in_map, out_map, n_samples, 0);
408                 samples_remain -= n_samples;
409
410                 /* zero input buffers */
411                 if (sample_pos == 0 && samples_remain > 0) {
412                         for (uint32_t i = 0; i < inputs; ++i) {
413                                 _bufferset.get_audio (i).data()[0] = 0.f;
414                         }
415                 }
416
417 #ifndef NDEBUG
418                 if (samples_remain > 0) {
419                         for (uint32_t i = 0; i < inputs; ++i) {
420                                 pframes_t unused;
421                                 assert (_bufferset.get_audio (i).check_silence (block_size, unused));
422                         }
423                 }
424 #endif
425
426                 if (sample_pos + n_samples > latency) {
427                         samplecnt_t dst_off = sample_pos >= latency ? sample_pos - latency : 0;
428                         samplecnt_t src_off = sample_pos >= latency ? 0 : latency - sample_pos;
429                         samplecnt_t n_copy = std::min (n_samples, sample_pos + n_samples - latency);
430
431                         assert (dst_off + n_copy <= _buffer_size);
432                         assert (src_off + n_copy <= _block_size);
433
434                         for (uint32_t i = 0; i < outputs; ++i) {
435                                 memcpy (
436                                                 &(_collect_bufferset.get_audio (i).data()[dst_off]),
437                                                 &(_bufferset.get_audio (inputs + i).data()[src_off]),
438                                                 n_copy * sizeof (float));
439                         }
440                 }
441
442                 sample_pos += n_samples;
443         }
444
445         for (uint32_t i = 0; i < outputs; ++i) {
446                 _impulse_fft->analyze (_collect_bufferset.get_audio (i).data());
447         }
448         _impulse_fft->calculate ();
449
450         _analysis_area->queue_draw ();
451
452         ARDOUR_UI::instance ()->drop_process_buffers ();
453 }
454
455 void
456 PluginEqGui::update_pointer_info( float x)
457 {
458         /* find the bin corresponding to x (see plot_impulse_amplitude) */
459         int i = roundf ((powf (10, _log_max * x / _analysis_width) - 1.0) * _impulse_fft->bins() / _log_coeff);
460         float dB = power_to_dB (_impulse_fft->power_at_bin (i));
461         /* calc freq corresponding to bin */
462         const int freq = std::max (1, (int) roundf ((float)i / (float)_impulse_fft->bins() * _samplerate / 2.f));
463
464         _pointer_in_area_freq = round (_analysis_width * log10f (1.0 + (float)i / (float)_impulse_fft->bins() * _log_coeff) / _log_max);
465
466         std::stringstream ss;
467         ss << std::fixed;
468         if (freq >= 10000) {
469                 ss <<  std::setprecision (1) << freq / 1000.0 << "kHz";
470         } else if (freq >= 1000) {
471                 ss <<  std::setprecision (2) << freq / 1000.0 << "kHz";
472         } else {
473                 ss <<  std::setprecision (0) << freq << "Hz";
474         }
475         ss << " " << std::setw (6) << std::setprecision (1) << std::showpos << dB;
476         ss << std::setw (0) << "dB";
477
478         if (_phase_button->get_active ()) {
479                 float phase = 180. * _impulse_fft->phase_at_bin (i) / M_PI;
480                 ss << " " << std::setw (6) << std::setprecision (1) << std::showpos << phase;
481                 ss << std::setw (0) << "\u00B0";
482         }
483         _pointer_info->set_text (ss.str());
484 }
485
486 bool
487 PluginEqGui::analysis_area_mouseover (GdkEventMotion *event)
488 {
489         update_pointer_info (event->x);
490         _pointer_in_area_xpos = event->x;
491         _analysis_area->queue_draw ();
492         return true;
493 }
494
495 bool
496 PluginEqGui::analysis_area_mouseexit (GdkEventCrossing *)
497 {
498         _pointer_info->set_text ("");
499         _pointer_in_area_xpos = -1;
500         _analysis_area->queue_draw ();
501         return true;
502 }
503
504 bool
505 PluginEqGui::expose_analysis_area (GdkEventExpose *)
506 {
507         redraw_analysis_area ();
508         return true;
509 }
510
511 void
512 PluginEqGui::draw_analysis_scales (cairo_t *ref_cr)
513 {
514         // TODO: check whether we need rounding
515         _analysis_scale_surface = cairo_surface_create_similar (cairo_get_target (ref_cr),
516                         CAIRO_CONTENT_COLOR,
517                         _analysis_width,
518                         _analysis_height);
519
520         cairo_t *cr = cairo_create (_analysis_scale_surface);
521
522         cairo_set_source_rgb (cr, 0.0, 0.0, 0.0);
523         cairo_rectangle (cr, 0.0, 0.0, _analysis_width, _analysis_height);
524         cairo_fill (cr);
525
526         draw_scales_power (_analysis_area, cr);
527
528         if (_phase_button->get_active ()) {
529                 draw_scales_phase (_analysis_area, cr);
530         }
531
532         cairo_destroy (cr);
533 }
534
535 void
536 PluginEqGui::redraw_analysis_area ()
537 {
538         cairo_t *cr;
539
540         cr = gdk_cairo_create (GDK_DRAWABLE(_analysis_area->get_window()->gobj()));
541
542         if (_analysis_scale_surface == 0) {
543                 draw_analysis_scales (cr);
544         }
545
546         cairo_copy_page (cr);
547
548         cairo_set_source_surface (cr, _analysis_scale_surface, 0.0, 0.0);
549         cairo_paint (cr);
550
551         cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND);
552
553         if (_phase_button->get_active()) {
554                 plot_impulse_phase (_analysis_area, cr);
555         }
556
557         plot_impulse_amplitude (_analysis_area, cr);
558
559         if (_pointer_in_area_xpos >= 0) {
560                 update_pointer_info (_pointer_in_area_xpos);
561         }
562
563         if (_signal_button->get_active()) {
564                 plot_signal_amplitude_difference (_analysis_area, cr);
565         }
566
567         if (_pointer_in_area_xpos >= 0 && _pointer_in_area_freq > 0) {
568                 const double dashed[] = {0.0, 2.0};
569                 cairo_set_dash (cr, dashed, 2, 0);
570                 cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
571                 cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
572                 cairo_set_line_width (cr, 1.0);
573                 cairo_move_to (cr, _pointer_in_area_freq - .5, -.5);
574                 cairo_line_to (cr, _pointer_in_area_freq - .5, _analysis_height - .5);
575                 cairo_stroke (cr);
576         }
577
578         cairo_destroy (cr);
579 }
580
581 #define PHASE_PROPORTION 0.5
582
583 void
584 PluginEqGui::draw_scales_phase (Gtk::Widget*, cairo_t *cr)
585 {
586         float y;
587         cairo_font_extents_t extents;
588         cairo_font_extents (cr, &extents);
589
590         char buf[256];
591         cairo_text_extents_t t_ext;
592
593         for (uint32_t i = 0; i < 3; i++) {
594
595                 y = _analysis_height / 2.0 - (float)i * (_analysis_height / 8.0) * PHASE_PROPORTION;
596
597                 cairo_set_source_rgb (cr, .8, .9, 0.2);
598                 if (i == 0) {
599                         snprintf (buf,256, "0\u00b0");
600                 } else {
601                         snprintf (buf,256, "%d\u00b0", (i * 45));
602                 }
603                 cairo_text_extents (cr, buf, &t_ext);
604                 cairo_move_to (cr, _analysis_width - t_ext.width - t_ext.x_bearing - 2.0, y - extents.descent);
605                 cairo_show_text (cr, buf);
606
607                 if (i == 0)
608                         continue;
609
610
611                 cairo_set_source_rgba (cr, .8, .9, 0.2, 0.6 / (float)i);
612                 cairo_move_to (cr, 0.0,             y);
613                 cairo_line_to (cr, _analysis_width, y);
614
615
616                 y = _analysis_height / 2.0 + (float)i * (_analysis_height / 8.0) * PHASE_PROPORTION;
617
618                 // label
619                 snprintf (buf,256, "-%d\u00b0", (i * 45));
620                 cairo_set_source_rgb (cr, .8, .9, 0.2);
621                 cairo_text_extents (cr, buf, &t_ext);
622                 cairo_move_to (cr, _analysis_width - t_ext.width - t_ext.x_bearing - 2.0, y - extents.descent);
623                 cairo_show_text (cr, buf);
624
625                 // line
626                 cairo_set_source_rgba (cr, .8, .9, 0.2, 0.6 / (float)i);
627                 cairo_move_to (cr, 0.0,             y);
628                 cairo_line_to (cr, _analysis_width, y);
629
630                 cairo_set_line_width (cr, 0.25 + 1.0 / (float)(i + 1));
631                 cairo_stroke (cr);
632         }
633 }
634
635 void
636 PluginEqGui::plot_impulse_phase (Gtk::Widget *w, cairo_t *cr)
637 {
638         float x,y;
639
640         int prevX = 0;
641         float avgY = 0.0;
642         int avgNum = 0;
643
644         // float width  = w->get_width();
645         float height = w->get_height ();
646
647         cairo_set_source_rgba (cr, 0.95, 0.3, 0.2, 1.0);
648         for (uint32_t i = 0; i < _impulse_fft->bins() - 1; ++i) {
649                 // x coordinate of bin i
650                 x  = log10f (1.0 + (float)i / (float)_impulse_fft->bins() * _log_coeff) / _log_max;
651                 x *= _analysis_width;
652
653                 y  = _analysis_height/2.0 - (_impulse_fft->phase_at_bin(i)/M_PI)*(_analysis_height/2.0)*PHASE_PROPORTION;
654                 if (i == 0) {
655                         cairo_move_to (cr, x, y);
656                         avgY = 0;
657                         avgNum = 0;
658                 } else if (rint (x) > prevX || i == _impulse_fft->bins() - 1) {
659                         avgY = avgY / (float)avgNum;
660                         if (avgY > (height * 10.0)) {
661                                 avgY = height * 10.0;
662                         }
663                         if (avgY < (-height * 10.0)) {
664                                 avgY = -height * 10.0;
665                         }
666
667                         cairo_line_to (cr, prevX, avgY);
668
669                         avgY = 0;
670                         avgNum = 0;
671                 }
672
673                 prevX = rint (x);
674                 avgY += y;
675                 avgNum++;
676         }
677
678         cairo_set_line_width (cr, 2.0);
679         cairo_stroke (cr);
680 }
681
682 void
683 PluginEqGui::draw_scales_power (Gtk::Widget */*w*/, cairo_t *cr)
684 {
685         if (_impulse_fft == 0) {
686                 return;
687         }
688
689         static float scales[] = { 30.0, 70.0, 125.0, 250.0, 500.0, 1000.0, 2000.0, 5000.0, 10000.0, 15000.0, 20000.0, -1.0 };
690         float divisor = _samplerate / 2.0 / _impulse_fft->bins();
691         float x;
692
693         cairo_set_line_width (cr, 1.5);
694         cairo_set_font_size (cr, 9);
695
696         cairo_font_extents_t extents;
697         cairo_font_extents (cr, &extents);
698         // float fontXOffset = extents.descent + 1.0;
699
700         char buf[256];
701
702         for (uint32_t i = 0; scales[i] != -1.0; ++i) {
703                 float bin = scales[i] / divisor;
704
705                 x  = log10f (1.0 + bin / (float)_impulse_fft->bins() * _log_coeff) / _log_max;
706                 x *= _analysis_width;
707
708                 if (scales[i] < 1000.0) {
709                         snprintf (buf, 256, "%0.0f", scales[i]);
710                 } else {
711                         snprintf (buf, 256, "%0.0fk", scales[i]/1000.0);
712                 }
713
714                 cairo_set_source_rgb (cr, 0.4, 0.4, 0.4);
715
716                 cairo_move_to (cr, x - extents.height, 3.0);
717
718                 cairo_rotate (cr, M_PI / 2.0);
719                 cairo_show_text (cr, buf);
720                 cairo_rotate (cr, -M_PI / 2.0);
721                 cairo_stroke (cr);
722
723                 cairo_set_source_rgb (cr, 0.3, 0.3, 0.3);
724                 cairo_move_to (cr, x, _analysis_height);
725                 cairo_line_to (cr, x, 0.0);
726                 cairo_stroke (cr);
727         }
728
729         float y;
730
731         //double dashes[] = { 1.0, 3.0, 4.5, 3.0 };
732         double dashes[] = { 3.0, 5.0 };
733
734         for (float dB = 0.0; dB < _max_dB; dB += _step_dB) {
735                 snprintf (buf, 256, "+%0.0f", dB);
736
737                 y  = (_max_dB - dB) / (_max_dB - _min_dB);
738                 //std::cerr << " y = " << y << std::endl;
739                 y *= _analysis_height;
740
741                 if (dB != 0.0) {
742                         cairo_set_source_rgb (cr, 0.4, 0.4, 0.4);
743                         cairo_move_to (cr, 1.0,     y + extents.height + 1.0);
744                         cairo_show_text (cr, buf);
745                         cairo_stroke (cr);
746                 }
747
748                 cairo_set_source_rgb (cr, 0.2, 0.2, 0.2);
749                 cairo_move_to (cr, 0,               y);
750                 cairo_line_to (cr, _analysis_width, y);
751                 cairo_stroke (cr);
752
753                 if (dB == 0.0) {
754                         cairo_set_dash (cr, dashes, 2, 0.0);
755                 }
756         }
757
758         for (float dB = - _step_dB; dB > _min_dB; dB -= _step_dB) {
759                 snprintf (buf, 256, "%0.0f", dB);
760
761                 y  = (_max_dB - dB) / (_max_dB - _min_dB);
762                 y *= _analysis_height;
763
764                 cairo_set_source_rgb (cr, 0.4, 0.4, 0.4);
765                 cairo_move_to (cr, 1.0, y - extents.descent - 1.0);
766                 cairo_show_text (cr, buf);
767                 cairo_stroke (cr);
768
769                 cairo_set_source_rgb (cr, 0.2, 0.2, 0.2);
770                 cairo_move_to (cr, 0,               y);
771                 cairo_line_to (cr, _analysis_width, y);
772                 cairo_stroke (cr);
773         }
774
775         cairo_set_dash (cr, 0, 0, 0.0);
776 }
777
778 void
779 PluginEqGui::plot_impulse_amplitude (Gtk::Widget *w, cairo_t *cr)
780 {
781         float x,y;
782         int prevX = 0;
783         float avgY = 0.0;
784         int avgNum = 0;
785
786         // float width  = w->get_width();
787         float height = w->get_height ();
788
789         cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
790         cairo_set_line_width (cr, 2.5);
791
792         for (uint32_t i = 0; i < _impulse_fft->bins() - 1; ++i) {
793                 // x coordinate of bin i
794                 x  = log10f (1.0 + (float)i / (float)_impulse_fft->bins() * _log_coeff) / _log_max;
795                 x *= _analysis_width;
796
797                 float yCoeff = (power_to_dB (_impulse_fft->power_at_bin (i)) - _min_dB) / (_max_dB - _min_dB);
798
799                 y = _analysis_height - _analysis_height * yCoeff;
800
801                 if (i == 0) {
802                         cairo_move_to (cr, x, y);
803                         avgY = 0;
804                         avgNum = 0;
805                 } else if (rint (x) > prevX || i == _impulse_fft->bins() - 1) {
806                         avgY = avgY / (float)avgNum;
807                         if (avgY > (height * 10.0)) {
808                                 avgY = height * 10.0;
809                         }
810                         if (avgY < (-height * 10.0)) {
811                                 avgY = -height * 10.0;
812                         }
813                         cairo_line_to (cr, prevX, avgY);
814
815                         avgY = 0;
816                         avgNum = 0;
817                 }
818
819                 prevX = rint (x);
820                 avgY += y;
821                 avgNum++;
822         }
823
824         cairo_stroke (cr);
825 }
826
827 void
828 PluginEqGui::plot_signal_amplitude_difference (Gtk::Widget *w, cairo_t *cr)
829 {
830         float x,y;
831
832         int prevX = 0;
833         float avgY = 0.0;
834         int avgNum = 0;
835
836         float height = w->get_height();
837
838         cairo_set_source_rgb (cr, 0.0, 1.0, 0.0);
839         cairo_set_line_width (cr, 1.5);
840
841         for (uint32_t i = 0; i < _signal_input_fft->bins() - 1; ++i) {
842                 // x coordinate of bin i
843                 x  = log10f (1.0 + (float)i / (float)_signal_input_fft->bins() * _log_coeff) / _log_max;
844                 x *= _analysis_width;
845
846                 float power_out = _signal_output_fft->power_at_bin (i) + 1e-30;
847                 float power_in  = _signal_input_fft ->power_at_bin (i) + 1e-30;
848                 float power = power_to_dB (power_out / power_in);
849
850                 assert (!ISINF(power));
851                 assert (!ISNAN(power));
852
853                 float yCoeff = (power - _min_dB) / (_max_dB - _min_dB);
854
855                 y = _analysis_height - _analysis_height*yCoeff;
856
857                 if (i == 0) {
858                         cairo_move_to (cr, x, y);
859
860                         avgY = 0;
861                         avgNum = 0;
862                 } else if (rint (x) > prevX || i == _impulse_fft->bins() - 1) {
863                         avgY = avgY / (float)avgNum;
864                         if (avgY > (height * 10.0)) {
865                                 avgY = height * 10.0;
866                         }
867                         if (avgY < (-height * 10.0)) {
868                                 avgY = -height * 10.0;
869                         }
870                         cairo_line_to (cr, prevX, avgY);
871
872                         avgY = 0;
873                         avgNum = 0;
874
875                 }
876
877                 prevX = rint (x);
878                 avgY += y;
879                 avgNum++;
880         }
881
882         cairo_stroke (cr);
883 }