2 Copyright 2009 by Hans Baier, Krzysztof Foltman
12 public interface ILineProperties
: GLib
.Object
{
13 public abstract void set_line_color (float r
, float g
, float b
, float a
= 1.0f
);
14 public abstract void set_line_width (float width
);
17 public class CairoLineProperties
: GLib
.Object
, ILineProperties
{
18 public Context context
{ get; set; }
19 public void set_line_color (float r
, float g
, float b
, float a
= 1.0f
) { context
.set_source_rgba (r
, g
, b
, a
); }
20 public void set_line_width (float width
) { context
.set_line_width (width
); }
23 public interface IGraphSource
: GLib
.Object
{
24 /** Obtain subindex'th graph of parameter 'index'
25 * @param index parameter/graph number (usually tied to particular plugin control port)
26 * @param subindex graph number (there may be multiple overlaid graphs for one parameter, eg. for monosynth 2x12dB filters)
27 * @param data buffer for normalized output values
28 * @param points number of points to fill
29 * @param line_props line properties to adjust (for multicolour graphs etc.)
30 * @retval true graph data was returned; subindex+1 graph may or may not be available
31 * @retval false graph data was not returned; subindex+1 graph does not exist either
33 public abstract bool get_graph (int index
, int subindex
, ref float[] data
, int points
, ref ILineProperties line_props
);
35 /** Obtain subindex'th dot of parameter 'index'
36 * @param index parameter/dot number (usually tied to particular plugin control port)
37 * @param subindex dot number (there may be multiple dots graphs for one parameter)
39 public abstract bool get_dot (int index
, int subindex
, ref float x
, ref float y
, ref int size
, ref ILineProperties line_props
);
41 /** Obtain subindex'th dot of parameter 'index'
42 * @param index parameter/dot number (usually tied to particular plugin control port)
43 * @param subindex dot number (there may be multiple dots graphs for one parameter)
46 /** Defines how the graph line of the given subindex will look like
48 public abstract void set_subindex_look (ILineProperties line_props
, int subindex
);
50 public abstract bool get_gridline (int index
, int subindex
, ref float pos
, ref bool vertical
, ref string legend
, ref ILineProperties line_props
);
52 /** Obtain subindex'th static graph of parameter index (static graphs are only dependent on parameter value, not plugin state)
53 * @param index parameter/graph number (usually tied to particular plugin control port)
54 * @param subindex graph number (there may be multiple overlaid graphs for one parameter, eg. for monosynth 2x12dB filters)
55 * @param value parameter value to pick the graph for
56 * @param data buffer for normalized output values
57 * @param points number of points to fill
58 * @param line_props line properties to adjust (for multicolour graphs etc.)
59 * @retval true graph data was returned; subindex+1 graph may or may not be available
60 * @retval false graph data was not returned; subindex+1 graph does not exist either
62 public abstract bool get_static_graph (int index
, int subindex
, float value
, ref float[] data
, int points
, ref ILineProperties line_props
);
64 /** Return which graphs need to be redrawn and which can be cached for later reuse
65 * @param generation 0 (at start) or the last value returned by the function (corresponds to a set of input values)
66 * @param subindex_graph First graph that has to be redrawn (because it depends on values that might have changed)
67 * @param subindex_dot First dot that has to be redrawn
68 * @param subindex_gridline First gridline/legend that has to be redrawn
69 * @retval Current generation (to pass when calling the function next time); if different than passed generation value, call the function again to retrieve which graph offsets should be put into cache
71 public abstract int get_changed_offsets (int generation
, ref int subindex_graph
, ref int subindex_dot
, ref int subindex_gridline
);
74 public class DefaultGraphSource
: GLib
.Object
, IGraphSource
{
75 public virtual bool get_graph (int index
, int subindex
, ref float[] data
, int points
, ref ILineProperties line_props
) {
79 public virtual bool get_dot (int index
, int subindex
, ref float x
, ref float y
, ref int size
, ref ILineProperties line_props
) {
83 public virtual void set_subindex_look (ILineProperties line_props
, int subindex
)
85 if ((subindex
& 1) != 0) {
86 RGBA c
= rgba_from_string (DisplayBase
.text_color_default
);
87 line_props
.set_line_color ((float) c
.red
, (float) c
.green
, (float) c
.blue
);
89 line_props
.set_line_color (0.62f
, 0.78f
, 0.09f
);
91 line_props
.set_line_width (1.5f
);
94 public virtual bool get_gridline (int index
, int subindex
, ref float pos
, ref bool vertical
, ref string legend
, ref ILineProperties line_props
) {
98 public virtual bool get_static_graph (int index
, int subindex
, float value
, ref float[] data
, int points
, ref ILineProperties line_props
) {
102 public virtual int get_changed_offsets (int generation
, ref int subindex_graph
, ref int subindex_dot
, ref int subindex_gridline
) {
103 subindex_graph
= subindex_dot
= subindex_gridline
= 0;
108 public interface IHasFrequencyResponse
: GLib
.Object
{
109 public abstract float freq_gain (int subindex
, float freq
, float srate
);
110 public abstract int sampling_rate ();
113 public class TestFrequencyResponseSource
: IHasFrequencyResponse
, GLib
.Object
{
114 public float freq_gain (int subindex
, float freq
, float srate
) {
117 public int sampling_rate() {
122 public class FrequencyResponseGraphSource
: DefaultGraphSource
{
123 public int max_index
{ get; set; }
124 public int max_subindex
{ get; set ;}
129 _freq_response_source
= new
TestFrequencyResponseSource ();
132 private IHasFrequencyResponse _freq_response_source
;
134 public IHasFrequencyResponse freq_response_source
{
136 return _freq_response_source
;
139 _freq_response_source
= value
;
143 public static inline
float dB_grid (float amp
) {
144 return Math
.logf (amp
) / Math
.logf (256.0f
) + 0.4f
;
147 public static bool get_freq_graph (IHasFrequencyResponse fx
, int subindex
, ref float[] data
, int points
)
149 for (int i
= 0; i
< points
; i
++)
151 double freq
= 20.0f
* Math
.pow (20000.0f
/ 20.0f
, i
* 1.0f
/ (float)points
);
152 data
[i
] = dB_grid (fx
.freq_gain (subindex
, (float)freq
, fx
.sampling_rate ()));
157 public override bool get_graph (int index
, int subindex
, ref float[] data
, int points
, ref ILineProperties line_props
) {
158 if (_freq_response_source
!= null && index
<= max_index
&& subindex
<= max_subindex
) {
159 return get_freq_graph (_freq_response_source
, subindex
, ref data
, points
);
165 public override bool get_gridline (int index
, int subindex
, ref float pos
, ref bool vertical
, ref string legend
, ref ILineProperties line_props
) {
166 return get_gridline_use (index
, subindex
, ref pos
, ref vertical
, ref legend
, ref line_props
, true);
169 public bool get_gridline_use (int index
, int subindex
, ref float pos
, ref bool vertical
, ref string legend
, ref ILineProperties line_props
, bool use_frequencies
= true) {
178 if (subindex
== 9) legend
= "100 Hz";
179 if (subindex
== 18) legend
= "1 kHz";
180 if (subindex
== 27) legend
= "10 kHz";
183 freq
= 10 * (subindex
+ 1);
184 else if (subindex
< 18)
185 freq
= 100 * (subindex
- 9 + 1);
186 else if (subindex
< 27)
187 freq
= 1000 * (subindex
- 18 + 1);
189 freq
= 10000 * (subindex
- 27 + 1);
190 pos
= Math
.logf (freq
/ 20.0f
) / Math
.logf (1000f
);
191 if (legend
.length
!= 0)
192 line_props
.set_line_color (0.25f
, 0.25f
, 0.25f
, 0.75f
);
194 line_props
.set_line_color (0.25f
, 0.25f
, 0.25f
, 0.5f
);
201 float gain
= 16.0f
/ (float) (1 << subindex
);
202 pos
= dB_grid (gain
);
206 line_props
.set_line_color (0.25f
, 0.25f
, 0.25f
, ((subindex
& 1) != 0) ?
0.5f
: 0.75f
);
207 if ((subindex
& 1) == 0)
209 legend
= "%d dB".printf (24 - 6 * subindex
);
215 int get_changed_offsets (int generation
, ref int subindex_graph
, ref int subindex_dot
, ref int subindex_gridline
)
219 subindex_gridline
= (generation
!= 0) ?
int.MAX
: 0;
224 public class LineGraph
: DrawingArea
{
226 public IGraphSource source
{ get; set; }
228 private int source_id
;
229 private bool is_square
;
230 private Surface? cache_surface
;
231 private int last_generation
;
234 set_size_request (50, 50);
235 cache_surface
= null;
237 var src
= new
FrequencyResponseGraphSource();
239 draw
.connect(on_draw
);
240 size_allocate
.connect(on_size_allocate
);
243 private void copy_cache_to_window (Context c
) {
245 c
.set_source_surface (cache_surface
, 0, 0);
251 private void copy_window_to_cache (Context c) {
252 Context cache_cr = new Context (cache_surface);
253 Surface window_surface = c.get_target ();
254 cache_cr.set_source_surface (window_surface, 0, 0);
259 public static void draw_grid (Context c
, ref string legend
, bool vertical
, float pos
, int phase
, int sx
, int sy
) {
261 TextExtents tx
= TextExtents ();
263 if (legend
.length
!= 0) {
264 c
.text_extents (legend
, out tx
);
269 float x
= Math
.floorf (ox
+ pos
* sx
) + 0.5f
;
274 c
.line_to (x
, oy
+ sy
);
278 if (phase
== 2 && (legend
.length
!= 0)) {
280 c
.set_source_rgba (1.0, 1.0, 1.0, 0.75);
281 c
.move_to (x
- (tx
.x_bearing
+ tx
.width
/ 2.0), oy
+ sy
- 2);
282 c
.show_text (legend
);
287 float y
= Math
.floorf (oy
+ sy
/ 2 - (sy
/ 2 - 1) * pos
) + 0.5f
;
292 c
.line_to (ox
+ sx
, y
);
296 if (phase
== 2 && (legend
.length
!= 0)) {
297 c
.set_source_rgba (1.0, 1.0, 1.0, 0.75);
298 c
.move_to (ox
+ sx
- 2 - tx
.width
, y
+ tx
.height
/2 - 1);
299 c
.show_text (legend
);
305 static void draw_graph (Context c
, ref float[] data
, int sx
, int sy
) {
308 for (int i
= 0; i
< 2 * sx
; i
++)
310 int y
= (int) (oy
+ sy
/ 2 - (sy
/ 2 - 1) * data
[i
]);
311 //if (y < oy) y = oy;
312 //if (y >= oy + sy) y = oy + sy - 1;
314 c
.line_to (ox
+ i
* 0.5, y
);
322 public bool on_draw (Cairo
.Context c
) {
323 Gtk
.Allocation allocation
;
324 get_allocation(out allocation
);
327 int sx
= allocation
.width
- 2, sy
= allocation
.height
- 2;
329 Gdk
.RGBA sc
= { 0, 0, 0 };
331 bool cache_dirty
= false;
333 if (cache_surface
== null) {
334 // looks like its either first call or the widget has been resized.
335 // create the cache_surface.
336 Surface window_surface
= c
.get_target ();
337 cache_surface
= new Surface
.similar (window_surface
,
342 //Context cache_cr = new Context (cache_surface);
343 //cache_cr.set_source_surface (window_surface, 0,0);
349 c
.select_font_face ("Bitstream Vera Sans", FontSlant
.NORMAL
, FontWeight
.NORMAL
);
352 cairo_set_source_rgba (c
, sc
);
353 c
.rectangle (ox
, oy
, sx
, sy
);
356 CairoLineProperties cairoimpl
= new
CairoLineProperties ();
357 cairoimpl
.context
= c
;
358 ILineProperties cimpl
= cairoimpl
;
360 if (source
!= null) {
362 bool vertical
= false;
364 float[] data
= new
float[2 * sx
];
365 Gdk
.RGBA sc2
= { 0, 1.0, 0 };
368 Gdk
.RGBA sc3
= { 0.5, 1.0, 0.0 };
370 int graph_n
= 0, grid_n
= 0, dot_n
= 0, grid_n_save
= 0;
372 int cache_graph_index
= 0, cache_dot_index
= 0, cache_grid_index
= 0;
373 int gen_index
= source
.get_changed_offsets (last_generation
, ref cache_graph_index
, ref cache_dot_index
, ref cache_grid_index
);
375 if (cache_dirty
|| (gen_index
!= last_generation
)) {
376 Context cache_cr
= new
Context (cache_surface
);
377 cache_cr
.select_font_face ("Bitstream Vera Sans", FontSlant
.NORMAL
, FontWeight
.NORMAL
);
378 cache_cr
.set_font_size (9);
380 cairo_set_source_rgba (cache_cr
, sc
);
381 cache_cr
.rectangle (ox
, oy
, sx
, sy
);
382 cache_cr
.clip_preserve ();
386 bool show_matrix
= true;
387 bool show_glass_rim
= false;
388 double inner_width
= show_glass_rim ?
(sx
- 14) : (sx
- 6);
389 double inner_height
= show_glass_rim ?
(sy
- 13) : (sy
- 6);
392 set_line_width_from_device (cache_cr
);
394 DisplayBase
.outer_rim_for_display (cache_cr
, 0, 0, sx
, sy
, gdk_color_to_rgba(style
.bg
[(int)Gtk
.StateType
.NORMAL
]));
396 if (show_glass_rim
) {
397 cache_cr
.translate (4.5, 3.5);
398 DisplayBase
.inner_glass_rim (cache_cr
, 0, 0, sx
- 8, sy
- 9, 2);
401 cache_cr
.translate (3.0, 3.0);
403 DisplayBase
.lcd(cache_cr
, inner_width
, inner_height
, sy
> 30 ?
5.0 : 3.0);
406 DisplayBase
.dot_matrix (cache_cr
, show_glass_rim ?
0 : 0.5, show_glass_rim ?
0 : 0.5, inner_width
, inner_height
, DisplayBase
.matrix_dot_color ());
409 DisplayBase
.reflection(cache_cr
, inner_width
, inner_height
, inner_height
/ 2.0);
415 CairoLineProperties cache_cairoimpl
= new
CairoLineProperties ();
416 cache_cairoimpl
.context
= cache_cr
;
417 ILineProperties cache_cimpl
= cache_cairoimpl
;
419 source
.get_changed_offsets (gen_index
, ref cache_graph_index
, ref cache_dot_index
, ref cache_grid_index
);
420 last_generation
= gen_index
;
422 cache_cr
.set_line_width (1);
423 for (int phase
= 1; phase
<= 2; phase
++)
425 for (grid_n
= 0;; grid_n
++)
428 cache_cr
.set_source_rgba (1, 1, 1, 0.5);
429 if ((grid_n
< cache_grid_index
) &&
430 source
.get_gridline (source_id
, grid_n
, ref pos
, ref vertical
, ref legend
, ref cache_cimpl
)) {
431 draw_grid (cache_cr
, ref legend
, vertical
, pos
, phase
, sx
, sy
);
437 grid_n_save
= grid_n
;
439 cairo_set_source_rgba (cache_cr
, sc2
);
440 cache_cr
.set_line_join (LineJoin
.MITER
);
441 cache_cr
.set_line_width (1);
444 (graph_n
<cache_graph_index
) &&
445 source
.get_graph (source_id
, graph_n
, ref data
, 2 * sx
, ref cache_cimpl
);
448 draw_graph (cache_cr
, ref data
, sx
, sy
);
451 cairo_set_source_rgba (cache_cr
, sc3
);
453 for (dot_n
= 0;; dot_n
++)
456 if ((dot_n
<cache_dot_index
) &&
457 source
.get_dot (source_id
, dot_n
, ref x
, ref y
, ref size
, ref cache_cimpl
)) {
459 int yv
= (int) (oy
+ sy
/ 2 - (sy
/ 2 - 1) * y
);
460 cache_cr
.arc (ox
+ x
* sx
, yv
, size
, 0, 2 * Math
.PI
);
468 copy_cache_to_window (c
);
471 grid_n_save
= cache_grid_index
;
472 graph_n
= cache_graph_index
;
473 dot_n
= cache_dot_index
;
474 copy_cache_to_window (c
);
477 c
.set_line_width (1);
479 for (int phase
= 1; phase
<= 2; phase
++)
481 for (int gn
= grid_n_save
;; gn
++)
484 c
.set_source_rgba (1, 1, 1, 0.5);
485 if (source
.get_gridline (source_id
, gn
, ref pos
, ref vertical
, ref legend
, ref cimpl
)) {
486 draw_grid (c
, ref legend
, vertical
, pos
, phase
, sx
, sy
);
493 cairo_set_source_rgba (c
, sc2
);
494 c
.set_line_join (LineJoin
.MITER
);
495 c
.set_line_width (1);
497 for (int gn
= graph_n
;
498 source
.get_graph (source_id
, gn
, ref data
, 2 * sx
, ref cimpl
);
501 source
.set_subindex_look (cimpl
, gn
);
502 draw_graph (c
, ref data
, sx
, sy
);
505 cairo_set_source_rgba (c
, sc3
);
506 for (int gn
= dot_n
;; gn
++)
509 if (source
.get_dot (source_id
, gn
, ref x
, ref y
, ref size
, ref cimpl
)) {
510 int yv
= (int) (oy
+ sy
/ 2 - (sy
/ 2 - 1) * y
);
511 c
.arc (ox
+ x
* sx
, yv
, size
, 0, 2 * Math
.PI
);
520 get_style_context().render_frame(c
, ox
- 1, oy
- 1, sx
+ 2, sy
+ 2);
521 //stderr.printf ("exposed %p %dx%d %d+%d\n", window, event.area.x, event.area.y, event.area.width, event.area.height);
527 int update_if (int last_drawn_generation)
529 int generation = last_drawn_generation;
532 int subgraph = 0, dot = 0, gridline = 0;
533 generation = source.get_changed_offsets (generation, ref subgraph, ref dot, ref gridline);
534 if (subgraph == int.MAX && dot == int.MAX && gridline == int.MAX && generation == last_drawn_generation) {
544 public void on_size_allocate (Gtk
.Allocation allocation
) {
545 set_allocation(allocation
);
547 Gtk
.Allocation a
= allocation
;
551 if (a
.width
> a
.height
)
553 a
.x
+= (a
.width
- a
.height
) / 2;
556 if (a
.width
< a
.height
)
558 a
.y
+= (a
.height
- a
.width
) / 2;
563 base.size_allocate (a
);
564 cache_surface
= null;
568 } // namespace Prolooks