Initial commit
[fillmore] / src / marina / TimeSystem.vala
1 /* Copyright 2009-2010 Yorba Foundation
2  *
3  * This software is licensed under the GNU Lesser General Public License
4  * (version 2.1 or later).  See the COPYING file in this distribution. 
5  */
6
7 namespace Model {
8 using Logging;
9
10 public interface TimeSystem : Object {
11     public signal void geometry_changed();
12     public abstract void calculate_pixel_step(float inc, float pixel_min, float pixel_div);
13     public abstract int64 xpos_to_time(int x);
14     public abstract int64 xsize_to_time(int x);
15     public abstract int time_to_xpos(int64 time);
16     public abstract int64 get_pixel_snap_time();
17     public abstract int time_to_xsize(int64 time);
18     public abstract float get_pixel_percentage();
19     public abstract int get_start_token(int xsize);
20     public abstract int get_next_position(int token);
21     public abstract int get_pixel_height(int token);
22     public abstract string? get_display_string(int token);
23     public abstract int frame_to_xsize(int frame);
24     public abstract int xsize_to_frame(int xsize);
25     public abstract string get_time_string(int64 time);
26     public abstract string get_time_duration(int64 time);
27 }
28
29 public abstract class TimeSystemBase : Object {
30     public const int PIXEL_SNAP_INTERVAL = 10;
31
32     public float pixel_percentage = 0.0f;
33     public float pixels_per_second;
34     public int64 pixel_snap_time;
35
36     const int BORDER = 4;  // TODO: should use same value as timeline.  will happen when this gets
37                            // refactored back into view code.
38
39     abstract int[] get_timeline_seconds();
40     abstract int correct_sub_second_value(float seconds, int div, int fps);
41
42     protected int correct_seconds_value (float seconds, int div, int fps) {
43         if (seconds < 1.0f) {
44             return correct_sub_second_value(seconds, div, fps);
45         }
46
47         int i;
48         int secs = (int) seconds;
49         int [] timeline_seconds = get_timeline_seconds();
50         for (i = timeline_seconds.length - 1; i > 0; i--) {
51             if (secs <= timeline_seconds[i] &&
52                 secs >= timeline_seconds[i - 1]) {
53                 if ((div % (timeline_seconds[i] * fps)) == 0) {
54                     break;
55                 }
56                 if ((div % (timeline_seconds[i - 1] * fps)) == 0) {
57                     i--;
58                     break;
59                 }
60             }
61         }
62         return timeline_seconds[i] * fps;
63     }
64
65     public int64 get_pixel_snap_time() {
66         return pixel_snap_time;
67     }
68
69     public float get_pixel_percentage() {
70         return pixel_percentage;
71     }
72
73     public int64 xpos_to_time(int x) {
74         return xsize_to_time(x - BORDER);
75     }
76
77     public int64 xsize_to_time(int size) {
78         return (int64) ((float)(size * Gst.SECOND) / pixels_per_second);
79     }
80
81     public int time_to_xsize(int64 time) {
82         return (int) (time * pixels_per_second / Gst.SECOND);
83     }
84
85     public int time_to_xpos(int64 time) {
86         int pos = time_to_xsize(time) + BORDER;
87         
88         if (xpos_to_time(pos) != time)
89             pos++;
90         return pos;
91     }
92 }
93
94 public class TimecodeTimeSystem : TimeSystem, TimeSystemBase {
95     float pixels_per_frame;
96
97     int small_pixel_frames = 0;
98     int medium_pixel_frames = 0;
99     int large_pixel_frames = 0;
100
101     public Fraction frame_rate_fraction = Fraction(30000, 1001);
102
103     override int correct_sub_second_value(float seconds, int div, int fps) {
104         int frames = (int)(fps * seconds);
105         if (frames == 0) {
106             return 1;
107         }
108
109         if (div == 0) {
110             div = fps;
111         }
112
113         int mod = div % frames;
114         while (mod != 0) {
115             mod = div % (++frames);
116         }
117         return frames;
118     }
119
120     public string get_time_string(int64 the_time) {
121         string time;
122
123         int frame = time_to_frame_with_rate(the_time, frame_rate_fraction);
124         time = frame_to_string(frame, frame_rate_fraction);
125
126         return time;
127     }
128
129     public string get_time_duration(int64 the_time) {
130         // Timecode is already zero-based
131         return get_time_string(the_time);
132     }
133     public void calculate_pixel_step(float inc, float pixel_min, float pixel_div) {
134         int pixels_per_large = 300;
135         int pixels_per_medium = 50;
136         int pixels_per_small = 20;
137
138         pixel_percentage += inc;
139         if (pixel_percentage < 0.0f)
140             pixel_percentage = 0.0f;
141         else if (pixel_percentage > 1.0f)
142             pixel_percentage = 1.0f;
143
144         pixels_per_second = pixel_min * GLib.Math.powf(pixel_div, pixel_percentage);
145         int fps = frame_rate_fraction.nearest_int();
146         large_pixel_frames = correct_seconds_value(pixels_per_large / pixels_per_second, 0, fps);
147         medium_pixel_frames = correct_seconds_value(pixels_per_medium / pixels_per_second, 
148                                                     large_pixel_frames, fps);
149         small_pixel_frames = correct_seconds_value(pixels_per_small / pixels_per_second, 
150                                                     medium_pixel_frames, fps);
151
152         if (small_pixel_frames == medium_pixel_frames) {
153             int i = medium_pixel_frames;
154
155             while (--i > 0) {
156                 if ((medium_pixel_frames % i) == 0) {
157                     small_pixel_frames = i;
158                     break;
159                 }
160             }
161         }
162
163         pixels_per_frame = pixels_per_second / (float) fps;
164         pixel_snap_time = xsize_to_time(PIXEL_SNAP_INTERVAL);
165     }
166
167     public int frame_to_xsize(int frame) {
168         return ((int) (frame * pixels_per_frame));
169     }
170
171     public int xsize_to_frame(int xsize) {
172         return (int) (xsize / pixels_per_frame);
173     }
174
175     public int get_start_token(int xsize) {
176         int start_frame = xsize_to_frame(xsize);
177         return large_pixel_frames * (start_frame / large_pixel_frames);
178     }
179
180     public int get_next_position(int token) {
181         return token + small_pixel_frames;
182     }
183
184     public string? get_display_string(int frame) {
185         if ((frame % large_pixel_frames) == 0) {
186             return frame_to_time(frame, frame_rate_fraction).to_string();
187         }
188         return null;
189     }
190
191     public int get_pixel_height(int frame) {
192         if ((frame % medium_pixel_frames) == 0) {
193             if (medium_pixel_frames == small_pixel_frames &&
194                     (medium_pixel_frames != large_pixel_frames &&
195                     frame % large_pixel_frames != 0)) {
196                 return 2;
197             }
198             else {
199                 return 6;
200             }
201         } else {
202             return 2;
203         }
204     }
205
206     override int[] get_timeline_seconds() {
207         return { 1, 2, 5, 10, 15, 20, 30, 60, 120, 300, 600, 900, 1200, 1800, 3600 };
208     }
209 }
210
211 public interface TempoInformation {
212     public abstract Fraction get_time_signature();
213     public abstract int get_bpm();
214     public signal void time_signature_changed(Fraction time_signature);
215     public signal void bpm_changed(int bpm);
216 }
217
218 public class BarBeatTimeSystem : TimeSystem, TimeSystemBase {
219     float pixels_per_sixteenth;
220
221     int small_pixel_sixteenth = 0;
222     int medium_pixel_sixteenth = 0;
223     int large_pixel_sixteenth = 0;
224     int[] timeline_bars = { 
225             1, 2, 4, 8, 16, 24, 32, 64, 128, 256, 512, 768, 1024, 2048, 3192 
226         };
227
228     int bpm;
229     Fraction time_signature;
230     float bars_per_minute;
231     float bars_per_second;
232     int sixteenths_per_bar;
233     int sixteenths_per_beat;
234
235     public BarBeatTimeSystem(TempoInformation tempo_information) {
236         bpm = tempo_information.get_bpm();
237         time_signature = tempo_information.get_time_signature();
238         tempo_information.bpm_changed.connect(on_bpm_changed);
239         tempo_information.time_signature_changed.connect(on_time_signature_changed);
240         set_constants();
241     }
242
243     void on_time_signature_changed(Fraction time_signature) {
244         emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_time_signature_changed");
245         this.time_signature = time_signature;
246         set_constants();
247     }
248
249     void on_bpm_changed(int bpm) {
250         emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_bpm_changed");
251         this.bpm = bpm;
252         set_constants();
253     }
254
255     void set_constants() {
256         bars_per_minute = bpm / (float)time_signature.numerator;
257         bars_per_second = bars_per_minute / 60.0f;
258
259         sixteenths_per_beat = 16 / time_signature.denominator;
260         sixteenths_per_bar = time_signature.numerator * sixteenths_per_beat;
261         geometry_changed();
262     }
263
264     override int correct_sub_second_value(float bars, int div, int unused) {
265         int sixteenths = (int)(sixteenths_per_bar * bars);
266
267         if (sixteenths == 0) {
268             return 1;
269         }
270
271         if (sixteenths > sixteenths_per_beat) {
272             return sixteenths_per_beat;
273         }
274
275         if (sixteenths > 2) {
276             return 2;
277         }
278
279         return 1;
280     }
281
282    string beats_to_string(int total_sixteenths, bool maximum_resolution, bool zero_based) {
283         int number_of_measures = 
284             (total_sixteenths / sixteenths_per_beat) / time_signature.numerator;
285
286         int number_of_beats = 
287             (total_sixteenths / sixteenths_per_beat) % time_signature.numerator;
288         int number_of_sixteenths = total_sixteenths % sixteenths_per_beat;
289         if (!zero_based) {
290             ++number_of_measures;
291             ++number_of_beats;
292             ++number_of_sixteenths;
293         }
294         float pixels_per_bar = pixels_per_second / bars_per_second;
295         float pixels_per_large_gap = large_pixel_sixteenth * pixels_per_sixteenth;
296         if (maximum_resolution ||
297             ((pixels_per_large_gap < pixels_per_sixteenth * sixteenths_per_beat) &&
298             number_of_sixteenths > 1)) {
299             return "%d.%d.%d".printf(number_of_measures, number_of_beats, number_of_sixteenths);
300         } else if (pixels_per_large_gap < pixels_per_bar && number_of_beats > 1) {
301             return "%d.%d".printf(number_of_measures, number_of_beats);
302         } else {
303             return "%d".printf(number_of_measures);
304         }
305     }
306
307     public string get_time_string(int64 the_time) {
308         double beats_per_second = bpm / 60.0;
309         double sixteenths_per_second = sixteenths_per_beat * beats_per_second;
310         double sixteenths_per_nanosecond = sixteenths_per_second / Gst.SECOND;
311         int total_beats = (int)(the_time * sixteenths_per_nanosecond);
312         return beats_to_string(total_beats, true, false);
313     }
314
315     public string get_time_duration(int64 the_time) {
316         double beats_per_second = bpm / 60.0;
317         double sixteenths_per_second = sixteenths_per_beat * beats_per_second;
318         double sixteenths_per_nanosecond = sixteenths_per_second / Gst.SECOND;
319         int total_beats = (int)(the_time * sixteenths_per_nanosecond);
320         if (total_beats == 0 && the_time > 0) {
321             // round up
322             total_beats = 1;
323         }
324         return beats_to_string(total_beats, true, true);
325     }
326
327     public void calculate_pixel_step(float inc, float pixel_min, float pixel_div) {
328         int pixels_per_large = 80;
329         int pixels_per_medium = 40;
330         int pixels_per_small = 20;
331
332         pixel_percentage += inc;
333         if (pixel_percentage < 0.0f) {
334             pixel_percentage = 0.0f;
335         } else if (pixel_percentage > 1.0f) {
336             pixel_percentage = 1.0f;
337         }
338
339         pixels_per_second = pixel_min * GLib.Math.powf(pixel_div, pixel_percentage);
340         float pixels_per_bar = pixels_per_second / bars_per_second;
341         large_pixel_sixteenth = correct_seconds_value(
342             pixels_per_large / pixels_per_bar, 0, sixteenths_per_bar);
343
344         medium_pixel_sixteenth = correct_seconds_value(pixels_per_medium / pixels_per_bar,
345             large_pixel_sixteenth, sixteenths_per_bar);
346         small_pixel_sixteenth = correct_seconds_value(pixels_per_small / pixels_per_bar,
347             medium_pixel_sixteenth, sixteenths_per_bar);
348         if (small_pixel_sixteenth == medium_pixel_sixteenth) {
349             int i = medium_pixel_sixteenth;
350
351             while (--i > 0) {
352                 if ((medium_pixel_sixteenth % i) == 0) {
353                     small_pixel_sixteenth = i;
354                     break;
355                 }
356             }
357         }
358
359         pixels_per_sixteenth = pixels_per_bar / (float) sixteenths_per_bar;
360         pixel_snap_time = xsize_to_time(PIXEL_SNAP_INTERVAL);
361     }
362
363     public int frame_to_xsize(int frame) {
364         return ((int) (frame * pixels_per_sixteenth));
365     }
366
367     public int xsize_to_frame(int xsize) {
368         return (int) (xsize / pixels_per_sixteenth);
369     }
370
371     public int get_start_token(int xsize) {
372         int start_frame = xsize_to_frame(xsize);
373         return large_pixel_sixteenth * (start_frame / large_pixel_sixteenth);
374     }
375
376     public int get_next_position(int token) {
377         return token + small_pixel_sixteenth;
378     }
379
380     public string? get_display_string(int frame) {
381         if ((frame % large_pixel_sixteenth) == 0) {
382             return beats_to_string(frame, false, false);
383         }
384         return null;
385     }
386
387     public int get_pixel_height(int frame) {
388         if ((frame % medium_pixel_sixteenth) == 0) {
389             if (medium_pixel_sixteenth == small_pixel_sixteenth &&
390                     (medium_pixel_sixteenth != large_pixel_sixteenth &&
391                     frame % large_pixel_sixteenth != 0)) {
392                 return 2;
393             }
394             else {
395                 return 6;
396             }
397         } else {
398             return 2;
399         }
400     }
401
402     override int[] get_timeline_seconds() {
403         return timeline_bars;
404     }
405 }
406 }