87ebcddc19d3ba82403e89369e14199d151d8d44
[terminatorX.git] / src / tX_widget.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_widget.c
19  
20     Description: This contains the implementation of the tx_widget.
21                  This file is based on the GTK+ widget example from
22                  the GTK+ 1.2 tutorial.
23 */
24
25 #include <math.h>
26
27 #include <gtk/gtk.h>
28 #include "tX_widget.h"
29 #include "tX_types.h"
30 #include "tX_global.h"
31 #include <malloc.h>
32 #include <stdlib.h>
33 #include <string.h>
34
35 #ifndef WIN32
36 #include <unistd.h>
37 #endif
38
39 #define TX_DEFAULT_SIZE_X 100
40 #define TX_DEFAULT_SIZE_Y 11
41
42 /* forward declaration */
43 static void gtk_tx_class_init(GtkTxClass *);
44 static void gtk_tx_init(GtkTx * tx);
45 GtkWidget *gtk_tx_new(int16_t * wavdata, int wavsamples);
46 static void gtk_tx_destroy(GtkWidget * widget);
47 void gtk_tx_set_data(GtkTx * tx, int16_t * wavdata, int wavsamples);
48 static void gtk_tx_realize(GtkWidget * widget);
49
50 static void gtk_tx_get_preferred_width (GtkWidget *widget, gint *minimal_height, gint *natural_height);
51 static void gtk_tx_get_preferred_height (GtkWidget *widget, gint *minimal_height, gint *natural_height);
52
53 static void gtk_tx_size_allocate(GtkWidget * widget, GtkAllocation * allocation);
54 static gboolean gtk_tx_draw(GtkWidget * widget, cairo_t *cr);
55 static void gtk_tx_update(GtkTx * tx);
56 static void gtk_tx_prepare(GtkWidget * widget);
57
58 /* data */
59 static GtkWidgetClass *parent_class = NULL;
60
61 /* widget "methods" */
62
63 GType gtk_tx_get_type() {
64         static GType tx_type = 0;
65
66         if (!tx_type) {
67                 static const GTypeInfo tx_info = {
68                         sizeof (GtkTxClass),
69                         NULL,
70                         NULL,
71                         (GClassInitFunc) gtk_tx_class_init, 
72                         NULL,
73                         NULL,
74                         sizeof (GtkTx),
75                 0,
76                         (GInstanceInitFunc) gtk_tx_init,
77                 };
78
79                 tx_type = g_type_register_static(GTK_TYPE_WIDGET, "GtkTx", &tx_info, 0);
80     }
81         
82         return tx_type;
83 }
84
85 static void gtk_tx_class_init(GtkTxClass * gclass) {
86         GtkWidgetClass *widget_class;
87         
88         widget_class = (GtkWidgetClass *) gclass;
89         
90         parent_class = (GtkWidgetClass *) g_type_class_peek(gtk_widget_get_type());
91         
92         widget_class->destroy = gtk_tx_destroy;
93         
94         widget_class->realize = gtk_tx_realize;
95         widget_class->draw = gtk_tx_draw;
96         widget_class->get_preferred_height = gtk_tx_get_preferred_height;
97         widget_class->get_preferred_width = gtk_tx_get_preferred_width;
98         widget_class->size_allocate = gtk_tx_size_allocate;
99 //      widget_class->button_press_event = gtk_tx_button_press;
100 //      widget_class->button_release_event = gtk_tx_button_release;
101 //      widget_class->motion_notify_event = gtk_tx_motion_notify;
102 }
103
104 #define COL_BG_FOCUS     0
105 #define COL_BG_NO_FOCUS  1
106 #define COL_FG_FOCUS     2
107 #define COL_FG_NO_FOCUS  3
108 #define COL_CURSOR       4
109 #define COL_CURSOR_MUTE  5
110
111 void gtk_tx_update_colors(GtkTx *tx)
112 {
113         gdk_rgba_parse(&tx->colors[COL_BG_FOCUS], globals.wav_display_bg_focus);
114         tx->colors[COL_BG_FOCUS].alpha=1.0;
115         gdk_rgba_parse(&tx->colors[COL_BG_NO_FOCUS], globals.wav_display_bg_no_focus);
116         tx->colors[COL_BG_NO_FOCUS].alpha=1.0;
117         
118         gdk_rgba_parse(&tx->colors[COL_FG_FOCUS], globals.wav_display_fg_focus);
119         tx->colors[COL_FG_FOCUS].alpha=1.0;
120         gdk_rgba_parse(&tx->colors[COL_FG_NO_FOCUS], globals.wav_display_fg_no_focus);
121         tx->colors[COL_FG_NO_FOCUS].alpha=1.0;
122         
123         gdk_rgba_parse(&tx->colors[COL_CURSOR], globals.wav_display_cursor);
124         tx->colors[COL_CURSOR].alpha=1.0;
125         gdk_rgba_parse(&tx->colors[COL_CURSOR_MUTE], globals.wav_display_cursor_mute);
126         tx->colors[COL_CURSOR_MUTE].alpha=1.0;
127 }
128
129
130 static void gtk_tx_init(GtkTx * tx) {
131         tx->disp_data = NULL;
132         tx->data = NULL;
133         tx->samples = 0;
134         tx->do_showframe = 0;
135 #ifdef USE_DISPLAY_NORMALIZE
136         tx->max_value=-1;
137 #endif
138         
139         memset(tx->colors, 0, sizeof(tx->colors));
140         
141         gtk_tx_update_colors(tx);
142         
143         tx->current_fg=tx->audio_colors_focus;
144         tx->current_bg=&tx->colors[COL_BG_NO_FOCUS];
145         
146         tx->audio_colors_focus = NULL;
147         tx->audio_colors_nofocus = NULL;
148         
149         tx->spp=1;
150         tx->lastmute=-1;
151         tx->zoom=0;
152         tx->cursor_pos=0;
153         tx->cursor_x_pos=0;
154         
155         tx->surface = NULL;
156 }
157
158 GtkWidget *gtk_tx_new(int16_t * wavdata, int wavsamples) {
159         GtkTx *tx;
160
161         tx = (GtkTx *) g_object_new(gtk_tx_get_type (), NULL);
162
163         tx->data = wavdata;
164         tx->samples = wavsamples;
165         tx->zoom=0;
166         tx->display_x_offset=0;
167         
168         return GTK_WIDGET(tx);
169 }
170
171 static void gtk_tx_destroy(GtkWidget * widget) {
172         GtkTx *tx;
173         g_return_if_fail(widget != NULL);
174         g_return_if_fail(GTK_IS_TX(widget));
175
176         tx=GTK_TX(widget);
177         
178         if (tx->disp_data) { free(tx->disp_data); tx->disp_data=NULL; }
179         
180         if (GTK_WIDGET_CLASS(parent_class)->destroy) {
181                 (*GTK_WIDGET_CLASS(parent_class)->destroy) (widget);
182         }       
183 }
184
185 #define MAX_ZOOM_WIDTH 500000.0
186
187 void gtk_tx_set_data(GtkTx * tx, int16_t * wavdata, int wavsamples) {
188         g_return_if_fail(tx != NULL);
189         g_return_if_fail(GTK_IS_TX(tx));
190
191         tx->data = wavdata;
192         tx->samples = wavsamples;
193 #ifdef USE_DISPLAY_NORMALIZE    
194         tx->max_value=-1;
195 #endif
196         
197         gtk_tx_prepare(GTK_WIDGET(tx));
198         gtk_tx_update(tx);
199 }
200
201 static void gtk_tx_realize(GtkWidget * widget) {
202         GdkWindowAttr attributes;
203         gint attributes_mask;
204         GtkTx *tx;
205         
206         g_return_if_fail(widget != NULL);
207         g_return_if_fail(GTK_IS_TX(widget));
208
209         tx = GTK_TX(widget);
210         gtk_widget_set_realized(widget, TRUE);
211
212         GtkAllocation allocation;
213         gtk_widget_get_allocation(widget, &allocation);
214         attributes.x = allocation.x;
215         attributes.y = allocation.y;
216         attributes.width = allocation.width;
217         attributes.height = allocation.height;
218         attributes.wclass = GDK_INPUT_OUTPUT;
219         attributes.window_type = GDK_WINDOW_CHILD;
220         attributes.event_mask = gtk_widget_get_events(widget) | GDK_EXPOSURE_MASK;
221         attributes.visual = gtk_widget_get_visual(widget);
222         attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL;
223
224         gtk_widget_set_window(widget, gdk_window_new(gtk_widget_get_parent_window(widget), &attributes, attributes_mask));
225
226         gdk_window_set_user_data(gtk_widget_get_window(widget), widget);
227
228         if (tx->surface) {
229                 cairo_surface_destroy (tx->surface);
230         }
231         
232         tx->surface = gdk_window_create_similar_surface (gtk_widget_get_window(widget), CAIRO_CONTENT_COLOR, allocation.width, allocation.height);
233 }
234
235 static void gtk_tx_get_preferred_width (GtkWidget *widget, gint *minimal_width, gint *natural_width)
236 {
237   *minimal_width = *natural_width = TX_DEFAULT_SIZE_X;
238 }
239
240 static void gtk_tx_get_preferred_height (GtkWidget *widget, gint *minimal_height, gint *natural_height)
241 {
242     *minimal_height = *natural_height = TX_DEFAULT_SIZE_Y;
243 }
244
245 static void gtk_tx_prepare(GtkWidget * widget) {
246         int x, sample;
247         int16_t *ptr;
248         f_prec value;
249         GtkTx *tx;
250         int avg_pos, color;
251         gboolean *success;
252
253         g_return_if_fail(widget != NULL);
254         g_return_if_fail(GTK_IS_TX(widget));
255
256         tx = GTK_TX(widget);
257         
258         GtkAllocation allocation;
259         GdkRGBA midColor;
260         gboolean fg = (tx->current_fg == tx->audio_colors_focus);
261         
262         if (tx->audio_colors_focus) { 
263                 free(tx->audio_colors_focus); 
264                 free(tx->audio_colors_nofocus); 
265                 
266                 tx->audio_colors_focus = NULL; 
267                 tx->audio_colors_nofocus = NULL; 
268         } else {
269                 fg = FALSE;
270         }
271         
272         // update tx->yc
273         
274         gtk_widget_get_allocation(widget, &allocation);
275         tx->yc = allocation.height / 2;
276         
277         // allocate colors
278         
279         tx->audio_colors_focus = (GdkRGBA *) malloc(tx->yc * sizeof(GdkRGBA));
280         tx->audio_colors_nofocus = (GdkRGBA *) malloc(tx->yc * sizeof(GdkRGBA));
281         
282         success = (gboolean *) malloc(tx->yc * sizeof(gboolean));
283
284         // no focus colors
285
286         midColor.red = tx->colors[COL_BG_NO_FOCUS].red + (tx->colors[COL_FG_NO_FOCUS].red - tx->colors[COL_BG_NO_FOCUS].red) / 4;
287         midColor.green = tx->colors[COL_BG_NO_FOCUS].green + (tx->colors[COL_FG_NO_FOCUS].green - tx->colors[COL_BG_NO_FOCUS].green) / 4;
288         midColor.blue = tx->colors[COL_BG_NO_FOCUS].blue + (tx->colors[COL_FG_NO_FOCUS].blue - tx->colors[COL_BG_NO_FOCUS].blue) / 4;
289         
290         for (color=0 ; color < tx->yc; color++) {
291                 float dist = (float) color / (float) tx->yc;
292                 
293                 tx->audio_colors_nofocus[color].red = midColor.red + dist*(tx->colors[COL_FG_NO_FOCUS].red - midColor.red);
294                 tx->audio_colors_nofocus[color].green = midColor.green + dist*(tx->colors[COL_FG_NO_FOCUS].green - midColor.green);
295                 tx->audio_colors_nofocus[color].blue = midColor.blue + dist*(tx->colors[COL_FG_NO_FOCUS].blue - midColor.blue);
296                 tx->audio_colors_nofocus[color].alpha = 1.0;
297         }
298         // focus colors
299
300         midColor.red = tx->colors[COL_BG_FOCUS].red + (tx->colors[COL_FG_FOCUS].red - tx->colors[COL_BG_FOCUS].red) / 4;
301         midColor.green = tx->colors[COL_BG_FOCUS].green + (tx->colors[COL_FG_FOCUS].green - tx->colors[COL_BG_FOCUS].green) / 4;
302         midColor.blue = tx->colors[COL_BG_FOCUS].blue + (tx->colors[COL_FG_FOCUS].blue - tx->colors[COL_BG_FOCUS].blue) / 4;
303         
304         for (color=0 ; color < tx->yc; color++) {
305                 float dist = (float) color / (float) tx->yc;
306                 
307                 tx->audio_colors_focus[color].red = midColor.red + dist*(tx->colors[COL_FG_FOCUS].red - midColor.red);
308                 tx->audio_colors_focus[color].green = midColor.green + dist*(tx->colors[COL_FG_FOCUS].green - midColor.green);
309                 tx->audio_colors_focus[color].blue = midColor.blue + dist*(tx->colors[COL_FG_FOCUS].blue - midColor.blue);
310                 tx->audio_colors_focus[color].alpha = 1.0;
311         }
312         
313         if (fg) {
314                 tx->current_fg = tx->audio_colors_focus;
315         } else {
316                 tx->current_fg = tx->audio_colors_nofocus;
317         }
318         
319         // give up success
320         free(success);
321
322         if (tx->disp_data) { free(tx->disp_data); tx->disp_data=NULL; }
323
324         if (tx->data) {
325                 int max_spp=tx->samples/allocation.width;
326                 int min_spp=tx->samples/MAX_ZOOM_WIDTH;
327                 gdouble diff;
328                 
329                 if (min_spp==0) min_spp=1;
330                 
331                 diff=max_spp-min_spp;
332                 
333                 tx->spp=min_spp+diff*(1.0-tx->zoom);
334                 tx->display_width = tx->samples/tx->spp;
335                 
336 #ifdef USE_DISPLAY_NORMALIZE    
337                 tx->max_value=-1;
338 #endif          
339                 
340             tx->disp_data = (int16_t *) malloc(tx->display_width * sizeof(int16_t));
341
342             if (tx->disp_data) {                        
343 #ifdef USE_DISPLAY_NORMALIZE            
344                         if (tx->max_value==-1) {
345                                 /* We haven't figured a max value yet... */
346                                 //puts("searching max...");
347                 
348                                 for (x = 0, ptr = tx->disp_data; x < tx->display_width; ptr++, x++) {
349                                         value = 0;
350                                         for (sample = x * tx->spp, avg_pos=1; sample < (x + 1) * tx->spp; sample++) {
351                                                 value += (abs(tx->data[sample])-value)/(double) avg_pos++;
352                                         }
353                                         if (value>tx->max_value) tx->max_value=value;
354                                         tx->disp_data[x] = value; 
355                                 }
356                                 for (x = 0; x < tx->display_width; x++) {
357                                         f_prec t=tx->disp_data[x]/(double) tx->max_value;
358                                         tx->disp_data[x]=(int) (t * (f_prec) (tx->yc));
359                                 }
360                         } else {
361 #endif                          
362                                 //puts("have max...");
363                                 /* We have a max value... */
364                                 for (x = 0, ptr = tx->disp_data; x < tx->display_width; ptr++, x++) {
365                                         f_prec t;
366                                         value = 0;
367                                         for (sample = x * tx->spp, avg_pos=1; sample < (x + 1) * tx->spp; sample++) {
368                                                 value += (abs(tx->data[sample])-value)/(double) avg_pos++;
369                                         }
370 #ifdef USE_DISPLAY_NORMALIZE                                    
371                                         t=value/(double) tx->max_value;
372 #else
373                                         t=value/32768.0;
374 #endif                                  
375                                         tx->disp_data[x] = (int) (t * (f_prec) (tx->yc)); 
376                                 }
377 #ifdef USE_DISPLAY_NORMALIZE
378                         }
379 #endif                  
380                 }
381         } else {
382             tx->disp_data = NULL;
383         }
384         
385         tx->cursor_pos=-1;
386         tx->lastmute=-1;
387         
388         //tX_warning("spp: %i samples: %i width %i x %i", tx->spp, tx->samples, tx->display_width, x);
389 }
390
391 static void gtk_tx_size_allocate(GtkWidget * widget, GtkAllocation * allocation) {
392         g_return_if_fail(widget != NULL);
393         g_return_if_fail(GTK_IS_TX(widget));
394         g_return_if_fail(allocation != NULL);
395
396         gtk_widget_set_allocation(widget, allocation);
397
398         gtk_tx_prepare(widget);
399
400         if (gtk_widget_get_realized(widget)) {
401 #ifdef USE_DISPLAY_NORMALIZE            
402             GtkTx *tx = GTK_TX(widget);
403                 tx->max_value=-1;
404 #endif          
405             gdk_window_move_resize(gtk_widget_get_window(widget), allocation->x, allocation->y, allocation->width, allocation->height);
406         }
407 }
408
409 #define min(a,b) ((a) < (b) ? (a) : (b))
410 #define max(a,b) ((a) > (b) ? (a) : (b))
411
412 static gboolean gtk_tx_draw(GtkWidget * widget, cairo_t *cr) {
413         GtkTx *tx;
414         gint x;
415         GdkRectangle area;
416         
417         g_return_val_if_fail(widget != NULL, FALSE);
418         g_return_val_if_fail(GTK_IS_TX(widget), FALSE);
419
420         gdk_cairo_get_clip_rectangle(cr, &area);
421         
422         tx = GTK_TX(widget);
423         
424         cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
425         cairo_set_source_surface (cr, tx->surface, 0, 0);
426         cairo_set_line_width(cr,1);
427
428         gdk_cairo_set_source_rgba (cr, tx->current_bg);
429
430         cairo_rectangle(cr, area.x, area.y, area.width, area.height);
431         cairo_fill(cr);
432
433         if (tx->disp_data) {
434                 int max_x=area.x+area.width+1;
435
436             for (x =area.x; x < max_x; x++) {
437                         int dy = tx->disp_data[tx->display_x_offset+x];
438                         gdk_cairo_set_source_rgba (cr, &tx->current_fg[dy]);
439                         cairo_move_to (cr, x, tx->yc - dy);
440                         cairo_line_to (cr, x, tx->yc + dy+1);
441                         cairo_stroke (cr);
442             }
443         } else {
444                 GtkAllocation allocation;
445                 gtk_widget_get_allocation(widget, &allocation);
446                 
447                 gdk_cairo_set_source_rgba (cr, tx->current_fg);
448                 cairo_move_to (cr, 0, tx->yc);
449                 cairo_line_to (cr, allocation.width, tx->yc);
450                 cairo_stroke (cr);
451         }
452         
453         return FALSE;
454 }
455
456 void gtk_tx_set_zoom(GtkTx *tx, f_prec zoom) {
457         GtkWidget *wid=GTK_WIDGET(tx);
458         
459         tx->zoom=zoom;
460         gtk_tx_prepare(wid);
461         gtk_widget_queue_draw(wid);
462 }
463
464 static void gtk_tx_update(GtkTx * tx) {
465         g_return_if_fail(tx != NULL);
466         g_return_if_fail(GTK_IS_TX(tx));
467
468         gtk_widget_queue_draw(GTK_WIDGET(tx));
469 }
470
471 void gtk_tx_update_pos_display(GtkTx * tx, int sample, int mute) {
472         GtkWidget *widget;
473         GdkWindow *window;
474         cairo_t *cr;
475         
476         int x, y, yc, ymax, tmp;
477         int current_pos, current_pos_x, x_offset;
478         int force_draw=0;
479
480         /* Don't update if not required */
481
482         //current_x = sample / tx->spp + FR_SIZE;
483         current_pos = sample / tx->spp;
484         
485         if ((current_pos == tx->cursor_pos) && 
486                 (tx->lastmute == mute)) return;
487         tx->lastmute = mute;
488
489         /* speedup + easyness */
490
491         widget = GTK_WIDGET(tx);
492         window = gtk_widget_get_window(widget);
493
494         yc = tx->yc;
495         GtkAllocation allocation;
496         gtk_widget_get_allocation(widget, &allocation);
497         ymax = allocation.height-1;
498
499         /* clean up last pos */
500         
501         x = tx->cursor_x_pos;
502         
503         cr = gdk_cairo_create (gtk_widget_get_window(widget));
504         cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
505         cairo_set_source_surface (cr, tx->surface, 0, 0);
506         cairo_set_line_width(cr,1);
507         
508         if (x >= 0) {
509                 gdk_cairo_set_source_rgba (cr, tx->current_bg);
510                 
511                 cairo_move_to (cr, x, 0);
512                 cairo_line_to (cr, x, ymax);
513                 cairo_stroke (cr);
514                 
515             y = tx->disp_data[x+tx->display_x_offset];
516             gdk_cairo_set_source_rgba (cr, &tx->current_fg[y]);
517             
518                 cairo_move_to (cr, x, yc + y);
519                 cairo_line_to (cr, x, yc - y+1);
520                 cairo_stroke (cr);
521         }
522         
523         /* compute new position */
524         if (tx->zoom==0) {
525                 current_pos_x=current_pos;
526                 x_offset=0;             
527         } else {                
528                 tmp=allocation.width/2+1;
529                 
530                 if (current_pos>tmp) {
531                         x_offset=current_pos-tmp;
532                         
533                         if (x_offset+allocation.width>=tx->display_width) {
534                                 x_offset=tx->display_width-allocation.width;
535                         }
536                         
537                         current_pos_x=current_pos-x_offset;
538                 } else {
539                         x_offset=0;
540                         current_pos_x=current_pos;
541                 }
542                 
543                 if (x_offset!=tx->display_x_offset) {
544                         int x_move=tx->display_x_offset-x_offset;
545                         
546                         if (abs(x_move)<allocation.width) {
547                                 gdk_window_scroll(window, x_move, 0);
548                         } else {
549                                 /* we've moved so far that there's nothing to keep from our current display */
550                                 force_draw=1;
551                         }
552                 }
553         }
554         
555         /* store current_pos */
556
557         tx->cursor_pos = current_pos;
558         tx->cursor_x_pos = current_pos_x;
559         tx->display_x_offset = x_offset;
560         
561         /* not drawing current pos - let expose() take care of this... */
562
563         x = current_pos_x;
564
565         if (mute) gdk_cairo_set_source_rgba(cr, &tx->colors[COL_CURSOR_MUTE]);
566         else gdk_cairo_set_source_rgba(cr, &tx->colors[COL_CURSOR]);
567
568         cairo_move_to (cr, x, 0);
569         cairo_line_to (cr, x, ymax);
570         cairo_stroke (cr);
571         
572         cairo_destroy (cr);
573         
574         if (force_draw) {
575                 gtk_widget_queue_draw_area(widget, 0, 0, allocation.width, allocation.height);
576         }
577 }
578
579 void gtk_tx_cleanup_pos_display(GtkTx * tx) {
580         GtkWidget *widget;
581         GtkAllocation allocation;
582
583         widget = GTK_WIDGET(tx);
584         gtk_widget_get_allocation(widget, &allocation);
585
586         tx->display_x_offset=0;
587         tx->cursor_pos=-1;
588         tx->cursor_x_pos=-1;
589         tx->do_showframe=0;
590         //tx->current_fg=&tx->fg;
591         
592         gtk_widget_queue_draw(widget);
593 }
594
595 void gtk_tx_show_frame(GtkTx *tx, int show) {
596         if (show) {
597                 tx->current_fg=tx->audio_colors_focus;
598                 tx->current_bg=&tx->colors[COL_BG_FOCUS];
599         } else {
600                 tx->current_fg=tx->audio_colors_nofocus;
601                 tx->current_bg=&tx->colors[COL_BG_NO_FOCUS];
602         }
603         
604         gtk_widget_queue_draw(GTK_WIDGET(tx));  
605 }
606
607 f_prec gtk_tx_get_zoom(GtkTx *tx) {
608         return tx->zoom;
609 }