Class: HeadMusic::Time::EventMap
- Inherits:
-
Object
- Object
- HeadMusic::Time::EventMap
- Defined in:
- lib/head_music/time/event_map.rb
Overview
An ordered list of (position, value) events along a timeline, answering what value is in force at any position.
Everything that changes partway through a flow is this shape -- meter, tempo, key signature, instrument, staff system, clef, staff assignment -- so the mechanism is written once and composed into each typed map rather than inherited by it. The typed maps differ in their first-event policy: a meter map's first event cannot be removed, a staff-assignment map has no stored first event at all and answers a default. A constructor argument expresses that; a superclass would need a hook per policy.
Values are opaque. This class never interprets one, which is why the same class serves a Meter and a Clef.
Defined Under Namespace
Classes: Event
Instance Attribute Summary collapse
-
#default ⇒ Object?
readonly
The value in force before the first event.
-
#events ⇒ Array<Event>
readonly
The events, earliest first.
Class Method Summary collapse
-
.tuple_for(position) ⇒ Array<Integer>
The sort key for a position.
Instance Method Summary collapse
-
#add(position, value) ⇒ Event
Add or replace the event at a position.
-
#at(position) ⇒ Object?
The value in force at a position -- the most recent event at or before it, or the default when there is none.
-
#change_at(position) ⇒ Event?
The event starting at a position, as distinct from the one in force there.
-
#clear ⇒ self
Remove every event, except the first when the map protects it.
-
#each_segment(from_position, to_position) {|start_position, end_position, value| ... } ⇒ Object
Yield each [start_position, end_position, value] span between two positions.
-
#empty? ⇒ Boolean
True when no event has been added.
-
#event_at(position) ⇒ Event?
The most recent event at or before a position.
-
#initialize(default: nil, removable_first_event: true) ⇒ EventMap
constructor
A new instance of EventMap.
- #insertion_index(tuple) ⇒ Object private
-
#remove(position) ⇒ Event?
Remove the event at a position, if there is one.
-
#values ⇒ Array<Object>
The values, earliest first.
Constructor Details
#initialize(default: nil, removable_first_event: true) ⇒ EventMap
Returns a new instance of EventMap.
48 49 50 51 52 |
# File 'lib/head_music/time/event_map.rb', line 48 def initialize(default: nil, removable_first_event: true) @default = default @removable_first_event = removable_first_event @events = [] end |
Instance Attribute Details
#default ⇒ Object? (readonly)
Returns the value in force before the first event.
43 44 45 |
# File 'lib/head_music/time/event_map.rb', line 43 def default @default end |
#events ⇒ Array<Event> (readonly)
Returns the events, earliest first.
55 56 57 |
# File 'lib/head_music/time/event_map.rb', line 55 def events @events end |
Class Method Details
.tuple_for(position) ⇒ Array<Integer>
The sort key for a position. Lexical on the four components, which is the ordering MusicalPosition itself uses.
161 162 163 |
# File 'lib/head_music/time/event_map.rb', line 161 def self.tuple_for(position) [position., position.count, position.tick, position.subtick] end |
Instance Method Details
#add(position, value) ⇒ Event
Add or replace the event at a position.
70 71 72 73 74 75 76 77 78 79 80 |
# File 'lib/head_music/time/event_map.rb', line 70 def add(position, value) tuple = self.class.tuple_for(position) index = events.index { |event| event.at?(tuple) } event = Event.new(position, value, tuple) if index events[index] = event else events.insert(insertion_index(tuple), event) end event end |
#at(position) ⇒ Object?
The value in force at a position -- the most recent event at or before it, or the default when there is none.
A binary search rather than a reverse scan: every rendered note takes several map lookups, and position construction itself asks for a meter in a rollover loop.
110 111 112 |
# File 'lib/head_music/time/event_map.rb', line 110 def at(position) event_at(position)&.value || default end |
#change_at(position) ⇒ Event?
The event starting at a position, as distinct from the one in force there. A writer needs this to decide whether to print a change.
118 119 120 121 |
# File 'lib/head_music/time/event_map.rb', line 118 def change_at(position) tuple = self.class.tuple_for(position) events.find { |event| event.at?(tuple) } end |
#clear ⇒ self
Remove every event, except the first when the map protects it.
97 98 99 100 |
# File 'lib/head_music/time/event_map.rb', line 97 def clear @events = @removable_first_event ? [] : events.first(1) self end |
#each_segment(from_position, to_position) {|start_position, end_position, value| ... } ⇒ Object
Yield each [start_position, end_position, value] span between two positions. Positions are yielded as given -- no normalization -- so a caller that needs normalized bounds normalizes them first.
138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 |
# File 'lib/head_music/time/event_map.rb', line 138 def each_segment(from_position, to_position) from_tuple = self.class.tuple_for(from_position) to_tuple = self.class.tuple_for(to_position) current_position = from_position current_value = at(from_position) events.each do |event| next unless event.after?(from_tuple) break unless (event.tuple <=> to_tuple) < 0 yield current_position, event.position, current_value current_position = event.position current_value = event.value end yield current_position, to_position, current_value end |
#empty? ⇒ Boolean
Returns true when no event has been added.
63 64 65 |
# File 'lib/head_music/time/event_map.rb', line 63 def empty? events.empty? end |
#event_at(position) ⇒ Event?
The most recent event at or before a position.
126 127 128 129 130 131 |
# File 'lib/head_music/time/event_map.rb', line 126 def event_at(position) tuple = self.class.tuple_for(position) index = events.bsearch_index { |event| event.after?(tuple) } index = index ? index - 1 : events.length - 1 events[index] if index >= 0 end |
#insertion_index(tuple) ⇒ Object (private)
167 168 169 |
# File 'lib/head_music/time/event_map.rb', line 167 def insertion_index(tuple) events.bsearch_index { |event| event.after?(tuple) } || events.length end |
#remove(position) ⇒ Event?
Remove the event at a position, if there is one.
85 86 87 88 89 90 91 92 |
# File 'lib/head_music/time/event_map.rb', line 85 def remove(position) tuple = self.class.tuple_for(position) index = events.index { |event| event.at?(tuple) } return if index.nil? return if index.zero? && !@removable_first_event events.delete_at(index) end |
#values ⇒ Array<Object>
Returns the values, earliest first.
58 59 60 |
# File 'lib/head_music/time/event_map.rb', line 58 def values events.map(&:value) end |