RingModulator: meter_in with MeterScale
[calf.git] / src / ctl_knob.cpp
blobc14b92a569ddcf96229ef47c968b6f47fea9cb27
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 ///////////////////////////////////////// knob ///////////////////////////////////////////////
38 static float
39 calf_knob_get_color (int type, float deg, float phase, float start)
41 double on = 1.0;
42 double off = 0.22;
43 //printf ("get color: phase %.2f deg %.2f\n", phase, deg);
44 if (type == 0) {
45 // normal
46 if (deg > phase or phase == start)
47 return off;
48 else return on;
50 if (type == 1) {
51 // centered
52 if (deg > 270 and deg <= phase and phase > 270)
53 return on;
54 if (deg <= 270 and deg > phase and phase < 270)
55 return on;
56 if ((deg == start and phase == start)
57 or (deg == 270. and phase > 270.))
58 return on;
59 return off;
61 if (type == 2) {
62 // reverse
63 if (deg > phase or phase == start)
64 return on;
65 else return off;
67 return 1;
71 static gboolean
72 calf_knob_expose (GtkWidget *widget, GdkEventExpose *event)
74 g_assert(CALF_IS_KNOB(widget));
75 CalfKnob *self = CALF_KNOB(widget);
76 CalfKnobClass *cls = CALF_KNOB_CLASS(GTK_OBJECT_GET_CLASS(widget));
77 GdkPixbuf *pixbuf = cls->knob_image[self->size - 1];
78 gint iw = gdk_pixbuf_get_width(pixbuf);
79 gint ih = gdk_pixbuf_get_height(pixbuf);
81 float widths[6] = {0, 2.5, 3.5, 3.5, 4.2, 5.5};
82 float margins[6] = {0, 2.2, 3.5, 3.8, 4.2, 4.5};
83 float pins_m[6] = {0, 6, 10, 10, 11, 13};
84 float pins_s[6] = {0, 3, 4, 4, 4, 4};
86 GtkAdjustment *adj = gtk_range_get_adjustment(GTK_RANGE(widget));
87 cairo_t *ctx = gdk_cairo_create(GDK_DRAWABLE(widget->window));
89 float r, g, b;
90 get_fg_color(widget, NULL, &r, &g, &b);
92 double ox = widget->allocation.x + (widget->allocation.width - iw) / 2;
93 double oy = widget->allocation.y + (widget->allocation.height - ih) / 2;
94 double size = iw;
95 float rad = size / 2;
96 double xc = ox + rad;
97 double yc = oy + rad;
99 unsigned int tick;
100 double phase;
101 double base;
102 double deg;
103 double end;
104 double last;
105 double start;
106 double nend;
107 double zero;
108 double opac;
110 double lwidth = widths[self->size];
111 double lmarg = margins[self->size];
112 double perim = (rad - lmarg) * 2 * M_PI;
113 double tickw = 2. / perim * 360.;
114 double tickw2 = tickw / 2.;
116 cairo_rectangle(ctx, ox, oy, size + size / 2, size + size / 2);
117 cairo_clip(ctx);
119 // draw background
120 gdk_draw_pixbuf(GDK_DRAWABLE(widget->window), widget->style->fg_gc[0], pixbuf,
121 0, 0, ox, oy, iw, ih, GDK_RGB_DITHER_NORMAL, 0, 0);
123 cairo_set_line_width(ctx, lwidth);
125 switch (self->type) {
126 default:
127 case 0:
128 // normal knob
129 start = 135.;
130 end = 405.;
131 base = 270.;
132 zero = 135.;
133 case 1:
134 // centered @ 270°
135 start = 135.;
136 end = 405.;
137 base = 270.;
138 zero = 270.;
139 case 2:
140 // reversed
141 start = 135.;
142 end = 405.;
143 base = 270.;
144 zero = 135.;
145 break;
146 case 3:
147 // 360°
148 start = -90.;
149 end = 360.;
150 base = 360.;
151 zero = 360.;
152 break;
154 tick = 0;
155 nend = 0.;
156 deg = last = start;
157 phase = (adj->value - adj->lower) * base / (adj->upper - adj->lower) + start;
158 while (deg <= end) {
159 if (self->ticks.size() and deg == start + self->ticks[tick] * base) {
160 // seems we want to draw a tick on this angle.
161 // so we have to fill the void between the last set angle
162 // and the point directly before the tick first.
163 // (draw from last known angle to tickw2 + tickw before actual deg)
164 if (last < deg - tickw - tickw2) {
165 opac = calf_knob_get_color(self->type, (deg - tickw - tickw2), phase, start);
166 cairo_set_source_rgba(ctx, r, g, b, opac);
167 cairo_arc(ctx, xc, yc, rad - lmarg, last * (M_PI / 180.), std::max(last, std::min(nend, (deg - tickw - tickw2))) * (M_PI / 180.));
168 cairo_stroke(ctx);
169 //printf("fill from %.2f to %.2f @ %.2f\n", last, (deg - tickw - tickw2), opac);
171 // draw the tick itself
172 opac = calf_knob_get_color(self->type, deg, phase, start);
173 cairo_set_source_rgba(ctx, r, g, b, opac);
174 cairo_arc(ctx, xc, yc, rad - lmarg, (deg - tickw2) * (M_PI / 180.), (deg + tickw2) * (M_PI / 180.));
175 cairo_stroke(ctx);
176 //printf("tick from %.2f to %.2f @ %.2f\n", (deg - tickw2), (deg + tickw2), opac);
177 // set last known angle to deg plus tickw + tickw2
178 last = deg + tickw + tickw2;
179 // and count up tick
180 tick ++;
181 // remember the next ticks void end
182 nend = self->ticks[tick] * base + start - tickw - tickw2;
183 } else {
184 // seems we want to fill a gap between the last event and
185 // the actual one, while the actual one isn't a tick (but a
186 // knobs position or a center)
187 if ((last < deg)) {
188 opac = calf_knob_get_color(self->type, deg, phase, start);
189 cairo_set_source_rgba(ctx, r, g, b, opac);
190 cairo_arc(ctx, xc, yc, rad - lmarg, last * (M_PI / 180.), std::min(nend, deg) * (M_PI / 180.));
191 cairo_stroke(ctx);
192 //printf("void from %.2f to %.2f @ %.2f\n", last, deg, opac);
194 last = deg;
196 //printf("o %.2f start %.2f end %.2f last %.2f deg %.2f tick %d phase %.2f base %.2f nend %.2f\n", o, start, end, last, deg, tick, phase, nend);
197 if (deg >= end)
198 break;
199 // set deg to zero, phase, end or next tick
200 double p[3] = { zero, phase, end };
201 std::sort(p, p + 3);
202 for (int i = 0; i < 3; i++) {
203 if (p[i] > deg) {
204 deg = p[i];
205 break;
208 if (tick < self->ticks.size())
209 deg = std::min(deg, start + self->ticks[tick] * base);
210 deg = std::max(last, deg);
212 //printf("\n");
214 // draw pin
215 float x1 = ox + rad + (rad - pins_m[self->size]) * cos(phase * (M_PI / 180.));
216 float y1 = oy + rad + (rad - pins_m[self->size]) * sin(phase * (M_PI / 180.));
217 float x2 = ox + rad + (rad - pins_s[self->size] - pins_m[self->size]) * cos(phase * (M_PI / 180.));
218 float y2 = oy + rad + (rad - pins_s[self->size] - pins_m[self->size]) * sin(phase * (M_PI / 180.));
219 cairo_move_to(ctx, x1, y1);
220 cairo_line_to(ctx, x2, y2);
221 cairo_set_source_rgba(ctx, r, g, b, 0.99);
222 cairo_set_line_width(ctx, lwidth / 2.);
223 cairo_stroke(ctx);
224 cairo_destroy(ctx);
226 return TRUE;
229 static void
230 calf_knob_size_request (GtkWidget *widget,
231 GtkRequisition *requisition)
233 g_assert(CALF_IS_KNOB(widget));
235 CalfKnob *self = CALF_KNOB(widget);
237 CalfKnobClass * cls = CALF_KNOB_CLASS(GTK_OBJECT_GET_CLASS(widget));
238 requisition->width = gdk_pixbuf_get_width(cls->knob_image[self->size - 1]);
239 requisition->height = gdk_pixbuf_get_height(cls->knob_image[self->size - 1]);
242 static gboolean calf_knob_enter (GtkWidget *widget, GdkEventCrossing* ev)
244 if (gtk_widget_get_state(widget) == GTK_STATE_NORMAL) {
245 gtk_widget_set_state(widget, GTK_STATE_PRELIGHT);
246 gtk_widget_queue_draw(widget);
248 return TRUE;
251 static gboolean calf_knob_leave (GtkWidget *widget, GdkEventCrossing *ev)
253 if (gtk_widget_get_state(widget) == GTK_STATE_PRELIGHT) {
254 gtk_widget_set_state(widget, GTK_STATE_NORMAL);
255 gtk_widget_queue_draw(widget);
257 return TRUE;
260 static void
261 calf_knob_incr (GtkWidget *widget, int dir_down)
263 g_assert(CALF_IS_KNOB(widget));
264 CalfKnob *self = CALF_KNOB(widget);
265 GtkAdjustment *adj = gtk_range_get_adjustment(GTK_RANGE(widget));
267 int oldstep = (int)(0.5f + (adj->value - adj->lower) / adj->step_increment);
268 int step;
269 int nsteps = (int)(0.5f + (adj->upper - adj->lower) / adj->step_increment); // less 1 actually
270 if (dir_down)
271 step = oldstep - 1;
272 else
273 step = oldstep + 1;
274 if (self->type == 3 && step >= nsteps)
275 step %= nsteps;
276 if (self->type == 3 && step < 0)
277 step = nsteps - (nsteps - step) % nsteps;
279 // trying to reduce error cumulation here, by counting from lowest or from highest
280 float value = adj->lower + step * double(adj->upper - adj->lower) / nsteps;
281 gtk_range_set_value(GTK_RANGE(widget), value);
282 // printf("step %d:%d nsteps %d value %f:%f\n", oldstep, step, nsteps, oldvalue, value);
285 static gboolean
286 calf_knob_key_press (GtkWidget *widget, GdkEventKey *event)
288 g_assert(CALF_IS_KNOB(widget));
289 CalfKnob *self = CALF_KNOB(widget);
290 GtkAdjustment *adj = gtk_range_get_adjustment(GTK_RANGE(widget));
291 gtk_widget_set_state(widget, GTK_STATE_ACTIVE);
292 gtk_widget_queue_draw(widget);
293 switch(event->keyval)
295 case GDK_Home:
296 gtk_range_set_value(GTK_RANGE(widget), adj->lower);
297 return TRUE;
299 case GDK_End:
300 gtk_range_set_value(GTK_RANGE(widget), adj->upper);
301 return TRUE;
303 case GDK_Up:
304 calf_knob_incr(widget, 0);
305 return TRUE;
307 case GDK_Down:
308 calf_knob_incr(widget, 1);
309 return TRUE;
311 case GDK_Shift_L:
312 case GDK_Shift_R:
313 self->start_value = gtk_range_get_value(GTK_RANGE(widget));
314 self->start_y = self->last_y;
315 return TRUE;
318 return FALSE;
321 static gboolean
322 calf_knob_key_release (GtkWidget *widget, GdkEventKey *event)
324 g_assert(CALF_IS_KNOB(widget));
325 CalfKnob *self = CALF_KNOB(widget);
327 if(event->keyval == GDK_Shift_L || event->keyval == GDK_Shift_R)
329 self->start_value = gtk_range_get_value(GTK_RANGE(widget));
330 self->start_y = self->last_y;
331 return TRUE;
333 gtk_widget_set_state(widget, GTK_STATE_NORMAL);
334 gtk_widget_queue_draw(widget);
335 return FALSE;
338 static gboolean
339 calf_knob_button_press (GtkWidget *widget, GdkEventButton *event)
341 g_assert(CALF_IS_KNOB(widget));
342 CalfKnob *self = CALF_KNOB(widget);
344 if (event->type == GDK_2BUTTON_PRESS) {
345 gtk_range_set_value(GTK_RANGE(widget), self->default_value);
348 // CalfKnob *lg = CALF_KNOB(widget);
349 gtk_widget_grab_focus(widget);
350 gtk_grab_add(widget);
351 self->start_x = event->x;
352 self->last_y = self->start_y = event->y;
353 self->start_value = gtk_range_get_value(GTK_RANGE(widget));
354 gtk_widget_set_state(widget, GTK_STATE_ACTIVE);
355 gtk_widget_queue_draw(widget);
356 return TRUE;
359 static gboolean
360 calf_knob_button_release (GtkWidget *widget, GdkEventButton *event)
362 g_assert(CALF_IS_KNOB(widget));
364 if (GTK_WIDGET_HAS_GRAB(widget))
365 gtk_grab_remove(widget);
366 gtk_widget_set_state(widget, GTK_STATE_NORMAL);
367 gtk_widget_queue_draw(widget);
368 return FALSE;
371 static inline float endless(float value)
373 if (value >= 0)
374 return fmod(value, 1.f);
375 else
376 return fmod(1.f - fmod(1.f - value, 1.f), 1.f);
379 static inline float deadzone(GtkWidget *widget, float value, float incr)
381 // map to dead zone
382 float ov = value;
383 if (ov > 0.5)
384 ov = 0.1 + ov;
385 if (ov < 0.5)
386 ov = ov - 0.1;
388 float nv = ov + incr;
390 if (nv > 0.6)
391 return nv - 0.1;
392 if (nv < 0.4)
393 return nv + 0.1;
394 return 0.5;
397 static gboolean
398 calf_knob_pointer_motion (GtkWidget *widget, GdkEventMotion *event)
400 g_assert(CALF_IS_KNOB(widget));
401 CalfKnob *self = CALF_KNOB(widget);
403 float scale = (event->state & GDK_SHIFT_MASK) ? 2500 : 250;
404 gboolean moved = FALSE;
406 if (GTK_WIDGET_HAS_GRAB(widget))
408 if (self->type == 3)
410 gtk_range_set_value(GTK_RANGE(widget), endless(self->start_value - (event->y - self->start_y) / scale));
412 else
413 if (self->type == 1)
415 gtk_range_set_value(GTK_RANGE(widget), deadzone(GTK_WIDGET(widget), self->start_value, -(event->y - self->start_y) / scale));
417 else
419 gtk_range_set_value(GTK_RANGE(widget), self->start_value - (event->y - self->start_y) / scale);
421 moved = TRUE;
423 self->last_y = event->y;
424 return moved;
427 static gboolean
428 calf_knob_scroll (GtkWidget *widget, GdkEventScroll *event)
430 calf_knob_incr(widget, event->direction);
431 return TRUE;
434 static void
435 calf_knob_class_init (CalfKnobClass *klass)
437 // GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
438 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
439 widget_class->expose_event = calf_knob_expose;
440 widget_class->size_request = calf_knob_size_request;
441 widget_class->enter_notify_event = calf_knob_enter;
442 widget_class->leave_notify_event = calf_knob_leave;
443 widget_class->button_press_event = calf_knob_button_press;
444 widget_class->button_release_event = calf_knob_button_release;
445 widget_class->motion_notify_event = calf_knob_pointer_motion;
446 widget_class->key_press_event = calf_knob_key_press;
447 widget_class->key_release_event = calf_knob_key_release;
448 widget_class->scroll_event = calf_knob_scroll;
449 GError *error = NULL;
450 klass->knob_image[0] = gdk_pixbuf_new_from_file(PKGLIBDIR "/knob1.png", &error);
451 klass->knob_image[1] = gdk_pixbuf_new_from_file(PKGLIBDIR "/knob2.png", &error);
452 klass->knob_image[2] = gdk_pixbuf_new_from_file(PKGLIBDIR "/knob3.png", &error);
453 klass->knob_image[3] = gdk_pixbuf_new_from_file(PKGLIBDIR "/knob4.png", &error);
454 klass->knob_image[4] = gdk_pixbuf_new_from_file(PKGLIBDIR "/knob5.png", &error);
455 g_assert(klass->knob_image != NULL);
458 static void
459 calf_knob_init (CalfKnob *self)
461 GtkWidget *widget = GTK_WIDGET(self);
462 GTK_WIDGET_SET_FLAGS (GTK_WIDGET(self), GTK_CAN_FOCUS);
463 widget->requisition.width = 40;
464 widget->requisition.height = 40;
467 GtkWidget *
468 calf_knob_new()
470 GtkAdjustment *adj = (GtkAdjustment *)gtk_adjustment_new(0, 0, 1, 0.01, 0.5, 0);
471 return calf_knob_new_with_adjustment(adj);
474 static gboolean calf_knob_value_changed(gpointer obj)
476 GtkWidget *widget = (GtkWidget *)obj;
477 gtk_widget_queue_draw(widget);
478 return FALSE;
481 GtkWidget *calf_knob_new_with_adjustment(GtkAdjustment *_adjustment)
483 GtkWidget *widget = GTK_WIDGET( g_object_new (CALF_TYPE_KNOB, NULL ));
484 if (widget) {
485 gtk_range_set_adjustment(GTK_RANGE(widget), _adjustment);
486 g_signal_connect(GTK_OBJECT(widget), "value-changed", G_CALLBACK(calf_knob_value_changed), widget);
488 return widget;
491 GType
492 calf_knob_get_type (void)
494 static GType type = 0;
495 if (!type) {
497 static const GTypeInfo type_info = {
498 sizeof(CalfKnobClass),
499 NULL, /* base_init */
500 NULL, /* base_finalize */
501 (GClassInitFunc)calf_knob_class_init,
502 NULL, /* class_finalize */
503 NULL, /* class_data */
504 sizeof(CalfKnob),
505 0, /* n_preallocs */
506 (GInstanceInitFunc)calf_knob_init
509 for (int i = 0; ; i++) {
510 char *name = g_strdup_printf("CalfKnob%u%d",
511 ((unsigned int)(intptr_t)calf_knob_class_init) >> 16, i);
512 if (g_type_from_name(name)) {
513 free(name);
514 continue;
516 type = g_type_register_static(GTK_TYPE_RANGE,
517 name,
518 &type_info,
519 (GTypeFlags)0);
520 free(name);
521 break;
524 return type;