OctaveSpanTimeline class

Resolves WHICH OctaveMark is in force at a given point of MUSICAL TIME on a staff, for every voice at once.

Why this exists: the octave bracket is a property of the STAFF

Wave 1 gave OctaveMark its first reader by walking the element stream in document order with an OctaveSpanTracker. That is exact for a monophonic staff and WRONG for a polyphonic one, because MultiVoiceMeasure serialises voice 1 entirely before voice 2. Measured on a two-voice bar whose voices both hold a C5 (staff position 1 under a treble clef):

mark written in voice 1 shift voice 2 shift
voice 1 +1 +1
voice 2 0 +1

The same musical marking produced two different engravings depending only on which voice the author happened to type it in.

The semantics chosen here is (b): the bracket belongs to the STAFF. A mark written in any voice displaces every voice of that staff, from the musical instant it starts. That is what both interchange formats say:

  • MusicXML. <octave-shift> is a <direction-type>, and a <direction> is placed on a STAFF (<staff>); its optional <voice> child exists to say which voice the direction is typeset with — the spec's own wording is that it is used "for cases where the direction applies to a specific voice", and every mainstream exporter (Finale, Sibelius, MuseScore) emits an 8va without one. A reader that scoped the bracket to a voice would move the notes of one hand and leave the other behind on the same staff.
  • MEI. <octave> is a controlEvent whose @staff is what anchors it; @layer is optional and, when absent, the event applies to ALL layers of that staff (MEI Guidelines, "Control events"). Our OctaveMark carries no voice/layer field at all, so it cannot express the narrowed form even in principle — it is unambiguously the staff-wide case.

Scoping it to a voice (option (a)) was rejected for a third reason: it would make a bracket un-writable for the common case. A pianist's 8va over a two-voice right hand is one bracket over both voices; under (a) the author would have to duplicate the mark into every voice, and forgetting one would print half the texture an octave away from the other half.

The rule

  1. A mark takes effect at the ONSET where it appears in its own voice and stays in force for every voice from that instant on. Notes sounding strictly earlier in the bar — in any voice — keep the previous displacement, which preserves the mid-measure behaviour OctaveSpanTracker documents for a monophonic staff.
  2. A later mark simply replaces the earlier one (the model has no nesting).
  3. A span ends after OctaveMark.endMeasure, or at the end of the measure it was written in when endMeasure <= startMeasure — the degenerate span both importers produce. See OctaveSpanTracker for why.

The timeline is immutable and can be queried in any order, which is what lets a polyphonic bar be laid out voice by voice and still agree with itself.

Constructors

OctaveSpanTimeline(Iterable<OctaveSpanEvent> events)
Builds a timeline from events, which may arrive in any order.
factory

Properties

hashCode int
The hash code for this object.
no setterinherited
isEmpty bool
True when no OctaveMark exists anywhere on the staff.
no setter
length int
Number of activations on the timeline (diagnostics and tests).
no setter
runtimeType Type
A representation of the runtime type of the object.
no setterinherited

Methods

markAt({required int measureIndex, required double onset}) OctaveMark?
The mark in force at onset whole notes into measure measureIndex, or null outside every span.
noSuchMethod(Invocation invocation) → dynamic
Invoked when a nonexistent method or property is accessed.
inherited
shiftAt({required int measureIndex, required double onset}) int
Displacement in octaves in force at that point (0 outside every span).
toString() String
A string representation of this object.
inherited

Operators

operator ==(Object other) bool
The equality operator.
inherited

Constants

empty → const OctaveSpanTimeline
Empty timeline: shiftAt is always 0.