/* Copyright 2009-2010 Yorba Foundation * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. */ namespace Model { using Logging; public interface TimeSystem : Object { public signal void geometry_changed(); public abstract void calculate_pixel_step(float inc, float pixel_min, float pixel_div); public abstract int64 xpos_to_time(int x); public abstract int64 xsize_to_time(int x); public abstract int time_to_xpos(int64 time); public abstract int64 get_pixel_snap_time(); public abstract int time_to_xsize(int64 time); public abstract float get_pixel_percentage(); public abstract int get_start_token(int xsize); public abstract int get_next_position(int token); public abstract int get_pixel_height(int token); public abstract string? get_display_string(int token); public abstract int frame_to_xsize(int frame); public abstract int xsize_to_frame(int xsize); public abstract string get_time_string(int64 time); public abstract string get_time_duration(int64 time); } public abstract class TimeSystemBase : Object { public const int PIXEL_SNAP_INTERVAL = 10; public float pixel_percentage = 0.0f; public float pixels_per_second; public int64 pixel_snap_time; const int BORDER = 4; // TODO: should use same value as timeline. will happen when this gets // refactored back into view code. abstract int[] get_timeline_seconds(); abstract int correct_sub_second_value(float seconds, int div, int fps); protected int correct_seconds_value (float seconds, int div, int fps) { if (seconds < 1.0f) { return correct_sub_second_value(seconds, div, fps); } int i; int secs = (int) seconds; int [] timeline_seconds = get_timeline_seconds(); for (i = timeline_seconds.length - 1; i > 0; i--) { if (secs <= timeline_seconds[i] && secs >= timeline_seconds[i - 1]) { if ((div % (timeline_seconds[i] * fps)) == 0) { break; } if ((div % (timeline_seconds[i - 1] * fps)) == 0) { i--; break; } } } return timeline_seconds[i] * fps; } public int64 get_pixel_snap_time() { return pixel_snap_time; } public float get_pixel_percentage() { return pixel_percentage; } public int64 xpos_to_time(int x) { return xsize_to_time(x - BORDER); } public int64 xsize_to_time(int size) { return (int64) ((float)(size * Gst.SECOND) / pixels_per_second); } public int time_to_xsize(int64 time) { return (int) (time * pixels_per_second / Gst.SECOND); } public int time_to_xpos(int64 time) { int pos = time_to_xsize(time) + BORDER; if (xpos_to_time(pos) != time) pos++; return pos; } } public class TimecodeTimeSystem : TimeSystem, TimeSystemBase { float pixels_per_frame; int small_pixel_frames = 0; int medium_pixel_frames = 0; int large_pixel_frames = 0; public Fraction frame_rate_fraction = Fraction(30000, 1001); override int correct_sub_second_value(float seconds, int div, int fps) { int frames = (int)(fps * seconds); if (frames == 0) { return 1; } if (div == 0) { div = fps; } int mod = div % frames; while (mod != 0) { mod = div % (++frames); } return frames; } public string get_time_string(int64 the_time) { string time; int frame = time_to_frame_with_rate(the_time, frame_rate_fraction); time = frame_to_string(frame, frame_rate_fraction); return time; } public string get_time_duration(int64 the_time) { // Timecode is already zero-based return get_time_string(the_time); } public void calculate_pixel_step(float inc, float pixel_min, float pixel_div) { int pixels_per_large = 300; int pixels_per_medium = 50; int pixels_per_small = 20; pixel_percentage += inc; if (pixel_percentage < 0.0f) pixel_percentage = 0.0f; else if (pixel_percentage > 1.0f) pixel_percentage = 1.0f; pixels_per_second = pixel_min * GLib.Math.powf(pixel_div, pixel_percentage); int fps = frame_rate_fraction.nearest_int(); large_pixel_frames = correct_seconds_value(pixels_per_large / pixels_per_second, 0, fps); medium_pixel_frames = correct_seconds_value(pixels_per_medium / pixels_per_second, large_pixel_frames, fps); small_pixel_frames = correct_seconds_value(pixels_per_small / pixels_per_second, medium_pixel_frames, fps); if (small_pixel_frames == medium_pixel_frames) { int i = medium_pixel_frames; while (--i > 0) { if ((medium_pixel_frames % i) == 0) { small_pixel_frames = i; break; } } } pixels_per_frame = pixels_per_second / (float) fps; pixel_snap_time = xsize_to_time(PIXEL_SNAP_INTERVAL); } public int frame_to_xsize(int frame) { return ((int) (frame * pixels_per_frame)); } public int xsize_to_frame(int xsize) { return (int) (xsize / pixels_per_frame); } public int get_start_token(int xsize) { int start_frame = xsize_to_frame(xsize); return large_pixel_frames * (start_frame / large_pixel_frames); } public int get_next_position(int token) { return token + small_pixel_frames; } public string? get_display_string(int frame) { if ((frame % large_pixel_frames) == 0) { return frame_to_time(frame, frame_rate_fraction).to_string(); } return null; } public int get_pixel_height(int frame) { if ((frame % medium_pixel_frames) == 0) { if (medium_pixel_frames == small_pixel_frames && (medium_pixel_frames != large_pixel_frames && frame % large_pixel_frames != 0)) { return 2; } else { return 6; } } else { return 2; } } override int[] get_timeline_seconds() { return { 1, 2, 5, 10, 15, 20, 30, 60, 120, 300, 600, 900, 1200, 1800, 3600 }; } } public interface TempoInformation { public abstract Fraction get_time_signature(); public abstract int get_bpm(); public signal void time_signature_changed(Fraction time_signature); public signal void bpm_changed(int bpm); } public class BarBeatTimeSystem : TimeSystem, TimeSystemBase { float pixels_per_sixteenth; int small_pixel_sixteenth = 0; int medium_pixel_sixteenth = 0; int large_pixel_sixteenth = 0; int[] timeline_bars = { 1, 2, 4, 8, 16, 24, 32, 64, 128, 256, 512, 768, 1024, 2048, 3192 }; int bpm; Fraction time_signature; float bars_per_minute; float bars_per_second; int sixteenths_per_bar; int sixteenths_per_beat; public BarBeatTimeSystem(TempoInformation tempo_information) { bpm = tempo_information.get_bpm(); time_signature = tempo_information.get_time_signature(); tempo_information.bpm_changed.connect(on_bpm_changed); tempo_information.time_signature_changed.connect(on_time_signature_changed); set_constants(); } void on_time_signature_changed(Fraction time_signature) { emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_time_signature_changed"); this.time_signature = time_signature; set_constants(); } void on_bpm_changed(int bpm) { emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_bpm_changed"); this.bpm = bpm; set_constants(); } void set_constants() { bars_per_minute = bpm / (float)time_signature.numerator; bars_per_second = bars_per_minute / 60.0f; sixteenths_per_beat = 16 / time_signature.denominator; sixteenths_per_bar = time_signature.numerator * sixteenths_per_beat; geometry_changed(); } override int correct_sub_second_value(float bars, int div, int unused) { int sixteenths = (int)(sixteenths_per_bar * bars); if (sixteenths == 0) { return 1; } if (sixteenths > sixteenths_per_beat) { return sixteenths_per_beat; } if (sixteenths > 2) { return 2; } return 1; } string beats_to_string(int total_sixteenths, bool maximum_resolution, bool zero_based) { int number_of_measures = (total_sixteenths / sixteenths_per_beat) / time_signature.numerator; int number_of_beats = (total_sixteenths / sixteenths_per_beat) % time_signature.numerator; int number_of_sixteenths = total_sixteenths % sixteenths_per_beat; if (!zero_based) { ++number_of_measures; ++number_of_beats; ++number_of_sixteenths; } float pixels_per_bar = pixels_per_second / bars_per_second; float pixels_per_large_gap = large_pixel_sixteenth * pixels_per_sixteenth; if (maximum_resolution || ((pixels_per_large_gap < pixels_per_sixteenth * sixteenths_per_beat) && number_of_sixteenths > 1)) { return "%d.%d.%d".printf(number_of_measures, number_of_beats, number_of_sixteenths); } else if (pixels_per_large_gap < pixels_per_bar && number_of_beats > 1) { return "%d.%d".printf(number_of_measures, number_of_beats); } else { return "%d".printf(number_of_measures); } } public string get_time_string(int64 the_time) { double beats_per_second = bpm / 60.0; double sixteenths_per_second = sixteenths_per_beat * beats_per_second; double sixteenths_per_nanosecond = sixteenths_per_second / Gst.SECOND; int total_beats = (int)(the_time * sixteenths_per_nanosecond); return beats_to_string(total_beats, true, false); } public string get_time_duration(int64 the_time) { double beats_per_second = bpm / 60.0; double sixteenths_per_second = sixteenths_per_beat * beats_per_second; double sixteenths_per_nanosecond = sixteenths_per_second / Gst.SECOND; int total_beats = (int)(the_time * sixteenths_per_nanosecond); if (total_beats == 0 && the_time > 0) { // round up total_beats = 1; } return beats_to_string(total_beats, true, true); } public void calculate_pixel_step(float inc, float pixel_min, float pixel_div) { int pixels_per_large = 80; int pixels_per_medium = 40; int pixels_per_small = 20; pixel_percentage += inc; if (pixel_percentage < 0.0f) { pixel_percentage = 0.0f; } else if (pixel_percentage > 1.0f) { pixel_percentage = 1.0f; } pixels_per_second = pixel_min * GLib.Math.powf(pixel_div, pixel_percentage); float pixels_per_bar = pixels_per_second / bars_per_second; large_pixel_sixteenth = correct_seconds_value( pixels_per_large / pixels_per_bar, 0, sixteenths_per_bar); medium_pixel_sixteenth = correct_seconds_value(pixels_per_medium / pixels_per_bar, large_pixel_sixteenth, sixteenths_per_bar); small_pixel_sixteenth = correct_seconds_value(pixels_per_small / pixels_per_bar, medium_pixel_sixteenth, sixteenths_per_bar); if (small_pixel_sixteenth == medium_pixel_sixteenth) { int i = medium_pixel_sixteenth; while (--i > 0) { if ((medium_pixel_sixteenth % i) == 0) { small_pixel_sixteenth = i; break; } } } pixels_per_sixteenth = pixels_per_bar / (float) sixteenths_per_bar; pixel_snap_time = xsize_to_time(PIXEL_SNAP_INTERVAL); } public int frame_to_xsize(int frame) { return ((int) (frame * pixels_per_sixteenth)); } public int xsize_to_frame(int xsize) { return (int) (xsize / pixels_per_sixteenth); } public int get_start_token(int xsize) { int start_frame = xsize_to_frame(xsize); return large_pixel_sixteenth * (start_frame / large_pixel_sixteenth); } public int get_next_position(int token) { return token + small_pixel_sixteenth; } public string? get_display_string(int frame) { if ((frame % large_pixel_sixteenth) == 0) { return beats_to_string(frame, false, false); } return null; } public int get_pixel_height(int frame) { if ((frame % medium_pixel_sixteenth) == 0) { if (medium_pixel_sixteenth == small_pixel_sixteenth && (medium_pixel_sixteenth != large_pixel_sixteenth && frame % large_pixel_sixteenth != 0)) { return 2; } else { return 6; } } else { return 2; } } override int[] get_timeline_seconds() { return timeline_bars; } } }