Class: HeadMusic::Time::MusicalPosition
- Inherits:
-
Object
- Object
- HeadMusic::Time::MusicalPosition
- Includes:
- Comparable, RadixCarry
- Defined in:
- lib/head_music/time/musical_position.rb
Overview
Representation of a musical position in bars:beats:ticks:subticks notation
A MusicalPosition represents a point in musical time using a hierarchical structure:
- bar: the measure number (1-indexed)
- count: the count within the bar (1-indexed)
- tick: subdivision of a beat (0-indexed, 960 ticks per quarter note)
- subtick: finest resolution (0-indexed, 240 subticks per tick)
The position can be normalized according to a meter, which handles overflow by carrying excess values to higher levels (e.g., excess ticks become beats, excess beats become bars).
Constant Summary collapse
- DEFAULT_FIRST_BAR =
Default starting bar number
1- FIRST_COUNT =
First count in a bar
1- FIRST_TICK =
First tick in a beat
0- FIRST_SUBTICK =
First subtick in a tick
0
Instance Attribute Summary collapse
-
#bar ⇒ Integer
readonly
The bar (measure) number (1-indexed).
-
#count ⇒ Integer
readonly
A count, not a beat: Meter distinguishes the two, and in 6/8 there are two beats and six counts.
-
#subtick ⇒ Integer
readonly
The subtick within the tick (0-indexed).
-
#tick ⇒ Integer
readonly
The tick within the beat (0-indexed).
Class Method Summary collapse
-
.parse(identifier) ⇒ MusicalPosition
Parse a position from a string representation.
Instance Method Summary collapse
-
#<=>(other) ⇒ Integer
Compare this position to another.
-
#carry(component, radix, first: 0) ⇒ Integer
included
from RadixCarry
private
Divide the named component by its radix, store the remainder back, and return the amount to carry into the next-higher component.
-
#eql?(other) ⇒ Boolean
True when other is a position with the same components.
-
#hash ⇒ Integer
A hash over the position's components.
-
#initialize(bar = DEFAULT_FIRST_BAR, count = FIRST_COUNT, tick = FIRST_TICK, subtick = FIRST_SUBTICK) ⇒ MusicalPosition
constructor
Create a new musical position.
-
#normalize!(meter) ⇒ self
Normalize the position according to a meter, handling overflow.
-
#to_a ⇒ Array<Integer>
Convert position to array format.
-
#to_s ⇒ String
Convert position to string format.
Constructor Details
#initialize(bar = DEFAULT_FIRST_BAR, count = FIRST_COUNT, tick = FIRST_TICK, subtick = FIRST_SUBTICK) ⇒ MusicalPosition
Create a new musical position
87 88 89 90 91 92 93 94 95 96 97 |
# File 'lib/head_music/time/musical_position.rb', line 87 def initialize( = DEFAULT_FIRST_BAR, count = FIRST_COUNT, tick = FIRST_TICK, subtick = FIRST_SUBTICK ) @bar = .to_i @count = count.to_i @tick = tick.to_i @subtick = subtick.to_i end |
Instance Attribute Details
#bar ⇒ Integer (readonly)
Returns the bar (measure) number (1-indexed).
45 46 47 |
# File 'lib/head_music/time/musical_position.rb', line 45 def @bar end |
#count ⇒ Integer (readonly)
A count, not a beat: Meter distinguishes the two, and in 6/8 there are two beats and six counts. A position addresses the latter.
51 52 53 |
# File 'lib/head_music/time/musical_position.rb', line 51 def count @count end |
#subtick ⇒ Integer (readonly)
Returns the subtick within the tick (0-indexed).
57 58 59 |
# File 'lib/head_music/time/musical_position.rb', line 57 def subtick @subtick end |
#tick ⇒ Integer (readonly)
Returns the tick within the beat (0-indexed).
54 55 56 |
# File 'lib/head_music/time/musical_position.rb', line 54 def tick @tick end |
Class Method Details
.parse(identifier) ⇒ MusicalPosition
Parse a position from a string representation
77 78 79 |
# File 'lib/head_music/time/musical_position.rb', line 77 def self.parse(identifier) new(*identifier.scan(/\d+/)[0..3]) end |
Instance Method Details
#<=>(other) ⇒ Integer
Compare this position to another
Positions compare lexically on [bar, count, tick, subtick]. Elapsed time is deliberately not consulted: a position is a coordinate, and converting it to a duration requires the meter of every intervening bar, which only a meter map knows. Ordering two normalized positions needs no such knowledge.
147 148 149 |
# File 'lib/head_music/time/musical_position.rb', line 147 def <=>(other) to_a <=> other.to_a end |
#carry(component, radix, first: 0) ⇒ Integer (private) Originally defined in module RadixCarry
Divide the named component by its radix, store the remainder back, and return the amount to carry into the next-higher component.
A 1-indexed component (a beat, whose values run 1..radix) is shifted into 0-indexed space before the divmod and back afterward, so that the last value in the range stays put instead of carrying.
#eql?(other) ⇒ Boolean
Returns true when other is a position with the same components.
158 159 160 |
# File 'lib/head_music/time/musical_position.rb', line 158 def eql?(other) other.is_a?(self.class) && to_a == other.to_a end |
#hash ⇒ Integer
Returns a hash over the position's components.
152 153 154 |
# File 'lib/head_music/time/musical_position.rb', line 152 def hash to_a.hash end |
#normalize!(meter) ⇒ self
Normalize the position according to a meter, handling overflow
This method modifies the position in place, carrying excess values from lower levels to higher levels (subticks → ticks → beats → bars). Also handles negative values by borrowing from higher levels.
125 126 127 128 129 130 131 132 133 134 135 |
# File 'lib/head_music/time/musical_position.rb', line 125 def normalize!(meter) return self unless meter # Carry overflow (and borrow underflow) up through each level. # divmod handles both in-range and out-of-range values uniformly. @tick += carry(:subtick, HeadMusic::Time::SUBTICKS_PER_TICK) @count += carry(:tick, meter.ticks_per_count) @bar += carry(:count, meter., first: FIRST_COUNT) self end |
#to_a ⇒ Array<Integer>
Convert position to array format
102 103 104 |
# File 'lib/head_music/time/musical_position.rb', line 102 def to_a [, count, tick, subtick] end |
#to_s ⇒ String
Convert position to string format
109 110 111 |
# File 'lib/head_music/time/musical_position.rb', line 109 def to_s "#{}:#{count}:#{tick}:#{subtick}" end |