rework tempo editing.
[ardour.git] / gtk2_ardour / editor_tempodisplay.cc
1 /*
2     Copyright (C) 2002 Paul Davis
3
4     This program is free software; you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation; either version 2 of the License, or
7     (at your option) any later version.
8
9     This program is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12     GNU General Public License for more details.
13
14     You should have received a copy of the GNU General Public License
15     along with this program; if not, write to the Free Software
16     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17
18 */
19
20 #ifdef WAF_BUILD
21 #include "gtk2ardour-config.h"
22 #endif
23
24 #include <cstdio> // for sprintf, grrr
25 #include <cstdlib>
26 #include <cmath>
27 #include <string>
28 #include <climits>
29
30 #include "pbd/error.h"
31 #include "pbd/memento_command.h"
32
33 #include <gtkmm2ext/utils.h>
34 #include <gtkmm2ext/gtk_ui.h>
35
36 #include "ardour/session.h"
37 #include "ardour/tempo.h"
38 #include <gtkmm2ext/doi.h>
39 #include <gtkmm2ext/utils.h>
40
41 #include "canvas/canvas.h"
42 #include "canvas/item.h"
43 #include "canvas/line_set.h"
44
45 #include "editor.h"
46 #include "marker.h"
47 #include "tempo_dialog.h"
48 #include "rgb_macros.h"
49 #include "gui_thread.h"
50 #include "time_axis_view.h"
51 #include "tempo_lines.h"
52 #include "ui_config.h"
53
54 #include "pbd/i18n.h"
55
56 using namespace std;
57 using namespace ARDOUR;
58 using namespace PBD;
59 using namespace Gtk;
60 using namespace Gtkmm2ext;
61 using namespace Editing;
62
63 void
64 Editor::remove_metric_marks ()
65 {
66         /* don't delete these while handling events, just punt till the GUI is idle */
67
68         for (Marks::iterator x = metric_marks.begin(); x != metric_marks.end(); ++x) {
69                 delete_when_idle (*x);
70         }
71         metric_marks.clear ();
72
73         for (Curves::iterator x = tempo_curves.begin(); x != tempo_curves.end(); ++x) {
74                 delete (*x);
75         }
76         tempo_curves.clear ();
77 }
78 struct CurveComparator {
79         bool operator() (TempoCurve const * a, TempoCurve const * b) {
80                 return a->tempo().frame() < b->tempo().frame();
81         }
82 };
83 void
84 Editor::draw_metric_marks (const Metrics& metrics)
85 {
86         char buf[64];
87         double max_tempo = 0.0;
88         double min_tempo = DBL_MAX;
89         const TempoSection *prev_ts = 0;
90
91         remove_metric_marks (); // also clears tempo curves
92
93         for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
94                 const MeterSection *ms;
95                 const TempoSection *ts;
96
97                 if ((ms = dynamic_cast<const MeterSection*>(*i)) != 0) {
98                         snprintf (buf, sizeof(buf), "%g/%g", ms->divisions_per_bar(), ms->note_divisor ());
99                         if (ms->position_lock_style() == MusicTime) {
100                                 metric_marks.push_back (new MeterMarker (*this, *meter_group, UIConfiguration::instance().color ("meter marker music"), buf,
101                                                                          *(const_cast<MeterSection*>(ms))));
102                         } else {
103                                 metric_marks.push_back (new MeterMarker (*this, *meter_group, UIConfiguration::instance().color ("meter marker"), buf,
104                                                                          *(const_cast<MeterSection*>(ms))));
105                         }
106                 } else if ((ts = dynamic_cast<const TempoSection*>(*i)) != 0) {
107                         if (UIConfiguration::instance().get_allow_non_quarter_pulse()) {
108                                 snprintf (buf, sizeof (buf), "%.3f/%.0f", ts->note_types_per_minute(), ts->note_type());
109                         } else {
110                                 snprintf (buf, sizeof (buf), "%.3f", ts->note_types_per_minute());
111                         }
112
113                         max_tempo = max (max_tempo, ts->note_types_per_minute());
114                         max_tempo = max (max_tempo, ts->end_note_types_per_minute());
115                         min_tempo = min (min_tempo, ts->note_types_per_minute());
116                         min_tempo = min (min_tempo, ts->end_note_types_per_minute());
117                         uint32_t tc_color = UIConfiguration::instance().color ("tempo curve");
118
119                         if (prev_ts &&  abs (prev_ts->end_note_types_per_minute() - ts->note_types_per_minute()) < 2) {
120                                 tc_color = UIConfiguration::instance().color ("location loop");
121                         }
122
123                         tempo_curves.push_back (new TempoCurve (*this, *tempo_group, tc_color,
124                                                                 *(const_cast<TempoSection*>(ts)), ts->frame(), false));
125
126                         if (ts->position_lock_style() == MusicTime) {
127                                 metric_marks.push_back (new TempoMarker (*this, *tempo_group, UIConfiguration::instance().color ("tempo marker music"), buf,
128                                                                  *(const_cast<TempoSection*>(ts))));
129                         } else {
130                                 metric_marks.push_back (new TempoMarker (*this, *tempo_group, UIConfiguration::instance().color ("tempo marker"), buf,
131                                                                  *(const_cast<TempoSection*>(ts))));
132                         }
133
134                         prev_ts = ts;
135
136                 }
137
138         }
139         tempo_curves.sort (CurveComparator());
140
141         const double min_tempo_range = 5.0;
142         const double tempo_delta = fabs (max_tempo - min_tempo);
143
144         if (tempo_delta < min_tempo_range) {
145                 max_tempo += min_tempo_range - tempo_delta;
146                 min_tempo += tempo_delta - min_tempo_range;
147         }
148
149         for (Curves::iterator x = tempo_curves.begin(); x != tempo_curves.end(); ) {
150                 Curves::iterator tmp = x;
151                 (*x)->set_max_tempo (max_tempo);
152                 (*x)->set_min_tempo (min_tempo);
153                 ++tmp;
154                 if (tmp != tempo_curves.end()) {
155                         (*x)->set_position ((*x)->tempo().frame(), (*tmp)->tempo().frame());
156                 } else {
157                         (*x)->set_position ((*x)->tempo().frame(), UINT32_MAX);
158                 }
159
160                 if (!(*x)->tempo().active()) {
161                         (*x)->hide();
162                 } else {
163                         (*x)->show();
164                 }
165
166                 ++x;
167         }
168
169         for (Marks::iterator x = metric_marks.begin(); x != metric_marks.end(); ++x) {
170                 TempoMarker* tempo_marker;
171
172                 if ((tempo_marker = dynamic_cast<TempoMarker*> (*x)) != 0) {
173                         tempo_marker->update_height_mark ((tempo_marker->tempo().note_types_per_minute() - min_tempo) / max (10.0, max_tempo - min_tempo));
174                 }
175         }
176 }
177
178
179 void
180 Editor::tempo_map_changed (const PropertyChange& /*ignored*/)
181 {
182         if (!_session) {
183                 return;
184         }
185
186         ENSURE_GUI_THREAD (*this, &Editor::tempo_map_changed, ignored);
187
188         if (tempo_lines) {
189                 tempo_lines->tempo_map_changed();
190         }
191
192         compute_bbt_ruler_scale (leftmost_frame, leftmost_frame + current_page_samples());
193         std::vector<TempoMap::BBTPoint> grid;
194         if (bbt_ruler_scale != bbt_show_many) {
195                 compute_current_bbt_points (grid, leftmost_frame, leftmost_frame + current_page_samples());
196         }
197         _session->tempo_map().apply_with_metrics (*this, &Editor::draw_metric_marks); // redraw metric markers
198         draw_measures (grid);
199         update_tempo_based_rulers ();
200 }
201
202 void
203 Editor::tempometric_position_changed (const PropertyChange& /*ignored*/)
204 {
205         if (!_session) {
206                 return;
207         }
208
209         ENSURE_GUI_THREAD (*this, &Editor::tempo_map_changed);
210
211         if (tempo_lines) {
212                 tempo_lines->tempo_map_changed();
213         }
214
215         double max_tempo = 0.0;
216         double min_tempo = DBL_MAX;
217
218         for (Marks::iterator x = metric_marks.begin(); x != metric_marks.end(); ++x) {
219                 TempoMarker* tempo_marker;
220                 MeterMarker* meter_marker;
221                 const TempoSection *ts;
222                 const MeterSection *ms;
223
224                 if ((tempo_marker = dynamic_cast<TempoMarker*> (*x)) != 0) {
225                         if ((ts = &tempo_marker->tempo()) != 0) {
226                                 tempo_marker->set_position (ts->frame ());
227                                 char buf[64];
228
229                                 if (UIConfiguration::instance().get_allow_non_quarter_pulse()) {
230                                         snprintf (buf, sizeof (buf), "%.3f/%.0f", ts->note_types_per_minute(), ts->note_type());
231                                 } else {
232                                         snprintf (buf, sizeof (buf), "%.3f", ts->note_types_per_minute());
233                                 }
234
235                                 tempo_marker->set_name (buf);
236
237                                 max_tempo = max (max_tempo, ts->note_types_per_minute());
238                                 max_tempo = max (max_tempo, ts->end_note_types_per_minute());
239                                 min_tempo = min (min_tempo, ts->note_types_per_minute());
240                                 min_tempo = min (min_tempo, ts->end_note_types_per_minute());
241                         }
242                 }
243                 if ((meter_marker = dynamic_cast<MeterMarker*> (*x)) != 0) {
244                         if ((ms = &meter_marker->meter()) != 0) {
245                                 meter_marker->set_position (ms->frame ());
246                         }
247                 }
248         }
249
250         tempo_curves.sort (CurveComparator());
251
252         const double min_tempo_range = 5.0;
253         const double tempo_delta = fabs (max_tempo - min_tempo);
254
255         if (tempo_delta < min_tempo_range) {
256                 max_tempo += min_tempo_range - tempo_delta;
257                 min_tempo += tempo_delta - min_tempo_range;
258         }
259
260         for (Curves::iterator x = tempo_curves.begin(); x != tempo_curves.end(); ) {
261                 Curves::iterator tmp = x;
262                 (*x)->set_max_tempo (max_tempo);
263                 (*x)->set_min_tempo (min_tempo);
264                 ++tmp;
265                 if (tmp != tempo_curves.end()) {
266                         if (abs ((*tmp)->tempo().note_types_per_minute() - (*x)->tempo().end_note_types_per_minute()) < 2) {
267                                 (*tmp)->set_color_rgba (UIConfiguration::instance().color ("location loop"));
268                         } else {
269                                 (*tmp)->set_color_rgba (UIConfiguration::instance().color ("tempo curve"));
270                         }
271                         (*x)->set_position ((*x)->tempo().frame(), (*tmp)->tempo().frame());
272                 } else {
273                         (*x)->set_position ((*x)->tempo().frame(), UINT32_MAX);
274                 }
275
276                 if (!(*x)->tempo().active()) {
277                         (*x)->hide();
278                 } else {
279                         (*x)->show();
280                 }
281
282                 ++x;
283         }
284
285         for (Marks::iterator x = metric_marks.begin(); x != metric_marks.end(); ++x) {
286                 TempoMarker* tempo_marker;
287                 if ((tempo_marker = dynamic_cast<TempoMarker*> (*x)) != 0) {
288                         tempo_marker->update_height_mark ((tempo_marker->tempo().note_types_per_minute() - min_tempo) / max (max_tempo - min_tempo, 10.0));
289                 }
290         }
291
292         compute_bbt_ruler_scale (leftmost_frame, leftmost_frame + current_page_samples());
293         std::vector<TempoMap::BBTPoint> grid;
294
295         if (bbt_ruler_scale != bbt_show_many) {
296                 compute_current_bbt_points (grid, leftmost_frame, leftmost_frame + current_page_samples());
297         }
298
299         draw_measures (grid);
300         update_tempo_based_rulers ();
301 }
302
303 void
304 Editor::redisplay_tempo (bool immediate_redraw)
305 {
306         if (!_session) {
307                 return;
308         }
309
310         if (immediate_redraw) {
311                 compute_bbt_ruler_scale (leftmost_frame, leftmost_frame + current_page_samples());
312                 std::vector<TempoMap::BBTPoint> grid;
313
314                 if (bbt_ruler_scale != bbt_show_many) {
315                         compute_current_bbt_points (grid, leftmost_frame, leftmost_frame + current_page_samples());
316                 }
317
318                 draw_measures (grid);
319                 update_tempo_based_rulers (); // redraw rulers and measure lines
320
321         } else {
322                 Glib::signal_idle().connect (sigc::bind_return (sigc::bind (sigc::mem_fun (*this, &Editor::redisplay_tempo), true), false));
323         }
324 }
325
326 /* computes a grid starting a beat before and ending a beat after leftmost and rightmost respectively */
327 void
328 Editor::compute_current_bbt_points (std::vector<TempoMap::BBTPoint>& grid, framepos_t leftmost, framepos_t rightmost)
329 {
330         if (!_session) {
331                 return;
332         }
333
334         /* prevent negative values of leftmost from creeping into tempomap
335          */
336         const double lower_beat = floor (max (0.0, _session->tempo_map().beat_at_frame (leftmost))) - 1.0;
337         switch (bbt_ruler_scale) {
338
339         case bbt_show_beats:
340         case bbt_show_ticks:
341         case bbt_show_ticks_detail:
342         case bbt_show_ticks_super_detail:
343                 _session->tempo_map().get_grid (grid, max (_session->tempo_map().frame_at_beat (lower_beat), (framepos_t) 0), rightmost);
344                 break;
345
346         case bbt_show_1:
347                 _session->tempo_map().get_grid (grid, max (_session->tempo_map().frame_at_beat (lower_beat), (framepos_t) 0), rightmost, 1);
348                 break;
349
350         case bbt_show_4:
351                 _session->tempo_map().get_grid (grid, max (_session->tempo_map().frame_at_beat (lower_beat), (framepos_t) 0), rightmost, 4);
352                 break;
353
354         case bbt_show_16:
355                 _session->tempo_map().get_grid (grid, max (_session->tempo_map().frame_at_beat (lower_beat), (framepos_t) 0), rightmost, 16);
356                 break;
357
358         case bbt_show_64:
359                 _session->tempo_map().get_grid (grid, max (_session->tempo_map().frame_at_beat (lower_beat), (framepos_t) 0), rightmost, 64);
360                 break;
361
362         default:
363                 /* bbt_show_many */
364                 _session->tempo_map().get_grid (grid, max (_session->tempo_map().frame_at_beat (lower_beat), (framepos_t) 0), rightmost, 128);
365                 break;
366         }
367 }
368
369 void
370 Editor::hide_measures ()
371 {
372         if (tempo_lines) {
373                 tempo_lines->hide();
374         }
375 }
376
377 void
378 Editor::draw_measures (std::vector<ARDOUR::TempoMap::BBTPoint>& grid)
379 {
380         if (_session == 0 || _show_measures == false || distance (grid.begin(), grid.end()) == 0) {
381                 return;
382         }
383
384         if (tempo_lines == 0) {
385                 tempo_lines = new TempoLines (time_line_group, ArdourCanvas::LineSet::Vertical);
386         }
387
388         const unsigned divisions = get_grid_beat_divisions(leftmost_frame);
389         tempo_lines->draw (grid, divisions, leftmost_frame, _session->frame_rate());
390 }
391
392 void
393 Editor::mouse_add_new_tempo_event (framepos_t frame)
394 {
395         if (_session == 0) {
396                 return;
397         }
398
399         TempoMap& map(_session->tempo_map());
400
401         begin_reversible_command (_("add tempo mark"));
402         const double pulse = map.exact_qn_at_frame (frame, get_grid_music_divisions (0)) / 4.0;
403
404         if (pulse > 0.0) {
405                 XMLNode &before = map.get_state();
406                 /* add music-locked ramped (?) tempo using the bpm/note type at frame*/
407                 map.add_tempo (map.tempo_at_frame (frame), pulse, 0, TempoSection::Ramp, MusicTime);
408
409                 XMLNode &after = map.get_state();
410                 _session->add_command(new MementoCommand<TempoMap>(map, &before, &after));
411                 commit_reversible_command ();
412         }
413
414         //map.dump (cerr);
415 }
416
417 void
418 Editor::mouse_add_new_meter_event (framepos_t frame)
419 {
420         if (_session == 0) {
421                 return;
422         }
423
424
425         TempoMap& map(_session->tempo_map());
426         MeterDialog meter_dialog (map, frame, _("add"));
427
428         switch (meter_dialog.run ()) {
429         case RESPONSE_ACCEPT:
430                 break;
431         default:
432                 return;
433         }
434
435         double bpb = meter_dialog.get_bpb ();
436         bpb = max (1.0, bpb); // XXX is this a reasonable limit?
437
438         double note_type = meter_dialog.get_note_type ();
439
440         Timecode::BBT_Time requested;
441         meter_dialog.get_bbt_time (requested);
442
443         const double beat = map.beat_at_bbt (requested);
444         const double al_frame = map.frame_at_beat (beat);
445         begin_reversible_command (_("add meter mark"));
446         XMLNode &before = map.get_state();
447
448         if (meter_dialog.get_lock_style() == MusicTime) {
449                 map.add_meter (Meter (bpb, note_type), beat, requested, 0, MusicTime);
450         } else {
451                 map.add_meter (Meter (bpb, note_type), beat, requested, al_frame, AudioTime);
452         }
453
454         _session->add_command(new MementoCommand<TempoMap>(map, &before, &map.get_state()));
455         commit_reversible_command ();
456
457         //map.dump (cerr);
458 }
459
460 void
461 Editor::remove_tempo_marker (ArdourCanvas::Item* item)
462 {
463         ArdourMarker* marker;
464         TempoMarker* tempo_marker;
465
466         if ((marker = reinterpret_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
467                 fatal << _("programming error: tempo marker canvas item has no marker object pointer!") << endmsg;
468                 abort(); /*NOTREACHED*/
469         }
470
471         if ((tempo_marker = dynamic_cast<TempoMarker*> (marker)) == 0) {
472                 fatal << _("programming error: marker for tempo is not a tempo marker!") << endmsg;
473                 abort(); /*NOTREACHED*/
474         }
475
476         if (!tempo_marker->tempo().locked_to_meter() && tempo_marker->tempo().active()) {
477                 Glib::signal_idle().connect (sigc::bind (sigc::mem_fun(*this, &Editor::real_remove_tempo_marker), &tempo_marker->tempo()));
478         }
479 }
480
481 void
482 Editor::edit_meter_section (MeterSection* section)
483 {
484         MeterDialog meter_dialog (_session->tempo_map(), *section, _("done"));
485
486         switch (meter_dialog.run()) {
487         case RESPONSE_ACCEPT:
488                 break;
489         default:
490                 return;
491         }
492
493         double bpb = meter_dialog.get_bpb ();
494         bpb = max (1.0, bpb); // XXX is this a reasonable limit?
495
496         double const note_type = meter_dialog.get_note_type ();
497         const Meter meter (bpb, note_type);
498
499         Timecode::BBT_Time when;
500         meter_dialog.get_bbt_time (when);
501         const framepos_t frame = _session->tempo_map().frame_at_bbt (when);
502         const PositionLockStyle pls = (meter_dialog.get_lock_style() == AudioTime) ? AudioTime : MusicTime;
503
504         begin_reversible_command (_("replace meter mark"));
505         XMLNode &before = _session->tempo_map().get_state();
506
507         _session->tempo_map().replace_meter (*section, meter, when, frame, pls);
508
509         XMLNode &after = _session->tempo_map().get_state();
510         _session->add_command(new MementoCommand<TempoMap>(_session->tempo_map(), &before, &after));
511         commit_reversible_command ();
512 }
513
514 void
515 Editor::edit_tempo_section (TempoSection* section)
516 {
517         TempoDialog tempo_dialog (_session->tempo_map(), *section, _("done"));
518
519         switch (tempo_dialog.run ()) {
520         case RESPONSE_ACCEPT:
521                 break;
522         default:
523                 return;
524         }
525
526         double bpm = tempo_dialog.get_bpm ();
527         double nt = tempo_dialog.get_note_type ();
528         bpm = max (0.01, bpm);
529         const Tempo tempo (bpm, nt);
530
531         Timecode::BBT_Time when;
532         tempo_dialog.get_bbt_time (when);
533
534         const TempoSection::Type ttype (tempo_dialog.get_tempo_type());
535
536         begin_reversible_command (_("replace tempo mark"));
537         XMLNode &before = _session->tempo_map().get_state();
538
539         if (tempo_dialog.get_lock_style() == AudioTime) {
540                 framepos_t const f = _session->tempo_map().predict_tempo_position (section, when).second;
541                 _session->tempo_map().replace_tempo (*section, tempo, 0.0, f, ttype, AudioTime);
542         } else {
543                 double const p = _session->tempo_map().predict_tempo_position (section, when).first;
544                 _session->tempo_map().replace_tempo (*section, tempo, p, 0, ttype, MusicTime);
545         }
546
547         XMLNode &after = _session->tempo_map().get_state();
548         _session->add_command (new MementoCommand<TempoMap>(_session->tempo_map(), &before, &after));
549         commit_reversible_command ();
550 }
551
552 void
553 Editor::edit_tempo_marker (TempoMarker& tm)
554 {
555         edit_tempo_section (&tm.tempo());
556 }
557
558 void
559 Editor::edit_meter_marker (MeterMarker& mm)
560 {
561         edit_meter_section (&mm.meter());
562 }
563
564 gint
565 Editor::real_remove_tempo_marker (TempoSection *section)
566 {
567         begin_reversible_command (_("remove tempo mark"));
568         XMLNode &before = _session->tempo_map().get_state();
569         _session->tempo_map().remove_tempo (*section, true);
570         XMLNode &after = _session->tempo_map().get_state();
571         _session->add_command(new MementoCommand<TempoMap>(_session->tempo_map(), &before, &after));
572         commit_reversible_command ();
573
574         return FALSE;
575 }
576
577 void
578 Editor::remove_meter_marker (ArdourCanvas::Item* item)
579 {
580         ArdourMarker* marker;
581         MeterMarker* meter_marker;
582
583         if ((marker = reinterpret_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
584                 fatal << _("programming error: meter marker canvas item has no marker object pointer!") << endmsg;
585                 abort(); /*NOTREACHED*/
586         }
587
588         if ((meter_marker = dynamic_cast<MeterMarker*> (marker)) == 0) {
589                 fatal << _("programming error: marker for meter is not a meter marker!") << endmsg;
590                 abort(); /*NOTREACHED*/
591         }
592
593         if (!meter_marker->meter().initial()) {
594           Glib::signal_idle().connect (sigc::bind (sigc::mem_fun(*this, &Editor::real_remove_meter_marker), &meter_marker->meter()));
595         }
596 }
597
598 gint
599 Editor::real_remove_meter_marker (MeterSection *section)
600 {
601         begin_reversible_command (_("remove tempo mark"));
602         XMLNode &before = _session->tempo_map().get_state();
603         _session->tempo_map().remove_meter (*section, true);
604         XMLNode &after = _session->tempo_map().get_state();
605         _session->add_command(new MementoCommand<TempoMap>(_session->tempo_map(), &before, &after));
606         commit_reversible_command ();
607
608         return FALSE;
609 }