Closed ttblum closed 10 months ago
Hi, sure, here how you can do it: this code is written for musiclib==2.2.0
import functools
import itertools
import operator
from typing import NamedTuple
from opseq import OpSeq # the opseq library is installed when you install musiclib
from musiclib.scale import Scale
from musiclib.noteset import SpecificNoteSet
from musiclib.note import SpecificNote
from musiclib.note import Note
from musiclib.note import config
from musiclib.progression import Progression
from musiclib.voice_leading import checks
scale = Scale.from_name('C', 'major')
noterange = SpecificNoteSet.from_noterange(SpecificNote('C', 2), SpecificNote('C', 5), noteset=scale.noteset)
class SNSR(NamedTuple):
"""SpecificNoteSet with root Note"""
sns: SpecificNoteSet
root: Note
def possible_chords(noterange: SpecificNoteSet, scale: Scale) -> tuple[SpecificNoteSet]:
scale_triads = scale.nths(config.nths['triads'])
notes_to_triad_root = {s.notes: s.root for s in scale_triads}
def _notes_to_chord(notes: frozenset[SpecificNote]):
abstract = frozenset({n.abstract for n in notes})
if root := notes_to_triad_root.get(abstract):
sns = SpecificNoteSet(notes)
if sns.noteset.name == 'dim':
return
snsr = SNSR(sns=sns, root=root)
yield snsr
it = itertools.combinations(noterange, 4) # 4 voice chords
it = itertools.chain.from_iterable(_notes_to_chord(frozenset(x)) for x in it)
it = [snsr for snsr in set(it) if not checks.is_large_spacing(snsr.sns, 7)]
return it
@functools.cache
def no_bad_checks(a_: SNSR, b_: SNSR):
a = a_.sns
b = b_.sns
return all((
not checks.is_parallel_interval(a, b, 0),
not checks.is_parallel_interval(a, b, 7),
not checks.is_hidden_parallel(a, b, 0),
not checks.is_hidden_parallel(a, b, 7),
not checks.is_voice_crossing(a, b),
not checks.is_large_leaps(a, b, 7),
))
def unique(it, f=None):
key = f or (lambda x: x)
seen = set()
for item in it:
k = key(item)
if k in seen:
continue
seen.add(k)
yield item
def make_progressions(noterange: SpecificNoteSet, n, scale):
it = OpSeq(
n,
options=possible_chords(noterange, scale=scale),
curr_prev_constraint={-1: no_bad_checks},
i_constraints={
0: lambda chord: chord.root == Note('C'), # I
1: lambda chord: chord.root == Note('F'), # IV
2: lambda chord: chord.root == Note('G'), # V
3: lambda chord: chord.root == Note('C'), # I
},
)
it = (Progression(tuple(snsr.sns for snsr in snsrs)) for snsrs in it)
it = unique(it, f=operator.methodcaller('transpose_unique_key'))
it = sorted(it, key=operator.attrgetter('distance'))
return it
progressions = make_progressions(noterange, n=4, scale=scale)
print(progressions)
# [
# Progression('G3_C4_E4_G4', 'A3_C4_F4_C5', 'G3_D4_G4_B4', 'G3_C4_E4_G4'),
# Progression('C3_G3_C4_E4', 'C3_F3_A3_C4', 'B2_D3_G3_D4', 'C3_G3_C4_E4'),
# Progression('E3_G3_C4_G4', 'F3_C4_F4_A4', 'G3_B3_D4_G4', 'E3_G3_C4_G4'),
# Progression('G3_C4_E4_G4', 'F3_C4_F4_A4', 'G3_B3_D4_G4', 'E3_G3_C4_G4'),
# Progression('C3_G3_C4_E4', 'C3_F3_A3_C4', 'B2_D3_G3_D4', 'C3_E3_G3_C4'),
# Progression('C3_E3_G3_C4', 'F2_C3_F3_A3', 'B2_D3_G3_D4', 'C3_E3_G3_C4'),
# Progression('G2_C3_E3_G3', 'F2_C3_F3_A3', 'B2_D3_G3_D4', 'C3_E3_G3_C4'),
# Progression('C4_E4_G4_C5', 'A3_C4_F4_C5', 'G3_D4_G4_B4', 'G3_C4_E4_G4'),
# Progression('E3_G3_C4_G4', 'F3_C4_F4_A4', 'G3_B3_D4_G4', 'C3_G3_C4_E4'),
# Progression('G3_C4_E4_G4', 'F3_C4_F4_A4', 'G3_B3_D4_G4', 'C3_G3_C4_E4'),
# Progression('C3_E3_G3_C4', 'F2_C3_F3_A3', 'B2_D3_G3_D4', 'C3_G3_C4_E4'),
# Progression('C3_G3_C4_E4', 'C3_F3_A3_C4', 'G2_D3_G3_B3', 'G2_C3_E3_G3'),
# Progression('G2_C3_E3_G3', 'F2_C3_F3_A3', 'B2_D3_G3_D4', 'C3_G3_C4_E4'),
# Progression('C4_E4_G4_C5', 'F3_C4_F4_A4', 'G3_B3_D4_G4', 'E3_G3_C4_G4'),
# Progression('E2_G2_C3_G3', 'F2_C3_F3_A3', 'B2_D3_G3_D4', 'C3_E3_G3_C4'),
# Progression('C4_E4_G4_C5', 'F3_C4_F4_A4', 'G3_B3_D4_G4', 'C3_G3_C4_E4'),
# Progression('E2_G2_C3_G3', 'F2_C3_F3_A3', 'B2_D3_G3_D4', 'C3_G3_C4_E4'),
# ]
Hello,
Is it possible for musiclib to generate voicings for chord progressions, for example I - IV - V - I in the key of C?
Does it check for parallel fifths and parallel octaves?