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