Previous topic

music21.corpus.virtual

Next topic

music21.derivation

Table Of Contents

Table Of Contents

This Page

music21.counterpoint.species

counterpoint.species – set of tools for dealing with Species Counterpoint and later other forms of counterpoint.

Mostly coded by Jackie Rogoff – some routines have been moved to by VoiceLeadingQuartet, and that module should be used for future work

Functions

music21.counterpoint.species.getRandomCF(mode=None)

Function to obtain a dictionary representation of a cantus firmus. Cantus firmi should be added to the list above in the format of a dictionary with the keys ‘notes’ and ‘mode’. Under ‘notes’ is a tiny notation string to be parsed into music21 objects, while ‘mode’ accesses a string representing the name of the related scale’s tonic, which can be made into a note and a scale object.

>>> cf = counterpoint.species.getRandomCF()
>>> sorted(list(cf.keys()))
['mode', 'notes']
>>> isinstance(cf['notes'], str)
True
>>> isinstance(cf['mode'], str)
True

ModalCounterpoint

class music21.counterpoint.species.ModalCounterpoint(stream1=None, stream2=None)

ModalCounterpoint methods

ModalCounterpoint.allValidHarmony(stream1, stream2)

Given two simultaneous streams, returns True if all of the harmonies are legal and False if one or more is not. Legal harmonic intervals include ‘P1’, ‘P5’, ‘P8’, ‘m3’, ‘M3’, ‘m6’, and ‘M6’. Also assumes that final interval must be a perfect unison or octave.

>>> n1 = note.Note('G4')
>>> n2 = note.Note('A4')
>>> n3 = note.Note('D4')
>>> n4 = note.Note('C5')
>>> m1 = note.Note('G4')
>>> m2 = note.Note('A4')
>>> m3 = note.Note('B4')
>>> m4 = note.Note('C5')
>>> bass = stream.Stream()
>>> bass.append([n1, n2, n3, n4])
>>> sop = stream.Stream()
>>> sop.append([m1, m2, m3, m4])
>>> cp = counterpoint.species.ModalCounterpoint(stream1 = sop, stream2 = bass)
>>> cp.allValidHarmony(cp.stream1, cp.stream2)
False
>>> n4.name = 'C4'
>>> cp.allValidHarmony(cp.stream1, cp.stream2)
True
>>> n1.name = 'F#4'
>>> cp.allValidHarmony(cp.stream1, cp.stream2)
False
>>> n1.name = 'G4'
>>> m4.name = 'G5'
>>> cp.allValidHarmony(cp.stream1, cp.stream2)
False
ModalCounterpoint.allValidHarmonyMiddleVoices(stream1, stream2)

Given two simultaneous streams, returns True if all of the harmonies are legal and False if one or more is not. Legal harmonic intervals include ‘P1’, ‘P5’, ‘P8’, ‘m3’, ‘M3’, ‘m6’, and ‘M6’. As this is for middle voices, ‘P4’ is also allowed and the final interval is allowed to be a fifth.

>>> n1 = note.Note('G4')
>>> n2 = note.Note('A4')
>>> n3 = note.Note('B4')
>>> n4 = note.Note('C5')
>>> m1 = note.Note('G4')
>>> m2 = note.Note('A4')
>>> m3 = note.Note('B4')
>>> m4 = note.Note('C5')
>>> bass = stream.Stream()
>>> bass.append([n1, n2, n3, n4])
>>> sop = stream.Stream()
>>> sop.append([m1, m2, m3, m4])
>>> cp = counterpoint.species.ModalCounterpoint(stream1 = bass, stream2 = sop)
>>> cp.allValidHarmonyMiddleVoices(cp.stream1, cp.stream2)
True
>>> n1.name = 'F#4'
>>> cp.allValidHarmonyMiddleVoices(cp.stream1, cp.stream2)
False
ModalCounterpoint.countBadHarmonies(stream1, stream2)

Given two simultaneous streams, counts the number of notes (in the first stream given) that create illegal harmonies when attacked.

>>> n1 = note.Note('G4')
>>> n2 = note.Note('A4')
>>> n3 = note.Note('B4')
>>> n4 = note.Note('C5')
>>> m1 = note.Note('G4')
>>> m2 = note.Note('A4')
>>> m3 = note.Note('B4')
>>> m4 = note.Note('C5')
>>> bass = stream.Stream()
>>> bass.append([n1, n2, n3, n4])
>>> sop = stream.Stream()
>>> sop.append([m1, m2, m3, m4])
>>> cp = counterpoint.species.ModalCounterpoint(stream1 = bass, stream2 = sop)
>>> cp.countBadHarmonies(cp.stream1, cp.stream2)
0
>>> n1.name = 'F#4'
>>> cp.countBadHarmonies(cp.stream1, cp.stream2)
1
ModalCounterpoint.countBadSteps(stream1)

Given a single stream, returns the number of illegal melodic intervals.

SHOULD BE RENAMED countBadMelodies?

>>> n1 = note.Note('G-4')
>>> n2 = note.Note('A4')
>>> n3 = note.Note('B4')
>>> n4 = note.Note('A5')
>>> m1 = note.Note('G4')
>>> m2 = note.Note('A4')
>>> m3 = note.Note('B4')
>>> m4 = note.Note('C5')
>>> bass = stream.Stream()
>>> bass.append([n1, n2, n3, n4])
>>> sop = stream.Stream()
>>> sop.append([m1, m2, m3, m4])
>>> cp = counterpoint.species.ModalCounterpoint(stream1 = bass, stream2 = sop)
>>> cp.countBadSteps(cp.stream1)
2
>>> n1.name = 'F#4'
>>> cp.countBadSteps(cp.stream2)
0
ModalCounterpoint.findAllBadFifths(stream1, stream2)

Given two streams, returns the total parallel and hidden fifths, and also puts the appropriate tags in note.editorial.misc under “Parallel Fifth” and “Hidden Fifth”.

>>> n1 = note.Note('C4')
>>> n2 = note.Note('D4')
>>> n3 = note.Note('E4')
>>> n4 = note.Note('F4')
>>> s1 = stream.Stream()
>>> s1.append([n1, n2, n3, n4])
>>> m1 = note.Note('G4')
>>> m2 = note.Note('A4')
>>> m3 = note.Note('G4')
>>> m4 = note.Note('C5')
>>> s2 = stream.Stream()
>>> s2.append([m1, m2, m3, m4])
>>> cp = counterpoint.species.ModalCounterpoint(s1, s2)
>>> cp.findAllBadFifths(cp.stream1, cp.stream2)
2
ModalCounterpoint.findAllBadOctaves(stream1, stream2)

Given two streams, returns the total parallel and hidden octaves, and also puts the appropriate tags in note.editorial.misc under “Parallel Octave” and “Hidden Octave”.

>>> n1 = note.Note('C4')
>>> n2 = note.Note('D4')
>>> n3 = note.Note('E4')
>>> n4 = note.Note('F4')
>>> s1 = stream.Stream()
>>> s1.append([n1, n2, n3, n4])
>>> m1 = note.Note('C5')
>>> m2 = note.Note('D5')
>>> m3 = note.Note('C5')
>>> m4 = note.Note('F5')
>>> s2 = stream.Stream()
>>> s2.append([m1, m2, m3, m4])
>>> cp = counterpoint.species.ModalCounterpoint(s1, s2)
>>> cp.findAllBadOctaves(cp.stream1, cp.stream2)
2
ModalCounterpoint.findHiddenFifths(stream1, stream2)

Given two streams, returns the number of hidden fifths and also assigns a flag under note.editorial.misc under “Hidden Fifth” for any note that has harmonic interval of a fifth where it creates a hidden parallel fifth. Note: a hidden fifth here is defined as anything where the two streams reach a fifth through parallel motion, but is not a parallel fifth.

>>> n1 = note.Note('G3')
>>> n2 = note.Note('A3')
>>> n3 = note.Note('B3')
>>> n4 = note.Note('C4')
>>> m1 = note.Note('C4')
>>> m2 = note.Note('E4')
>>> m3 = note.Note('D4')
>>> m4 = note.Note('G4')
>>> bass = stream.Stream()
>>> bass.append([n1, n2, n3, n4])
>>> sop = stream.Stream()
>>> sop.append([m1, m2, m3, m4])
>>> cp = counterpoint.species.ModalCounterpoint(stream1 = bass, stream2 = sop)
>>> cp.findHiddenFifths(cp.stream1, cp.stream2)
2
>>> n2.editorial.misc['Hidden Fifth']
True
>>> cp.findHiddenFifths(cp.stream2, cp.stream1)
2
ModalCounterpoint.findHiddenOctaves(stream1, stream2)

Given two streams, returns the number of hidden octaves and also assigns a flag under note.editorial.misc[“Hidden Octave”]for any note that has harmonic interval of an octave where it creates a hidden parallel octave. Note: a hidden octave here is defined as anything where the two streams reach an octave through parallel motion, but is not a parallel octave.

>>> n1 = note.Note('F3')
>>> n2 = note.Note('A3')
>>> n3 = note.Note('A3')
>>> n4 = note.Note('C4')
>>> m1 = note.Note('G4')
>>> m2 = note.Note('A4')
>>> m3 = note.Note('B4')
>>> m4 = note.Note('C5')
>>> bass = stream.Stream()
>>> bass.append([n1, n2, n3, n4])
>>> sop = stream.Stream()
>>> sop.append([m1, m2, m3, m4])
>>> cp = counterpoint.species.ModalCounterpoint(stream1 = bass, stream2 = sop)
>>> cp.findHiddenOctaves(cp.stream1, cp.stream2)
2
>>> n2.editorial.misc['Hidden Octave']
True
>>> cp.findHiddenOctaves(cp.stream2, cp.stream1)
2
>>> m4.octave = 6
>>> cp.findHiddenOctaves(cp.stream2, cp.stream1)
2
ModalCounterpoint.findParallelFifths(srcStream, cmpStream)

Given two streams, returns the number of parallel fifths and also assigns a flag under note.editorial.misc[“Parallel Fifth”] for any note that has harmonic interval of a fifth and is preceded by a harmonic interval of a fifth.

>>> n1 = note.Note('G3')
>>> n2 = note.Note('A3')
>>> n3 = note.Note('B3')
>>> n4 = note.Note('C4')
>>> m1 = note.Note('D4')
>>> m2 = note.Note('E4')
>>> m3 = note.Note('F#4')
>>> m4 = note.Note('G4')
>>> bass = stream.Stream()
>>> bass.append([n1, n2, n3, n4])
>>> sop = stream.Stream()
>>> sop.append([m1, m2, m3, m4])
>>> cp = counterpoint.species.ModalCounterpoint(stream1 = bass, stream2 = sop)
>>> cp.findParallelFifths(cp.stream1, cp.stream2)
3
>>> n1.editorial.harmonicInterval.name
'P5'
>>> m4.octave = 5 #checking for 12ths as well
>>> cp.findParallelFifths(cp.stream1, cp.stream2)
3
ModalCounterpoint.findParallelOctaves(stream1, stream2)

Given two streams, returns the number of parallel octaves and also assigns a flag under note.editorial.misc[“Parallel Octave”] for any note that has harmonic interval of an octave and is preceded by a harmonic interval of an octave.

>>> n1 = note.Note('G3')
>>> n2 = note.Note('A3')
>>> n3 = note.Note('B3')
>>> n4 = note.Note('C4')
>>> m1 = note.Note('G4')
>>> m2 = note.Note('A4')
>>> m3 = note.Note('B4')
>>> m4 = note.Note('C5')
>>> bass = stream.Stream()
>>> bass.append([n1, n2, n3, n4])
>>> sop = stream.Stream()
>>> sop.append([m1, m2, m3, m4])
>>> cp = counterpoint.species.ModalCounterpoint(stream1 = bass, stream2 = sop)
>>> cp.findParallelOctaves(cp.stream1, cp.stream2)
3
>>> n2.editorial.misc['Parallel Octave']
True
>>> cp.findParallelOctaves(cp.stream2, cp.stream1)
3
>>> m3.octave = 5
>>> m4.octave = 6 #check for parallel 17ths
>>> cp.findParallelOctaves(cp.stream2, cp.stream1)
3
ModalCounterpoint.findParallelUnisons(stream1, stream2)

Given two streams, returns the number of parallel unisons and also assigns a flag under note.editorial.misc[“Parallel Unison”] for any note that has harmonic interval of P1 and is preceded by a P1.

>>> n1 = note.Note('G4')
>>> n2 = note.Note('A4')
>>> n3 = note.Note('B4')
>>> n4 = note.Note('C5')
>>> m1 = note.Note('G4')
>>> m2 = note.Note('A4')
>>> m3 = note.Note('B4')
>>> m4 = note.Note('C5')
>>> bass = stream.Stream()
>>> bass.append([n1, n2, n3, n4])
>>> sop = stream.Stream()
>>> sop.append([m1, m2, m3, m4])
>>> cp = counterpoint.species.ModalCounterpoint(stream1 = bass, stream2 = sop)
>>> cp.findParallelUnisons(cp.stream1, cp.stream2)
3
>>> n2.editorial.misc['Parallel Unison']
True
>>> cp.findParallelUnisons(cp.stream2, cp.stream1)
3
ModalCounterpoint.generateFirstSpecies(cantusFirmus, minorScale, choice='random')

Doc

ModalCounterpoint.generateValidLastNotes(prevFirmus, currFirmus, prevNote, afterLeap, minorScale, topVoice=True)

Helper function for generateFirstSpecies; gets a list of possible next notes based on valid melodic intervals, then checks each one so that parallel/hidden fifths/octaves, voice crossing, and invalid harmonies are prevented. Adds extra weight to notes that would create contrary motion.

ModalCounterpoint.generateValidNotes(prevFirmus, currFirmus, prevNote, afterLeap, minorScale)

Helper function for getValidSecondVoice; gets a list of possible next notes based on valid melodic intervals, then checks each one so that parallel/hidden fifths/octaves, voice crossing, and invalid harmonies are prevented. Adds extra weight to notes that would create contrary motion.

ModalCounterpoint.getValidSecondVoice(stream1, minorScale, choice='random')

Given a stream (the cantus firmus) and the stream’s key in the form of a MinorScale object, generates a stream of first species counterpoint that follows the rules of 21M.301.

choice is a flag that can be set to deterministically choose notes to add to the counterpoint. Right now, ‘random’, ‘first’, and ‘last’ are supported. This will be expanded so that all solution sets can be generated.

ModalCounterpoint.isHiddenFifth(note11, note12, note21, note22)

Given four notes, assuming the first pair is one part and the second is another part sounding at the same time (i.e. argument order is isHiddenFifth(v1n1, v1n2, v2n1, v2n2)), returns True if there is a hidden fifth and false otherwise.

>>> n1 = note.Note('G3')
>>> n2 = note.Note('B-3')
>>> m1 = note.Note('E4')
>>> m2 = note.Note('F4')
>>> cp = counterpoint.species.ModalCounterpoint()
>>> cp.isHiddenFifth(n1, m1, n2, m2) #(n1, n2) and (m1, m2) are chords
False
>>> cp.isHiddenFifth(n1, n2, m1, m2) #(n1, m1) and (n2, m2) are chords
True
>>> m1.octave = 5
>>> m2.octave = 5 # check for hidden 12ths
>>> cp.isHiddenFifth(n1, n2, m1, m2)
True
ModalCounterpoint.isHiddenOctave(note11, note12, note21, note22)

Given four notes, assuming the first pair is from one part and the second pair is from another part sounding at the same time, (i.e. argument order is isHiddenOctave(v1n1, v1n2, v2n1, v2n2)) returns True if there is a hidden octave and false otherwise.

>>> n1 = note.Note('A3')
>>> n2 = note.Note('B-3')
>>> m1 = note.Note('F4')
>>> m2 = note.Note('B-4')
>>> cp = counterpoint.species.ModalCounterpoint()
>>> cp.isHiddenOctave(n1, m1, n2, m2) #(n1, n2) and (m1, m2) are chords
False
>>> cp.isHiddenOctave(n1, n2, m1, m2) #(n1, m1) and (n2, m2) are chords
True
>>> m1.octave = 5
>>> m2.octave = 5
>>> cp.isHiddenOctave(n1, n2, m1, m2)
True
ModalCounterpoint.isParallelFifth(note11, note12, note21, note22)

Given four notes, assuming the first pair is one voice and the second pair is another voice sounding at the same time (i.e. argument order is isParallelFifth(v1n1, v1n2, v2n1, v2n2)), returns True if the two harmonic intervals are P5 and False otherwise.

>>> n1 = note.Note('G3')
>>> n2 = note.Note('B-3')
>>> m1 = note.Note('D4')
>>> m2 = note.Note('F4')
>>> cp = counterpoint.species.ModalCounterpoint()
>>> cp.isParallelFifth(n1, m1, n2, m2) #(n1, n2) and (m1, m2) are chords
False
>>> cp.isParallelFifth(n1, n2, m1, m2) #(n1, m1) and (n2, m2) are chords
True
>>> m1.octave = 5
>>> m2.octave = 5 #test parallel 12ths
>>> cp.isParallelFifth(n1, n2, m1, m2)
True
ModalCounterpoint.isParallelOctave(note11, note12, note21, note22)

Given four notes, assuming the first pair sounds at the same time and the second pair sounds at the same time (i.e. argument order is isParallelOctave(v1n1, v1n2, v2n1, v2n2)), returns True if the two harmonic intervals are P8 and False otherwise.

>>> n1 = note.Note('A3')
>>> n2 = note.Note('B-3')
>>> m1 = note.Note('A4')
>>> m2 = note.Note('B-4')
>>> cp = counterpoint.species.ModalCounterpoint()
>>> cp.isParallelOctave(n1, m1, n2, m2) #(n1, n2) and (m1, m2) are chords
False
>>> cp.isParallelOctave(n1, n2, m1, m2) #(n1, m1) and (n2, m2) are chords
True
>>> m1.octave = 5
>>> m2.octave = 5
>>> cp.isParallelOctave(n1, n2, m1, m2)
True
ModalCounterpoint.isParallelUnison(note11, note12, note21, note22)

Given four notes, assuming the first pair sounds at the same time and the second pair sounds at the same time, (i.e. argument order is isParallelFifth(v1n1, v1n2, v2n1, v2n2)) returns True if the two harmonic intervals are P1 and False otherwise.

>>> n1 = note.Note('A3')
>>> n2 = note.Note('B-3')
>>> m1 = note.Note('A3')
>>> m2 = note.Note('B-3')
>>> cp = counterpoint.species.ModalCounterpoint()
>>> cp.isParallelUnison(n1, m1, n2, m2) #(n1, n2) and (m1, m2) are chords
False
>>> cp.isParallelUnison(n1, n2, m1, m2) #(n1, m1) and (n2, m2) are chords
True
>>> m1.octave = 4
>>> m2.octave = 4
>>> cp.isParallelUnison(n1, n2, m1, m2) #parallel octaves, not unison
False
ModalCounterpoint.isValidHarmony(note11, note21)

Determines if the harmonic interval between two given notes is “legal” according to 21M.301 rules of counterpoint. Legal harmonic intervals include ‘P1’, ‘P5’, ‘P8’, ‘m3’, ‘M3’, ‘m6’, and ‘M6’.

>>> c = note.Note('C4')
>>> d = note.Note('D4')
>>> e = note.Note('E4')
>>> cp = counterpoint.species.ModalCounterpoint()
>>> cp.isValidHarmony(c, d)
False
>>> cp.isValidHarmony(c, c)
True
>>> cp.isValidHarmony(c, e)
True
ModalCounterpoint.isValidMelody(stream1)

Given a single stream, returns True if all melodic intervals between notes are legal and False otherwise. Legal melodic intervals include ‘P4’, ‘P5’, ‘P8’, ‘m2’, ‘M2’, ‘m3’, ‘M3’, and ‘m6’.

SHOULD BE RENAMED allValidMelody?

>>> n1 = note.Note('G-4')
>>> n2 = note.Note('A4')
>>> n3 = note.Note('B4')
>>> n4 = note.Note('B5')
>>> m1 = note.Note('G4')
>>> m2 = note.Note('A4')
>>> m3 = note.Note('B4')
>>> m4 = note.Note('C5')
>>> bass = stream.Stream()
>>> bass.append([n1, n2, n3, n4])
>>> sop = stream.Stream()
>>> sop.append([m1, m2, m3, m4])
>>> cp = counterpoint.species.ModalCounterpoint(stream1 = bass, stream2 = sop)
>>> cp.isValidMelody(cp.stream1)
False
>>> n1.name = 'F#4'
>>> cp.isValidMelody(cp.stream2)
True
ModalCounterpoint.isValidMiddleHarmony(note11, note21)

Determines if the harmonic interval between two given notes is “legal” according to simple, species rules of counterpoint. Legal harmonic intervals for middle voices (i.e., not above the bass) include ‘P1’, ‘P5’, ‘P8’, ‘m3’, ‘M3’, ‘m6’, and ‘M6’, from before. ‘P4’ is now included because it is legal for middle harmonies.

>>> c = note.Note('C4')
>>> d = note.Note('D4')
>>> e = note.Note('E4')
>>> f = note.Note('F4')
>>> cp = counterpoint.species.ModalCounterpoint()
>>> cp.isValidMiddleHarmony(c, d)
False
>>> cp.isValidMiddleHarmony(c, c)
True
>>> cp.isValidMiddleHarmony(c, e)
True
>>> cp.isValidMiddleHarmony(c, f)
True
ModalCounterpoint.isValidStep(note11, note12)

Determines if the melodic interval between two given notes is “legal” according to 21M.301 rules of counterpoint. Legal melodic intervals include ‘P4’, ‘P5’, ‘P8’, ‘m2’, ‘M2’, ‘m3’, ‘M3’, and ‘m6’.

SHOULD BE RENAMED isValidMelody?

>>> c = note.Note('C4')
>>> d = note.Note('D4')
>>> e = note.Note('E#4')
>>> cp = counterpoint.species.ModalCounterpoint()
>>> cp.isValidStep(c, d)
True
>>> cp.isValidStep(c, c)
False
>>> cp.isValidStep(c, e)
False
ModalCounterpoint.raiseLeadingTone(stream1, minorScale)

Given a stream of notes and a minor scale object, returns a new stream that raises all the leading tones of the original stream. Also raises the sixth if applicable to avoid augmented intervals.

>>> n1 = note.Note('C4')
>>> n2 = note.Note('G4')
>>> n3 = note.Note('A4')
>>> n4 = note.Note('G4')
>>> n5 = note.Note('F4')
>>> n6 = note.Note('G4')
>>> n7 = note.Note('A4')
>>> n8 = note.Note('F4')
>>> n9 = note.Note('G4')
>>> s1 = stream.Stream()
>>> s2 = stream.Stream()
>>> s1.append([n1, n2, n3, n4, n5, n6, n7, n8, n9])
>>> s2.append([n1, n2, n3, n4, n5, n6, n7, n8, n9])
>>> cp = counterpoint.species.ModalCounterpoint(s1, s2)
>>> aMinor = scale.MinorScale(n3)
>>> s2 = cp.raiseLeadingTone(s1, aMinor)
>>> s2.notes[1].name
'G#'
>>> s2.notes[3].name
'G'
>>> s2.notes[4].name
'F#'
>>> s2.notes[5].name
'G#'
>>> s2.notes[7].name
'F'
ModalCounterpoint.tooManySixths(stream1, stream2, limit=3)

Given two consecutive streams and a limit, returns True if the number of consecutive harmonic sixths exceeds the limit and False otherwise.

>>> n1 = note.Note('E4')
>>> n2 = note.Note('F4')
>>> n3 = note.Note('G4')
>>> n4 = note.Note('A4')
>>> m1 = note.Note('C5')
>>> m2 = note.Note('D5')
>>> m3 = note.Note('E5')
>>> m4 = note.Note('F5')
>>> bass = stream.Stream()
>>> bass.append([n1, n2, n3, n4])
>>> sop = stream.Stream()
>>> sop.append([m1, m2, m3, m4])
>>> cp = counterpoint.species.ModalCounterpoint(stream1 = bass, stream2 = sop)
>>> cp.tooManySixths(cp.stream1, cp.stream2)
True
>>> cp.tooManySixths(cp.stream1, cp.stream2, 4)
False
ModalCounterpoint.tooManyThirds(stream1, stream2, limit=3)

Given two consecutive streams and a limit, returns True if the number of consecutive harmonic thirds exceeds the limit and False otherwise.

>>> n1 = note.Note('E4')
>>> n2 = note.Note('F4')
>>> n3 = note.Note('G4')
>>> n4 = note.Note('A4')
>>> m1 = note.Note('G4')
>>> m2 = note.Note('A4')
>>> m3 = note.Note('B4')
>>> m4 = note.Note('C5')
>>> bass = stream.Stream()
>>> bass.append([n1, n2, n3, n4])
>>> sop = stream.Stream()
>>> sop.append([m1, m2, m3, m4])
>>> cp = counterpoint.species.ModalCounterpoint(stream1 = bass, stream2 = sop)
>>> cp.tooManyThirds(cp.stream1, cp.stream2)
True
>>> cp.tooManyThirds(cp.stream1, cp.stream2, 4)
False