fix some inactive tempi bugs
[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
90         remove_metric_marks (); // also clears tempo curves
91
92         for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
93                 const MeterSection *ms;
94                 const TempoSection *ts;
95
96                 if ((ms = dynamic_cast<const MeterSection*>(*i)) != 0) {
97                         snprintf (buf, sizeof(buf), "%g/%g", ms->divisions_per_bar(), ms->note_divisor ());
98                         if (ms->position_lock_style() == MusicTime) {
99                                 metric_marks.push_back (new MeterMarker (*this, *meter_group, UIConfiguration::instance().color ("meter marker music"), buf,
100                                                                          *(const_cast<MeterSection*>(ms))));
101                         } else {
102                                 metric_marks.push_back (new MeterMarker (*this, *meter_group, UIConfiguration::instance().color ("meter marker"), buf,
103                                                                          *(const_cast<MeterSection*>(ms))));
104                         }
105                 } else if ((ts = dynamic_cast<const TempoSection*>(*i)) != 0) {
106                         if (UIConfiguration::instance().get_allow_non_quarter_pulse()) {
107                                 snprintf (buf, sizeof (buf), "%.3f/%.0f", ts->note_types_per_minute(), ts->note_type());
108                         } else {
109                                 snprintf (buf, sizeof (buf), "%.3f", ts->note_types_per_minute());
110                         }
111
112                         max_tempo = max (max_tempo, ts->note_types_per_minute());
113                         min_tempo = min (min_tempo, ts->note_types_per_minute());
114
115                         tempo_curves.push_back (new TempoCurve (*this, *tempo_group, UIConfiguration::instance().color ("tempo curve"),
116                                                                 *(const_cast<TempoSection*>(ts)), ts->frame(), false));
117                         if (ts->position_lock_style() == MusicTime) {
118                                 metric_marks.push_back (new TempoMarker (*this, *tempo_group, UIConfiguration::instance().color ("tempo marker music"), buf,
119                                                                  *(const_cast<TempoSection*>(ts))));
120                         } else {
121                                 metric_marks.push_back (new TempoMarker (*this, *tempo_group, UIConfiguration::instance().color ("tempo marker"), buf,
122                                                                  *(const_cast<TempoSection*>(ts))));
123                         }
124
125                 }
126
127         }
128         tempo_curves.sort (CurveComparator());
129
130         const double min_tempo_range = 5.0;
131         const double tempo_delta = fabs (max_tempo - min_tempo);
132
133         if (tempo_delta < min_tempo_range) {
134                 max_tempo += min_tempo_range - tempo_delta;
135                 min_tempo += tempo_delta - min_tempo_range;
136         }
137
138         for (Curves::iterator x = tempo_curves.begin(); x != tempo_curves.end(); ) {
139                 Curves::iterator tmp = x;
140                 (*x)->set_max_tempo (max_tempo);
141                 (*x)->set_min_tempo (min_tempo);
142                 ++tmp;
143                 if (tmp != tempo_curves.end()) {
144
145                         if (!(*x)->tempo().active()) {
146                                 (*x)->hide();
147                         } else {
148                                 (*x)->show();
149                         }
150
151                         (*x)->set_position ((*x)->tempo().frame(), (*tmp)->tempo().frame());
152                 } else {
153                         (*x)->set_position ((*x)->tempo().frame(), UINT32_MAX);
154                 }
155                 ++x;
156         }
157
158         for (Marks::iterator x = metric_marks.begin(); x != metric_marks.end(); ++x) {
159                 TempoMarker* tempo_marker;
160
161                 if ((tempo_marker = dynamic_cast<TempoMarker*> (*x)) != 0) {
162                         tempo_marker->update_height_mark ((tempo_marker->tempo().note_types_per_minute() - min_tempo) / max (10.0, max_tempo - min_tempo));
163                 }
164         }
165 }
166
167
168 void
169 Editor::tempo_map_changed (const PropertyChange& /*ignored*/)
170 {
171         if (!_session) {
172                 return;
173         }
174
175         ENSURE_GUI_THREAD (*this, &Editor::tempo_map_changed, ignored);
176
177         if (tempo_lines) {
178                 tempo_lines->tempo_map_changed();
179         }
180
181         compute_bbt_ruler_scale (leftmost_frame, leftmost_frame + current_page_samples());
182         std::vector<TempoMap::BBTPoint> grid;
183         if (bbt_ruler_scale != bbt_show_many) {
184                 compute_current_bbt_points (grid, leftmost_frame, leftmost_frame + current_page_samples());
185         }
186         _session->tempo_map().apply_with_metrics (*this, &Editor::draw_metric_marks); // redraw metric markers
187         draw_measures (grid);
188         update_tempo_based_rulers ();
189 }
190
191 void
192 Editor::marker_position_changed ()
193 {
194         if (!_session) {
195                 return;
196         }
197
198         ENSURE_GUI_THREAD (*this, &Editor::tempo_map_changed);
199
200         if (tempo_lines) {
201                 tempo_lines->tempo_map_changed();
202         }
203
204         double max_tempo = 0.0;
205         double min_tempo = DBL_MAX;
206
207         for (Marks::iterator x = metric_marks.begin(); x != metric_marks.end(); ++x) {
208                 TempoMarker* tempo_marker;
209                 MeterMarker* meter_marker;
210                 const TempoSection *ts;
211                 const MeterSection *ms;
212
213                 if ((tempo_marker = dynamic_cast<TempoMarker*> (*x)) != 0) {
214                         if ((ts = &tempo_marker->tempo()) != 0) {
215                                 tempo_marker->set_position (ts->frame ());
216                                 char buf[64];
217
218                                 if (UIConfiguration::instance().get_allow_non_quarter_pulse()) {
219                                         snprintf (buf, sizeof (buf), "%.3f/%.0f", ts->note_types_per_minute(), ts->note_type());
220                                 } else {
221                                         snprintf (buf, sizeof (buf), "%.3f", ts->note_types_per_minute());
222                                 }
223
224                                 tempo_marker->set_name (buf);
225
226                                 max_tempo = max (max_tempo, ts->note_types_per_minute());
227                                 min_tempo = min (min_tempo, ts->note_types_per_minute());
228                         }
229                 }
230                 if ((meter_marker = dynamic_cast<MeterMarker*> (*x)) != 0) {
231                         if ((ms = &meter_marker->meter()) != 0) {
232                                 meter_marker->set_position (ms->frame ());
233                         }
234                 }
235         }
236
237         tempo_curves.sort (CurveComparator());
238
239         const double min_tempo_range = 5.0;
240         const double tempo_delta = fabs (max_tempo - min_tempo);
241
242         if (tempo_delta < min_tempo_range) {
243                 max_tempo += min_tempo_range - tempo_delta;
244                 min_tempo += tempo_delta - min_tempo_range;
245         }
246
247         for (Curves::iterator x = tempo_curves.begin(); x != tempo_curves.end(); ) {
248                 Curves::iterator tmp = x;
249                 (*x)->set_max_tempo (max_tempo);
250                 (*x)->set_min_tempo (min_tempo);
251                 ++tmp;
252                 if (tmp != tempo_curves.end()) {
253
254                         if (!(*x)->tempo().active()) {
255                                 (*x)->hide();
256                         } else {
257                                 (*x)->show();
258                         }
259
260                         (*x)->set_position ((*x)->tempo().frame(), (*tmp)->tempo().frame());
261                 } else {
262                         (*x)->set_position ((*x)->tempo().frame(), UINT32_MAX);
263                 }
264                 ++x;
265         }
266
267         for (Marks::iterator x = metric_marks.begin(); x != metric_marks.end(); ++x) {
268                 TempoMarker* tempo_marker;
269                 if ((tempo_marker = dynamic_cast<TempoMarker*> (*x)) != 0) {
270                         tempo_marker->update_height_mark ((tempo_marker->tempo().note_types_per_minute() - min_tempo) / max (max_tempo - min_tempo, 10.0));
271                 }
272         }
273
274         compute_bbt_ruler_scale (leftmost_frame, leftmost_frame + current_page_samples());
275         std::vector<TempoMap::BBTPoint> grid;
276
277         if (bbt_ruler_scale != bbt_show_many) {
278                 compute_current_bbt_points (grid, leftmost_frame, leftmost_frame + current_page_samples());
279         }
280
281         draw_measures (grid);
282         update_tempo_based_rulers ();
283 }
284
285 void
286 Editor::redisplay_tempo (bool immediate_redraw)
287 {
288         if (!_session) {
289                 return;
290         }
291
292         if (immediate_redraw) {
293                 compute_bbt_ruler_scale (leftmost_frame, leftmost_frame + current_page_samples());
294                 std::vector<TempoMap::BBTPoint> grid;
295
296                 if (bbt_ruler_scale != bbt_show_many) {
297                         compute_current_bbt_points (grid, leftmost_frame, leftmost_frame + current_page_samples());
298                 }
299
300                 draw_measures (grid);
301                 update_tempo_based_rulers (); // redraw rulers and measure lines
302
303         } else {
304                 Glib::signal_idle().connect (sigc::bind_return (sigc::bind (sigc::mem_fun (*this, &Editor::redisplay_tempo), true), false));
305         }
306 }
307
308 /* computes a grid starting a beat before and ending a beat after leftmost and rightmost respectively */
309 void
310 Editor::compute_current_bbt_points (std::vector<TempoMap::BBTPoint>& grid, framepos_t leftmost, framepos_t rightmost)
311 {
312         if (!_session) {
313                 return;
314         }
315
316         /* prevent negative values of leftmost from creeping into tempomap
317          */
318         const double lower_beat = floor (max (0.0, _session->tempo_map().beat_at_frame (leftmost))) - 1.0;
319         switch (bbt_ruler_scale) {
320
321         case bbt_show_beats:
322         case bbt_show_ticks:
323         case bbt_show_ticks_detail:
324         case bbt_show_ticks_super_detail:
325                 _session->tempo_map().get_grid (grid, max (_session->tempo_map().frame_at_beat (lower_beat), (framepos_t) 0), rightmost);
326                 break;
327
328         case bbt_show_1:
329                 _session->tempo_map().get_grid (grid, max (_session->tempo_map().frame_at_beat (lower_beat), (framepos_t) 0), rightmost, 1);
330                 break;
331
332         case bbt_show_4:
333                 _session->tempo_map().get_grid (grid, max (_session->tempo_map().frame_at_beat (lower_beat), (framepos_t) 0), rightmost, 4);
334                 break;
335
336         case bbt_show_16:
337                 _session->tempo_map().get_grid (grid, max (_session->tempo_map().frame_at_beat (lower_beat), (framepos_t) 0), rightmost, 16);
338                 break;
339
340         case bbt_show_64:
341                 _session->tempo_map().get_grid (grid, max (_session->tempo_map().frame_at_beat (lower_beat), (framepos_t) 0), rightmost, 64);
342                 break;
343
344         default:
345                 /* bbt_show_many */
346                 _session->tempo_map().get_grid (grid, max (_session->tempo_map().frame_at_beat (lower_beat), (framepos_t) 0), rightmost, 128);
347                 break;
348         }
349 }
350
351 void
352 Editor::hide_measures ()
353 {
354         if (tempo_lines) {
355                 tempo_lines->hide();
356         }
357 }
358
359 void
360 Editor::draw_measures (std::vector<ARDOUR::TempoMap::BBTPoint>& grid)
361 {
362         if (_session == 0 || _show_measures == false || distance (grid.begin(), grid.end()) == 0) {
363                 return;
364         }
365
366         if (tempo_lines == 0) {
367                 tempo_lines = new TempoLines (time_line_group, ArdourCanvas::LineSet::Vertical);
368         }
369
370         const unsigned divisions = get_grid_beat_divisions(leftmost_frame);
371         tempo_lines->draw (grid, divisions, leftmost_frame, _session->frame_rate());
372 }
373
374 void
375 Editor::mouse_add_new_tempo_event (framepos_t frame)
376 {
377         if (_session == 0) {
378                 return;
379         }
380
381         TempoMap& map(_session->tempo_map());
382
383         begin_reversible_command (_("add tempo mark"));
384         const double pulse = map.exact_qn_at_frame (frame, get_grid_music_divisions (0)) / 4.0;
385
386         if (pulse > 0.0) {
387                 XMLNode &before = map.get_state();
388                 /* add music-locked ramped (?) tempo using the bpm/note type at frame*/
389                 map.add_tempo (map.tempo_at_frame (frame), pulse, 0, TempoSection::Ramp, MusicTime);
390
391                 XMLNode &after = map.get_state();
392                 _session->add_command(new MementoCommand<TempoMap>(map, &before, &after));
393                 commit_reversible_command ();
394         }
395
396         //map.dump (cerr);
397 }
398
399 void
400 Editor::mouse_add_new_meter_event (framepos_t frame)
401 {
402         if (_session == 0) {
403                 return;
404         }
405
406
407         TempoMap& map(_session->tempo_map());
408         MeterDialog meter_dialog (map, frame, _("add"));
409
410         switch (meter_dialog.run ()) {
411         case RESPONSE_ACCEPT:
412                 break;
413         default:
414                 return;
415         }
416
417         double bpb = meter_dialog.get_bpb ();
418         bpb = max (1.0, bpb); // XXX is this a reasonable limit?
419
420         double note_type = meter_dialog.get_note_type ();
421
422         Timecode::BBT_Time requested;
423         meter_dialog.get_bbt_time (requested);
424
425         const double beat = map.beat_at_bbt (requested);
426         const double al_frame = map.frame_at_beat (beat);
427         begin_reversible_command (_("add meter mark"));
428         XMLNode &before = map.get_state();
429
430         if (meter_dialog.get_lock_style() == MusicTime) {
431                 map.add_meter (Meter (bpb, note_type), beat, requested, 0, MusicTime);
432         } else {
433                 map.add_meter (Meter (bpb, note_type), beat, requested, al_frame, AudioTime);
434         }
435
436         _session->add_command(new MementoCommand<TempoMap>(map, &before, &map.get_state()));
437         commit_reversible_command ();
438
439         //map.dump (cerr);
440 }
441
442 void
443 Editor::remove_tempo_marker (ArdourCanvas::Item* item)
444 {
445         ArdourMarker* marker;
446         TempoMarker* tempo_marker;
447
448         if ((marker = reinterpret_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
449                 fatal << _("programming error: tempo marker canvas item has no marker object pointer!") << endmsg;
450                 abort(); /*NOTREACHED*/
451         }
452
453         if ((tempo_marker = dynamic_cast<TempoMarker*> (marker)) == 0) {
454                 fatal << _("programming error: marker for tempo is not a tempo marker!") << endmsg;
455                 abort(); /*NOTREACHED*/
456         }
457
458         if (!tempo_marker->tempo().locked_to_meter() && tempo_marker->tempo().active()) {
459                 Glib::signal_idle().connect (sigc::bind (sigc::mem_fun(*this, &Editor::real_remove_tempo_marker), &tempo_marker->tempo()));
460         }
461 }
462
463 void
464 Editor::edit_meter_section (MeterSection* section)
465 {
466         MeterDialog meter_dialog (_session->tempo_map(), *section, _("done"));
467
468         switch (meter_dialog.run()) {
469         case RESPONSE_ACCEPT:
470                 break;
471         default:
472                 return;
473         }
474
475         double bpb = meter_dialog.get_bpb ();
476         bpb = max (1.0, bpb); // XXX is this a reasonable limit?
477
478         double const note_type = meter_dialog.get_note_type ();
479         const Meter meter (bpb, note_type);
480
481         Timecode::BBT_Time when;
482         meter_dialog.get_bbt_time (when);
483         const framepos_t frame = _session->tempo_map().frame_at_bbt (when);
484         const PositionLockStyle pls = (meter_dialog.get_lock_style() == AudioTime) ? AudioTime : MusicTime;
485
486         begin_reversible_command (_("replace meter mark"));
487         XMLNode &before = _session->tempo_map().get_state();
488
489         _session->tempo_map().replace_meter (*section, meter, when, frame, pls);
490
491         XMLNode &after = _session->tempo_map().get_state();
492         _session->add_command(new MementoCommand<TempoMap>(_session->tempo_map(), &before, &after));
493         commit_reversible_command ();
494 }
495
496 void
497 Editor::edit_tempo_section (TempoSection* section)
498 {
499         TempoDialog tempo_dialog (_session->tempo_map(), *section, _("done"));
500
501         switch (tempo_dialog.run ()) {
502         case RESPONSE_ACCEPT:
503                 break;
504         default:
505                 return;
506         }
507
508         double bpm = tempo_dialog.get_bpm ();
509         double nt = tempo_dialog.get_note_type ();
510         bpm = max (0.01, bpm);
511         const Tempo tempo (bpm, nt);
512
513         Timecode::BBT_Time when;
514         tempo_dialog.get_bbt_time (when);
515
516         const TempoSection::Type ttype (tempo_dialog.get_tempo_type());
517
518         begin_reversible_command (_("replace tempo mark"));
519         XMLNode &before = _session->tempo_map().get_state();
520
521         if (tempo_dialog.get_lock_style() == AudioTime) {
522                 framepos_t const f = _session->tempo_map().predict_tempo_position (section, when).second;
523                 _session->tempo_map().replace_tempo (*section, tempo, 0.0, f, ttype, AudioTime);
524         } else {
525                 double const p = _session->tempo_map().predict_tempo_position (section, when).first;
526                 _session->tempo_map().replace_tempo (*section, tempo, p, 0, ttype, MusicTime);
527         }
528
529         XMLNode &after = _session->tempo_map().get_state();
530         _session->add_command (new MementoCommand<TempoMap>(_session->tempo_map(), &before, &after));
531         commit_reversible_command ();
532 }
533
534 void
535 Editor::edit_tempo_marker (TempoMarker& tm)
536 {
537         edit_tempo_section (&tm.tempo());
538 }
539
540 void
541 Editor::edit_meter_marker (MeterMarker& mm)
542 {
543         edit_meter_section (&mm.meter());
544 }
545
546 gint
547 Editor::real_remove_tempo_marker (TempoSection *section)
548 {
549         begin_reversible_command (_("remove tempo mark"));
550         XMLNode &before = _session->tempo_map().get_state();
551         _session->tempo_map().remove_tempo (*section, true);
552         XMLNode &after = _session->tempo_map().get_state();
553         _session->add_command(new MementoCommand<TempoMap>(_session->tempo_map(), &before, &after));
554         commit_reversible_command ();
555
556         return FALSE;
557 }
558
559 void
560 Editor::remove_meter_marker (ArdourCanvas::Item* item)
561 {
562         ArdourMarker* marker;
563         MeterMarker* meter_marker;
564
565         if ((marker = reinterpret_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
566                 fatal << _("programming error: meter marker canvas item has no marker object pointer!") << endmsg;
567                 abort(); /*NOTREACHED*/
568         }
569
570         if ((meter_marker = dynamic_cast<MeterMarker*> (marker)) == 0) {
571                 fatal << _("programming error: marker for meter is not a meter marker!") << endmsg;
572                 abort(); /*NOTREACHED*/
573         }
574
575         if (!meter_marker->meter().initial()) {
576           Glib::signal_idle().connect (sigc::bind (sigc::mem_fun(*this, &Editor::real_remove_meter_marker), &meter_marker->meter()));
577         }
578 }
579
580 gint
581 Editor::real_remove_meter_marker (MeterSection *section)
582 {
583         begin_reversible_command (_("remove tempo mark"));
584         XMLNode &before = _session->tempo_map().get_state();
585         _session->tempo_map().remove_meter (*section, true);
586         XMLNode &after = _session->tempo_map().get_state();
587         _session->add_command(new MementoCommand<TempoMap>(_session->tempo_map(), &before, &after));
588         commit_reversible_command ();
589
590         return FALSE;
591 }