EQ8/EQ12: High pass and low pass with Q
[calf.git] / src / ctl_knob.cpp
blobf7a3326bbcc61b871d2132b82cdaa27fc384d46a
1 /* Calf DSP Library
2 * Knob control.
3 * Copyright (C) 2007-2010 Krzysztof Foltman, Torben Hohn, Markus Schmidt
4 * and others
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2 of the License, or (at your option) any later version.
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 GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General
17 * Public License along with this program; if not, write to the
18 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19 * Boston, MA 02110-1301 USA
21 #include "config.h"
22 #include <calf/ctl_knob.h>
23 #include <calf/drawingutils.h>
24 #include <gdk/gdkkeysyms.h>
25 #include <cairo/cairo.h>
26 #if !defined(__APPLE__)
27 #include <malloc.h>
28 #endif
29 #include <math.h>
30 #include <stdint.h>
31 #include <stdlib.h>
32 #include <gdk/gdk.h>
33 #include <algorithm>
34 #include <stdlib.h>
36 #define range01(tick) std::min(1., std::max(0., tick))
38 ///////////////////////////////////////// knob ///////////////////////////////////////////////
40 static void
41 calf_knob_get_color (CalfKnob *self, float deg, float phase, float start, float last, float tickw, float *r, float *g, float *b, float *a)
43 GtkStateType state = GTK_STATE_NORMAL;
44 GtkWidget *widget = GTK_WIDGET(self);
46 //printf ("get color: phase %.2f deg %.2f\n", phase, deg);
47 if (self->type == 0) {
48 // normal
49 if (!(deg > phase or phase == start))
50 state = GTK_STATE_PRELIGHT;
52 if (self->type == 1) {
53 // centered
54 if (deg > 270 and deg <= phase and phase > 270)
55 state = GTK_STATE_PRELIGHT;
56 if (deg <= 270 and deg > phase and phase < 270)
57 state = GTK_STATE_PRELIGHT;
58 if ((deg == start and phase == start)
59 or (deg == 270. and phase > 270.))
60 state = GTK_STATE_PRELIGHT;
62 if (self->type == 2) {
63 // reverse
64 if (deg > phase or phase == start)
65 state = GTK_STATE_PRELIGHT;
67 if (self->type == 3) {
68 for (unsigned j = 0; j < self->ticks.size(); j++) {
69 float tp = fmod((start + range01(self->ticks[j]) * 360.) - phase + 360, 360);
70 if (tp > 360 - tickw or tp < tickw) {
71 state = GTK_STATE_PRELIGHT;
74 if (deg > phase and deg > last + tickw and last < phase)
75 state = GTK_STATE_PRELIGHT;
78 get_fg_color(widget, &state, r, g, b);
79 if (state == GTK_STATE_NORMAL)
80 gtk_widget_style_get(widget, "alpha-normal", a, NULL);
81 else
82 gtk_widget_style_get(widget, "alpha-prelight", a, NULL);
86 static gboolean
87 calf_knob_expose (GtkWidget *widget, GdkEventExpose *event)
89 g_assert(CALF_IS_KNOB(widget));
90 CalfKnob *self = CALF_KNOB(widget);
92 if (!self->knob_image)
93 return FALSE;
95 GdkPixbuf *pixbuf = self->knob_image;
96 gint iw = gdk_pixbuf_get_width(pixbuf);
97 gint ih = gdk_pixbuf_get_height(pixbuf);
99 GtkAdjustment *adj = gtk_range_get_adjustment(GTK_RANGE(widget));
100 cairo_t *ctx = gdk_cairo_create(GDK_DRAWABLE(widget->window));
102 float r, g, b;
103 GtkStateType state;
105 float rmargin, rwidth, tmargin, twidth, tlength, flw;
106 gtk_widget_style_get(widget, "ring-margin", &rmargin,
107 "ring-width", &rwidth,
108 "tick-margin", &tmargin,
109 "tick-width", &twidth,
110 "tick-length", &tlength,
111 "focus-line-width", &flw, NULL);
113 double ox = widget->allocation.x + (widget->allocation.width - iw) / 2;
114 double oy = widget->allocation.y + (widget->allocation.height - ih) / 2;
115 double size = iw;
116 float rad = size / 2;
117 double xc = ox + rad;
118 double yc = oy + rad;
120 unsigned int tick;
121 double phase;
122 double base;
123 double deg;
124 double end;
125 double last;
126 double start;
127 double nend;
128 double zero;
129 float opac = 0;
131 double perim = (rad - rmargin) * 2 * M_PI;
132 double tickw = 2. / perim * 360.;
133 double tickw2 = tickw / 2.;
135 const unsigned int debug = 0;
137 cairo_rectangle(ctx, ox, oy, size + size / 2, size + size / 2);
138 cairo_clip(ctx);
140 // draw background
141 gdk_draw_pixbuf(GDK_DRAWABLE(widget->window), widget->style->fg_gc[0], pixbuf,
142 0, 0, ox, oy, iw, ih, GDK_RGB_DITHER_NORMAL, 0, 0);
144 switch (self->type) {
145 default:
146 case 0:
147 // normal knob
148 start = 135.;
149 end = 405.;
150 base = 270.;
151 zero = 135.;
152 case 1:
153 // centered @ 270°
154 start = 135.;
155 end = 405.;
156 base = 270.;
157 zero = 270.;
158 case 2:
159 // reversed
160 start = 135.;
161 end = 405.;
162 base = 270.;
163 zero = 135.;
164 break;
165 case 3:
166 // 360°
167 start = -90.;
168 end = 270.;
169 base = 360.;
170 zero = -90.;
171 break;
173 tick = 0;
174 nend = 0.;
175 deg = last = start;
176 phase = (adj->value - adj->lower) * base / (adj->upper - adj->lower) + start;
178 // draw pin
179 state = GTK_STATE_ACTIVE;
180 get_fg_color(widget, &state, &r, &g, &b);
181 float x1 = ox + rad + (rad - tmargin) * cos(phase * (M_PI / 180.));
182 float y1 = oy + rad + (rad - tmargin) * sin(phase * (M_PI / 180.));
183 float x2 = ox + rad + (rad - tlength - tmargin) * cos(phase * (M_PI / 180.));
184 float y2 = oy + rad + (rad - tlength - tmargin) * sin(phase * (M_PI / 180.));
185 cairo_move_to(ctx, x1, y1);
186 cairo_line_to(ctx, x2, y2);
187 cairo_set_source_rgba(ctx, r, g, b, 1);
188 cairo_set_line_width(ctx, twidth);
189 cairo_stroke(ctx);
191 cairo_set_line_width(ctx, rwidth);
193 // draw ticks and rings
194 state = GTK_STATE_NORMAL;
195 get_fg_color(widget, &state, &r, &g, &b);
196 unsigned int evsize = 4;
197 double events[4] = { start, zero, end, phase };
198 if (self->type == 3)
199 evsize = 3;
200 std::sort(events, events + evsize);
201 if (debug) {
202 printf("start %.2f end %.2f last %.2f deg %.2f tick %d ticks %d phase %.2f base %.2f nend %.2f\n", start, end, last, deg, tick, int(self->ticks.size()), phase, base, nend);
203 for (unsigned int i = 0; i < self->ticks.size(); i++) {
204 printf("tick %d %.2f\n", i, self->ticks[i]);
207 while (deg <= end) {
208 if (debug) printf("tick %d deg %.2f last %.2f end %.2f\n", tick, deg, last, end);
209 if (self->ticks.size() and deg == start + range01(self->ticks[tick]) * base) {
210 // seems we want to draw a tick on this angle.
211 // so we have to fill the void between the last set angle
212 // and the point directly before the tick first.
213 // (draw from last known angle to tickw2 + tickw before actual deg)
214 if (last < deg - tickw - tickw2) {
215 calf_knob_get_color(self, (deg - tickw - tickw2), phase, start, last, tickw + tickw2, &r, &g, &b, &opac);
216 cairo_set_source_rgba(ctx, r, g, b, opac);
217 cairo_arc(ctx, xc, yc, rad - rmargin, last * (M_PI / 180.), std::max(last, std::min(nend, (deg - tickw - tickw2))) * (M_PI / 180.));
218 cairo_stroke(ctx);
219 if (debug) printf("fill from %.2f to %.2f @ %.2f\n", last, (deg - tickw - tickw2), opac);
221 // draw the tick itself
222 calf_knob_get_color(self, deg, phase, start, end, tickw + tickw2, &r, &g, &b, &opac);
223 cairo_set_source_rgba(ctx, r, g, b, opac);
224 cairo_arc(ctx, xc, yc, rad - rmargin, (deg - tickw2) * (M_PI / 180.), (deg + tickw2) * (M_PI / 180.));
225 cairo_stroke(ctx);
226 if (debug) printf("tick from %.2f to %.2f @ %.2f\n", (deg - tickw2), (deg + tickw2), opac);
227 // set last known angle to deg plus tickw + tickw2
228 last = deg + tickw + tickw2;
229 // and count up tick
230 tick ++;
231 // remember the next ticks void end
232 if (tick < self->ticks.size())
233 nend = range01(self->ticks[tick]) * base + start - tickw - tickw2;
234 else
235 nend = end;
236 } else {
237 // seems we want to fill a gap between the last event and
238 // the actual one, while the actual one isn't a tick (but a
239 // knobs position or a center)
240 if ((last < deg)) {
241 calf_knob_get_color(self, deg, phase, start, last, tickw + tickw2, &r, &g, &b, &opac);
242 cairo_set_source_rgba(ctx, r, g, b, opac);
243 cairo_arc(ctx, xc, yc, rad - rmargin, last * (M_PI / 180.), std::min(nend, std::max(last, deg)) * (M_PI / 180.));
244 cairo_stroke(ctx);
245 if (debug) printf("void from %.2f to %.2f @ %.2f\n", last, std::min(nend, std::max(last, deg)), opac);
247 last = deg;
249 if (deg >= end)
250 break;
251 // set deg to next event
252 for (unsigned int i = 0; i < evsize; i++) {
253 if (debug > 1) printf("checking %.2f (start %.2f zero %.2f phase %.2f end %.2f)\n", events[i], start, zero, phase, end);
254 if (events[i] > deg) {
255 deg = events[i];
256 if (debug > 1) printf("taken.\n");
257 break;
260 if (tick < self->ticks.size()) {
261 deg = std::min(deg, start + range01(self->ticks[tick]) * base);
262 if (debug > 1) printf("checking tick %d %.2f\n", tick, start + range01(self->ticks[tick]) * base);
264 //deg = std::max(last, deg);
265 if (debug > 1) printf("finally! deg %.2f\n", deg);
267 if (debug) printf("\n");
268 cairo_destroy(ctx);
269 return TRUE;
272 static void
273 calf_knob_size_request (GtkWidget *widget,
274 GtkRequisition *requisition)
276 g_assert(CALF_IS_KNOB(widget));
277 CalfKnob *self = CALF_KNOB(widget);
278 if (!self->knob_image)
279 return;
280 requisition->width = gdk_pixbuf_get_width(self->knob_image);
281 requisition->height = gdk_pixbuf_get_height(self->knob_image);
284 void
285 calf_knob_set_size (CalfKnob *self, int size)
287 char name[128];
288 GtkWidget *widget = GTK_WIDGET(self);
289 self->size = size;
290 sprintf(name, "%s_%d\n", gtk_widget_get_name(widget), size);
291 gtk_widget_set_name(widget, name);
292 gtk_widget_queue_resize(widget);
295 void
296 calf_knob_set_pixbuf (CalfKnob *self, GdkPixbuf *pixbuf)
298 self->knob_image = pixbuf;
299 gtk_widget_queue_resize(GTK_WIDGET(self));
302 static gboolean calf_knob_enter (GtkWidget *widget, GdkEventCrossing* ev)
304 if (gtk_widget_get_state(widget) == GTK_STATE_NORMAL) {
305 gtk_widget_set_state(widget, GTK_STATE_PRELIGHT);
306 gtk_widget_queue_draw(widget);
308 return TRUE;
311 static gboolean calf_knob_leave (GtkWidget *widget, GdkEventCrossing *ev)
313 if (gtk_widget_get_state(widget) == GTK_STATE_PRELIGHT) {
314 gtk_widget_set_state(widget, GTK_STATE_NORMAL);
315 gtk_widget_queue_draw(widget);
317 return TRUE;
320 static void
321 calf_knob_incr (GtkWidget *widget, int dir_down)
323 g_assert(CALF_IS_KNOB(widget));
324 CalfKnob *self = CALF_KNOB(widget);
325 GtkAdjustment *adj = gtk_range_get_adjustment(GTK_RANGE(widget));
327 int oldstep = (int)(0.5f + (adj->value - adj->lower) / adj->step_increment);
328 int step;
329 int nsteps = (int)(0.5f + (adj->upper - adj->lower) / adj->step_increment); // less 1 actually
330 if (dir_down)
331 step = oldstep - 1;
332 else
333 step = oldstep + 1;
334 if (self->type == 3 && step >= nsteps)
335 step %= nsteps;
336 if (self->type == 3 && step < 0)
337 step = nsteps - (nsteps - step) % nsteps;
339 // trying to reduce error cumulation here, by counting from lowest or from highest
340 float value = adj->lower + step * double(adj->upper - adj->lower) / nsteps;
341 gtk_range_set_value(GTK_RANGE(widget), value);
342 // printf("step %d:%d nsteps %d value %f:%f\n", oldstep, step, nsteps, oldvalue, value);
345 static gboolean
346 calf_knob_key_press (GtkWidget *widget, GdkEventKey *event)
348 g_assert(CALF_IS_KNOB(widget));
349 CalfKnob *self = CALF_KNOB(widget);
350 GtkAdjustment *adj = gtk_range_get_adjustment(GTK_RANGE(widget));
351 gtk_widget_set_state(widget, GTK_STATE_ACTIVE);
352 gtk_widget_queue_draw(widget);
353 switch(event->keyval)
355 case GDK_Home:
356 gtk_range_set_value(GTK_RANGE(widget), adj->lower);
357 return TRUE;
359 case GDK_End:
360 gtk_range_set_value(GTK_RANGE(widget), adj->upper);
361 return TRUE;
363 case GDK_Up:
364 calf_knob_incr(widget, 0);
365 return TRUE;
367 case GDK_Down:
368 calf_knob_incr(widget, 1);
369 return TRUE;
371 case GDK_Shift_L:
372 case GDK_Shift_R:
373 self->start_value = gtk_range_get_value(GTK_RANGE(widget));
374 self->start_y = self->last_y;
375 return TRUE;
378 return FALSE;
381 static gboolean
382 calf_knob_key_release (GtkWidget *widget, GdkEventKey *event)
384 g_assert(CALF_IS_KNOB(widget));
385 CalfKnob *self = CALF_KNOB(widget);
387 if(event->keyval == GDK_Shift_L || event->keyval == GDK_Shift_R)
389 self->start_value = gtk_range_get_value(GTK_RANGE(widget));
390 self->start_y = self->last_y;
391 return TRUE;
393 gtk_widget_set_state(widget, GTK_STATE_NORMAL);
394 gtk_widget_queue_draw(widget);
395 return FALSE;
398 static gboolean
399 calf_knob_button_press (GtkWidget *widget, GdkEventButton *event)
401 g_assert(CALF_IS_KNOB(widget));
402 CalfKnob *self = CALF_KNOB(widget);
404 if (event->type == GDK_2BUTTON_PRESS) {
405 gtk_range_set_value(GTK_RANGE(widget), self->default_value);
408 // CalfKnob *lg = CALF_KNOB(widget);
409 gtk_widget_grab_focus(widget);
410 gtk_grab_add(widget);
411 self->start_x = event->x;
412 self->last_y = self->start_y = event->y;
413 self->start_value = gtk_range_get_value(GTK_RANGE(widget));
414 gtk_widget_set_state(widget, GTK_STATE_ACTIVE);
415 gtk_widget_queue_draw(widget);
416 return TRUE;
419 static gboolean
420 calf_knob_button_release (GtkWidget *widget, GdkEventButton *event)
422 g_assert(CALF_IS_KNOB(widget));
424 if (GTK_WIDGET_HAS_GRAB(widget))
425 gtk_grab_remove(widget);
426 gtk_widget_set_state(widget, GTK_STATE_NORMAL);
427 gtk_widget_queue_draw(widget);
428 return FALSE;
431 static inline float endless(float value)
433 if (value >= 0)
434 return fmod(value, 1.f);
435 else
436 return fmod(1.f - fmod(1.f - value, 1.f), 1.f);
439 static inline float deadzone(GtkWidget *widget, float value, float incr)
441 // map to dead zone
442 float ov = value;
443 if (ov > 0.5)
444 ov = 0.1 + ov;
445 if (ov < 0.5)
446 ov = ov - 0.1;
448 float nv = ov + incr;
450 if (nv > 0.6)
451 return nv - 0.1;
452 if (nv < 0.4)
453 return nv + 0.1;
454 return 0.5;
457 static gboolean
458 calf_knob_pointer_motion (GtkWidget *widget, GdkEventMotion *event)
460 g_assert(CALF_IS_KNOB(widget));
461 CalfKnob *self = CALF_KNOB(widget);
463 float scale = (event->state & GDK_SHIFT_MASK) ? 2500 : 250;
464 gboolean moved = FALSE;
466 if (GTK_WIDGET_HAS_GRAB(widget))
468 if (self->type == 3)
470 gtk_range_set_value(GTK_RANGE(widget), endless(self->start_value - (event->y - self->start_y) / scale));
472 else
473 if (self->type == 1)
475 gtk_range_set_value(GTK_RANGE(widget), deadzone(GTK_WIDGET(widget), self->start_value, -(event->y - self->start_y) / scale));
477 else
479 gtk_range_set_value(GTK_RANGE(widget), self->start_value - (event->y - self->start_y) / scale);
481 moved = TRUE;
483 self->last_y = event->y;
484 return moved;
487 static gboolean
488 calf_knob_scroll (GtkWidget *widget, GdkEventScroll *event)
490 calf_knob_incr(widget, event->direction);
491 return TRUE;
494 static void
495 calf_knob_class_init (CalfKnobClass *klass)
497 // GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
498 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
499 widget_class->expose_event = calf_knob_expose;
500 widget_class->size_request = calf_knob_size_request;
501 widget_class->enter_notify_event = calf_knob_enter;
502 widget_class->leave_notify_event = calf_knob_leave;
503 widget_class->button_press_event = calf_knob_button_press;
504 widget_class->button_release_event = calf_knob_button_release;
505 widget_class->motion_notify_event = calf_knob_pointer_motion;
506 widget_class->key_press_event = calf_knob_key_press;
507 widget_class->key_release_event = calf_knob_key_release;
508 widget_class->scroll_event = calf_knob_scroll;
509 gtk_widget_class_install_style_property(
510 widget_class, g_param_spec_float("ring-margin", "Ring Margin", "Margin of the ring from edge",
511 0.0, 100.0, 0.0, GParamFlags(G_PARAM_READWRITE)));
512 gtk_widget_class_install_style_property(
513 widget_class, g_param_spec_float("ring-width", "Ring Width", "Width of the ring",
514 0.0, 100.0, 0.0, GParamFlags(G_PARAM_READWRITE)));
515 gtk_widget_class_install_style_property(
516 widget_class, g_param_spec_float("tick-margin", "Tick Margin", "Margin of the tick from edge",
517 0.0, 100.0, 0.0, GParamFlags(G_PARAM_READWRITE)));
518 gtk_widget_class_install_style_property(
519 widget_class, g_param_spec_float("tick-length", "Tick Length", "Length of the tick",
520 0.0, 100.0, 0.0, GParamFlags(G_PARAM_READWRITE)));
521 gtk_widget_class_install_style_property(
522 widget_class, g_param_spec_float("tick-width", "Tick Width", "Width of the tick",
523 0.0, 100.0, 0.0, GParamFlags(G_PARAM_READWRITE)));
524 gtk_widget_class_install_style_property(
525 widget_class, g_param_spec_float("alpha-normal", "Alpha Normal", "Alpha of ring in normal state",
526 0.0, 1.0, 0.2, GParamFlags(G_PARAM_READWRITE)));
527 gtk_widget_class_install_style_property(
528 widget_class, g_param_spec_float("alpha-prelight", "Alpha Prelight", "Alpha of ring in prelight state",
529 0.0, 1.0, 1.0, GParamFlags(G_PARAM_READWRITE)));
533 static void
534 calf_knob_init (CalfKnob *self)
536 GtkWidget *widget = GTK_WIDGET(self);
537 GTK_WIDGET_SET_FLAGS (GTK_WIDGET(self), GTK_CAN_FOCUS);
538 widget->requisition.width = 40;
539 widget->requisition.height = 40;
540 self->knob_image = NULL;
543 GtkWidget *
544 calf_knob_new()
546 GtkAdjustment *adj = (GtkAdjustment *)gtk_adjustment_new(0, 0, 1, 0.01, 0.5, 0);
547 return calf_knob_new_with_adjustment(adj);
550 static gboolean calf_knob_value_changed(gpointer obj)
552 GtkWidget *widget = (GtkWidget *)obj;
553 gtk_widget_queue_draw(widget);
554 return FALSE;
557 GtkWidget *calf_knob_new_with_adjustment(GtkAdjustment *_adjustment)
559 GtkWidget *widget = GTK_WIDGET( g_object_new (CALF_TYPE_KNOB, NULL ));
560 if (widget) {
561 gtk_range_set_adjustment(GTK_RANGE(widget), _adjustment);
562 g_signal_connect(GTK_OBJECT(widget), "value-changed", G_CALLBACK(calf_knob_value_changed), widget);
564 return widget;
567 GType
568 calf_knob_get_type (void)
570 static GType type = 0;
571 if (!type) {
573 static const GTypeInfo type_info = {
574 sizeof(CalfKnobClass),
575 NULL, /* base_init */
576 NULL, /* base_finalize */
577 (GClassInitFunc)calf_knob_class_init,
578 NULL, /* class_finalize */
579 NULL, /* class_data */
580 sizeof(CalfKnob),
581 0, /* n_preallocs */
582 (GInstanceInitFunc)calf_knob_init
585 for (int i = 0; ; i++) {
586 char *name = g_strdup_printf("CalfKnob%u%d",
587 ((unsigned int)(intptr_t)calf_knob_class_init) >> 16, i);
588 //const char *name = "CalfKnob";
589 if (g_type_from_name(name)) {
590 free(name);
591 continue;
593 type = g_type_register_static(GTK_TYPE_RANGE,
594 name,
595 &type_info,
596 (GTypeFlags)0);
597 free(name);
598 break;
601 return type;