Class: Musa::Datasets::Score Private
- Defined in:
- lib/musa-dsl/datasets/score.rb,
lib/musa-dsl/datasets/score/render.rb,
lib/musa-dsl/datasets/score/queriable.rb,
lib/musa-dsl/datasets/score/to-mxml/to-mxml.rb,
lib/musa-dsl/datasets/score/to-mxml/process-ps.rb,
lib/musa-dsl/datasets/score/to-mxml/process-pdv.rb,
lib/musa-dsl/datasets/score/to-mxml/process-time.rb
Overview
This class is part of a private API. You should avoid using this class if possible, as it may be removed or be changed in the future.
Time-indexed container for musical events.
Score organizes musical events along a timeline, storing them at specific
time points and providing efficient queries for time intervals.
Implements Enumerable for iteration over time slots.
Purpose
Score provides:
- Time-indexed storage: Events organized by start time (Rational)
- Interval queries: Find events in time ranges (#between, #changes_between)
- Duration tracking: Automatically tracks event durations
- Export formats: MusicXML export via ToMXML
- Rendering: MIDI rendering via Render
- Filtering: Create subsets via #subset
Structure
Internally maintains two structures:
- @score: Hash mapping time → Array of events
- @indexer: Array of { start, finish, dataset } for interval queries
Event Requirements
Events must:
Time Representation
All times are stored as Rational numbers for exact arithmetic:
score.at(0r, add: event) # At time 0
score.at(1/4r, add: event) # a quarter of a bar in
Defined Under Namespace
Modules: Queriable, Render, ToMXML
Constant Summary collapse
- NaturalKeys =
NaturalKeys.freeze
Instance Method Summary collapse
-
#at(time, add: nil) ⇒ Array<Abs>?
Adds event at time or gets time slot.
-
#between(closed_interval_start, open_interval_finish) ⇒ Array<Hash>
Queries events overlapping time interval.
-
#changes_between(closed_interval_start, open_interval_finish) ⇒ Array<Hash>
Queries start/finish change events in interval.
-
#duration ⇒ Rational
Returns total duration of score.
-
#each {|time, events| ... } ⇒ void
Iterates over time slots in order.
-
#finish ⇒ Rational?
Returns latest finish time of all events.
-
#forward_duration ⇒ Numeric
included
from AbsD
Returns forward duration (time until next event).
-
#get(key) ⇒ Object?
(also: #[])
private
Gets attribute value.
-
#initialize(hash = nil) ⇒ Score
constructor
Creates new score.
-
#inspect ⇒ String
private
Returns formatted string representation.
-
#note_duration ⇒ Numeric
included
from AbsD
Returns actual note duration.
-
#positions ⇒ Array<Rational>
Returns all time positions sorted.
-
#render(on:) {|event| ... } ⇒ nil
included
from Render
Renders score on sequencer.
-
#reset ⇒ void
Clears all events from score.
-
#size ⇒ Integer
Returns number of time positions.
-
#subset {|dataset| ... } ⇒ Score
Creates filtered subset of score.
-
#to_h ⇒ Hash{Rational => Array<Abs>}
Converts to hash representation.
-
#to_mxml(beats_per_bar, ticks_per_beat, beat_type: nil, bpm: nil, title: nil, creators: nil, encoding_date: nil, parts:, logger: nil, do_log: nil) ⇒ Musa::MusicXML::Builder::ScorePartwise
included
from ToMXML
Converts score to MusicXML.
-
#valid? ⇒ Boolean
included
from E
Checks if event is valid.
-
#validate! ⇒ void
included
from E
Validates event, raising if invalid.
-
#values_of(attribute) ⇒ Set
Collects all values for an attribute.
Constructor Details
#initialize(hash = nil) ⇒ Score
Creates new score.
122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 |
# File 'lib/musa-dsl/datasets/score.rb', line 122 def initialize(hash = nil) raise ArgumentError, "'hash' parameter should be a Hash with time and events information" unless hash.nil? || hash.is_a?(Hash) @score = {} @indexer = [] if hash hash.sort.each do |k, v| raise ArgumentError, "'hash' values for time #{k} should be an Array of events" unless v.is_a?(Array) v.each do |vv| at(k, add: vv) end end end end |
Instance Method Details
#at(time, add: nil) ⇒ Array<Abs>?
Adds event at time or gets time slot.
Without add parameter, returns array of events at that time. With add parameter, adds event to that time slot.
215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 |
# File 'lib/musa-dsl/datasets/score.rb', line 215 def at(time, add: nil) time = time.rationalize if add raise ArgumentError, "#{add} is not a Abs dataset" unless add&.is_a?(Musa::Datasets::Abs) slot = @score[time] ||= [].extend(QueryableByTimeSlot) slot << add @indexer << { start: time, finish: time + add.duration.rationalize, dataset: add } nil else @score[time] ||= [].extend(QueryableByTimeSlot) end end |
#between(closed_interval_start, open_interval_finish) ⇒ Array<Hash>
Queries events overlapping time interval.
Returns events that are active (playing) during the interval [start, finish). Interval uses closed start (included) and open finish (excluded).
Events are included if they:
- Start before interval finish AND finish after interval start
- OR are instant events (start == finish) at interval instant
338 339 340 341 342 343 344 345 346 347 348 349 350 |
# File 'lib/musa-dsl/datasets/score.rb', line 338 def between(closed_interval_start, open_interval_finish) @indexer .select { |i| i[:start] < open_interval_finish && i[:finish] > closed_interval_start || closed_interval_start == open_interval_finish && i[:start] == i[:finish] && i[:start] == closed_interval_start } .sort_by { |i| i[:start] } .collect { |i| { start: i[:start], finish: i[:finish], start_in_interval: i[:start] > closed_interval_start ? i[:start] : closed_interval_start, finish_in_interval: i[:finish] < open_interval_finish ? i[:finish] : open_interval_finish, dataset: i[:dataset] } }.extend(QueryableByDataset) end |
#changes_between(closed_interval_start, open_interval_finish) ⇒ Array<Hash>
Queries start/finish change events in interval.
Returns timeline of note-on/note-off style events for the interval. Useful for real-time rendering or event-based processing.
Returns events sorted by time, with :finish events before :start events at the same time (to avoid gaps).
395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 |
# File 'lib/musa-dsl/datasets/score.rb', line 395 def changes_between(closed_interval_start, open_interval_finish) ( # # we have a start event if the element # begins between queried interval start (included) and interval finish (excluded) # @indexer .select { |i| i[:start] >= closed_interval_start && i[:start] < open_interval_finish } .collect { |i| i.clone.merge({ change: :start, time: i[:start] }) } + # # we have a finish event if the element interval finishes # between queried interval start (excluded) and queried interval finish (included) or # element interval finishes exactly on queried interval start # but the element interval started before queried interval start # (the element is not an instant) # @indexer .select { |i| ( i[:finish] > closed_interval_start || i[:finish] == closed_interval_start && i[:finish] == i[:start]) && ( i[:finish] < open_interval_finish || i[:finish] == open_interval_finish && i[:start] < open_interval_finish) } .collect { |i| i.clone.merge({ change: :finish, time: i[:finish] }) } + # # when the queried interval has no duration (it's an instant) we have a start and a finish event # if the element also is an instant exactly coincident with the queried interval # @indexer .select { |i| ( closed_interval_start == open_interval_finish && i[:start] == closed_interval_start && i[:finish] == open_interval_finish) } .collect { |i| [i.clone.merge({ change: :start, time: i[:start] }), i.clone.merge({ change: :finish, time: i[:finish] })] } .flatten(1) ) .sort_by { |i| [ i[:time], i[:start] < i[:finish] && i[:change] == :finish ? 0 : 1] } .collect { |i| { change: i[:change], time: i[:time], start: i[:start], finish: i[:finish], start_in_interval: i[:start] > closed_interval_start ? i[:start] : closed_interval_start, finish_in_interval: i[:finish] < open_interval_finish ? i[:finish] : open_interval_finish, time_in_interval: if i[:time] < closed_interval_start closed_interval_start elsif i[:time] > open_interval_finish open_interval_finish else i[:time] end, dataset: i[:dataset] } }.extend(QueryableByDataset) end |
#duration ⇒ Rational
Returns total duration of score.
Calculated as finish time minus 1.
187 188 189 |
# File 'lib/musa-dsl/datasets/score.rb', line 187 def duration (finish || 1r) - 1r end |
#each {|time, events| ... } ⇒ void
This method returns an undefined value.
Iterates over time slots in order.
Yields [time, events] pairs sorted by time.
Implements Enumerable.
285 286 287 |
# File 'lib/musa-dsl/datasets/score.rb', line 285 def each(&block) @score.sort.each(&block) end |
#finish ⇒ Rational?
Returns latest finish time of all events.
174 175 176 |
# File 'lib/musa-dsl/datasets/score.rb', line 174 def finish @indexer.collect { |i| i[:finish] }.max end |
#forward_duration ⇒ Numeric Originally defined in module AbsD
Returns forward duration (time until next event).
Defaults to :duration if :forward_duration not specified. This is the
value play waits on in :wait mode, so it is what advances a serie.
#get(key) ⇒ Object? Also known as: []
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.
Gets attribute value.
Supports accessing natural keys like :duration, :finish.
159 160 161 162 163 |
# File 'lib/musa-dsl/datasets/score.rb', line 159 def get(key) if NaturalKeys.include?(key) && self.respond_to?(key) self.send(key) end end |
#inspect ⇒ String
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.
Returns formatted string representation.
Produces multiline representation suitable for inspection.
536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 |
# File 'lib/musa-dsl/datasets/score.rb', line 536 def inspect s = StringIO.new first_level1 = true s.write "Musa::Datasets::Score.new({\n" @score.each do |k, v| s.write "#{ ", \n" unless first_level1 } #{ k.inspect } => [\n" first_level1 = false first_level2 = true v.each do |vv| s.write "#{ ", \n" unless first_level2 }\t#{ vv }" first_level2 = false end s.write " ]" end s.write "\n})" s.string end |
#note_duration ⇒ Numeric Originally defined in module AbsD
Returns actual note duration.
Defaults to :duration if :note_duration not specified.
#positions ⇒ Array<Rational>
Returns all time positions sorted.
259 260 261 |
# File 'lib/musa-dsl/datasets/score.rb', line 259 def positions @score.keys.sort end |
#render(on:) {|event| ... } ⇒ nil Originally defined in module Render
Renders score on sequencer.
Schedules all events in the score on the sequencer, calling the block for each event at its scheduled time. Score times are converted to sequencer wait times (score_time - 1).
Supports nested scores recursively.
#reset ⇒ void
This method returns an undefined value.
Clears all events from score.
146 147 148 149 |
# File 'lib/musa-dsl/datasets/score.rb', line 146 def reset @score.clear @indexer.clear end |
#size ⇒ Integer
Returns number of time positions.
246 247 248 |
# File 'lib/musa-dsl/datasets/score.rb', line 246 def size @score.keys.size end |
#subset {|dataset| ... } ⇒ Score
Creates filtered subset of score.
Returns new Score containing only events matching the condition.
515 516 517 518 519 520 521 522 523 524 525 526 527 |
# File 'lib/musa-dsl/datasets/score.rb', line 515 def subset raise ArgumentError, "subset needs a block with the inclusion condition on the dataset" unless block_given? filtered_score = Score.new @score.each_pair do |time, datasets| datasets.each do |dataset| filtered_score.at time, add: dataset if yield(dataset) end end filtered_score end |
#to_h ⇒ Hash{Rational => Array<Abs>}
Converts to hash representation.
296 297 298 |
# File 'lib/musa-dsl/datasets/score.rb', line 296 def to_h @score.sort.to_h end |
#to_mxml(beats_per_bar, ticks_per_beat, beat_type: nil, bpm: nil, title: nil, creators: nil, encoding_date: nil, parts:, logger: nil, do_log: nil) ⇒ Musa::MusicXML::Builder::ScorePartwise Originally defined in module ToMXML
Converts score to MusicXML.
Creates complete MusicXML document with metadata, parts, measures, notes, rests, and dynamics markings.
#valid? ⇒ Boolean Originally defined in module E
Checks if event is valid.
Base implementation always returns true. Subclasses should override to implement specific validation logic.
#validate! ⇒ void Originally defined in module E
This method returns an undefined value.
Validates event, raising if invalid.
#values_of(attribute) ⇒ Set
Collects all values for an attribute.
Returns set of all unique values across all events.
469 470 471 472 473 474 475 |
# File 'lib/musa-dsl/datasets/score.rb', line 469 def values_of(attribute) values = Set[] @score.each_value do |slot| slot.each { |dataset| values << dataset[attribute] } end values end |