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.
-
#subticks_to_musical_position(total_subticks, meter) ⇒ Object
private
Decompose total subticks into a normalized bar:beat: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.
63 64 65 |
# File 'lib/head_music/time/musical_time_converter.rb', line 63 def meter_map @meter_map end |
#starting_musical_position ⇒ Object (readonly, private)
Returns the value of attribute starting_musical_position.
63 64 65 |
# File 'lib/head_music/time/musical_time_converter.rb', line 63 def starting_musical_position @starting_musical_position end |
#tempo_map ⇒ Object (readonly, private)
Returns the value of attribute tempo_map.
63 64 65 |
# File 'lib/head_music/time/musical_time_converter.rb', line 63 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 44 45 |
# File 'lib/head_music/time/musical_time_converter.rb', line 19 def clock_to_musical(clock_position) target_nanoseconds = clock_position.nanoseconds accumulated_nanoseconds = 0 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 accumulated_nanoseconds + segment_nanoseconds >= target_nanoseconds remaining_nanoseconds = target_nanoseconds - accumulated_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 accumulated_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
66 67 68 69 70 71 72 73 74 75 |
# File 'lib/head_music/time/musical_time_converter.rb', line 66 def musical_position_to_subticks(position, meter = nil) meter ||= meter_map.meter_at(position) subticks_per_count = meter.ticks_per_count * HeadMusic::Time::SUBTICKS_PER_TICK = meter. * subticks_per_count (position. - 1) * + (position.beat - 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.
49 50 51 52 53 54 55 56 57 58 59 |
# File 'lib/head_music/time/musical_time_converter.rb', line 49 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
78 79 80 81 82 83 |
# File 'lib/head_music/time/musical_time_converter.rb', line 78 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
86 87 88 89 |
# File 'lib/head_music/time/musical_time_converter.rb', line 86 def nanoseconds_to_subticks(nanoseconds, tempo) ticks = nanoseconds / tempo.tick_duration_in_nanoseconds.to_f (ticks * HeadMusic::Time::SUBTICKS_PER_TICK).round end |
#subticks_to_musical_position(total_subticks, meter) ⇒ Object (private)
Decompose total subticks into a normalized bar:beat:tick:subtick position
92 93 94 95 96 97 98 99 100 101 |
# File 'lib/head_music/time/musical_time_converter.rb', line 92 def subticks_to_musical_position(total_subticks, meter) subticks_per_count = meter.ticks_per_count * HeadMusic::Time::SUBTICKS_PER_TICK = meter. * subticks_per_count , remaining = total_subticks.divmod() beats, remaining = remaining.divmod(subticks_per_count) ticks, subticks = remaining.divmod(HeadMusic::Time::SUBTICKS_PER_TICK) MusicalPosition.new( + 1, beats + 1, ticks, subticks).normalize!(meter) end |