The Big Commit (tm): Remove Cairo.Color and most of Gdk.Color usage from libprolooks
[libprolooks.git] / src / LineGraph.vala
blob9443ec92b7fae8f24a3314b6a48085570639d9f1
1 /*
2 Copyright 2009 by Hans Baier, Krzysztof Foltman
3 License: LGPLv2+
4 */
6 using Gtk;
7 using Gdk;
8 using Cairo;
10 namespace Prolooks {
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) {
76 return false;
79 public virtual bool get_dot (int index, int subindex, ref float x, ref float y, ref int size, ref ILineProperties line_props) {
80 return false;
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);
88 } else {
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) {
95 return false;
98 public virtual bool get_static_graph (int index, int subindex, float value, ref float[] data, int points, ref ILineProperties line_props) {
99 return false;
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;
104 return 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) {
115 return freq / srate;
117 public int sampling_rate() {
118 return 44100;
122 public class FrequencyResponseGraphSource : DefaultGraphSource {
123 public int max_index { get; set; }
124 public int max_subindex { get; set ;}
126 construct {
127 max_index = 1;
128 max_subindex = 1;
129 _freq_response_source = new TestFrequencyResponseSource ();
132 private IHasFrequencyResponse _freq_response_source;
134 public IHasFrequencyResponse freq_response_source {
135 get {
136 return _freq_response_source;
138 set {
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 ()));
154 return true;
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);
160 } else {
161 return false;
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) {
170 if (subindex < 0 )
171 return false;
173 if (use_frequencies)
175 if (subindex < 28)
177 vertical = true;
178 if (subindex == 9) legend = "100 Hz";
179 if (subindex == 18) legend = "1 kHz";
180 if (subindex == 27) legend = "10 kHz";
181 float freq = 100;
182 if (subindex < 9)
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);
188 else
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);
193 else
194 line_props.set_line_color (0.25f, 0.25f, 0.25f, 0.5f);
195 return true;
197 subindex -= 28;
199 if (subindex >= 32)
200 return false;
201 float gain = 16.0f / (float) (1 << subindex);
202 pos = dB_grid (gain);
203 if (pos < -1)
204 return false;
205 if (subindex != 4)
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);
211 vertical = false;
212 return true;
215 int get_changed_offsets (int generation, ref int subindex_graph, ref int subindex_dot, ref int subindex_gridline)
217 subindex_graph = 0;
218 subindex_dot = 0;
219 subindex_gridline = (generation != 0) ? int.MAX : 0;
220 return 1;
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;
233 construct {
234 set_size_request (50, 50);
235 cache_surface = null;
236 last_generation = 0;
237 var src = new FrequencyResponseGraphSource();
238 source = src;
239 draw.connect(on_draw);
240 size_allocate.connect(on_size_allocate);
243 private void copy_cache_to_window (Context c) {
244 c.save ();
245 c.set_source_surface (cache_surface, 0, 0);
246 c.paint ();
247 c.restore ();
250 /* unused
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);
255 cache_cr.paint ();
259 public static void draw_grid (Context c, ref string legend, bool vertical, float pos, int phase, int sx, int sy) {
260 int ox = 1, oy = 1;
261 TextExtents tx = TextExtents ();
263 if (legend.length != 0) {
264 c.text_extents (legend, out tx);
267 if (vertical)
269 float x = Math.floorf (ox + pos * sx) + 0.5f;
271 if (phase == 1)
273 c.move_to (x, oy);
274 c.line_to (x, oy + sy);
275 c.stroke ();
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);
285 else
287 float y = Math.floorf (oy + sy / 2 - (sy / 2 - 1) * pos) + 0.5f;
289 if (phase == 1)
291 c.move_to (ox, y);
292 c.line_to (ox + sx, y);
293 c.stroke ();
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) {
306 int ox=1, oy=1;
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;
313 if (i != 0) {
314 c.line_to (ox + i * 0.5, y);
315 } else {
316 c.move_to (ox, y);
319 c.stroke ();
322 public bool on_draw (Cairo.Context c) {
323 Gtk.Allocation allocation;
324 get_allocation(out allocation);
326 int ox = 1, oy = 1;
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,
338 Content.COLOR,
339 allocation.width,
340 allocation.height);
342 //Context cache_cr = new Context (cache_surface);
343 //cache_cr.set_source_surface (window_surface, 0,0);
344 //cache_cr.paint ();
346 cache_dirty = true;
349 c.select_font_face ("Bitstream Vera Sans", FontSlant.NORMAL, FontWeight.NORMAL);
350 c.set_font_size (9);
352 cairo_set_source_rgba (c, sc);
353 c.rectangle (ox, oy, sx, sy);
354 c.clip ();
356 CairoLineProperties cairoimpl = new CairoLineProperties ();
357 cairoimpl.context = c;
358 ILineProperties cimpl = cairoimpl;
360 if (source != null) {
361 float pos = 0;
362 bool vertical = false;
363 string legend;
364 float[] data = new float[2 * sx];
365 Gdk.RGBA sc2 = { 0, 1.0, 0 };
366 float x = 0, y = 0;
367 int size = 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 ();
383 cache_cr.fill ();
385 { //Display
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);
390 cache_cr.save();
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);
405 if (show_matrix) {
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);
411 cache_cr.restore();
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++)
427 legend = "";
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);
432 } else {
433 break;
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);
443 for (graph_n = 0;
444 (graph_n<cache_graph_index) &&
445 source.get_graph (source_id, graph_n, ref data, 2 * sx, ref cache_cimpl);
446 graph_n++)
448 draw_graph (cache_cr, ref data, sx, sy);
451 cairo_set_source_rgba (cache_cr, sc3);
453 for (dot_n = 0;; dot_n++)
455 size = 3;
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);
461 cache_cr.fill ();
462 } else {
463 break;
467 cache_cr = null;
468 copy_cache_to_window (c);
470 } else {
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++)
483 legend = "";
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);
487 } else {
488 break;
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);
499 gn++)
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++)
508 size = 3;
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);
512 c.fill ();
513 } else {
514 break;
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);
523 return true;
526 /* unused
527 int update_if (int last_drawn_generation)
529 int generation = last_drawn_generation;
530 if (source != null)
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) {
535 return generation;
538 queue_draw ();
540 return generation;
544 public void on_size_allocate (Gtk.Allocation allocation) {
545 set_allocation(allocation);
547 Gtk.Allocation a = allocation;
549 if (is_square)
551 if (a.width > a.height)
553 a.x += (a.width - a.height) / 2;
554 a.width = a.height;
556 if (a.width < a.height)
558 a.y += (a.height - a.width) / 2;
559 a.height = a.width;
563 base.size_allocate (a);
564 cache_surface = null;
568 } // namespace Prolooks