ï~~Proceedings of the International Computer Music Conference 2011, University of Huddersfield, UK, 31 July - 5 August 2011
such approaches require searching, from any member,
backward or forward to find the associated objects.
Music21 borrows the term "spanner" from Lilypond
[9] and, in part, from the "Leaf Container Spanner"
model employed in Abjad [1]. Spanners are Music21Objects that store a collection of spanned objects,
called components, that can exist in any hierarchical or
non-hierarchical arrangment.
Internally, the Spanner stores components in a specialized Stream subclass called SpannerStorage. This
object, unlike other Streams, stores a reference to the
Spanner within which it is instantiated. The Spanner
object provides the interface to SpannerStorage, as typical Stream functionality may not always have the same
meaning when applied to storing Spanner components.
For example, the offsets of components in a SpannerStorage object are irrelevant. However, by storing components in a Stream subclass, elements can directly list
all Spanners in which they are components. The Music2lObject method getSpannerSites () returns all
Spanners for which the object is a component, by simply
looking at the DefinedContexts object for SpannerStorage locations, and returning the reference to the Spanner
instance stored in the found SpannerStorage object.
The Python example in Figure 10 demonstrates basic
functionality of Slurs, a Spanner subclass. The getOffsetSpanBySite() and getDurationSpanBySite() methods can be used to determine the relative offset and
duration span of stored components. These methods
require a site argument, as a Spanner might connect
Music21Objects in different sites. A common site, usually a flat representation, must exist to find offset and
duration spans.
# Spanners positioned in a Part and a Measure
spl = spanner.Slur([nl, n4])
pl. append (spl)
sp2 = spanner.Slur([n5, n6])
m4.insert(0, sp2)
# Elements can get their Spanners
assert nl.getSpannerSites() == [spl]
assert n6.getSpannerSites() == [sp2]
plFlat = pl.flat
assert spl.getDurationSpanBySite (plFlat) ==
[0.0, 8.0]
p2Flat = p2.flat
assert sp2.getDurationSpanBySite (p2Flat) ==
[4.0, 8.0]
s1. show ()
4. CASE STUDY
The following demonstration (Figure 11) illustrates the
music21 Stream model's utility in a more comprehensive example. This example is a typical application of
music21 in the following ways: (1) the code is compact
and highly readable; (2) non-sounding notations can be
examined with the same ease as sounding events; (3) a
general approach that works for one work in one encoding can easily be employed on thousands of works in
many encodings; (4) numerical output, textual output,
and annotated score-based representations can all be
created simultaneously. No other system for symbolic
music manipulation offers all these features in such an
easy-to-use framework.
The example below analyzes the beat location and
the initial pitches of all melismas, that is syllables
spread over multiple notes, in a score, and marks them
with slurs. The example first parses a MusicXML representation of a fifteenth-century Gloria by D. Luca,
stored in the integrated music21 corpus (a collection of
works distributed with music21 for immediate experimentation and research). Using a flat representation of
an extracted sub-section of Measures, the code finds all
melismas by looking at pairs of Notes and determining
the spans of Notes after a lyric ends and before a new
Note starts. The code then spans each of them with a
Slur. These slurs are then collected and are used to find
the starting pitch and starting beat of the melisma (each
one in this example happens on the downbeat), as well
as the total duration of each melisma. The starting pitch
and total duration are then printed.
Streams are critical for this procedure because: (1) a
single Part, and a sub-section of Measures within that
part, can be extracted while retaining musical and
notational coherency (appropriate clefs, meters, etc.);
(2) flat and filtered representations of the same Notes
can be iterated over in series to examine pairwise relationships without altering the hierarchical representation; (3) elements stored in alternate representations can
be modified, producing changes that are retained in the
source representation; (4) the list-like functionality of
Streams gives easy access to boundary elements.
nStart = None; nEnd = None
ex = corpus.parseWork(
'luca/gloria').parts['cantus'].measures (
1,11)
exFlatNotes = ex.flat.notes
nLast = exFlatNotes [-1]
for i, n in enumerate (exFlatNotes):
if i < len(exFlatNotes) - 1:
nNext = exFlatNotes[i+1]
else: continue
if n.hasLyrics ():
nStart = n
# if next is a begin, then this is an end
elif (nStart is not None and
nNext.hasLyrics () and n.tie is None):
nEnd = n
elif nNext is nLast:
nEnd = n..............:................................................:...........................................
Figure 10. Creating a Slurs across Measure boundaries
and realizing their duration span.
67