8320728f52d36c477a8cf4c8eb73dc2a1fbd73e5
[terminatorX.git] / src / tX_flash.c
1 /*
2     terminatorX - realtime audio scratching software
3     Copyright (C) 1999-2016  Alexander K├Ânig
4  
5     This program is free software; you can redistribute it and/or modify
6     it under the terms of the GNU General Public License as published by
7     the Free Software Foundation; either version 2 of the License, or
8     (at your option) any later version.
9  
10     This program is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU General Public License for more details.
14  
15     You should have received a copy of the GNU General Public License
16     along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  
18     File: tX_flash.c
19  
20     Description: This contains the implementation of the tx_flash_flash widget.
21 */    
22
23 #include <math.h>
24
25
26 #define IS_TX_FLASH 1
27 #include "tX_flash.h"
28 #include "tX_types.h"
29 #include "tX_global.h"
30 #include <malloc.h>
31
32 #ifndef WIN32
33 #include <unistd.h>
34 #endif
35
36 #ifdef __cplusplus
37 extern "C" {
38 #endif /* __cplusplus */
39
40 #define MAX_VALUE 1.0f
41 // 32767
42 #define RED_BORDER 0.8f
43 // 25000
44 #define TX_FLASH_DEFAULT_SIZE_X 25
45 #define TX_FLASH_DEFAULT_SIZE_Y 30
46 #define DY 5
47 #define DX 5
48 #define DMINIX 2
49 #define S_MINIX 2
50 #define L_MINIX 3
51 #define DLEVEL 3
52 #define MAX_MAX_CYCLES 30;
53
54 /* pre dec */
55 static void gtk_tx_flash_class_init (GtkTxFlashClass *);
56 static void gtk_tx_flash_init (GtkTxFlash *tx_flash);
57 GtkWidget* gtk_tx_flash_new ();
58 static void gtk_tx_flash_destroy (GtkWidget *widget);
59 static void gtk_tx_flash_realize (GtkWidget *widget);
60 static void gtk_tx_flash_get_preferred_width (GtkWidget *widget, gint *minimal_height, gint *natural_height);
61 static void gtk_tx_flash_get_preferred_height (GtkWidget *widget, gint *minimal_height, gint *natural_height);
62
63 static void gtk_tx_flash_size_allocate (GtkWidget *widget, GtkAllocation *allocation);
64 static gboolean gtk_tx_flash_draw (GtkWidget *widget, cairo_t *cairo);
65
66 /* Local data */
67
68 static GtkWidgetClass *parent_class = NULL;
69
70 GType gtk_tx_flash_get_type ()
71 {
72         static GType tx_flash_type = 0;
73
74         if (!tx_flash_type) {
75                 static const GTypeInfo tx_flash_info = {
76                         sizeof (GtkTxFlashClass),
77                         NULL,
78                         NULL,
79                         (GClassInitFunc) gtk_tx_flash_class_init, 
80                         NULL,
81                         NULL,
82                         sizeof (GtkTxFlash),
83                 0,
84                         (GInstanceInitFunc) gtk_tx_flash_init,
85                 };
86
87                 tx_flash_type = g_type_register_static(GTK_TYPE_WIDGET, "GtkTxFlash", &tx_flash_info, 0);
88     }
89         
90         return tx_flash_type;
91 }
92
93 static void gtk_tx_flash_class_init (GtkTxFlashClass *gclass)
94 {
95         GtkWidgetClass *widget_class;
96         
97         widget_class = (GtkWidgetClass*) gclass;
98         
99         parent_class = (GtkWidgetClass*) g_type_class_peek (gtk_widget_get_type ());
100         
101         widget_class->destroy = gtk_tx_flash_destroy;
102         
103         widget_class->realize = gtk_tx_flash_realize;
104         widget_class->draw = gtk_tx_flash_draw;
105         
106         widget_class->get_preferred_height = gtk_tx_flash_get_preferred_height;
107         widget_class->get_preferred_width = gtk_tx_flash_get_preferred_width;
108         
109         widget_class->size_allocate = gtk_tx_flash_size_allocate;
110 }
111
112 enum {
113         COL_BG,
114         COL_NORM,
115         COL_NORM_HALF,
116         COL_LOUD,
117         COL_LOUD_HALF,
118         NO_COLS
119 };
120
121 /* c=a+(a-b)*x; */
122
123 inline void mk_half(double s, GdkRGBA *a, GdkRGBA *b, GdkRGBA *c)
124 {
125         c->red=a->red-(a->red-b->red)*s;
126         c->green=a->green-(a->green-b->green)*s;
127         c->blue=a->blue-(a->blue-b->blue)*s;
128         c->alpha=1.0;
129 }
130
131 void gtk_tx_flash_update_colors(GtkTxFlash *tx)
132 {
133         gdk_rgba_parse(&tx->colors[COL_BG], globals.vu_meter_bg);
134         tx->colors[COL_BG].alpha = 1.0;
135         gdk_rgba_parse(&tx->colors[COL_LOUD], globals.vu_meter_loud);
136         tx->colors[COL_LOUD].alpha = 1.0;
137         gdk_rgba_parse(&tx->colors[COL_NORM], globals.vu_meter_normal);
138         tx->colors[COL_NORM].alpha = 1.0;
139         mk_half(globals.vu_meter_border_intensity, &tx->colors[COL_BG], &tx->colors[COL_LOUD], &tx->colors[COL_LOUD_HALF]);
140         mk_half(globals.vu_meter_border_intensity, &tx->colors[COL_BG], &tx->colors[COL_NORM], &tx->colors[COL_NORM_HALF]);
141 }
142
143 static void gtk_tx_flash_init (GtkTxFlash *tx_flash)
144 {
145         tx_flash->surface = NULL;
146
147         gtk_tx_flash_update_colors(tx_flash);
148 }
149
150 GtkWidget* gtk_tx_flash_new ()
151 {
152         GtkTxFlash *tx_flash;
153         tx_flash = (GtkTxFlash *)g_object_new(gtk_tx_flash_get_type (), NULL);  
154         
155         // tX_msg("creating a new flash: %08x\n", tx_flash);
156         return GTK_WIDGET (tx_flash);
157 }
158
159 static void gtk_tx_flash_destroy (GtkWidget *widget)
160 {
161         g_return_if_fail (widget != NULL);
162         g_return_if_fail (GTK_IS_TX_FLASH (widget));
163         
164         if (GTK_WIDGET_CLASS (parent_class)->destroy)
165                 (*GTK_WIDGET_CLASS (parent_class)->destroy) (widget);
166 }
167
168 static void gtk_tx_flash_realize (GtkWidget *widget)
169 {
170         GtkTxFlash *tx_flash;
171         GdkWindowAttr attributes;
172         GtkStyleContext *context;
173         gint attributes_mask;
174         
175         g_return_if_fail (widget != NULL);
176         g_return_if_fail (GTK_IS_TX_FLASH (widget));
177         
178         tx_flash=GTK_TX_FLASH(widget);
179
180         gtk_widget_set_realized(widget, TRUE);
181         GtkAllocation allocation;
182         gtk_widget_get_allocation(widget, &allocation);
183         
184         attributes.x = allocation.x;
185         attributes.y = allocation.y;
186         attributes.width = allocation.width;
187         attributes.height = allocation.height;
188         attributes.wclass = GDK_INPUT_OUTPUT;
189         attributes.window_type = GDK_WINDOW_CHILD;
190         attributes.event_mask = gtk_widget_get_events (widget) | 
191                 GDK_EXPOSURE_MASK;
192         attributes.visual = gtk_widget_get_visual (widget);
193         attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL;
194         
195         gtk_widget_set_window(widget, gdk_window_new (gtk_widget_get_parent_window(widget), &attributes, attributes_mask));
196         
197         gdk_window_set_user_data (gtk_widget_get_window(widget), widget);
198         
199         context = gtk_widget_get_style_context(widget);
200         gtk_style_context_set_background(context, gtk_widget_get_window(widget));
201         
202         if (tx_flash->surface) {
203                 cairo_surface_destroy (tx_flash->surface);
204         }
205         
206         tx_flash->surface = gdk_window_create_similar_surface (gtk_widget_get_window(widget), CAIRO_CONTENT_COLOR, allocation.width, allocation.height);
207 }
208
209 static void gtk_tx_flash_get_preferred_width (GtkWidget *widget, gint *minimal_width, gint *natural_width)
210 {
211   *minimal_width = *natural_width = TX_FLASH_DEFAULT_SIZE_X;
212 }
213
214 static void gtk_tx_flash_get_preferred_height (GtkWidget *widget, gint *minimal_height, gint *natural_height)
215 {
216     *minimal_height = *natural_height = TX_FLASH_DEFAULT_SIZE_Y;
217 }
218
219
220 static void gtk_tx_flash_prepare(GtkWidget *widget)
221 {
222         GtkTxFlash *tx_flash;
223
224         g_return_if_fail (widget != NULL);
225         g_return_if_fail (GTK_IS_TX_FLASH (widget));
226         
227         tx_flash=GTK_TX_FLASH(widget);
228
229         GtkAllocation allocation;
230         gtk_widget_get_allocation(widget, &allocation);
231         
232         tx_flash->levels=(allocation.height-(2*DY))/DLEVEL;
233         tx_flash->channel[0].last_level=0;
234         tx_flash->channel[1].last_level=0;
235         tx_flash->channel[0].max=0;
236         tx_flash->channel[1].max=0;
237         tx_flash->level_value=MAX_VALUE/(f_prec) tx_flash->levels;
238         tx_flash->red_level=(RED_BORDER/tx_flash->level_value);
239         
240         tx_flash->channel[0].x1=DMINIX+S_MINIX+2;
241         tx_flash->channel[1].x2=allocation.width-tx_flash->channel[0].x1;
242                 
243         if (allocation.width%2>0) {
244                 // odd
245                 tx_flash->center_expand=0;
246                 tx_flash->channel[0].x2=allocation.width/2-1;
247         } else {
248                 // even
249                 tx_flash->center_expand=1;
250                 tx_flash->channel[0].x2=allocation.width/2-2;
251         }
252         tx_flash->channel[1].x1=allocation.width/2+2;
253         
254         //tX_msg("flash: width %i: left %i, right %i", widget->allocation.width, tx_flash->channel[0].x2-tx_flash->channel[0].x1, tx_flash->channel[1].x2-tx_flash->channel[1].x1);
255 }
256
257 static void gtk_tx_flash_size_allocate(GtkWidget *widget, GtkAllocation *allocation)
258 {
259         g_return_if_fail (widget != NULL);
260         g_return_if_fail (GTK_IS_TX_FLASH (widget));
261         g_return_if_fail (allocation != NULL);
262         
263         gtk_widget_set_allocation(widget, allocation);
264         
265         gtk_tx_flash_prepare(widget);
266         
267         if (gtk_widget_get_realized (widget)) {
268                 gdk_window_move_resize (gtk_widget_get_window(widget),
269                                   allocation->x, allocation->y,
270                                   allocation->width, allocation->height);
271         }
272 }
273
274 static void gtk_tx_flash_paint_yourself(GtkWidget *widget, cairo_t *cr)
275 {
276         GtkTxFlash *tx_flash;
277         GtkAllocation allocation;
278         
279         gint i, x11, x12,x21, x22, y, middle;
280         int long_level;
281         
282         tx_flash = GTK_TX_FLASH (widget);
283         gtk_widget_get_allocation(widget, &allocation);
284         
285         cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
286         cairo_set_source_surface (cr, tx_flash->surface, 0, 0);
287
288         gdk_cairo_set_source_rgba (cr, &tx_flash->colors[COL_BG]);
289         cairo_rectangle(cr, 0, 0, allocation.width, allocation.height);
290         cairo_fill(cr);
291         
292         cairo_set_line_width(cr,1);
293         gdk_cairo_set_source_rgba (cr, &tx_flash->colors[COL_NORM]);
294         
295         x12=DMINIX+S_MINIX;
296         x21=allocation.width-1-x12;
297         middle=allocation.width/2;
298         
299         for (i=0, y=allocation.height-DY; i<=tx_flash->levels; y-=DLEVEL, i++) {
300                 if (i==0) {
301                         long_level=1;
302                 } else if (i==tx_flash->red_level-1) {
303                         long_level=1;
304                 } else if (i==tx_flash->red_level) {
305                         long_level=1;
306                         gdk_cairo_set_source_rgba (cr, &tx_flash->colors[COL_LOUD_HALF]);       
307                 } else if (i==tx_flash->levels) {
308                         long_level=1;
309                 } else long_level=0;
310                 
311                 if (long_level) {
312                         x11=x12-L_MINIX;
313                         x22=x21+L_MINIX;
314                 } else {
315                         x11=x12-S_MINIX;
316                         x22=x21+S_MINIX;                
317                 }
318                 
319                 cairo_move_to (cr, x11, y);
320                 cairo_line_to (cr, x12+1, y);
321                 
322                 cairo_move_to (cr, x21, y);
323                 cairo_line_to (cr, x22+1, y);
324
325                 if (tx_flash->center_expand) {
326                         cairo_move_to (cr, middle-1, y);
327                         cairo_line_to (cr, middle+1, y);
328                 } else {
329                         cairo_move_to (cr, middle, y);
330                         cairo_line_to (cr, middle+1, y);
331                 }
332                 cairo_stroke (cr);
333         }
334 }
335
336 static gboolean gtk_tx_flash_draw(GtkWidget *widget, cairo_t *cr)
337 {
338         g_return_val_if_fail (widget != NULL, FALSE);
339         g_return_val_if_fail (GTK_IS_TX_FLASH (widget), FALSE);
340         
341         gtk_tx_flash_prepare(widget);
342         gtk_tx_flash_paint_yourself(widget, cr); 
343         
344         return FALSE;
345
346
347
348 static void gtk_tx_flash_set_channel_level(GtkTxFlash *tx_flash, f_prec new_value, struct flash_channel *channel);
349
350 void gtk_tx_flash_set_level(GtkWidget *widget, f_prec left_channel, f_prec right_channel)
351 {
352         GtkTxFlash *tx_flash;
353         
354         g_return_if_fail (widget != NULL);
355         g_return_if_fail (GTK_IS_TX_FLASH (widget));
356         
357         tx_flash = GTK_TX_FLASH (widget);
358         
359         gtk_tx_flash_set_channel_level(tx_flash, left_channel, &tx_flash->channel[0]);
360         gtk_tx_flash_set_channel_level(tx_flash, right_channel, &tx_flash->channel[1]);
361 }
362
363 static void gtk_tx_flash_set_channel_level(GtkTxFlash *tx_flash, f_prec new_value, struct flash_channel *channel)
364 {
365         GtkWidget *widget=GTK_WIDGET(tx_flash);
366         GtkAllocation allocation;
367         cairo_t *cr;
368         gint i, y;
369         int new_level;
370         int red=0;
371         
372         new_level=(int) (new_value/tx_flash->level_value);
373         gtk_widget_get_allocation(widget, &allocation);
374         
375         cr = gdk_cairo_create (gtk_widget_get_window(widget));
376         cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
377         cairo_set_line_width(cr,1);
378         cairo_set_source_surface (cr, tx_flash->surface, 0, 0);
379
380         gdk_cairo_set_source_rgba (cr, &tx_flash->colors[COL_BG]);
381
382         // tX_msg("setting level: %5d for widget %08x channel %08x\n", new_level, tx_flash, channel);
383         
384         if (new_level>tx_flash->levels) 
385                 new_level=tx_flash->levels;
386         
387         if (new_level>channel->max) {
388                 channel->max=new_level;
389                 tx_flash->max_cycles=MAX_MAX_CYCLES;
390         } else {
391                 tx_flash->max_cycles--;
392         }
393         
394         if (tx_flash->max_cycles <= 0) {
395                 y=allocation.height-(DY+(channel->max)*DLEVEL);
396                 gdk_cairo_set_source_rgba (cr, &tx_flash->colors[COL_BG]);
397                 cairo_move_to (cr, channel->x1, y);
398                 cairo_line_to (cr, channel->x2, y);
399                 cairo_stroke (cr);
400                 
401                 if (channel->max>0) {
402                         channel->max--;
403                         y+=DLEVEL;
404                         if (channel->max>tx_flash->red_level) {
405                                 gdk_cairo_set_source_rgba (cr, &tx_flash->colors[COL_LOUD]);
406                         } else {
407                                 gdk_cairo_set_source_rgba (cr, &tx_flash->colors[COL_NORM]);
408                         }
409                         
410                         cairo_move_to (cr, channel->x1, y);
411                         cairo_line_to (cr, channel->x2, y);
412                         cairo_stroke (cr);
413                 }
414         }
415         
416         if (new_level==channel->last_level) 
417                 return;
418         
419         if (new_level<channel->last_level) {
420                 new_level=channel->last_level*globals.flash_response;
421         }
422         
423         if (new_level>channel->last_level) {
424                 gdk_cairo_set_source_rgba (cr, &tx_flash->colors[COL_NORM]);
425                 
426                 for (i=channel->last_level, y=allocation.height-(DY+channel->last_level*DLEVEL); i<=new_level; y-=DLEVEL, i++) {
427                         if (!red) {
428                                 if (i>=tx_flash->red_level) {
429                                         gdk_cairo_set_source_rgba (cr, &tx_flash->colors[COL_LOUD]);
430                                         red=1;
431                                 }
432                         }
433                         cairo_move_to (cr, channel->x1, y);
434                         cairo_line_to (cr, channel->x2, y);
435                         cairo_stroke (cr);
436                 }
437         } else {
438                 gdk_cairo_set_source_rgba (cr, &tx_flash->colors[COL_BG]);
439                 
440                 if (channel->last_level==channel->max) {
441                         i=channel->last_level-1;
442                 } else {
443                         i=channel->last_level;
444                 }
445                 
446                 for (y=allocation.height-(DY+i*DLEVEL); i>new_level; y+=DLEVEL, i--) {
447                         cairo_move_to (cr, channel->x1, y);
448                         cairo_line_to (cr, channel->x2, y);
449                         cairo_stroke (cr);
450                 }
451         }
452         channel->last_level=new_level;
453         
454         cairo_destroy(cr);
455 }
456
457 void
458 gtk_tx_flash_clear (GtkWidget *widget)
459 {
460         GtkTxFlash *tx_flash;
461
462         tx_flash = GTK_TX_FLASH (widget);
463         
464         tx_flash->channel[0].max=0;
465         tx_flash->channel[1].max=0;
466         tx_flash->channel[0].last_level=0;
467         tx_flash->channel[1].last_level=0;
468         tx_flash->max_cycles=0;
469         
470         cairo_t *cr = gdk_cairo_create (gtk_widget_get_window(widget));
471         gtk_tx_flash_paint_yourself(widget, cr);
472         cairo_destroy(cr);
473 }
474
475 #ifdef __cplusplus
476 }
477 #endif /* __cplusplus */