Class: HeadMusic::Time::MusicalTimeConverter
- Inherits:
-
Object
- Object
- HeadMusic::Time::MusicalTimeConverter
- Defined in:
- lib/head_music/time/musical_time_converter.rb
Overview
Converts between clock time (elapsed nanoseconds) and musical position (bars:beats:ticks:subticks) using a tempo map and a meter map, walking the timeline one tempo segment at a time so tempo and meter changes are accounted for. Subticks are the shared integer unit both directions pass through.
Instance Attribute Summary collapse
-
#meter_map ⇒ Object
readonly
private
Returns the value of attribute meter_map.
-
#starting_musical_position ⇒ Object
readonly
private
Returns the value of attribute starting_musical_position.
-
#tempo_map ⇒ Object
readonly
private
Returns the value of attribute tempo_map.
Instance Method Summary collapse
-
#clock_to_musical(clock_position) ⇒ MusicalPosition
The corresponding musical position.
-
#initialize(tempo_map:, meter_map:, starting_musical_position:) ⇒ MusicalTimeConverter
constructor
A new instance of MusicalTimeConverter.
-
#musical_position_to_subticks(position, meter = nil) ⇒ Object
private
Convert a musical position to total subticks for calculation.
-
#musical_to_clock(musical_position) ⇒ ClockPosition
The corresponding clock time.
-
#nanoseconds_in_segment(start_pos, end_pos, tempo, meter) ⇒ Object
private
Clock duration of a tempo segment, in nanoseconds.
-
#nanoseconds_to_subticks(nanoseconds, tempo) ⇒ Object
private
Convert an elapsed nanosecond duration into subticks at the given tempo.
-
#subtick_sizes(meter) ⇒ Object
private
Subticks in one count and in one bar of the meter.
-
#subticks_to_musical_position(total_subticks, meter) ⇒ Object
private
Decompose total subticks into a normalized bar:count:tick:subtick position.
Constructor Details
#initialize(tempo_map:, meter_map:, starting_musical_position:) ⇒ MusicalTimeConverter
Returns a new instance of MusicalTimeConverter.
11 12 13 14 15 |
# File 'lib/head_music/time/musical_time_converter.rb', line 11 def initialize(tempo_map:, meter_map:, starting_musical_position:) @tempo_map = tempo_map @meter_map = meter_map @starting_musical_position = starting_musical_position end |
Instance Attribute Details
#meter_map ⇒ Object (readonly, private)
Returns the value of attribute meter_map.
61 62 63 |
# File 'lib/head_music/time/musical_time_converter.rb', line 61 def meter_map @meter_map end |
#starting_musical_position ⇒ Object (readonly, private)
Returns the value of attribute starting_musical_position.
61 62 63 |
# File 'lib/head_music/time/musical_time_converter.rb', line 61 def starting_musical_position @starting_musical_position end |
#tempo_map ⇒ Object (readonly, private)
Returns the value of attribute tempo_map.
61 62 63 |
# File 'lib/head_music/time/musical_time_converter.rb', line 61 def tempo_map @tempo_map end |
Instance Method Details
#clock_to_musical(clock_position) ⇒ MusicalPosition
Returns the corresponding musical position.
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
# File 'lib/head_music/time/musical_time_converter.rb', line 19 def clock_to_musical(clock_position) remaining_nanoseconds = clock_position.nanoseconds current_position = starting_musical_position # We need an end position far enough to contain our target clock time estimated_end = MusicalPosition.new(starting_musical_position. + 1000, 1, 0, 0) tempo_map.each_segment(starting_musical_position, estimated_end) do |start_pos, end_pos, tempo| meter = meter_map.meter_at(start_pos) segment_nanoseconds = nanoseconds_in_segment(start_pos, end_pos, tempo, meter) # If our target falls within this segment, calculate the exact position if segment_nanoseconds >= remaining_nanoseconds total_subticks = musical_position_to_subticks(start_pos, meter) + nanoseconds_to_subticks(remaining_nanoseconds, tempo) return subticks_to_musical_position(total_subticks, meter) end remaining_nanoseconds -= segment_nanoseconds current_position = end_pos end # If we get here, return the last position (shouldn't normally happen) current_position end |
#musical_position_to_subticks(position, meter = nil) ⇒ Object (private)
Convert a musical position to total subticks for calculation
64 65 66 67 68 69 70 71 72 |
# File 'lib/head_music/time/musical_time_converter.rb', line 64 def musical_position_to_subticks(position, meter = nil) meter ||= meter_map.meter_at(position) subticks_per_count, = subtick_sizes(meter) (position. - 1) * + (position.count - 1) * subticks_per_count + position.tick * HeadMusic::Time::SUBTICKS_PER_TICK + position.subtick end |
#musical_to_clock(musical_position) ⇒ ClockPosition
Returns the corresponding clock time.
47 48 49 50 51 52 53 54 55 56 57 |
# File 'lib/head_music/time/musical_time_converter.rb', line 47 def musical_to_clock(musical_position) total_nanoseconds = 0 # Iterate through each tempo segment from start to target position tempo_map.each_segment(starting_musical_position, musical_position) do |start_pos, end_pos, tempo| meter = meter_map.meter_at(start_pos) total_nanoseconds += nanoseconds_in_segment(start_pos, end_pos, tempo, meter) end ClockPosition.new(total_nanoseconds) end |
#nanoseconds_in_segment(start_pos, end_pos, tempo, meter) ⇒ Object (private)
Clock duration of a tempo segment, in nanoseconds
75 76 77 78 79 80 |
# File 'lib/head_music/time/musical_time_converter.rb', line 75 def nanoseconds_in_segment(start_pos, end_pos, tempo, meter) segment_subticks = musical_position_to_subticks(end_pos, meter) - musical_position_to_subticks(start_pos, meter) segment_ticks = segment_subticks / HeadMusic::Time::SUBTICKS_PER_TICK.to_f (segment_ticks * tempo.tick_duration_in_nanoseconds).round end |
#nanoseconds_to_subticks(nanoseconds, tempo) ⇒ Object (private)
Convert an elapsed nanosecond duration into subticks at the given tempo
83 84 85 86 |
# File 'lib/head_music/time/musical_time_converter.rb', line 83 def nanoseconds_to_subticks(nanoseconds, tempo) ticks = nanoseconds / tempo.tick_duration_in_nanoseconds.to_f (ticks * HeadMusic::Time::SUBTICKS_PER_TICK).round end |
#subtick_sizes(meter) ⇒ Object (private)
Subticks in one count and in one bar of the meter
99 100 101 102 |
# File 'lib/head_music/time/musical_time_converter.rb', line 99 def subtick_sizes(meter) per_count = meter.ticks_per_count * HeadMusic::Time::SUBTICKS_PER_TICK [per_count, meter. * per_count] end |
#subticks_to_musical_position(total_subticks, meter) ⇒ Object (private)
Decompose total subticks into a normalized bar:count:tick:subtick position
89 90 91 92 93 94 95 96 |
# File 'lib/head_music/time/musical_time_converter.rb', line 89 def subticks_to_musical_position(total_subticks, meter) subticks_per_count, = subtick_sizes(meter) , remaining = total_subticks.divmod() counts, remaining = remaining.divmod(subticks_per_count) ticks, subticks = remaining.divmod(HeadMusic::Time::SUBTICKS_PER_TICK) MusicalPosition.new( + 1, counts + 1, ticks, subticks).normalize!(meter) end |