86b1b8d65a54c5b4ed5ce12166fd9530f2c07bbf
[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                         max_tempo = max (max_tempo, ts->end_note_types_per_minute());
114                         min_tempo = min (min_tempo, ts->note_types_per_minute());
115                         min_tempo = min (min_tempo, ts->end_note_types_per_minute());
116                         uint32_t const tc_color = UIConfiguration::instance().color ("tempo curve");
117
118                         tempo_curves.push_back (new TempoCurve (*this, *tempo_group, tc_color,
119                                                                 *(const_cast<TempoSection*>(ts)), ts->frame(), false));
120
121                         if (ts->position_lock_style() == MusicTime) {
122                                 metric_marks.push_back (new TempoMarker (*this, *tempo_group, UIConfiguration::instance().color ("tempo marker music"), buf,
123                                                                  *(const_cast<TempoSection*>(ts))));
124                         } else {
125                                 metric_marks.push_back (new TempoMarker (*this, *tempo_group, UIConfiguration::instance().color ("tempo marker"), buf,
126                                                                  *(const_cast<TempoSection*>(ts))));
127                         }
128                 }
129
130         }
131         tempo_curves.sort (CurveComparator());
132
133         const double min_tempo_range = 5.0;
134         const double tempo_delta = fabs (max_tempo - min_tempo);
135
136         if (tempo_delta < min_tempo_range) {
137                 max_tempo += min_tempo_range - tempo_delta;
138                 min_tempo += tempo_delta - min_tempo_range;
139         }
140
141         for (Curves::iterator x = tempo_curves.begin(); x != tempo_curves.end(); ) {
142                 Curves::iterator tmp = x;
143                 (*x)->set_max_tempo (max_tempo);
144                 (*x)->set_min_tempo (min_tempo);
145                 ++tmp;
146                 if (tmp != tempo_curves.end()) {
147                         (*x)->set_position ((*x)->tempo().frame(), (*tmp)->tempo().frame());
148                 } else {
149                         (*x)->set_position ((*x)->tempo().frame(), UINT32_MAX);
150                 }
151
152                 if (!(*x)->tempo().active()) {
153                         (*x)->hide();
154                 } else {
155                         (*x)->show();
156                 }
157
158                 ++x;
159         }
160
161         for (Marks::iterator x = metric_marks.begin(); x != metric_marks.end(); ++x) {
162                 TempoMarker* tempo_marker;
163
164                 if ((tempo_marker = dynamic_cast<TempoMarker*> (*x)) != 0) {
165                         tempo_marker->update_height_mark ((tempo_marker->tempo().note_types_per_minute() - min_tempo) / max (10.0, max_tempo - min_tempo));
166                 }
167         }
168 }
169
170
171 void
172 Editor::tempo_map_changed (const PropertyChange& /*ignored*/)
173 {
174         if (!_session) {
175                 return;
176         }
177
178         ENSURE_GUI_THREAD (*this, &Editor::tempo_map_changed, ignored);
179
180         if (tempo_lines) {
181                 tempo_lines->tempo_map_changed();
182         }
183
184         compute_bbt_ruler_scale (leftmost_frame, leftmost_frame + current_page_samples());
185         std::vector<TempoMap::BBTPoint> grid;
186         if (bbt_ruler_scale != bbt_show_many) {
187                 compute_current_bbt_points (grid, leftmost_frame, leftmost_frame + current_page_samples());
188         }
189         _session->tempo_map().apply_with_metrics (*this, &Editor::draw_metric_marks); // redraw metric markers
190         draw_measures (grid);
191         update_tempo_based_rulers ();
192 }
193
194 void
195 Editor::tempometric_position_changed (const PropertyChange& /*ignored*/)
196 {
197         if (!_session) {
198                 return;
199         }
200
201         ENSURE_GUI_THREAD (*this, &Editor::tempo_map_changed);
202
203         if (tempo_lines) {
204                 tempo_lines->tempo_map_changed();
205         }
206
207         double max_tempo = 0.0;
208         double min_tempo = DBL_MAX;
209
210         for (Marks::iterator x = metric_marks.begin(); x != metric_marks.end(); ++x) {
211                 TempoMarker* tempo_marker;
212                 MeterMarker* meter_marker;
213                 const TempoSection *ts;
214                 const MeterSection *ms;
215
216                 if ((tempo_marker = dynamic_cast<TempoMarker*> (*x)) != 0) {
217                         if ((ts = &tempo_marker->tempo()) != 0) {
218                                 tempo_marker->set_position (ts->frame ());
219                                 char buf[64];
220
221                                 if (UIConfiguration::instance().get_allow_non_quarter_pulse()) {
222                                         snprintf (buf, sizeof (buf), "%.3f/%.0f", ts->note_types_per_minute(), ts->note_type());
223                                 } else {
224                                         snprintf (buf, sizeof (buf), "%.3f", ts->note_types_per_minute());
225                                 }
226
227                                 tempo_marker->set_name (buf);
228
229                                 max_tempo = max (max_tempo, ts->note_types_per_minute());
230                                 max_tempo = max (max_tempo, ts->end_note_types_per_minute());
231                                 min_tempo = min (min_tempo, ts->note_types_per_minute());
232                                 min_tempo = min (min_tempo, ts->end_note_types_per_minute());
233                         }
234                 }
235                 if ((meter_marker = dynamic_cast<MeterMarker*> (*x)) != 0) {
236                         if ((ms = &meter_marker->meter()) != 0) {
237                                 meter_marker->set_position (ms->frame ());
238                         }
239                 }
240         }
241
242         tempo_curves.sort (CurveComparator());
243
244         const double min_tempo_range = 5.0;
245         const double tempo_delta = fabs (max_tempo - min_tempo);
246
247         if (tempo_delta < min_tempo_range) {
248                 max_tempo += min_tempo_range - tempo_delta;
249                 min_tempo += tempo_delta - min_tempo_range;
250         }
251
252         for (Curves::iterator x = tempo_curves.begin(); x != tempo_curves.end(); ) {
253                 Curves::iterator tmp = x;
254                 (*x)->set_max_tempo (max_tempo);
255                 (*x)->set_min_tempo (min_tempo);
256                 ++tmp;
257                 if (tmp != tempo_curves.end()) {
258                         (*x)->set_position ((*x)->tempo().frame(), (*tmp)->tempo().frame());
259                 } else {
260                         (*x)->set_position ((*x)->tempo().frame(), UINT32_MAX);
261                 }
262
263                 if (!(*x)->tempo().active()) {
264                         (*x)->hide();
265                 } else {
266                         (*x)->show();
267                 }
268
269                 ++x;
270         }
271
272         for (Marks::iterator x = metric_marks.begin(); x != metric_marks.end(); ++x) {
273                 TempoMarker* tempo_marker;
274                 if ((tempo_marker = dynamic_cast<TempoMarker*> (*x)) != 0) {
275                         tempo_marker->update_height_mark ((tempo_marker->tempo().note_types_per_minute() - min_tempo) / max (max_tempo - min_tempo, 10.0));
276                 }
277         }
278
279         compute_bbt_ruler_scale (leftmost_frame, leftmost_frame + current_page_samples());
280         std::vector<TempoMap::BBTPoint> grid;
281
282         if (bbt_ruler_scale != bbt_show_many) {
283                 compute_current_bbt_points (grid, leftmost_frame, leftmost_frame + current_page_samples());
284         }
285
286         draw_measures (grid);
287         update_tempo_based_rulers ();
288 }
289
290 void
291 Editor::redisplay_tempo (bool immediate_redraw)
292 {
293         if (!_session) {
294                 return;
295         }
296
297         if (immediate_redraw) {
298                 compute_bbt_ruler_scale (leftmost_frame, leftmost_frame + current_page_samples());
299                 std::vector<TempoMap::BBTPoint> grid;
300
301                 if (bbt_ruler_scale != bbt_show_many) {
302                         compute_current_bbt_points (grid, leftmost_frame, leftmost_frame + current_page_samples());
303                 }
304
305                 draw_measures (grid);
306                 update_tempo_based_rulers (); // redraw rulers and measure lines
307
308         } else {
309                 Glib::signal_idle().connect (sigc::bind_return (sigc::bind (sigc::mem_fun (*this, &Editor::redisplay_tempo), true), false));
310         }
311 }
312 void
313 Editor::tempo_curve_selected (TempoSection* ts, bool yn)
314 {
315         for (Curves::iterator x = tempo_curves.begin(); x != tempo_curves.end(); ++x) {
316                 if (&(*x)->tempo() == ts && yn) {
317                         (*x)->set_color_rgba (UIConfiguration::instance().color ("location marker"));
318                         break;
319                 } else if (&(*x)->tempo() == ts) {
320                         (*x)->set_color_rgba (UIConfiguration::instance().color ("tempo curve"));
321                         break;
322                 }
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, 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         begin_reversible_command (_("replace tempo mark"));
535         XMLNode &before = _session->tempo_map().get_state();
536
537         if (tempo_dialog.get_lock_style() == AudioTime) {
538                 framepos_t const f = _session->tempo_map().predict_tempo_position (section, when).second;
539                 _session->tempo_map().replace_tempo (*section, tempo, 0.0, f, AudioTime);
540         } else {
541                 double const p = _session->tempo_map().predict_tempo_position (section, when).first;
542                 _session->tempo_map().replace_tempo (*section, tempo, p, 0, MusicTime);
543         }
544
545         XMLNode &after = _session->tempo_map().get_state();
546         _session->add_command (new MementoCommand<TempoMap>(_session->tempo_map(), &before, &after));
547         commit_reversible_command ();
548 }
549
550 void
551 Editor::edit_tempo_marker (TempoMarker& tm)
552 {
553         edit_tempo_section (&tm.tempo());
554 }
555
556 void
557 Editor::edit_meter_marker (MeterMarker& mm)
558 {
559         edit_meter_section (&mm.meter());
560 }
561
562 gint
563 Editor::real_remove_tempo_marker (TempoSection *section)
564 {
565         begin_reversible_command (_("remove tempo mark"));
566         XMLNode &before = _session->tempo_map().get_state();
567         _session->tempo_map().remove_tempo (*section, true);
568         XMLNode &after = _session->tempo_map().get_state();
569         _session->add_command(new MementoCommand<TempoMap>(_session->tempo_map(), &before, &after));
570         commit_reversible_command ();
571
572         return FALSE;
573 }
574
575 void
576 Editor::remove_meter_marker (ArdourCanvas::Item* item)
577 {
578         ArdourMarker* marker;
579         MeterMarker* meter_marker;
580
581         if ((marker = reinterpret_cast<ArdourMarker *> (item->get_data ("marker"))) == 0) {
582                 fatal << _("programming error: meter marker canvas item has no marker object pointer!") << endmsg;
583                 abort(); /*NOTREACHED*/
584         }
585
586         if ((meter_marker = dynamic_cast<MeterMarker*> (marker)) == 0) {
587                 fatal << _("programming error: marker for meter is not a meter marker!") << endmsg;
588                 abort(); /*NOTREACHED*/
589         }
590
591         if (!meter_marker->meter().initial()) {
592           Glib::signal_idle().connect (sigc::bind (sigc::mem_fun(*this, &Editor::real_remove_meter_marker), &meter_marker->meter()));
593         }
594 }
595
596 gint
597 Editor::real_remove_meter_marker (MeterSection *section)
598 {
599         begin_reversible_command (_("remove tempo mark"));
600         XMLNode &before = _session->tempo_map().get_state();
601         _session->tempo_map().remove_meter (*section, true);
602         XMLNode &after = _session->tempo_map().get_state();
603         _session->add_command(new MementoCommand<TempoMap>(_session->tempo_map(), &before, &after));
604         commit_reversible_command ();
605
606         return FALSE;
607 }