1 /***************************************************************************
2  *   Copyright (C) 2015 by Pere Ràfols Soler                               *
3  *   sapista2@gmail.com                                                    *
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                         *
17  *   Free Software Foundation, Inc.,                                       *
18  *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
19  ***************************************************************************/
20 
21 #include "dynplot.h"
22 #include "colors.h"
23 #include <iostream>
24 #include <iomanip>
25 #include <cstring>
26 #include <cmath>
27 
28 #define CURVE_BORDER 1.5
29 #define CURVE_MARGIN 15
30 #define CURVE_TEXT_OFFSET 18
31 #define PLOT_WIDTH_HEIGHT 250
32 #define DATA_RANGE_MIN -60.0
33 #define DATA_RANGE_MAX 10.0
34 #define GRID_STEP 10.0
35 
PlotDynCurve(bool isCompressor)36 PlotDynCurve::PlotDynCurve(bool isCompressor):
37 m_Ratio(1.0), m_Range(-100.0), m_Knee(0.0), m_Threshold(0.0), m_Makeup(0.0), m_GainReduction(0.0), m_InputVu(-100.0), m_bIsCompressor(isCompressor)
38 {
39   set_size_request(PLOT_WIDTH_HEIGHT, PLOT_WIDTH_HEIGHT);
40 }
41 
~PlotDynCurve()42 PlotDynCurve::~PlotDynCurve()
43 {
44 
45 }
46 
set_gainreduction(double gainreduction)47 void PlotDynCurve::set_gainreduction(double gainreduction)
48 {
49   m_GainReduction = gainreduction != 0.0 ? 20.0*log10(gainreduction) : -100.0;
50   redraw();
51 }
52 
set_inputvu(double inputvu)53 void PlotDynCurve::set_inputvu(double inputvu)
54 {
55   m_InputVu = inputvu != 0.0 ?  20.0*log10(inputvu) : -100.0;
56   redraw();
57 }
58 
set_knee(double knee)59 void PlotDynCurve::set_knee(double knee)
60 {
61   m_Knee = knee;
62   redraw();
63 }
64 
set_makeup(double makeup)65 void PlotDynCurve::set_makeup(double makeup)
66 {
67   m_Makeup = makeup;
68   redraw();
69 }
70 
set_range(double range)71 void PlotDynCurve::set_range(double range)
72 {
73   m_Range = range;
74   redraw();
75 }
76 
set_ratio(double ratio)77 void PlotDynCurve::set_ratio(double ratio)
78 {
79   m_Ratio = ratio;
80   redraw();
81 }
82 
set_threshold(double threshold)83 void PlotDynCurve::set_threshold(double threshold)
84 {
85   m_Threshold = threshold;
86   redraw();
87 }
88 
redraw()89 void PlotDynCurve::redraw()
90 {
91   Glib::RefPtr<Gdk::Window> win = get_window();
92   if(win)
93   {
94     Gdk::Rectangle r(0, 0, get_allocation().get_width(), get_allocation().get_height());
95     win->invalidate_rect(r, false);
96   }
97 }
98 
dB2PixelsX(double db)99 double PlotDynCurve::dB2PixelsX(double db)
100 {
101   const double m = ((double)width - 2*CURVE_MARGIN - CURVE_TEXT_OFFSET)/((double)(DATA_RANGE_MAX - DATA_RANGE_MIN));
102   const double n = CURVE_MARGIN + CURVE_TEXT_OFFSET - m*DATA_RANGE_MIN;
103   return m*db + n;
104 }
105 
dB2PixelsY(double db)106 double PlotDynCurve::dB2PixelsY(double db)
107 {
108   const double m = (2*CURVE_MARGIN + CURVE_TEXT_OFFSET - (double)height)/((double)(DATA_RANGE_MAX - DATA_RANGE_MIN));
109   const double n = ((double)height) - CURVE_MARGIN - CURVE_TEXT_OFFSET - m*DATA_RANGE_MIN;
110   return m*db + n;
111 }
112 
on_expose_event(GdkEventExpose * event)113 bool PlotDynCurve::on_expose_event(GdkEventExpose* event)
114 {
115 
116   Glib::RefPtr<Gdk::Window> window = get_window();
117   if(window)
118   {
119 
120     Gtk::Allocation allocation = get_allocation();
121     width = allocation.get_width();
122     height = allocation.get_height();
123 
124 
125     Cairo::RefPtr<Cairo::Context> cr = window->create_cairo_context();
126 
127     //Paint backgroud
128     cr->save();
129     cr->set_source_rgb(BACKGROUND_R, BACKGROUND_G, BACKGROUND_B);
130     cr->paint(); //Fill all with background color
131     cr->restore();
132 
133 
134     //Draw an interesting frame
135     cr->save();
136     double radius = height / 50.0;
137     double degrees = M_PI / 180.0;
138     cr->begin_new_sub_path();
139     cr->arc (width - CURVE_BORDER - radius, CURVE_BORDER + radius, radius, -90 * degrees, 0 * degrees);
140     cr->arc (width - CURVE_BORDER - radius, height - CURVE_BORDER - radius, radius, 0 * degrees, 90 * degrees);
141     cr->arc (CURVE_BORDER + radius, height- CURVE_BORDER - radius, radius, 90 * degrees, 180 * degrees);
142     cr->arc ( CURVE_BORDER + radius, CURVE_BORDER + radius, radius, 180 * degrees, 270 * degrees);
143     cr->close_path();
144     Cairo::RefPtr<Cairo::LinearGradient> bkg_gradient_ptr = Cairo::LinearGradient::create(width/2, CURVE_BORDER, width/2, height - CURVE_BORDER);
145     bkg_gradient_ptr->add_color_stop_rgba (0.0, 0.1, 0.1, 0.1, 0.6 );
146     bkg_gradient_ptr->add_color_stop_rgba (0.5, 0.2, 0.3, 0.3, 0.3 );
147     bkg_gradient_ptr->add_color_stop_rgba (1.0, 0.1, 0.1, 0.1, 0.6 );
148     cr->set_source(bkg_gradient_ptr);
149     cr->fill_preserve();
150     cr->set_line_width(1.0);
151     cr->set_source_rgb(0.3, 0.3, 0.4);
152     cr->stroke();
153     cr->restore();
154 
155     //Draw the grid
156     cr->save();
157     cr->set_source_rgb(0.3, 0.3, 0.3);
158     cr->set_line_width(1);
159     for(double i = DATA_RANGE_MIN; i <= DATA_RANGE_MAX; i+=GRID_STEP)
160     {
161       //Vertical grid
162       cr->move_to( dB2PixelsX(i) + 0.5, CURVE_MARGIN);
163       cr->line_to( dB2PixelsX(i) + 0.5, height - CURVE_MARGIN - CURVE_TEXT_OFFSET);
164       //Horizontal grid
165       cr->move_to( CURVE_MARGIN + CURVE_TEXT_OFFSET, dB2PixelsY(i) + 0.5);
166       cr->line_to( width - CURVE_MARGIN, dB2PixelsY(i) + 0.5);
167       cr->stroke();
168     }
169     cr->restore();
170 
171     //Draw text with pango to grid
172     cr->save();
173     cr->set_source_rgb(0.6, 0.6, 0.6);
174     Glib::RefPtr<Pango::Layout> pangoLayout = Pango::Layout::create(cr);
175     Pango::FontDescription font_desc("sans 9px");
176     pangoLayout->set_font_description(font_desc);
177     pangoLayout->set_alignment(Pango::ALIGN_RIGHT);
178     for(double i = DATA_RANGE_MIN; i <= DATA_RANGE_MAX; i+=GRID_STEP)
179     {
180       std::stringstream ss;
181       ss<< std::setprecision(2) << i;
182       pangoLayout->set_text(ss.str());
183       cr->move_to(dB2PixelsX(i) - 3.5, height - CURVE_MARGIN - CURVE_TEXT_OFFSET + 3.5);
184       pangoLayout->show_in_cairo_context(cr);
185       cr->move_to(CURVE_MARGIN, dB2PixelsY(i) - 3.5);
186       pangoLayout->show_in_cairo_context(cr);
187       cr->stroke();
188     }
189     cr->restore();
190 
191     //Plot InputVU and GainReduction
192     double prange = 0.05*(m_InputVu - DATA_RANGE_MIN);
193     cr->save();
194     cr->rectangle(CURVE_MARGIN + CURVE_TEXT_OFFSET + 0.5, CURVE_MARGIN + 0.5, width - 2*CURVE_MARGIN - CURVE_TEXT_OFFSET, height -  2*CURVE_MARGIN - CURVE_TEXT_OFFSET);
195     cr->clip();
196     cr->begin_new_sub_path();
197     cr->move_to(dB2PixelsX(DATA_RANGE_MIN), dB2PixelsY(DATA_RANGE_MIN));
198     cr->line_to(dB2PixelsX(DATA_RANGE_MIN), dB2PixelsY(DATA_RANGE_MIN  + m_Makeup));
199     cr->line_to( dB2PixelsX(m_InputVu + prange),  dB2PixelsY(m_InputVu + prange - m_GainReduction + m_Makeup) );
200     cr->line_to( dB2PixelsX(m_InputVu + prange),  dB2PixelsY(DATA_RANGE_MIN));
201     cr->close_path();
202     bkg_gradient_ptr = Cairo::LinearGradient::create(dB2PixelsX(DATA_RANGE_MIN), dB2PixelsY(DATA_RANGE_MIN),dB2PixelsX(m_InputVu + prange), dB2PixelsY(DATA_RANGE_MIN) );
203     bkg_gradient_ptr->add_color_stop_rgba (0.0, 0.1, 0.2, 0.8, 0.4 );
204     bkg_gradient_ptr->add_color_stop_rgba (0.9, 0.1, 0.6, 0.4, 0.3 );
205     bkg_gradient_ptr->add_color_stop_rgba (1.0, 0.1, 0.6, 0.4, 0.0 );
206     cr->set_source(bkg_gradient_ptr);
207     cr->fill();
208     cr->restore();
209 
210 
211     //Draw the curve
212     cr->save();
213     cr->rectangle(CURVE_MARGIN + CURVE_TEXT_OFFSET + 0.5, CURVE_MARGIN + 0.5, width - 2*CURVE_MARGIN - CURVE_TEXT_OFFSET, height -  2*CURVE_MARGIN - CURVE_TEXT_OFFSET);
214     cr->clip();
215     cr->move_to( dB2PixelsX(DATA_RANGE_MIN) + 0.5, dB2PixelsY(DATA_RANGE_MIN) + 0.5 );
216     double knee_range, y_dB;
217 
218     if(m_bIsCompressor)
219     {
220       for(double x_dB = DATA_RANGE_MIN; x_dB <= DATA_RANGE_MAX; x_dB += 1.0)
221       {
222         knee_range = 2.0*(x_dB - m_Threshold);
223         if (knee_range < -m_Knee)
224         {
225           //Under Threshold
226           y_dB = x_dB;
227         }
228         else if(knee_range >= m_Knee )
229         {
230           //Over Threshold
231           y_dB = m_Threshold + (x_dB - m_Threshold)/m_Ratio;
232         }
233         else
234         {
235           //On Knee
236           y_dB = x_dB + ((1.0/m_Ratio -1.0)*(x_dB - m_Threshold + m_Knee/2.0)*(x_dB - m_Threshold + m_Knee/2.0))/(2.0*m_Knee);
237         }
238         y_dB += m_Makeup;
239         cr->line_to( dB2PixelsX(x_dB) + 0.5, dB2PixelsY(y_dB) + 0.5);
240       }
241     }
242     else
243     {
244       //Draw Expander/Gate
245       for(double x_dB = DATA_RANGE_MIN; x_dB <= DATA_RANGE_MAX; x_dB += 1.0)
246       {
247         knee_range = 2.0*(x_dB - m_Threshold);
248         if (knee_range < -m_Knee)
249         {
250           //Under Threshold
251           y_dB = m_Threshold + (x_dB - m_Threshold)*m_Ratio;
252         }
253         else if(knee_range >= m_Knee )
254         {
255           //Over Threshold
256           y_dB = x_dB;
257         }
258         else
259         {
260           //On Knee
261           y_dB = x_dB + ((1.0 - m_Ratio)*(x_dB - m_Threshold - m_Knee/2)*(x_dB - m_Threshold - m_Knee/2))/(2*m_Knee);
262         }
263 
264 	if( y_dB < x_dB + m_Range )
265 	{
266 	  y_dB = x_dB + m_Range;
267 	}
268         cr->line_to( dB2PixelsX(x_dB) + 0.5, dB2PixelsY(y_dB) + 0.5);
269       }
270 
271     }
272     cr->set_line_width(1.0);
273     cr->set_line_cap(Cairo::LINE_CAP_ROUND);
274     cr->set_source_rgb(1,1,1);
275     cr->stroke();
276     cr->restore();
277 
278 
279     //draw de outer grind box
280     cr->save();
281     cr->set_source_rgb(0.3, 0.3, 0.3);
282     cr->set_line_width(1);
283     cr->move_to(CURVE_MARGIN + CURVE_TEXT_OFFSET + 0.5, CURVE_MARGIN + 0.5);
284     cr->line_to(width - CURVE_MARGIN + 0.5, CURVE_MARGIN + 0.5);
285     cr->line_to(width - CURVE_MARGIN + 0.5, height - CURVE_MARGIN - CURVE_TEXT_OFFSET + 0.5);
286     cr->line_to(CURVE_MARGIN + CURVE_TEXT_OFFSET + 0.5 , height - CURVE_MARGIN - CURVE_TEXT_OFFSET + 0.5);
287     cr->line_to(CURVE_MARGIN + CURVE_TEXT_OFFSET + 0.5, CURVE_MARGIN + 0.5);
288     cr->stroke();
289     cr->restore();
290   }
291   return true;
292 }
293