diff --git a/music21/clef.py b/music21/clef.py
index 00f5cdf0c2..5ac4429b22 100644
--- a/music21/clef.py
+++ b/music21/clef.py
@@ -1015,128 +1015,7 @@ def testCopyAndDeepcopy(self):
from music21.test.commonTest import testCopyAll
testCopyAll(self, globals())
- def testConversionClassMatch(self):
- from xml.etree.ElementTree import fromstring as El
- from music21.musicxml.xmlToM21 import MeasureParser
- from music21 import clef
- # need to get music21.clef.X, not X, because
- # we are comparing the result to a translation outside
- # clef.py
- src = [
- [('G', 1, 0), clef.FrenchViolinClef],
- [('G', 2, 0), clef.TrebleClef],
- [('G', 2, -1), clef.Treble8vbClef],
- [('G', 2, 1), clef.Treble8vaClef],
- [('G', 3, 0), clef.GSopranoClef],
- [('C', 1, 0), clef.SopranoClef],
- [('C', 2, 0), clef.MezzoSopranoClef],
- [('C', 3, 0), clef.AltoClef],
- [('C', 4, 0), clef.TenorClef],
- [('C', 5, 0), clef.CBaritoneClef],
- [('F', 3, 0), clef.FBaritoneClef],
- [('F', 4, 0), clef.BassClef],
- [('F', 4, 1), clef.Bass8vaClef],
- [('F', 4, -1), clef.Bass8vbClef],
- [('F', 5, 0), clef.SubBassClef],
- [('TAB', 5, 0), clef.TabClef]
- ]
-
- MP = MeasureParser()
-
- for params, className in src:
- sign, line, octaveChange = params
- mxClef = El(r''
- + sign + ''
- + str(line) + ''
- + ''
- + str(octaveChange)
- + '')
- c = MP.xmlToClef(mxClef)
-
- # environLocal.printDebug([type(c).__name__])
-
- self.assertEqual(c.sign, params[0])
- self.assertEqual(c.line, params[1])
- self.assertEqual(c.octaveChange, params[2])
- self.assertIsInstance(c, className,
- f'Failed Conversion of classes: {c} is not a {className}')
-
- def testContexts(self):
- from music21 import stream
- from music21 import note
- from music21 import meter
-
- n1 = note.Note('C')
- n1.offset = 10
- c1 = AltoClef()
- c1.offset = 0
- s1 = stream.Stream([c1, n1])
-
- self.assertIs(s1.recurse().notes[0].getContextByClass(Clef), c1)
- # equally good: getContextsByClass(Clef)[0]
-
- del s1
-
- n2 = note.Note('D')
- n2.duration.type = 'whole'
- n3 = note.Note('E')
- n3.duration.type = 'whole'
- ts1 = meter.TimeSignature('4/4')
- s2 = stream.Stream()
- s2.append(c1)
- s2.append(ts1)
- s2.append(n2)
- s2.append(n3)
- s2.makeMeasures()
- self.assertIs(n2.getContextByClass(Clef), c1)
-
- del s2
-
- n4 = note.Note('F')
- n4.duration.type = 'half'
- n5 = note.Note('G')
- n5.duration.type = 'half'
- n6 = note.Note('A')
- n6.duration.type = 'whole'
-
- ts2 = meter.TimeSignature('4/4')
- bc1 = BassClef()
- tc1 = TrebleClef()
-
- s3 = stream.Stream()
- s3.append(bc1)
- s3.append(ts2)
- s3.append(n4)
- s3.append(tc1)
- s3.append(n5)
- s3.append(n6)
- s3.makeMeasures()
-
- self.assertIs(n4.getContextByClass(stream.Measure), n5.getContextByClass(stream.Measure))
- self.assertIs(n4.getContextByClass(Clef), bc1)
- self.assertIs(n5.getContextByClass(Clef), tc1)
- self.assertIs(n6.getContextByClass(Clef), tc1)
-
- def testTabClefBeamDirections(self):
-
- from music21 import stream
- from music21 import clef
- from music21 import meter
- from music21 import note
-
- m = stream.Measure()
-
- n1 = note.Note(64, quarterLength=0.25)
- n2 = note.Note(67, quarterLength=0.25)
-
- m.append(clef.TabClef())
- m.append(meter.TimeSignature('4/4'))
- m.append(n1)
- m.append(n2)
- m.makeBeams(inPlace=True)
-
- self.assertEqual(m.notes[0].stemDirection, 'down')
-
+ # all other tests in test/test_clef
# ------------------------------------------------------------------------------
# define presented order in documentation
diff --git a/music21/expressions.py b/music21/expressions.py
index 0baed86a63..6625ca9e5b 100644
--- a/music21/expressions.py
+++ b/music21/expressions.py
@@ -26,7 +26,6 @@
import copy
import string
-import unittest
import typing as t
from music21 import base
@@ -37,6 +36,7 @@
from music21 import spanner
from music21 import style
+
if t.TYPE_CHECKING:
from music21 import note
@@ -1593,7 +1593,7 @@ def noteExtremes(self) -> tuple[t.Optional[note.Note],
(, )
'''
from music21 import chord
- from music21 import note
+ from music21 import note # pylint: disable=redefined-outer-name
notes = []
for n_or_ch in self:
if isinstance(n_or_ch, note.Note):
@@ -1604,168 +1604,7 @@ def noteExtremes(self) -> tuple[t.Optional[note.Note],
# ------------------------------------------------------------------------------
-class Test(unittest.TestCase):
-
- def testRealize(self):
- from music21 import note
- from music21 import stream
- n1 = note.Note('D4')
- n1.quarterLength = 4
- n1.expressions.append(WholeStepMordent())
- expList = realizeOrnaments(n1)
- st1 = stream.Stream()
- st1.append(expList)
- st1n = st1.notes
- self.assertEqual(st1n[0].name, 'D')
- self.assertEqual(st1n[0].quarterLength, 0.125)
- self.assertEqual(st1n[1].name, 'C')
- self.assertEqual(st1n[1].quarterLength, 0.125)
- self.assertEqual(st1n[2].name, 'D')
- self.assertEqual(st1n[2].quarterLength, 3.75)
-
- def testGetRepeatExpression(self):
- from music21 import expressions
-
- te = expressions.TextExpression('lightly')
- # no repeat expression is possible
- self.assertEqual(te.getRepeatExpression(), None)
-
- te = expressions.TextExpression('d.c.')
- self.assertEqual(str(te.getRepeatExpression()),
- "")
- re = te.getRepeatExpression()
- self.assertEqual(re.getTextExpression().content, 'd.c.')
-
- te = expressions.TextExpression('DC al coda')
- self.assertEqual(str(te.getRepeatExpression()),
- "")
- re = te.getRepeatExpression()
- self.assertEqual(re.getTextExpression().content, 'DC al coda')
-
- te = expressions.TextExpression('DC al fine')
- self.assertEqual(str(te.getRepeatExpression()),
- "")
- re = te.getRepeatExpression()
- self.assertEqual(re.getTextExpression().content, 'DC al fine')
-
- te = expressions.TextExpression('ds al coda')
- self.assertEqual(str(te.getRepeatExpression()),
- "")
- re = te.getRepeatExpression()
- self.assertEqual(re.getTextExpression().content, 'ds al coda')
-
- te = expressions.TextExpression('d.s. al fine')
- self.assertEqual(str(te.getRepeatExpression()),
- "")
- re = te.getRepeatExpression()
- self.assertEqual(re.getTextExpression().content, 'd.s. al fine')
-
- def testExpandTurns(self):
- from music21 import note
- from music21 import stream
- from music21 import clef
- from music21 import key
- from music21 import meter
- p1 = stream.Part()
- m1 = stream.Measure()
- m2 = stream.Measure()
- p1.append(clef.TrebleClef())
- p1.append(key.Key('F', 'major'))
- p1.append(meter.TimeSignature('2/4'))
- n1 = note.Note('C5', type='half')
- turn0 = Turn()
- n1.expressions.append(turn0)
- n2 = note.Note('B4', type='quarter')
- n2.duration.dots = 1
-
- n2.expressions.append(InvertedTurn())
- m1.append(n1)
- m2.append(key.KeySignature(5))
- m2.append(n2)
- m2.append(note.Rest('eighth'))
- p1.append(m1)
- p1.append(m2)
- realized1 = realizeOrnaments(n1)
- realized2 = realizeOrnaments(n2)
- self.assertEqual('C5 D5 C5 B-4 C5', ' '.join(n.pitch.nameWithOctave for n in realized1))
- self.assertEqual('B4 A#4 B4 C#5 B4', ' '.join(n.pitch.nameWithOctave for n in realized2))
- self.assertEqual(realized1[0].quarterLength, 1.0)
- self.assertEqual(realized1[1].quarterLength, 0.25)
- self.assertEqual(realized2[0].quarterLength, 0.5)
- self.assertEqual(realized2[1].quarterLength, 0.25)
-
- turn0.quarterLength = 0.125
- realized1b = realizeOrnaments(n1)
- self.assertEqual(realized1b[0].quarterLength, 1.5)
- self.assertEqual(realized1b[1].quarterLength, 0.125)
-
-
- def testExpandTrills(self):
- from music21 import note
- from music21 import stream
- from music21 import clef
- from music21 import key
- from music21 import meter
- p1 = stream.Part()
- m1 = stream.Measure()
- p1.append(clef.TrebleClef())
- p1.append(key.Key('D', 'major'))
- p1.append(meter.TimeSignature('1/4'))
- n1 = note.Note('E4', type='eighth')
- n1.expressions.append(Trill())
- m1.append(n1)
- p1.append(m1)
- realized = realizeOrnaments(n1)
- self.assertIsInstance(realized, list)
- self.assertEqual(len(realized), 4)
- self.assertIsInstance(realized[0], note.Note)
- self.assertEqual(realized[0].quarterLength, 0.125)
- self.assertEqual('E4 F#4 E4 F#4', ' '.join(n.pitch.nameWithOctave for n in realized))
-
-
- def testTrillExtensionA(self):
- '''Test basic wave line creation and output, as well as passing
- objects through make measure calls.
- '''
- from music21 import stream
- from music21 import note
- from music21 import chord
- from music21 import expressions
- from music21.musicxml import m21ToXml
- s = stream.Stream()
- s.repeatAppend(note.Note(), 12)
- n1 = s.notes[0]
- n2 = s.notes[-1]
- sp1 = expressions.TrillExtension(n1, n2)
- s.append(sp1)
- raw = m21ToXml.GeneralObjectExporter().parse(s)
- self.assertEqual(raw.count(b'wavy-line'), 2)
-
- s = stream.Stream()
- s.repeatAppend(chord.Chord(['c-3', 'g4']), 12)
- n1 = s.notes[0]
- n2 = s.notes[-1]
- sp1 = expressions.TrillExtension(n1, n2)
- s.append(sp1)
- raw = m21ToXml.GeneralObjectExporter().parse(s)
- # s.show()
- self.assertEqual(raw.count(b'wavy-line'), 2)
-
- def testUnpitchedUnsupported(self):
- from music21 import note
-
- unp = note.Unpitched()
- mord = Mordent()
- with self.assertRaises(TypeError):
- mord.realize(unp) # type: ignore
-
-
-# class TestExternal(unittest.TestCase):
-# def testCPEBachRealizeOrnaments(self):
-# from music21 import corpus
-# cpe = corpus.parse('cpebach/h186').parts[0].measures(1, 4)
-# cpe2 = cpe.realizeOrnaments()
-# cpe2.show()
+# Tests moved to test/test_expressions
# ------------------------------------------------------------------------------
@@ -1774,5 +1613,5 @@ def testUnpitchedUnsupported(self):
if __name__ == '__main__':
import music21
- music21.mainTest(Test)
+ music21.mainTest()
diff --git a/music21/graph/__init__.py b/music21/graph/__init__.py
index ab3d55b3c8..7d140ae37b 100644
--- a/music21/graph/__init__.py
+++ b/music21/graph/__init__.py
@@ -184,7 +184,7 @@ def testAll(self):
def testPlotChordsC(self):
from music21 import dynamics
from music21 import note
- from music21 import stream
+ from music21 import stream # pylint: disable=redefined-outer-name # only in TYPE_CHECKING
from music21 import scale
sc = scale.MajorScale('c4')
@@ -219,7 +219,7 @@ def testPlotChordsC(self):
def testHorizontalInstrumentationB(self):
from music21 import corpus
from music21 import dynamics
- from music21 import stream
+ from music21 import stream # pylint: disable=redefined-outer-name # only in TYPE_CHECKING
s = corpus.parse('bwv66.6')
dyn = ['p', 'mf', 'f', 'ff', 'mp', 'fff', 'ppp']
i = 0
diff --git a/music21/interval.py b/music21/interval.py
index 1ecbd8b1b4..be98ef2e60 100644
--- a/music21/interval.py
+++ b/music21/interval.py
@@ -26,7 +26,6 @@
import enum
import math
import re
-import unittest
import typing as t
from music21 import base
@@ -35,7 +34,13 @@
from music21.common.types import StepName
from music21 import environment
from music21 import exceptions21
-# from music21 import pitch # SHOULD NOT, b/c of enharmonics
+
+
+if t.TYPE_CHECKING:
+ from music21 import key
+ from music21 import note
+ from music21 import pitch
+
environLocal = environment.Environment('interval')
@@ -308,8 +313,8 @@ class IntervalException(exceptions21.Music21Exception):
# some utility functions
def _extractPitch(
- nOrP: t.Union['music21.note.Note', 'music21.pitch.Pitch']
-) -> 'music21.pitch.Pitch':
+ nOrP: t.Union[note.Note, pitch.Pitch]
+) -> pitch.Pitch:
'''
utility function to return either the object itself
or the `.pitch` if it's a Note.
@@ -637,7 +642,7 @@ def convertSemitoneToSpecifierGeneric(count: t.Union[int, float]) -> tuple[Speci
return convertSemitoneToSpecifierGenericMicrotone(count)[:2]
-_pythagorean_cache: dict[str, tuple['music21.pitch.Pitch', Fraction]] = {}
+_pythagorean_cache: dict[str, tuple[pitch.Pitch, Fraction]] = {}
def intervalToPythagoreanRatio(intervalObj: Interval) -> Fraction:
@@ -1416,8 +1421,8 @@ def transposePitch(self, p: 'music21.pitch.Pitch', *, inPlace=False):
def transposePitchKeyAware(
self,
- p: 'music21.pitch.Pitch',
- k: t.Optional['music21.key.KeySignature'] = None,
+ p: pitch.Pitch,
+ k: t.Optional[key.KeySignature] = None,
*,
inPlace: bool = False
):
@@ -1488,7 +1493,7 @@ def transposePitchKeyAware(
Does not take into account harmonic or melodic minor.
'''
- from music21 import pitch
+ from music21 import pitch # pylint: disable=redefined-outer-name # only in TYPE_CHECKING
if k is None:
return self.transposePitch(p, inPlace=inPlace)
@@ -2587,8 +2592,8 @@ def _stringToDiatonicChromatic(
def notesToGeneric(
- n1: t.Union['music21.pitch.Pitch', 'music21.note.Note'],
- n2: t.Union['music21.pitch.Pitch', 'music21.note.Note']
+ n1: t.Union[pitch.Pitch, note.Note],
+ n2: t.Union[pitch.Pitch, note.Note]
) -> GenericInterval:
'''
Given two :class:`~music21.note.Note` objects,
@@ -2615,8 +2620,8 @@ def notesToGeneric(
return GenericInterval(genDist)
def notesToChromatic(
- n1: t.Union['music21.pitch.Pitch', 'music21.note.Note'],
- n2: t.Union['music21.pitch.Pitch', 'music21.note.Note']
+ n1: t.Union[pitch.Pitch, note.Note],
+ n2: t.Union[pitch.Pitch, note.Note]
) -> ChromaticInterval:
'''
Given two :class:`~music21.note.Note` objects,
@@ -2982,18 +2987,18 @@ def __init__(self,
arg0: t.Union[str,
int,
float,
- 'music21.pitch.Pitch',
- 'music21.note.Note',
+ pitch.Pitch,
+ note.Note,
None] = None,
- arg1: t.Union['music21.pitch.Pitch', 'music21.note.Note', None] = None,
+ arg1: t.Union[pitch.Pitch, note.Note, None] = None,
/,
*,
diatonic: t.Optional[DiatonicInterval] = None,
chromatic: t.Optional[ChromaticInterval] = None,
- pitchStart: t.Optional['music21.pitch.Pitch'] = None,
- pitchEnd: t.Optional['music21.pitch.Pitch'] = None,
- noteStart: t.Union['music21.note.Note', 'music21.pitch.Pitch', None] = None,
- noteEnd: t.Union['music21.note.Note', 'music21.pitch.Pitch', None] = None,
+ pitchStart: t.Optional[pitch.Pitch] = None,
+ pitchEnd: t.Optional[pitch.Pitch] = None,
+ noteStart: t.Union[note.Note, pitch.Pitch, None] = None,
+ noteEnd: t.Union[note.Note, pitch.Pitch, None] = None,
name: t.Optional[str] = None,
**keywords):
super().__init__(**keywords)
@@ -3070,13 +3075,13 @@ def __init__(self,
self.chromatic: ChromaticInterval = chromatic
# these can be accessed through pitchStart and pitchEnd properties
- self._pitchStart: t.Optional['music21.pitch.Pitch'] = pitchStart
- self._pitchEnd: t.Optional['music21.pitch.Pitch'] = pitchEnd
+ self._pitchStart: t.Optional[pitch.Pitch] = pitchStart
+ self._pitchEnd: t.Optional[pitch.Pitch] = pitchEnd
self.intervalType: t.Literal['harmonic', 'melodic', ''] = ''
def _reprInternal(self):
- from music21 import pitch
+ from music21 import pitch # pylint: disable=redefined-outer-name # only in TYPE_CHECKING
try:
shift = self._diatonicIntervalCentShift()
except AttributeError:
@@ -3640,7 +3645,7 @@ def noteStart(self) -> t.Optional[music21.note.Note]:
if p and p._client:
return p._client
elif p:
- from music21 import note
+ from music21 import note # pylint: disable=redefined-outer-name
return note.Note(pitch=p)
@noteStart.setter
@@ -3660,7 +3665,7 @@ def noteEnd(self) -> t.Optional[music21.note.Note]:
if p and p._client:
return p._client
elif p:
- from music21 import note
+ from music21 import note # pylint: disable=redefined-outer-name
return note.Note(pitch=p)
@noteEnd.setter
@@ -3672,9 +3677,9 @@ def noteEnd(self, n: t.Optional[music21.note.Note]):
# ------------------------------------------------------------------------------
-def getWrittenHigherNote(note1: t.Union['music21.note.Note', 'music21.pitch.Pitch'],
- note2: t.Union['music21.note.Note', 'music21.pitch.Pitch']
- ) -> t.Union['music21.note.Note', 'music21.pitch.Pitch']:
+def getWrittenHigherNote(note1: t.Union[note.Note, pitch.Pitch],
+ note2: t.Union[note.Note, pitch.Pitch]
+ ) -> t.Union[note.Note, pitch.Pitch]:
'''
Given two :class:`~music21.pitch.Pitch` or :class:`~music21.note.Note` objects,
this function returns the higher element based on diatonic note
@@ -3710,9 +3715,9 @@ def getWrittenHigherNote(note1: t.Union['music21.note.Note', 'music21.pitch.Pitc
return getAbsoluteHigherNote(note1, note2)
-def getAbsoluteHigherNote(note1: t.Union['music21.note.Note', 'music21.pitch.Pitch'],
- note2: t.Union['music21.note.Note', 'music21.pitch.Pitch']
- ) -> t.Union['music21.note.Note', 'music21.pitch.Pitch']:
+def getAbsoluteHigherNote(note1: t.Union[note.Note, pitch.Pitch],
+ note2: t.Union[note.Note, pitch.Pitch]
+ ) -> t.Union[note.Note, pitch.Pitch]:
'''
Given two :class:`~music21.pitch.Pitch` or :class:`~music21.note.Note` objects,
returns the higher element based on sounding pitch.
@@ -3733,9 +3738,9 @@ def getAbsoluteHigherNote(note1: t.Union['music21.note.Note', 'music21.pitch.Pit
return note1
-def getWrittenLowerNote(note1: t.Union['music21.note.Note', 'music21.pitch.Pitch'],
- note2: t.Union['music21.note.Note', 'music21.pitch.Pitch']
- ) -> t.Union['music21.note.Note', 'music21.pitch.Pitch']:
+def getWrittenLowerNote(note1: t.Union[note.Note, pitch.Pitch],
+ note2: t.Union[note.Note, pitch.Pitch]
+ ) -> t.Union[note.Note, pitch.Pitch]:
'''
Given two :class:`~music21.pitch.Pitch` or :class:`~music21.note.Note` objects,
returns the lower element based on diatonic note
@@ -3765,9 +3770,9 @@ def getWrittenLowerNote(note1: t.Union['music21.note.Note', 'music21.pitch.Pitch
return getAbsoluteLowerNote(note1, note2)
-def getAbsoluteLowerNote(note1: t.Union['music21.note.Note', 'music21.pitch.Pitch'],
- note2: t.Union['music21.note.Note', 'music21.pitch.Pitch']
- ) -> t.Union['music21.note.Note', 'music21.pitch.Pitch']:
+def getAbsoluteLowerNote(note1: t.Union[note.Note, pitch.Pitch],
+ note2: t.Union[note.Note, pitch.Pitch]
+ ) -> t.Union[note.Note, pitch.Pitch]:
'''
Given two :class:`~music21.note.Note` or :class:`~music21.pitch.Pitch` objects, returns
the lower element based on actual pitch.
@@ -3914,7 +3919,7 @@ def add(intervalList):
>>> interval.add([P5, 'P-4'])
'''
- from music21 import pitch
+ from music21 import pitch # pylint: disable=redefined-outer-name # only in TYPE_CHECKING
if not intervalList:
raise IntervalException('Cannot add an empty set of intervals')
@@ -3962,7 +3967,7 @@ def subtract(intervalList):
-1
'''
- from music21 import pitch
+ from music21 import pitch # pylint: disable=redefined-outer-name # only in TYPE_CHECKING
if not intervalList:
raise IntervalException('Cannot add an empty set of intervals')
@@ -3980,470 +3985,9 @@ def subtract(intervalList):
# print(n1.nameWithOctave, n2.nameWithOctave)
return Interval(noteStart=n1, noteEnd=n2)
-# ------------------------------------------------------------------------------
-
-
-class Test(unittest.TestCase):
-
- def testConstructorPitches(self):
- from music21 import interval
- from music21 import pitch
- p1 = pitch.Pitch('A4')
- p2 = pitch.Pitch('E5')
- intv = interval.Interval(p1, p2)
- self.assertEqual(intv.name, 'P5')
- self.assertIs(intv.pitchStart, p1)
- self.assertIs(intv.pitchEnd, p2)
-
- # same with keywords, but reversed
- intv = interval.Interval(pitchStart=p2, pitchEnd=p1)
- self.assertEqual(intv.name, 'P5')
- self.assertEqual(intv.directedName, 'P-5')
- self.assertIs(intv.pitchStart, p2)
- self.assertIs(intv.pitchEnd, p1)
-
- def testFirst(self):
- from music21.note import Note
- from music21.pitch import Accidental
- n1 = Note()
- n2 = Note()
-
- n1.step = 'C'
- n1.octave = 4
-
- n2.step = 'B'
- n2.octave = 5
- n2.pitch.accidental = Accidental('-')
-
- int0 = Interval(noteStart=n1, noteEnd=n2)
- dInt0 = int0.diatonic
- gInt0 = dInt0.generic
-
- self.assertFalse(gInt0.isDiatonicStep)
- self.assertTrue(gInt0.isSkip)
-
- n1.pitch.accidental = Accidental('#')
- int1 = Interval(noteStart=n1, noteEnd=n2)
-
- cInt1 = notesToChromatic(n1, n2) # returns music21.interval.ChromaticInterval object
- cInt2 = int1.chromatic # returns same as cInt1 -- a different way of thinking of things
- self.assertEqual(cInt1.semitones, cInt2.semitones)
-
- self.assertEqual(int1.simpleNiceName, 'Diminished Seventh')
-
- self.assertEqual(int1.directedSimpleNiceName, 'Ascending Diminished Seventh')
- self.assertEqual(int1.name, 'd14')
- self.assertEqual(int1.specifier, Specifier.DIMINISHED)
-
- # returns same as gInt1 -- just a different way of thinking of things
- dInt1 = int1.diatonic
- gInt1 = dInt1.generic
-
- self.assertEqual(gInt1.directed, 14)
- self.assertEqual(gInt1.undirected, 14)
- self.assertEqual(gInt1.simpleDirected, 7)
- self.assertEqual(gInt1.simpleUndirected, 7)
-
- self.assertEqual(cInt1.semitones, 21)
- self.assertEqual(cInt1.undirected, 21)
- self.assertEqual(cInt1.mod12, 9)
- self.assertEqual(cInt1.intervalClass, 3)
-
- n4 = Note()
- n4.step = 'D'
- n4.octave = 3
- n4.pitch.accidental = '-'
-
- # n3 = interval.transposePitch(n4, 'AA8')
- # if n3.pitch.accidental is not None:
- # print(n3.step, n3.pitch.accidental.name, n3.octave)
- # else:
- # print(n3.step, n3.octave)
- # print(n3.name)
- # print()
-
- cI = ChromaticInterval(-14)
- self.assertEqual(cI.semitones, -14)
- self.assertEqual(cI.cents, -1400)
- self.assertEqual(cI.undirected, 14)
- self.assertEqual(cI.mod12, 10)
- self.assertEqual(cI.intervalClass, 2)
-
- lowB = Note()
- lowB.name = 'B'
- highBb = Note()
- highBb.name = 'B-'
- highBb.octave = 5
- dimOct = Interval(lowB, highBb)
- self.assertEqual(dimOct.niceName, 'Diminished Octave')
-
- noteA1 = Note()
- noteA1.name = 'E-'
- noteA1.octave = 4
- noteA2 = Note()
- noteA2.name = 'F#'
- noteA2.octave = 5
- intervalA1 = Interval(noteA1, noteA2)
-
- noteA3 = Note()
- noteA3.name = 'D'
- noteA3.octave = 1
-
- noteA4 = transposeNote(noteA3, intervalA1)
- self.assertEqual(noteA4.name, 'E#')
- self.assertEqual(noteA4.octave, 2)
-
- interval1 = Interval('-P5')
-
- n5 = transposeNote(n4, interval1)
- n6 = transposeNote(n4, 'P-5')
- self.assertEqual(n5.name, 'G-')
- self.assertEqual(n6.name, n5.name)
-
- # same thing using newer syntax:
-
- interval1 = Interval('P-5')
-
- n5 = transposeNote(n4, interval1)
- self.assertEqual(n5.name, 'G-')
- n7 = Note()
- n8 = transposeNote(n7, 'P8')
- self.assertEqual(n8.name, 'C')
- self.assertEqual(n8.octave, 5)
-
- n9 = transposeNote(n7, 'm7') # should be B-
- self.assertEqual(n9.name, 'B-')
- self.assertEqual(n9.octave, 4)
- n10 = transposeNote(n7, 'dd-2') # should be B##
- self.assertEqual(n10.name, 'B##')
- self.assertEqual(n10.octave, 3)
-
- # test getWrittenHigherNote functions
- (nE, nESharp, nFFlat, nF1, nF2) = (Note(), Note(), Note(), Note(), Note())
-
- nE.name = 'E'
- nESharp.name = 'E#'
- nFFlat.name = 'F-'
- nF1.name = 'F'
- nF2.name = 'F'
-
- higher1 = getWrittenHigherNote(nE, nESharp)
- higher2 = getWrittenHigherNote(nESharp, nFFlat)
- higher3 = getWrittenHigherNote(nF1, nF2)
-
- self.assertEqual(higher1, nESharp)
- self.assertEqual(higher2, nFFlat)
- self.assertEqual(higher3, nF1) # in case of ties, first is returned
-
- higher4 = getAbsoluteHigherNote(nE, nESharp)
- higher5 = getAbsoluteHigherNote(nESharp, nFFlat)
- higher6 = getAbsoluteHigherNote(nESharp, nF1)
- higher7 = getAbsoluteHigherNote(nF1, nESharp)
-
- self.assertEqual(higher4, nESharp)
- self.assertEqual(higher5, nESharp)
- self.assertEqual(higher6, nESharp)
- self.assertEqual(higher7, nF1)
-
- lower1 = getWrittenLowerNote(nESharp, nE)
- lower2 = getWrittenLowerNote(nFFlat, nESharp)
- lower3 = getWrittenLowerNote(nF1, nF2)
-
- self.assertEqual(lower1, nE)
- self.assertEqual(lower2, nESharp)
- self.assertEqual(lower3, nF1) # still returns first.
-
- lower4 = getAbsoluteLowerNote(nESharp, nE)
- lower5 = getAbsoluteLowerNote(nFFlat, nESharp)
- lower6 = getAbsoluteLowerNote(nESharp, nF1)
-
- self.assertEqual(lower4, nE)
- self.assertEqual(lower5, nFFlat)
- self.assertEqual(lower6, nESharp)
-
- middleC = Note()
- lowerC = Note()
- lowerC.octave = 3
- descendingOctave = Interval(middleC, lowerC)
- self.assertEqual(descendingOctave.generic.simpleDirected, 1)
- # no descending unisons ever
- self.assertEqual(descendingOctave.generic.semiSimpleDirected, -8)
- # no descending unisons ever
- self.assertEqual(descendingOctave.directedName, 'P-8')
- self.assertEqual(descendingOctave.directedSimpleName, 'P1')
-
- lowerG = Note()
- lowerG.name = 'G'
- lowerG.octave = 3
- descendingFourth = Interval(middleC, lowerG)
- self.assertEqual(descendingFourth.diatonic.directedNiceName,
- 'Descending Perfect Fourth')
- self.assertEqual(descendingFourth.diatonic.directedSimpleName, 'P-4')
- self.assertEqual(descendingFourth.diatonic.simpleName, 'P4')
- self.assertEqual(descendingFourth.diatonic.mod7, 'P5')
-
- perfectFifth = descendingFourth.complement
- self.assertEqual(perfectFifth.niceName, 'Perfect Fifth')
- self.assertEqual(perfectFifth.diatonic.simpleName, 'P5')
- self.assertEqual(perfectFifth.diatonic.mod7, 'P5')
- self.assertEqual(perfectFifth.complement.niceName, 'Perfect Fourth')
-
- def testCreateIntervalFromPitch(self):
- from music21 import pitch
- p1 = pitch.Pitch('c')
- p2 = pitch.Pitch('g')
- i = Interval(p1, p2)
- self.assertEqual(i.intervalClass, 5)
-
- def testTransposeImported(self):
-
- def collectAccidentalDisplayStatus(s_inner):
- post = []
- for e in s_inner.flatten().notes:
- if e.pitch.accidental is not None:
- post.append(e.pitch.accidental.displayStatus)
- else: # mark as not having an accidental
- post.append('x')
- return post
-
- from music21 import corpus
- s = corpus.parse('bach/bwv66.6')
- # this has accidentals in measures 2 and 6
- sSub = s.parts[3].measures(2, 6)
-
- self.assertEqual(collectAccidentalDisplayStatus(sSub),
- ['x', False, 'x', 'x', True, False, 'x', False, False, False,
- False, False, False, 'x', 'x', 'x', False, False, False,
- 'x', 'x', 'x', 'x', True, False])
-
- sTransposed = sSub.flatten().transpose('p5')
- # sTransposed.show()
-
- self.assertEqual(collectAccidentalDisplayStatus(sTransposed),
- ['x', None, 'x', 'x', None, None, None, None, None,
- None, None, None, None, 'x', None, None, None, None,
- None, 'x', 'x', 'x', None, None, None])
-
- def testIntervalMicrotonesA(self):
- from music21 import interval
- from music21 import pitch
-
- i = interval.Interval('m3')
- self.assertEqual(i.chromatic.cents, 300)
- self.assertEqual(i.cents, 300.0)
-
- i = interval.Interval('p5')
- self.assertEqual(i.chromatic.cents, 700)
- self.assertEqual(i.cents, 700.0)
-
- i = interval.Interval(8)
- self.assertEqual(i.chromatic.cents, 800)
- self.assertEqual(i.cents, 800.0)
-
- i = interval.Interval(8.5)
- self.assertEqual(i.chromatic.cents, 850.0)
- self.assertEqual(i.cents, 850.0)
-
- i = interval.Interval(5.25) # a sharp p4
- self.assertEqual(i.cents, 525.0)
- # we can subtract the two to get an offset
- self.assertEqual(i.cents, 525.0)
- self.assertEqual(str(i), '')
- self.assertEqual(i._diatonicIntervalCentShift(), 25)
-
- i = interval.Interval(4.75) # a flat p4
- self.assertEqual(str(i), '')
- self.assertEqual(i._diatonicIntervalCentShift(), -25)
-
- i = interval.Interval(4.48) # a sharp M3
- self.assertEqual(str(i), '')
- self.assertAlmostEqual(i._diatonicIntervalCentShift(), 48.0)
-
- i = interval.Interval(4.5) # a sharp M3
- self.assertEqual(str(i), '')
- self.assertAlmostEqual(i._diatonicIntervalCentShift(), 50.0)
-
- i = interval.Interval(5.25) # a sharp p4
- p1 = pitch.Pitch('c4')
- p2 = i.transposePitch(p1)
- self.assertEqual(str(p2), 'F4(+25c)')
-
- i = interval.Interval(5.80) # a sharp p4
- p1 = pitch.Pitch('c4')
- p2 = i.transposePitch(p1)
- self.assertEqual(str(p2), 'F#4(-20c)')
-
- i = interval.Interval(6.00) # an exact Tritone
- p1 = pitch.Pitch('c4')
- p2 = i.transposePitch(p1)
- self.assertEqual(str(p2), 'F#4')
-
- i = interval.Interval(5) # a chromatic p4
- p1 = pitch.Pitch('c4')
- p1.microtone = 10 # c+20
- self.assertEqual(str(p1), 'C4(+10c)')
- p2 = i.transposePitch(p1)
- self.assertEqual(str(p2), 'F4(+10c)')
-
- i = interval.Interval(7.20) # a sharp P5
- p1 = pitch.Pitch('c4')
- p1.microtone = -20 # c+20
- self.assertEqual(str(p1), 'C4(-20c)')
- p2 = i.transposePitch(p1)
- self.assertEqual(str(p2), 'G4')
-
- i = interval.Interval(7.20) # a sharp P5
- p1 = pitch.Pitch('c4')
- p1.microtone = 80 # c+20
- p2 = i.transposePitch(p1)
- self.assertEqual(str(p2), 'G#4')
-
- i = interval.Interval(0.20) # a sharp unison
- p1 = pitch.Pitch('e4')
- p1.microtone = 10
- p2 = i.transposePitch(p1)
- self.assertEqual(str(p2), 'E~4(-20c)')
-
- i = interval.Interval(0.05) # a tiny bit sharp unison
- p1 = pitch.Pitch('e4')
- p1.microtone = 5
- p2 = i.transposePitch(p1)
- self.assertEqual(str(p2), 'E4(+10c)')
-
- i = interval.Interval(12.05) # a tiny bit sharp octave
- p1 = pitch.Pitch('e4')
- p1.microtone = 5
- p2 = i.transposePitch(p1)
- self.assertEqual(str(p2), 'E5(+10c)')
-
- i = interval.Interval(11.85) # a flat octave
- p1 = pitch.Pitch('e4')
- p1.microtone = 5
- p2 = i.transposePitch(p1)
- self.assertEqual(str(p2), 'E5(-10c)')
-
- i = interval.Interval(11.85) # a flat octave
- p1 = pitch.Pitch('e4')
- p1.microtone = -20
- p2 = i.transposePitch(p1)
- self.assertEqual(str(p2), 'E`5(+15c)')
-
- def testIntervalMicrotonesB(self):
- from music21 import interval
- from music21 import note
- i = interval.Interval(note.Note('c4'), note.Note('c#4'))
- self.assertEqual(str(i), '')
-
- i = interval.Interval(note.Note('c4'), note.Note('c~4'))
- self.assertEqual(str(i), '')
-
- def testDescendingAugmentedUnison(self):
- from music21 import interval
- from music21 import note
- ns = note.Note('C4')
- ne = note.Note('C-4')
- i = interval.Interval(noteStart=ns, noteEnd=ne)
- directedNiceName = i.directedNiceName
- self.assertEqual(directedNiceName, 'Descending Diminished Unison')
-
- def testTransposeWithChromaticInterval(self):
- from music21 import interval
- from music21 import note
- ns = note.Note('C4')
- i = interval.ChromaticInterval(5)
- n2 = ns.transpose(i)
- self.assertEqual(n2.nameWithOctave, 'F4')
-
- ns = note.Note('B#3')
- i = interval.ChromaticInterval(5)
- n2 = ns.transpose(i)
- self.assertEqual(n2.nameWithOctave, 'F4')
-
- def testTransposeImplicit(self):
- from music21 import interval
- from music21 import pitch
- p = pitch.Pitch('D#4')
- i = interval.Interval(0)
- self.assertTrue(i.implicitDiatonic)
- p2 = i.transposePitch(p)
- self.assertEqual(p.ps, p2.ps)
- self.assertNotEqual(p.name, p2.name)
-
- def testRepeatedTransposePitch(self):
- from music21 import interval
- from music21 import pitch
- p = pitch.Pitch('C1')
- intv = interval.Interval('P5')
- out = []
- for _ in range(33):
- out.append(p.nameWithOctave)
- intv.transposePitch(p, maxAccidental=None, inPlace=True)
- self.assertEqual(
- out,
- ['C1', 'G1', 'D2', 'A2', 'E3', 'B3', 'F#4',
- 'C#5', 'G#5', 'D#6', 'A#6', 'E#7', 'B#7', 'F##8',
- 'C##9', 'G##9', 'D##10', 'A##10', 'E##11', 'B##11', 'F###12',
- 'C###13', 'G###13', 'D###14', 'A###14', 'E###15', 'B###15', 'F####16',
- 'C####17', 'G####17', 'D####18', 'A####18', 'E####19',
- ]
- )
- with self.assertRaisesRegex(pitch.AccidentalException,
- '5.0 is not a supported accidental type'):
- intv.transposePitch(p, maxAccidental=None)
- p2 = intv.transposePitch(p)
- self.assertEqual(p2.nameWithOctave, 'B-20')
-
-
- def testIntervalWithOneNoteGiven(self):
- from music21 import interval
- from music21 import note
- noteC = note.Note('C4')
- with self.assertRaises(ValueError):
- i = interval.Interval(name='P4', noteStart=noteC)
- i = interval.Interval(name='P4')
- i.noteStart = noteC
- self.assertEqual(i.noteEnd.nameWithOctave, 'F4')
- noteF = i.noteEnd
-
- # giving noteStart and noteEnd and a name where the name does not match
- # the notes is an exception.
- with self.assertRaises(ValueError):
- interval.Interval(name='d5', noteStart=noteC, noteEnd=noteF)
-
- # same with chromatic only intervals
- with self.assertRaises(ValueError):
- interval.Interval(chromatic=interval.ChromaticInterval(6),
- noteStart=noteC,
- noteEnd=noteF)
-
- # but these should work
- i2 = interval.Interval(name='P4', noteStart=noteC, noteEnd=noteF)
- self.assertIs(i2.noteStart, noteC)
- self.assertIs(i2.noteEnd, noteF)
- self.assertEqual(i2.name, 'P4')
- self.assertEqual(i2.semitones, 5)
-
- i3 = interval.Interval(chromatic=interval.ChromaticInterval(5),
- noteStart=noteC,
- noteEnd=noteF)
- self.assertIs(i3.noteStart, noteC)
- self.assertIs(i3.noteEnd, noteF)
- self.assertEqual(i3.name, 'P4')
- self.assertEqual(i3.semitones, 5)
-
-
- def testEmptyIntervalProperties(self):
- '''
- As of v8, an empty Interval is equal to P1
- '''
- empty = DiatonicInterval()
- self.assertEqual(empty.cents, 0.0)
-
- empty = Interval()
- self.assertEqual(empty.cents, 0.0)
- self.assertEqual(empty.intervalClass, 0)
- self.assertEqual(empty.name, 'P1')
+# ------------------------------------------------------------------------------
+# tests in test/test_interval
# ------------------------------------------------------------------------------
# define presented order in documentation
@@ -4453,8 +3997,7 @@ def testEmptyIntervalProperties(self):
if __name__ == '__main__':
- # sys.arg test options will be used in mainTest()
import music21
- music21.mainTest(Test)
+ music21.mainTest()
diff --git a/music21/note.py b/music21/note.py
index 1abb3edbfd..54a79d74a1 100644
--- a/music21/note.py
+++ b/music21/note.py
@@ -2071,345 +2071,6 @@ def testCopyAndDeepcopy(self):
from music21.test.commonTest import testCopyAll
testCopyAll(self, globals())
- def testLyricRepr(self):
- from music21 import note
- ly = note.Lyric()
- self.assertEqual(repr(ly), '')
- ly.text = 'hi'
- self.assertEqual(repr(ly), "")
- ly.identifier = 'verse'
- self.assertEqual(repr(ly), "")
- ly.text = None
- self.assertEqual(repr(ly), "")
-
- def testComplex(self):
- from music21 import note
- from music21.duration import DurationTuple
- note1 = note.Note()
- note1.duration.clear()
- d1 = DurationTuple('whole', 0, 4.0)
- d2 = DurationTuple('quarter', 0, 1.0)
- note1.duration.addDurationTuple(d1)
- note1.duration.addDurationTuple(d2)
- self.assertEqual(note1.duration.quarterLength, 5.0)
- self.assertEqual(note1.duration.componentIndexAtQtrPosition(2), 0)
- self.assertEqual(note1.duration.componentIndexAtQtrPosition(4), 1)
- self.assertEqual(note1.duration.componentIndexAtQtrPosition(4.5), 1)
- note1.duration.sliceComponentAtPosition(1.0)
-
- matchStr = "c'4~\nc'2.~\nc'4"
- from music21.lily.translate import LilypondConverter
- conv = LilypondConverter()
- conv.appendM21ObjectToContext(note1)
- outStr = str(conv.context).replace(' ', '').strip()
- # print(outStr)
- self.assertEqual(matchStr, outStr)
- i = 0
- for thisNote in note1.splitAtDurations():
- matchSub = matchStr.split('\n')[i] # pylint: disable=use-maxsplit-arg
- conv = LilypondConverter()
- conv.appendM21ObjectToContext(thisNote)
- outStr = str(conv.context).replace(' ', '').strip()
- self.assertEqual(matchSub, outStr)
- i += 1
-
- def testNote(self):
- from music21 import note
- note2 = note.Rest()
- self.assertTrue(note2.isRest)
- note3 = note.Note()
- note3.pitch.name = 'B-'
- # not sure how to test not None
- # self.assertFalse (note3.pitch.accidental, None)
- self.assertEqual(note3.pitch.accidental.name, 'flat')
- self.assertEqual(note3.pitch.pitchClass, 10)
-
- a5 = note.Note()
- a5.name = 'A'
- a5.octave = 5
- self.assertAlmostEqual(a5.pitch.frequency, 880.0)
- self.assertEqual(a5.pitch.pitchClass, 9)
-
- def testCopyNote(self):
- from music21 import note
- a = note.Note()
- a.quarterLength = 3.5
- a.name = 'D'
- b = copy.deepcopy(a)
- self.assertEqual(b.name, a.name)
-
- def testMusicXMLFermata(self):
- from music21 import corpus
- a = corpus.parse('bach/bwv5.7')
- found = []
- for n in a.flatten().notesAndRests:
- for obj in n.expressions:
- if isinstance(obj, expressions.Fermata):
- found.append(obj)
- self.assertEqual(len(found), 24)
-
- def testNoteBeatProperty(self):
- from music21 import meter
- from music21 import note
- from music21 import stream
-
- data = [
- ['3/4', 0.5, 6, [1.0, 1.5, 2.0, 2.5, 3.0, 3.5],
- [1.0] * 6, ],
- ['3/4', 0.25, 8, [1.0, 1.25, 1.5, 1.75, 2.0, 2.25, 2.5, 2.75],
- [1.0] * 8],
- ['3/2', 0.5, 8, [1.0, 1.25, 1.5, 1.75, 2.0, 2.25, 2.5, 2.75],
- [2.0] * 8],
-
- ['6/8', 0.5, 6, [1.0, 1.3333, 1.66666, 2.0, 2.3333, 2.666666],
- [1.5] * 6],
- ['9/8', 0.5, 6, [1.0, 1.3333, 1.66666, 2.0, 2.3333, 2.666666],
- [1.5] * 6],
- ['12/8', 0.5, 6, [1.0, 1.3333, 1.66666, 2.0, 2.3333, 2.666666],
- [1.5] * 6],
-
- ['6/16', 0.25, 6, [1.0, 1.3333, 1.66666, 2.0, 2.3333, 2.666666],
- [0.75] * 6],
-
- ['5/4', 1, 5, [1.0, 2.0, 3.0, 4.0, 5.0],
- [1.] * 5],
-
- ['2/8+3/8+2/8', 0.5, 6, [1.0, 1.5, 2.0, 2.33333, 2.66666, 3.0],
- [1., 1., 1.5, 1.5, 1.5, 1.]],
-
- ]
-
- # one measure case
- for tsStr, nQL, nCount, matchBeat, matchBeatDur in data:
- n = note.Note() # need fully qualified name
- n.quarterLength = nQL
- m = stream.Measure()
- m.timeSignature = meter.TimeSignature(tsStr)
- m.repeatAppend(n, nCount)
-
- self.assertEqual(len(m), nCount + 1)
-
- # test matching beat proportion value
- post = [m.notesAndRests[i].beat for i in range(nCount)]
- for i in range(len(matchBeat)):
- self.assertAlmostEqual(post[i], matchBeat[i], 4)
-
- # test getting beat duration
- post = [m.notesAndRests[i].beatDuration.quarterLength for i in range(nCount)]
-
- for i in range(len(matchBeat)):
- self.assertAlmostEqual(post[i], matchBeatDur[i], 4)
-
- # two measure case
- for tsStr, nQL, nCount, matchBeat, matchBeatDur in data:
- p = stream.Part()
- n = note.Note()
- n.quarterLength = nQL
-
- # m1 has time signature
- m1 = stream.Measure()
- m1.timeSignature = meter.TimeSignature(tsStr)
- p.append(m1)
-
- # m2 does not have time signature
- m2 = stream.Measure()
- m2.repeatAppend(n, nCount)
- self.assertEqual(len(m2), nCount)
- self.assertEqual(len(m2.notesAndRests), nCount)
-
- p.append(m2)
-
- # test matching beat proportion value
- post = [m2.notesAndRests[i].beat for i in range(nCount)]
- for i in range(len(matchBeat)):
- self.assertAlmostEqual(post[i], matchBeat[i], 4)
- # test getting beat duration
- post = [m2.notesAndRests[i].beatDuration.quarterLength for i in range(nCount)]
- for i in range(len(matchBeat)):
- self.assertAlmostEqual(post[i], matchBeatDur[i], 4)
-
- def testNoteBeatPropertyCorpus(self):
- data = [['bach/bwv255', [4.0, 1.0, 2.5, 3.0, 4.0, 4.5, 1.0, 1.5]],
- ['bach/bwv153.9', [1.0, 2.0, 3.0, 1.0, 2.0, 3.0, 1.0, 3.0, 1.0]]
- ]
-
- for work, match in data:
- from music21 import corpus
- s = corpus.parse(work)
- # always use tenor line
- found = []
- for n in s.parts[2].flatten().notesAndRests:
- n.lyric = n.beatStr
- found.append(n.beat)
-
- for i in range(len(match)):
- self.assertEqual(match[i], found[i])
-
- # s.show()
-
- def testNoteEquality(self):
- from music21 import articulations
- from music21 import note
-
- n1 = note.Note('A#')
- n2 = note.Note('G')
- n3 = note.Note('A-')
- n4 = note.Note('A#')
-
- self.assertNotEqual(n1, n2)
- self.assertNotEqual(n1, n3)
- self.assertEqual(n1, n4)
-
- # test durations with the same pitch
- for x, y, match in [
- (1, 1, True),
- (1, 0.5, False),
- (1, 2, False),
- (1, 1.5, False)
- ]:
- n1.quarterLength = x
- n4.quarterLength = y
- self.assertEqual(n1 == n4, match) # sub1
-
- # test durations with different pitch
- for x, y, match in [(1, 1, False), (1, 0.5, False),
- (1, 2, False), (1, 1.5, False)]:
- n1.quarterLength = x
- n2.quarterLength = y
- self.assertEqual(n1 == n2, match) # sub2
-
- # same pitches different octaves
- n1.quarterLength = 1.0
- n4.quarterLength = 1.0
- for x, y, match in [(4, 4, True), (3, 4, False), (2, 4, False)]:
- n1.pitch.octave = x
- n4.pitch.octave = y
- self.assertEqual(n1 == n4, match) # sub4
-
- # with and without ties
- n1.pitch.octave = 4
- n4.pitch.octave = 4
- t1 = tie.Tie()
- t2 = tie.Tie()
- for x, y, match in [(t1, None, False), (t1, t2, True)]:
- n1.tie = x
- n4.tie = y
- self.assertEqual(n1 == n4, match) # sub4
-
- # with ties but different pitches
- for n in [n1, n2, n3, n4]:
- n.quarterLength = 1.0
- t1 = tie.Tie()
- t2 = tie.Tie()
- for a, b, match in [(n1, n2, False), (n1, n3, False),
- (n2, n3, False), (n1, n4, True)]:
- a.tie = t1
- b.tie = t2
- self.assertEqual(a == b, match) # sub5
-
- # articulation groups
- a1 = [articulations.Accent()]
- a2 = [articulations.Accent(), articulations.StrongAccent()]
- a3 = [articulations.StrongAccent(), articulations.Accent()]
- a4 = [articulations.StrongAccent(), articulations.Accent(),
- articulations.Tenuto()]
- a5 = [articulations.Accent(), articulations.Tenuto(),
- articulations.StrongAccent()]
-
- for a, b, c, d, match in [(n1, n4, a1, a1, True),
- (n1, n2, a1, a1, False), (n1, n3, a1, a1, False),
- # same pitch different orderings
- (n1, n4, a2, a3, True), (n1, n4, a4, a5, True),
- # different pitch same orderings
- (n1, n2, a2, a3, False), (n1, n3, a4, a5, False),
- ]:
- a.articulations = c
- b.articulations = d
- self.assertEqual(a == b, match) # sub6
-
- def testMetricalAccent(self):
- from music21 import meter
- from music21 import note
- from music21 import stream
- data = [
- ('4/4', 8, 0.5, [1.0, 0.125, 0.25, 0.125, 0.5, 0.125, 0.25, 0.125]),
- ('3/4', 6, 0.5, [1.0, 0.25, 0.5, 0.25, 0.5, 0.25]),
- ('6/8', 6, 0.5, [1.0, 0.25, 0.25, 0.5, 0.25, 0.25]),
-
- ('12/32', 12, 0.125, [1.0, 0.125, 0.125, 0.25, 0.125, 0.125,
- 0.5, 0.125, 0.125, 0.25, 0.125, 0.125]),
-
- ('5/8', 10, 0.25, [1.0, 0.25, 0.5, 0.25, 0.5, 0.25, 0.5, 0.25, 0.5, 0.25]),
-
- # test notes that do not have defined accents
- ('4/4', 16, 0.25, [1.0, 0.0625, 0.125, 0.0625, 0.25, 0.0625, 0.125, 0.0625,
- 0.5, 0.0625, 0.125, 0.0625, 0.25, 0.0625, 0.125, 0.0625]),
- ('4/4', 32, 0.125, [1.0, 0.0625, 0.0625, 0.0625, 0.125, 0.0625, 0.0625, 0.0625,
- 0.25, 0.0625, 0.0625, 0.0625, 0.125, 0.0625, 0.0625, 0.0625,
- 0.5, 0.0625, 0.0625, 0.0625, 0.125, 0.0625, 0.0625, 0.0625,
- 0.25, 0.0625, 0.0625, 0.0625, 0.125, 0.0625, 0.0625, 0.0625]),
- ]
-
- for tsStr, nCount, dur, match in data:
- m = stream.Measure()
- m.timeSignature = meter.TimeSignature(tsStr)
- n = note.Note()
- n.quarterLength = dur
- m.repeatAppend(n, nCount)
-
- self.assertEqual([n.beatStrength for n in m.notesAndRests], match)
-
- def testTieContinue(self):
- from music21 import note
- from music21 import stream
-
- n1 = note.Note()
- n1.tie = tie.Tie()
- n1.tie.type = 'start'
-
- n2 = note.Note()
- n2.tie = tie.Tie()
- n2.tie.type = 'continue'
-
- n3 = note.Note()
- n3.tie = tie.Tie()
- n3.tie.type = 'stop'
-
- s = stream.Stream()
- s.append([n1, n2, n3])
-
- # need to test that this gets us a "continue" tie, but hard to test
- # post musicxml processing
- # s.show()
-
- def testVolumeA(self):
- from music21 import note
- v1 = volume.Volume()
-
- n1 = note.Note()
- n2 = note.Note()
-
- n1.volume = v1 # can set as v1 has no client
- self.assertEqual(n1.volume, v1)
- self.assertEqual(n1.volume.client, n1)
-
- # object is created on demand
- self.assertIsNot(n2.volume, v1)
- self.assertIsNotNone(n2.volume)
-
- def testVolumeB(self):
- from music21 import note
- # manage deepcopying properly
- n1 = note.Note()
-
- n1.volume.velocity = 100
- self.assertEqual(n1.volume.velocity, 100)
- self.assertEqual(n1.volume.client, n1)
-
- n1Copy = copy.deepcopy(n1)
- self.assertEqual(n1Copy.volume.velocity, 100)
- self.assertEqual(n1Copy.volume.client, n1Copy)
-
# ------------------------------------------------------------------------------
# define presented order in documentation
diff --git a/music21/pitch.py b/music21/pitch.py
index 99468cd246..068f17f1d7 100644
--- a/music21/pitch.py
+++ b/music21/pitch.py
@@ -5224,7 +5224,7 @@ def getStringHarmonic(self, chordIn):
'''
# Takes in a chord, finds the interval between the notes
- from music21 import note
+ from music21 import note # pylint: disable=redefined-outer-name # only in TYPE_CHECKING
from music21 import chord
pitchList = chordIn.pitches
diff --git a/music21/stream/core.py b/music21/stream/core.py
index fc6a9326bc..3cf68d8fcc 100644
--- a/music21/stream/core.py
+++ b/music21/stream/core.py
@@ -38,6 +38,10 @@
from music21.stream.iterator import StreamIterator, RecursiveIterator
+if t.TYPE_CHECKING:
+ from music21.stream import Stream
+
+
class StreamCore(Music21Object):
'''
Core aspects of a Stream's behavior. Any of these can change at any time.
@@ -259,7 +263,7 @@ def coreElementsChanged(
if self._derivation is not None:
sdm = self._derivation.method
if sdm in ('flat', 'semiflat'):
- origin: stream.Stream = self._derivation.origin
+ origin: Stream = self._derivation.origin
origin.clearCache()
# may not always need to clear cache of all living sites, but may
diff --git a/music21/test/__init__.py b/music21/test/__init__.py
index 2385f1d51f..ab56566290 100644
--- a/music21/test/__init__.py
+++ b/music21/test/__init__.py
@@ -1,7 +1,11 @@
from __future__ import annotations
from music21.test import test_base as base
+from music21.test import test_clef as clef
+from music21.test import test_expressions as expressions
+from music21.test import test_interval as interval
from music21.test import test_metadata as metadata
+from music21.test import test_note as note
from music21.test import test_pitch as pitch
from music21.test import test_repeat as repeat
diff --git a/music21/test/test_clef.py b/music21/test/test_clef.py
new file mode 100644
index 0000000000..5f5ba15352
--- /dev/null
+++ b/music21/test/test_clef.py
@@ -0,0 +1,126 @@
+from __future__ import annotations
+
+from xml.etree.ElementTree import fromstring as El
+import unittest
+
+from music21 import clef
+from music21 import meter
+from music21.musicxml.xmlToM21 import MeasureParser
+from music21 import note
+from music21 import stream
+
+
+class Test(unittest.TestCase):
+ def testConversionClassMatch(self):
+ # need to get music21.clef.X, not X, because
+ # we are comparing the result to a translation outside
+ # clef.py
+ src = [
+ [('G', 1, 0), clef.FrenchViolinClef],
+ [('G', 2, 0), clef.TrebleClef],
+ [('G', 2, -1), clef.Treble8vbClef],
+ [('G', 2, 1), clef.Treble8vaClef],
+ [('G', 3, 0), clef.GSopranoClef],
+ [('C', 1, 0), clef.SopranoClef],
+ [('C', 2, 0), clef.MezzoSopranoClef],
+ [('C', 3, 0), clef.AltoClef],
+ [('C', 4, 0), clef.TenorClef],
+ [('C', 5, 0), clef.CBaritoneClef],
+ [('F', 3, 0), clef.FBaritoneClef],
+ [('F', 4, 0), clef.BassClef],
+ [('F', 4, 1), clef.Bass8vaClef],
+ [('F', 4, -1), clef.Bass8vbClef],
+ [('F', 5, 0), clef.SubBassClef],
+ [('TAB', 5, 0), clef.TabClef]
+ ]
+
+ MP = MeasureParser()
+
+ for params, className in src:
+ sign, line, octaveChange = params
+ mxClef = El(r''
+ + sign + ''
+ + str(line) + ''
+ + ''
+ + str(octaveChange)
+ + '')
+ c = MP.xmlToClef(mxClef)
+
+ # environLocal.printDebug([type(c).__name__])
+
+ self.assertEqual(c.sign, params[0])
+ self.assertEqual(c.line, params[1])
+ self.assertEqual(c.octaveChange, params[2])
+ self.assertIsInstance(c, className,
+ f'Failed Conversion of classes: {c} is not a {className}')
+
+ def testContexts(self):
+ n1 = note.Note('C')
+ n1.offset = 10
+ c1 = clef.AltoClef()
+ c1.offset = 0
+ s1 = stream.Stream([c1, n1])
+
+ self.assertIs(s1.recurse().notes[0].getContextByClass(clef.Clef), c1)
+ # equally good: getContextsByClass(Clef)[0]
+
+ del s1
+
+ n2 = note.Note('D')
+ n2.duration.type = 'whole'
+ n3 = note.Note('E')
+ n3.duration.type = 'whole'
+ ts1 = meter.TimeSignature('4/4')
+ s2 = stream.Stream()
+ s2.append(c1)
+ s2.append(ts1)
+ s2.append(n2)
+ s2.append(n3)
+ s2.makeMeasures()
+ self.assertIs(n2.getContextByClass(clef.Clef), c1)
+
+ del s2
+
+ n4 = note.Note('F')
+ n4.duration.type = 'half'
+ n5 = note.Note('G')
+ n5.duration.type = 'half'
+ n6 = note.Note('A')
+ n6.duration.type = 'whole'
+
+ ts2 = meter.TimeSignature('4/4')
+ bc1 = clef.BassClef()
+ tc1 = clef.TrebleClef()
+
+ s3 = stream.Stream()
+ s3.append(bc1)
+ s3.append(ts2)
+ s3.append(n4)
+ s3.append(tc1)
+ s3.append(n5)
+ s3.append(n6)
+ s3.makeMeasures()
+
+ self.assertIs(n4.getContextByClass(stream.Measure), n5.getContextByClass(stream.Measure))
+ self.assertIs(n4.getContextByClass(clef.Clef), bc1)
+ self.assertIs(n5.getContextByClass(clef.Clef), tc1)
+ self.assertIs(n6.getContextByClass(clef.Clef), tc1)
+
+ def testTabClefBeamDirections(self):
+ m = stream.Measure()
+
+ n1 = note.Note(64, quarterLength=0.25)
+ n2 = note.Note(67, quarterLength=0.25)
+
+ m.append(clef.TabClef())
+ m.append(meter.TimeSignature('4/4'))
+ m.append(n1)
+ m.append(n2)
+ m.makeBeams(inPlace=True)
+
+ self.assertEqual(m.notes[0].stemDirection, 'down')
+
+
+if __name__ == '__main__':
+ import music21
+ music21.mainTest(Test)
diff --git a/music21/test/test_expressions.py b/music21/test/test_expressions.py
new file mode 100644
index 0000000000..ca89b0420a
--- /dev/null
+++ b/music21/test/test_expressions.py
@@ -0,0 +1,161 @@
+
+from __future__ import annotations
+
+import unittest
+
+from music21 import chord
+from music21 import clef
+from music21 import expressions
+from music21 import key
+from music21 import meter
+from music21.musicxml import m21ToXml
+from music21 import note
+from music21 import stream
+
+
+class Test(unittest.TestCase):
+
+ def testRealize(self):
+ n1 = note.Note('D4')
+ n1.quarterLength = 4
+ n1.expressions.append(expressions.WholeStepMordent())
+ expList = expressions.realizeOrnaments(n1)
+ st1 = stream.Stream()
+ st1.append(expList)
+ st1n = st1.notes
+ self.assertEqual(st1n[0].name, 'D')
+ self.assertEqual(st1n[0].quarterLength, 0.125)
+ self.assertEqual(st1n[1].name, 'C')
+ self.assertEqual(st1n[1].quarterLength, 0.125)
+ self.assertEqual(st1n[2].name, 'D')
+ self.assertEqual(st1n[2].quarterLength, 3.75)
+
+ def testGetRepeatExpression(self):
+ te = expressions.TextExpression('lightly')
+ # no repeat expression is possible
+ self.assertEqual(te.getRepeatExpression(), None)
+
+ te = expressions.TextExpression('d.c.')
+ self.assertEqual(str(te.getRepeatExpression()),
+ "")
+ re = te.getRepeatExpression()
+ self.assertEqual(re.getTextExpression().content, 'd.c.')
+
+ te = expressions.TextExpression('DC al coda')
+ self.assertEqual(str(te.getRepeatExpression()),
+ "")
+ re = te.getRepeatExpression()
+ self.assertEqual(re.getTextExpression().content, 'DC al coda')
+
+ te = expressions.TextExpression('DC al fine')
+ self.assertEqual(str(te.getRepeatExpression()),
+ "")
+ re = te.getRepeatExpression()
+ self.assertEqual(re.getTextExpression().content, 'DC al fine')
+
+ te = expressions.TextExpression('ds al coda')
+ self.assertEqual(str(te.getRepeatExpression()),
+ "")
+ re = te.getRepeatExpression()
+ self.assertEqual(re.getTextExpression().content, 'ds al coda')
+
+ te = expressions.TextExpression('d.s. al fine')
+ self.assertEqual(str(te.getRepeatExpression()),
+ "")
+ re = te.getRepeatExpression()
+ self.assertEqual(re.getTextExpression().content, 'd.s. al fine')
+
+ def testExpandTurns(self):
+ p1 = stream.Part()
+ m1 = stream.Measure()
+ m2 = stream.Measure()
+ p1.append(clef.TrebleClef())
+ p1.append(key.Key('F', 'major'))
+ p1.append(meter.TimeSignature('2/4'))
+ n1 = note.Note('C5', type='half')
+ turn0 = expressions.Turn()
+ n1.expressions.append(turn0)
+ n2 = note.Note('B4', type='quarter')
+ n2.duration.dots = 1
+
+ n2.expressions.append(expressions.InvertedTurn())
+ m1.append(n1)
+ m2.append(key.KeySignature(5))
+ m2.append(n2)
+ m2.append(note.Rest('eighth'))
+ p1.append(m1)
+ p1.append(m2)
+ realized1 = expressions.realizeOrnaments(n1)
+ realized2 = expressions.realizeOrnaments(n2)
+ self.assertEqual('C5 D5 C5 B-4 C5', ' '.join(n.pitch.nameWithOctave for n in realized1))
+ self.assertEqual('B4 A#4 B4 C#5 B4', ' '.join(n.pitch.nameWithOctave for n in realized2))
+ self.assertEqual(realized1[0].quarterLength, 1.0)
+ self.assertEqual(realized1[1].quarterLength, 0.25)
+ self.assertEqual(realized2[0].quarterLength, 0.5)
+ self.assertEqual(realized2[1].quarterLength, 0.25)
+
+ turn0.quarterLength = 0.125
+ realized1b = expressions.realizeOrnaments(n1)
+ self.assertEqual(realized1b[0].quarterLength, 1.5)
+ self.assertEqual(realized1b[1].quarterLength, 0.125)
+
+
+ def testExpandTrills(self):
+ p1 = stream.Part()
+ m1 = stream.Measure()
+ p1.append(clef.TrebleClef())
+ p1.append(key.Key('D', 'major'))
+ p1.append(meter.TimeSignature('1/4'))
+ n1 = note.Note('E4', type='eighth')
+ n1.expressions.append(expressions.Trill())
+ m1.append(n1)
+ p1.append(m1)
+ realized = expressions.realizeOrnaments(n1)
+ self.assertIsInstance(realized, list)
+ self.assertEqual(len(realized), 4)
+ self.assertIsInstance(realized[0], note.Note)
+ self.assertEqual(realized[0].quarterLength, 0.125)
+ self.assertEqual('E4 F#4 E4 F#4', ' '.join(n.pitch.nameWithOctave for n in realized))
+
+
+ def testTrillExtensionA(self):
+ '''Test basic wave line creation and output, as well as passing
+ objects through make measure calls.
+ '''
+ s = stream.Stream()
+ s.repeatAppend(note.Note(), 12)
+ n1 = s.notes[0]
+ n2 = s.notes[-1]
+ sp1 = expressions.TrillExtension(n1, n2)
+ s.append(sp1)
+ raw = m21ToXml.GeneralObjectExporter().parse(s)
+ self.assertEqual(raw.count(b'wavy-line'), 2)
+
+ s = stream.Stream()
+ s.repeatAppend(chord.Chord(['c-3', 'g4']), 12)
+ n1 = s.notes[0]
+ n2 = s.notes[-1]
+ sp1 = expressions.TrillExtension(n1, n2)
+ s.append(sp1)
+ raw = m21ToXml.GeneralObjectExporter().parse(s)
+ # s.show()
+ self.assertEqual(raw.count(b'wavy-line'), 2)
+
+ def testUnpitchedUnsupported(self):
+ unp = note.Unpitched()
+ mord = expressions.Mordent()
+ with self.assertRaises(TypeError):
+ mord.realize(unp) # type: ignore
+
+
+# class TestExternal(unittest.TestCase):
+# def testCPEBachRealizeOrnaments(self):
+# from music21 import corpus
+# cpe = corpus.parse('cpebach/h186').parts[0].measures(1, 4)
+# cpe2 = cpe.realizeOrnaments()
+# cpe2.show()
+
+
+if __name__ == '__main__':
+ import music21
+ music21.mainTest(Test)
diff --git a/music21/test/test_interval.py b/music21/test/test_interval.py
new file mode 100644
index 0000000000..d222181f77
--- /dev/null
+++ b/music21/test/test_interval.py
@@ -0,0 +1,453 @@
+from __future__ import annotations
+
+import unittest
+
+from music21 import corpus
+from music21 import interval
+from music21 import note
+from music21 import pitch
+
+
+class Test(unittest.TestCase):
+
+ def testConstructorPitches(self):
+ p1 = pitch.Pitch('A4')
+ p2 = pitch.Pitch('E5')
+ intv = interval.Interval(p1, p2)
+ self.assertEqual(intv.name, 'P5')
+ self.assertIs(intv.pitchStart, p1)
+ self.assertIs(intv.pitchEnd, p2)
+
+ # same with keywords, but reversed
+ intv = interval.Interval(pitchStart=p2, pitchEnd=p1)
+ self.assertEqual(intv.name, 'P5')
+ self.assertEqual(intv.directedName, 'P-5')
+ self.assertIs(intv.pitchStart, p2)
+ self.assertIs(intv.pitchEnd, p1)
+
+ def testFirst(self):
+ n1 = note.Note()
+ n2 = note.Note()
+
+ n1.step = 'C'
+ n1.octave = 4
+
+ n2.step = 'B'
+ n2.octave = 5
+ n2.pitch.accidental = pitch.Accidental('-')
+
+ int0 = interval.Interval(noteStart=n1, noteEnd=n2)
+ dInt0 = int0.diatonic
+ gInt0 = dInt0.generic
+
+ self.assertFalse(gInt0.isDiatonicStep)
+ self.assertTrue(gInt0.isSkip)
+
+ n1.pitch.accidental = pitch.Accidental('#')
+ int1 = interval.Interval(noteStart=n1, noteEnd=n2)
+
+ # returns music21.interval.ChromaticInterval object
+ cInt1 = interval.notesToChromatic(n1, n2)
+ self.assertIsInstance(cInt1, interval.ChromaticInterval)
+ cInt2 = int1.chromatic # returns same as cInt1 -- a different way of thinking of things
+ self.assertEqual(cInt1.semitones, cInt2.semitones)
+
+ self.assertEqual(int1.simpleNiceName, 'Diminished Seventh')
+
+ self.assertEqual(int1.directedSimpleNiceName, 'Ascending Diminished Seventh')
+ self.assertEqual(int1.name, 'd14')
+ self.assertEqual(int1.specifier, interval.Specifier.DIMINISHED)
+
+ # returns same as gInt1 -- just a different way of thinking of things
+ dInt1 = int1.diatonic
+ gInt1 = dInt1.generic
+
+ self.assertEqual(gInt1.directed, 14)
+ self.assertEqual(gInt1.undirected, 14)
+ self.assertEqual(gInt1.simpleDirected, 7)
+ self.assertEqual(gInt1.simpleUndirected, 7)
+
+ self.assertEqual(cInt1.semitones, 21)
+ self.assertEqual(cInt1.undirected, 21)
+ self.assertEqual(cInt1.mod12, 9)
+ self.assertEqual(cInt1.intervalClass, 3)
+
+ n4 = note.Note()
+ n4.step = 'D'
+ n4.octave = 3
+ n4.pitch.accidental = '-'
+
+ # n3 = interval.transposePitch(n4, 'AA8')
+ # if n3.pitch.accidental is not None:
+ # print(n3.step, n3.pitch.accidental.name, n3.octave)
+ # else:
+ # print(n3.step, n3.octave)
+ # print(n3.name)
+ # print()
+
+ cI = interval.ChromaticInterval(-14)
+ self.assertEqual(cI.semitones, -14)
+ self.assertEqual(cI.cents, -1400)
+ self.assertEqual(cI.undirected, 14)
+ self.assertEqual(cI.mod12, 10)
+ self.assertEqual(cI.intervalClass, 2)
+
+ lowB = note.Note()
+ lowB.name = 'B'
+ highBb = note.Note()
+ highBb.name = 'B-'
+ highBb.octave = 5
+ dimOct = interval.Interval(lowB, highBb)
+ self.assertEqual(dimOct.niceName, 'Diminished Octave')
+
+ noteA1 = note.Note()
+ noteA1.name = 'E-'
+ noteA1.octave = 4
+ noteA2 = note.Note()
+ noteA2.name = 'F#'
+ noteA2.octave = 5
+ intervalA1 = interval.Interval(noteA1, noteA2)
+
+ noteA3 = note.Note()
+ noteA3.name = 'D'
+ noteA3.octave = 1
+
+ noteA4 = interval.transposeNote(noteA3, intervalA1)
+ self.assertEqual(noteA4.name, 'E#')
+ self.assertEqual(noteA4.octave, 2)
+
+ interval1 = interval.Interval('-P5')
+
+ n5 = interval.transposeNote(n4, interval1)
+ n6 = interval.transposeNote(n4, 'P-5')
+ self.assertEqual(n5.name, 'G-')
+ self.assertEqual(n6.name, n5.name)
+
+ # same thing using newer syntax:
+
+ interval1 = interval.Interval('P-5')
+
+ n5 = interval.transposeNote(n4, interval1)
+ self.assertEqual(n5.name, 'G-')
+ n7 = note.Note()
+ n8 = interval.transposeNote(n7, 'P8')
+ self.assertEqual(n8.name, 'C')
+ self.assertEqual(n8.octave, 5)
+
+ n9 = interval.transposeNote(n7, 'm7') # should be B-
+ self.assertEqual(n9.name, 'B-')
+ self.assertEqual(n9.octave, 4)
+ n10 = interval.transposeNote(n7, 'dd-2') # should be B##
+ self.assertEqual(n10.name, 'B##')
+ self.assertEqual(n10.octave, 3)
+
+ # test getWrittenHigherNote functions
+ nE = note.Note('E')
+ nESharp = note.Note('E#')
+ nFFlat = note.Note('F-')
+ nF1 = note.Note('F')
+ nF2 = note.Note('F')
+
+ higher1 = interval.getWrittenHigherNote(nE, nESharp)
+ higher2 = interval.getWrittenHigherNote(nESharp, nFFlat)
+ higher3 = interval.getWrittenHigherNote(nF1, nF2)
+
+ self.assertEqual(higher1, nESharp)
+ self.assertEqual(higher2, nFFlat)
+ self.assertEqual(higher3, nF1) # in case of ties, first is returned
+
+ higher4 = interval.getAbsoluteHigherNote(nE, nESharp)
+ higher5 = interval.getAbsoluteHigherNote(nESharp, nFFlat)
+ higher6 = interval.getAbsoluteHigherNote(nESharp, nF1)
+ higher7 = interval.getAbsoluteHigherNote(nF1, nESharp)
+
+ self.assertEqual(higher4, nESharp)
+ self.assertEqual(higher5, nESharp)
+ self.assertEqual(higher6, nESharp)
+ self.assertEqual(higher7, nF1)
+
+ lower1 = interval.getWrittenLowerNote(nESharp, nE)
+ lower2 = interval.getWrittenLowerNote(nFFlat, nESharp)
+ lower3 = interval.getWrittenLowerNote(nF1, nF2)
+
+ self.assertEqual(lower1, nE)
+ self.assertEqual(lower2, nESharp)
+ self.assertEqual(lower3, nF1) # still returns first.
+
+ lower4 = interval.getAbsoluteLowerNote(nESharp, nE)
+ lower5 = interval.getAbsoluteLowerNote(nFFlat, nESharp)
+ lower6 = interval.getAbsoluteLowerNote(nESharp, nF1)
+
+ self.assertEqual(lower4, nE)
+ self.assertEqual(lower5, nFFlat)
+ self.assertEqual(lower6, nESharp)
+
+ middleC = note.Note()
+ lowerC = note.Note()
+ lowerC.octave = 3
+ descendingOctave = interval.Interval(middleC, lowerC)
+ self.assertEqual(descendingOctave.generic.simpleDirected, 1)
+ # no descending unisons ever
+ self.assertEqual(descendingOctave.generic.semiSimpleDirected, -8)
+ # no descending unisons ever
+ self.assertEqual(descendingOctave.directedName, 'P-8')
+ self.assertEqual(descendingOctave.directedSimpleName, 'P1')
+
+ lowerG = note.Note()
+ lowerG.name = 'G'
+ lowerG.octave = 3
+ descendingFourth = interval.Interval(middleC, lowerG)
+ self.assertEqual(descendingFourth.diatonic.directedNiceName,
+ 'Descending Perfect Fourth')
+ self.assertEqual(descendingFourth.diatonic.directedSimpleName, 'P-4')
+ self.assertEqual(descendingFourth.diatonic.simpleName, 'P4')
+ self.assertEqual(descendingFourth.diatonic.mod7, 'P5')
+
+ perfectFifth = descendingFourth.complement
+ self.assertEqual(perfectFifth.niceName, 'Perfect Fifth')
+ self.assertEqual(perfectFifth.diatonic.simpleName, 'P5')
+ self.assertEqual(perfectFifth.diatonic.mod7, 'P5')
+ self.assertEqual(perfectFifth.complement.niceName, 'Perfect Fourth')
+
+ def testCreateIntervalFromPitch(self):
+ p1 = pitch.Pitch('c')
+ p2 = pitch.Pitch('g')
+ i = interval.Interval(p1, p2)
+ self.assertEqual(i.intervalClass, 5)
+
+ def testTransposeImported(self):
+ def collectAccidentalDisplayStatus(s_inner):
+ post = []
+ for e in s_inner.flatten().notes:
+ if e.pitch.accidental is not None:
+ post.append(e.pitch.accidental.displayStatus)
+ else: # mark as not having an accidental
+ post.append('x')
+ return post
+
+ s = corpus.parse('bach/bwv66.6')
+ # this has accidentals in measures 2 and 6
+ sSub = s.parts[3].measures(2, 6)
+
+ self.assertEqual(collectAccidentalDisplayStatus(sSub),
+ ['x', False, 'x', 'x', True, False, 'x', False, False, False,
+ False, False, False, 'x', 'x', 'x', False, False, False,
+ 'x', 'x', 'x', 'x', True, False])
+
+ sTransposed = sSub.flatten().transpose('p5')
+ # sTransposed.show()
+
+ self.assertEqual(collectAccidentalDisplayStatus(sTransposed),
+ ['x', None, 'x', 'x', None, None, None, None, None,
+ None, None, None, None, 'x', None, None, None, None,
+ None, 'x', 'x', 'x', None, None, None])
+
+ def testIntervalMicrotonesA(self):
+ i = interval.Interval('m3')
+ self.assertEqual(i.chromatic.cents, 300)
+ self.assertEqual(i.cents, 300.0)
+
+ i = interval.Interval('p5')
+ self.assertEqual(i.chromatic.cents, 700)
+ self.assertEqual(i.cents, 700.0)
+
+ i = interval.Interval(8)
+ self.assertEqual(i.chromatic.cents, 800)
+ self.assertEqual(i.cents, 800.0)
+
+ i = interval.Interval(8.5)
+ self.assertEqual(i.chromatic.cents, 850.0)
+ self.assertEqual(i.cents, 850.0)
+
+ i = interval.Interval(5.25) # a sharp p4
+ self.assertEqual(i.cents, 525.0)
+ # we can subtract the two to get an offset
+ self.assertEqual(i.cents, 525.0)
+ self.assertEqual(str(i), '')
+ self.assertEqual(i._diatonicIntervalCentShift(), 25)
+
+ i = interval.Interval(4.75) # a flat p4
+ self.assertEqual(str(i), '')
+ self.assertEqual(i._diatonicIntervalCentShift(), -25)
+
+ i = interval.Interval(4.48) # a sharp M3
+ self.assertEqual(str(i), '')
+ self.assertAlmostEqual(i._diatonicIntervalCentShift(), 48.0)
+
+ i = interval.Interval(4.5) # a sharp M3
+ self.assertEqual(str(i), '')
+ self.assertAlmostEqual(i._diatonicIntervalCentShift(), 50.0)
+
+ i = interval.Interval(5.25) # a sharp p4
+ p1 = pitch.Pitch('c4')
+ p2 = i.transposePitch(p1)
+ self.assertEqual(str(p2), 'F4(+25c)')
+
+ i = interval.Interval(5.80) # a sharp p4
+ p1 = pitch.Pitch('c4')
+ p2 = i.transposePitch(p1)
+ self.assertEqual(str(p2), 'F#4(-20c)')
+
+ i = interval.Interval(6.00) # an exact Tritone
+ p1 = pitch.Pitch('c4')
+ p2 = i.transposePitch(p1)
+ self.assertEqual(str(p2), 'F#4')
+
+ i = interval.Interval(5) # a chromatic p4
+ p1 = pitch.Pitch('c4')
+ p1.microtone = 10 # c+20
+ self.assertEqual(str(p1), 'C4(+10c)')
+ p2 = i.transposePitch(p1)
+ self.assertEqual(str(p2), 'F4(+10c)')
+
+ i = interval.Interval(7.20) # a sharp P5
+ p1 = pitch.Pitch('c4')
+ p1.microtone = -20 # c+20
+ self.assertEqual(str(p1), 'C4(-20c)')
+ p2 = i.transposePitch(p1)
+ self.assertEqual(str(p2), 'G4')
+
+ i = interval.Interval(7.20) # a sharp P5
+ p1 = pitch.Pitch('c4')
+ p1.microtone = 80 # c+20
+ p2 = i.transposePitch(p1)
+ self.assertEqual(str(p2), 'G#4')
+
+ i = interval.Interval(0.20) # a sharp unison
+ p1 = pitch.Pitch('e4')
+ p1.microtone = 10
+ p2 = i.transposePitch(p1)
+ self.assertEqual(str(p2), 'E~4(-20c)')
+
+ i = interval.Interval(0.05) # a tiny bit sharp unison
+ p1 = pitch.Pitch('e4')
+ p1.microtone = 5
+ p2 = i.transposePitch(p1)
+ self.assertEqual(str(p2), 'E4(+10c)')
+
+ i = interval.Interval(12.05) # a tiny bit sharp octave
+ p1 = pitch.Pitch('e4')
+ p1.microtone = 5
+ p2 = i.transposePitch(p1)
+ self.assertEqual(str(p2), 'E5(+10c)')
+
+ i = interval.Interval(11.85) # a flat octave
+ p1 = pitch.Pitch('e4')
+ p1.microtone = 5
+ p2 = i.transposePitch(p1)
+ self.assertEqual(str(p2), 'E5(-10c)')
+
+ i = interval.Interval(11.85) # a flat octave
+ p1 = pitch.Pitch('e4')
+ p1.microtone = -20
+ p2 = i.transposePitch(p1)
+ self.assertEqual(str(p2), 'E`5(+15c)')
+
+ def testIntervalMicrotonesB(self):
+ i = interval.Interval(note.Note('c4'), note.Note('c#4'))
+ self.assertEqual(str(i), '')
+
+ i = interval.Interval(note.Note('c4'), note.Note('c~4'))
+ self.assertEqual(str(i), '')
+
+ def testDescendingAugmentedUnison(self):
+ ns = note.Note('C4')
+ ne = note.Note('C-4')
+ i = interval.Interval(noteStart=ns, noteEnd=ne)
+ directedNiceName = i.directedNiceName
+ self.assertEqual(directedNiceName, 'Descending Diminished Unison')
+
+ def testTransposeWithChromaticInterval(self):
+ ns = note.Note('C4')
+ i = interval.ChromaticInterval(5)
+ n2 = ns.transpose(i)
+ self.assertEqual(n2.nameWithOctave, 'F4')
+
+ ns = note.Note('B#3')
+ i = interval.ChromaticInterval(5)
+ n2 = ns.transpose(i)
+ self.assertEqual(n2.nameWithOctave, 'F4')
+
+ def testTransposeImplicit(self):
+ p = pitch.Pitch('D#4')
+ i = interval.Interval(0)
+ self.assertTrue(i.implicitDiatonic)
+ p2 = i.transposePitch(p)
+ self.assertEqual(p.ps, p2.ps)
+ self.assertNotEqual(p.name, p2.name)
+
+ def testRepeatedTransposePitch(self):
+ p = pitch.Pitch('C1')
+ intv = interval.Interval('P5')
+ out = []
+ for _ in range(33):
+ out.append(p.nameWithOctave)
+ intv.transposePitch(p, maxAccidental=None, inPlace=True)
+ self.assertEqual(
+ out,
+ ['C1', 'G1', 'D2', 'A2', 'E3', 'B3', 'F#4',
+ 'C#5', 'G#5', 'D#6', 'A#6', 'E#7', 'B#7', 'F##8',
+ 'C##9', 'G##9', 'D##10', 'A##10', 'E##11', 'B##11', 'F###12',
+ 'C###13', 'G###13', 'D###14', 'A###14', 'E###15', 'B###15', 'F####16',
+ 'C####17', 'G####17', 'D####18', 'A####18', 'E####19',
+ ]
+ )
+ with self.assertRaisesRegex(pitch.AccidentalException,
+ '5.0 is not a supported accidental type'):
+ intv.transposePitch(p, maxAccidental=None)
+ p2 = intv.transposePitch(p)
+ self.assertEqual(p2.nameWithOctave, 'B-20')
+
+
+ def testIntervalWithOneNoteGiven(self):
+ noteC = note.Note('C4')
+ with self.assertRaises(ValueError):
+ i = interval.Interval(name='P4', noteStart=noteC)
+ i = interval.Interval(name='P4')
+ i.noteStart = noteC
+ self.assertEqual(i.noteEnd.nameWithOctave, 'F4')
+ noteF = i.noteEnd
+
+ # giving noteStart and noteEnd and a name where the name does not match
+ # the notes is an exception.
+ with self.assertRaises(ValueError):
+ interval.Interval(name='d5', noteStart=noteC, noteEnd=noteF)
+
+ # same with chromatic only intervals
+ with self.assertRaises(ValueError):
+ interval.Interval(chromatic=interval.ChromaticInterval(6),
+ noteStart=noteC,
+ noteEnd=noteF)
+
+ # but these should work
+ i2 = interval.Interval(name='P4', noteStart=noteC, noteEnd=noteF)
+ self.assertIs(i2.noteStart, noteC)
+ self.assertIs(i2.noteEnd, noteF)
+ self.assertEqual(i2.name, 'P4')
+ self.assertEqual(i2.semitones, 5)
+
+ i3 = interval.Interval(chromatic=interval.ChromaticInterval(5),
+ noteStart=noteC,
+ noteEnd=noteF)
+ self.assertIs(i3.noteStart, noteC)
+ self.assertIs(i3.noteEnd, noteF)
+ self.assertEqual(i3.name, 'P4')
+ self.assertEqual(i3.semitones, 5)
+
+
+ def testEmptyIntervalProperties(self):
+ '''
+ As of v8, an empty Interval is equal to P1
+ '''
+ empty = interval.DiatonicInterval()
+ self.assertEqual(empty.cents, 0.0)
+
+ empty = interval.Interval()
+ self.assertEqual(empty.cents, 0.0)
+ self.assertEqual(empty.intervalClass, 0)
+ self.assertEqual(empty.name, 'P1')
+
+
+if __name__ == '__main__':
+ import music21
+ music21.mainTest(Test)
diff --git a/music21/test/test_note.py b/music21/test/test_note.py
new file mode 100644
index 0000000000..6b376023f2
--- /dev/null
+++ b/music21/test/test_note.py
@@ -0,0 +1,337 @@
+from __future__ import annotations
+
+import copy
+import unittest
+
+from music21 import articulations
+from music21 import corpus
+from music21.duration import DurationTuple
+from music21 import expressions
+from music21.lily.translate import LilypondConverter
+from music21 import meter
+from music21 import note
+from music21 import stream
+from music21 import tie
+from music21 import volume
+
+class Test(unittest.TestCase):
+ def testLyricRepr(self):
+ ly = note.Lyric()
+ self.assertEqual(repr(ly), '')
+ ly.text = 'hi'
+ self.assertEqual(repr(ly), "")
+ ly.identifier = 'verse'
+ self.assertEqual(repr(ly), "")
+ ly.text = None
+ self.assertEqual(repr(ly), "")
+
+ def testComplex(self):
+ note1 = note.Note()
+ note1.duration.clear()
+ d1 = DurationTuple('whole', 0, 4.0)
+ d2 = DurationTuple('quarter', 0, 1.0)
+ note1.duration.addDurationTuple(d1)
+ note1.duration.addDurationTuple(d2)
+ self.assertEqual(note1.duration.quarterLength, 5.0)
+ self.assertEqual(note1.duration.componentIndexAtQtrPosition(2), 0)
+ self.assertEqual(note1.duration.componentIndexAtQtrPosition(4), 1)
+ self.assertEqual(note1.duration.componentIndexAtQtrPosition(4.5), 1)
+ note1.duration.sliceComponentAtPosition(1.0)
+
+ matchStr = "c'4~\nc'2.~\nc'4"
+ conv = LilypondConverter()
+ conv.appendM21ObjectToContext(note1)
+ outStr = str(conv.context).replace(' ', '').strip()
+ # print(outStr)
+ self.assertEqual(matchStr, outStr)
+ i = 0
+ for thisNote in note1.splitAtDurations():
+ matchSub = matchStr.split('\n')[i] # pylint: disable=use-maxsplit-arg
+ conv = LilypondConverter()
+ conv.appendM21ObjectToContext(thisNote)
+ outStr = str(conv.context).replace(' ', '').strip()
+ self.assertEqual(matchSub, outStr)
+ i += 1
+
+ def testNote(self):
+ note2 = note.Rest()
+ self.assertTrue(note2.isRest)
+ note3 = note.Note()
+ note3.pitch.name = 'B-'
+ # not sure how to test not None
+ # self.assertFalse (note3.pitch.accidental, None)
+ self.assertEqual(note3.pitch.accidental.name, 'flat')
+ self.assertEqual(note3.pitch.pitchClass, 10)
+
+ a5 = note.Note()
+ a5.name = 'A'
+ a5.octave = 5
+ self.assertAlmostEqual(a5.pitch.frequency, 880.0)
+ self.assertEqual(a5.pitch.pitchClass, 9)
+
+ def testCopyNote(self):
+ a = note.Note()
+ a.quarterLength = 3.5
+ a.name = 'D'
+ b = copy.deepcopy(a)
+ self.assertEqual(b.name, a.name)
+
+ def testMusicXMLFermata(self):
+ a = corpus.parse('bach/bwv5.7')
+ found = []
+ for n in a.flatten().notesAndRests:
+ for obj in n.expressions:
+ if isinstance(obj, expressions.Fermata):
+ found.append(obj)
+ self.assertEqual(len(found), 24)
+
+ def testNoteBeatProperty(self):
+ data = [
+ ['3/4', 0.5, 6, [1.0, 1.5, 2.0, 2.5, 3.0, 3.5],
+ [1.0] * 6, ],
+ ['3/4', 0.25, 8, [1.0, 1.25, 1.5, 1.75, 2.0, 2.25, 2.5, 2.75],
+ [1.0] * 8],
+ ['3/2', 0.5, 8, [1.0, 1.25, 1.5, 1.75, 2.0, 2.25, 2.5, 2.75],
+ [2.0] * 8],
+
+ ['6/8', 0.5, 6, [1.0, 1.3333, 1.66666, 2.0, 2.3333, 2.666666],
+ [1.5] * 6],
+ ['9/8', 0.5, 6, [1.0, 1.3333, 1.66666, 2.0, 2.3333, 2.666666],
+ [1.5] * 6],
+ ['12/8', 0.5, 6, [1.0, 1.3333, 1.66666, 2.0, 2.3333, 2.666666],
+ [1.5] * 6],
+
+ ['6/16', 0.25, 6, [1.0, 1.3333, 1.66666, 2.0, 2.3333, 2.666666],
+ [0.75] * 6],
+
+ ['5/4', 1, 5, [1.0, 2.0, 3.0, 4.0, 5.0],
+ [1.] * 5],
+
+ ['2/8+3/8+2/8', 0.5, 6, [1.0, 1.5, 2.0, 2.33333, 2.66666, 3.0],
+ [1., 1., 1.5, 1.5, 1.5, 1.]],
+
+ ]
+
+ # one measure case
+ for tsStr, nQL, nCount, matchBeat, matchBeatDur in data:
+ n = note.Note() # need fully qualified name
+ n.quarterLength = nQL
+ m = stream.Measure()
+ m.timeSignature = meter.TimeSignature(tsStr)
+ m.repeatAppend(n, nCount)
+
+ self.assertEqual(len(m), nCount + 1)
+
+ # test matching beat proportion value
+ post = [m.notesAndRests[i].beat for i in range(nCount)]
+ for i in range(len(matchBeat)):
+ self.assertAlmostEqual(post[i], matchBeat[i], 4)
+
+ # test getting beat duration
+ post = [m.notesAndRests[i].beatDuration.quarterLength for i in range(nCount)]
+
+ for i in range(len(matchBeat)):
+ self.assertAlmostEqual(post[i], matchBeatDur[i], 4)
+
+ # two measure case
+ for tsStr, nQL, nCount, matchBeat, matchBeatDur in data:
+ p = stream.Part()
+ n = note.Note()
+ n.quarterLength = nQL
+
+ # m1 has time signature
+ m1 = stream.Measure()
+ m1.timeSignature = meter.TimeSignature(tsStr)
+ p.append(m1)
+
+ # m2 does not have time signature
+ m2 = stream.Measure()
+ m2.repeatAppend(n, nCount)
+ self.assertEqual(len(m2), nCount)
+ self.assertEqual(len(m2.notesAndRests), nCount)
+
+ p.append(m2)
+
+ # test matching beat proportion value
+ post = [m2.notesAndRests[i].beat for i in range(nCount)]
+ for i in range(len(matchBeat)):
+ self.assertAlmostEqual(post[i], matchBeat[i], 4)
+ # test getting beat duration
+ post = [m2.notesAndRests[i].beatDuration.quarterLength for i in range(nCount)]
+ for i in range(len(matchBeat)):
+ self.assertAlmostEqual(post[i], matchBeatDur[i], 4)
+
+ def testNoteBeatPropertyCorpus(self):
+ data = [['bach/bwv255', [4.0, 1.0, 2.5, 3.0, 4.0, 4.5, 1.0, 1.5]],
+ ['bach/bwv153.9', [1.0, 2.0, 3.0, 1.0, 2.0, 3.0, 1.0, 3.0, 1.0]]
+ ]
+
+ for work, match in data:
+ s = corpus.parse(work)
+ # always use tenor line
+ found = []
+ for n in s.parts[2].flatten().notesAndRests:
+ n.lyric = n.beatStr
+ found.append(n.beat)
+
+ for i in range(len(match)):
+ self.assertEqual(match[i], found[i])
+
+ # s.show()
+
+ def testNoteEquality(self):
+ n1 = note.Note('A#')
+ n2 = note.Note('G')
+ n3 = note.Note('A-')
+ n4 = note.Note('A#')
+
+ self.assertNotEqual(n1, n2)
+ self.assertNotEqual(n1, n3)
+ self.assertEqual(n1, n4)
+
+ # test durations with the same pitch
+ for x, y, match in [
+ (1, 1, True),
+ (1, 0.5, False),
+ (1, 2, False),
+ (1, 1.5, False)
+ ]:
+ n1.quarterLength = x
+ n4.quarterLength = y
+ self.assertEqual(n1 == n4, match) # sub1
+
+ # test durations with different pitch
+ for x, y, match in [(1, 1, False), (1, 0.5, False),
+ (1, 2, False), (1, 1.5, False)]:
+ n1.quarterLength = x
+ n2.quarterLength = y
+ self.assertEqual(n1 == n2, match) # sub2
+
+ # same pitches different octaves
+ n1.quarterLength = 1.0
+ n4.quarterLength = 1.0
+ for x, y, match in [(4, 4, True), (3, 4, False), (2, 4, False)]:
+ n1.pitch.octave = x
+ n4.pitch.octave = y
+ self.assertEqual(n1 == n4, match) # sub4
+
+ # with and without ties
+ n1.pitch.octave = 4
+ n4.pitch.octave = 4
+ t1 = tie.Tie()
+ t2 = tie.Tie()
+ for x, y, match in [(t1, None, False), (t1, t2, True)]:
+ n1.tie = x
+ n4.tie = y
+ self.assertEqual(n1 == n4, match) # sub4
+
+ # with ties but different pitches
+ for n in [n1, n2, n3, n4]:
+ n.quarterLength = 1.0
+ t1 = tie.Tie()
+ t2 = tie.Tie()
+ for a, b, match in [(n1, n2, False), (n1, n3, False),
+ (n2, n3, False), (n1, n4, True)]:
+ a.tie = t1
+ b.tie = t2
+ self.assertEqual(a == b, match) # sub5
+
+ # articulation groups
+ a1 = [articulations.Accent()]
+ a2 = [articulations.Accent(), articulations.StrongAccent()]
+ a3 = [articulations.StrongAccent(), articulations.Accent()]
+ a4 = [articulations.StrongAccent(), articulations.Accent(),
+ articulations.Tenuto()]
+ a5 = [articulations.Accent(), articulations.Tenuto(),
+ articulations.StrongAccent()]
+
+ for a, b, c, d, match in [(n1, n4, a1, a1, True),
+ (n1, n2, a1, a1, False), (n1, n3, a1, a1, False),
+ # same pitch different orderings
+ (n1, n4, a2, a3, True), (n1, n4, a4, a5, True),
+ # different pitch same orderings
+ (n1, n2, a2, a3, False), (n1, n3, a4, a5, False),
+ ]:
+ a.articulations = c
+ b.articulations = d
+ self.assertEqual(a == b, match) # sub6
+
+ def testMetricalAccent(self):
+ data = [
+ ('4/4', 8, 0.5, [1.0, 0.125, 0.25, 0.125, 0.5, 0.125, 0.25, 0.125]),
+ ('3/4', 6, 0.5, [1.0, 0.25, 0.5, 0.25, 0.5, 0.25]),
+ ('6/8', 6, 0.5, [1.0, 0.25, 0.25, 0.5, 0.25, 0.25]),
+
+ ('12/32', 12, 0.125, [1.0, 0.125, 0.125, 0.25, 0.125, 0.125,
+ 0.5, 0.125, 0.125, 0.25, 0.125, 0.125]),
+
+ ('5/8', 10, 0.25, [1.0, 0.25, 0.5, 0.25, 0.5, 0.25, 0.5, 0.25, 0.5, 0.25]),
+
+ # test notes that do not have defined accents
+ ('4/4', 16, 0.25, [1.0, 0.0625, 0.125, 0.0625, 0.25, 0.0625, 0.125, 0.0625,
+ 0.5, 0.0625, 0.125, 0.0625, 0.25, 0.0625, 0.125, 0.0625]),
+ ('4/4', 32, 0.125, [1.0, 0.0625, 0.0625, 0.0625, 0.125, 0.0625, 0.0625, 0.0625,
+ 0.25, 0.0625, 0.0625, 0.0625, 0.125, 0.0625, 0.0625, 0.0625,
+ 0.5, 0.0625, 0.0625, 0.0625, 0.125, 0.0625, 0.0625, 0.0625,
+ 0.25, 0.0625, 0.0625, 0.0625, 0.125, 0.0625, 0.0625, 0.0625]),
+ ]
+
+ for tsStr, nCount, dur, match in data:
+ m = stream.Measure()
+ m.timeSignature = meter.TimeSignature(tsStr)
+ n = note.Note()
+ n.quarterLength = dur
+ m.repeatAppend(n, nCount)
+
+ self.assertEqual([n.beatStrength for n in m.notesAndRests], match)
+
+ def testTieContinue(self):
+ n1 = note.Note()
+ n1.tie = tie.Tie()
+ n1.tie.type = 'start'
+
+ n2 = note.Note()
+ n2.tie = tie.Tie()
+ n2.tie.type = 'continue'
+
+ n3 = note.Note()
+ n3.tie = tie.Tie()
+ n3.tie.type = 'stop'
+
+ s = stream.Stream()
+ s.append([n1, n2, n3])
+
+ # need to test that this gets us a "continue" tie, but hard to test
+ # post musicxml processing
+ # s.show()
+
+ def testVolumeA(self):
+ v1 = volume.Volume()
+
+ n1 = note.Note()
+ n2 = note.Note()
+
+ n1.volume = v1 # can set as v1 has no client
+ self.assertEqual(n1.volume, v1)
+ self.assertEqual(n1.volume.client, n1)
+
+ # object is created on demand
+ self.assertIsNot(n2.volume, v1)
+ self.assertIsNotNone(n2.volume)
+
+ def testVolumeB(self):
+ # manage deepcopying properly
+ n1 = note.Note()
+
+ n1.volume.velocity = 100
+ self.assertEqual(n1.volume.velocity, 100)
+ self.assertEqual(n1.volume.client, n1)
+
+ n1Copy = copy.deepcopy(n1)
+ self.assertEqual(n1Copy.volume.velocity, 100)
+ self.assertEqual(n1Copy.volume.client, n1Copy)
+
+
+if __name__ == '__main__':
+ import music21
+ music21.mainTest(Test)
diff --git a/music21/tree/fromStream.py b/music21/tree/fromStream.py
index 7544d145ea..bffdeb7f3c 100644
--- a/music21/tree/fromStream.py
+++ b/music21/tree/fromStream.py
@@ -90,7 +90,7 @@ def listOfTreesByClass(
Changed in v8: it is now a stickler that classLists must be sequences of sequences,
such as tuples of tuples.
'''
- from music21 import stream
+ from music21 import stream # pylint: disable=redefined-outer-name # only in TYPE_CHECKING
if currentParentage is None:
currentParentage = (inputStream,)