1 
2 /*
3  * The Real SoundTracker - GTK+ sample display widget
4  *
5  * Copyright (C) 1998-2019 Michael Krause
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 2 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, write to the Free Software
19  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20  */
21 
22 #include <stdlib.h>
23 #include <string.h>
24 
25 #include "colors.h"
26 #include "marshal.h"
27 #include "sample-display.h"
28 
29 #define XPOS_TO_OFFSET(x) (s->win_start + ((guint64)(x)) * s->win_length / s->width)
30 #define OFFSET_RANGE(l, x) (x < 0 ? 0 : (x >= l ? l - 1 : x))
31 
32 /* I (ya) hope that the mixer and system endianesses are consistent
33    and don't regard (rare?) cases when the system endianess differs
34    from that of the peripheral HW */
35 #ifdef WORDS_BIGENDIAN
36   #define ST_MIXER_FORMAT_S16 ST_MIXER_FORMAT_S16_BE
37   #define ST_MIXER_FORMAT_U16 ST_MIXER_FORMAT_U16_BE
38 #else
39   #define ST_MIXER_FORMAT_S16 ST_MIXER_FORMAT_S16_LE
40   #define ST_MIXER_FORMAT_U16 ST_MIXER_FORMAT_U16_LE
41 #endif
42 
43 
44 enum {
45     SELECTING_NOTHING = 0,
46     SELECTING_SELECTION_START,
47     SELECTING_SELECTION_END,
48     SELECTING_LOOP_START,
49     SELECTING_LOOP_END,
50     SELECTING_PAN_WINDOW,
51 };
52 
53 enum {
54     SIG_SELECTION_CHANGED,
55     SIG_LOOP_CHANGED,
56     SIG_WINDOW_CHANGED,
57     SIG_POS_CHANGED,
58     LAST_SIGNAL
59 };
60 
61 #define IS_INITIALIZED(s) (s->datalen != 0)
62 
63 static guint sample_display_signals[LAST_SIGNAL] = { 0 };
64 
65 static int sample_display_startoffset_to_xpos(SampleDisplay* s,
66     int offset);
67 static gint sample_display_idle_draw_function(SampleDisplay* s);
68 
69 G_DEFINE_TYPE(SampleDisplay, sample_display, custom_drawing_get_type());
70 
71 gint
sample_display_xpos_to_offset(SampleDisplay * s,const gint x)72 sample_display_xpos_to_offset(SampleDisplay* s, const gint x)
73 {
74     return XPOS_TO_OFFSET(x);
75 }
76 
77 static void
sample_display_idle_draw(SampleDisplay * s)78 sample_display_idle_draw(SampleDisplay* s)
79 {
80     if (!s->idle_handler) {
81         s->idle_handler = g_idle_add((GSourceFunc)sample_display_idle_draw_function,
82             (gpointer)s);
83         g_assert(s->idle_handler != 0);
84     }
85 }
86 
sample_display_enable_zero_line(SampleDisplay * s,gboolean enable)87 void sample_display_enable_zero_line(SampleDisplay* s,
88     gboolean enable)
89 {
90     s->display_zero_line = enable;
91 
92     if (s->datalen) {
93         gtk_widget_queue_draw(GTK_WIDGET(s));
94     }
95 }
96 
sample_display_set_mode(SampleDisplay * s,SampleDisplayMode mode)97 void sample_display_set_mode(SampleDisplay* s,
98     SampleDisplayMode mode)
99 {
100     s->mode = mode;
101 }
102 
103 /* Len is in samples, not bytes! */
sample_display_set_data(SampleDisplay * s,void * data,STMixerFormat type,int len,gboolean copy)104 void sample_display_set_data(SampleDisplay* s,
105     void* data,
106     STMixerFormat type,
107     int len,
108     gboolean copy)
109 {
110     g_return_if_fail(s != NULL);
111     g_return_if_fail(IS_SAMPLE_DISPLAY(s));
112 
113     if (!data || !len) {
114         s->datalen = 0;
115     } else {
116         guint byteslen = len << (mixer_get_resolution(type) + mixer_is_format_stereo(type) - 1);
117         if (copy) {
118             if (s->datacopy) {
119                 if (s->datacopylen < byteslen) {
120                     g_free(s->data);
121                     s->data = g_new(gint8, byteslen);
122                     s->datacopylen = byteslen;
123                 }
124             } else {
125                 s->data = g_new(gint8, byteslen);
126                 s->datacopylen = byteslen;
127             }
128             g_assert(s->data != NULL);
129             memcpy(s->data, data, byteslen);
130             s->datalen = len;
131         } else {
132             if (s->datacopy) {
133                 g_free(s->data);
134             }
135             s->data = data;
136             s->datalen = len;
137         }
138         s->datacopy = copy;
139         s->datatype = type;
140     }
141 
142     s->old_mixerpos = -1;
143     s->mixerpos = -1;
144 
145     s->win_start = 0;
146     s->win_length = len;
147     if (s->edit)
148         g_signal_emit(G_OBJECT(s), sample_display_signals[SIG_WINDOW_CHANGED], 0, s->win_start, s->win_start + s->win_length);
149 
150     s->sel_start = -1;
151     s->old_ss = s->old_se = -1;
152     s->selecting = 0;
153 
154     s->loop_start = -1;
155 
156     gtk_widget_queue_draw(GTK_WIDGET(s));
157 }
158 
sample_display_set_loop(SampleDisplay * s,int start,int end)159 void sample_display_set_loop(SampleDisplay* s,
160     int start,
161     int end)
162 {
163     g_return_if_fail(s != NULL);
164     g_return_if_fail(IS_SAMPLE_DISPLAY(s));
165 
166     if (!s->edit || !IS_INITIALIZED(s))
167         return;
168 
169     if (s->loop_start == start && s->loop_end == end)
170         return;
171 
172     g_return_if_fail(start >= -1 && start < s->datalen);
173     g_return_if_fail(end > 0 && end <= s->datalen);
174     g_return_if_fail(end > start);
175 
176     s->loop_start = start;
177     s->loop_end = end;
178 
179     gtk_widget_queue_draw(GTK_WIDGET(s));
180     g_signal_emit(G_OBJECT(s), sample_display_signals[SIG_LOOP_CHANGED], 0, start, end);
181 }
182 
sample_display_set_selection(SampleDisplay * s,int start,int end)183 void sample_display_set_selection(SampleDisplay* s,
184     int start,
185     int end)
186 {
187     g_return_if_fail(s != NULL);
188     g_return_if_fail(IS_SAMPLE_DISPLAY(s));
189 
190     if (!s->edit || !IS_INITIALIZED(s))
191         return;
192 
193     g_return_if_fail(start >= -1 && start < s->datalen);
194     g_return_if_fail(end >= 1 && end <= s->datalen);
195     g_return_if_fail(end > start);
196 
197     s->sel_start = start;
198     s->sel_end = end;
199 
200     sample_display_idle_draw(s);
201     g_signal_emit(G_OBJECT(s), sample_display_signals[SIG_SELECTION_CHANGED], 0, start, end);
202 }
203 
sample_display_set_mixer_position(SampleDisplay * s,int offset)204 void sample_display_set_mixer_position(SampleDisplay* s,
205     int offset)
206 {
207     g_return_if_fail(s != NULL);
208     g_return_if_fail(IS_SAMPLE_DISPLAY(s));
209 
210     if (!s->edit || !IS_INITIALIZED(s))
211         return;
212 
213     if (offset != s->mixerpos) {
214         s->mixerpos = offset;
215         sample_display_idle_draw(s);
216     }
217 }
218 
219 static void
sample_display_set_window_full(SampleDisplay * s,int start,int end,gboolean sig_pos_changed)220 sample_display_set_window_full(SampleDisplay *s,
221    int start,
222    int end,
223    gboolean sig_pos_changed)
224 {
225     g_return_if_fail(s != NULL);
226     g_return_if_fail(IS_SAMPLE_DISPLAY(s));
227     g_return_if_fail(start >= 0 && start < s->datalen);
228     g_return_if_fail(end < 0 || (end > 0 && end <= s->datalen));
229     g_return_if_fail(end < 0 || end > start);
230 
231     s->win_start = start;
232     if (end > 0) {
233         s->win_length = end - start;
234         if (s->edit)
235             g_signal_emit(G_OBJECT(s), sample_display_signals[SIG_WINDOW_CHANGED], 0, start, end);
236     } else if (sig_pos_changed)
237         g_signal_emit(G_OBJECT(s), sample_display_signals[SIG_POS_CHANGED], 0, start);
238 
239     gtk_widget_queue_draw(GTK_WIDGET(s));
240 }
241 
242 void
sample_display_set_window(SampleDisplay * s,int start,int end)243 sample_display_set_window(SampleDisplay *s,
244    int start,
245    int end)
246 {
247     sample_display_set_window_full(s, start, end, FALSE);
248 }
249 
250 static void
sample_display_init_display(SampleDisplay * s,int w,int h)251 sample_display_init_display(SampleDisplay* s,
252     int w,
253     int h)
254 {
255     s->width = w;
256     s->height = h;
257 }
258 
259 static void
sample_display_size_allocate(GtkWidget * widget,GtkAllocation * allocation)260 sample_display_size_allocate(GtkWidget* widget,
261     GtkAllocation* allocation)
262 {
263     SampleDisplay* s;
264 
265     g_return_if_fail(widget != NULL);
266     g_return_if_fail(IS_SAMPLE_DISPLAY(widget));
267     g_return_if_fail(allocation != NULL);
268 
269     widget->allocation = *allocation;
270     s = SAMPLE_DISPLAY(widget);
271 
272     if (allocation->width > s->seg_allocated) {
273         s->seg_allocated = allocation->width + (allocation->width >> 1);
274 
275         if (!s->segments)
276             s->segments = g_new(DISegment, s->seg_allocated);
277         else
278             s->segments = g_renew(DISegment, s->segments, s->seg_allocated);
279     }
280 
281     if (gtk_widget_get_realized(widget)) {
282         gdk_window_move_resize(widget->window,
283             allocation->x, allocation->y,
284             allocation->width, allocation->height);
285 
286         sample_display_init_display(s, allocation->width, allocation->height);
287     }
288     (*GTK_WIDGET_CLASS(sample_display_parent_class)->size_allocate)(widget, allocation);
289 }
290 
291 static void
sample_display_realize(GtkWidget * widget)292 sample_display_realize(GtkWidget* widget)
293 {
294     GdkWindowAttr attributes;
295     gint attributes_mask;
296     SampleDisplay* s;
297 
298     g_return_if_fail(widget != NULL);
299     g_return_if_fail(IS_SAMPLE_DISPLAY(widget));
300 
301     gtk_widget_set_realized(widget, TRUE);
302     s = SAMPLE_DISPLAY(widget);
303 
304     attributes.x = widget->allocation.x;
305     attributes.y = widget->allocation.y;
306     attributes.width = widget->allocation.width;
307     attributes.height = widget->allocation.height;
308     attributes.wclass = GDK_INPUT_OUTPUT;
309     attributes.window_type = GDK_WINDOW_CHILD;
310     attributes.event_mask = gtk_widget_get_events(widget)
311         | GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
312         | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK;
313 
314     attributes.visual = gtk_widget_get_visual(widget);
315     attributes.colormap = gtk_widget_get_colormap(widget);
316 
317     attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
318     widget->window = gdk_window_new(widget->parent->window, &attributes, attributes_mask);
319 
320     widget->style = gtk_style_attach(widget->style, widget->window);
321 
322     if (!s->bg_gc) {
323         s->bg_gc = colors_get_gc(COLOR_BG);
324         colors_add_widget(COLOR_BG, widget);
325         s->fg_gc = colors_get_gc(COLOR_NOTES);
326         s->zeroline_gc = colors_get_gc(COLOR_ZEROLINE);
327         if (s->edit) {
328             s->loop_gc = colors_get_gc(COLOR_CHANNUMS);
329             s->mixerpos_gc = colors_get_gc(COLOR_MIXERPOS);
330         }
331     }
332 
333     sample_display_init_display(s, attributes.width, attributes.height);
334 
335     gdk_window_set_user_data(widget->window, widget);
336     (*GTK_WIDGET_CLASS(sample_display_parent_class)->realize)(widget);
337 }
338 
339 static void
sample_display_size_request(GtkWidget * widget,GtkRequisition * requisition)340 sample_display_size_request(GtkWidget* widget,
341     GtkRequisition* requisition)
342 {
343     requisition->width = 10;
344     requisition->height = 32;
345 }
346 
347 static inline gint32
normalize(const SampleDisplay * s,const guint offset,const gint format)348 normalize(const SampleDisplay* s,
349     const guint offset,
350     const gint format)
351 {
352     gint32 c;
353     const gint sh = s->height;
354     /* s->datalen is in frames, for stereo formats each frame contains 2 samples */
355     const guint efflen = s->datalen << mixer_is_format_stereo(format);
356 
357     /* Since format is a constant, this should be expanded to a single case
358        by compiler optimization */
359     switch (format) {
360     case ST_MIXER_FORMAT_S16:
361         c = ((gint16*)s->data)[OFFSET_RANGE(efflen, offset)];
362         c = ((32767 - c) * sh) >> 16;
363         break;
364     case ST_MIXER_FORMAT_U16:
365         c = ((gint16*)s->data)[OFFSET_RANGE(efflen, offset)];
366         c = ((65535 - c) * sh) >> 16;
367         break;
368     case ST_MIXER_FORMAT_S8:
369         c = ((gint8*)s->data)[OFFSET_RANGE(efflen, offset)];
370         c = ((127 - c) * sh) >> 8;
371         break;
372     case ST_MIXER_FORMAT_U8:
373         c = ((gint8*)s->data)[OFFSET_RANGE(efflen, offset)];
374         c = ((255 - c) * sh) >> 8;
375         break;
376     /* Average left and right channels if stereo */
377     case ST_MIXER_FORMAT_S16 | ST_MIXER_FORMAT_STEREO:
378         c = (((gint16*)s->data)[OFFSET_RANGE(efflen, offset)]
379             + ((gint16*)s->data)[OFFSET_RANGE(efflen, offset + 1)]) >> 1;
380         c = ((32767 - c) * sh) >> 16;
381         break;
382     case ST_MIXER_FORMAT_U16 | ST_MIXER_FORMAT_STEREO:
383         c = (((gint16*)s->data)[OFFSET_RANGE(efflen, offset)]
384             + ((gint16*)s->data)[OFFSET_RANGE(efflen, offset + 1)]) >> 1;
385         c = ((65535 - c) * sh) >> 16;
386         break;
387     case ST_MIXER_FORMAT_S8 | ST_MIXER_FORMAT_STEREO:
388         c = (((gint8*)s->data)[OFFSET_RANGE(efflen, offset)]
389             + ((gint8*)s->data)[OFFSET_RANGE(efflen, offset + 1)]) >> 1;
390         c = ((127 - c) * sh) >> 8;
391         break;
392     case ST_MIXER_FORMAT_U8 | ST_MIXER_FORMAT_STEREO:
393         c = (((gint8*)s->data)[OFFSET_RANGE(efflen, offset)]
394             + ((gint8*)s->data)[OFFSET_RANGE(efflen, offset + 1)]) >> 1;
395         c = ((255 - c) * sh) >> 8;
396         break;
397     default:
398         c = 0;
399     }
400 
401     return c;
402 }
403 
404 static inline gint
sample_display_prepare_data_strobo_do(const SampleDisplay * s,gint x,const gint width,const gint type)405 sample_display_prepare_data_strobo_do(const SampleDisplay* s,
406     gint x,
407     const gint width,
408     const gint type)
409 {
410     guint32 c, d;
411     guint i;
412     gint prev_x = (x > 0 ? x - 1 : 0);
413     const gboolean is_stereo = type & ST_MIXER_FORMAT_STEREO;
414 
415     c = normalize(s,
416         is_stereo ? XPOS_TO_OFFSET(prev_x) << 1 : XPOS_TO_OFFSET(prev_x),
417         type);
418 
419     for (i = 0; i < width; i++, x++, c = d) {
420         d = normalize(s,
421             is_stereo ? XPOS_TO_OFFSET(x) << 1 : XPOS_TO_OFFSET(x),
422             type);
423         s->segments[i].x1 = x - 1;
424         s->segments[i].y1 = c;
425         s->segments[i].x2 = x;
426         s->segments[i].y2 = d;
427     }
428 
429     return width;
430 }
431 
432 static gint
sample_display_prepare_data_strobo(const SampleDisplay * s,gint x,const gint width)433 sample_display_prepare_data_strobo(const SampleDisplay* s,
434     gint x,
435     const gint width)
436 {
437     gint nseg = width;
438 
439     g_assert(s->segments != NULL);
440 
441     switch (s->datatype) {
442     case ST_MIXER_FORMAT_S16:
443         sample_display_prepare_data_strobo_do(s, x, width, ST_MIXER_FORMAT_S16);
444         break;
445     case ST_MIXER_FORMAT_U16:
446         sample_display_prepare_data_strobo_do(s, x, width, ST_MIXER_FORMAT_U16);
447         break;
448     case ST_MIXER_FORMAT_S8:
449         sample_display_prepare_data_strobo_do(s, x, width, ST_MIXER_FORMAT_S8);
450         break;
451     case ST_MIXER_FORMAT_U8:
452         sample_display_prepare_data_strobo_do(s, x, width, ST_MIXER_FORMAT_U8);
453         break;
454     case ST_MIXER_FORMAT_S16 | ST_MIXER_FORMAT_STEREO:
455         sample_display_prepare_data_strobo_do(s, x, width, ST_MIXER_FORMAT_S16 | ST_MIXER_FORMAT_STEREO);
456         break;
457     case ST_MIXER_FORMAT_U16 | ST_MIXER_FORMAT_STEREO:
458         sample_display_prepare_data_strobo_do(s, x, width, ST_MIXER_FORMAT_U16 | ST_MIXER_FORMAT_STEREO);
459         break;
460     case ST_MIXER_FORMAT_S8 | ST_MIXER_FORMAT_STEREO:
461         sample_display_prepare_data_strobo_do(s, x, width, ST_MIXER_FORMAT_S8 | ST_MIXER_FORMAT_STEREO);
462         break;
463     case ST_MIXER_FORMAT_U8 | ST_MIXER_FORMAT_STEREO:
464         sample_display_prepare_data_strobo_do(s, x, width, ST_MIXER_FORMAT_U8 | ST_MIXER_FORMAT_STEREO);
465         break;
466     default:
467         nseg = 0;
468         break; /* Draw nothing on unknown format */
469     }
470     return nseg;
471 }
472 
473 static inline guint
sample_display_prepare_data_minmax_do(const SampleDisplay * s,gint x,const gint width,const gint type)474 sample_display_prepare_data_minmax_do(const SampleDisplay* s,
475     gint x,
476     const gint width,
477     const gint type)
478 {
479     guint32 i, offs;
480     gint32 c, d;
481     const gboolean is_stereo = type & ST_MIXER_FORMAT_STEREO;
482 
483     offs = XPOS_TO_OFFSET(x);
484     /* Take the previous offset for consistency */
485     offs = (offs == 0 ? 0 : offs - 1);
486     c = d = normalize(s, is_stereo ? offs << 1 : offs, type);
487     for (i = 0; i < width; i++, x++) {
488         gint32 tmp;
489         guint32 endoffs = XPOS_TO_OFFSET(x + 1);
490 
491         for(; offs < endoffs; offs++) {
492             gint32 curval = normalize(s, is_stereo ? offs << 1 : offs, type);
493 
494             if (curval < c)
495                 c = curval;
496             if (curval > d)
497                 d = curval;
498         }
499 
500         s->segments[i].x1 = x;
501         s->segments[i].y1 = c;
502         s->segments[i].x2 = x;
503         s->segments[i].y2 = d;
504 
505         offs = endoffs;
506         tmp = d + 1;
507         d = c - 1;
508         c = tmp;
509     }
510     return width;
511 }
512 
513 static guint
sample_display_prepare_data_minmax(const SampleDisplay * s,gint x,gint width)514 sample_display_prepare_data_minmax(const SampleDisplay* s,
515     gint x,
516     gint width)
517 {
518     guint nsegs;
519 
520     if (s->width >= s->win_length)
521         return sample_display_prepare_data_strobo(s, x, width);
522 
523     switch (s->datatype) {
524     case ST_MIXER_FORMAT_S16:
525         nsegs = sample_display_prepare_data_minmax_do(s, x, width, ST_MIXER_FORMAT_S16);
526         break;
527     case ST_MIXER_FORMAT_U16:
528         nsegs = sample_display_prepare_data_minmax_do(s, x, width, ST_MIXER_FORMAT_U16);
529         break;
530     case ST_MIXER_FORMAT_S8:
531         nsegs = sample_display_prepare_data_minmax_do(s, x, width, ST_MIXER_FORMAT_S8);
532         break;
533     case ST_MIXER_FORMAT_U8:
534         nsegs = sample_display_prepare_data_minmax_do(s, x, width, ST_MIXER_FORMAT_U8);
535         break;
536     case ST_MIXER_FORMAT_S16 | ST_MIXER_FORMAT_STEREO:
537         nsegs = sample_display_prepare_data_minmax_do(s, x, width, ST_MIXER_FORMAT_S16 | ST_MIXER_FORMAT_STEREO);
538         break;
539     case ST_MIXER_FORMAT_U16 | ST_MIXER_FORMAT_STEREO:
540         nsegs = sample_display_prepare_data_minmax_do(s, x, width, ST_MIXER_FORMAT_U16 | ST_MIXER_FORMAT_STEREO);
541         break;
542     case ST_MIXER_FORMAT_S8 | ST_MIXER_FORMAT_STEREO:
543         nsegs = sample_display_prepare_data_minmax_do(s, x, width, ST_MIXER_FORMAT_S8 | ST_MIXER_FORMAT_STEREO);
544         break;
545     case ST_MIXER_FORMAT_U8 | ST_MIXER_FORMAT_STEREO:
546         nsegs = sample_display_prepare_data_minmax_do(s, x, width, ST_MIXER_FORMAT_U8 | ST_MIXER_FORMAT_STEREO);
547         break;
548     default:
549         nsegs = 0;
550     }
551     return nsegs;
552 }
553 
554 static void
sample_display_draw_data(DIDrawable win,const SampleDisplay * s,int color,int x,int width)555 sample_display_draw_data(DIDrawable win,
556     const SampleDisplay* s,
557     int color,
558     int x,
559     int width)
560 {
561     DIGC gc;
562     gint nseg;
563 
564     if (width == 0)
565         return;
566 
567     g_return_if_fail(x >= 0);
568     g_return_if_fail(x + width <= s->width);
569 
570     di_draw_rectangle(win, color ? s->fg_gc : s->bg_gc,
571         TRUE, x, 0, width, s->height);
572 
573     if (s->display_zero_line) {
574         di_draw_line(win, s->zeroline_gc, x, s->height / 2, x + width - 1, s->height / 2);
575     }
576 
577     gc = color ? s->bg_gc : s->fg_gc;
578 
579     switch(s->mode) {
580     case SAMPLE_DISPLAY_MODE_STROBO:
581         nseg = sample_display_prepare_data_strobo(s, x, width);
582         break;
583     case SAMPLE_DISPLAY_MODE_MINMAX:
584         nseg = sample_display_prepare_data_minmax(s, x, width);
585         break;
586     default:
587         nseg = 0; /* Unknown display mode, just draw nothing */
588         break;
589     }
590     di_draw_segments(win, gc, s->segments, nseg);
591 }
592 
593 static int
sample_display_startoffset_to_xpos(SampleDisplay * s,int offset)594 sample_display_startoffset_to_xpos(SampleDisplay* s,
595     int offset)
596 {
597     gint64 d = offset - s->win_start;
598 
599     if (d < 0)
600         return 0;
601     if (d >= s->win_length)
602         return s->width;
603 
604     return d * s->width / s->win_length;
605 }
606 
607 static int
sample_display_endoffset_to_xpos(SampleDisplay * s,int offset)608 sample_display_endoffset_to_xpos(SampleDisplay* s,
609     int offset)
610 {
611     if (s->win_length < s->width) {
612         return sample_display_startoffset_to_xpos(s, offset);
613     } else {
614         gint64 d = offset - s->win_start;
615         int l = (1 - s->win_length) / s->width;
616 
617         /* you get these tests by setting the complete formula below equal to 0 or s->width, respectively,
618 	   and then resolving towards d. */
619         if (d < l)
620             return 0;
621         if (d > s->win_length + l)
622             return s->width;
623 
624         return (d * s->width + s->win_length - 1) / s->win_length;
625     }
626 }
627 
628 static void
sample_display_do_marker_line(DIDrawable win,SampleDisplay * s,int endoffset,int offset,int x_min,int x_max,DIGC gc)629 sample_display_do_marker_line(DIDrawable win,
630     SampleDisplay* s,
631     int endoffset,
632     int offset,
633     int x_min,
634     int x_max,
635     DIGC gc)
636 {
637     int x;
638 
639     if (offset >= s->win_start && offset <= s->win_start + s->win_length) {
640         if (!endoffset)
641             x = sample_display_startoffset_to_xpos(s, offset);
642         else
643             x = sample_display_endoffset_to_xpos(s, offset);
644         if (x + 3 >= x_min && x - 3 < x_max) {
645             di_draw_line(win, gc, x, 0, x, s->height);
646             di_draw_rectangle(win, gc, TRUE,
647                 x - 3, 0, 7, 10);
648             di_draw_rectangle(win, gc, TRUE,
649                 x - 3, s->height - 10, 7, 10);
650         }
651     }
652 }
653 
654 static gboolean
sample_display_draw_main(GtkWidget * widget,GdkRectangle * area)655 sample_display_draw_main(GtkWidget* widget,
656     GdkRectangle* area)
657 {
658     SampleDisplay* s = SAMPLE_DISPLAY(widget);
659     DIDrawable drw = custom_drawing_get_drawable(CUSTOM_DRAWING(widget));
660     GdkEventExpose event;
661     int x, x2;
662 
663     g_return_val_if_fail(area->x >= 0, FALSE);
664 
665     if (area->width == 0)
666         return FALSE;
667 
668     if (area->x + area->width > s->width)
669         return FALSE;
670 
671     if (!IS_INITIALIZED(s)) {
672         di_draw_rectangle(drw, s->bg_gc,
673             TRUE, area->x, area->y, area->width, area->height);
674         di_draw_line(drw, s->fg_gc,
675             area->x, s->height / 2,
676             area->x + area->width - 1, s->height / 2);
677     } else {
678         const int x_min = area->x;
679         const int x_max = area->x + area->width;
680 
681         if (s->sel_start != -1) {
682             /* draw the part to the left of the selection */
683             x = sample_display_startoffset_to_xpos(s, s->sel_start);
684             x = MIN(x_max, MAX(x_min, x));
685             sample_display_draw_data(drw, s, 0, x_min, x - x_min);
686 
687             /* draw the selection */
688             x2 = sample_display_endoffset_to_xpos(s, s->sel_end);
689             x2 = MIN(x_max, MAX(x_min, x2));
690             sample_display_draw_data(drw, s, 1, x, x2 - x);
691         } else {
692             /* we don't have a selection */
693             x2 = x_min;
694         }
695 
696         /* draw the part to the right of the selection */
697         sample_display_draw_data(drw, s, 0, x2, x_max - x2);
698 
699         if (s->loop_start != -1) {
700             sample_display_do_marker_line(drw, s, 0, s->loop_start, x_min, x_max, s->loop_gc);
701             sample_display_do_marker_line(drw, s, 1, s->loop_end, x_min, x_max, s->loop_gc);
702         }
703 
704         if (s->mixerpos != -1) {
705             sample_display_do_marker_line(drw, s, 0, s->mixerpos, x_min, x_max, s->mixerpos_gc);
706             s->old_mixerpos = s->mixerpos;
707         }
708     }
709 
710     event.area = *area;
711     /* We need to redo explicitly the custom drawing stuff */
712     return (*GTK_WIDGET_CLASS(sample_display_parent_class)->expose_event)(widget, &event);
713 }
714 
715 static void
sample_display_draw_update(GtkWidget * widget,GdkRectangle * area)716 sample_display_draw_update(GtkWidget* widget,
717     GdkRectangle* area)
718 {
719     SampleDisplay* s = SAMPLE_DISPLAY(widget);
720     GdkRectangle area2 = { 0, 0, 0, s->height };
721     int x, i;
722     const int x_min = area->x;
723     const int x_max = area->x + area->width;
724     gboolean special_draw = FALSE;
725 
726     if (s->mixerpos != s->old_mixerpos) {
727         /* Redraw area of old position, redraw area of new position. */
728         for (i = 0; i < 2; i++) {
729             if (s->old_mixerpos >= s->win_start && s->old_mixerpos < s->win_start + s->win_length) {
730                 x = sample_display_startoffset_to_xpos(s, s->old_mixerpos);
731                 area2.x = MIN(x_max - 1, MAX(x_min, x - 3));
732                 area2.width = 7;
733                 if (area2.x + area2.width > x_max) {
734                     area2.width = x_max - area2.x;
735                 }
736                 sample_display_draw_main(widget, &area2);
737             }
738             s->old_mixerpos = s->mixerpos;
739         }
740         special_draw = TRUE;
741     }
742 
743     if (s->sel_start != s->old_ss || s->sel_end != s->old_se) {
744         if (s->sel_start == -1 || s->old_ss == -1) {
745             sample_display_draw_main(widget, area);
746         } else {
747             if (s->sel_start < s->old_ss) {
748                 /* repaint left additional side */
749                 x = sample_display_startoffset_to_xpos(s, s->sel_start);
750                 area2.x = MIN(x_max, MAX(x_min, x));
751                 x = sample_display_startoffset_to_xpos(s, s->old_ss);
752                 area2.width = MIN(x_max, MAX(x_min, x)) - area2.x;
753             } else {
754                 /* repaint left removed side */
755                 x = sample_display_startoffset_to_xpos(s, s->old_ss);
756                 area2.x = MIN(x_max, MAX(x_min, x));
757                 x = sample_display_startoffset_to_xpos(s, s->sel_start);
758                 area2.width = MIN(x_max, MAX(x_min, x)) - area2.x;
759             }
760             sample_display_draw_main(widget, &area2);
761 
762             if (s->sel_end < s->old_se) {
763                 /* repaint right removed side */
764                 x = sample_display_endoffset_to_xpos(s, s->sel_end);
765                 area2.x = MIN(x_max, MAX(x_min, x));
766                 x = sample_display_endoffset_to_xpos(s, s->old_se);
767                 area2.width = MIN(x_max, MAX(x_min, x)) - area2.x;
768             } else {
769                 /* repaint right additional side */
770                 x = sample_display_endoffset_to_xpos(s, s->old_se);
771                 area2.x = MIN(x_max, MAX(x_min, x));
772                 x = sample_display_endoffset_to_xpos(s, s->sel_end);
773                 area2.width = MIN(x_max, MAX(x_min, x)) - area2.x;
774             }
775             sample_display_draw_main(widget, &area2);
776         }
777 
778         s->old_ss = s->sel_start;
779         s->old_se = s->sel_end;
780         special_draw = TRUE;
781     }
782 
783     if (!special_draw) {
784         sample_display_draw_main(widget, area);
785     }
786 }
787 
788 static gint
sample_display_expose(GtkWidget * widget,GdkEventExpose * event)789 sample_display_expose(GtkWidget* widget,
790     GdkEventExpose* event)
791 {
792     return sample_display_draw_main(widget, &event->area);
793 }
794 
795 static gint
sample_display_idle_draw_function(SampleDisplay * s)796 sample_display_idle_draw_function(SampleDisplay* s)
797 {
798     GdkRectangle area = { 0, 0, s->width, s->height };
799 
800     if (gtk_widget_get_mapped(GTK_WIDGET(s))) {
801         sample_display_draw_update(GTK_WIDGET(s), &area);
802     }
803 
804     s->idle_handler = 0;
805     return FALSE;
806 }
807 
808 static void
sample_display_handle_motion(SampleDisplay * s,int x,int y,int just_clicked)809 sample_display_handle_motion(SampleDisplay* s,
810     int x,
811     int y,
812     int just_clicked)
813 {
814     int ol, or ;
815     int ss = s->sel_start, se = s->sel_end;
816     int ls = s->loop_start, le = s->loop_end;
817 
818     if (!s->selecting)
819         return;
820 
821     if (x < 0)
822         x = 0;
823     else if (x >= s->width)
824         x = s->width - 1;
825 
826     ol = XPOS_TO_OFFSET(x);
827     if (s->win_length < s->width) {
828         or = XPOS_TO_OFFSET(x) + 1;
829     } else {
830         or = XPOS_TO_OFFSET(x + 1);
831     }
832 
833     g_return_if_fail(ol >= 0 && ol < s->datalen);
834     g_return_if_fail(or > 0 && or <= s->datalen);
835     g_return_if_fail(ol < or);
836 
837     switch (s->selecting) {
838     case SELECTING_SELECTION_START:
839         if (just_clicked) {
840             if (ss != -1 && ol < se) {
841                 ss = ol;
842             } else {
843                 ss = ol;
844                 se = ol + 1;
845             }
846         } else {
847             if (ol < se) {
848                 ss = ol;
849             } else {
850                 ss = se - 1;
851                 se = or ;
852                 s->selecting = SELECTING_SELECTION_END;
853             }
854         }
855         break;
856     case SELECTING_SELECTION_END:
857         if (just_clicked) {
858             if (ss != -1 && or > ss) {
859                 se = or ;
860             } else {
861                 ss = or -1;
862                 se = or ;
863             }
864         } else {
865             if (or > ss) {
866                 se = or ;
867             } else {
868                 se = ss + 1;
869                 ss = ol;
870                 s->selecting = SELECTING_SELECTION_START;
871             }
872         }
873         break;
874     case SELECTING_LOOP_START:
875         if (ol < s->loop_end)
876             ls = ol;
877         else
878             ls = le - 1;
879         break;
880     case SELECTING_LOOP_END:
881         if (or > s->loop_start)
882             le = or ;
883         else
884             le = ls + 1;
885         break;
886     default:
887         g_assert_not_reached();
888         break;
889     }
890 
891     if (s->sel_start != ss || s->sel_end != se) {
892         s->sel_start = ss;
893         s->sel_end = se;
894         sample_display_idle_draw(s);
895         g_signal_emit(G_OBJECT(s), sample_display_signals[SIG_SELECTION_CHANGED], 0, ss, se);
896     }
897 
898     if (s->loop_start != ls || s->loop_end != le) {
899         s->loop_start = ls;
900         s->loop_end = le;
901         sample_display_idle_draw(s);
902         g_signal_emit(G_OBJECT(s), sample_display_signals[SIG_LOOP_CHANGED], 0, ls, le);
903     }
904 }
905 
906 // Handle middle mousebutton display window panning
907 static void
sample_display_handle_motion_2(SampleDisplay * s,int x,int y)908 sample_display_handle_motion_2(SampleDisplay* s,
909     int x,
910     int y)
911 {
912     int new_win_start = s->selecting_wins0 + (s->selecting_x0 - x) * s->win_length / s->width;
913 
914     new_win_start = CLAMP(new_win_start, 0, s->datalen - s->win_length);
915 
916     if (new_win_start != s->win_start) {
917         sample_display_set_window_full(s, new_win_start, -1, TRUE);
918     }
919 }
920 
921 static gint
sample_display_button_press(GtkWidget * widget,GdkEventButton * event)922 sample_display_button_press(GtkWidget* widget,
923     GdkEventButton* event)
924 {
925     SampleDisplay* s;
926     int x, y;
927     GdkModifierType state;
928 
929     g_return_val_if_fail(widget != NULL, FALSE);
930     g_return_val_if_fail(IS_SAMPLE_DISPLAY(widget), FALSE);
931     g_return_val_if_fail(event != NULL, FALSE);
932 
933     s = SAMPLE_DISPLAY(widget);
934 
935     if (!s->edit)
936         return FALSE;
937 
938     if (!IS_INITIALIZED(s))
939         return TRUE;
940 
941     if (s->selecting && event->button != s->button) {
942         s->selecting = 0;
943     } else {
944         s->button = event->button;
945         gdk_window_get_pointer(event->window, &x, &y, &state);
946         if (!(state & GDK_SHIFT_MASK)) {
947             if (s->button == 1) {
948                 s->selecting = SELECTING_SELECTION_START;
949             } else if (s->button == 2) {
950                 s->selecting = SELECTING_PAN_WINDOW;
951                 gdk_window_get_pointer(event->window, &s->selecting_x0, NULL, NULL);
952                 s->selecting_wins0 = s->win_start;
953             } else if (s->button == 3) {
954                 s->selecting = SELECTING_SELECTION_END;
955             }
956         } else {
957             if (s->loop_start != -1) {
958                 if (s->button == 1) {
959                     s->selecting = SELECTING_LOOP_START;
960                 } else if (s->button == 3) {
961                     s->selecting = SELECTING_LOOP_END;
962                 }
963             }
964         }
965         if (!s->selecting)
966             return TRUE;
967         if (s->selecting != SELECTING_PAN_WINDOW)
968             sample_display_handle_motion(s, x, y, 1);
969     }
970 
971     return TRUE;
972 }
973 
974 static gint
sample_display_button_release(GtkWidget * widget,GdkEventButton * event)975 sample_display_button_release(GtkWidget* widget,
976     GdkEventButton* event)
977 {
978     SampleDisplay* s;
979 
980     g_return_val_if_fail(widget != NULL, FALSE);
981     g_return_val_if_fail(IS_SAMPLE_DISPLAY(widget), FALSE);
982     g_return_val_if_fail(event != NULL, FALSE);
983 
984     s = SAMPLE_DISPLAY(widget);
985 
986     if (!s->edit)
987         return FALSE;
988 
989     if (s->selecting && event->button == s->button) {
990         s->selecting = 0;
991     }
992 
993     return TRUE;
994 }
995 
996 static gint
sample_display_motion_notify(GtkWidget * widget,GdkEventMotion * event)997 sample_display_motion_notify(GtkWidget* widget,
998     GdkEventMotion* event)
999 {
1000     SampleDisplay* s;
1001     int x, y;
1002     GdkModifierType state;
1003 
1004     s = SAMPLE_DISPLAY(widget);
1005 
1006     if (!s->edit)
1007         return FALSE;
1008 
1009     if (!IS_INITIALIZED(s) || !s->selecting)
1010         return TRUE;
1011 
1012     if (event->is_hint) {
1013         gdk_window_get_pointer(event->window, &x, &y, &state);
1014     } else {
1015         x = event->x;
1016         y = event->y;
1017         state = event->state;
1018     }
1019 
1020     if (((state & GDK_BUTTON1_MASK) && s->button == 1) || ((state & GDK_BUTTON3_MASK) && s->button == 3)) {
1021         sample_display_handle_motion(SAMPLE_DISPLAY(widget), x, y, 0);
1022     } else if ((state & GDK_BUTTON2_MASK) && s->button == 2) {
1023         sample_display_handle_motion_2(SAMPLE_DISPLAY(widget), x, y);
1024     } else {
1025         s->selecting = 0;
1026     }
1027 
1028     return TRUE;
1029 }
1030 
1031 static void
sample_display_class_init(SampleDisplayClass * class)1032 sample_display_class_init(SampleDisplayClass* class)
1033 {
1034     GObjectClass* object_class;
1035     GtkWidgetClass* widget_class;
1036 
1037     object_class = G_OBJECT_CLASS(class);
1038     widget_class = GTK_WIDGET_CLASS(class);
1039 
1040     widget_class->realize = sample_display_realize;
1041     widget_class->size_allocate = sample_display_size_allocate;
1042     widget_class->expose_event = sample_display_expose;
1043     widget_class->size_request = sample_display_size_request;
1044     widget_class->button_press_event = sample_display_button_press;
1045     widget_class->button_release_event = sample_display_button_release;
1046     widget_class->motion_notify_event = sample_display_motion_notify;
1047 
1048     sample_display_signals[SIG_SELECTION_CHANGED] = g_signal_new("selection_changed",
1049         G_TYPE_FROM_CLASS(object_class),
1050         (GSignalFlags)G_SIGNAL_RUN_FIRST,
1051         G_STRUCT_OFFSET(SampleDisplayClass, selection_changed),
1052         NULL, NULL,
1053         __marshal_VOID__INT_INT,
1054         G_TYPE_NONE, 2,
1055         G_TYPE_INT, G_TYPE_INT);
1056     sample_display_signals[SIG_LOOP_CHANGED] = g_signal_new("loop_changed",
1057         G_TYPE_FROM_CLASS(object_class),
1058         (GSignalFlags)G_SIGNAL_RUN_FIRST,
1059         G_STRUCT_OFFSET(SampleDisplayClass, loop_changed),
1060         NULL, NULL,
1061         __marshal_VOID__INT_INT,
1062         G_TYPE_NONE, 2,
1063         G_TYPE_INT, G_TYPE_INT);
1064     sample_display_signals[SIG_WINDOW_CHANGED] = g_signal_new("window_changed",
1065         G_TYPE_FROM_CLASS(object_class),
1066         (GSignalFlags)G_SIGNAL_RUN_FIRST,
1067         G_STRUCT_OFFSET(SampleDisplayClass, window_changed),
1068         NULL, NULL,
1069         __marshal_VOID__INT_INT,
1070         G_TYPE_NONE, 2,
1071         G_TYPE_INT, G_TYPE_INT);
1072     sample_display_signals[SIG_POS_CHANGED] =
1073         g_signal_new ("position_changed",
1074         G_TYPE_FROM_CLASS (object_class),
1075         (GSignalFlags)G_SIGNAL_RUN_FIRST,
1076         G_STRUCT_OFFSET(SampleDisplayClass, position_changed),
1077         NULL, NULL,
1078         g_cclosure_marshal_VOID__INT,
1079         G_TYPE_NONE, 1,
1080         G_TYPE_INT);
1081 
1082     class->selection_changed = NULL;
1083     class->loop_changed = NULL;
1084     class->window_changed = NULL;
1085     class->position_changed = NULL;
1086 }
1087 
1088 static void
sample_display_init(SampleDisplay * s)1089 sample_display_init(SampleDisplay* s)
1090 {
1091     s->idle_handler = 0;
1092     s->bg_gc = NULL;
1093     s->seg_allocated = 0;
1094     s->segments = NULL;
1095 }
1096 
1097 GtkWidget*
sample_display_new(gboolean edit,SampleDisplayMode mode)1098 sample_display_new(gboolean edit, SampleDisplayMode mode)
1099 {
1100     SampleDisplay* s = SAMPLE_DISPLAY(g_object_new(sample_display_get_type(), NULL));
1101 
1102     s->edit = edit;
1103     s->datacopy = 0;
1104     s->display_zero_line = 0;
1105     s->mode = mode;
1106     di_widget_configure(GTK_WIDGET(s));
1107     return GTK_WIDGET(s);
1108 }
1109