Quick search

This Page

Music21 contains a powerful context and hierarchy system that lets
users find analytical information in a single place that actually
requires looking at many pieces of information scattered throughout a
score. Take for instance, a fragment of code that we use a lot such as:

>>> n.beat4

It’s great when it works, but then there are times when it doesn’t and
it’s just frustrating. Avoiding those times is what this chapter is
about. And to do so, we’ll need to start asking some “how” questions.

How does a note know what beat it is on? It might help to think about
when we read a printed score, how do we know what beat a note is on? We
have to look at the note, then look up the score to find the most recent
time signature, then find the note again and look at the measure it is
in, count everything preceeding it in the measure, and then calculate
the beat. At least three different musical objects need to be consulted:
the note itself, the surrounding measure, and the time signature that
provides an interpretation of how note durations translate to beats.
Music21 needs to do the same search, and it does all that just on a
little call to the .beat property.

(In the early days of ``music21``, I did not know about the convention
that properties should be fast and easily computable so that the user
does not even realize it is something more complex than an attribute
lookup. The property ``.beat`` is none of the above. If I were starting
over, it would be a method, ``.beat()``, but it is too late to change
now.)

To understand how this lookup works, we will need to understand better
how Sites and Contexts work. Advanced users and beginners alike
(and occasionally even the music21 developers) are frequently
confused by music21’s context and hierarchy system. When it works,
it works great, it’s just magic. But, when it doesn’t, it appears to be
a random bug, and it is probably the most common type of bug mention on
the music21 GitHub tracker that gets “not a bug” as a response.
Magic is fickle that way.

Let’s start by looking at a simple example. We will create a measure and
add a single E-flat to it:

At this point, there’s obviously a connection made between the Measure
and the Note.

m[0]

<music21.note.NoteE->

es.activeSite

<music21.stream.Measure1offset=0.0>

es.offset

2.0

m.elementOffset(es)

2.0

But we need to know, what is the nature of this connection? It has
changed several times as music21 has developed but has been stable
since at least v.4, and it looks to stay that way. The measure (or any
Stream) contains an ordered list of the elements in it, and it also
contains a mapping of each element in it to its offset.

The element in a stream (such as a Note) does not however, have a direct
list of what stream or streams it is in. Instead elements have a
property .sites that is itself a rather complex object called
Sites that keeps track of this information for
it:

The Sites object keeps track of how many and which streams an
element has been placed in:

es.sites.getSiteCount()

1

v=stream.Voice()v.append(es)es.sites.getSiteCount()

2

If we need to figure out a particular attribute based on context, the
Sites object comes in very handy. For instance, the
.measureNumber on a Note or other element, finds a container that is
a Measure and has a .number attribute:

es.measureNumber

1

We can do the same sort of thing, by calling the Sites object
directly with the getAttrByName method:

es.sites.getAttrByName('number')

1

Or we can just get a list of sites that match a certain class:

es.sites.getSitesByClass(stream.Voice)

[<music21.stream.Voice0x11677edd8>]

Or with a string:

es.sites.getSitesByClass('Measure')

[<music21.stream.Measure1offset=0.0>]

Notice that what is returned is a list, because an element can appear in
multiple sites, even multiple sites of the same class, so long as those
sites don’t belong to the same hierarchy (that is, those streams are not
both in the same stream or have a common stream somewhere in their
Sites). Let’s put the note in another Measure object:

Note two things: (1) each element has a special site called “None” that
stores information about the element when it is not in any Stream (it
used to be used much more, but is not used as much anymore, and is not
counted in the number of sites an element is in), and (2) the sites are
yielded from earliest added to latest. We can reverse it and eliminate
None, with a few parameters:

Each element has as its index the memory location of the site and a
lightweight wrapper object around the site (i.e., stream) called a
SiteRef.

(all sites except “None” are currently “Stream” objects – is was our
intention at the beginning to have other types of site contexts, such as
interpretative contexts (“baroque”, “meantone tuning”) and we might
still someday add those, but for now, a site is a Stream)

Let’s look at the last one:

lastSiteRef=list(es.sites.siteDict.items())[-1][1]lastSiteRef

<music21.sites.SiteRef2/2to<music21.stream.Measure10offset=0.0>>

lastSiteRef.siteIndex

2

lastSiteRef.siteIndex

2

lastSiteRef.globalSiteIndex

2

This item allows music21 to find sites by class without needing to
unwrap the site

From what has been shown so far, it appears that effectively the
relationship between a stream and its containing element is a mirror or
two-way: streams know what notes they contain and notes know what
streams they are contained in. But it is a bit more complicated and it
comes from the type of reference each holds to each other.

Streams hold elements with a standard or “strong” reference. As long as
the Stream exists in the computer’s memory, all notes contained in it
will also continue to exist. You never need to worry about streams
losing notes.

The .sites object, does not, however, contain strong references to
Streams. Instead it contains what are called “weak references” to
streams. A weak reference allows notes and their Sites object to get
access to the stream, as long as it is still in use somewhere else,
but once the stream is no longer in use it is allowed to disappear
anytime.

As a demonstration, let’s delete that pesky voice:

delv

Now whenever Python determines that it needs some extra space, the Note
object will no longer have reference to the voice in its sites. Note
that, this might not happen immediately. The removal of dead weak
references, part of Python’s garbage collection, takes place at odd
times, dependent on the amount of memory currently used and many other
factors. So one cannot predict whether the Voice object would still
be in the note’s sites or not by the time you finish reading this
paragraph. Music21 uses weak references in a number of other
situations, such as
derivation objects, though we are
reducing the number of places where they are used as the version of
Python supported by music21 gets a smarter and smarter garbage
collector.

You might be thinking that it’s been years since the last time you
called del on a variable, so this doesn’t really apply to you. But
look at this code, which represents pretty typical music21 usage:

forninp.recurse():ifnisfirstNote:print('Yup, there is a D in the part')

Yup,thereisaDinthepart

Did you see where we created an extra stream only to immediately discard
it? The expression p.flat creates a new stream that exists just for
long enough to get the first note from it. (We actually store it in a
cache on p so that it’s faster the next time we need it, but once we
add another note to m the cache is invalidated). The creation of
another stream is one reason to generally prefer .recurse() over
.flat. Prior to music21 v.3, the .notes call would have
created yet another Stream, but we’ve optimized this out.

The reason why music21 uses weak references instead of normal
(strong) references to sites is to save some memory because how
frequently objects, such as notes and streams, are copied. When you run
certain analytical or manipulation routines such as
toSoundingPitch() or stripTies() or even common operations such
as .flat and show(), copies of streams need to be made, often
only to be discarded in a single line of code. If every one of those
streams, with all of their contents, persisted forever just because a
single note from that stream stayed in memory, then the memory usage of
music21 would be much higher.

Also note that the Sites object cleans up “dead” sites from time to
time, and certain context-dependent calls, such as .next(note.Rest)
or .getContextByClass('Measure') need to search every living site.
Over time, these calls would get slower and slower if otherwise
long-forgotten streams created as byproducts of .show() or .flat
stuck around.

Here’s an example adapted from actual code that caused a problem. The
user was trying to figure out the beat for each note that was not the
continuation of a tie, and he wrote:

Problems quickly arose though when he tried to figure out the note’s
beat via firstNote.beat – it said that it was on beat 1, even
though this piece in 4/4 began with a one-beat pickup, and should be on
beat 4.

What happened? Again, it’s a problem with confusions from disappearing
streams and sites. The .stripTies() method creates a new score
hierarchy, where each note in the score hierarchy is a copy derived from
the previous one:

So the Note object obtained from .stripTies() is not to be found in
the original bach score:

bach.containerInHierarchy(firstNoteStripped)isNone

True

bach.containerInHierarchy(firstNoteOriginal)

<music21.stream.Measure0offset=0.0>

in fact, firstNoteStripped’s entire containerHierarchy is generally
empty if garbage collection has run. Why? Because firstNoteStripped only
directly belongs to the hierarchy created by the unnamed and unsaved
stream created by stripTies(). So how to solve this? In code that
needs access to the hierarchy, make sure that it is preserved by saving
it to a variable. Here we will break up the code calling stripTies()
and save it as a variable, st_bach. Not only does this solve our
problems, but it makes the code more readable:

Doing this also fixes another thing that looked like a bug, but is
expected behavior – that getContextByClass('Measure') was needing
to follow the derivation chain to find a measure that firstNote was
not in. Here it works as expected:

firstNote.getContextByClass('Measure').elementOffset(firstNote)

0.0

This was a pretty dense chapter, I know, and it barely scratches the
surface of the complexities of the Context system, so we’ll move to
something lighter if even more distant, and look at Medieval and
Renaissance extensions in music21.