Music Computation Code from September 26, 2017

My approach was to plot the percentage of ascending departures from a pitch as a function of distance from the median. As pitch height increases relative to the median, the percentage of ascending departures should decrease. Multiple plots are generated with differing constraints on the size of the approaching interval. Comparing these different plots can yield insight into the relative effects of regression to the mean and post-skip reversal for skips of various sizes.

```
from music21 import *
from collections import Counter
import os
```

"Matplotlib is a Python 2D plotting library which produces publication quality
figures in a variety of hardcopy formats and interactive environments across
platforms. For simple plotting the `pyplot`

module provides a MATLAB-like
interface." See https://matplotlib.org/ for more details.

`import matplotlib.pyplot as plt`

NumPy is the fundamental package for scientific computing with Python. See http://www.numpy.org/ for more details.

`import numpy as np`

We will also go over some of the basic uses of Matplotlib and NumPy in class.

Class that brings together four pieces of information about every pitch (current): its predecessor (previous), successor (following), and the median pitch (median) of the song in which current occurs.

`class PitchNeighborhood(object):`

```
def __init__(self, previous, current, following, median):
self.previous = previous
self.current = current
self.following = following
self.median = median
```

Basic methods for obtaining intervals from the previous pitch, to the following pitch, and from the median pitch.

```
def getFromPrevious(self):
return self.current - self.previous
```

```
def getToNext(self):
return self.following - self.current
```

```
def getFromMedian(self):
return self.current - self.median
```

Given a flattened Stream of notes, creates an instance
of the `PitchNeighborhood`

class for each pitch and adds the class
to the list `neighborhoods`

.

`def update_neighborhoods(neighborhoods, song):`

` pitches = [n.pitch.ps for n in song]`

NumPy has a function for finding the median of list or NumPy array.

` med_pitch = np.median(pitches)`

Ignore if the median pitch is not an actual pitch. This avoids keeping track of fractional distances from the median. Only 22 out of 704 songs are omitted.

```
if int(med_pitch) != med_pitch:
return
```

Create `PitchNeighborhood`

for every pitch (other than the beginning and
ending pitches in each song) and append to the `neighborhoods`

list.

```
for i in range(1, len(pitches) - 1):
p = PitchNeighborhood(pitches[i-1], pitches[i], pitches[i+1], med_pitch)
neighborhoods.append(p)
```

Function to test regression to the mean versus
post-skip reversal. `neighborhoods`

is a list of instances of
the `PitchNeighborhood`

class. `leaps`

is a list of numbers
corresponding to plots of the graphs. If a member of `leaps`

, n,
is positive, the corresponding plot includes data only for those
pitches approached by an ascent of at least n. For negative n,
the plot includes data only for those pitches approached by a
descent of at least abs(n). If n is zero, the plot includes data
for all pitches.

`def experiment(neighborhoods, leaps, figure):`

Create `Counter`

objects to keep track of frequencies of ascents and all
all non-unison motions. This is to calculate percentages of non-unison
motions that ascend.

```
ascents = Counter()
ascents_descents = Counter()
```

Details for plotting the figures.

```
plt.figure(figure)
plt.subplot(121)
for leap in leaps:
```

`sign`

is a NumPy function for determining the sign of a number.

`>>> np.sign(-2)`

`-1`

`>>> np.sign(2)`

`1`

`>>> np.sign(0)`

`0`

` sign = np.sign(leap)`

Loop to update the `ascents`

and `ascents_descents`

counters with
pitches that are approached by an interval of at least size `leap`

.

` for p in neighborhoods:`

Filters the data to be included in the plot.

` if sign * p.getFromPrevious() >= sign * leap:`

Pitches left by an ascent.

```
if p.getToNext() > 0:
ascents.update([p.getFromMedian()])
```

Pitches not followed by the same pitch.

```
if p.getToNext() != 0:
ascents_descents.update([p.getFromMedian()])
```

The following loop deletes any keys in the `ascents_descents`

counter
if there are fewer than 10 instances of the key. Keys in the counters
correspond to distances from the median. Deleting the keys is to
avoid plotting data with a very small sample size.

Note that this `for`

loop is iterating over a list of keys in the
counter. Since we are potentially altering the counter (by deleting
keys), it would create problem to iterate over the keys directly with

`for key in ascents_descents`

.

```
for key in list(ascents_descents):
if ascents_descents[key] < 10:
del ascents_descents[key]
```

Create values for x- and y-axes of the figure.

```
xvalues = sorted(ascents_descents)
yvalues = [round(float(ascents[x]) / ascents_descents[x], 2)
for x in xvalues]
```

Generate legend for the figure.

```
if leap == 0:
lab = 'All pitches'
elif leap > 0:
lab = 'Approached by ascent of ' + str(leap) + ' or more'
else:
lab = 'Approached by descent of ' + str(leap) + ' or more'
```

Create the plot corresponding to the current value of `leap`

.

` plt.plot(xvalues, yvalues, label = lab)`

Clear the two counters for the next value of `leap`

.

```
ascents.clear()
ascents_descents.clear()
```

Label axes, provide title, and place legend in the figure.

```
plt.xlabel('Distance of pitch from median')
plt.ylabel('Percent of motions from pitch that ascend')
plt.title('Regression to the mean')
plt.legend(bbox_to_anchor=(1.05, 1), loc=2, borderaxespad=0.)
```

Write figure to the hard drive.

` plt.savefig('Figure ' + str(figure))`

Parse songs in the 'boehme' subdirectory. (Substitute the appropriate file path for your computer.)

```
dir_name = '/Users/ccallender/Desktop/europa/deutschl/boehme/'
filenames = [filename for filename in os.listdir(dir_name)]
paths = [dir_name + filename for filename in filenames if filename.endswith('.krn')]
songs = [converter.parse(path).flat.notes for path in paths]
print("Songs loaded")
```

Create and update `neighborhoods`

for each `song`

.

```
neighborhoods = []
for song in songs:
update_neighborhoods(neighborhoods, song)
```

Figure 1: All pitches regardless of approach. Base plot demonstrating influence of regression to the mean

```
figure = 1
experiment(neighborhoods, [0], figure)
```

Figures 2-5: Base plot plus pitches approached by leaps of at least a minor third, perfect fourth, perfect fifth, and minor sixth, respectively.

```
for leap in [3, 5, 7, 8]:
figure += 1
experiment(neighborhoods, [0, leap, -leap], figure)
```