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