1 /*
2 * Copyright (C) 2018-2021 Alexandros Theodotou <alex at zrythm dot org>
3 *
4 * This file is part of Zrythm
5 *
6 * Zrythm is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU Affero General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * Zrythm is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Affero General Public License for more details.
15 *
16 * You should have received a copy of the GNU Affero General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
20 #include "audio/fade.h"
21 #include "audio/control_port.h"
22 #include "audio/track_lane.h"
23 #include "audio/tracklist.h"
24 #include "gui/backend/arranger_object.h"
25 #include "gui/widgets/arranger_draw.h"
26 #include "gui/widgets/arranger_object.h"
27 #include "gui/widgets/bot_bar.h"
28 #include "gui/widgets/bot_dock_edge.h"
29 #include "gui/widgets/center_dock.h"
30 #include "gui/widgets/clip_editor.h"
31 #include "gui/widgets/clip_editor_inner.h"
32 #include "gui/widgets/cpu.h"
33 #include "gui/widgets/editor_ruler.h"
34 #include "gui/widgets/main_notebook.h"
35 #include "gui/widgets/midi_arranger.h"
36 #include "gui/widgets/midi_editor_space.h"
37 #include "gui/widgets/piano_roll_keys.h"
38 #include "gui/widgets/ruler.h"
39 #include "gui/widgets/timeline_panel.h"
40 #include "gui/widgets/timeline_ruler.h"
41 #include "gui/widgets/track.h"
42 #include "gui/widgets/tracklist.h"
43 #include "project.h"
44 #include "settings/settings.h"
45 #include "utils/cairo.h"
46 #include "utils/color.h"
47 #include "utils/debug.h"
48 #include "utils/flags.h"
49 #include "utils/math.h"
50 #include "utils/object_pool.h"
51 #include "utils/objects.h"
52 #include "zrythm_app.h"
53
54 /*#include <valgrind/callgrind.h>*/
55
56 #define TYPE(x) ARRANGER_WIDGET_TYPE_##x
57
58 static void
draw_selections(ArrangerWidget * self,cairo_t * cr,GdkRectangle * rect)59 draw_selections (
60 ArrangerWidget * self,
61 cairo_t * cr,
62 GdkRectangle * rect)
63 {
64 double offset_x, offset_y;
65 offset_x =
66 self->start_x + self->last_offset_x > 0 ?
67 self->last_offset_x :
68 1 - self->start_x;
69 offset_y =
70 self->start_y + self->last_offset_y > 0 ?
71 self->last_offset_y :
72 1 - self->start_y;
73
74 /* if action is selecting and not selecting range
75 * (in the case of timeline */
76 switch (self->action)
77 {
78 case UI_OVERLAY_ACTION_SELECTING:
79 case UI_OVERLAY_ACTION_DELETE_SELECTING:
80 z_cairo_draw_selection (
81 cr, self->start_x - rect->x,
82 self->start_y - rect->y,
83 offset_x, offset_y);
84 break;
85 case UI_OVERLAY_ACTION_RAMPING:
86 cairo_set_source_rgba (
87 cr, 0.9, 0.9, 0.9, 1.0);
88 cairo_set_line_width (cr, 2.0);
89 cairo_move_to (
90 cr, self->start_x - rect->x,
91 self->start_y - rect->y);
92 cairo_line_to (
93 cr,
94 (self->start_x + self->last_offset_x) -
95 rect->x,
96 (self->start_y + self->last_offset_y) -
97 rect->y);
98 cairo_stroke (cr);
99 break;
100 default:
101 break;
102 }
103 }
104
105 static void
draw_highlight(ArrangerWidget * self,cairo_t * cr,GdkRectangle * rect)106 draw_highlight (
107 ArrangerWidget * self,
108 cairo_t * cr,
109 GdkRectangle * rect)
110 {
111 if (!self->is_highlighted)
112 {
113 return;
114 }
115
116 z_cairo_draw_selection_with_color (
117 cr, &UI_COLORS->bright_orange,
118 (self->highlight_rect.x + 1) - rect->x,
119 (self->highlight_rect.y + 1) - rect->y,
120 self->highlight_rect.width - 1,
121 self->highlight_rect.height - 1);
122 }
123
124 /**
125 * @param rect Arranger's rectangle.
126 */
127 static void
draw_arranger_object(ArrangerWidget * self,ArrangerObject * obj,cairo_t * cr,GdkRectangle * rect)128 draw_arranger_object (
129 ArrangerWidget * self,
130 ArrangerObject * obj,
131 cairo_t * cr,
132 GdkRectangle * rect)
133 {
134 /* loop once or twice (2nd time for transient) */
135 for (int i = 0;
136 i < 1 +
137 (arranger_object_should_orig_be_visible (
138 obj) &&
139 arranger_object_is_selected (obj));
140 i++)
141 {
142 /* if looping 2nd time (transient) */
143 if (i == 1)
144 {
145 g_return_if_fail (obj->transient);
146 obj = obj->transient;
147 }
148
149 arranger_object_set_full_rectangle (
150 obj, self);
151
152 /* only draw if the object's rectangle is
153 * hit by the drawable region (for regions,
154 * the logic is handled inside region_draw()
155 * so the check is skipped) */
156 bool rect_hit_or_region =
157 ui_rectangle_overlap (
158 &obj->full_rect, rect) ||
159 obj->type == ARRANGER_OBJECT_TYPE_REGION;
160
161 Track * track =
162 arranger_object_get_track (obj);
163 bool should_be_visible =
164 (track->visible &&
165 self->is_pinned ==
166 track_is_pinned (track)) ||
167 self->type !=
168 ARRANGER_WIDGET_TYPE_TIMELINE;
169
170 if (rect_hit_or_region && should_be_visible)
171 {
172 arranger_object_draw (
173 obj, self, cr, rect);
174 }
175 }
176 }
177
178 /**
179 * @param rect Arranger draw rectangle.
180 */
181 static void
draw_playhead(ArrangerWidget * self,cairo_t * cr,GdkRectangle * rect)182 draw_playhead (
183 ArrangerWidget * self,
184 cairo_t * cr,
185 GdkRectangle * rect)
186 {
187 int cur_playhead_px =
188 arranger_widget_get_playhead_px (self);
189 int px = cur_playhead_px;
190 /*int px = self->queued_playhead_px;*/
191 /*g_message ("drawing %d", px);*/
192
193 if (px >= rect->x && px <= rect->x + rect->width)
194 {
195 cairo_set_source_rgba (
196 cr, 1, 0, 0, 1);
197 switch (ui_get_detail_level ())
198 {
199 case UI_DETAIL_HIGH:
200 cairo_rectangle (
201 cr, (px - rect->x) - 1, 0, 2,
202 rect->height);
203 break;
204 case UI_DETAIL_NORMAL:
205 case UI_DETAIL_LOW:
206 case UI_DETAIL_ULTRA_LOW:
207 cairo_rectangle (
208 cr, (int) (px - rect->x) - 1, 0, 2,
209 (int) rect->height);
210 break;
211 }
212 cairo_fill (cr);
213 self->last_playhead_px = px;
214
215 #if 0
216 if (cur_playhead_px !=
217 self->queued_playhead_px)
218 {
219 arranger_widget_redraw_playhead (self);
220 }
221 #endif
222 }
223 }
224
225 static void
draw_timeline_bg(ArrangerWidget * self,cairo_t * cr,GdkRectangle * rect)226 draw_timeline_bg (
227 ArrangerWidget * self,
228 cairo_t * cr,
229 GdkRectangle * rect)
230 {
231 /* handle horizontal drawing for tracks */
232 GtkWidget * tw_widget;
233 int line_y, i, j;
234 for (i = 0; i < TRACKLIST->num_tracks; i++)
235 {
236 Track * const track =
237 TRACKLIST->tracks[i];
238
239 /* skip tracks in the other timeline (pinned/
240 * non-pinned) */
241 if (!track->visible ||
242 (!self->is_pinned &&
243 track_is_pinned (track)) ||
244 (self->is_pinned &&
245 !track_is_pinned (track)))
246 continue;
247
248 /* draw line below track */
249 TrackWidget * tw = track->widget;
250 if (!GTK_IS_WIDGET (tw))
251 continue;
252 tw_widget = (GtkWidget *) tw;
253
254 double full_track_height =
255 track_get_full_visible_height (track);
256
257 gint track_start_offset;
258 gtk_widget_translate_coordinates (
259 tw_widget,
260 GTK_WIDGET (
261 self->is_pinned ?
262 MW_TRACKLIST->pinned_box :
263 MW_TRACKLIST->unpinned_box),
264 0, 0, NULL, &track_start_offset);
265
266 line_y =
267 track_start_offset +
268 (int) full_track_height;
269
270 if (line_y >= rect->y &&
271 line_y < rect->y + rect->height)
272 {
273 cairo_set_source_rgb (
274 self->cached_cr, 0.3, 0.3, 0.3);
275 cairo_rectangle (
276 cr, 0, (line_y - rect->y) - 1,
277 rect->width, 2);
278 cairo_fill (cr);
279 }
280
281 double total_height = track->main_height;
282
283 #define OFFSET_PLUS_TOTAL_HEIGHT \
284 (track_start_offset + total_height)
285
286 /* --- draw lanes --- */
287
288 if (track->lanes_visible)
289 {
290 for (j = 0; j < track->num_lanes; j++)
291 {
292 TrackLane * lane = track->lanes[j];
293
294 /* horizontal line above lane */
295 if (OFFSET_PLUS_TOTAL_HEIGHT >
296 rect->y &&
297 OFFSET_PLUS_TOTAL_HEIGHT <
298 rect->y + rect->height)
299 {
300 z_cairo_draw_horizontal_line (
301 cr,
302 OFFSET_PLUS_TOTAL_HEIGHT -
303 rect->y,
304 0, rect->width, 0.5, 0.4);
305 }
306
307 total_height += (double) lane->height;
308 }
309 }
310
311 /* --- draw automation --- */
312
313 /* skip tracks without visible automation */
314 if (!track->automation_visible)
315 continue;
316
317 AutomationTracklist * atl =
318 track_get_automation_tracklist (track);
319 if (atl)
320 {
321 AutomationTrack * at;
322 for (j = 0; j < atl->num_ats; j++)
323 {
324 at = atl->ats[j];
325
326 if (!at->created || !at->visible)
327 continue;
328
329 /* horizontal line above automation
330 * track */
331 if (OFFSET_PLUS_TOTAL_HEIGHT >
332 rect->y &&
333 OFFSET_PLUS_TOTAL_HEIGHT <
334 rect->y + rect->height)
335 {
336 z_cairo_draw_horizontal_line (
337 cr,
338 OFFSET_PLUS_TOTAL_HEIGHT -
339 rect->y,
340 0, rect->width, 0.5, 0.2);
341 }
342
343 float normalized_val =
344 automation_track_get_val_at_pos (
345 at, PLAYHEAD, true, true);
346 Port * port =
347 port_find_from_identifier (
348 &at->port_id);
349 AutomationPoint * ap =
350 automation_track_get_ap_before_pos (
351 at, PLAYHEAD, true);
352 if (!ap)
353 {
354 normalized_val =
355 control_port_real_val_to_normalized (
356 port,
357 control_port_get_val (port));
358 }
359
360 int y_px =
361 automation_track_get_y_px_from_normalized_val (
362 at,
363 normalized_val);
364
365 /* line at current val */
366 cairo_set_source_rgba (
367 cr,
368 track->color.red,
369 track->color.green,
370 track->color.blue,
371 0.3);
372 cairo_rectangle (
373 cr, 0,
374 (OFFSET_PLUS_TOTAL_HEIGHT + y_px) -
375 rect->y,
376 rect->width, 1);
377 cairo_fill (cr);
378
379 /* show shade under the line */
380 /*cairo_set_source_rgba (*/
381 /*cr,*/
382 /*track->color.red,*/
383 /*track->color.green,*/
384 /*track->color.blue,*/
385 /*0.06);*/
386 /*cairo_rectangle (*/
387 /*cr,*/
388 /*0,*/
389 /*(OFFSET_PLUS_TOTAL_HEIGHT + y_px) -*/
390 /*rect->y,*/
391 /*rect->width,*/
392 /*at->height - y_px);*/
393 /*cairo_fill (cr);*/
394
395 total_height += (double) at->height;
396 }
397 }
398 }
399 }
400
401 static void
draw_borders(ArrangerWidget * self,cairo_t * cr,int x_from,int x_to,double y_offset)402 draw_borders (
403 ArrangerWidget * self,
404 cairo_t * cr,
405 int x_from,
406 int x_to,
407 double y_offset)
408 {
409 cairo_set_source_rgb (cr, 0.7, 0.7, 0.7);
410 cairo_rectangle (
411 cr, x_from, (int) y_offset, x_to - x_from, 0.5);
412 cairo_fill (cr);
413 }
414
415 static void
draw_midi_bg(ArrangerWidget * self,cairo_t * cr,GdkRectangle * rect)416 draw_midi_bg (
417 ArrangerWidget * self,
418 cairo_t * cr,
419 GdkRectangle * rect)
420 {
421 /* px per key adjusted for border width */
422 double adj_px_per_key =
423 MW_PIANO_ROLL_KEYS->px_per_key + 1.0;
424 /*double adj_total_key_px =*/
425 /*MW_PIANO_ROLL->total_key_px + 126;*/
426
427 /*handle horizontal drawing*/
428 double y_offset;
429 for (int i = 0; i < 128; i++)
430 {
431 y_offset =
432 adj_px_per_key * i;
433 /* if key is visible */
434 if (y_offset > rect->y &&
435 y_offset < (rect->y + rect->height))
436 {
437 draw_borders (
438 self, cr, 0, rect->width,
439 y_offset - rect->y);
440 if (piano_roll_is_key_black (
441 PIANO_ROLL->piano_descriptors[i]->
442 value))
443 {
444 cairo_set_source_rgba (
445 cr, 0, 0, 0, 0.2);
446 cairo_rectangle (
447 cr, 0,
448 /* + 1 since the border is
449 * bottom */
450 (int) ((y_offset - rect->y) + 1),
451 rect->width, (int) adj_px_per_key);
452 cairo_fill (cr);
453 }
454 }
455 bool drum_mode =
456 arranger_widget_get_drum_mode_enabled (
457 self);
458 if ((drum_mode
459 && PIANO_ROLL->drum_descriptors[i]->value
460 == MW_MIDI_ARRANGER->hovered_note)
461 ||
462 (!drum_mode
463 && PIANO_ROLL->piano_descriptors[i]->value
464 == MW_MIDI_ARRANGER->hovered_note))
465 {
466 cairo_set_source_rgba (
467 cr, 1, 1, 1, 0.06);
468 cairo_rectangle (
469 cr, 0,
470 /* + 1 since the border is bottom */
471 (y_offset - rect->y) + 1,
472 rect->width, adj_px_per_key);
473 cairo_fill (cr);
474 }
475 }
476 }
477
478 static void
draw_velocity_bg(ArrangerWidget * self,cairo_t * cr,GdkRectangle * rect)479 draw_velocity_bg (
480 ArrangerWidget * self,
481 cairo_t * cr,
482 GdkRectangle * rect)
483 {
484 int height =
485 gtk_widget_get_allocated_height (
486 GTK_WIDGET (self));
487 cairo_set_source_rgba (
488 cr, 1, 1, 1, 0.2);
489 for (int i = 1; i < 4; i++)
490 {
491 double y_offset = height * (i / 4.0);
492 cairo_rectangle (
493 cr, 0, y_offset - rect->y,
494 rect->width, 1);
495 cairo_fill (cr);
496 }
497 }
498
499 static void
draw_audio_bg(ArrangerWidget * self,cairo_t * cr,GdkRectangle * rect)500 draw_audio_bg (
501 ArrangerWidget * self,
502 cairo_t * cr,
503 GdkRectangle * rect)
504 {
505 ZRegion * ar =
506 clip_editor_get_region (CLIP_EDITOR);
507 if (!ar)
508 {
509 g_message (
510 "audio region not found, skipping draw");
511 return;
512 }
513 if (ar->stretching)
514 {
515 arranger_widget_redraw_whole (self);
516 return;
517 }
518 ArrangerObject * obj = (ArrangerObject *) ar;
519 TrackLane * lane = region_get_lane (ar);
520 Track * track =
521 track_lane_get_track (lane);
522 g_return_if_fail (lane);
523
524 int height =
525 gtk_widget_get_allocated_height (
526 GTK_WIDGET (self));
527
528 AudioClip * clip =
529 AUDIO_POOL->clips[ar->pool_id];
530
531 double local_start_x =
532 (double) rect->x;
533 double local_end_x =
534 local_start_x +
535 (double) rect->width;
536
537 /* frames in the clip to start drawing from */
538 long prev_frames =
539 MAX (
540 ui_px_to_frames_editor (local_start_x, 1) -
541 obj->pos.frames,
542 0);
543
544 UiDetail detail = ui_get_detail_level ();
545 double increment = 1;
546 double width = 1;
547
548 /* if > 40% CPU, force lower level of detail */
549 if (detail < UI_DETAIL_ULTRA_LOW)
550 {
551 if (MW_CPU->cpu > 60)
552 detail = UI_DETAIL_ULTRA_LOW;
553 else if (MW_CPU->cpu > 50)
554 detail = UI_DETAIL_LOW;
555 else if (MW_CPU->cpu > 40)
556 detail++;
557 }
558
559 switch (detail)
560 {
561 case UI_DETAIL_HIGH:
562 increment = 0.5;
563 width = 1;
564 break;
565 case UI_DETAIL_NORMAL:
566 increment = 1;
567 width = 1;
568 break;
569 case UI_DETAIL_LOW:
570 increment = 2;
571 width = 2;
572 break;
573 case UI_DETAIL_ULTRA_LOW:
574 increment = 4;
575 width = 4;
576 break;
577 }
578
579 /* draw fades */
580 long obj_length_frames =
581 arranger_object_get_length_in_frames (obj);
582 GdkRGBA base_color = {
583 .red = 0.3, .green = 0.3, .blue = 0.3,
584 .alpha = 0.0 };
585 GdkRGBA fade_color;
586 color_morph (
587 &base_color, &track->color, 0.5, &fade_color);
588 fade_color.alpha = 0.5;
589 gdk_cairo_set_source_rgba (cr, &fade_color);
590 for (double i = local_start_x;
591 i < local_end_x; i += increment)
592 {
593 long curr_frames =
594 ui_px_to_frames_editor (i, 1) -
595 obj->pos.frames;
596 if (curr_frames < 0
597 || curr_frames >= obj_length_frames)
598 continue;
599
600 double max;
601 if (curr_frames < obj->fade_in_pos.frames)
602 {
603 z_return_if_fail_cmp (
604 obj->fade_in_pos.frames, >, 0);
605 max =
606 fade_get_y_normalized (
607 (double) curr_frames /
608 (double)
609 obj->fade_in_pos.frames,
610 &obj->fade_in_opts, 1);
611 }
612 else if (
613 curr_frames >= obj->fade_out_pos.frames)
614 {
615 z_return_if_fail_cmp (
616 obj->end_pos.frames -
617 (obj->fade_out_pos.frames +
618 obj->pos.frames), >, 0);
619 max =
620 fade_get_y_normalized (
621 (double)
622 (curr_frames -
623 obj->fade_out_pos.frames) /
624 (double)
625 (obj->end_pos.frames -
626 (obj->fade_out_pos.frames +
627 obj->pos.frames)),
628 &obj->fade_out_opts, 0);
629 }
630 else
631 continue;
632
633 /* invert because cairo draws the other
634 * way around */
635 max = 1.0 - max;
636
637 double from_y = - rect->y;
638 double draw_height =
639 (MIN (
640 (double) max * (double) height,
641 (double) height) - rect->y) - from_y;
642
643 cairo_rectangle (
644 cr, i - rect->x, from_y, width,
645 draw_height);
646 cairo_fill (cr);
647
648 if (curr_frames >= clip->num_frames)
649 break;
650 }
651
652 /* draw audio part */
653 GdkRGBA * color = &track->color;
654 cairo_set_source_rgba (
655 cr, color->red + 0.3, color->green + 0.3,
656 color->blue + 0.3, 0.9);
657 cairo_set_line_width (cr, 1);
658 for (double i = local_start_x;
659 i < local_end_x; i += increment)
660 {
661 long curr_frames =
662 ui_px_to_frames_editor (i, 1) -
663 obj->pos.frames;
664 if (curr_frames < 0)
665 continue;
666
667 float min = 0.f, max = 0.f;
668 for (long j = prev_frames;
669 j < curr_frames; j++)
670 {
671 if (j >= (long) clip->num_frames)
672 break;
673 for (unsigned int k = 0;
674 k < clip->channels; k++)
675 {
676 long index =
677 j * (long) clip->channels + (long) k;
678 g_return_if_fail (
679 index >= 0 &&
680 index <
681 (long)
682 (clip->num_frames *
683 clip->channels));
684 float val = clip->frames[index];
685 if (val > max)
686 {
687 max = val;
688 }
689 if (val < min)
690 {
691 min = val;
692 }
693 }
694 }
695 #define DRAW_VLINE(cr,x,from_y,_height) \
696 switch (detail) \
697 { \
698 case UI_DETAIL_HIGH: \
699 cairo_rectangle ( \
700 cr, x, from_y, \
701 width, _height); \
702 break; \
703 case UI_DETAIL_NORMAL: \
704 case UI_DETAIL_LOW: \
705 case UI_DETAIL_ULTRA_LOW: \
706 cairo_rectangle ( \
707 cr, (int) (x), (int) (from_y), \
708 width, (int) _height); \
709 break; \
710 } \
711 cairo_fill (cr)
712
713 min = (min + 1.f) / 2.f; /* normallize */
714 max = (max + 1.f) / 2.f; /* normalize */
715 double from_y =
716 MAX (
717 (double) min * (double) height, 0.0) - rect->y;
718 double draw_height =
719 (MIN (
720 (double) max * (double) height,
721 (double) height) - rect->y) - from_y;
722 DRAW_VLINE (
723 cr,
724 /* x */
725 i - rect->x,
726 /* from y */
727 from_y,
728 /* to y */
729 draw_height);
730
731 if (curr_frames >= clip->num_frames)
732 break;
733
734 prev_frames = curr_frames;
735 }
736 #undef DRAW_VLINE
737 cairo_fill (cr);
738
739 /* draw gain line */
740 gdk_cairo_set_source_rgba (
741 cr, &UI_COLORS->bright_orange);
742 float gain_fader_val =
743 math_get_fader_val_from_amp (ar->gain);
744 int gain_line_start_x =
745 ui_pos_to_px_editor (&obj->pos, F_PADDING);
746 int gain_line_end_x =
747 ui_pos_to_px_editor (&obj->end_pos, F_PADDING);
748 cairo_rectangle (
749 cr,
750 /* need 1 pixel extra for some reason */
751 1 + (gain_line_start_x - rect->x),
752 /* invert because cairo draws the opposite
753 * way */
754 height * (1.0 - gain_fader_val) - rect->y,
755 gain_line_end_x - gain_line_start_x,
756 2);
757 cairo_fill (cr);
758
759 /* draw gain text */
760 double gain_db = math_amp_to_dbfs (ar->gain);
761 char gain_txt[50];
762 sprintf (gain_txt, "%.1fdB", gain_db);
763 int gain_txt_padding = 3;
764 z_cairo_draw_text_full (
765 cr, GTK_WIDGET (self), self->audio_layout,
766 gain_txt,
767 (gain_txt_padding + gain_line_start_x) -
768 rect->x,
769 gain_txt_padding +
770 (int)
771 (height * (1.0 - gain_fader_val) - rect->y));
772 }
773
774 static void
draw_vertical_lines(ArrangerWidget * self,RulerWidget * ruler,cairo_t * cr,GdkRectangle * rect)775 draw_vertical_lines (
776 ArrangerWidget * self,
777 RulerWidget * ruler,
778 cairo_t * cr,
779 GdkRectangle * rect)
780 {
781 /* if time display */
782 if (self->ruler_display == TRANSPORT_DISPLAY_TIME)
783 {
784 /* get sec interval */
785 int sec_interval =
786 ruler_widget_get_sec_interval (ruler);
787
788 /* get 10 sec interval */
789 int ten_sec_interval =
790 ruler_widget_get_10sec_interval (ruler);
791
792 /* get the interval for mins */
793 int min_interval =
794 (int)
795 MAX ((RW_PX_TO_HIDE_BEATS) /
796 (double) ruler->px_per_min, 1.0);
797
798 int i = 0;
799 double curr_px;
800 while (
801 (curr_px =
802 ruler->px_per_min * (i += min_interval) +
803 SPACE_BEFORE_START) <
804 rect->x + rect->width)
805 {
806 if (curr_px < rect->x)
807 continue;
808
809 cairo_set_source_rgb (
810 cr, 0.3, 0.3, 0.3);
811 double x = curr_px - rect->x;
812 cairo_rectangle (
813 cr, (int) x, 0,
814 1, rect->height);
815 cairo_fill (cr);
816 }
817 i = 0;
818 if (ten_sec_interval > 0)
819 {
820 while ((curr_px =
821 ruler->px_per_10sec *
822 (i += ten_sec_interval) +
823 SPACE_BEFORE_START) <
824 rect->x + rect->width)
825 {
826 if (curr_px < rect->x)
827 continue;
828
829 cairo_set_source_rgba (
830 cr, 0.25, 0.25, 0.25,
831 0.6);
832 double x = curr_px - rect->x;
833 cairo_rectangle (
834 cr, (int) x, 0,
835 1, rect->height);
836 cairo_fill (cr);
837 }
838 }
839 i = 0;
840 if (sec_interval > 0)
841 {
842 while ((curr_px =
843 ruler->px_per_sec *
844 (i += sec_interval) +
845 SPACE_BEFORE_START) <
846 rect->x + rect->width)
847 {
848 if (curr_px < rect->x)
849 continue;
850
851 cairo_set_source_rgb (
852 cr, 0.2, 0.2, 0.2);
853 cairo_set_line_width (cr, 0.5);
854 double x = curr_px - rect->x;
855 cairo_move_to (cr, x, 0);
856 cairo_line_to (cr, x, rect->height);
857 cairo_stroke (cr);
858 }
859 }
860 }
861 /* else if BBT display */
862 else
863 {
864 /* get sixteenth interval */
865 int sixteenth_interval =
866 ruler_widget_get_sixteenth_interval (
867 ruler);
868
869 /* get the beat interval */
870 int beat_interval =
871 ruler_widget_get_beat_interval (
872 ruler);
873
874 /* get the interval for bars */
875 int bar_interval =
876 (int)
877 MAX ((RW_PX_TO_HIDE_BEATS) /
878 (double) ruler->px_per_bar, 1.0);
879
880 int i = 0;
881 double curr_px;
882 while (
883 (curr_px =
884 ruler->px_per_bar * (i += bar_interval) +
885 SPACE_BEFORE_START) <
886 rect->x + rect->width)
887 {
888 if (curr_px < rect->x)
889 continue;
890
891 cairo_set_source_rgb (
892 cr, 0.3, 0.3, 0.3);
893 double x = curr_px - rect->x;
894 cairo_rectangle (
895 cr, (int) x, 0,
896 1, rect->height);
897 cairo_fill (cr);
898 }
899 i = 0;
900 if (beat_interval > 0)
901 {
902 while ((curr_px =
903 ruler->px_per_beat *
904 (i += beat_interval) +
905 SPACE_BEFORE_START) <
906 rect->x + rect->width)
907 {
908 if (curr_px < rect->x)
909 continue;
910
911 cairo_set_source_rgba (
912 cr, 0.25, 0.25, 0.25,
913 0.6);
914 double x = curr_px - rect->x;
915 cairo_rectangle (
916 cr, (int) x, 0,
917 1, rect->height);
918 cairo_fill (cr);
919 }
920 }
921 i = 0;
922 if (sixteenth_interval > 0)
923 {
924 while ((curr_px =
925 ruler->px_per_sixteenth *
926 (i += sixteenth_interval) +
927 SPACE_BEFORE_START) <
928 rect->x + rect->width)
929 {
930 if (curr_px < rect->x)
931 continue;
932
933 cairo_set_source_rgb (
934 cr, 0.2, 0.2, 0.2);
935 cairo_set_line_width (cr, 0.5);
936 double x = curr_px - rect->x;
937 cairo_move_to (cr, x, 0);
938 cairo_line_to (cr, x, rect->height);
939 cairo_stroke (cr);
940 }
941 }
942 }
943 }
944
945 static void
draw_range(ArrangerWidget * self,int range_first_px,int range_second_px,GdkRectangle * rect,cairo_t * cr)946 draw_range (
947 ArrangerWidget * self,
948 int range_first_px,
949 int range_second_px,
950 GdkRectangle * rect,
951 cairo_t * cr)
952 {
953 /* draw range */
954 cairo_set_source_rgba (
955 cr, 0.3, 0.3, 0.3, 0.3);
956 cairo_rectangle (
957 cr,
958 MAX (0, range_first_px - rect->x), 0,
959 range_second_px -
960 MAX (rect->x, range_first_px),
961 rect->height);
962 cairo_fill (cr);
963 cairo_set_source_rgba (
964 cr, 0.8, 0.8, 0.8, 0.4);
965 cairo_set_line_width (cr, 2);
966
967 /* if start is within the screen */
968 if (range_first_px > rect->x &&
969 range_first_px <= rect->x + rect->width)
970 {
971 /* draw the start line */
972 double x =
973 (range_first_px - rect->x) + 1.0;
974 cairo_move_to (
975 cr, x, 0);
976 cairo_line_to (
977 cr, x, rect->height);
978 cairo_stroke (cr);
979 }
980 /* if end is within the screen */
981 if (range_second_px > rect->x &&
982 range_second_px < rect->x + rect->width)
983 {
984 double x =
985 (range_second_px - rect->x) - 1.0;
986 cairo_move_to (
987 cr, x, 0);
988 cairo_line_to (
989 cr, x, rect->height);
990 cairo_stroke (cr);
991 }
992 }
993
994 gboolean
arranger_draw_cb(GtkWidget * widget,cairo_t * cr,ArrangerWidget * self)995 arranger_draw_cb (
996 GtkWidget * widget,
997 cairo_t * cr,
998 ArrangerWidget * self)
999 {
1000 gint64 start_time = g_get_monotonic_time ();
1001
1002 #if 0
1003 if (!self->dummy_surface)
1004 {
1005 self->dummy_surface =
1006 cairo_surface_create_similar (
1007 cairo_get_target (cr),
1008 CAIRO_CONTENT_COLOR_ALPHA,
1009 1, 1);
1010 }
1011 #endif
1012
1013 RulerWidget * ruler =
1014 arranger_widget_get_ruler (self);
1015 if (ruler->px_per_bar < 2.0)
1016 return FALSE;
1017
1018 if (self->first_draw)
1019 {
1020 self->first_draw = false;
1021
1022 GtkScrolledWindow * scroll =
1023 arranger_widget_get_scrolled_window (self);
1024 GtkAdjustment * hadj =
1025 gtk_scrolled_window_get_hadjustment (
1026 scroll);
1027 GtkAdjustment * vadj =
1028 gtk_scrolled_window_get_vadjustment (
1029 scroll);
1030
1031 EditorSettings * settings =
1032 arranger_widget_get_editor_settings (self);
1033
1034 int new_x = settings->scroll_start_x;
1035 int new_y = settings->scroll_start_y;
1036 if (self->type == TYPE (TIMELINE) &&
1037 self->is_pinned)
1038 {
1039 new_y = 0;
1040 }
1041 else if (self->type == TYPE (MIDI_MODIFIER))
1042 {
1043 new_y = 0;
1044 }
1045
1046 g_debug (
1047 "setting arranger adjustment to %d, %d",
1048 new_x, new_y);
1049
1050 gtk_adjustment_set_value (hadj, new_x);
1051 gtk_adjustment_set_value (vadj, new_y);
1052 }
1053
1054 GdkRectangle rect;
1055 gdk_cairo_get_clip_rectangle (cr, &rect);
1056
1057 if (self->redraw ||
1058 !gdk_rectangle_equal (
1059 &rect, &self->last_rect))
1060 {
1061 /* skip drawing if rectangle too large */
1062 if (rect.width > 10000 ||
1063 rect.height > 10000)
1064 {
1065 g_warning (
1066 "skipping draw - rectangle too large");
1067 return false;
1068 }
1069
1070 /*g_message (*/
1071 /*"redrawing arranger in rect: "*/
1072 /*"(%d, %d) width: %d height %d)",*/
1073 /*rect.x, rect.y, rect.width, rect.height);*/
1074 self->last_rect = rect;
1075
1076 GtkStyleContext *context =
1077 gtk_widget_get_style_context (widget);
1078
1079 z_cairo_reset_caches (
1080 &self->cached_cr,
1081 &self->cached_surface, rect.width,
1082 rect.height, cr);
1083
1084 cairo_antialias_t antialias =
1085 cairo_get_antialias (self->cached_cr);
1086 double tolerance =
1087 cairo_get_tolerance (self->cached_cr);
1088 cairo_set_antialias (
1089 self->cached_cr, CAIRO_ANTIALIAS_FAST);
1090 cairo_set_tolerance (self->cached_cr, 1.5);
1091
1092 gtk_render_background (
1093 context, self->cached_cr, 0, 0,
1094 rect.width, rect.height);
1095
1096 /* draw loop background */
1097 if (TRANSPORT->loop)
1098 {
1099 double start_px = 0, end_px = 0;
1100 if (self->type == TYPE (TIMELINE))
1101 {
1102 start_px =
1103 ui_pos_to_px_timeline (
1104 &TRANSPORT->loop_start_pos, 1);
1105 end_px =
1106 ui_pos_to_px_timeline (
1107 &TRANSPORT->loop_end_pos, 1);
1108 }
1109 else
1110 {
1111 start_px =
1112 ui_pos_to_px_editor (
1113 &TRANSPORT->loop_start_pos, 1);
1114 end_px =
1115 ui_pos_to_px_editor (
1116 &TRANSPORT->loop_end_pos, 1);
1117 }
1118 cairo_set_source_rgba (
1119 self->cached_cr, 0, 0.9, 0.7, 0.08);
1120 cairo_set_line_width (
1121 self->cached_cr, 2);
1122
1123 /* if transport loop start is within the
1124 * screen */
1125 if (start_px > rect.x &&
1126 start_px <= rect.x + rect.width)
1127 {
1128 /* draw the loop start line */
1129 double x =
1130 (start_px - rect.x) + 1.0;
1131 cairo_rectangle (
1132 self->cached_cr,
1133 (int) x, 0, 2, rect.height);
1134 cairo_fill (self->cached_cr);
1135 }
1136 /* if transport loop end is within the
1137 * screen */
1138 if (end_px > rect.x &&
1139 end_px < rect.x + rect.width)
1140 {
1141 double x =
1142 (end_px - rect.x) - 1.0;
1143 cairo_rectangle (
1144 self->cached_cr,
1145 (int) x, 0, 2, rect.height);
1146 cairo_fill (self->cached_cr);
1147 }
1148
1149 /* draw transport loop area */
1150 cairo_set_source_rgba (
1151 self->cached_cr, 0, 0.9, 0.7, 0.02);
1152 double loop_start_local_x =
1153 MAX (0, start_px - rect.x);
1154 cairo_rectangle (
1155 self->cached_cr,
1156 (int) loop_start_local_x, 0,
1157 (int) (end_px - MAX (rect.x, start_px)),
1158 rect.height);
1159 cairo_fill (self->cached_cr);
1160 }
1161
1162 /* --- handle vertical drawing --- */
1163
1164 draw_vertical_lines (
1165 self, ruler, self->cached_cr, &rect);
1166
1167 /* draw range */
1168 int range_first_px, range_second_px;
1169 bool have_range = false;
1170 if (self->type == TYPE (AUDIO) &&
1171 AUDIO_SELECTIONS->has_selection)
1172 {
1173 Position * range_first_pos,
1174 * range_second_pos;
1175 if (position_is_before_or_equal (
1176 &TRANSPORT->range_1,
1177 &TRANSPORT->range_2))
1178 {
1179 range_first_pos =
1180 &AUDIO_SELECTIONS->sel_start;
1181 range_second_pos =
1182 &AUDIO_SELECTIONS->sel_end;
1183 }
1184 else
1185 {
1186 range_first_pos =
1187 &AUDIO_SELECTIONS->sel_end;
1188 range_second_pos =
1189 &AUDIO_SELECTIONS->sel_start;
1190 }
1191
1192 range_first_px =
1193 ui_pos_to_px_editor (
1194 range_first_pos, 1);
1195 range_second_px =
1196 ui_pos_to_px_editor (
1197 range_second_pos, 1);
1198 have_range = true;
1199 }
1200 else if (self->type == TYPE (TIMELINE) &&
1201 TRANSPORT->has_range)
1202 {
1203 /* in order they appear */
1204 Position * range_first_pos,
1205 * range_second_pos;
1206 if (position_is_before_or_equal (
1207 &TRANSPORT->range_1,
1208 &TRANSPORT->range_2))
1209 {
1210 range_first_pos = &TRANSPORT->range_1;
1211 range_second_pos =
1212 &TRANSPORT->range_2;
1213 }
1214 else
1215 {
1216 range_first_pos = &TRANSPORT->range_2;
1217 range_second_pos =
1218 &TRANSPORT->range_1;
1219 }
1220
1221 range_first_px =
1222 ui_pos_to_px_timeline (
1223 range_first_pos, 1);
1224 range_second_px =
1225 ui_pos_to_px_timeline (
1226 range_second_pos, 1);
1227 have_range = true;
1228 }
1229
1230 if (have_range)
1231 {
1232 draw_range (
1233 self, range_first_px, range_second_px,
1234 &rect, self->cached_cr);
1235 }
1236
1237 if (self->type == TYPE (TIMELINE))
1238 {
1239 draw_timeline_bg (
1240 self, self->cached_cr, &rect);
1241 }
1242 else if (self->type == TYPE (MIDI))
1243 {
1244 draw_midi_bg (
1245 self, self->cached_cr, &rect);
1246 }
1247 else if (self->type == TYPE (MIDI_MODIFIER))
1248 {
1249 draw_velocity_bg (
1250 self, self->cached_cr, &rect);
1251 }
1252 else if (self->type == TYPE (AUDIO))
1253 {
1254 draw_audio_bg (
1255 self, self->cached_cr, &rect);
1256 }
1257
1258 /* draw each arranger object */
1259 ArrangerObject * objs[2000];
1260 int num_objs;
1261 arranger_widget_get_hit_objects_in_rect (
1262 self, ARRANGER_OBJECT_TYPE_ALL, &rect,
1263 objs, &num_objs);
1264
1265 /*g_message (*/
1266 /*"objects found: %d (is pinned %d)",*/
1267 /*num_objs, self->is_pinned);*/
1268 /* note: these are only project objects */
1269 for (int j = 0; j < num_objs; j++)
1270 {
1271 draw_arranger_object (
1272 self, objs[j], self->cached_cr,
1273 &rect);
1274 }
1275
1276 /* draw dnd highlight */
1277 draw_highlight (
1278 self, self->cached_cr, &rect);
1279
1280 /* draw selections */
1281 draw_selections (
1282 self, self->cached_cr, &rect);
1283
1284 draw_playhead (self, self->cached_cr, &rect);
1285
1286 cairo_set_antialias (
1287 self->cached_cr, antialias);
1288 cairo_set_tolerance (self->cached_cr, tolerance);
1289
1290 self->redraw = false;
1291 }
1292
1293 cairo_set_source_surface (
1294 cr, self->cached_surface, rect.x, rect.y);
1295 cairo_paint (cr);
1296
1297 gint64 end_time = g_get_monotonic_time ();
1298
1299 (void) start_time;
1300 (void) end_time;
1301 #if 0
1302 g_debug ("finished drawing in %ld microseconds, "
1303 "rect x:%d y:%d w:%d h:%d for %s "
1304 "arranger",
1305 end_time - start_time,
1306 rect.x, rect.y, rect.width, rect.height,
1307 arranger_widget_get_type_str (self));
1308 #endif
1309
1310 return FALSE;
1311 }
1312
1313 void *
arranger_draw_task_data_new(void)1314 arranger_draw_task_data_new (void)
1315 {
1316 ArrangerDrawTaskData * self =
1317 object_new (ArrangerDrawTaskData);
1318
1319 return self;
1320 }
1321
1322 void
arranger_draw_task_data_free(void * data)1323 arranger_draw_task_data_free (
1324 void * data)
1325 {
1326 ArrangerDrawTaskData * task_data =
1327 (ArrangerDrawTaskData *) data;
1328
1329 if (task_data->surface)
1330 {
1331 cairo_surface_destroy (
1332 task_data->surface);
1333 }
1334 if (task_data->cr)
1335 {
1336 cairo_destroy (task_data->cr);
1337 }
1338
1339 object_zero_and_free (task_data);
1340 }
1341
1342 #if 0
1343 /**
1344 * Function to be executed for new tasks in the draw
1345 * thread pool.
1346 */
1347 void
1348 arranger_draw_thread_func (
1349 gpointer task_data,
1350 gpointer pool_data)
1351 {
1352 ArrangerWidget * self =
1353 Z_ARRANGER_WIDGET (pool_data);
1354 ArrangerDrawTaskData * task =
1355 (ArrangerDrawTaskData *) task_data;
1356
1357 gint64 start_time = g_get_monotonic_time ();
1358
1359 if (!task->surface || !task->cr)
1360 {
1361 object_pool_return (
1362 self->draw_task_obj_pool, task);
1363 return;
1364 }
1365
1366 RulerWidget * ruler =
1367 arranger_widget_get_ruler (self);
1368 if (ruler->px_per_bar < 2.0)
1369 return;
1370
1371 GdkRectangle rect = task->rect;
1372
1373 GtkStyleContext *context =
1374 gtk_widget_get_style_context (
1375 GTK_WIDGET (self));
1376
1377 gtk_render_background (
1378 context, task->cr, 0, 0,
1379 rect.width, rect.height);
1380
1381 /* draw loop background */
1382 if (TRANSPORT->loop)
1383 {
1384 double start_px = 0, end_px = 0;
1385 if (self->type == TYPE (TIMELINE))
1386 {
1387 start_px =
1388 ui_pos_to_px_timeline (
1389 &TRANSPORT->loop_start_pos, 1);
1390 end_px =
1391 ui_pos_to_px_timeline (
1392 &TRANSPORT->loop_end_pos, 1);
1393 }
1394 else
1395 {
1396 start_px =
1397 ui_pos_to_px_editor (
1398 &TRANSPORT->loop_start_pos, 1);
1399 end_px =
1400 ui_pos_to_px_editor (
1401 &TRANSPORT->loop_end_pos, 1);
1402 }
1403 cairo_set_source_rgba (
1404 task->cr, 0, 0.9, 0.7, 0.08);
1405 cairo_set_line_width (
1406 task->cr, 2);
1407
1408 /* if transport loop start is within the
1409 * screen */
1410 if (start_px > rect.x &&
1411 start_px <= rect.x + rect.width)
1412 {
1413 /* draw the loop start line */
1414 double x =
1415 (start_px - rect.x) + 1.0;
1416 cairo_rectangle (
1417 task->cr,
1418 (int) x, 0, 2, rect.height);
1419 cairo_fill (task->cr);
1420 }
1421 /* if transport loop end is within the
1422 * screen */
1423 if (end_px > rect.x &&
1424 end_px < rect.x + rect.width)
1425 {
1426 double x =
1427 (end_px - rect.x) - 1.0;
1428 cairo_rectangle (
1429 task->cr,
1430 (int) x, 0, 2, rect.height);
1431 cairo_fill (task->cr);
1432 }
1433
1434 /* draw transport loop area */
1435 cairo_set_source_rgba (
1436 task->cr, 0, 0.9, 0.7, 0.02);
1437 double loop_start_local_x =
1438 MAX (0, start_px - rect.x);
1439 cairo_rectangle (
1440 task->cr,
1441 (int) loop_start_local_x, 0,
1442 (int) (end_px - MAX (rect.x, start_px)),
1443 rect.height);
1444 cairo_fill (task->cr);
1445 }
1446
1447 /* --- handle vertical drawing --- */
1448
1449 draw_vertical_lines (
1450 self, ruler, task->cr, &rect);
1451
1452 /* draw range */
1453 int range_first_px, range_second_px;
1454 bool have_range = false;
1455 if (self->type == TYPE (AUDIO) &&
1456 AUDIO_SELECTIONS->has_selection)
1457 {
1458 Position * range_first_pos,
1459 * range_second_pos;
1460 if (position_is_before_or_equal (
1461 &TRANSPORT->range_1,
1462 &TRANSPORT->range_2))
1463 {
1464 range_first_pos =
1465 &AUDIO_SELECTIONS->sel_start;
1466 range_second_pos =
1467 &AUDIO_SELECTIONS->sel_end;
1468 }
1469 else
1470 {
1471 range_first_pos =
1472 &AUDIO_SELECTIONS->sel_end;
1473 range_second_pos =
1474 &AUDIO_SELECTIONS->sel_start;
1475 }
1476
1477 range_first_px =
1478 ui_pos_to_px_editor (
1479 range_first_pos, 1);
1480 range_second_px =
1481 ui_pos_to_px_editor (
1482 range_second_pos, 1);
1483 have_range = true;
1484 }
1485 else if (self->type == TYPE (TIMELINE) &&
1486 TRANSPORT->has_range)
1487 {
1488 /* in order they appear */
1489 Position * range_first_pos,
1490 * range_second_pos;
1491 if (position_is_before_or_equal (
1492 &TRANSPORT->range_1,
1493 &TRANSPORT->range_2))
1494 {
1495 range_first_pos = &TRANSPORT->range_1;
1496 range_second_pos =
1497 &TRANSPORT->range_2;
1498 }
1499 else
1500 {
1501 range_first_pos = &TRANSPORT->range_2;
1502 range_second_pos =
1503 &TRANSPORT->range_1;
1504 }
1505
1506 range_first_px =
1507 ui_pos_to_px_timeline (
1508 range_first_pos, 1);
1509 range_second_px =
1510 ui_pos_to_px_timeline (
1511 range_second_pos, 1);
1512 have_range = true;
1513 }
1514
1515 if (have_range)
1516 {
1517 draw_range (
1518 self, range_first_px, range_second_px,
1519 &rect, task->cr);
1520 }
1521
1522 if (self->type == TYPE (TIMELINE))
1523 {
1524 draw_timeline_bg (
1525 self, task->cr, &rect);
1526 }
1527 else if (self->type == TYPE (MIDI))
1528 {
1529 draw_midi_bg (
1530 self, task->cr, &rect);
1531 }
1532 else if (self->type == TYPE (AUDIO))
1533 {
1534 draw_audio_bg (
1535 self, task->cr, &rect);
1536 }
1537
1538 /* draw each arranger object */
1539 ArrangerObject * objs[2000];
1540 int num_objs;
1541 arranger_widget_get_hit_objects_in_rect (
1542 self, ARRANGER_OBJECT_TYPE_ALL, &rect,
1543 objs, &num_objs);
1544
1545 /*g_message (*/
1546 /*"objects found: %d (is pinned %d)",*/
1547 /*num_objs, self->is_pinned);*/
1548 /* note: these are only project objects */
1549 for (int j = 0; j < num_objs; j++)
1550 {
1551 draw_arranger_object (
1552 self, objs[j], task->cr,
1553 &rect);
1554 }
1555
1556 /* draw dnd highlight */
1557 draw_highlight (
1558 self, task->cr, &rect);
1559
1560 /* draw selections */
1561 draw_selections (
1562 self, task->cr, &rect);
1563
1564 draw_playhead (self, task->cr, &rect);
1565
1566 object_pool_return (
1567 self->draw_task_obj_pool, task);
1568
1569 gint64 end_time = g_get_monotonic_time ();
1570
1571 g_message (
1572 "drawn in thread in %ld microseconds, "
1573 "rect x:%d y:%d w:%d h:%d for %s "
1574 "arranger",
1575 end_time - start_time,
1576 rect.x, rect.y, rect.width, rect.height,
1577 arranger_widget_get_type_str (self));
1578 }
1579 #endif
1580