Music014/102 Lab2: Tonality and Rhythm

Prof. Michael Casey

Dartmouth College


In [1]:
# import libraries that we will use
from pylab import * 
from bregman.suite import *
import tonality as ton # The Well-Tempered Clavier Tools
import mayavi.mlab as mylab
from pylab import rcParams
rcParams['figure.figsize'] = 14, 8

In [2]:
execfile('tonality.py')


/usr/local/lib/python2.7/dist-packages/ipykernel/__main__.py:55: VisibleDeprecationWarning: using a non-integer number instead of an integer will result in an error in the future
/usr/local/lib/python2.7/dist-packages/ipykernel/__main__.py:131: FutureWarning: comparison to `None` will result in an elementwise object comparison in the future.

In [3]:
reload(ton)


Out[3]:
<module 'tonality' from 'tonality.pyc'>

In [4]:
help(ton)


Help on module tonality:

NAME
    tonality - tonality.py - Matrix representations of musical scores, corpara, and their tonality

FILE
    /home/mkc/exp/music21/WTC_ASCII/tonality.py

DESCRIPTION
    Example: J. S. Bach's "Well Tempered Clavier" Books 1 and 2
    
    2015, Michael A. Casey, Dartmouth College, Bregman Media Labs
    
    License: 
    Creative Commons Attribution-NonCommercial 4.0 International (CC BY-NC 4.0)
    http://creativecommons.org/licenses/by-nc/4.0/

FUNCTIONS
    center_mtx(D)
        Given a dissimilarity or dissonance matrix, center the matrix by subtracting the mean of 
        the rows and columns. For a dissimilarity matrix this operation yields the "scatter matrix".
    
    dissimilarity_mtx(A)
        Given a piano-roll indicator matrix, construct self-dissimilarity matrix
    
    dissonance_fun(A)
        Given a piano-roll indicator matrix representation of a musical work (128 pitches x beats),
        return the dissonance as a function of beats.
        Input:
            A  - 128 x beats indicator matrix of MIDI pitch number
    
    dissonance_mtx(A)
        Given a piano-roll indicator matrix, construct pair-wise dissonance matrix
    
    fold_mtx(a)
        Fold piano-roll matrix into single octave beginning with 'C'.
    
    hist_mtx(mtx, tstr='')
        Given a piano-roll matrix, 128 MIDI piches x beats, plot the pitch class histogram
    
    load_wtc(idx=None, win_len=1, sample_len=0)
        Load scores in matrix form in the entire WTC dataset.
        Inputs:
                idx - slice argument giving range of works [None] (all)
            win_len - num tactus beats to integrate [1] (no integration)
         sample_len - number of sampled windows per work [0] (all)
    
    plot_mtx(mtx=None, title=None, newfig=False, cbar=True, **kwargs)
        ::
        
            static method for plotting a matrix as a time-frequency distribution (audio features)
    
    scale_mtx(M, normalize=False, dbscale=False, norm=False, bels=False)
        ::
        
            Perform mutually-orthogonal scaling operations, otherwise return identity:
              normalize [False]
              dbscale  [False]
              norm      [False]
    
    win_mtx(a, win_len=2)
        Options:
            win_len  - window length [2]

DATA
    pc_labels = array(['C', 'C#', 'D', 'Eb', 'E', 'F', 'F#', 'G'..., 'G', ...


In this lab you will:

1. Analyze music using both audio features and symbolic (score-based) features
2. Analyze a musical corpus by sampling from multiple works
3. Extract the latent geometry in tonal music 

Part A

First, let's load a single piece of music: the Prelude in C Major from J. S. Bach's Well Tempered Clavier Book 1


In [5]:
prelude1 = loadtxt('01.ascii') # Load the score in matrix form, 128 MIDI notes x time (tactus)

In [6]:
ton.plot_mtx(prelude1, 'Prelude No. 1 in C Major')


Dissonance Overlay Plot


In [7]:
# Overlay dissonance plot, scaled to range of pitch values
ton.plot_mtx(prelude1, 'Prelude No. 1 in C Major') 
d = ton.dissonance_fun(prelude1)
plot(d*30,'r')
title('Dissonance',fontsize=16)
ax = axis('tight')


tonality.py:131: FutureWarning: comparison to `None` will result in an elementwise object comparison in the future.
  if amps == None: amps = [1]*len(freqs)

Pitch Usage Histogram


In [8]:
# Summarize the work as a histogram, showing only range of active pitches
ton.hist_mtx(prelude1, 'J. S. Bach, Prelude No. 1 in C Major')


Dissimilarity Matrix


In [9]:
D = ton.dissimilarity_mtx(prelude1)
imshow(D)
colorbar()
title('Dissimilarity Matrix: WTC 1: Prelude No. 1 in C Major')
xlabel('Tactus', fontsize=14); ylabel('Tactus', fontsize=14)


Out[9]:
<matplotlib.text.Text at 0x7ff1ea777cd0>

Part B: Working with a Corpus

So far we have considered only one work at a time. Here we will investigate working with a corpus of works: the Well Tempered Clavier (WTC) Books 1 and 2 by J. S. Bach

WTC is organized into two books with 48 works each: a prelude and a fugue for every major and minor key in pitch class order: ['C','C#','D',...].

The WTC folder contains MIDI piano roll matrix representations of each work.

BOOK 1:

  • 1.ascii = Prelude No. 1 in C Major
  • 2.ascii = Fugue No. 1 in C Major
  • 3.ascii = Prelude No. 2 in C Minor
  • 4.ascii = Fugue No. 2 in C Minor
  • 5.ascii = Prelude No. 3 in C# Major
  • 6.ascii = Fugue No. 3 in C# Major
  • 7.ascii = Prelude No. 4 in C# Minor
  • 8.ascii = Fugue No. 4 in C# Minor
  • ...
  • 47.ascii - Prelude No. 24 in B Minor
  • 48.ascii = Fugue No. 24 in B Minor

BOOK 2:

  • 49.ascii = Prelude No. 25 in C Major
  • 59.ascii = Fugue No. 25 in C Major
  • ...
  • 95.ascii = Prelude No. 48 in B Minor
  • 96.ascii = Fugue No. 48 in B Minor

Loading and Displaying Multiple Works


In [10]:
# Learn about the load_wtc function
help(ton.load_wtc)


Help on function load_wtc in module tonality:

load_wtc(idx=None, win_len=1, sample_len=0)
    Load scores in matrix form in the entire WTC dataset.
    Inputs:
            idx - slice argument giving range of works [None] (all)
        win_len - num tactus beats to integrate [1] (no integration)
     sample_len - number of sampled windows per work [0] (all)


In [11]:
# Load the first two works in WTC
A = ton.load_wtc(slice(0,10,2)) # sample_len=0
A = hstack(A) # Stack (concatenate) the two works into a single matrix
ton.plot_mtx(A, 'WTC Preludes 1-5')


tonality.py:55: VisibleDeprecationWarning: using a non-integer number instead of an integer will result in an error in the future
  aa.append(a[:,k*win_len:(k+1)*win_len].mean(1))

In [12]:
# Load the first five Fugues in WTC
A = ton.load_wtc(slice(1,10,2)) # sample_len=0
A = hstack(A) # Stack (concatenate) the works into a single matrix
ton.plot_mtx(A, 'WTC Fugues 1-5')



In [13]:
# Load the 24 Preludes in WTC Book 1
A = ton.load_wtc(slice(0,48,2)) # sample_len=0
A = hstack(A) # Stack (concatenate) the two works into a single matrix
#A = ton.scale_mtx(A.T, norm=True).T
ton.plot_mtx(A, 'WTC Preludes 1-24')
print "Matrix Size is:", A.shape


Matrix Size is: (128, 21671)

In [14]:
# Make a plot of pitch usage in all Preludes in book 1
ton.hist_mtx(ton.fold_mtx(A), 'WTC Preludes 1-24')


QUESTION: What do you conclude about pitch usage in Preludes 1-24 in Book 1 of WTC?

WRITE YOUR ANSWER HERE

Finding Implicit Geometry in Tonality


In [15]:
# Loading whole works results in very large matrices.
# We need to restrict the size of matrices to have <=5000 time points
# We have two choices:
#  1) Average Adjacent Tactus into pitch profiles spanning Beats, Bars or Phrases
#  2) Randomly sample subsets of Beats, Bars, or Phrases in each work
A = ton.load_wtc(win_len=16, sample_len=20) # Integrate 16 adjacent frames into beat-level
A = hstack(A) # Stack (concatenate) the two works into a single matrix
ton.plot_mtx(A)


Making a Corpus-Level Dissimilarity Matrix


In [16]:
# Now that we have fewer time-points due to integration, we can form the pair-wise dissimilarity matrix
D = ton.dissimilarity_mtx(A)
imshow(D**2)
colorbar()


Out[16]:
<matplotlib.colorbar.Colorbar at 0x7ff1ea64ec50>

Eigen Pitches

Principal Component Analysis (PCA) using the Singular Value Decomposition (SVD)


In [17]:
# Center the Dissimilarity Matrix
B = ton.center_mtx(D**2) # Use squared dissimilarity

# Singular Value Decomposition (Eigenvector Analysis)
u,s,v = svd(B) # 
figure(figsize=(12,8))
stem(arange(32),s[:32])
grid()



In [18]:
# Now make a plot of each 'beat' projected onto the first two eigenvector dimensions
figure(figsize=(6,6))
plot(u[:,0], u[:,1],'.')
grid()
figure(figsize=(6,6))
plot3(u[:,0],u[:,1],u[:,2], linestyle='', marker='.')


Out[18]:
<mpl_toolkits.mplot3d.axes3d.Axes3D at 0x7ff1e8ba76d0>

In [19]:
mylab.points3d(u[:,0], u[:,1], u[:,2], color=(.5,1,.5), scale_factor=.1)
mylab.show()

QUESTION: What do you see. What do you think this represents ?


In [20]:
sys.path.append('/home/mkc/exp/tsne_python')

In [21]:
import tsne

In [22]:
tsne.tsne?

In [ ]:


In [23]:
p = tsne.tsne(A.T, no_dims=3, perplexity=5)


Preprocessing the data using PCA...
Computing pairwise distances...
Computing P-values for point  0  of  1918 ...
Computing P-values for point  500  of  1918 ...
Computing P-values for point  1000  of  1918 ...
Computing P-values for point  1500  of  1918 ...
Mean value of sigma:  0.209178784402
Iteration  10 : error is  26.4542414369
Iteration  20 : error is  25.1614123521
Iteration  30 : error is  23.1504069892
Iteration  40 : error is  22.7349659756
Iteration  50 : error is  22.8526930357
Iteration  60 : error is  22.9815380658
Iteration  70 : error is  23.0227838535
Iteration  80 : error is  23.003862655
Iteration  90 : error is  22.9597626334
Iteration  100 : error is  22.9230024689
Iteration  110 : error is  3.66247149031
Iteration  120 : error is  2.99032651986
Iteration  130 : error is  2.63713554445
Iteration  140 : error is  2.42455160831
Iteration  150 : error is  2.28321238072
Iteration  160 : error is  2.18330612693
Iteration  170 : error is  2.11146174279
Iteration  180 : error is  2.05680836444
Iteration  190 : error is  2.01224044291
Iteration  200 : error is  1.97502737686
Iteration  210 : error is  1.94350952387
Iteration  220 : error is  1.91630491758
Iteration  230 : error is  1.89337642392
Iteration  240 : error is  1.87363846987
Iteration  250 : error is  1.8564206107
Iteration  260 : error is  1.84130368804
Iteration  270 : error is  1.82777641416
Iteration  280 : error is  1.81563651154
Iteration  290 : error is  1.80469513002
Iteration  300 : error is  1.79491295119
Iteration  310 : error is  1.78602448883
Iteration  320 : error is  1.77794951245
Iteration  330 : error is  1.77056335163
Iteration  340 : error is  1.76382107399
Iteration  350 : error is  1.75756671839
Iteration  360 : error is  1.75155809385
Iteration  370 : error is  1.74613373197
Iteration  380 : error is  1.74115700607
Iteration  390 : error is  1.73652545328
Iteration  400 : error is  1.73222784392
Iteration  410 : error is  1.72826670969
Iteration  420 : error is  1.7246022201
Iteration  430 : error is  1.72118649723
Iteration  440 : error is  1.71797540673
Iteration  450 : error is  1.71494295353
Iteration  460 : error is  1.71208177831
Iteration  470 : error is  1.70939766403
Iteration  480 : error is  1.70685537253
Iteration  490 : error is  1.70443056578
Iteration  500 : error is  1.70216025804
Iteration  510 : error is  1.7000301359
Iteration  520 : error is  1.69801499622
Iteration  530 : error is  1.69609692803
Iteration  540 : error is  1.6942818189
Iteration  550 : error is  1.69255088824
Iteration  560 : error is  1.69090491629
Iteration  570 : error is  1.68933100767
Iteration  580 : error is  1.6878257634
Iteration  590 : error is  1.68639116857
Iteration  600 : error is  1.68502680715
Iteration  610 : error is  1.68373022809
Iteration  620 : error is  1.68248624216
Iteration  630 : error is  1.68128920932
Iteration  640 : error is  1.68014290842
Iteration  650 : error is  1.67903891635
Iteration  660 : error is  1.67797116346
Iteration  670 : error is  1.67694625095
Iteration  680 : error is  1.67596136931
Iteration  690 : error is  1.67501240814
Iteration  700 : error is  1.67410469068
Iteration  710 : error is  1.67322967828
Iteration  720 : error is  1.67237642128
Iteration  730 : error is  1.67154809597
Iteration  740 : error is  1.67074920342
Iteration  750 : error is  1.66998144342
Iteration  760 : error is  1.66923945261
Iteration  770 : error is  1.66851980983
Iteration  780 : error is  1.66782034845
Iteration  790 : error is  1.66714502997
Iteration  800 : error is  1.66649158489
Iteration  810 : error is  1.66586025894
Iteration  820 : error is  1.66524686437
Iteration  830 : error is  1.66465274444
Iteration  840 : error is  1.66407762067
Iteration  850 : error is  1.66352284923
Iteration  860 : error is  1.66298512926
Iteration  870 : error is  1.66245879248
Iteration  880 : error is  1.66194488753
Iteration  890 : error is  1.66144714613
Iteration  900 : error is  1.66096562626
Iteration  910 : error is  1.66049655479
Iteration  920 : error is  1.66003794799
Iteration  930 : error is  1.65958937105
Iteration  940 : error is  1.65915171634
Iteration  950 : error is  1.65872515693
Iteration  960 : error is  1.65831285147
Iteration  970 : error is  1.65791361107
Iteration  980 : error is  1.65752645732
Iteration  990 : error is  1.657147537
Iteration  1000 : error is  1.65677778316

In [24]:
plot(p[:,0],p[:,1],'.')


Out[24]:
[<matplotlib.lines.Line2D at 0x7ff1e8ad4150>]

In [25]:
figure(figsize=(12,8))
ax = plot3(p, linestyle='', marker='.')



In [27]:
figure(figsize=(20,6))
subplot(131)
plot(p[:,0],p[:,1],'.')
subplot(132)
plot(p[:,0],p[:,2],'.')
subplot(133)
plot(p[:,1],p[:,2],'.')


Out[27]:
[<matplotlib.lines.Line2D at 0x7ff1e1170ed0>]

In [28]:
# Interactive 3D plot (would be great to make this interactive audio-visual)

In [33]:
mylab.points3d(p[:,0], p[:,1], p[:,2], color=(0,1,1), scale_factor=2.5)
mylab.show()


---------------------------------------------------------------------------
TraitError                                Traceback (most recent call last)
/usr/lib/python2.7/dist-packages/tvtk/pyface/ui/wx/scene.pyc in OnKeyUp(self, event)
    482                         x = event.GetX()
    483                         y = self._vtk_control.GetSize()[1] - event.GetY()
--> 484                     data = self.picker.pick_world(x, y)
    485                     coord = data.coordinate
    486                     if coord is not None:

/usr/lib/python2.7/dist-packages/tvtk/pyface/picker.pyc in pick_world(self, x, y)
    393             # does not seem to work properly.
    394             probe = tvtk.ProbeFilter()
--> 395             probe.source = data
    396             probe.input = self.probe_data
    397             probe.update()

/usr/lib/python2.7/dist-packages/traits/trait_handlers.pyc in _read_only(object, name, value)
    102 def _read_only ( object, name, value ):
    103     raise TraitError, "The '%s' trait of %s instance is 'read only'." % (
--> 104                       name, class_of( object ) )
    105 
    106 def _undefined_get ( object, name ):

TraitError: The 'source' trait of a ProbeFilter instance is 'read only'.

In [118]:
A.shape


Out[118]:
(128, 1918)

In [32]:
mylab.points3d?

In [29]:


In [ ]: