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 acontrolEventwhose@staffis what anchors it;@layeris 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
- 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.
- A later mark simply replaces the earlier one (the model has no nesting).
- 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
onsetwhole notes into measuremeasureIndex, 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.