musicalValueOf static method
Calculates the rhythmic value occupied by element.
Returns 0.0 for elements that do not occupy musical time (clefs, key
signatures, barlines, ...). Tuplets are scaled by their
normalNotes / actualNotes ratio.
Grace notes take no time
A Note with isGraceNote == true returns 0.0. A grace note is an
ornament: it is printed small, it is drawn, it occupies horizontal WIDTH,
but it does not advance the musical clock — it is stolen from the
neighbouring note, not added to the bar (Behind Bars p.125; MusicXML
<grace> notes carry no <duration> at all, and MEI @grace likewise
excludes the note from the layer's rhythmic total).
Measured before this rule existed: a 4/4 bar of four quarters plus two
eighth grace notes reported currentMusicalValue = 1.1875 instead of
1.0, and the layout onsets came out
[0, 0.125, 0.1875, 0.4375, 0.6875, 0.9375] while MidiMapper (which
already skipped grace notes) produced a correct 3840 ticks at 960 ppq.
Layout and playback therefore disagreed, and the ADR-002 shared onset
grid — the thing that makes a grand staff line up — broke for any staff
containing a grace note.
Chord and Tuplet used to be resolved through
runtimeType.toString() "to avoid circular imports". Verified: there is
no cycle. chord.dart imports musical_element/note/duration/ornament/
dynamic/bounding_box_support and tuplet.dart imports musical_element/
note/rest/chord/time_signature/tuplet_bracket/tuplet_number — neither
reaches measure.dart, and nothing in their transitive closure does. The
string comparison was also silently wrong for any subclass of Chord or
Tuplet, so both are matched with is now.
Implementation
static double musicalValueOf(MusicalElement element) {
if (element is Note) {
return element.isGraceNote ? 0.0 : element.duration.realValue;
} else if (element is Rest) {
return element.duration.realValue;
} else if (element is Chord) {
return element.duration.realValue;
} else if (element is Tuplet) {
double tupletValue = 0.0;
for (final tupletElement in element.elements) {
tupletValue += musicalValueOf(tupletElement);
}
// Apply the tuplet ratio
if (element.actualNotes > 0) {
tupletValue = tupletValue * (element.normalNotes / element.actualNotes);
}
return tupletValue;
}
return 0.0; // Elements without duration (clef, key signature, etc.)
}