all new implementation of audio clocks, with entirely new editing model. not entirely...
[ardour.git] / gtk2_ardour / audio_clock.cc
1 /*
2     Copyright (C) 1999 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 #include <cstdio> // for sprintf
21 #include <cmath>
22
23 #include "pbd/convert.h"
24 #include "pbd/enumwriter.h"
25
26 #include <gtkmm/style.h>
27
28 #include "gtkmm2ext/cairocell.h"
29 #include "gtkmm2ext/utils.h"
30 #include "gtkmm2ext/rgb_macros.h"
31
32 #include "ardour/ardour.h"
33 #include "ardour/session.h"
34 #include "ardour/tempo.h"
35 #include "ardour/profile.h"
36 #include <sigc++/bind.h>
37
38 #include "ardour_ui.h"
39 #include "audio_clock.h"
40 #include "utils.h"
41 #include "keyboard.h"
42 #include "gui_thread.h"
43 #include "i18n.h"
44
45 using namespace ARDOUR;
46 using namespace PBD;
47 using namespace Gtk;
48 using namespace std;
49
50 using Gtkmm2ext::Keyboard;
51
52 sigc::signal<void> AudioClock::ModeChanged;
53 vector<AudioClock*> AudioClock::clocks;
54 const double AudioClock::info_font_scale_factor = 0.6;
55 const double AudioClock::separator_height = 2.0;
56
57 AudioClock::AudioClock (const string& clock_name, bool transient, const string& widget_name,
58                         bool allow_edit, bool follows_playhead, bool duration, bool with_info)
59         : _name (clock_name)
60         , is_transient (transient)
61         , is_duration (duration)
62         , editable (allow_edit)
63         , _follows_playhead (follows_playhead)
64         , _off (false)
65         , _need_bg (true)
66         , ops_menu (0)
67         , editing_attr (0)
68         , background_attr (0)
69         , foreground_attr (0)
70         , mode_based_info_ratio (1.0)
71         , editing (false)
72         , bbt_reference_time (-1)
73         , last_when(0)
74         , last_pdelta (0)
75         , last_sdelta (0)
76         , dragging (false)
77         , drag_field (Field (0))
78         , _canonical_time_is_displayed (true)
79         , _canonical_time (0)
80
81 {
82         set_flags (CAN_FOCUS);
83
84         _layout = Pango::Layout::create (get_pango_context());
85         _layout->set_attributes (normal_attributes);
86
87         if (with_info) {
88                 _left_layout = Pango::Layout::create (get_pango_context());
89                 _right_layout = Pango::Layout::create (get_pango_context());
90         }
91
92         set_widget_name (widget_name);
93
94         _mode = BBT; /* lie to force mode switch */
95         set_mode (Timecode);
96         set (last_when, true);
97
98         if (!is_transient) {
99                 clocks.push_back (this);
100         }
101 }
102
103 AudioClock::~AudioClock ()
104 {
105         delete background_attr;
106         delete foreground_attr;
107         delete editing_attr;
108 }
109
110 void
111 AudioClock::set_widget_name (const string& str)
112 {
113         if (str.empty()) {
114                 set_name ("clock");
115         } else {
116                 set_name (str + " clock");
117         }
118 }
119
120
121 void
122 AudioClock::on_realize ()
123 {
124         CairoWidget::on_realize ();
125         set_font ();
126         set_colors ();
127 }
128
129 void
130 AudioClock::set_font ()
131 {
132         Glib::RefPtr<Gtk::Style> style = get_style ();
133         Pango::FontDescription font; 
134         Pango::AttrFontDesc* font_attr;
135
136         if (!is_realized()) {
137                 font = get_font_for_style (get_name());
138         } else {
139                 font = style->get_font();
140         }
141
142         font_attr = new Pango::AttrFontDesc (Pango::Attribute::create_attr_font_desc (font));
143
144         normal_attributes.change (*font_attr);
145         editing_attributes.change (*font_attr);
146
147         /* now a smaller version of the same font */
148
149         delete font_attr;
150         font.set_size ((int) lrint (font.get_size() * info_font_scale_factor));
151         font.set_weight (Pango::WEIGHT_NORMAL);
152         font_attr = new Pango::AttrFontDesc (Pango::Attribute::create_attr_font_desc (font));
153  
154         info_attributes.change (*font_attr);
155         
156         delete font_attr;
157 }
158
159 void
160 AudioClock::set_active_state (Gtkmm2ext::ActiveState s)
161 {
162         CairoWidget::set_active_state (s);
163         set_colors ();
164 }
165
166 void
167 AudioClock::set_colors ()
168 {
169         int r, g, b, a;
170
171         uint32_t bg_color;
172         uint32_t text_color;
173         uint32_t editing_color;
174
175         if (active_state()) {
176                 bg_color = ARDOUR_UI::config()->color_by_name (string_compose ("%1 active: background", get_name()));
177                 text_color = ARDOUR_UI::config()->color_by_name (string_compose ("%1 active: text", get_name()));
178                 editing_color = ARDOUR_UI::config()->color_by_name (string_compose ("%1 active: edited text", get_name()));
179         } else {
180                 bg_color = ARDOUR_UI::config()->color_by_name (string_compose ("%1: background", get_name()));
181                 text_color = ARDOUR_UI::config()->color_by_name (string_compose ("%1: text", get_name()));
182                 editing_color = ARDOUR_UI::config()->color_by_name (string_compose ("%1: edited text", get_name()));
183         }
184
185         UINT_TO_RGBA (bg_color, &r, &g, &b, &a);
186         r = lrint ((r/256.0) * 65535.0);
187         g = lrint ((g/256.0) * 65535.0);
188         b = lrint ((b/256.0) * 65535.0);
189         background_attr = new Pango::AttrColor (Pango::Attribute::create_attr_background (r, g, b));
190
191         /* store for bg in ::render() */
192         bg_r = r/256.0;
193         bg_g = g/256.0;
194         bg_b = b/256.0;
195         bg_a = a/256.0;
196
197         UINT_TO_RGBA (text_color, &r, &g, &b, &a);
198         r = lrint ((r/256.0) * 65535.0);
199         g = lrint ((g/256.0) * 65535.0);
200         b = lrint ((b/256.0) * 65535.0);
201         foreground_attr = new Pango::AttrColor (Pango::Attribute::create_attr_foreground (r, g, b));
202
203         UINT_TO_RGBA (editing_color, &r, &g, &b, &a);
204         r = lrint ((r/256.0) * 65535.0);
205         g = lrint ((g/256.0) * 65535.0);
206         b = lrint ((b/256.0) * 65535.0);
207         editing_attr = new Pango::AttrColor (Pango::Attribute::create_attr_foreground (r, g, b));
208         
209         normal_attributes.change (*background_attr);
210         normal_attributes.change (*foreground_attr);
211
212         info_attributes.change (*background_attr);
213         info_attributes.change (*foreground_attr);
214
215         editing_attributes.change (*background_attr);
216         editing_attributes.change (*foreground_attr);
217         editing_attributes.change (*editing_attr);
218
219         if (!editing) {
220                 _layout->set_attributes (normal_attributes);
221         } else {
222                 _layout->set_attributes (editing_attributes);
223         }
224
225         if (_left_layout) {
226                 _left_layout->set_attributes (info_attributes);
227                 _right_layout->set_attributes (info_attributes);
228         }
229
230         queue_draw ();
231 }
232
233 void
234 AudioClock::render (cairo_t* cr)
235 {
236         if (_need_bg) {
237                 /* paint entire area the color of the parent window bg 
238                    
239                    XXX try to optimize this so that we just paint the corners and
240                    other areas that may be exposed by rounded corners.
241                 */
242                 
243                 Gdk::Color bg (get_parent_bg());
244                 cairo_rectangle (cr, 0, 0, _width, _height);
245                 cairo_stroke_preserve (cr);
246                 cairo_set_source_rgb (cr, bg.get_red_p(), bg.get_green_p(), bg.get_blue_p());
247                 cairo_fill (cr);
248         }
249
250         /* main layout: rounded rect, plus the text */
251         
252         cairo_set_source_rgba (cr, bg_r, bg_g, bg_b, bg_a);
253         Gtkmm2ext::rounded_rectangle (cr, 0, 0, _width, upper_height, 9);
254
255         cairo_move_to (cr, 6, (upper_height - layout_height) / 2.0);
256         pango_cairo_show_layout (cr, _layout->gobj());
257
258         if (_left_layout) {
259
260                 double h = _height - upper_height - separator_height;
261
262                 if (mode_based_info_ratio != 1.0) {
263
264                         double left_rect_width = round (((_width - separator_height) * mode_based_info_ratio) + 0.5);
265
266                         cairo_set_source_rgba (cr, bg_r, bg_g, bg_b, bg_a);
267                         Gtkmm2ext::rounded_rectangle (cr, 0, upper_height + separator_height, left_rect_width, h, 9);
268                         
269                         cairo_move_to (cr, 6, upper_height + separator_height + ((h - info_height)/2.0));
270                         pango_cairo_show_layout (cr, _left_layout->gobj());
271                         
272                         Gtkmm2ext::rounded_rectangle (cr, left_rect_width + separator_height, upper_height + separator_height, 
273                                                       _width - separator_height - left_rect_width, h, 9);
274                         
275                         cairo_move_to (cr, 6 + left_rect_width + separator_height, upper_height + separator_height + ((h - info_height)/2.0));
276                         pango_cairo_show_layout (cr, _right_layout->gobj());
277
278                 } else {
279                         /* no info to display, or just one */
280
281                         cairo_set_source_rgba (cr, bg_r, bg_g, bg_b, bg_a);
282                         Gtkmm2ext::rounded_rectangle (cr, 0, upper_height + separator_height, _width, h, 9);
283                 }
284         }
285 }
286
287 void
288 AudioClock::on_size_allocate (Gtk::Allocation& alloc)
289 {
290         CairoWidget::on_size_allocate (alloc);
291         
292         if (_left_layout) {
293                 upper_height = (_height/2.0) - 1.0;
294         } else {
295                 upper_height = _height;
296         }
297 }
298
299 void
300 AudioClock::on_size_request (Gtk::Requisition* req)
301 {
302         Glib::RefPtr<Pango::Layout> tmp;
303         Glib::RefPtr<Gtk::Style> style = get_style ();
304         Pango::FontDescription font; 
305
306         tmp = Pango::Layout::create (get_pango_context());
307
308         if (!is_realized()) {
309                 font = get_font_for_style (get_name());
310         } else {
311                 font = style->get_font();
312         }
313
314         tmp->set_font_description (font);
315
316         /* this string is the longest thing we will ever display,
317            and also includes the BBT "|" that may descends below
318            the baseline a bit, and a comma for the minsecs mode
319            where we printf a fractional value (XXX or should)
320         */
321
322         tmp->set_text (" 88|88:88:88,88"); 
323
324         tmp->get_pixel_size (req->width, req->height);
325
326         layout_height = req->height;
327         layout_width = req->width;
328
329         /* now tackle height, for which we need to know the height of the lower
330          * layout
331          */
332
333         if (_left_layout) {
334
335                 int w;
336
337                 font.set_size ((int) lrint (font.get_size() * info_font_scale_factor));
338                 font.set_weight (Pango::WEIGHT_NORMAL);
339                 tmp->set_font_description (font);
340
341                 /* we only care about height, so put as much stuff in here
342                    as possible that might change the height.
343                 */
344                 tmp->set_text ("qyhH|"); /* one ascender, one descender */
345                 
346                 tmp->get_pixel_size (w, info_height);
347                 
348                 info_height += 4;
349
350                 req->height += info_height;
351                 req->height += separator_height;
352         }
353 }
354
355 void
356 AudioClock::show_edit_status (int length)
357 {
358         editing_attr->set_start_index (edit_string.length() - length);
359         editing_attr->set_end_index (edit_string.length());
360         
361         editing_attributes.change (*background_attr);
362         editing_attributes.change (*foreground_attr);
363         editing_attributes.change (*editing_attr);
364
365         _layout->set_attributes (editing_attributes);
366 }
367
368 void
369 AudioClock::start_edit ()
370 {
371         edit_string = _layout->get_text ();
372         pre_edit_string = edit_string;
373         input_string.clear ();
374         editing = true;
375
376         show_edit_status (1);
377
378         Keyboard::magic_widget_grab_focus ();
379         grab_focus ();
380 }
381
382 void
383 AudioClock::end_edit (bool modify)
384 {
385         if (modify) {
386
387                 bool ok = true;
388                 
389                 switch (_mode) {
390                 case Timecode:
391                         ok = timecode_validate_edit (edit_string);
392                         break;
393                         
394                 case BBT:
395                         ok = bbt_validate_edit (edit_string);
396                         break;
397                         
398                 case MinSec:
399                         break;
400                         
401                 case Frames:
402                         break;
403                 }
404                 
405                 if (!ok) {
406                         edit_string = pre_edit_string;
407                         input_string.clear ();
408                         _layout->set_text (edit_string);
409                 } else {
410                         editing = false;
411                         _layout->set_attributes (normal_attributes);
412                         ValueChanged(); /* EMIT_SIGNAL */
413                 }
414
415         } else {
416                 editing = false;
417                 _layout->set_text (pre_edit_string);
418         }
419
420         queue_draw ();
421
422         if (!editing) {
423
424                 /* move focus back to the default widget in the top level window */
425                 
426                 Keyboard::magic_widget_drop_focus ();
427                 
428                 Widget* top = get_toplevel();
429                 
430                 if (top->is_toplevel ()) {
431                         Window* win = dynamic_cast<Window*> (top);
432                         win->grab_focus ();
433                 }
434         }
435 }
436
437 void
438 AudioClock::session_configuration_changed (std::string p)
439 {
440         if (p != "timecode-offset" && p != "timecode-offset-negative") {
441                 return;
442         }
443
444         framecnt_t current;
445
446         switch (_mode) {
447         case Timecode:
448                 if (is_duration) {
449                         current = current_duration ();
450                 } else {
451                         current = current_time ();
452                 }
453                 set (current, true);
454                 break;
455         default:
456                 break;
457         }
458 }
459
460 void
461 AudioClock::set (framepos_t when, bool force, framecnt_t offset, char which)
462 {
463         if ((!force && !is_visible()) || _session == 0) {
464                 return;
465         }
466
467         bool const pdelta = Config->get_primary_clock_delta_edit_cursor ();
468         bool const sdelta = Config->get_secondary_clock_delta_edit_cursor ();
469
470         if (offset && which == 'p' && pdelta) {
471                 when = (when > offset) ? when - offset : offset - when;
472         } else if (offset && which == 's' && sdelta) {
473                 when = (when > offset) ? when - offset : offset - when;
474         }
475
476         if (when == last_when && !force) {
477                 return;
478         }
479
480         if (which == 'p' && pdelta && !last_pdelta) {
481                 set_name("TransportClockDisplayDelta");
482                 last_pdelta = true;
483         } else if (which == 'p' && !pdelta && last_pdelta) {
484                 set_name("TransportClockDisplay");
485                 last_pdelta = false;
486         } else if (which == 's' && sdelta && !last_sdelta) {
487                 set_name("SecondaryClockDisplayDelta");
488                 last_sdelta = true;
489         } else if (which == 's' && !sdelta && last_sdelta) {
490                 set_name("SecondaryClockDisplay");
491                 last_sdelta = false;
492         }
493
494         if (!editing) {
495
496                 switch (_mode) {
497                 case Timecode:
498                         set_timecode (when, force);
499                         break;
500                         
501                 case BBT:
502                         set_bbt (when, force);
503                         break;
504                         
505                 case MinSec:
506                         set_minsec (when, force);
507                         break;
508                         
509                 case Frames:
510                         set_frames (when, force);
511                         break;
512                 }
513         }
514
515         if (when != last_when || force) {
516                 queue_draw ();
517         }
518
519         last_when = when;
520
521         /* we're setting the time from a frames value, so keep it as the canonical value */
522         _canonical_time = when;
523         _canonical_time_is_displayed = false;
524 }
525
526 void
527 AudioClock::set_frames (framepos_t when, bool /*force*/)
528 {
529         char buf[32];
530
531         if (_off) {
532                 /* XXX with cairo, we can do better, surely? */
533
534                 _layout->set_text ("-----------");
535
536                 if (_left_layout) {
537                         _left_layout->set_text ("");
538                         _right_layout->set_text ("");
539                 }
540                 
541                 return;
542         }
543         
544         snprintf (buf, sizeof (buf), "%10" PRId64, when);
545         _layout->set_text (buf);
546
547         if (_left_layout) {
548                 framecnt_t rate = _session->frame_rate();
549
550                 if (fmod (rate, 1000.0) == 0.000) {
551                         sprintf (buf, "%" PRId64 "K", rate/1000);
552                 } else {
553                         sprintf (buf, "%" PRId64, rate);
554                 }
555
556                 _left_layout->set_text (buf);
557
558                 float vid_pullup = _session->config.get_video_pullup();
559
560                 if (vid_pullup == 0.0) {
561                         _right_layout->set_text (_("none"));
562                 } else {
563                         sprintf (buf, "%-6.4f", vid_pullup);
564                         _right_layout->set_text (buf);
565                 }
566         }
567 }
568
569 void
570 AudioClock::set_minsec (framepos_t when, bool force)
571 {
572         char buf[32];
573         framecnt_t left;
574         int hrs;
575         int mins;
576         int secs;
577         int millisecs;
578
579         if (_off) {
580                 _layout->set_text ("--:--:--");
581
582                 if (_left_layout) {
583                         _left_layout->set_text ("");
584                         _right_layout->set_text ("");
585                 }
586                 
587                 return;
588         }       
589
590         left = when;
591         hrs = (int) floor (left / (_session->frame_rate() * 60.0f * 60.0f));
592         left -= (framecnt_t) floor (hrs * _session->frame_rate() * 60.0f * 60.0f);
593         mins = (int) floor (left / (_session->frame_rate() * 60.0f));
594         left -= (framecnt_t) floor (mins * _session->frame_rate() * 60.0f);
595         secs = (int) floor (left / (float) _session->frame_rate());
596         left -= (framecnt_t) floor (secs * _session->frame_rate());
597         millisecs = floor (left * 1000.0 / (float) _session->frame_rate());
598
599         snprintf (buf, sizeof (buf), "%02" PRIu32 ":%02" PRIu32 ":%02" PRIu32 ".%03" PRIu32, hrs, mins, secs, millisecs);
600         _layout->set_text (buf);
601 }
602
603 void
604 AudioClock::set_timecode (framepos_t when, bool force)
605 {
606         char buf[32];
607         Timecode::Time TC;
608         
609         if (_off) {
610                 _layout->set_text ("--:--:--:--");
611                 if (_left_layout) {
612                         _left_layout->set_text ("");
613                         _right_layout->set_text ("");
614                 }
615                 
616                 return;
617         }
618
619         if (is_duration) {
620                 _session->timecode_duration (when, TC);
621         } else {
622                 _session->timecode_time (when, TC);
623         }
624         
625         if (TC.negative) {
626                 snprintf (buf, sizeof (buf), "-%02" PRIu32 ":%02" PRIu32 ":%02" PRIu32 ":%02" PRIu32, TC.hours, TC.minutes, TC.seconds, TC.frames);
627         } else {
628                 snprintf (buf, sizeof (buf), " %02" PRIu32 ":%02" PRIu32 ":%02" PRIu32 ":%02" PRIu32, TC.hours, TC.minutes, TC.seconds, TC.frames);
629         }
630
631         _layout->set_text (buf);
632
633         if (_right_layout) {
634                 double timecode_frames = _session->timecode_frames_per_second();
635         
636                 if (fmod(timecode_frames, 1.0) == 0.0) {
637                         sprintf (buf, "FPS %u %s", int (timecode_frames), (_session->timecode_drop_frames() ? "D" : ""));
638                 } else {
639                         sprintf (buf, "%.2f %s", timecode_frames, (_session->timecode_drop_frames() ? "D" : ""));
640                 }
641
642                 _right_layout->set_text (buf);
643         }
644 }
645
646 void
647 AudioClock::set_bbt (framepos_t when, bool force)
648 {
649         char buf[16];
650         Timecode::BBT_Time BBT;
651
652         if (_off) {
653                 _layout->set_text ("--|--|--");
654                 if (_left_layout) {
655                         _left_layout->set_text ("");
656                         _right_layout->set_text ("");
657                 }
658                 return;
659         }
660
661         /* handle a common case */
662         if (is_duration) {
663                 if (when == 0) {
664                         BBT.bars = 0;
665                         BBT.beats = 0;
666                         BBT.ticks = 0;
667                 } else {
668                         _session->tempo_map().bbt_time (when, BBT);
669                         BBT.bars--;
670                         BBT.beats--;
671                 }
672         } else {
673                 _session->tempo_map().bbt_time (when, BBT);
674         }
675
676         snprintf (buf, sizeof (buf), "%02" PRIu32 "|%02" PRIu32 "|%04" PRIu32, BBT.bars, BBT.beats, BBT.ticks);
677         _layout->set_text (buf);
678                  
679         if (_right_layout) {
680                 framepos_t pos;
681
682                 if (bbt_reference_time < 0) {
683                         pos = when;
684                 } else {
685                         pos = bbt_reference_time;
686                 }
687
688                 TempoMetric m (_session->tempo_map().metric_at (pos));
689
690                 sprintf (buf, "%-5.2f", m.tempo().beats_per_minute());
691                 _left_layout->set_text (buf);
692
693                 sprintf (buf, "%g|%g", m.meter().beats_per_bar(), m.meter().note_divisor());
694                 _right_layout->set_text (buf);
695         }
696 }
697
698 void
699 AudioClock::set_session (Session *s)
700 {
701         SessionHandlePtr::set_session (s);
702
703         if (_session) {
704
705                 _session->config.ParameterChanged.connect (_session_connections, invalidator (*this), boost::bind (&AudioClock::session_configuration_changed, this, _1), gui_context());
706
707                 const XMLProperty* prop;
708                 XMLNode* node = _session->extra_xml (X_("ClockModes"));
709                 AudioClock::Mode amode;
710
711                 if (node) {
712                         for (XMLNodeList::const_iterator i = node->children().begin(); i != node->children().end(); ++i) {
713                                 if ((prop = (*i)->property (X_("name"))) && prop->value() == _name) {
714
715                                         if ((prop = (*i)->property (X_("mode"))) != 0) {
716                                                 amode = AudioClock::Mode (string_2_enum (prop->value(), amode));
717                                                 set_mode (amode);
718                                         }
719                                         if ((prop = (*i)->property (X_("on"))) != 0) {
720                                                 set_off (!string_is_affirmative (prop->value()));
721                                         }
722                                         break;
723                                 }
724                         }
725                 }
726
727                 set (last_when, true);
728         }
729 }
730
731 bool
732 AudioClock::on_key_press_event (GdkEventKey* ev)
733 {
734         if (!editing) {
735                 return false;
736         }
737         
738         /* return true for keys that we MIGHT use 
739            at release
740         */
741         switch (ev->keyval) {
742         case GDK_0:
743         case GDK_KP_0:
744         case GDK_1:
745         case GDK_KP_1:
746         case GDK_2:
747         case GDK_KP_2:
748         case GDK_3:
749         case GDK_KP_3:
750         case GDK_4:
751         case GDK_KP_4:
752         case GDK_5:
753         case GDK_KP_5:
754         case GDK_6:
755         case GDK_KP_6:
756         case GDK_7:
757         case GDK_KP_7:
758         case GDK_8:
759         case GDK_KP_8:
760         case GDK_9:
761         case GDK_KP_9:
762         case GDK_period:
763         case GDK_comma:
764         case GDK_KP_Decimal:
765         case GDK_Tab:
766         case GDK_Return:
767         case GDK_KP_Enter:
768         case GDK_Escape:
769                 return true;
770         default:
771                 return false;
772         }
773 }
774
775 bool
776 AudioClock::on_key_release_event (GdkEventKey *ev)
777 {
778         if (!editing) {
779                 return false;
780         }
781
782         string new_text;
783         char new_char = 0;
784
785         switch (ev->keyval) {
786         case GDK_0:
787         case GDK_KP_0:
788                 new_char = '0';
789                 break;
790         case GDK_1:
791         case GDK_KP_1:
792                 new_char = '1';
793                 break;
794         case GDK_2:
795         case GDK_KP_2:
796                 new_char = '2';
797                 break;
798         case GDK_3:
799         case GDK_KP_3:
800                 new_char = '3';
801                 break;
802         case GDK_4:
803         case GDK_KP_4:
804                 new_char = '4';
805                 break;
806         case GDK_5:
807         case GDK_KP_5:
808                 new_char = '5';
809                 break;
810         case GDK_6:
811         case GDK_KP_6:
812                 new_char = '6';
813                 break;
814         case GDK_7:
815         case GDK_KP_7:
816                 new_char = '7';
817                 break;
818         case GDK_8:
819         case GDK_KP_8:
820                 new_char = '8';
821                 break;
822         case GDK_9:
823         case GDK_KP_9:
824                 new_char = '9';
825                 break;
826
827         case GDK_Tab:
828         case GDK_Return:
829         case GDK_KP_Enter:
830                 end_edit (true);
831                 return true;
832                 break;
833
834         case GDK_Escape:
835                 end_edit (false);
836                 ChangeAborted();  /*  EMIT SIGNAL  */
837                 return true;
838
839         default:
840                 return false;
841         }
842
843         if (input_string.length() >= insert_max) {
844                 /* eat the key event, but do no nothing with it */
845                 return true;
846         }
847
848         input_string.insert (input_string.begin(), new_char);
849         
850         string::reverse_iterator ri;
851         vector<int> insert_at;
852         int highlight_length;
853         
854         /* merge with pre-edit-string into edit string */
855         
856         switch (_mode) {
857         case Frames:
858                 edit_string = input_string;
859                 highlight_length = edit_string.length();
860                 break;
861                 
862         default:
863                 edit_string = pre_edit_string;
864                 
865                 /* backup through the original string, till we have
866                  * enough digits locations to put all the digits from
867                  * the input string.
868                  */
869                 
870                 for (ri = edit_string.rbegin(); ri != edit_string.rend(); ++ri) {
871                         if (isdigit (*ri)) {
872                                 insert_at.push_back (edit_string.length() - (ri - edit_string.rbegin()) - 1);
873                                 if (insert_at.size() == input_string.length()) {
874                                         break;
875                                 }
876                         }
877                 }
878                 
879                 if (insert_at.size() != input_string.length()) {
880                         error << "something went wrong " << endmsg;
881                 } else {
882                         for (int i = input_string.length() - 1; i >= 0; --i) {
883                                 edit_string[insert_at[i]] = input_string[i];
884                         }
885                         
886                         highlight_length = edit_string.length() - insert_at.back();
887                 }
888                 
889                 break;
890         }
891         
892         if (edit_string != _layout->get_text()) {
893                 show_edit_status (highlight_length);
894                 _layout->set_text (edit_string);
895                 queue_draw ();
896         } 
897
898         return true;
899 }
900
901 AudioClock::Field
902 AudioClock::index_to_field (int index) const
903 {
904         switch (_mode) {
905         case Timecode:
906                 if (index < 4) {
907                         return Timecode_Hours;
908                 } else if (index < 7) {
909                         return Timecode_Minutes;
910                 } else if (index < 10) {
911                         return Timecode_Seconds;
912                 } else if (index < 13) {
913                         return Timecode_Frames;
914                 } else {
915                         return Field (0);
916                 }
917                 break;
918         case BBT:
919                 if (index < 5) {
920                         return Bars;
921                 } else if (index < 7) {
922                         return Beats;
923                 } else if (index < 12) {
924                         return Ticks;
925                 } else {
926                         return Field (0);
927                 }
928                 break;
929
930         case MinSec:
931                 if (index < 3) {
932                         return Timecode_Hours;
933                 } else if (index < 6) {
934                         return MS_Minutes;
935                 } else if (index < 9) {
936                         return MS_Seconds;
937                 } else if (index < 12) {
938                         return MS_Milliseconds;
939                 } else {
940                         return Field (0);
941                 }
942                 break;
943
944         case Frames:
945                 return AudioFrames;
946                 break;
947         }
948 }
949
950 bool
951 AudioClock::on_button_press_event (GdkEventButton *ev)
952 {
953         switch (ev->button) {
954         case 1:
955                 if (editable) {
956                         dragging = true;
957                         /* make absolutely sure that the pointer is grabbed */
958                         gdk_pointer_grab(ev->window,false ,
959                                          GdkEventMask( Gdk::POINTER_MOTION_MASK | Gdk::BUTTON_PRESS_MASK |Gdk::BUTTON_RELEASE_MASK),
960                                          NULL,NULL,ev->time);
961                         drag_accum = 0;
962                         drag_start_y = ev->y;
963                         drag_y = ev->y;
964                         
965                         int index;
966                         int trailing;
967
968                         if (_layout->xy_to_index (ev->x * PANGO_SCALE, ev->y * PANGO_SCALE, index, trailing)) {                 
969                                 drag_field = index_to_field (index);
970                         } else {
971                                 drag_field = Field (0);
972                         }
973                 }
974                 break;
975                 
976         default:
977                 return false;
978                 break;
979         }
980
981         return true;
982 }
983
984 bool
985 AudioClock::on_button_release_event (GdkEventButton *ev)
986 {
987         if (editable) {
988                 if (dragging) {
989                         gdk_pointer_ungrab (GDK_CURRENT_TIME);
990                         dragging = false;
991                         if (ev->y > drag_start_y+1 || ev->y < drag_start_y-1 || Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)){
992                                 // we actually dragged so return without
993                                 // setting editing focus, or we shift clicked
994                                 return true;
995                         } else {
996                                 if (ev->button == 1) {
997                                         start_edit ();
998                                 }
999                         }
1000
1001                 }
1002         }
1003
1004         if (Keyboard::is_context_menu_event (ev)) {
1005                 if (ops_menu == 0) {
1006                         build_ops_menu ();
1007                 }
1008                 ops_menu->popup (1, ev->time);
1009                 return true;
1010         }
1011
1012         return false;
1013 }
1014
1015 bool
1016 AudioClock::on_scroll_event (GdkEventScroll *ev)
1017 {
1018         int index;
1019         int trailing;
1020
1021         if (_session == 0 || !editable) {
1022                 return false;
1023         }
1024
1025         if (!_layout->xy_to_index (ev->x * PANGO_SCALE, ev->y * PANGO_SCALE, index, trailing)) {
1026                 /* not in the main layout */
1027                 return false;
1028         }
1029         
1030         Field f = index_to_field (index);
1031         framepos_t frames = 0;
1032
1033         switch (ev->direction) {
1034
1035         case GDK_SCROLL_UP:
1036                 frames = get_frame_step (f);
1037                 if (frames != 0) {
1038                         if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
1039                                 frames *= 10;
1040                         }
1041                         set (current_time() + frames, true);
1042                         ValueChanged (); /* EMIT_SIGNAL */
1043                 }
1044                 break;
1045                 
1046         case GDK_SCROLL_DOWN:
1047                 frames = get_frame_step (f);
1048                 if (frames != 0) {
1049                         if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
1050                                 frames *= 10;
1051                         }
1052                         
1053                         if ((double)current_time() - (double)frames < 0.0) {
1054                                 set (0, true);
1055                         } else {
1056                                 set (current_time() - frames, true);
1057                         }
1058                         
1059                         ValueChanged (); /* EMIT_SIGNAL */
1060                 }
1061                 break;
1062                 
1063         default:
1064                 return false;
1065                 break;
1066         }
1067         
1068         return true;
1069 }
1070
1071 bool
1072 AudioClock::on_motion_notify_event (GdkEventMotion *ev)
1073 {
1074         if (_session == 0 || !dragging) {
1075                 return false;
1076         }
1077
1078         float pixel_frame_scale_factor = 0.2f;
1079
1080         if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier))  {
1081                 pixel_frame_scale_factor = 0.1f;
1082         }
1083
1084
1085         if (Keyboard::modifier_state_contains (ev->state,
1086                                                Keyboard::PrimaryModifier|Keyboard::SecondaryModifier)) {
1087
1088                 pixel_frame_scale_factor = 0.025f;
1089         }
1090
1091         double y_delta = ev->y - drag_y;
1092
1093         drag_accum +=  y_delta*pixel_frame_scale_factor;
1094
1095         drag_y = ev->y;
1096
1097         if (trunc (drag_accum) != 0) {
1098
1099                 framepos_t frames;
1100                 framepos_t pos;
1101                 int dir;
1102                 dir = (drag_accum < 0 ? 1:-1);
1103                 pos = current_time();
1104                 frames = get_frame_step (drag_field,pos,dir);
1105
1106                 if (frames  != 0 &&  frames * drag_accum < current_time()) {
1107                         set ((framepos_t) floor (pos - drag_accum * frames), false); // minus because up is negative in GTK
1108                 } else {
1109                         set (0 , false);
1110                 }
1111
1112                 drag_accum= 0;
1113                 ValueChanged();  /* EMIT_SIGNAL */
1114         }
1115
1116         return true;
1117 }
1118
1119 framepos_t
1120 AudioClock::get_frame_step (Field field, framepos_t pos, int dir)
1121 {
1122         framecnt_t f = 0;
1123         Timecode::BBT_Time BBT;
1124         switch (field) {
1125         case Timecode_Hours:
1126                 f = (framecnt_t) floor (3600.0 * _session->frame_rate());
1127                 break;
1128         case Timecode_Minutes:
1129                 f = (framecnt_t) floor (60.0 * _session->frame_rate());
1130                 break;
1131         case Timecode_Seconds:
1132                 f = _session->frame_rate();
1133                 break;
1134         case Timecode_Frames:
1135                 f = (framecnt_t) floor (_session->frame_rate() / _session->timecode_frames_per_second());
1136                 break;
1137
1138         case AudioFrames:
1139                 f = 1;
1140                 break;
1141
1142         case MS_Hours:
1143                 f = (framecnt_t) floor (3600.0 * _session->frame_rate());
1144                 break;
1145         case MS_Minutes:
1146                 f = (framecnt_t) floor (60.0 * _session->frame_rate());
1147                 break;
1148         case MS_Seconds:
1149                 f = (framecnt_t) _session->frame_rate();
1150                 break;
1151         case MS_Milliseconds:
1152                 f = (framecnt_t) floor (_session->frame_rate() / 1000.0);
1153                 break;
1154
1155         case Bars:
1156                 BBT.bars = 1;
1157                 BBT.beats = 0;
1158                 BBT.ticks = 0;
1159                 f = _session->tempo_map().bbt_duration_at (pos,BBT,dir);
1160                 break;
1161         case Beats:
1162                 BBT.bars = 0;
1163                 BBT.beats = 1;
1164                 BBT.ticks = 0;
1165                 f = _session->tempo_map().bbt_duration_at(pos,BBT,dir);
1166                 break;
1167         case Ticks:
1168                 BBT.bars = 0;
1169                 BBT.beats = 0;
1170                 BBT.ticks = 1;
1171                 f = _session->tempo_map().bbt_duration_at(pos,BBT,dir);
1172                 break;
1173         default:
1174                 error << string_compose (_("programming error: %1"), "attempt to get frames from non-text field!") << endmsg;
1175                 f = 0;
1176                 break;
1177         }
1178
1179         return f;
1180 }
1181
1182 framepos_t
1183 AudioClock::current_time (framepos_t pos) const
1184 {
1185         // if (!_canonical_time_is_displayed) {
1186         // return _canonical_time;
1187         //}
1188
1189         framepos_t ret = 0;
1190
1191         switch (_mode) {
1192         case Timecode:
1193                 ret = timecode_frame_from_display ();
1194                 break;
1195         case BBT:
1196                 ret = bbt_frame_from_display (pos);
1197                 break;
1198
1199         case MinSec:
1200                 ret = minsec_frame_from_display ();
1201                 break;
1202
1203         case Frames:
1204                 ret = audio_frame_from_display ();
1205                 break;
1206         }
1207
1208         return ret;
1209 }
1210
1211 framepos_t
1212 AudioClock::current_duration (framepos_t pos) const
1213 {
1214         framepos_t ret = 0;
1215
1216         switch (_mode) {
1217         case Timecode:
1218                 ret = timecode_frame_from_display ();
1219                 break;
1220         case BBT:
1221                 ret = bbt_frame_duration_from_display (pos);
1222                 break;
1223
1224         case MinSec:
1225                 ret = minsec_frame_from_display ();
1226                 break;
1227
1228         case Frames:
1229                 ret = audio_frame_from_display ();
1230                 break;
1231         }
1232
1233         return ret;
1234 }
1235
1236 bool
1237 AudioClock::bbt_validate_edit (const string& str)
1238 {
1239         AnyTime any;
1240
1241         sscanf (str.c_str(), "%" PRIu32 "|%" PRIu32 "|%" PRIu32, &any.bbt.bars, &any.bbt.beats, &any.bbt.ticks);
1242         
1243         if (!is_duration && any.bbt.bars == 0) {
1244                 return false;
1245         }
1246
1247         if (!is_duration && any.bbt.beats == 0) {
1248                 return false;
1249         }
1250
1251         return true;
1252 }
1253
1254 bool
1255 AudioClock::timecode_validate_edit (const string& str)
1256 {
1257         Timecode::Time TC;
1258
1259         if (sscanf (_layout->get_text().c_str(), "%" PRId32 ":%" PRId32 ":%" PRId32 ":%" PRId32, 
1260                     &TC.hours, &TC.minutes, &TC.seconds, &TC.frames) != 4) {
1261                 return false;
1262         }
1263
1264         if (TC.minutes > 59 || TC.seconds > 59) {
1265                 return false;
1266         }
1267
1268         if (TC.frames > (long)rint(_session->timecode_frames_per_second()) - 1) {
1269                 return false;
1270         }
1271
1272         if (_session->timecode_drop_frames()) {
1273                 if (TC.minutes % 10 && TC.seconds == 0 && TC.frames < 2) {
1274                         return false;
1275                 }
1276         }
1277
1278         return true;
1279 }
1280
1281 framepos_t
1282 AudioClock::timecode_frame_from_display () const
1283 {
1284         if (_session == 0) {
1285                 return 0;
1286         }
1287
1288         Timecode::Time TC;
1289         framepos_t sample;
1290
1291         sscanf (_layout->get_text().c_str(), "%d:%d:%d:%d", &TC.hours, &TC.minutes, &TC.seconds, &TC.frames);
1292
1293         TC.rate = _session->timecode_frames_per_second();
1294         TC.drop= _session->timecode_drop_frames();
1295
1296         _session->timecode_to_sample (TC, sample, false /* use_offset */, false /* use_subframes */ );
1297         
1298         // timecode_tester ();
1299
1300         return sample;
1301 }
1302
1303 framepos_t
1304 AudioClock::minsec_frame_from_display () const
1305 {
1306         if (_session == 0) {
1307                 return 0;
1308         }
1309
1310         int hrs, mins, secs, millisecs;
1311         framecnt_t sr = _session->frame_rate();
1312
1313         sscanf (_layout->get_text().c_str(), "%d:%d:%d:%d", &hrs, &mins, &secs, &millisecs);
1314         return (framepos_t) floor ((hrs * 60.0f * 60.0f * sr) + (mins * 60.0f * sr) + (secs * sr) + (millisecs * sr / 1000.0));
1315 }
1316
1317 framepos_t
1318 AudioClock::bbt_frame_from_display (framepos_t pos) const
1319 {
1320         if (_session == 0) {
1321                 error << "AudioClock::current_time() called with BBT mode but without session!" << endmsg;
1322                 return 0;
1323         }
1324
1325         AnyTime any;
1326         any.type = AnyTime::BBT;
1327
1328         sscanf (_layout->get_text().c_str(), "%" PRId32 "|%" PRId32 "|%" PRId32, &any.bbt.bars, &any.bbt.beats, &any.bbt.ticks);
1329
1330         if (is_duration) {
1331                 any.bbt.bars++;
1332                 any.bbt.beats++;
1333                 return _session->any_duration_to_frames (pos, any);
1334         } else {
1335                 return _session->convert_to_frames (any);
1336         }
1337 }
1338
1339
1340 framepos_t
1341 AudioClock::bbt_frame_duration_from_display (framepos_t pos) const
1342 {
1343         if (_session == 0) {
1344                 error << "AudioClock::current_time() called with BBT mode but without session!" << endmsg;
1345                 return 0;
1346         }
1347
1348         Timecode::BBT_Time bbt;
1349
1350         sscanf (_layout->get_text().c_str(), "%" PRIu32 "|%" PRIu32 "|%" PRIu32, &bbt.bars, &bbt.beats, &bbt.ticks);
1351
1352         return _session->tempo_map().bbt_duration_at(pos,bbt,1);
1353 }
1354
1355 framepos_t
1356 AudioClock::audio_frame_from_display () const
1357 {
1358         framepos_t f;
1359         sscanf (_layout->get_text().c_str(), "%" PRId64, &f);
1360         return f;
1361 }
1362
1363 void
1364 AudioClock::build_ops_menu ()
1365 {
1366         using namespace Menu_Helpers;
1367         ops_menu = new Menu;
1368         MenuList& ops_items = ops_menu->items();
1369         ops_menu->set_name ("ArdourContextMenu");
1370
1371         if (!Profile->get_sae()) {
1372                 ops_items.push_back (MenuElem (_("Timecode"), sigc::bind (sigc::mem_fun(*this, &AudioClock::set_mode), Timecode)));
1373         }
1374         ops_items.push_back (MenuElem (_("Bars:Beats"), sigc::bind (sigc::mem_fun(*this, &AudioClock::set_mode), BBT)));
1375         ops_items.push_back (MenuElem (_("Minutes:Seconds"), sigc::bind (sigc::mem_fun(*this, &AudioClock::set_mode), MinSec)));
1376         ops_items.push_back (MenuElem (_("Samples"), sigc::bind (sigc::mem_fun(*this, &AudioClock::set_mode), Frames)));
1377
1378         if (editable && !is_duration && !_follows_playhead) {
1379                 ops_items.push_back (SeparatorElem());
1380                 ops_items.push_back (MenuElem (_("Set From Playhead"), sigc::mem_fun(*this, &AudioClock::set_from_playhead)));
1381                 ops_items.push_back (MenuElem (_("Locate to This Time"), sigc::mem_fun(*this, &AudioClock::locate)));
1382         }
1383 }
1384
1385 void
1386 AudioClock::set_from_playhead ()
1387 {
1388         if (!_session) {
1389                 return;
1390         }
1391
1392         set (_session->transport_frame());
1393         ValueChanged ();
1394 }
1395
1396 void
1397 AudioClock::locate ()
1398 {
1399         if (!_session || is_duration) {
1400                 return;
1401         }
1402
1403         _session->request_locate (current_time(), _session->transport_rolling ());
1404 }
1405
1406 void
1407 AudioClock::set_mode (Mode m)
1408 {
1409         if (_mode == m) {
1410                 return;
1411         }
1412
1413         _mode = m;
1414
1415         switch (_mode) {
1416         case Timecode:
1417                 insert_max = 9; // 8 digits + sign [-]2:2:2:2
1418                 mode_based_info_ratio = 0.5;
1419                 break;
1420                 
1421         case BBT:
1422                 insert_max = 8; // 8 digits, 2|2|4
1423                 mode_based_info_ratio = 0.5;
1424                 break;
1425                 
1426         case MinSec:
1427                 insert_max = 9; // 7 digits 2:2:2.3
1428                 mode_based_info_ratio = 1.0;
1429                 break;
1430                 
1431         case Frames:
1432                 insert_max = INT_MAX;
1433                 mode_based_info_ratio = 1.0;
1434                 break;
1435         }
1436
1437         set (last_when, true);
1438
1439         if (!is_transient) {
1440                 ModeChanged (); /* EMIT SIGNAL (the static one)*/
1441         }
1442
1443         mode_changed (); /* EMIT SIGNAL (the member one) */
1444 }
1445
1446 void
1447 AudioClock::set_bbt_reference (framepos_t pos)
1448 {
1449         bbt_reference_time = pos;
1450 }
1451
1452 void
1453 AudioClock::on_style_changed (const Glib::RefPtr<Gtk::Style>& old_style)
1454 {
1455         CairoWidget::on_style_changed (old_style);
1456         set_font ();
1457         set_colors ();
1458 }
1459
1460 void
1461 AudioClock::set_is_duration (bool yn)
1462 {
1463         if (yn == is_duration) {
1464                 return;
1465         }
1466
1467         is_duration = yn;
1468         set (last_when, true, 0, 's');
1469 }
1470
1471 void
1472 AudioClock::set_off (bool yn) 
1473 {
1474         if (_off == yn) {
1475                 return;
1476         }
1477
1478         _off = yn;
1479
1480         if (_off) {
1481                 _canonical_time = current_time ();
1482                 _canonical_time_is_displayed = false;
1483         } else {
1484                 _canonical_time_is_displayed = true;
1485         }
1486
1487         /* force a possible redraw */
1488         
1489         set (_canonical_time, true);
1490 }
1491
1492 void
1493 AudioClock::focus ()
1494 {
1495         start_edit ();
1496 }
1497
1498 void
1499 AudioClock::set_draw_background (bool yn)
1500 {
1501         _need_bg = yn;
1502 }
1503
1504 void
1505 AudioClock::timecode_tester ()
1506 {
1507 #if 0
1508 #define Timecode_SAMPLE_TEST_1
1509 #define Timecode_SAMPLE_TEST_2
1510 #define Timecode_SAMPLE_TEST_3
1511 #define Timecode_SAMPLE_TEST_4
1512 #define Timecode_SAMPLE_TEST_5
1513 #define Timecode_SAMPLE_TEST_6
1514 #define Timecode_SAMPLE_TEST_7
1515
1516         // Testcode for timecode<->sample conversions (P.S.)
1517         Timecode::Time timecode1;
1518         framepos_t sample1;
1519         framepos_t oldsample = 0;
1520         Timecode::Time timecode2;
1521         framecnt_t sample_increment;
1522
1523         sample_increment = (framecnt_t)rint(_session->frame_rate() / _session->timecode_frames_per_second);
1524
1525 #ifdef Timecode_SAMPLE_TEST_1
1526         // Test 1: use_offset = false, use_subframes = false
1527         cout << "use_offset = false, use_subframes = false" << endl;
1528         for (int i = 0; i < 108003; i++) {
1529                 _session->timecode_to_sample( timecode1, sample1, false /* use_offset */, false /* use_subframes */ );
1530                 _session->sample_to_timecode( sample1, timecode2, false /* use_offset */, false /* use_subframes */ );
1531
1532                 if ((i > 0) && ( ((sample1 - oldsample) != sample_increment) && ((sample1 - oldsample) != (sample_increment + 1)) && ((sample1 - oldsample) != (sample_increment - 1)))) {
1533                         cout << "ERROR: sample increment not right: " << (sample1 - oldsample) << " != " << sample_increment << endl;
1534                         cout << "timecode1: " << timecode1.hours << ":" << timecode1.minutes << ":" << timecode1.seconds << ":" << timecode1.frames << "::" << timecode1.subframes << " -> ";
1535                         cout << "sample: " << sample1 << endl;
1536                         cout << "sample: " << sample1 << " -> ";
1537                         cout << "timecode2: " << timecode2.hours << ":" << timecode2.minutes << ":" << timecode2.seconds << ":" << timecode2.frames << "::" << timecode2.subframes << endl;
1538                         break;
1539                 }
1540
1541                 if (timecode2.hours != timecode1.hours || timecode2.minutes != timecode1.minutes || timecode2.seconds != timecode2.seconds || timecode2.frames != timecode1.frames) {
1542                         cout << "ERROR: timecode2 not equal timecode1" << endl;
1543                         cout << "timecode1: " << timecode1.hours << ":" << timecode1.minutes << ":" << timecode1.seconds << ":" << timecode1.frames << "::" << timecode1.subframes << " -> ";
1544                         cout << "sample: " << sample1 << endl;
1545                         cout << "sample: " << sample1 << " -> ";
1546                         cout << "timecode2: " << timecode2.hours << ":" << timecode2.minutes << ":" << timecode2.seconds << ":" << timecode2.frames << "::" << timecode2.subframes << endl;
1547                         break;
1548                 }
1549                 oldsample = sample1;
1550                 _session->timecode_increment( timecode1 );
1551         }
1552
1553         cout << "sample_increment: " << sample_increment << endl;
1554         cout << "sample: " << sample1 << " -> ";
1555         cout << "timecode: " << timecode2.hours << ":" << timecode2.minutes << ":" << timecode2.seconds << ":" << timecode2.frames << "::" << timecode2.subframes << endl;
1556 #endif
1557
1558 #ifdef Timecode_SAMPLE_TEST_2
1559         // Test 2: use_offset = true, use_subframes = false
1560         cout << "use_offset = true, use_subframes = false" << endl;
1561
1562         timecode1.hours = 0;
1563         timecode1.minutes = 0;
1564         timecode1.seconds = 0;
1565         timecode1.frames = 0;
1566         timecode1.subframes = 0;
1567         sample1 = oldsample = 0;
1568
1569         _session->sample_to_timecode( sample1, timecode1, true /* use_offset */, false /* use_subframes */ );
1570         cout << "Starting at sample: " << sample1 << " -> ";
1571         cout << "timecode: " << (timecode1.negative ? "-" : "") << timecode1.hours << ":" << timecode1.minutes << ":" << timecode1.seconds << ":" << timecode1.frames << "::" << timecode1.subframes << endl;
1572
1573         for (int i = 0; i < 108003; i++) {
1574                 _session->timecode_to_sample( timecode1, sample1, true /* use_offset */, false /* use_subframes */ );
1575                 _session->sample_to_timecode( sample1, timecode2, true /* use_offset */, false /* use_subframes */ );
1576
1577 //     cout << "timecode: " << (timecode1.negative ? "-" : "") << timecode1.hours << ":" << timecode1.minutes << ":" << timecode1.seconds << ":" << timecode1.frames << "::" << timecode1.subframes << " -> ";
1578 //     cout << "sample: " << sample1 << endl;
1579 //     cout << "sample: " << sample1 << " -> ";
1580 //     cout << "timecode: " << (timecode2.negative ? "-" : "") << timecode2.hours << ":" << timecode2.minutes << ":" << timecode2.seconds << ":" << timecode2.frames << "::" << timecode2.subframes << endl;
1581
1582                 if ((i > 0) && ( ((sample1 - oldsample) != sample_increment) && ((sample1 - oldsample) != (sample_increment + 1)) && ((sample1 - oldsample) != (sample_increment - 1)))) {
1583                         cout << "ERROR: sample increment not right: " << (sample1 - oldsample) << " != " << sample_increment << endl;
1584                         cout << "timecode1: " << (timecode1.negative ? "-" : "") << timecode1.hours << ":" << timecode1.minutes << ":" << timecode1.seconds << ":" << timecode1.frames << "::" << timecode1.subframes << " -> ";
1585                         cout << "sample: " << sample1 << endl;
1586                         cout << "sample: " << sample1 << " -> ";
1587                         cout << "timecode2: " << (timecode2.negative ? "-" : "") << timecode2.hours << ":" << timecode2.minutes << ":" << timecode2.seconds << ":" << timecode2.frames << "::" << timecode2.subframes << endl;
1588                         break;
1589                 }
1590
1591                 if (timecode2.hours != timecode1.hours || timecode2.minutes != timecode1.minutes || timecode2.seconds != timecode2.seconds || timecode2.frames != timecode1.frames) {
1592                         cout << "ERROR: timecode2 not equal timecode1" << endl;
1593                         cout << "timecode1: " << (timecode1.negative ? "-" : "") << timecode1.hours << ":" << timecode1.minutes << ":" << timecode1.seconds << ":" << timecode1.frames << "::" << timecode1.subframes << " -> ";
1594                         cout << "sample: " << sample1 << endl;
1595                         cout << "sample: " << sample1 << " -> ";
1596                         cout << "timecode2: " << (timecode2.negative ? "-" : "") << timecode2.hours << ":" << timecode2.minutes << ":" << timecode2.seconds << ":" << timecode2.frames << "::" << timecode2.subframes << endl;
1597                         break;
1598                 }
1599                 oldsample = sample1;
1600                 _session->timecode_increment( timecode1 );
1601         }
1602
1603         cout << "sample_increment: " << sample_increment << endl;
1604         cout << "sample: " << sample1 << " -> ";
1605         cout << "timecode: " << (timecode2.negative ? "-" : "") << timecode2.hours << ":" << timecode2.minutes << ":" << timecode2.seconds << ":" << timecode2.frames << "::" << timecode2.subframes << endl;
1606 #endif
1607
1608 #ifdef Timecode_SAMPLE_TEST_3
1609         // Test 3: use_offset = true, use_subframes = false, decrement
1610         cout << "use_offset = true, use_subframes = false, decrement" << endl;
1611
1612         _session->sample_to_timecode( sample1, timecode1, true /* use_offset */, false /* use_subframes */ );
1613         cout << "Starting at sample: " << sample1 << " -> ";
1614         cout << "timecode: " << (timecode1.negative ? "-" : "") << timecode1.hours << ":" << timecode1.minutes << ":" << timecode1.seconds << ":" << timecode1.frames << "::" << timecode1.subframes << endl;
1615
1616         for (int i = 0; i < 108003; i++) {
1617                 _session->timecode_to_sample( timecode1, sample1, true /* use_offset */, false /* use_subframes */ );
1618                 _session->sample_to_timecode( sample1, timecode2, true /* use_offset */, false /* use_subframes */ );
1619
1620 //     cout << "timecode: " << (timecode1.negative ? "-" : "") << timecode1.hours << ":" << timecode1.minutes << ":" << timecode1.seconds << ":" << timecode1.frames << "::" << timecode1.subframes << " -> ";
1621 //     cout << "sample: " << sample1 << endl;
1622 //     cout << "sample: " << sample1 << " -> ";
1623 //     cout << "timecode: " << (timecode2.negative ? "-" : "") << timecode2.hours << ":" << timecode2.minutes << ":" << timecode2.seconds << ":" << timecode2.frames << "::" << timecode2.subframes << endl;
1624
1625                 if ((i > 0) && ( ((oldsample - sample1) != sample_increment) && ((oldsample - sample1) != (sample_increment + 1)) && ((oldsample - sample1) != (sample_increment - 1)))) {
1626                         cout << "ERROR: sample increment not right: " << (oldsample - sample1) << " != " << sample_increment << endl;
1627                         cout << "timecode1: " << (timecode1.negative ? "-" : "") << timecode1.hours << ":" << timecode1.minutes << ":" << timecode1.seconds << ":" << timecode1.frames << "::" << timecode1.subframes << " -> ";
1628                         cout << "sample: " << sample1 << endl;
1629                         cout << "sample: " << sample1 << " -> ";
1630                         cout << "timecode2: " << (timecode2.negative ? "-" : "") << timecode2.hours << ":" << timecode2.minutes << ":" << timecode2.seconds << ":" << timecode2.frames << "::" << timecode2.subframes << endl;
1631                         break;
1632                 }
1633
1634                 if (timecode2.hours != timecode1.hours || timecode2.minutes != timecode1.minutes || timecode2.seconds != timecode2.seconds || timecode2.frames != timecode1.frames) {
1635                         cout << "ERROR: timecode2 not equal timecode1" << endl;
1636                         cout << "timecode1: " << (timecode1.negative ? "-" : "") << timecode1.hours << ":" << timecode1.minutes << ":" << timecode1.seconds << ":" << timecode1.frames << "::" << timecode1.subframes << " -> ";
1637                         cout << "sample: " << sample1 << endl;
1638                         cout << "sample: " << sample1 << " -> ";
1639                         cout << "timecode2: " << (timecode2.negative ? "-" : "") << timecode2.hours << ":" << timecode2.minutes << ":" << timecode2.seconds << ":" << timecode2.frames << "::" << timecode2.subframes << endl;
1640                         break;
1641                 }
1642                 oldsample = sample1;
1643                 _session->timecode_decrement( timecode1 );
1644         }
1645
1646         cout << "sample_decrement: " << sample_increment << endl;
1647         cout << "sample: " << sample1 << " -> ";
1648         cout << "timecode: " << (timecode2.negative ? "-" : "") << timecode2.hours << ":" << timecode2.minutes << ":" << timecode2.seconds << ":" << timecode2.frames << "::" << timecode2.subframes << endl;
1649 #endif
1650
1651
1652 #ifdef Timecode_SAMPLE_TEST_4
1653         // Test 4: use_offset = true, use_subframes = true
1654         cout << "use_offset = true, use_subframes = true" << endl;
1655
1656         for (long sub = 5; sub < 80; sub += 5) {
1657                 timecode1.hours = 0;
1658                 timecode1.minutes = 0;
1659                 timecode1.seconds = 0;
1660                 timecode1.frames = 0;
1661                 timecode1.subframes = 0;
1662                 sample1 = oldsample = (sample_increment * sub) / 80;
1663
1664                 _session->sample_to_timecode( sample1, timecode1, true /* use_offset */, true /* use_subframes */ );
1665
1666                 cout << "starting at sample: " << sample1 << " -> ";
1667                 cout << "timecode: " << (timecode1.negative ? "-" : "") << timecode1.hours << ":" << timecode1.minutes << ":" << timecode1.seconds << ":" << timecode1.frames << "::" << timecode1.subframes << endl;
1668
1669                 for (int i = 0; i < 108003; i++) {
1670                         _session->timecode_to_sample( timecode1, sample1, true /* use_offset */, true /* use_subframes */ );
1671                         _session->sample_to_timecode( sample1, timecode2, true /* use_offset */, true /* use_subframes */ );
1672
1673                         if ((i > 0) && ( ((sample1 - oldsample) != sample_increment) && ((sample1 - oldsample) != (sample_increment + 1)) && ((sample1 - oldsample) != (sample_increment - 1)))) {
1674                                 cout << "ERROR: sample increment not right: " << (sample1 - oldsample) << " != " << sample_increment << endl;
1675                                 cout << "timecode1: " << (timecode1.negative ? "-" : "") << timecode1.hours << ":" << timecode1.minutes << ":" << timecode1.seconds << ":" << timecode1.frames << "::" << timecode1.subframes << " -> ";
1676                                 cout << "sample: " << sample1 << endl;
1677                                 cout << "sample: " << sample1 << " -> ";
1678                                 cout << "timecode2: " << (timecode2.negative ? "-" : "") << timecode2.hours << ":" << timecode2.minutes << ":" << timecode2.seconds << ":" << timecode2.frames << "::" << timecode2.subframes << endl;
1679                                 //break;
1680                         }
1681
1682                         if (timecode2.hours != timecode1.hours || timecode2.minutes != timecode1.minutes || timecode2.seconds != timecode2.seconds || timecode2.frames != timecode1.frames || timecode2.subframes != timecode1.subframes) {
1683                                 cout << "ERROR: timecode2 not equal timecode1" << endl;
1684                                 cout << "timecode1: " << (timecode1.negative ? "-" : "") << timecode1.hours << ":" << timecode1.minutes << ":" << timecode1.seconds << ":" << timecode1.frames << "::" << timecode1.subframes << " -> ";
1685                                 cout << "sample: " << sample1 << endl;
1686                                 cout << "sample: " << sample1 << " -> ";
1687                                 cout << "timecode2: " << (timecode2.negative ? "-" : "") << timecode2.hours << ":" << timecode2.minutes << ":" << timecode2.seconds << ":" << timecode2.frames << "::" << timecode2.subframes << endl;
1688                                 break;
1689                         }
1690                         oldsample = sample1;
1691                         _session->timecode_increment( timecode1 );
1692                 }
1693
1694                 cout << "sample_increment: " << sample_increment << endl;
1695                 cout << "sample: " << sample1 << " -> ";
1696                 cout << "timecode: " << (timecode2.negative ? "-" : "") << timecode2.hours << ":" << timecode2.minutes << ":" << timecode2.seconds << ":" << timecode2.frames << "::" << timecode2.subframes << endl;
1697
1698                 for (int i = 0; i < 108003; i++) {
1699                         _session->timecode_to_sample( timecode1, sample1, true /* use_offset */, true /* use_subframes */ );
1700                         _session->sample_to_timecode( sample1, timecode2, true /* use_offset */, true /* use_subframes */ );
1701
1702                         if ((i > 0) && ( ((oldsample - sample1) != sample_increment) && ((oldsample - sample1) != (sample_increment + 1)) && ((oldsample - sample1) != (sample_increment - 1)))) {
1703                                 cout << "ERROR: sample increment not right: " << (oldsample - sample1) << " != " << sample_increment << endl;
1704                                 cout << "timecode1: " << (timecode1.negative ? "-" : "") << timecode1.hours << ":" << timecode1.minutes << ":" << timecode1.seconds << ":" << timecode1.frames << "::" << timecode1.subframes << " -> ";
1705                                 cout << "sample: " << sample1 << endl;
1706                                 cout << "sample: " << sample1 << " -> ";
1707                                 cout << "timecode2: " << (timecode2.negative ? "-" : "") << timecode2.hours << ":" << timecode2.minutes << ":" << timecode2.seconds << ":" << timecode2.frames << "::" << timecode2.subframes << endl;
1708                                 //break;
1709                         }
1710
1711                         if (timecode2.hours != timecode1.hours || timecode2.minutes != timecode1.minutes || timecode2.seconds != timecode2.seconds || timecode2.frames != timecode1.frames || timecode2.subframes != timecode1.subframes) {
1712                                 cout << "ERROR: timecode2 not equal timecode1" << endl;
1713                                 cout << "timecode1: " << (timecode1.negative ? "-" : "") << timecode1.hours << ":" << timecode1.minutes << ":" << timecode1.seconds << ":" << timecode1.frames << "::" << timecode1.subframes << " -> ";
1714                                 cout << "sample: " << sample1 << endl;
1715                                 cout << "sample: " << sample1 << " -> ";
1716                                 cout << "timecode2: " << (timecode2.negative ? "-" : "") << timecode2.hours << ":" << timecode2.minutes << ":" << timecode2.seconds << ":" << timecode2.frames << "::" << timecode2.subframes << endl;
1717                                 break;
1718                         }
1719                         oldsample = sample1;
1720                         _session->timecode_decrement( timecode1 );
1721                 }
1722
1723                 cout << "sample_decrement: " << sample_increment << endl;
1724                 cout << "sample: " << sample1 << " -> ";
1725                 cout << "timecode: " << (timecode2.negative ? "-" : "") << timecode2.hours << ":" << timecode2.minutes << ":" << timecode2.seconds << ":" << timecode2.frames << "::" << timecode2.subframes << endl;
1726         }
1727 #endif
1728
1729
1730 #ifdef Timecode_SAMPLE_TEST_5
1731         // Test 5: use_offset = true, use_subframes = false, increment seconds
1732         cout << "use_offset = true, use_subframes = false, increment seconds" << endl;
1733
1734         timecode1.hours = 0;
1735         timecode1.minutes = 0;
1736         timecode1.seconds = 0;
1737         timecode1.frames = 0;
1738         timecode1.subframes = 0;
1739         sample1 = oldsample = 0;
1740         sample_increment = _session->frame_rate();
1741
1742         _session->sample_to_timecode( sample1, timecode1, true /* use_offset */, false /* use_subframes */ );
1743         cout << "Starting at sample: " << sample1 << " -> ";
1744         cout << "timecode: " << (timecode1.negative ? "-" : "") << timecode1.hours << ":" << timecode1.minutes << ":" << timecode1.seconds << ":" << timecode1.frames << "::" << timecode1.subframes << endl;
1745
1746         for (int i = 0; i < 3600; i++) {
1747                 _session->timecode_to_sample( timecode1, sample1, true /* use_offset */, false /* use_subframes */ );
1748                 _session->sample_to_timecode( sample1, timecode2, true /* use_offset */, false /* use_subframes */ );
1749
1750 //     cout << "timecode: " << (timecode1.negative ? "-" : "") << timecode1.hours << ":" << timecode1.minutes << ":" << timecode1.seconds << ":" << timecode1.frames << "::" << timecode1.subframes << " -> ";
1751 //     cout << "sample: " << sample1 << endl;
1752 //     cout << "sample: " << sample1 << " -> ";
1753 //     cout << "timecode: " << (timecode2.negative ? "-" : "") << timecode2.hours << ":" << timecode2.minutes << ":" << timecode2.seconds << ":" << timecode2.frames << "::" << timecode2.subframes << endl;
1754
1755 //     if ((i > 0) && ( ((sample1 - oldsample) != sample_increment) && ((sample1 - oldsample) != (sample_increment + 1)) && ((sample1 - oldsample) != (sample_increment - 1))))
1756 //     {
1757 //       cout << "ERROR: sample increment not right: " << (sample1 - oldsample) << " != " << sample_increment << endl;
1758 //       break;
1759 //     }
1760
1761                 if (timecode2.hours != timecode1.hours || timecode2.minutes != timecode1.minutes || timecode2.seconds != timecode2.seconds || timecode2.frames != timecode1.frames) {
1762                         cout << "ERROR: timecode2 not equal timecode1" << endl;
1763                         cout << "timecode: " << (timecode1.negative ? "-" : "") << timecode1.hours << ":" << timecode1.minutes << ":" << timecode1.seconds << ":" << timecode1.frames << "::" << timecode1.subframes << " -> ";
1764                         cout << "sample: " << sample1 << endl;
1765                         cout << "sample: " << sample1 << " -> ";
1766                         cout << "timecode: " << (timecode2.negative ? "-" : "") << timecode2.hours << ":" << timecode2.minutes << ":" << timecode2.seconds << ":" << timecode2.frames << "::" << timecode2.subframes << endl;
1767                         break;
1768                 }
1769                 oldsample = sample1;
1770                 _session->timecode_increment_seconds( timecode1 );
1771         }
1772
1773         cout << "sample_increment: " << sample_increment << endl;
1774         cout << "sample: " << sample1 << " -> ";
1775         cout << "timecode: " << (timecode2.negative ? "-" : "") << timecode2.hours << ":" << timecode2.minutes << ":" << timecode2.seconds << ":" << timecode2.frames << "::" << timecode2.subframes << endl;
1776 #endif
1777
1778
1779 #ifdef Timecode_SAMPLE_TEST_6
1780         // Test 6: use_offset = true, use_subframes = false, increment minutes
1781         cout << "use_offset = true, use_subframes = false, increment minutes" << endl;
1782
1783         timecode1.hours = 0;
1784         timecode1.minutes = 0;
1785         timecode1.seconds = 0;
1786         timecode1.frames = 0;
1787         timecode1.subframes = 0;
1788         sample1 = oldsample = 0;
1789         sample_increment = _session->frame_rate() * 60;
1790
1791         _session->sample_to_timecode( sample1, timecode1, true /* use_offset */, false /* use_subframes */ );
1792         cout << "Starting at sample: " << sample1 << " -> ";
1793         cout << "timecode: " << (timecode1.negative ? "-" : "") << timecode1.hours << ":" << timecode1.minutes << ":" << timecode1.seconds << ":" << timecode1.frames << "::" << timecode1.subframes << endl;
1794
1795         for (int i = 0; i < 60; i++) {
1796                 _session->timecode_to_sample( timecode1, sample1, true /* use_offset */, false /* use_subframes */ );
1797                 _session->sample_to_timecode( sample1, timecode2, true /* use_offset */, false /* use_subframes */ );
1798
1799 //     cout << "timecode: " << (timecode1.negative ? "-" : "") << timecode1.hours << ":" << timecode1.minutes << ":" << timecode1.seconds << ":" << timecode1.frames << "::" << timecode1.subframes << " -> ";
1800 //     cout << "sample: " << sample1 << endl;
1801 //     cout << "sample: " << sample1 << " -> ";
1802 //     cout << "timecode: " << (timecode2.negative ? "-" : "") << timecode2.hours << ":" << timecode2.minutes << ":" << timecode2.seconds << ":" << timecode2.frames << "::" << timecode2.subframes << endl;
1803
1804 //     if ((i > 0) && ( ((sample1 - oldsample) != sample_increment) && ((sample1 - oldsample) != (sample_increment + 1)) && ((sample1 - oldsample) != (sample_increment - 1))))
1805 //     {
1806 //       cout << "ERROR: sample increment not right: " << (sample1 - oldsample) << " != " << sample_increment << endl;
1807 //       break;
1808 //     }
1809
1810                 if (timecode2.hours != timecode1.hours || timecode2.minutes != timecode1.minutes || timecode2.seconds != timecode2.seconds || timecode2.frames != timecode1.frames) {
1811                         cout << "ERROR: timecode2 not equal timecode1" << endl;
1812                         cout << "timecode: " << (timecode1.negative ? "-" : "") << timecode1.hours << ":" << timecode1.minutes << ":" << timecode1.seconds << ":" << timecode1.frames << "::" << timecode1.subframes << " -> ";
1813                         cout << "sample: " << sample1 << endl;
1814                         cout << "sample: " << sample1 << " -> ";
1815                         cout << "timecode: " << (timecode2.negative ? "-" : "") << timecode2.hours << ":" << timecode2.minutes << ":" << timecode2.seconds << ":" << timecode2.frames << "::" << timecode2.subframes << endl;
1816                         break;
1817                 }
1818                 oldsample = sample1;
1819                 _session->timecode_increment_minutes( timecode1 );
1820         }
1821
1822         cout << "sample_increment: " << sample_increment << endl;
1823         cout << "sample: " << sample1 << " -> ";
1824         cout << "timecode: " << (timecode2.negative ? "-" : "") << timecode2.hours << ":" << timecode2.minutes << ":" << timecode2.seconds << ":" << timecode2.frames << "::" << timecode2.subframes << endl;
1825 #endif
1826
1827 #ifdef Timecode_SAMPLE_TEST_7
1828         // Test 7: use_offset = true, use_subframes = false, increment hours
1829         cout << "use_offset = true, use_subframes = false, increment hours" << endl;
1830
1831         timecode1.hours = 0;
1832         timecode1.minutes = 0;
1833         timecode1.seconds = 0;
1834         timecode1.frames = 0;
1835         timecode1.subframes = 0;
1836         sample1 = oldsample = 0;
1837         sample_increment = _session->frame_rate() * 60 * 60;
1838
1839         _session->sample_to_timecode( sample1, timecode1, true /* use_offset */, false /* use_subframes */ );
1840         cout << "Starting at sample: " << sample1 << " -> ";
1841         cout << "timecode: " << (timecode1.negative ? "-" : "") << timecode1.hours << ":" << timecode1.minutes << ":" << timecode1.seconds << ":" << timecode1.frames << "::" << timecode1.subframes << endl;
1842
1843         for (int i = 0; i < 10; i++) {
1844                 _session->timecode_to_sample( timecode1, sample1, true /* use_offset */, false /* use_subframes */ );
1845                 _session->sample_to_timecode( sample1, timecode2, true /* use_offset */, false /* use_subframes */ );
1846
1847 //     cout << "timecode: " << (timecode1.negative ? "-" : "") << timecode1.hours << ":" << timecode1.minutes << ":" << timecode1.seconds << ":" << timecode1.frames << "::" << timecode1.subframes << " -> ";
1848 //     cout << "sample: " << sample1 << endl;
1849 //     cout << "sample: " << sample1 << " -> ";
1850 //     cout << "timecode: " << (timecode2.negative ? "-" : "") << timecode2.hours << ":" << timecode2.minutes << ":" << timecode2.seconds << ":" << timecode2.frames << "::" << timecode2.subframes << endl;
1851
1852 //     if ((i > 0) && ( ((sample1 - oldsample) != sample_increment) && ((sample1 - oldsample) != (sample_increment + 1)) && ((sample1 - oldsample) != (sample_increment - 1))))
1853 //     {
1854 //       cout << "ERROR: sample increment not right: " << (sample1 - oldsample) << " != " << sample_increment << endl;
1855 //       break;
1856 //     }
1857
1858                 if (timecode2.hours != timecode1.hours || timecode2.minutes != timecode1.minutes || timecode2.seconds != timecode2.seconds || timecode2.frames != timecode1.frames) {
1859                         cout << "ERROR: timecode2 not equal timecode1" << endl;
1860                         cout << "timecode: " << (timecode1.negative ? "-" : "") << timecode1.hours << ":" << timecode1.minutes << ":" << timecode1.seconds << ":" << timecode1.frames << "::" << timecode1.subframes << " -> ";
1861                         cout << "sample: " << sample1 << endl;
1862                         cout << "sample: " << sample1 << " -> ";
1863                         cout << "timecode: " << (timecode2.negative ? "-" : "") << timecode2.hours << ":" << timecode2.minutes << ":" << timecode2.seconds << ":" << timecode2.frames << "::" << timecode2.subframes << endl;
1864                         break;
1865                 }
1866                 oldsample = sample1;
1867                 _session->timecode_increment_hours( timecode1 );
1868         }
1869
1870         cout << "sample_increment: " << sample_increment << endl;
1871         cout << "sample: " << sample1 << " -> ";
1872         cout << "timecode: " << (timecode2.negative ? "-" : "") << timecode2.hours << ":" << timecode2.minutes << ":" << timecode2.seconds << ":" << timecode2.frames << "::" << timecode2.subframes << endl;
1873 #endif
1874
1875 #endif
1876 }