Class: HeadMusic::Time::TempoMap

Inherits:
Object
  • Object
show all
Includes:
EventMapSupport
Defined in:
lib/head_music/time/tempo_map.rb

Overview

Manages tempo changes along a musical timeline

A TempoMap maintains a sorted list of tempo changes at specific musical positions, allowing you to determine which tempo is active at any point and iterate through tempo segments for time calculations.

Examples:

Basic usage

tempo_map = HeadMusic::Time::TempoMap.new
tempo_map.add_change(MusicalPosition.new(5, 1, 0, 0), "quarter", 96)
tempo_map.add_change(MusicalPosition.new(9, 1, 0, 0), "quarter", 140)

tempo = tempo_map.tempo_at(MusicalPosition.new(7, 1, 0, 0))
tempo.beats_per_minute # => 96.0

Iterating through segments

from = MusicalPosition.new(1, 1, 0, 0)
to = MusicalPosition.new(10, 1, 0, 0)
tempo_map.each_segment(from, to) do |start_pos, end_pos, tempo|
  # Calculate clock time for this segment
end

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(starting_tempo: nil, starting_position: nil) ⇒ TempoMap

Returns a new instance of TempoMap.



31
32
33
34
35
36
# File 'lib/head_music/time/tempo_map.rb', line 31

def initialize(starting_tempo: nil, starting_position: nil)
  starting_tempo ||= HeadMusic::Rudiment::Tempo.new("quarter", 120)
  starting_position ||= MusicalPosition.new
  @events = [TempoEvent.new(starting_position, starting_tempo.beat_value.to_s, starting_tempo.beats_per_minute)]
  @meter = nil
end

Instance Attribute Details

#eventsArray<TempoEvent> (readonly)

Returns all tempo events in chronological order.

Returns:

  • (Array<TempoEvent>)

    all tempo events in chronological order



29
30
31
# File 'lib/head_music/time/tempo_map.rb', line 29

def events
  @events
end

#meter=(value) ⇒ Object (writeonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



94
95
96
# File 'lib/head_music/time/tempo_map.rb', line 94

def meter=(value)
  @meter = value
end

Instance Method Details

#add_change(position, beat_value_or_tempo, beats_per_minute = nil) ⇒ Object



38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/head_music/time/tempo_map.rb', line 38

def add_change(position, beat_value_or_tempo, beats_per_minute = nil)
  remove_change(position)
  event = if beat_value_or_tempo.is_a?(HeadMusic::Rudiment::Tempo)
    TempoEvent.new(position, beat_value_or_tempo.beat_value.to_s, beat_value_or_tempo.beats_per_minute).tap do |tempo_event|
      tempo_event.tempo = beat_value_or_tempo
    end
  else
    TempoEvent.new(position, beat_value_or_tempo, beats_per_minute)
  end
  @events << event
  sort_events!
  event
end

#clear_changesObject



58
59
60
# File 'lib/head_music/time/tempo_map.rb', line 58

def clear_changes
  @events = [@events.first]
end

#compare_positions(first_position, second_position) ⇒ Object (private) Originally defined in module EventMapSupport

#each_segment(from_position, to_position) {|current_pos, to_pos, current_tempo| ... } ⇒ Object

Yields:

  • (current_pos, to_pos, current_tempo)


70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/head_music/time/tempo_map.rb', line 70

def each_segment(from_position, to_position)
  from_pos = normalize_position(from_position)
  to_pos = normalize_position(to_position)

  relevant_events = @events.select do |event|
    normalize_position(event.position) < to_pos
  end

  current_pos = from_pos
  current_tempo = tempo_at(from_pos)

  relevant_events.each do |event|
    normalized_event_pos = normalize_position(event.position)
    next if normalized_event_pos <= from_pos

    yield current_pos, normalized_event_pos, current_tempo
    current_pos = normalized_event_pos
    current_tempo = event.tempo
  end

  yield current_pos, to_pos, current_tempo
end

#normalize_position(position) ⇒ Object (private)



98
99
100
101
102
# File 'lib/head_music/time/tempo_map.rb', line 98

def normalize_position(position)
  return position unless @meter

  position.dup.tap { |pos| pos.normalize!(@meter) }
end

#position_tuple(position) ⇒ Object (private) Originally defined in module EventMapSupport

#positions_equal?(first_position, second_position) ⇒ Boolean (private) Originally defined in module EventMapSupport

Returns:

  • (Boolean)

#remove_change(position) ⇒ Object



52
53
54
55
56
# File 'lib/head_music/time/tempo_map.rb', line 52

def remove_change(position)
  @events.reject! do |event|
    event != @events.first && positions_equal?(event.position, position)
  end
end

#sort_events!Object (private) Originally defined in module EventMapSupport

#tempo_at(position) ⇒ Object



62
63
64
65
66
67
68
# File 'lib/head_music/time/tempo_map.rb', line 62

def tempo_at(position)
  normalized_pos = normalize_position(position)
  active_event = @events.reverse.find do |event|
    normalize_position(event.position) <= normalized_pos
  end
  active_event&.tempo || @events.first.tempo
end