Class: HeadMusic::Time::Conductor

Inherits:
Object
  • Object
show all
Defined in:
lib/head_music/time/conductor.rb

Overview

Representation of a conductor track for musical material

The Conductor class synchronizes three different time representations: - Clock time: elapsed nanoseconds (source of truth) - Musical position: bars:beats:ticks:subticks notation - SMPTE timecode: hours:minutes:seconds:frames

Each moment in a track corresponds to all three positions simultaneously. The conductor handles conversions between these representations based on the current tempo, meter, and framerate.

Examples:

Basic usage

conductor = HeadMusic::Time::Conductor.new
clock_pos = HeadMusic::Time::ClockPosition.new(1_000_000_000) # 1 second
musical_pos = conductor.clock_to_musical(clock_pos)
smpte = conductor.clock_to_smpte(clock_pos)

With custom tempo and meter

conductor = HeadMusic::Time::Conductor.new(
  starting_tempo: HeadMusic::Rudiment::Tempo.new("quarter", 96),
  starting_meter: HeadMusic::Rudiment::Meter.get("3/4")
)

Converting between representations

conductor = HeadMusic::Time::Conductor.new
musical = HeadMusic::Time::MusicalPosition.new(2, 1, 0, 0)
clock = conductor.musical_to_clock(musical)
smpte = conductor.clock_to_smpte(clock)

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(starting_musical_position: nil, starting_smpte_timecode: nil, framerate: SmpteTimecode::DEFAULT_FRAMERATE, starting_tempo: nil, starting_meter: nil, tempo_map: nil, meter_map: nil) ⇒ Conductor

Create a new conductor

Parameters:

  • starting_musical_position (MusicalPosition) (defaults to: nil)

    initial musical position (default: 1:1:0:0)

  • starting_smpte_timecode (SmpteTimecode) (defaults to: nil)

    initial SMPTE timecode (default: 00:00:00:00)

  • framerate (Integer) (defaults to: SmpteTimecode::DEFAULT_FRAMERATE)

    frames per second (default: 30)

  • starting_tempo (HeadMusic::Rudiment::Tempo) (defaults to: nil)

    initial tempo (default: quarter = 120)

  • starting_meter (HeadMusic::Rudiment::Meter, String) (defaults to: nil)

    initial meter (default: 4/4)

  • tempo_map (TempoMap) (defaults to: nil)

    custom tempo map (optional, creates one from starting_tempo if not provided)

  • meter_map (MeterMap) (defaults to: nil)

    custom meter map (optional, creates one from starting_meter if not provided)



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

def initialize(
  starting_musical_position: nil,
  starting_smpte_timecode: nil,
  framerate: SmpteTimecode::DEFAULT_FRAMERATE,
  starting_tempo: nil,
  starting_meter: nil,
  tempo_map: nil,
  meter_map: nil
)
  @starting_musical_position = starting_musical_position || MusicalPosition.new
  @starting_smpte_timecode = starting_smpte_timecode || SmpteTimecode.new(0, 0, 0, 0, framerate: framerate)
  @framerate = framerate

  # Create or use provided maps
  @tempo_map = tempo_map || TempoMap.new(
    starting_tempo: starting_tempo || HeadMusic::Rudiment::Tempo.new("quarter", 120),
    starting_position: @starting_musical_position
  )
  @meter_map = meter_map || MeterMap.new(
    starting_meter: starting_meter || "4/4",
    starting_position: @starting_musical_position
  )

  # Link maps together for position normalization
  @tempo_map.meter = @meter_map.meter_at(@starting_musical_position)
end

Instance Attribute Details

#framerateInteger

Returns frames per second for SMPTE conversions.

Returns:

  • (Integer)

    frames per second for SMPTE conversions



41
42
43
# File 'lib/head_music/time/conductor.rb', line 41

def framerate
  @framerate
end

#meter_mapMeterMap (readonly)

Returns the meter map for this conductor.

Returns:

  • (MeterMap)

    the meter map for this conductor



47
48
49
# File 'lib/head_music/time/conductor.rb', line 47

def meter_map
  @meter_map
end

#starting_musical_positionMusicalPosition

Returns the musical position at clock time 0.

Returns:



35
36
37
# File 'lib/head_music/time/conductor.rb', line 35

def starting_musical_position
  @starting_musical_position
end

#starting_smpte_timecodeSmpteTimecode

Returns the SMPTE timecode at clock time 0.

Returns:



38
39
40
# File 'lib/head_music/time/conductor.rb', line 38

def starting_smpte_timecode
  @starting_smpte_timecode
end

#tempo_mapTempoMap (readonly)

Returns the tempo map for this conductor.

Returns:

  • (TempoMap)

    the tempo map for this conductor



44
45
46
# File 'lib/head_music/time/conductor.rb', line 44

def tempo_map
  @tempo_map
end

Instance Method Details

#clock_to_musical(clock_position) ⇒ MusicalPosition

Convert clock position to musical position

Uses the tempo map to determine how many beats have elapsed, accounting for tempo changes along the timeline.

Parameters:

Returns:



102
103
104
# File 'lib/head_music/time/conductor.rb', line 102

def clock_to_musical(clock_position)
  musical_time_converter.clock_to_musical(clock_position)
end

#clock_to_smpte(clock_position) ⇒ SmpteTimecode

Convert clock position to SMPTE timecode

Uses the framerate to determine the timecode.

Parameters:

Returns:



123
124
125
# File 'lib/head_music/time/conductor.rb', line 123

def clock_to_smpte(clock_position)
  smpte_converter.clock_to_smpte(clock_position)
end

#musical_time_converterObject (private)

A fresh musical-time converter over the current maps and starting position (starting_musical_position is a mutable accessor), rebuilt per call rather than memoized so reassignment takes effect.



142
143
144
145
146
147
148
# File 'lib/head_music/time/conductor.rb', line 142

def musical_time_converter
  MusicalTimeConverter.new(
    tempo_map: tempo_map,
    meter_map: meter_map,
    starting_musical_position: starting_musical_position
  )
end

#musical_to_clock(musical_position) ⇒ ClockPosition

Convert musical position to clock position

Uses the tempo map to determine how much clock time has elapsed based on the musical position, accounting for tempo changes.

Parameters:

Returns:



113
114
115
# File 'lib/head_music/time/conductor.rb', line 113

def musical_to_clock(musical_position)
  musical_time_converter.musical_to_clock(musical_position)
end

#smpte_converterObject (private)

A fresh SMPTE converter reflecting the current framerate and starting timecode. Both are mutable accessors, so it is rebuilt per call rather than memoized, ensuring a reassigned framerate takes effect.



153
154
155
# File 'lib/head_music/time/conductor.rb', line 153

def smpte_converter
  SmpteConverter.new(framerate: framerate, starting_smpte_timecode: starting_smpte_timecode)
end

#smpte_to_clock(smpte_timecode) ⇒ ClockPosition

Convert SMPTE timecode to clock position

Uses the framerate to determine the clock time.

Parameters:

  • smpte_timecode (SmpteTimecode)

    the SMPTE timecode to convert

Returns:



133
134
135
# File 'lib/head_music/time/conductor.rb', line 133

def smpte_to_clock(smpte_timecode)
  smpte_converter.smpte_to_clock(smpte_timecode)
end

#starting_meterHeadMusic::Rudiment::Meter

Returns the initial meter (delegates to meter_map).

Returns:



55
56
57
# File 'lib/head_music/time/conductor.rb', line 55

def starting_meter
  meter_map.events.first.meter
end

#starting_tempoHeadMusic::Rudiment::Tempo

Returns the initial tempo (delegates to tempo_map).

Returns:



50
51
52
# File 'lib/head_music/time/conductor.rb', line 50

def starting_tempo
  tempo_map.events.first.tempo
end