ï~~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
Top of page Top of page