Class: HeadMusic::Content::Position
- Inherits:
-
Object
- Object
- HeadMusic::Content::Position
- Includes:
- Comparable
- Defined in:
- lib/head_music/content/position.rb
Overview
A position is a moment in time within the rhythmic framework of a flow.
The coordinate itself is a HeadMusic::Time::MusicalPosition; this class is the binding to a flow that makes meter lookup possible. Time stays pure and flow-unaware, and there is one set of rollover rules rather than two.
A position is immutable: it is normalized once at construction and then frozen, along with the value inside it. Positions are sort keys -- Voice#place binary-searches over them -- and a mutable sort key is a wrong-note bug that raises nothing.
Instance Attribute Summary collapse
-
#flow ⇒ Object
readonly
Returns the value of attribute flow.
-
#value ⇒ Object
readonly
private
Returns the value of attribute value.
Class Method Summary collapse
-
.value_for(code_or_bar, count, tick, subtick) ⇒ HeadMusic::Time::MusicalPosition
The coordinate, unbound.
Instance Method Summary collapse
- #+(other) ⇒ Object
-
#<=>(other) ⇒ Object
The flow deliberately takes no part in comparison.
- #bar_number ⇒ Object
-
#carried_counts ⇒ Object
private
The count the subticks and ticks carry up to, which may be more than a bar holds.
-
#code ⇒ Object
Subticks are emitted only when there are any, so the everyday "bar:count:tick" form is unchanged and stays parseable.
- #eql?(other) ⇒ Boolean
- #hash ⇒ Object
-
#initialize(flow, code_or_bar, count = nil, tick = nil, subtick = nil) ⇒ Position
constructor
A new instance of Position.
- #meter ⇒ Object
-
#normalize ⇒ Object
private
Normalizing a flow-bound position is not one carry but two jobs, because the bars it crosses need not share a meter.
- #start_of_next_bar ⇒ Object
- #strength ⇒ Object
- #strong? ⇒ Boolean
- #weak? ⇒ Boolean
- #within_placement?(placement) ⇒ Boolean
Constructor Details
#initialize(flow, code_or_bar, count = nil, tick = nil, subtick = nil) ⇒ Position
Returns a new instance of Position.
22 23 24 25 26 27 28 |
# File 'lib/head_music/content/position.rb', line 22 def initialize(flow, , count = nil, tick = nil, subtick = nil) @flow = flow @value = self.class.value_for(, count, tick, subtick) normalize @value.freeze freeze end |
Instance Attribute Details
#flow ⇒ Object (readonly)
Returns the value of attribute flow.
17 18 19 |
# File 'lib/head_music/content/position.rb', line 17 def flow @flow end |
#value ⇒ Object (readonly, private)
Returns the value of attribute value.
97 98 99 |
# File 'lib/head_music/content/position.rb', line 97 def value @value end |
Class Method Details
.value_for(code_or_bar, count, tick, subtick) ⇒ HeadMusic::Time::MusicalPosition
Returns the coordinate, unbound.
31 32 33 34 35 36 37 |
# File 'lib/head_music/content/position.rb', line 31 def self.value_for(, count, tick, subtick) if .is_a?(String) && =~ /\D/ HeadMusic::Time::MusicalPosition.parse() else HeadMusic::Time::MusicalPosition.new(.to_i, (count || 1).to_i, (tick || 0).to_i, (subtick || 0).to_i) end end |
Instance Method Details
#+(other) ⇒ Object
86 87 88 89 |
# File 'lib/head_music/content/position.rb', line 86 def +(other) other = HeadMusic::Rudiment::RhythmicValue.new(other) if [HeadMusic::Rudiment::RhythmicUnit, Symbol, String].include?(other.class) self.class.new(flow, , count, tick + other.ticks, subtick) end |
#<=>(other) ⇒ Object
The flow deliberately takes no part in comparison. Positions in different flows have always compared equal here, and Voice#placement_at guards with exactly that comparison -- narrowing it would silently change note lookup.
61 62 63 64 |
# File 'lib/head_music/content/position.rb', line 61 def <=>(other) other = self.class.new(flow, other) if other.is_a?(String) && other =~ /\D/ to_a <=> other.to_a end |
#bar_number ⇒ Object
39 40 41 |
# File 'lib/head_music/content/position.rb', line 39 def value. end |
#carried_counts ⇒ Object (private)
The count the subticks and ticks carry up to, which may be more than a bar holds. Any bars normalize! carried into are folded back into the count, so that the walk above -- not the divmod -- decides which bar this lands in.
130 131 132 133 |
# File 'lib/head_music/content/position.rb', line 130 def carried_counts carried = HeadMusic::Time::MusicalPosition.new(1, value.count, value.tick, value.subtick).normalize!(meter) [(carried. - 1) * meter. + carried.count, carried.tick, carried.subtick] end |
#code ⇒ Object
Subticks are emitted only when there are any, so the everyday "bar:count:tick" form is unchanged and stays parseable.
49 50 51 52 |
# File 'lib/head_music/content/position.rb', line 49 def code base = [, count, tick.to_s.rjust(3, "0")].join(":") subtick.zero? ? base : "#{base}:#{subtick.to_s.rjust(3, "0")}" end |
#eql?(other) ⇒ Boolean
66 67 68 |
# File 'lib/head_music/content/position.rb', line 66 def eql?(other) other.is_a?(self.class) && to_a == other.to_a end |
#hash ⇒ Object
70 71 72 |
# File 'lib/head_music/content/position.rb', line 70 def hash to_a.hash end |
#meter ⇒ Object
43 44 45 |
# File 'lib/head_music/content/position.rb', line 43 def meter flow.meter_at() end |
#normalize ⇒ Object (private)
Normalizing a flow-bound position is not one carry but two jobs, because the bars it crosses need not share a meter.
MusicalPosition#normalize! does the radix carry -- subticks into ticks, ticks into counts -- under a single meter, which is right, because those ratios are read from the bar the position starts in. But its counts-into- bars step is a single divmod, and that silently assumes every bar it crosses has the same number of counts. So the bar carry is walked here one bar at a time, asking the flow for each bar's meter as it goes.
And the radix carry was done under the origin bar's meter, so a position that lands in a bar with a different count unit is carried again under that bar's, until it lands in a bar it was carried under. One instant then has one spelling, which is what equality and hashing depend on.
113 114 115 116 117 118 119 120 121 122 123 124 125 |
# File 'lib/head_music/content/position.rb', line 113 def normalize loop do = value. counts, tick, subtick = carried_counts = while counts > flow.meter_at(). counts -= flow.meter_at(). += 1 end @value = HeadMusic::Time::MusicalPosition.new(, counts, tick, subtick) break if == end end |
#start_of_next_bar ⇒ Object
91 92 93 |
# File 'lib/head_music/content/position.rb', line 91 def self.class.new(flow, + 1, 1, 0) end |
#strength ⇒ Object
74 75 76 |
# File 'lib/head_music/content/position.rb', line 74 def strength meter.beat_strength(count, tick: tick) end |
#strong? ⇒ Boolean
78 79 80 |
# File 'lib/head_music/content/position.rb', line 78 def strong? strength >= 80 end |
#weak? ⇒ Boolean
82 83 84 |
# File 'lib/head_music/content/position.rb', line 82 def weak? !strong? end |
#within_placement?(placement) ⇒ Boolean
54 55 56 |
# File 'lib/head_music/content/position.rb', line 54 def within_placement?(placement) placement.position <= self && placement.next_position > self end |