Graphing Utilities

Music21 can create graphs and plots of multiple musical attributes using high-level analysis tools.

Graphs are any sort of graphical display. Plots are displays that run on a Stream and report data found in a Stream.

Users can create their own custom graphing routines by extending or reworking existing music21 Graph and PlotStream classes.

Alternatively, music21 has numerous pre-built Graph and Plot classes for quick access to common graphical views such as piano rolls, etc.

Installing and Using matplotlib

The powerful and flexible graphing routines of music21 use the matplotlib and numpy libraries. They can be downloaded from the following URLs:

For instructions on getting them installed and running see the following URL:

For more information about matplotlib with music21 see Advanced Graphing Topics.

Plotting Streams

Music21 features numerous ways to display the data within a Stream.

Complete documentation for these graphing objects can be found with the following classes: PlotHistogramPitchSpace, PlotHistogramPitchClass, PlotHistogramQuarterLength, PlotScatterPitchSpaceQuarterLength, PlotScatterPitchClassOffset, PlotHorizontalBarPitchClassOffset, PlotHorizontalBarPitchSpaceOffset, PlotScatterWeightedPitchSpaceQuarterLength, PlotScatterWeightedPitchClassQuarterLength, Plot3DBarsPitchSpaceQuarterLength, Plot3DBarsPitchSpaceQuarterLength,

The plotStream() Utility Function

The module offers the plotStream() function for easy access to graphing music21 Streams. This function requires a Stream as an argument and a number of additional arguments to specify the output of one or more plot types.

Calling plotStream() with no arguments creates a default graph, using PlotHorizontalBarPitchSpaceOffset, of a flat version of the Stream:

>>> from music21 import corpus, graph
>>> aStream = corpus.parse('bach/bwv57.8')
>>> graph.plotStream(aStream)    

Calling plotStream() with a Stream and the name of plot class will use that class to create a and display a graph.

>>> graph.plotStream(aStream, 'PlotHistogramPitchClass')  

Alternatively, the type of desired graph can be given as the format keyword argument, and list of values desired can be given with the values keyword argument. If one or more plots are available that match the requested values, these will be displayed

>>> graph.plotStream(aStream, format='scatterweighted') 
>>> graph.plotStream(aStream, format='scatter', values=['pitch'])  

Note that the exact same functionality of plotStream() is available as the Stream plot() method.

>>> aStream.plot(format='scatterweighted', values='pitchclass') 

Creating and Calling Plot Objects

Graphs can be created and/or written to a file by creating an instance of a plot class with the Stream as an argument. Once created, the object’s process() method is called to obtain a result. The result is determined by the doneAction keyword argument.

>>> from music21 import corpus, graph
>>> aStream = corpus.parse('bach/bwv57.8')
>>> aPlot = graph.PlotHistogramPitchClass(aStream)
>>> aPlot.process()  

The default Action is to call show() method the graph, which will write it as a temporary file and open the graph. To write a graph to a file, use the write() with a file path as the argument.

Analytical Graphing Objects

Music21 features graphing objects that display the results of analysis. Complete documentation for these graphing objects can be found with the following classes: PlotWindowedKrumhanslSchmuckler, PlotWindowedKrumhanslKessler, PlotWindowedAardenEssen, PlotWindowedSimpleWeights, PlotWindowedBellmanBudge, PlotWindowedTemperleyKostkaPayne, PlotWindowedAmbitus, PlotDolan

A basic example follows:

>>> from music21 import *
>>> haydn = converter.parse('haydn/symphony94/02') 
>>> plot = graph.PlotDolan(haydn) 
>>> plot.process() 

See a larger version

The ActivityMatch Object

The ActivityMatch object provides a way to examine, for a given parameter, what other parameters are active.

Elementary Graphing Classes

Music21 provides low-level access to basic graphing routines through classes for each graphing archetype.

Complete documentation for these graphing objects can be found with the following classes: GraphHorizontalBar, GraphScatterWeighted, GraphScatter, GraphHistogram, Graph3DPolygonBars.

Two-Dimensional Scatter Plot

A two-dimensional scatter graph can be made from any list of x, y data pairs.

The doneAction keyword argument determines what happens after the process() method is called. A ‘show’ value (the default) will immediately open the output format in platform- and backend-specific viewer. A write value will write the output in backend-specific format using a music21-generated temporary file.

A basic example follows:

>>> from music21 import *
>>> a = graph.GraphScatter(title = 'Chromatic Scale', doneAction='show')
>>> data = []
>>> for midiNumber in range(36,120):
...     n = note.Note()
...     n.midi = midiNumber
...     frequency = n.frequency
...     data.append( (midiNumber, int(frequency) ) )
>>> a.setData(data)
>>> a.process()  

Numerous parameters can be specified through keyword arguments when creating a scatter plot, and also attached to each point.

The ‘alpha’ keyword argument sets transparency, from 0 (transparent) to 1 (opaque).

The ‘title’ keyword argument sets the title of the graph.

The ‘colors’ keyword argument sets the colors of data points, specified as HTML color codes or matplotlib’s single-letter abbreviations.

This example provides basic customization to a scatter graph:

>>> from music21 import *
>>> a = graph.GraphScatter(title = 'Color-coded chromatic scale showing C major', doneAction='show')
>>> data = []
>>> for midiNumber in range(36,120):
...     n = note.Note()
...     n.midi = midiNumber
...     frequency = n.frequency
...     if n.pitchClass in [0, 2, 4, 5, 7, 9, 11]:
...         alpha = 1
...         marker = 'o'
...         color = 'white'
...         markerSize = 10
...     else:
...         alpha = 1
...         marker = 'd'
...         color = 'black'
...         markerSize = 8
...     data.append( (midiNumber, int(frequency), {'color':color, 'alpha': alpha, 'marker': marker, 'markerSize':markerSize} ) )
>>> a.setData(data)
>>> a.setAxisLabel('x', 'midi number')
>>> a.setAxisLabel('y', 'frequency')
>>> a.process()  

Grouped Bar Graph

This graph allows you to plot multiple sets of data in parallel bar graphs. Data for this graph is provided in a list of tuples of the form (dataLabel, {plotGroup: value, plotGroup2: value ... }). The example below iterates through the Bach Chorale corpus using corpus.chorales.Iterator and stores the frequency at which each part exhibits notes of each quarter length present. It displays the normalized frequency of each quarterLength as four bars, each corresponding to an SATB part.

The example follows:

>>> from music21 import *
>>> sopranoDict, altoDict, tenorDict, bassDict, data, noteTotal = {}, {}, {}, {}, [], 0.0
>>> for chorale in corpus.chorales.Iterator():
...     soprano, alto, tenor, bass = chorale.getElementById('Soprano'), chorale.getElementById('Alto'), chorale.getElementById('Tenor'), chorale.getElementById('Bass')
...     for (part, partDict) in [(soprano, sopranoDict), (alto, altoDict), (tenor, tenorDict), (bass, bassDict)]:
...         if part is not None:
...             part = part.flat.notes
...             for n in part:
...                 noteTotal += 1.0
...                 noteLength = n.duration.quarterLength
...                 if noteLength in partDict:
...                     partDict[noteLength] += 1
...                 else:
...                     partDict[noteLength] = 1
>>> quarterLengths = list(set(list(sopranoDict.keys())
...                           + list(altoDict.keys())
...                           + list(tenorDict.keys())
...                           + list(bassDict.keys())))
>>> for ql in quarterLengths:
...     values = []
...     for partDict in [sopranoDict, altoDict, tenorDict, bassDict]:
...         if ql in partDict:
...             values.append(partDict[ql]/noteTotal)
...         else:
...             values.append(0.0)
...     data.append((ql, {'soprano': values[0], 'alto': values[1], 'tenor': values[2], 'bass': values[3]}))
>>> a = graph.GraphGroupedVerticalBar(title="Frequency of note durations in Bach's Chorales",
...                                   doneAction='show',
...                                   binWidth = 1,
...                                   colors = ['#605C7F', '#5c7f60', '#715c7f', '#3FEE32', '#01FFEE'],
...                                   roundDigits = 4)
>>> a.setData(sorted(data, key = lambda datum: datum[0]))
>>> a.setAxisLabel('x', 'Note duration in quarter lengths')
>>> xtickValues = range(len(quarterLengths))
>>> xtickLabels = sorted(quarterLengths)
>>> a.axis['x']['ticks'] = (xtickValues, xtickLabels)
>>> a.process() 

See full-size graph

Three-Dimensional Bar Graphs

A three dimensional graph made of numerous rows of bars can be used to graph three-dimensional data. Data for this graph is provided by a dictionary of key-value pairs, where values are equal-sized-lists of values.

In addition to keyword arguments described for other graphs, this graph supports the following additional keyword arguments.

The barWidth keyword argument sets the width of bars. The useKeyValues keyword argument determines whether or not the keys in the data dictionary are interpreted as numerical values or labels. The zeroFloor keyword argument determines whether or not the vertical axis is sized to contain 0 or not.

A basic example follows:

>>> import random
>>> from music21 import graph
>>> a = graph.Graph3DPolygonBars(doneAction='show')
>>> data = {1:[], 2:[], 3:[]}
>>> for i in range(len(data.keys())):
...    q = [(x, random.choice(range(10*(i+1)))) for x in range(20)]
...    data[list(data.keys())[i]] = q
>>> a.setData(data)
>>> a.process()  

Here is an example from music. This graphs the 12 major scales next to each other in terms of frequency showing which notes are present and which notes are not:

>>> from music21 import *
>>> data = {0:[], 1:[], 2:[], 3:[], 4:[], 5:[], 6:[], 7:[], 8:[], 9:[], 10:[], 11:[]}
>>> majorScale = [0, 2, 4, 5, 7, 9, 11]
>>> for pitchClass in range(12):
...     n = note.Note()
...     n.pitchClass = pitchClass
...     frequency = n.frequency
...     for scale in data.keys():
...         if (pitchClass - scale) % 12 in majorScale:
...             data[scale].append((pitchClass, frequency))
>>> a = graph.Graph3DPolygonBars(title='The Twelve Major Scales',
...                             alpha=.8,
...                             barWidth=.2,
...                             doneAction='show',
...                             useKeyValues = True,
...                             zeroFloor = True,
...                             colors = ['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet'])
>>> a.setData(data)
>>> a.axis['x']['ticks'] = (range(12), ['c', 'c#', 'd', 'd#', 'e', 'f', 'f#', 'g', 'g#', 'a', 'a#', 'b'])
>>> a.axis['y']['ticks'] = (range(12), ['c', 'c#', 'd', 'd#', 'e', 'f', 'f#', 'g', 'g#', 'a', 'a#', 'b'])
>>> a.setAxisLabel('y', 'Root Notes')
>>> a.setAxisLabel('x', 'Scale Degrees')
>>> a.setAxisLabel('z', 'Frequency in Hz')
>>> a.process()   

See full-size graph