# ------------------------------------------------------------------------------
# Name: serial.py
# Purpose: music21 classes for serial transformations
#
# Authors: Michael Scott Asato Cuthbert
# Christopher Ariza
#
# Copyright: Copyright © 2009-2012 Michael Scott Asato Cuthbert
# License: BSD, see license.txt
# ------------------------------------------------------------------------------
'''
This module defines objects for defining and manipulating structures
common to serial and/or twelve-tone music,
including :class:`~music21.serial.ToneRow` subclasses.
Serial searching methods that were previously here have been moved to
:mod:`~music21.search.serial`.
'''
from __future__ import annotations
import typing as t
import unittest
from music21 import chord
from music21 import environment
from music21 import exceptions21
from music21 import note
from music21 import pitch
from music21 import stream
environLocal = environment.Environment('serial')
# ------------------------------------------------------------------------------
class SerialException(exceptions21.Music21Exception):
pass
# ------------------------------------------------------------------------------
[docs]
class TwelveToneMatrix(stream.Stream):
'''
An object representation of a 2-dimensional array of 12 pitches.
Internal representation is as a :class:`~music21.stream.Stream`,
which stores 12 Streams, each Stream a horizontal row of pitches
in the matrix.
This object is commonly used by calling the
:meth:`~music21.stream.TwelveToneRow.matrix` method of
:class:`~music21.stream.TwelveToneRow` (or a subclass).
>>> ttr = serial.TwelveToneRow([0, 2, 11, 7, 8, 3, 9, 1, 4, 10, 6, 5])
>>> aMatrix = ttr.matrix()
>>> print(aMatrix)
0 2 B 7 8 3 9 1 4 A 6 5
A 0 9 5 6 1 7 B 2 8 4 3
1 3 0 8 9 4 A 2 5 B 7 6
5 7 4 0 1 8 2 6 9 3 B A
4 6 3 B 0 7 1 5 8 2 A 9
9 B 8 4 5 0 6 A 1 7 3 2
3 5 2 A B 6 0 4 7 1 9 8
B 1 A 6 7 2 8 0 3 9 5 4
8 A 7 3 4 B 5 9 0 6 2 1
2 4 1 9 A 5 B 3 6 0 8 7
6 8 5 1 2 9 3 7 A 4 0 B
7 9 6 2 3 A 4 8 B 5 1 0
>>> repr(aMatrix)
'<music21.serial.TwelveToneMatrix for [<music21.serial.TwelveToneRow row-1>]>'
>>> fourthQuartetMatrix = serial.getHistoricalRowByName('SchoenbergOp37').matrix()
>>> repr(fourthQuartetMatrix)
'<music21.serial.TwelveToneMatrix for
[<music21.serial.HistoricalTwelveToneRow Schoenberg Op. 37 Fourth String Quartet>]>'
'''
def __str__(self):
'''
Return a string representation of the matrix.
'''
ret = []
for rowForm in self:
msg = []
for n in rowForm:
msg.append(str(n.pitch.pitchClassString).rjust(3))
ret.append(''.join(msg))
return '\n'.join(ret)
def _reprInternal(self):
if self:
if isinstance(self[0], ToneRow):
return f'for [{self[0]}]'
else:
return super()._reprInternal()
else:
return super()._reprInternal()
# ------------------------------------------------------------------------------
# noinspection SpellCheckingInspection
historicalDict = {
'WebernOp29': ('Webern', 'Op. 29', 'Cantata I',
[3, 11, 2, 1, 5, 4, 7, 6, 10, 9, 0, 8]),
'WebernOp28': ('Webern', 'Op. 28', 'String Quartet',
[1, 0, 3, 2, 6, 7, 4, 5, 9, 8, 11, 10]),
'SchoenbergOp24Mvmt5': ('Schoenberg', 'Op. 24', 'Serenade, Mvt. 5, "Tanzscene"',
[9, 10, 0, 3, 4, 6, 5, 7, 8, 11, 1, 2]),
'SchoenbergOp24Mvmt4': ('Schoenberg', 'Op. 24', 'Serenade, Mvt. 4, "Sonett"',
[4, 2, 3, 11, 0, 1, 8, 6, 9, 5, 7, 10]),
'SchoenbergJakobsleiter': ('Schoenberg', None, 'Die Jakobsleiter',
[1, 2, 5, 4, 8, 7, 0, 3, 11, 10, 6, 9]),
'SchoenbergOp27No4': ('Schoenberg', 'Op. 27 No. 4', 'Four Pieces for Mixed Chorus, No. 4',
[1, 3, 10, 6, 8, 4, 11, 0, 2, 9, 5, 7]),
'WebernOp23': ('Webern', 'Op. 23', 'Three Songs',
[8, 3, 7, 4, 10, 6, 2, 5, 1, 0, 9, 11]),
'BergLuluActIIScene1': ('Berg', 'Lulu, Act II, Scene 1',
'Perm. (Every 5th Note Of Transposed Primary Row)',
[10, 7, 1, 0, 9, 2, 4, 11, 5, 8, 3, 6]),
'SchoenbergOp27No1': ('Schoenberg', 'Op. 27 No. 1', 'Four Pieces for Mixed Chorus, No. 1',
[6, 5, 2, 8, 7, 1, 3, 4, 10, 9, 11, 0]),
'BergLuluActIScene20': ('Berg', 'Lulu, Act I , Scene XX',
'Perm. (Every 7th Note Of Transposed Primary Row)',
[10, 6, 3, 8, 5, 11, 4, 2, 9, 0, 1, 7]),
'SchoenbergOp27No3': ('Schoenberg', 'Op. 27 No. 3', 'Four Pieces for Mixed Chorus, No. 3',
[7, 6, 2, 4, 5, 3, 11, 0, 8, 10, 9, 1]),
'SchoenbergOp27No2': ('Schoenberg', 'Op. 27 No. 2', 'Four Pieces for Mixed Chorus, No. 2',
[0, 11, 4, 10, 2, 8, 3, 7, 6, 5, 9, 1]),
'SchoenbergFragPiano': ('Schoenberg', None, 'Fragment For Piano',
[6, 9, 0, 7, 1, 2, 8, 11, 5, 10, 4, 3]),
'SchoenbergOp50B': ('Schoenberg', 'Op. 50B', 'De Profundis',
[3, 9, 8, 4, 2, 10, 7, 11, 0, 6, 5, 1]),
'SchoenbergOp50C': ('Schoenberg', 'Op. 50C', 'Modern Psalms, The First Psalm',
[4, 3, 0, 8, 11, 7, 5, 9, 6, 10, 1, 2]),
'SchoenbergOp50A': ('Schoenberg', 'Op. 50A', 'Three Times A Thousand Years',
[7, 9, 6, 4, 5, 11, 10, 2, 0, 1, 3, 8]),
'SchoenbergMosesAron': ('Schoenberg', None, 'Moses And Aron',
[9, 10, 4, 2, 3, 1, 7, 5, 6, 8, 11, 0]),
'WebernOp25': ('Webern', 'Op. 25', 'Three Songs',
[7, 4, 3, 6, 1, 5, 2, 11, 10, 0, 9, 8]),
'SchoenbergOp23No5': ('Schoenberg', 'Op. 23, No. 5', 'Five Piano Pieces',
[1, 9, 11, 7, 8, 6, 10, 2, 4, 3, 0, 5]),
'SchoenbergOp28No1': ('Schoenberg', 'Op. 28 No. 1',
'Three Satires for Mixed Chorus, No. 1',
[0, 4, 7, 1, 9, 11, 5, 3, 2, 6, 8, 10]),
'SchoenbergOp28No3': ('Schoenberg', 'Op. 28 No. 3',
'Three Satires for Mixed Chorus, No. 3',
[5, 6, 4, 8, 2, 10, 7, 9, 3, 11, 1, 0]),
'WebernOp21': ('Webern', 'Op. 21', 'Chamber Symphony',
[5, 8, 7, 6, 10, 9, 3, 4, 0, 1, 2, 11]),
'SchoenbergIsraelExists': ('Schoenberg', None, 'Israel Exists Again',
[0, 3, 4, 9, 11, 5, 2, 1, 10, 8, 6, 7]),
'SchoenbergOp35No2': ('Schoenberg', 'Op. 35', 'Six Pieces for Male Chorus, No. 2',
[6, 9, 7, 1, 0, 2, 5, 11, 10, 3, 4, 8]),
'SchoenbergOp35No3': ('Schoenberg', 'Op. 35', 'Six Pieces for Male Chorus, No. 3',
[3, 6, 7, 8, 5, 0, 9, 10, 4, 11, 2, 1]),
'SchoenbergOp35No1': ('Schoenberg', 'Op. 35', 'Six Pieces for Male Chorus, No. 1',
[2, 11, 3, 5, 4, 1, 8, 10, 9, 6, 0, 7]),
'SchoenbergOp48No1': ('Schoenberg', 'Op. 48', 'Three Songs, No. 1, "Sommermud"',
[1, 2, 0, 6, 3, 5, 4, 10, 11, 7, 9, 8]),
'SchoenbergOp35No5': ('Schoenberg', 'Op. 35', 'Six Pieces for Male Chorus, No. 5',
[1, 7, 10, 2, 3, 11, 8, 4, 0, 6, 5, 9]),
'SchoenbergOp29': ('Schoenberg', 'Op. 29', 'Suite',
[3, 7, 6, 10, 2, 11, 0, 9, 8, 4, 5, 1]),
'BergLyricSuitePerm': ('Berg', None, 'Lyric Suite, Last Mvt. Permutation',
[5, 6, 10, 4, 1, 9, 2, 8, 7, 3, 0, 11]),
'WebernOp20': ('Webern', 'Op. 20', 'String Trio',
[8, 7, 2, 1, 6, 5, 9, 10, 3, 4, 0, 11]),
'SchoenbergOp46': ('Schoenberg', 'Op. 46', 'A Survivor From Warsaw',
[6, 7, 0, 8, 4, 3, 11, 10, 5, 9, 1, 2]),
'SchoenbergFragOrganSonata': ('Schoenberg', None, 'Fragment of Sonata For Organ',
[1, 7, 11, 3, 9, 2, 8, 6, 10, 5, 0, 4]),
'SchoenbergOp44': ('Schoenberg', 'Op. 44', 'Prelude To A Suite From "Genesis"',
[10, 6, 2, 5, 4, 0, 11, 8, 1, 3, 9, 7]),
'SchoenbergOp45': ('Schoenberg', 'Op. 45', 'String Trio',
[2, 10, 3, 9, 4, 1, 11, 8, 6, 7, 5, 0]),
'SchoenbergOp33A': ('Schoenberg', 'Op. 33A', 'Two Piano Pieces, No. 1',
[10, 5, 0, 11, 9, 6, 1, 3, 7, 8, 2, 4]),
'SchoenbergOp25': ('Schoenberg', 'Op.25', 'Suite for Piano',
[4, 5, 7, 1, 6, 3, 8, 2, 11, 0, 9, 10]),
'SchoenbergOp26': ('Schoenberg', 'Op. 26', 'Wind Quintet',
[3, 7, 9, 11, 1, 0, 10, 2, 4, 6, 8, 5]),
'SchoenbergOp33B': ('Schoenberg', 'Op. 33B', 'Two Piano Pieces, No. 2',
[11, 1, 5, 3, 9, 8, 6, 10, 7, 4, 0, 2]),
'BergViolinConcerto': ('Berg', None, 'Concerto For Violin And Orchestra',
[7, 10, 2, 6, 9, 0, 4, 8, 11, 1, 3, 5]),
'WebernOp22': ('Webern', 'Op. 22', 'Quartet For Violin, Clarinet, Tenor Sax, And Piano',
[6, 3, 2, 5, 4, 8, 9, 10, 11, 1, 7, 0]),
'BergLulu': ('Berg', None, 'Lulu: Primary Row',
[0, 4, 5, 2, 7, 9, 6, 8, 11, 10, 3, 1]),
'WebernOp30': ('Webern', 'Op. 30', 'Variations For Orchestra',
[9, 10, 1, 0, 11, 2, 3, 6, 5, 4, 7, 8]),
'WebernOp31': ('Webern', 'Op. 31', 'Cantata II',
[6, 9, 5, 4, 8, 3, 7, 11, 10, 2, 1, 0]),
'WebernOpNo17No1': ('Webern', 'Op. 17, No. 1', '"Armer Sunder, Du"',
[11, 10, 5, 6, 3, 4, 7, 8, 9, 0, 1, 2]),
'WebernOp24': ('Webern', 'Op. 24', 'Concerto For Nine Instruments',
[11, 10, 2, 3, 7, 6, 8, 4, 5, 0, 1, 9]),
'SchoenbergOp48No2': ('Schoenberg', 'Op. 48', 'Three Songs, No. 2, "Tot"',
[2, 3, 9, 1, 10, 4, 8, 7, 0, 11, 5, 6]),
'WebernOp27': ('Webern', 'Op. 27', 'Variations For Piano',
[3, 11, 10, 2, 1, 0, 6, 4, 7, 5, 9, 8]),
'SchoenbergOp47': ('Schoenberg', 'Op. 47', 'Fantasy For Violin And Piano',
[10, 9, 1, 11, 5, 7, 3, 4, 0, 2, 8, 6]),
'WebernOp19No2': ('Webern', 'Op. 19, No. 2', '"Ziehn Die Schafe"',
[8, 4, 9, 6, 7, 0, 11, 5, 3, 2, 10, 1]),
'WebernOp19No1': ('Webern', 'Op. 19, No. 1', '"Weiss Wie Lilien"',
[7, 10, 6, 5, 3, 9, 8, 1, 2, 11, 4, 0]),
'WebernOp26': ('Webern', 'Op. 26', 'Das Augenlicht',
[8, 10, 9, 0, 11, 3, 4, 1, 5, 2, 6, 7]),
'SchoenbergFragPianoPhantasia': ('Schoenberg', None, 'Fragment of Phantasia For Piano',
[1, 5, 3, 6, 4, 8, 0, 11, 2, 9, 10, 7]),
'BergDerWein': ('Berg', None, 'Der Wein',
[2, 4, 5, 7, 9, 10, 1, 6, 8, 0, 11, 3]),
'BergWozzeckPassacaglia': ('Berg', None, 'Wozzeck, Act I, Scene 4 "Passacaglia"',
[3, 11, 7, 1, 0, 6, 4, 10, 9, 5, 8, 2]),
'WebernOp18No1': ('Webern', 'Op. 18, No. 1', '"Schatzerl Klein"',
[0, 11, 5, 8, 10, 9, 3, 4, 1, 7, 2, 6]),
'WebernOp18No2': ('Webern', 'Op. 18, No. 2', '"Erlosung"',
[6, 9, 5, 8, 4, 7, 3, 11, 2, 10, 1, 0]),
'WebernOp18No3': ('Webern', 'Op. 18, No. 3', '"Ave, Regina Coelorum"',
[4, 3, 7, 6, 5, 11, 10, 2, 1, 0, 9, 8]),
'SchoenbergOp42': ('Schoenberg', 'Op. 42', 'Concerto For Piano And Orchestra',
[3, 10, 2, 5, 4, 0, 6, 8, 1, 9, 11, 7]),
'SchoenbergOp48No3': ('Schoenberg', 'Op. 48', 'Three Songs, No, 3, "Madchenlied"',
[1, 7, 9, 11, 3, 5, 10, 6, 4, 0, 8, 2]),
'SchoenbergOp37': ('Schoenberg', 'Op. 37', 'Fourth String Quartet',
[2, 1, 9, 10, 5, 3, 4, 0, 8, 7, 6, 11]),
'SchoenbergOp36': ('Schoenberg', 'Op. 36', 'Concerto for Violin and Orchestra',
[9, 10, 3, 11, 4, 6, 0, 1, 7, 8, 2, 5]),
'SchoenbergOp34': ('Schoenberg', 'Op. 34', 'Accompaniment to a Film Scene',
[3, 6, 2, 4, 1, 0, 9, 11, 10, 8, 5, 7]),
'BergChamberConcerto': ('Berg', None, 'Chamber Concerto',
[11, 7, 5, 9, 2, 3, 6, 8, 0, 1, 4, 10]),
'SchoenbergOp32': ('Schoenberg', 'Op. 32', 'Von Heute Auf Morgen',
[2, 3, 9, 1, 11, 5, 8, 7, 4, 0, 10, 6]),
'SchoenbergOp31': ('Schoenberg', 'Op. 31', 'Variations for Orchestra',
[10, 4, 6, 3, 5, 9, 2, 1, 7, 8, 11, 0]),
'SchoenbergOp30': ('Schoenberg', 'Op. 30', 'Third String Quartet',
[7, 4, 3, 9, 0, 5, 6, 11, 10, 1, 8, 2]),
'BergLyricSuite': ('Berg', None, 'Lyric Suite Primary Row',
[5, 4, 0, 9, 7, 2, 8, 1, 3, 6, 10, 11]),
'SchoenbergOp41': ('Schoenberg', 'Op. 41', 'Ode To Napoleon',
[1, 0, 4, 5, 9, 8, 3, 2, 6, 7, 11, 10]),
'WebernOp17No3': ('Webern', 'Op. 17, No. 3', '"Heiland, Unsere Missetaten..."',
[8, 5, 4, 3, 7, 6, 0, 1, 2, 11, 10, 9]),
'WebernOp17No2': ('Webern', 'Op. 17, No. 2', '"Liebste Jungfrau"',
[1, 0, 11, 7, 8, 2, 3, 6, 5, 4, 9, 10])
}
# ------------------------------------------------------------------------------
[docs]
class ToneRow(stream.Stream):
'''
A Stream representation of a tone row, or an ordered sequence of pitches;
can most importantly be used to deal with
serial transformations.
Unlike a normal Stream, the first argument is assumed to be a ToneRow:
>>> toneRow = serial.ToneRow([10, 9, 4, 5, 6, 3, 2, 8, 7, 11, 0, 1])
The representation of a ToneRow will be the contents:
>>> toneRow
<music21.serial.ToneRow A94563287B01>
Unless (like a Stream), the id is set:
>>> toneRow.id = 'retrograde_jungfrau'
>>> toneRow
<music21.serial.ToneRow retrograde_jungfrau>
A ToneRow does not need to have twelve pitches, like this ten-tone row
from Ruth Crawford Seeger's String Quartet 1931.
>>> rcsRow = serial.ToneRow([2, 4, 5, 3, 6, 9, 8, 7, 1, 0])
>>> rcsRow
<music21.serial.ToneRow 2453698710>
>>> len(rcsRow)
10
'''
_DOC_ATTR: dict[str, str] = {
'row': 'A list representing the pitch class values of the row.',
}
_DOC_ORDER = ['pitchClasses', 'noteNames', 'isTwelveToneRow', 'isSameRow',
'getIntervalsAsString',
'zeroCenteredTransformation', 'originalCenteredTransformation',
'findZeroCenteredTransformations', 'findOriginalCenteredTransformations']
def __init__(self, row=None, **keywords):
super().__init__(**keywords)
if row is not None:
self.row = row
else:
self.row = []
for pc in self.row:
if not isinstance(pc, (pitch.Pitch, note.Note)):
pc = pitch.Pitch(pc)
if not isinstance(pc, note.Note):
n = note.Note(pitch=pc)
else:
n = pc
n.pitch.octave = None
self.append(n)
def _reprInternal(self):
superInternal = super()._reprInternal()
if not superInternal.startswith('0x'):
return superInternal
return ''.join(n.pitch.pitchClassString for n in self)
[docs]
def pitchClasses(self):
'''
Convenience function showing the pitch classes of a
:class:`~music21.serial.ToneRow` as a list.
>>> fiveFold = [5 * i for i in range(12)]
>>> fiveFold
[0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55]
>>> quintupleRow = serial.pcToToneRow(fiveFold)
>>> quintupleRow
<music21.serial.TwelveToneRow 05A3816B4927>
>>> quintupleRow.pitchClasses()
[0, 5, 10, 3, 8, 1, 6, 11, 4, 9, 2, 7]
>>> halfStep = serial.pcToToneRow([0, 1])
>>> halfStep.pitchClasses()
[0, 1]
'''
pitchList = [n.pitch.pitchClass for n in self]
return pitchList
[docs]
def noteNames(self):
'''
Convenience function showing the note names of a
:class:`~music21.serial.ToneRow` as a list.
>>> chromatic = serial.TwelveToneRow(range(12))
>>> chromatic.noteNames()
['C', 'C#', 'D', 'E-', 'E', 'F', 'F#', 'G', 'G#', 'A', 'B-', 'B']
>>> halfStep = serial.ToneRow([0, 1])
>>> halfStep.noteNames()
['C', 'C#']
'''
noteList = [p.name for p in self]
return noteList
[docs]
def isTwelveToneRow(self):
'''
Describes whether a :class:`~music21.serial.ToneRow` constitutes
a twelve-tone row. Note that a
:class:`~music21.serial.TwelveToneRow` object might not be a twelve-tone row.
>>> serial.ToneRow([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]).isTwelveToneRow()
True
>>> serial.ToneRow([0, 4, 8]).isTwelveToneRow()
False
>>> serial.ToneRow([3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3]).isTwelveToneRow()
False
'''
pitchList = self.pitchClasses()
if len(pitchList) != 12:
return False
else:
temp = True
for i in range(12):
if i not in pitchList:
temp = False
return temp
[docs]
def makeTwelveToneRow(self):
# noinspection PyShadowingNames
'''
Convenience function returning a :class:`~music21.serial.TwelveToneRow` with the
same pitches.
Note that a :class:`~music21.serial.ToneRow` may be created without being a
true twelve tone row.
>>> r1 = serial.pcToToneRow(range(11))
>>> type(r1)
<class 'music21.serial.ToneRow'>
>>> n = note.Note()
>>> n.pitch.pitchClass = 11
>>> r1.append(n)
>>> r2 = r1.makeTwelveToneRow()
>>> type(r2)
<class 'music21.serial.TwelveToneRow'>
'''
pcSet = self.pitchClasses()
a = TwelveToneRow()
for thisPc in pcSet:
n = note.Note()
n.duration.quarterLength = 0.0
n.pitch.pitchClass = thisPc
n.pitch.octave = None
a.append(n)
return a
[docs]
def isSameRow(self, other):
'''
Convenience function describing if two rows are the same.
>>> row1 = serial.pcToToneRow([6, 7, 8])
>>> row2 = serial.pcToToneRow([-6, 19, 128])
>>> row3 = serial.pcToToneRow([6, 7, -8])
>>> row1.isSameRow(row2)
True
>>> row2.isSameRow(row1)
True
>>> row1.isSameRow(row3)
False
'''
if len(self) != len(other):
return False
for selfTone, otherTone in zip(self, other):
if selfTone.pitch.pitchClass != otherTone.pitch.pitchClass:
return False
return True
[docs]
def getIntervalsAsString(self):
'''
Returns the string of intervals between consecutive pitch classes of
a :class:`~music21.serial.ToneRow`.
'T' = 10, 'E' = 11.
>>> cRow = serial.pcToToneRow([0])
>>> cRow.getIntervalsAsString()
''
>>> reverseChromatic = serial.pcToToneRow([11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0])
>>> reverseChromatic.getIntervalsAsString()
'EEEEEEEEEEE'
'''
numPitches = len(self)
pitchList = self.pitchClasses()
intervalString = ''
for i in range(numPitches - 1):
interval = (pitchList[i + 1] - pitchList[i]) % 12
if interval in range(10):
intervalString = intervalString + str(interval)
if interval == 10:
intervalString = intervalString + 'T'
if interval == 11:
intervalString = intervalString + 'E'
return intervalString
# ----------------------------------------------------------------------------------------
[docs]
class TwelveToneRow(ToneRow):
'''
A Stream representation of a twelve-tone row, capable of producing a 12-tone matrix.
'''
_DOC_ORDER = ['matrix', 'isAllInterval',
'getLinkClassification', 'isLinkChord', 'areCombinatorial']
[docs]
def matrix(self):
# noinspection PyShadowingNames
'''
Returns a :class:`~music21.serial.TwelveToneMatrix` object for the row.
That object can just be printed (or displayed via .show()).
>>> src = serial.getHistoricalRowByName('SchoenbergOp37')
>>> [p.name for p in src]
['D', 'C#', 'A', 'B-', 'F', 'E-', 'E', 'C', 'G#', 'G', 'F#', 'B']
>>> len(src)
12
>>> s37 = serial.getHistoricalRowByName('SchoenbergOp37').matrix()
>>> print(s37)
0 B 7 8 3 1 2 A 6 5 4 9
1 0 8 9 4 2 3 B 7 6 5 A
5 4 0 1 8 6 7 3 B A 9 2
4 3 B 0 7 5 6 2 A 9 8 1
...
>>> [str(e.pitch) for e in s37[0]]
['C', 'B', 'G', 'G#', 'E-', 'C#', 'D', 'B-', 'F#', 'F', 'E', 'A']
'''
# note: do not want to return a TwelveToneRow() type, as this will
# add again the same pitches to the elements list twice.
noteList = self.getElementsByClass(note.Note)
i = [(12 - x.pitch.pitchClass) % 12 for x in noteList]
matrix = [[(x.pitch.pitchClass + trans) % 12 for x in noteList] for trans in i]
matrixObj = TwelveToneMatrix()
i = 0
for row in matrix:
i += 1
rowObject = self.__class__()
rowObject.mergeAttributes(self)
rowObject.id = 'row-' + str(i)
for p in row: # iterate over pitch class values
n = note.Note()
n.duration.quarterLength = 0.0
n.pitch.pitchClass = p
n.pitch.octave = None
rowObject.append(n)
matrixObj.insert(0, rowObject)
# environLocal.printDebug([
# 'calling matrix start: len row:', self.row, 'len self', len(self)])
return matrixObj
[docs]
def findHistorical(self):
'''
Checks if a given :class:`music21.serial.TwelveToneRow` is the same as
any of the historical
twelve-tone rows stored by music21: see :func:`music21.serial.getHistoricalRowByName`.
Returns a list of names of historical rows to which the input row is identical.
>>> row = serial.pcToToneRow([2, 3, 9, 1, 11, 5, 8, 7, 4, 0, 10, 6])
>>> row.findHistorical()
['SchoenbergOp32']
>>> chromatic = serial.pcToToneRow(range(12))
>>> chromatic.findHistorical()
[]
'''
sameRows = []
for historicalRow in historicalDict:
if self.isSameRow(getHistoricalRowByName(historicalRow)):
sameRows.append(historicalRow)
return sameRows
[docs]
def isAllInterval(self) -> bool:
'''
Describes whether a :class:`~music21.serial.TwelveToneRow` is an all-interval row.
>>> chromatic = serial.pcToToneRow(range(12))
>>> chromatic.pitchClasses()
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
>>> chromatic.isAllInterval()
False
>>> bergLyric = serial.getHistoricalRowByName('BergLyricSuite')
>>> bergLyric.pitchClasses()
[5, 4, 0, 9, 7, 2, 8, 1, 3, 6, 10, 11]
>>> bergLyric.isAllInterval()
True
'''
if self.isTwelveToneRow() is False:
raise SerialException('An all-interval row must be a twelve-tone row.')
tempAllInterval = True
intervalString = self.getIntervalsAsString()
for i in range(1, 10):
if str(i) not in intervalString:
tempAllInterval = False
if 'T' not in intervalString:
tempAllInterval = False
if 'E' not in intervalString:
tempAllInterval = False
return tempAllInterval
[docs]
def getLinkClassification(self):
'''
Gives the classification number of a Link Chord
(as given in http://www.johnlinkmusic.com/LinkChords.pdf),
that is, is an all-interval twelve-tone row containing a voicing of the
all-trichord hexachord: [0, 1, 2, 4, 7, 8].
In addition, gives a list of sets of five contiguous intervals
within the row representing a voicing
of the all-trichord hexachord. Note that the interval sets may be transformed.
Named for John Link who discovered them.
>>> bergLyric = serial.getHistoricalRowByName('BergLyricSuite')
>>> bergLyric.pitchClasses()
[5, 4, 0, 9, 7, 2, 8, 1, 3, 6, 10, 11]
>>> bergLyric.isAllInterval()
True
>>> bergLyric.getLinkClassification()
(None, [])
>>> link = serial.pcToToneRow([0, 3, 8, 2, 10, 11, 9, 4, 1, 5, 7, 6])
>>> link.getLinkClassification()
(62, ['8352E'])
>>> doubleLink = serial.pcToToneRow([0, 1, 8, 5, 7, 10, 4, 3, 11, 9, 2, 6])
>>> doubleLink.getLinkClassification()
(33, ['236E8', '36E8T'])
'''
# the link interval strings given below are untransformed: by inversion around 0,
# original-centered retrograde, and
# retrograde inversion around zero, we get three more link interval strings for
# each one in fullLinkIntervals.
# in the original rows, this corresponds simply to checking the interval string of any
# inversion, retrograde inversion, and retrograde, respectively.
fullLinkIntervals = ['125634T97E8', '134E78526T9', '134E79T6258', '134E79T6258',
'1367T89E254', '137E542896T', '137E982456T', '142965837ET',
'142973856ET', '1429738E65T', '14297TE6853', '145638E729T',
'1456T729E83', '1456T982E73', '145927E836T', '14598E63T72',
'14689T7E253', '1469E27T853', '149278356ET', '1492783E65T',
'1496258T73E', '1496E358T72', '14972836E5T', '14972E6385T',
'1497T853E62', '14E379T6528', '14E6T783529', '172E6853T94',
'17356ET8294', '1738T542E69', '173E65T8294', '1763T4952E8',
'176852E34T9', '179236E8T54', '179236E8T54', '17923E685T4',
'179245T8E63', '17924T586E3', '179T65234E8', '179T8E63254',
'179T8E63254', '1825E43796T', '1825E43796T', '1825E79T364',
'1825E79T364', '1852E43769T', '1852E79463T', '185629TE743',
'18563479TE2', '18563E7T492', '18734E5296T', '18734E5296T',
'187E259436T', '187E259436T', '18T352E7964', '18T497E3562',
'18T97E52364', '18T97E52364', '18E63T79452', '18E63T79452',
'19476538TE2', '1947T8536E2', '1954763T8E2', '19742538E6T',
'197425T6E83', '1974T8E6352', '197T8532E64', '1T5E7928364',
'1T63E874259', '1T6E3852479', '1T8352E4679', '1T974253E68',
'214367TE985', '214376598ET', '214376598ET', '2143E86597T',
'2149586E37T', '214976538ET', '214976538ET', '216734ET985',
'216743E9T85', '217634TE985', '21T8E956347', '21T96583E47',
'234T1596E87', '235189E647T', '235189E647T', '23689E7T145',
'236E981547T', '236E981547T', '23T514697E8', '2513647ET98',
'25189TE7463', '25189TE7463', '2546E981T73', '25691T8E473',
'2569E8T1437', '258T73E6149', '258T9614E37', '25T31496E87',
'25T89E61437', '2618T497E35', '26347ET9185', '263891T7E45',
'263891T7E45', '2653E718T49', '2654T1783E9', '2654T1783E9',
'2654E3871T9', '2654E3871T9', '2654E7T1983', '2654E7T1983',
'265819TE743', '2659E8T1347', '267431T8E95', '2694T817E35',
'269T1783E45', '269T1783E45', '269E3871T45', '269E3871T45',
'26E451T7389', '26E451T7389', '26E459817T3', '26E459817T3',
'26E4T718953', '26E4T718953', '26E873T5149', '26E95134T87',
'26E95178T43', '274316E985T', '274316E985T', '2743E86591T',
'274916E385T', '274916E385T', '2749586E31T', '276134ET985',
'276143E9T85', '27E34169T85', '2965387E41T', '2965387E41T',
'296E387T145', '29TE7436185', '29TE7463158', '29E7835641T',
'29E7835641T', '2E431T96587', '2E431T96587', '2E465387T19',
'2E637T85419', '2E783T51469', '2E783T51469', '2E796415T38',
'2E8T1956743', '2E9658T4137', '3142E8956T7', '3142ET79685',
'31456T972E8', '314672E9T85', '3152689TE74', '3152689TE74',
'3152E9T8764', '3158629TE74', '3158629TE74', '316452E98T7',
'317ET562894', '317ET926854', '317ET986254', '319765T42E8',
'3198265TE74', '31T524796E8', '31T567942E8', '31T7E294685',
'325E79T8164', '325E79T8164', '3265981TE74', '329E71T4568',
'329E71T4568', '32E981T6547', '347621T8E95', '3479TE26185',
'34T9E712568', '35146T927E8', '35146T927E8', '351T64927E8',
'351T64927E8', '351T7924E68', '35216E98T74', '3521T8E9674',
'3581629ET74', '3594T6127E8', '359E6128T74', '35E7216T498',
'35E72946T18', '35E729T6418', '3625189TE74', '3625189TE74',
'36524ET8917', '3674218T9E5', '36T154927E8', '36T154927E8',
'3764128T9E5', '38297E5T164', '38E729T6145', '3T17E924568',
'3T17E924568', '3T4952E8617', '3T6194527E8', '3T62E815497',
'3T97E528164', '3T97E528164', '3E2418596T7', '3E7T4926185',
'416352E7T98', '41T629E7835', '41T629E7835', '4328T56E917',
'4328TE65917', '43T9E865217', '463152E9T87', '46529E8T137',
'46731T8E925', '4692E513T87', '4692E513T87', '46982315ET7',
'469T315E287', '469T315E287', '4769E251T38', '4783T1629E5',
'47T198236E5', '47E928361T5', '4T1629E3785', '4TE86592317',
'4E2538T1697', '4E29658T317', '4E29658T317', '4ET85692317',
'4ET85692317', '5896T142E37']
specialLinkIntervals = [
'25634', '134E7', '134E7', 'E79T6', '89E25', '7E542', '7E982',
'29658', '856ET', '8E65T', 'E6853', '8E729', '729E8', '982E7',
'927E8', '8E63T', '7E253', '7T853', '78356', '783E6', '58T73',
'358T7', '2836E', '2E638', '7T853', '79T65', '78352', 'E6853',
'56ET8', '42E69', 'E65T8', '4952E', '52E34', '236E8', '36E8T',
'3E685', 'T8E63', '586E3', '79T65', 'T8E63', '8E632', '25E43',
'5E437', '25E79', '5E79T', 'E4376', 'E7946', '9TE74', '479TE',
'E7T49', '734E5', '34E52', '87E25', 'E2594', '352E7', 'T497E',
'T97E5', '97E52', '8E63T', 'T7945', '76538', '7T853', '4763T',
'97425', '97425', 'T8E63', '7T853', '5E792', '74259', '52479',
'8352E', '97425', '4367T', '43765', '76598', 'E8659', '586E3',
'49765', '76538', '6734E', '21674', '7634T', '1T8E9', 'T9658',
'34T15', '5189E', '189E6', '689E7', '6E981', 'E9815', '3T514',
'47ET9', '25189', '9TE74', '6E981', '91T8E', '9E8T1', '58T73',
'T9614', '5T314', '89E61', 'T497E', '47ET9', '891T7', '1T7E4',
'E718T', 'T1783', '1783E', 'E3871', '3871T', '4E7T1', '7T198',
'9TE74', '9E8T1', '1T8E9', 'T817E', 'T1783', '1783E', 'E3871',
'3871T', '451T7', '1T738', '59817', '9817T', 'T7189', '71895',
'3T514', '5134T', '5178T', '4316E', '16E98', 'E8659', '4916E',
'16E38', '586E3', '6134E', '27614', '4169T', '65387', '5387E',
'6E387', '9TE74', '9TE74', 'E7835', '78356', 'E431T', 'T9658',
'65387', '37T85', '2E783', '3T514', '415T3', 'E8T19', '9658T',
'3142E', '3142E', '56T97', '31467', '52689', '9TE74', '152E9',
'58629', '9TE74', '52E98', '56289', '92685', '98625', '765T4',
'98265', '52479', '56794', '31T7E', '25E79', '5E79T', '65981',
'29E71', '9E71T', '2E981', '1T8E9', '479TE', 'T9E71', '146T9',
'927E8', '1T649', '927E8', '51T79', '16E98', '1T8E9', '1629E',
'94T61', 'E6128', '16T49', '946T1', '9T641', '25189', '9TE74',
'36524', '36742', 'T1549', '927E8', '37641', '297E5', '8E729',
'T17E9', '17E92', '4952E', '61945', '15497', 'T97E5', '97E52',
'3E241', 'E7T49', '352E7', '629E7', 'E7835', '8T56E', '8TE65',
'9E865', '152E9', '9E8T1', '1T8E9', '2E513', 'E513T', '2315E',
'T315E', '315E2', '9E251', '1629E', '7T198', '8361T', '1629E',
'E8659', 'E2538', '29658', '9658T', 'T8569', '85692', '142E3']
linkClassification = [ # line breaks emphasize repetitions
1, 2, 3, 3,
4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,
18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
30, 31, 32, 33, 33,
34, 35, 36, 37, 38, 38,
39, 39,
40, 40, 41, 42, 43, 44, 45, 46, 46, 47, 47, 48, 49,
50, 50,
51, 51,
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,
64, 65, 65, 66, 67, 68, 68, 69, 70, 71, 72, 73, 74, 75, 75,
76, 77, 77,
78, 79, 80, 80,
81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 90,
91, 92, 92,
93, 93,
94, 94,
95, 96, 97, 98, 99, 99,
100, 100,
101, 101,
102, 102,
103, 103,
104, 105, 106, 107, 107, 108, 109, 109,
110, 111, 112, 113, 114, 114,
115, 116, 117, 118, 118,
119, 119,
120, 121, 122, 122,
123, 124, 125, 126, 127, 128, 129, 130, 130,
131, 132, 132,
133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 142,
143, 144, 144,
145, 146, 147, 148, 149, 149,
150, 150,
151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 160,
161, 162, 163, 163,
164, 165, 166, 167, 167, 168, 169, 170, 171, 171,
172, 173, 174, 175, 175,
176, 177, 178, 179, 180, 181, 182, 182,
183, 184, 184,
185, 186, 187, 188, 189, 190, 191, 192, 192,
193, 193,
194]
numChords = len(fullLinkIntervals)
if self.isTwelveToneRow() is False:
raise SerialException('A Link Chord must be a twelve-tone row.')
rowChecklist = [self,
self.zeroCenteredTransformation('I', 0),
self.zeroCenteredTransformation('R', 0),
self.zeroCenteredTransformation('RI', 0)]
specialIntervals = []
classification = None
for row in rowChecklist:
intervals = row.getIntervalsAsString()
for i in range(numChords):
if fullLinkIntervals[i] == intervals:
classification = linkClassification[i]
specialIntervals.append(specialLinkIntervals[i])
if not specialIntervals:
return None, []
else:
return classification, specialIntervals
[docs]
def isLinkChord(self) -> bool:
'''
Describes whether a :class:`~music21.serial.TwelveToneRow` is a Link Chord.
>>> bergLyric = serial.getHistoricalRowByName('BergLyricSuite')
>>> bergLyric.pitchClasses()
[5, 4, 0, 9, 7, 2, 8, 1, 3, 6, 10, 11]
>>> bergLyric.isAllInterval()
True
>>> bergLyric.isLinkChord()
False
>>> link = serial.pcToToneRow([0, 3, 8, 2, 10, 11, 9, 4, 1, 5, 7, 6])
>>> link.isLinkChord()
True
>>> doubleLink = serial.pcToToneRow([0, 1, 8, 5, 7, 10, 4, 3, 11, 9, 2, 6])
>>> doubleLink.isLinkChord()
True
'''
linkTuple = self.getLinkClassification()
if linkTuple[0] is None:
return False
else:
return True
[docs]
def areCombinatorial(self,
transType1: str,
index1: int,
transType2: str,
index2: int) -> bool:
'''
Describes whether two transformations of a twelve-tone row are combinatorial.
The first and second arguments describe one transformation, while the third and fourth
describe another.
First, let's take a row we know to have a combinatoriality pair:
>>> moses = serial.getHistoricalRowByName('SchoenbergMosesAron')
>>> moses.pitchClasses()
[9, 10, 4, 2, 3, 1, 7, 5, 6, 8, 11, 0]
Combinatoriality holds here between P0 and I3
>>> moses.areCombinatorial('P', 0, 'I', 3)
True
And a combinatorial pair like this between P0 and I3 will also hold
if you modify both rows in the same way, e.g.
if you transpose both by the same amount
>>> moses.areCombinatorial('P', 1, 'I', 4)
True
or if you retrograde both
>>> moses.areCombinatorial('R', 1, 'RI', 4)
True
Any modification made to one row form and not the other means all bets are off
>>> moses.areCombinatorial('R', 6, 'RI', 4)
False
* Changed in v7: `convention` is no longer necessary and no longer used.
Renamed to `unused_convention` and defaults None; to be removed in v8.
'''
if self.isTwelveToneRow() is False:
raise SerialException('Combinatoriality applies only to twelve-tone rows.')
# choice of convention does not matter
trans1 = self.zeroCenteredTransformation(transType1, index1)
trans2 = self.zeroCenteredTransformation(transType2, index2)
pitches1 = trans1.pitchClasses()
pitches2 = trans2.pitchClasses()
testRow = pitches1[:6] + pitches2[:6]
return pcToToneRow(testRow).isTwelveToneRow()
[docs]
class HistoricalTwelveToneRow(TwelveToneRow):
'''
Subclass of :class:`~music21.serial.TwelveToneRow` storing additional attributes of a
twelve-tone row used in the historical literature.
'''
_DOC_ATTR: dict[str, str] = {
'composer': 'The name of the composer, or None. (String)',
'opus': 'The opus of the work, or None. (String)',
'title': 'The title of the work, or None. (String)',
}
composer: None|str = None
opus: None|str = None
title: None|str = None
def __init__(self,
composer: None|str = None,
opus: None|str = None,
title: None|str = None,
row=None,
**keywords):
super().__init__(row, **keywords)
self.composer = composer
self.opus = opus
self.title = title
[docs]
def mergeAttributes(self, other):
super().mergeAttributes(other)
if not isinstance(other, HistoricalTwelveToneRow):
return
self.composer = other.composer
self.opus = other.opus
self.title = other.title
def _reprInternal(self):
return f'{self.composer} {self.opus} {self.title}'
[docs]
def getHistoricalRowByName(rowName):
'''
Given the name referring to a twelve-tone row used in the historical literature,
returns a :class:`~music21.serial.HistoricalTwelveToneRow` object with attributes
describing the row.
The names of the rows with stored attributes are below (each must be passed as a string,
in single quotes).
>>> for r in sorted(list(serial.historicalDict)):
... print(r)
BergChamberConcerto
BergDerWein
BergLulu
BergLuluActIIScene1
BergLuluActIScene20
BergLyricSuite
BergLyricSuitePerm
BergViolinConcerto
BergWozzeckPassacaglia
SchoenbergFragOrganSonata
SchoenbergFragPiano
SchoenbergFragPianoPhantasia
SchoenbergIsraelExists
SchoenbergJakobsleiter
SchoenbergMosesAron
SchoenbergOp23No5
SchoenbergOp24Mvmt4
SchoenbergOp24Mvmt5
SchoenbergOp25
SchoenbergOp26
SchoenbergOp27No1
SchoenbergOp27No2
SchoenbergOp27No3
SchoenbergOp27No4
SchoenbergOp28No1
SchoenbergOp28No3
SchoenbergOp29
SchoenbergOp30
SchoenbergOp31
SchoenbergOp32
SchoenbergOp33A
SchoenbergOp33B
SchoenbergOp34
SchoenbergOp35No1
SchoenbergOp35No2
SchoenbergOp35No3
SchoenbergOp35No5
SchoenbergOp36
SchoenbergOp37
SchoenbergOp41
SchoenbergOp42
SchoenbergOp44
SchoenbergOp45
SchoenbergOp46
SchoenbergOp47
SchoenbergOp48No1
SchoenbergOp48No2
SchoenbergOp48No3
SchoenbergOp50A
SchoenbergOp50B
SchoenbergOp50C
WebernOp17No2
WebernOp17No3
WebernOp18No1
WebernOp18No2
WebernOp18No3
WebernOp19No1
WebernOp19No2
WebernOp20
WebernOp21
WebernOp22
WebernOp23
WebernOp24
WebernOp25
WebernOp26
WebernOp27
WebernOp28
WebernOp29
WebernOp30
WebernOp31
WebernOpNo17No1
>>> web = serial.getHistoricalRowByName('WebernOp29')
>>> web.row
[3, 11, 2, 1, 5, 4, 7, 6, 10, 9, 0, 8]
>>> web.composer
'Webern'
>>> web.opus
'Op. 29'
>>> web.title
'Cantata I'
>>> web.isLinkChord()
False
NOTE: before v6, these rows had 'Row' in front of them,
like 'RowWebernOp29' instead of 'WebernOp29'. They can still be accessed
by the old name.
>>> rowWeb = serial.getHistoricalRowByName('RowWebernOp29')
>>> rowWeb.row == web.row
True
'''
if rowName.startswith('Row'):
rowName = rowName[3:]
if rowName in historicalDict:
attr = historicalDict[rowName]
rowObj = HistoricalTwelveToneRow(attr[0], attr[1], attr[2], attr[3])
return rowObj
else:
raise SerialException('No historical row with given name found')
# ------------------------------------------------------------------------------
[docs]
def pcToToneRow(pcSet):
# noinspection GrazieInspection
'''
A convenience function that, given a list of pitch classes represented as integers
and turns it into a :class:`~music21.serial.ToneRow` object.
>>> chromaticRow = serial.pcToToneRow([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
>>> chromaticRow.show('text')
{0.0} <music21.note.Note C>
{1.0} <music21.note.Note C#>
{2.0} <music21.note.Note D>
{3.0} <music21.note.Note E->
{4.0} <music21.note.Note E>
{5.0} <music21.note.Note F>
{6.0} <music21.note.Note F#>
{7.0} <music21.note.Note G>
{8.0} <music21.note.Note G#>
{9.0} <music21.note.Note A>
{10.0} <music21.note.Note B->
{11.0} <music21.note.Note B>
>>> matrixObj = chromaticRow.matrix()
>>> print(matrixObj)
0 1 2 3 4 5 6 7 8 9 A B
B 0 1 2 3 4 5 6 7 8 9 A
...
>>> fancyRow = serial.pcToToneRow([4, 5, 0, 6, 7, 2, 'a', 8, 9, 1, 'b', 3])
>>> matrixObj = fancyRow.matrix()
>>> print(matrixObj)
0 1 8 2 3 A 6 4 5 9 7 B
B 0 7 1 2 9 5 3 4 8 6 A
...
Note that the Row does not have to be a valid row to exist:
>>> multiSetRow = serial.pcToToneRow([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1])
>>> multiSetRow.pitchClasses()
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
Or even have 12 notes:
>>> shortRow = serial.pcToToneRow([3, 4])
>>> shortRow.pitchClasses()
[3, 4]
If the row does have twelve notes (whether unique or not) the object returned
is a `TwelveToneRow`:
>>> multiSetRow
<music21.serial.TwelveToneRow 111111111111>
Otherwise, it is simply a `ToneRow`:
>>> shortRow
<music21.serial.ToneRow 34>
'''
if len(pcSet) == 12:
a = TwelveToneRow()
else:
a = ToneRow()
for thisPc in pcSet:
n = note.Note()
n.pitch.pitchClass = thisPc
n.pitch.octave = None
a.append(n)
return a
[docs]
def rowToMatrix(p: list[int]) -> str:
# noinspection PyShadowingNames
'''
Takes a list of numbers and converts it to a string representation of a
12-tone matrix.
>>> aMatrix = serial.rowToMatrix([0, 2, 11, 7, 8, 3, 9, 1, 4, 10, 6, 5])
>>> print(aMatrix)
0 2 11 7 8 3 9 1 4 10 6 5
10 0 9 5 6 1 7 11 2 8 4 3
1 3 0 8 9 4 10 2 5 11 7 6
5 7 4 0 1 8 2 6 9 3 11 10
4 6 3 11 0 7 1 5 8 2 10 9
9 11 8 4 5 0 6 10 1 7 3 2
3 5 2 10 11 6 0 4 7 1 9 8
11 1 10 6 7 2 8 0 3 9 5 4
8 10 7 3 4 11 5 9 0 6 2 1
2 4 1 9 10 5 11 3 6 0 8 7
6 8 5 1 2 9 3 7 10 4 0 11
7 9 6 2 3 10 4 8 11 5 1 0
This function survives today (2020) because it's been
an example function for `music21` since the very first demonstrations
of predecessors from around 2000 onwards. Nowadays, better to create
a `TwelveToneRow()` object and then get a matrix from that:
>>> ttr = serial.TwelveToneRow([0, 2, 11, 7, 8, 3, 9, 1, 4, 10, 6, 5])
>>> matrix = ttr.matrix()
>>> print(matrix)
0 2 B 7 8 3 9 1 4 A 6 5
A 0 9 5 6 1 7 B 2 8 4 3
1 3 0 8 9 4 A 2 5 B 7 6
5 7 4 0 1 8 2 6 9 3 B A
4 6 3 B 0 7 1 5 8 2 A 9
9 B 8 4 5 0 6 A 1 7 3 2
3 5 2 A B 6 0 4 7 1 9 8
B 1 A 6 7 2 8 0 3 9 5 4
8 A 7 3 4 B 5 9 0 6 2 1
2 4 1 9 A 5 B 3 6 0 8 7
6 8 5 1 2 9 3 7 A 4 0 B
7 9 6 2 3 A 4 8 B 5 1 0
'''
i = [(12 - x) % 12 for x in p]
matrix = [[(x + trans) % 12 for x in p] for trans in i]
ret = []
for row in matrix:
msg = []
for thisPC in row:
msg.append(str(thisPC).rjust(3))
ret.append(''.join(msg))
return '\n'.join(ret)
# ------------------------------------------------------------------------------
class Test(unittest.TestCase):
def testMatrix(self):
src = getHistoricalRowByName('SchoenbergOp37')
self.assertEqual([p.name for p in src],
['D', 'C#', 'A', 'B-', 'F', 'E-', 'E', 'C', 'G#', 'G', 'F#', 'B'])
s37 = getHistoricalRowByName('SchoenbergOp37').matrix()
r0 = s37[0]
self.assertEqual([e.name for e in r0],
['C', 'B', 'G', 'G#', 'E-', 'C#', 'D', 'B-', 'F#', 'F', 'E', 'A'])
def testLabelingA(self):
from music21 import corpus
series = {'a': 1, 'g-': 2, 'g': 3, 'a-': 4,
'f': 5, 'e-': 6, 'e': 7, 'd': 8,
'c': 9, 'c#': 10, 'b-': 11, 'b': 12}
s = corpus.parse('bwv66.6')
for n in s.flatten().notes:
for key in series:
if n.pitch.pitchClass == pitch.Pitch(key).pitchClass:
n.addLyric(series[key])
match = []
for n in s.parts[0].flatten().notes:
match.append(n.lyric)
self.assertEqual(match, ['10', '12', '1', '12', '10', '7', '10', '12', '1', '10', '1',
'12', '4', '2', '1', '12', '12', '2', '7', '1', '12', '10',
'10', '1', '12', '10', '1', '4', '2', '4', '2', '2', '2',
'2', '2', '5', '2'])
# s.show()
def testHistorical(self):
nonRows = []
for historicalRow in historicalDict:
if getHistoricalRowByName(historicalRow).isTwelveToneRow() is False:
nonRows.append(historicalRow)
self.assertEqual(nonRows, [])
def testExtractRowParts(self):
'''
Was a problem in slices
'''
aRow = getHistoricalRowByName('BergViolinConcerto')
unused_aRow2 = aRow[0:3]
def testPostTonalDocs(self):
aRow = getHistoricalRowByName('BergViolinConcerto')
# aMatrix = aRow.matrix()
bStream = stream.Stream()
for i in range(0, 12, 3):
aRow2 = aRow[i:i + 3]
c = chord.Chord(aRow2)
c.addLyric(c.primeFormString)
c.addLyric(c.forteClass)
bStream.append(c)
def testCopyAndDeepcopy(self):
from music21.test.commonTest import testCopyAll
testCopyAll(self, globals())
# def testRows(self):
# from music21 import interval
#
# self.assertEqual(len(vienneseRows), 71)
#
# totalRows = 0
# cRows = 0
# for thisRow in vienneseRows:
# thisRow = thisRow()
# self.assertIsInstance(thisRow, TwelveToneRow)
#
# if thisRow.composer == 'Berg':
# continue
# post = thisRow.title
#
# totalRows += 1
# if thisRow[0].pitchClass == 0:
# cRows += 1
#
# if interval.Interval(thisRow[0],
# thisRow[6]).intervalClass == 6:
# # between element 1 and element 7 is there a TriTone?
# rowsWithTTRelations += 1
# ------------------------------------------------------------------------------
# define presented order in documentation
_DOC_ORDER = [ToneRow, TwelveToneRow, HistoricalTwelveToneRow,
pcToToneRow, TwelveToneMatrix, rowToMatrix, getHistoricalRowByName,
]
if __name__ == '__main__':
import music21
music21.mainTest(Test)