1
2 /*
3 * Diverse Bristol audio routines.
4 * Copyright (c) by Nick Copeland <nickycopeland@hotmail.com> 1996,2012
5 *
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, see <http://www.gnu.org/licenses/>.
19 *
20 */
21
22 /*
23 * This will be a rotary potmeter. Takes a bitmap and rotates it according to
24 * input from the mouse/keyboard. We need a few different parameters, and a
25 * hefty include file. Where possible will try and keep X11 requests in a
26 * separate set of files.
27 */
28
29 #include <math.h>
30
31 #include "brightoninternals.h"
32 /*#include "brightonX11.h" */
33
34 extern void brightonPanelLocation(brightonWindow *, int, int, int, int, int, int);
35
36 int
destroyRotary(brightonDevice * dev)37 destroyRotary(brightonDevice *dev)
38 {
39 printf("destroyRotary()\n");
40
41 if (dev->image)
42 brightonFreeBitmap(dev->bwin, dev->image);
43 dev->image = NULL;
44
45 return(0);
46 }
47
48 static int
displayrotary(brightonDevice * dev)49 displayrotary(brightonDevice *dev)
50 {
51 if (dev->bwin->app->resources[dev->panel].flags & BRIGHTON_WITHDRAWN)
52 return(0);
53
54 if (dev->bwin->app->resources[dev->panel].devlocn[dev->index].flags
55 & BRIGHTON_WITHDRAWN)
56 return(0);
57
58 /*
59 * Build up a smooth position for the pot. We may need to adjust this based
60 * on the to/from values.
61 */
62 if (dev->bwin->app->resources[dev->panel].devlocn[dev->index].flags
63 & BRIGHTON_STEPPED)
64 {
65 dev->position = dev->value < 0.5?
66 1 * M_PI * (1 - 2 * dev->value) / 3:
67 M_PI * (7 - dev->value * 2) / 3;
68 } else
69 dev->position = dev->value < 0.5?
70 7 * M_PI * (1 - 2 * dev->value) / 8:
71 M_PI * (23 - dev->value * 14) / 8;
72
73 /*
74 * Only draw fixed number of steps.
75 */
76 if ((int) (dev->value * 360 / M_PI) != (int) (dev->lastvalue * 360 / M_PI))
77 {
78 brightonIResource *panel;
79
80 if (dev->bwin->app->resources[dev->panel].devlocn[dev->index].flags
81 & BRIGHTON_REDRAW)
82 {
83 brightonDevUndraw(dev->bwin, dev->bwin->dlayer,
84 dev->x + dev->bwin->app->resources[dev->panel].sx,
85 dev->y + dev->bwin->app->resources[dev->panel].sy,
86 dev->width, dev->height);
87 }
88
89 panel = &dev->bwin->app->resources[dev->panel];
90
91 /*
92 * Rotate my image onto the parents canvas. For rotaries that have blue
93 * bitmaps its needs to be made sure that the background is clear
94 * before this rotation is done. This means we have to copy the image
95 * background into place first.
96 */
97 brightonRender(dev->bwin, dev->bwin->dlayer, dev->bwin->dlayer,
98 dev->x + dev->bwin->app->resources[dev->panel].sx,
99 dev->y + dev->bwin->app->resources[dev->panel].sy,
100 dev->width, dev->height, 0);
101 brightonRotate(dev->bwin, dev->image,
102 dev->bwin->dlayer,
103 dev->x + dev->bwin->app->resources[dev->panel].sx,
104 dev->y + dev->bwin->app->resources[dev->panel].sy,
105 dev->width, dev->height,
106 dev->position);
107
108 /*
109 * We can consider the alpha layer here.
110 */
111 if (dev->image2 != 0)
112 {
113 brightonAlphaLayer(dev->bwin, dev->image2,
114 dev->bwin->dlayer,
115 dev->x + dev->bwin->app->resources[dev->panel].sx,
116 dev->y + dev->bwin->app->resources[dev->panel].sy,
117 dev->width, dev->height,
118 dev->position);
119 }
120
121 /*
122 * And request the panel to put this onto the respective image.
123 */
124 brightonFinalRender(dev->bwin,
125 dev->x + dev->bwin->app->resources[dev->panel].sx,
126 dev->y + dev->bwin->app->resources[dev->panel].sy,
127 dev->width, dev->height);
128 }
129
130 dev->lastvalue = dev->value;
131 dev->lastposition = dev->position;
132
133 return(0);
134 }
135
136 /*
137 * This will go into brighton render
138 */
139 static void
renderHighlights(brightonWindow * bwin,brightonDevice * dev)140 renderHighlights(brightonWindow *bwin, brightonDevice *dev)
141 {
142 float d, ho2, streak;
143 float ox, oy, dx, dy;
144
145 if (dev->shadow.coords == 0)
146 dev->shadow.coords = brightonmalloc(7 * sizeof(brightonCoord));
147 dev->shadow.ccount = 7;
148 dev->shadow.flags = BRIGHTON_STATIC;
149
150 ho2 = dev->width / 2;
151 ox = dev->x + ho2;
152 oy = dev->y + ho2;
153
154 /*
155 * We are going to render the shadow directly onto the background bitmap.
156 * We have X and Y for the source of the shadow, plus its height and
157 * intensity. For now we will take a default relief, and highlight the
158 * background accordingly. This should be a 3D transform.....
159 *
160 * This can all be done with fractional distances, since we are going to
161 * be dealing with a number of similar triangles.
162 */
163 dx = ox - bwin->lightX;
164 dy = oy - bwin->lightY;
165 d = sqrt((double) (dx * dx + dy * dy));
166
167 dev->shadow.coords[0].x = ox + dy * ho2 / d;
168 dev->shadow.coords[0].y = oy - dx * ho2 / d;
169 dev->shadow.coords[1].x = ox - dy * ho2 / d;
170 dev->shadow.coords[1].y = oy + dx * ho2 / d;
171
172 streak = (dev->width * 1.6 * d / bwin->lightH)
173 / (1 - dev->width * 1.6 / bwin->lightH);
174
175 dev->shadow.coords[2].x = dev->shadow.coords[1].x
176 + streak * dx / d + dy * ho2 * 0.4 / d;
177 dev->shadow.coords[2].y = dev->shadow.coords[1].y
178 + streak * dy / d - dx * ho2 * 0.4 / d;
179
180 dev->shadow.coords[6].x = dev->shadow.coords[0].x
181 + streak * dx / d - dy * ho2 * 0.4 / d;
182 dev->shadow.coords[6].y = dev->shadow.coords[0].y
183 + streak * dy / d + dx * ho2 * 0.4 / d;
184
185 streak = (dev->width * 2.07 * d / bwin->lightH)
186 / (1 - dev->width * 2.07 / bwin->lightH);
187
188 dev->shadow.coords[3].x = ox + (streak * dx / d) - dy * ho2 * 0.5 / d;
189 dev->shadow.coords[3].y = oy + (streak * dy / d) + dx * ho2 * 0.5 / d;
190 dev->shadow.coords[5].x = ox + (streak * dx / d) + dy * ho2 * 0.5 / d;
191 dev->shadow.coords[5].y = oy + (streak * dy / d) - dx * ho2 * 0.5 / d;
192
193 streak = (dev->width * 2.2 * d / bwin->lightH)
194 / (1 - dev->width * 2.2 / bwin->lightH);
195
196 dev->shadow.coords[4].x = ox + (streak * dx / d);
197 dev->shadow.coords[4].y = oy + (streak * dy / d);
198
199 /*
200 printf("renderHighlights(%i) %i,%i-%i,%i-%i,%i-%i,%i-%i,%i-%i,%i-%i,%i\n",
201 dev->index, dev->shadow.coords[0].x,dev->shadow.coords[0].y,
202 dev->shadow.coords[1].x,dev->shadow.coords[1].y,
203 dev->shadow.coords[2].x,dev->shadow.coords[2].y,
204 dev->shadow.coords[3].x,dev->shadow.coords[3].y,
205 dev->shadow.coords[4].x,dev->shadow.coords[4].y,
206 dev->shadow.coords[5].x,dev->shadow.coords[5].y,
207 dev->shadow.coords[6].x,dev->shadow.coords[6].y);
208 */
209
210 dev->shadow.ccount = 7;
211 /*
212 * rather than fill the polygon, we need to generate this shape, and lower
213 * the shading of pixels that are within this area.
214 */
215 }
216
217 static void
considercallback(brightonDevice * dev)218 considercallback(brightonDevice *dev)
219 {
220 brightonIResource *panel = &dev->bwin->app->resources[dev->panel];
221
222 if (dev->bwin->flags & BRIGHTON_NO_DRAW)
223 return;
224
225 if (dev->value < 0)
226 dev->value = 0;
227
228 /*
229 * We now need to consider rounding this to the resolution of this
230 * device. If the max value is not 1.0 then we need to put fixed steps
231 * into our new device value.
232 */
233 if (dev->bwin->app->resources[dev->panel].devlocn[dev->index].to != 1.0)
234 {
235 dev->value =
236 (dev->value
237 * dev->bwin->app->resources[dev->panel].devlocn[dev->index].to);
238
239 if ((dev->value - ((int) dev->value)) > 0.5)
240 dev->value = ((float) ((int) dev->value) + 1)
241 / dev->bwin->app->resources[dev->panel].devlocn[dev->index].to;
242 else
243 dev->value = ((float) ((int) dev->value))
244 / dev->bwin->app->resources[dev->panel].devlocn[dev->index].to;
245 }
246
247 /*
248 * I am not sure this is desired functionality. Yes, it reduces the total
249 * number of events sent, but there are issues with multifunction panels
250 * and memory loading.
251 if (dev->lastvalue != dev->value)
252 */
253 {
254 if (panel->devlocn[dev->index].callback)
255 {
256 panel->devlocn[dev->index].callback(dev->bwin,
257 dev->panel, dev->index,
258 dev->value * panel->devlocn[dev->index].to);
259 } else if (panel->callback) {
260 panel->callback(dev->bwin, dev->panel, dev->index,
261 dev->value * panel->devlocn[dev->index].to);
262 }
263 }
264 }
265
266 static int cx, cy;
267 static float sval;
268
269 static int
configure(brightonDevice * dev,brightonEvent * event)270 configure(brightonDevice *dev, brightonEvent *event)
271 {
272 /*printf("configureRotary(%i)\n", event->command); */
273
274 if (event->command == -1)
275 return(-1);
276
277 if (event->command == BRIGHTON_BUTTONPRESS)
278 {
279 /*
280 * This is hard coded, it calls back to the GUI. This is incorrect as
281 * the callback dispatcher should be requested by the GUI.
282 *
283 * Perhaps the MIDI code should actually be in the same library? Why
284 * does the GUI need to know about this?
285 */
286 if (event->key == BRIGHTON_BUTTON2)
287 brightonRegisterController(dev);
288
289 return(0);
290 }
291
292 if (event->command == BRIGHTON_BUTTONRELEASE)
293 {
294 cx = cy = sval = -1;
295 dev->flags &= ~BRIGHTON_CONTROLKEY;
296 return(0);
297 }
298
299 if (event->command == BRIGHTON_RESIZE)
300 {
301 dev->originx = event->x;
302 dev->originy = event->y;
303
304 if (event->w < event->h)
305 {
306 /*
307 * If width is less than height, then we need to configure
308 * some offsets. Also, we only want even number of pixel areas.
309 */
310 dev->x = event->x;
311 dev->y = event->y;
312 dev->width = event->w & ~0x01;
313 dev->height = event->w & ~0x01;
314 } else if (event->w > event->h) {
315 dev->x = event->x;
316 dev->y = event->y;
317 dev->width = event->h & ~0x01;
318 dev->height = event->h & ~0x01;
319 } else {
320 dev->x = event->x;
321 dev->y = event->y;
322 dev->width = event->w & ~0x01;
323 dev->height = event->h & ~0x01;
324 }
325
326 /*
327 * We should now rework our parent understanding of our window, since
328 * it will have altered.
329 */
330 brightonPanelLocation(dev->bwin,
331 dev->panel, dev->index, dev->x, dev->y, dev->width, dev->height);
332
333 considercallback(dev);
334
335 if (dev->bwin->app->resources[dev->panel].devlocn[dev->index].flags
336 & BRIGHTON_WITHDRAWN)
337 return(0);
338
339 /*
340 * We need to build in some shadow, to prevent the rotary from looking
341 * like it is hanging in mid air.
342 */
343 brightonRenderShadow(dev, 0);
344
345 dev->lastvalue = -1;
346 displayrotary(dev);
347
348 return(0);
349 }
350
351 if (event->command == BRIGHTON_KEYRELEASE)
352 {
353 switch(event->key) {
354 default:
355 break;
356 case 37:
357 case 109:
358 case 65508:
359 case 65507:
360 dev->flags &= ~BRIGHTON_CONTROLKEY;
361 cx = cy = sval = -1;
362 break;
363 case 50:
364 case 62:
365 case 65505:
366 dev->flags &= ~BRIGHTON_SHIFTKEY;
367 break;
368 }
369 }
370
371 if (event->command == BRIGHTON_KEYPRESS)
372 {
373 switch(event->key) {
374 default:
375 break;
376 case 37:
377 case 109:
378 case 65508:
379 case 65507:
380 cx = event->x;
381 cy = event->y;
382 sval = dev->value;
383 dev->flags |= BRIGHTON_CONTROLKEY;
384 break;
385 case 50:
386 case 62:
387 case 65505:
388 dev->flags |= BRIGHTON_SHIFTKEY;
389 break;
390 case 0x6b:
391 case 0xff52:
392 /*
393 * UP arrow - we need to put some bristol keysyms in here. For
394 * portability we should not include the X11 headers but map
395 * our own translations.
396 */
397 if (dev->flags & BRIGHTON_SHIFTKEY)
398 {
399 if ((dev->value += ((float) 256) / 16384) > 1.0)
400 dev->value = 1.0;
401 } else {
402 if ((dev->value += ((float) 1) / 16384) > 1.0)
403 dev->value = 1.0;
404 }
405 break;
406 case 0x6a:
407 case 0xff54:
408 /*
409 * Down arrow
410 * We should have this consider stepped rotary motion where a
411 * single up or down would give a whole step rather than a
412 * micromovement
413 */
414 if (dev->flags & BRIGHTON_SHIFTKEY)
415 {
416 if ((dev->value -= ((float) 256) / 16384) < 0)
417 dev->value = 0;
418 } else {
419 if ((dev->value -= ((float) 1) / 16384) < 0)
420 dev->value = 0;
421 }
422 break;
423 case 0xff51:
424 /* Left arrow */
425 if (dev->flags & BRIGHTON_SHIFTKEY)
426 {
427 if ((dev->value -= ((float) 2048) / 16384) < 0)
428 dev->value = 0;
429 } else {
430 if ((dev->value -= ((float) 32) / 16384) < 0)
431 dev->value = 0;
432 }
433 break;
434 case 0xff53:
435 /* Right arrow */
436 if (dev->flags & BRIGHTON_SHIFTKEY)
437 {
438 if ((dev->value += ((float) 2048) / 16384) > 1.0)
439 dev->value = 1.0;
440 } else {
441 if ((dev->value += ((float) 32) / 16384) > 1.0)
442 dev->value = 1.0;
443 }
444 break;
445 }
446
447 considercallback(dev);
448
449 displayrotary(dev);
450 }
451
452 /*
453 * Adding a fine adjustment control, and adding a notch into the motion
454 * control under option
455 */
456 if (event->command == BRIGHTON_MOTION)
457 {
458 if (dev->flags & BRIGHTON_CONTROLKEY)
459 {
460 float deltax;
461
462 /*
463 * We should have this consider stepped rotary motion where a single
464 * up or down would give a whole step rather than a micromovement
465 */
466 if (cx == -1)
467 {
468 sval = dev->value;
469 cx = event->x;
470 cy = event->y;
471 }
472
473 deltax = ((float) (event->x - cx)) / 16383.0f;
474
475 dev->value = sval + deltax;
476
477 /*printf("Controlled motion from (%i, %i) to (%i, %i): %f\n", */
478 /*cx, cy, event->x, event->y, deltax); */
479 } else if (dev->bwin->flags & BRIGHTON_ROTARY_UD) {
480 if (dev->bwin->app->resources[dev->panel].devlocn[dev->index].flags
481 & BRIGHTON_NOTCH)
482 {
483 dev->value = 1.0f - ((float) (event->y)) * 1.1f
484 / (dev->bwin->height);
485
486 /* If in range 0.45/0.55 - interpret 0.5 */
487 if (dev->value > 0.55)
488 dev->value -= 0.05;
489 else if (dev->value < 0.45)
490 dev->value += 0.05;
491 else
492 dev->value = 0.5;
493
494 if (dev->value < 0.0f)
495 dev->value = 0.0f;
496 else if (dev->value > 1.0f)
497 dev->value = 1.0f;
498 } else {
499 if ((dev->value = 1.0f - ((float) event->y)
500 / (dev->bwin->height)) < 0)
501 dev->value = 0.0f;
502 else if (dev->value > 1.0f)
503 dev->value = 1.0f;
504 }
505 //printf("%f %i %i, %i %i\n", dev->value,
506 //event->x, event->y, event->ox, event->oy);
507 } else {
508 double angle, diffx, diffy;
509
510 diffx = event->x - (dev->width / 2 + dev->x);
511 diffy = event->y - (dev->height / 2 + dev->y);
512 angle = atan(diffy / diffx);
513
514 /*
515 * Adjust this so that we get counterclock rotating angles from
516 * mid top.
517 */
518 if (((diffx < 0) && (diffy < 0)) || ((diffx < 0) && (diffy >= 0)))
519 angle = M_PI / 2 - angle;
520 else
521 angle = 3 * M_PI / 2 - angle;
522
523 /*
524 * To correct to clock rotating angles, with stop points.
525 */
526 if (dev->bwin->app->resources[dev->panel].devlocn[dev->index].flags
527 & BRIGHTON_STEPPED)
528 {
529 /*
530 * Stepped controllers have limited motion, not from 145 to
531 * -145 but from 60 to -60
532 *
533 * The '7' is 2M_PI + M_PI/3
534 */
535 if (angle < M_PI)
536 dev->value = (M_PI / 3 - angle) / 2;
537 else
538 dev->value = (7 * M_PI / 3 - angle) / 2;
539 } else {
540 /*
541 * If the angle is negative then we are PM on clockface and
542 * we have motion up to 7/8 of M_PI.
543 *
544 * If the angle is positive then we are AM on clockface and
545 * we have motion from 7/8.
546 *
547 * The '23' is 2M_PI + 7M_PI/8
548 */
549 if (angle < M_PI)
550 dev->value = (7 * M_PI / 8 - angle) * 4 / (M_PI * 7);
551 else
552 dev->value = (23 * M_PI / 8 - angle) * 4 / (M_PI * 7);
553 }
554
555 if (dev->bwin->app->resources[dev->panel].devlocn[dev->index].flags
556 & BRIGHTON_NOTCH)
557 {
558 if (dev->value > 0.55)
559 dev->value -= 0.05;
560 else if (dev->value < 0.45)
561 dev->value += 0.05;
562 else
563 dev->value = 0.5;
564 }
565 }
566
567 if (dev->value < 0)
568 dev->value = 0;
569 else if (dev->value > 1.0)
570 dev->value = 1.0;
571
572 considercallback(dev);
573
574 displayrotary(dev);
575
576 return(0);
577 }
578
579 if (event->command == BRIGHTON_PARAMCHANGE)
580 {
581 dev->value = event->value
582 / dev->bwin->app->resources[dev->panel].devlocn[dev->index].to;
583
584 considercallback(dev);
585
586 displayrotary(dev);
587
588 return(0);
589 }
590
591 return(0);
592 }
593
594 int *
createRotary(brightonWindow * bwin,brightonDevice * dev,int index,char * bitmap)595 createRotary(brightonWindow *bwin, brightonDevice *dev, int index, char *bitmap)
596 {
597 /*printf("createRotary(%s)\n", bitmap); */
598
599 dev->destroy = destroyRotary;
600 dev->configure = configure;
601 dev->bwin = bwin;
602 dev->index = index;
603
604 if (bitmap == 0)
605 {
606 if (dev->image)
607 brightonFreeBitmap(bwin, dev->image);
608 /*
609 * Open the default bitmap
610 */
611 if (bwin->app->resources[dev->panel].devlocn[dev->index].image != 0)
612 dev->image =
613 bwin->app->resources[dev->panel].devlocn[dev->index].image;
614 else
615 dev->image = brightonReadImage(bwin, "bitmaps/knobs/knob.xpm");
616 } else {
617 if (dev->image)
618 brightonFreeBitmap(bwin, dev->image);
619 dev->image = brightonReadImage(bwin, bitmap);
620 }
621
622 /*
623 * We should take a peek at the second image resource. A rotary only uses
624 * a single bitmap, the second may be used as an alpha layer.
625 */
626 if (bwin->app->resources[dev->panel].devlocn[dev->index].image2 != 0)
627 dev->image2 =
628 bwin->app->resources[dev->panel].devlocn[dev->index].image2;
629
630 /*
631 * These will force an update when we first display ourselves.
632 */
633 dev->value = 0;
634 dev->lastvalue = -1;
635 dev->lastposition = -1;
636
637 return(0);
638 }
639
640