Refactored roman numeral chords
This commit is contained in:
@ -158,3 +158,23 @@ def test_rest(pattern: str, expected: list):
|
|||||||
)
|
)
|
||||||
def test_ranges(pattern: str, expected: list):
|
def test_ranges(pattern: str, expected: list):
|
||||||
assert collect(zparse(pattern),len(expected)*2,"note") == expected*2
|
assert collect(zparse(pattern),len(expected)*2,"note") == expected*2
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"pattern,expected",
|
||||||
|
[
|
||||||
|
("i ii iii iv v vi vii", [[60, 64, 67], [62, 65, 69], [64, 67, 71], [65, 69, 72], [67, 71, 74], [69, 72, 76], [71, 74, 77]])
|
||||||
|
]
|
||||||
|
)
|
||||||
|
def test_romans(pattern: str, expected: list):
|
||||||
|
assert collect(zparse(pattern),len(expected)*2,"note") == expected*2
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"pattern,expected",
|
||||||
|
[
|
||||||
|
("[: i vi v :]", [[0, 2, 4], [5, 0, 2], [4, 6, 1], [0, 2, 4], [5, 0, 2], [4, 6, 1]]),
|
||||||
|
("i ii iii iv v vi vii", [[0, 2, 4], [1, 3, 5], [2, 4, 6], [3, 5, 0], [4, 6, 1], [5, 0, 2], [6, 1, 3]]),
|
||||||
|
("i^7 i^min iv^6", [[0, 2, 4, 6], [0, 2, 4], [3, 5, 0, 1]])
|
||||||
|
]
|
||||||
|
)
|
||||||
|
def test_romans_pcs(pattern: str, expected: list):
|
||||||
|
assert collect(zparse(pattern),len(expected)*2,"pitches") == expected*2
|
||||||
|
|||||||
@ -6,7 +6,14 @@ import operator
|
|||||||
import random
|
import random
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from .defaults import DEFAULT_OPTIONS
|
from .defaults import DEFAULT_OPTIONS
|
||||||
from .scale import note_from_pc, midi_to_pitch_class, midi_to_freq, get_scale_length
|
from .common import repeat_text
|
||||||
|
from .scale import (
|
||||||
|
note_from_pc,
|
||||||
|
midi_to_pitch_class,
|
||||||
|
midi_to_freq,
|
||||||
|
get_scale_length,
|
||||||
|
chord_from_degree,
|
||||||
|
)
|
||||||
from .common import euclidian_rhythm
|
from .common import euclidian_rhythm
|
||||||
|
|
||||||
|
|
||||||
@ -39,7 +46,7 @@ class Meta:
|
|||||||
setattr(self, key, oct_change)
|
setattr(self, key, oct_change)
|
||||||
elif local_value:
|
elif local_value:
|
||||||
setattr(self, key, value + local_value)
|
setattr(self, key, value + local_value)
|
||||||
else:
|
elif getattr(self, key) is None:
|
||||||
setattr(self, key, value)
|
setattr(self, key, value)
|
||||||
elif getattr(self, key) is None:
|
elif getattr(self, key) is None:
|
||||||
local_value = self.local_options.get(key, False)
|
local_value = self.local_options.get(key, False)
|
||||||
@ -172,6 +179,13 @@ class Pitch(Event):
|
|||||||
if self.text is None:
|
if self.text is None:
|
||||||
self.text = str(self.pitch_class)
|
self.text = str(self.pitch_class)
|
||||||
self.update_note()
|
self.update_note()
|
||||||
|
#self._update_text()
|
||||||
|
|
||||||
|
def _update_text(self):
|
||||||
|
if self.octave is not None:
|
||||||
|
self.text = repeat_text("^","_",self.octave) + self.text
|
||||||
|
if self.modifier is not None:
|
||||||
|
self.text = repeat_text("#","b",self.modifier) + self.text
|
||||||
|
|
||||||
def get_note(self):
|
def get_note(self):
|
||||||
"""Getter for note"""
|
"""Getter for note"""
|
||||||
@ -295,6 +309,11 @@ class Chord(Event):
|
|||||||
if self.inversions is not None:
|
if self.inversions is not None:
|
||||||
self.invert(self.inversions)
|
self.invert(self.inversions)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def note(self):
|
||||||
|
"""Synonym for notes"""
|
||||||
|
return self.notes
|
||||||
|
|
||||||
def set_notes(self, notes: list[int]):
|
def set_notes(self, notes: list[int]):
|
||||||
"""Set notes to the class"""
|
"""Set notes to the class"""
|
||||||
self.notes = notes
|
self.notes = notes
|
||||||
@ -336,7 +355,7 @@ class Chord(Event):
|
|||||||
|
|
||||||
self.pitch_classes = new_pitches
|
self.pitch_classes = new_pitches
|
||||||
|
|
||||||
def update_notes(self, options):
|
def update_notes(self, options, force=False):
|
||||||
"""Update notes"""
|
"""Update notes"""
|
||||||
pitches, notes, freqs, octaves, durations, beats = ([] for _ in range(6))
|
pitches, notes, freqs, octaves, durations, beats = ([] for _ in range(6))
|
||||||
|
|
||||||
@ -371,9 +390,9 @@ class RomanNumeral(Event):
|
|||||||
|
|
||||||
value: str = field(default=None)
|
value: str = field(default=None)
|
||||||
chord_type: str = field(default=None)
|
chord_type: str = field(default=None)
|
||||||
notes: list[int] = field(default_factory=[])
|
notes: list[int] = field(default=None, init=False)
|
||||||
pitch_classes: list = None
|
pitch_classes: list = field(default=None, init=False)
|
||||||
inversions: int = None
|
inversions: int = field(default=None)
|
||||||
|
|
||||||
def set_notes(self, chord_notes: list[int]):
|
def set_notes(self, chord_notes: list[int]):
|
||||||
"""Set notes to roman numeral
|
"""Set notes to roman numeral
|
||||||
@ -623,30 +642,24 @@ class Sequence(Meta):
|
|||||||
scale = options["scale"]
|
scale = options["scale"]
|
||||||
pitch_text = ""
|
pitch_text = ""
|
||||||
pitch_classes = []
|
pitch_classes = []
|
||||||
chord_notes = []
|
current.notes = chord_from_degree(
|
||||||
|
current.value, current.chord_type, options["scale"], options["key"]
|
||||||
|
)
|
||||||
for note in current.notes:
|
for note in current.notes:
|
||||||
pitch_dict = midi_to_pitch_class(note, key, scale)
|
pitch_dict = midi_to_pitch_class(note, key, scale)
|
||||||
pitch_classes.append(
|
pitch_classes.append(
|
||||||
Pitch(
|
Pitch(
|
||||||
pitch_class=pitch_dict["pitch_class"],
|
pitch_class=pitch_dict["pitch_class"],
|
||||||
kwargs=(pitch_dict | options),
|
note=note,
|
||||||
|
kwargs=(options | pitch_dict),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
pitch_text += pitch_dict["text"]
|
pitch_text += pitch_dict["text"]
|
||||||
chord_notes.append(
|
|
||||||
note_from_pc(
|
|
||||||
root=key,
|
|
||||||
pitch_class=pitch_dict["pitch_class"],
|
|
||||||
intervals=scale,
|
|
||||||
modifier=pitch_dict.get("modifier", 0),
|
|
||||||
octave=pitch_dict.get("octave", 0),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
chord = Chord(
|
chord = Chord(
|
||||||
text=pitch_text,
|
text=pitch_text,
|
||||||
pitch_classes=pitch_classes,
|
pitch_classes=pitch_classes,
|
||||||
notes=chord_notes,
|
notes=current.notes,
|
||||||
kwargs=options,
|
kwargs=options,
|
||||||
inversions=current.inversions,
|
inversions=current.inversions,
|
||||||
)
|
)
|
||||||
@ -776,16 +789,24 @@ class Ziffers(Sequence):
|
|||||||
def notes(self) -> list[int]:
|
def notes(self) -> list[int]:
|
||||||
"""Return list of midi notes"""
|
"""Return list of midi notes"""
|
||||||
return [
|
return [
|
||||||
val.get_note() for val in self.evaluated_values if isinstance(val, (Pitch, Chord))
|
val.get_note()
|
||||||
|
for val in self.evaluated_values
|
||||||
|
if isinstance(val, (Pitch, Chord))
|
||||||
]
|
]
|
||||||
|
|
||||||
def durations(self) -> list[float]:
|
def durations(self) -> list[float]:
|
||||||
"""Return list of pitch durations as floats"""
|
"""Return list of pitch durations as floats"""
|
||||||
return [val.get_duration() for val in self.evaluated_values if isinstance(val, Event)]
|
return [
|
||||||
|
val.get_duration()
|
||||||
|
for val in self.evaluated_values
|
||||||
|
if isinstance(val, Event)
|
||||||
|
]
|
||||||
|
|
||||||
def beats(self) -> list[float]:
|
def beats(self) -> list[float]:
|
||||||
"""Return list of pitch durations as floats"""
|
"""Return list of pitch durations as floats"""
|
||||||
return [val.get_beat() for val in self.evaluated_values if isinstance(val, Event)]
|
return [
|
||||||
|
val.get_beat() for val in self.evaluated_values if isinstance(val, Event)
|
||||||
|
]
|
||||||
|
|
||||||
def pairs(self) -> list[tuple]:
|
def pairs(self) -> list[tuple]:
|
||||||
"""Return list of pitches and durations"""
|
"""Return list of pitches and durations"""
|
||||||
@ -806,7 +827,9 @@ class Ziffers(Sequence):
|
|||||||
def freqs(self) -> list[int]:
|
def freqs(self) -> list[int]:
|
||||||
"""Return list of octaves"""
|
"""Return list of octaves"""
|
||||||
return [
|
return [
|
||||||
val.get_freq() for val in self.evaluated_values if isinstance(val, (Pitch, Chord))
|
val.get_freq()
|
||||||
|
for val in self.evaluated_values
|
||||||
|
if isinstance(val, (Pitch, Chord))
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@ -1110,6 +1133,8 @@ class RepeatedSequence(Sequence):
|
|||||||
yield item.get_updated_item(self.local_options)
|
yield item.get_updated_item(self.local_options)
|
||||||
elif isinstance(item, Range):
|
elif isinstance(item, Range):
|
||||||
yield from item.evaluate(self.local_options)
|
yield from item.evaluate(self.local_options)
|
||||||
|
elif isinstance(item, (Pitch, Chord, RomanNumeral)):
|
||||||
|
yield item
|
||||||
elif isinstance(item, (Event, RandomInteger)):
|
elif isinstance(item, (Event, RandomInteger)):
|
||||||
yield Pitch(
|
yield Pitch(
|
||||||
pitch_class=item.get_value(self.local_options),
|
pitch_class=item.get_value(self.local_options),
|
||||||
|
|||||||
@ -10,6 +10,7 @@ def flatten(arr: list) -> list:
|
|||||||
else [arr]
|
else [arr]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def rotate(arr, k):
|
def rotate(arr, k):
|
||||||
"""Rotates array"""
|
"""Rotates array"""
|
||||||
# Calculate the effective rotation amount (mod the array length)
|
# Calculate the effective rotation amount (mod the array length)
|
||||||
@ -22,6 +23,13 @@ def rotate(arr, k):
|
|||||||
arr = arr[-k:] + arr[:-k]
|
arr = arr[-k:] + arr[:-k]
|
||||||
return arr
|
return arr
|
||||||
|
|
||||||
|
def repeat_text(pos,neg,times):
|
||||||
|
"""Helper to repeat text"""
|
||||||
|
if times>0:
|
||||||
|
return pos*times
|
||||||
|
if times<0:
|
||||||
|
return neg*abs(times)
|
||||||
|
return ""
|
||||||
|
|
||||||
def sum_dict(arr: list[dict]) -> dict:
|
def sum_dict(arr: list[dict]) -> dict:
|
||||||
"""Sums a list of dicts: [{a:3,b:3},{b:1}] -> {a:3,b:4}"""
|
"""Sums a list of dicts: [{a:3,b:3},{b:1}] -> {a:3,b:4}"""
|
||||||
@ -67,7 +75,7 @@ def string_rewrite(axiom: str, rules: dict):
|
|||||||
return pattern.sub(lambda m: next(_apply_rules(m)), axiom)
|
return pattern.sub(lambda m: next(_apply_rules(m)), axiom)
|
||||||
|
|
||||||
|
|
||||||
def euclidian_rhythm(pulses: int, length: int, rotate: int = 0):
|
def euclidian_rhythm(pulses: int, length: int, rot: int = 0):
|
||||||
"""Calculate Euclidean rhythms. Original algorithm by Thomas Morrill."""
|
"""Calculate Euclidean rhythms. Original algorithm by Thomas Morrill."""
|
||||||
|
|
||||||
def _starts_descent(arr, index):
|
def _starts_descent(arr, index):
|
||||||
@ -84,5 +92,4 @@ def euclidian_rhythm(pulses: int, length: int, rotate: int = 0):
|
|||||||
res_list = [pulses * t % length for t in range(-1, length - 1)]
|
res_list = [pulses * t % length for t in range(-1, length - 1)]
|
||||||
bool_list = [_starts_descent(res_list, index) for index in range(length)]
|
bool_list = [_starts_descent(res_list, index) for index in range(length)]
|
||||||
|
|
||||||
return rotation(bool_list, rotate)
|
return rotation(bool_list, rot)
|
||||||
|
|
||||||
|
|||||||
@ -1729,7 +1729,6 @@ def __build_chords():
|
|||||||
"a": aug,
|
"a": aug,
|
||||||
"aug": aug,
|
"aug": aug,
|
||||||
"diminished": dim,
|
"diminished": dim,
|
||||||
"dim": dim,
|
|
||||||
"i": dim,
|
"i": dim,
|
||||||
"diminished7": dim7,
|
"diminished7": dim7,
|
||||||
"dim7": dim7,
|
"dim7": dim7,
|
||||||
|
|||||||
@ -29,11 +29,11 @@ from .classes import (
|
|||||||
RepeatedSequence,
|
RepeatedSequence,
|
||||||
VariableAssignment,
|
VariableAssignment,
|
||||||
Variable,
|
Variable,
|
||||||
Measure
|
Measure,
|
||||||
)
|
)
|
||||||
from .common import flatten, sum_dict
|
from .common import flatten, sum_dict
|
||||||
from .defaults import DEFAULT_DURS, OPERATORS
|
from .defaults import DEFAULT_DURS, OPERATORS
|
||||||
from .scale import parse_roman, chord_from_roman_numeral
|
from .scale import parse_roman
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=locally-disabled, unused-argument, too-many-public-methods, invalid-name
|
# pylint: disable=locally-disabled, unused-argument, too-many-public-methods, invalid-name
|
||||||
@ -175,17 +175,15 @@ class ZiffersTransformer(Transformer):
|
|||||||
return Chord(pitch_classes=items, text="".join([val.text for val in items]))
|
return Chord(pitch_classes=items, text="".join([val.text for val in items]))
|
||||||
|
|
||||||
def invert(self, items):
|
def invert(self, items):
|
||||||
|
"""Return chord inversion"""
|
||||||
return items[0]
|
return items[0]
|
||||||
|
|
||||||
def named_roman(self, items) -> RomanNumeral:
|
def named_roman(self, items) -> RomanNumeral:
|
||||||
"""Parse chord from roman numeral"""
|
"""Parse chord from roman numeral"""
|
||||||
numeral = items[0].value
|
numeral = items[0].value
|
||||||
|
# TODO: Refactor this and the rule
|
||||||
if len(items) == 1:
|
if len(items) == 1:
|
||||||
return RomanNumeral(
|
return RomanNumeral(value=parse_roman(numeral), text=numeral)
|
||||||
value=parse_roman(numeral),
|
|
||||||
text=numeral,
|
|
||||||
notes=chord_from_roman_numeral(numeral),
|
|
||||||
)
|
|
||||||
if len(items) > 2:
|
if len(items) > 2:
|
||||||
name = items[1]
|
name = items[1]
|
||||||
inversions = int(items[-1].value[1:])
|
inversions = int(items[-1].value[1:])
|
||||||
@ -193,7 +191,6 @@ class ZiffersTransformer(Transformer):
|
|||||||
text=numeral,
|
text=numeral,
|
||||||
value=parse_roman(numeral),
|
value=parse_roman(numeral),
|
||||||
chord_type=name,
|
chord_type=name,
|
||||||
notes=chord_from_roman_numeral(numeral, name),
|
|
||||||
inversions=inversions,
|
inversions=inversions,
|
||||||
)
|
)
|
||||||
elif len(items) == 2:
|
elif len(items) == 2:
|
||||||
@ -202,15 +199,11 @@ class ZiffersTransformer(Transformer):
|
|||||||
return RomanNumeral(
|
return RomanNumeral(
|
||||||
value=parse_roman(numeral),
|
value=parse_roman(numeral),
|
||||||
text=numeral,
|
text=numeral,
|
||||||
notes=chord_from_roman_numeral(numeral),
|
|
||||||
inversions=inversions,
|
inversions=inversions,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
return RomanNumeral(
|
return RomanNumeral(
|
||||||
value=parse_roman(numeral),
|
value=parse_roman(numeral), text=numeral, chord_type=items[1]
|
||||||
text=numeral,
|
|
||||||
chord_type=items[1],
|
|
||||||
notes=chord_from_roman_numeral(numeral),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def chord_name(self, item):
|
def chord_name(self, item):
|
||||||
@ -219,7 +212,7 @@ class ZiffersTransformer(Transformer):
|
|||||||
|
|
||||||
def roman_number(self, item):
|
def roman_number(self, item):
|
||||||
"""Return roman numeral"""
|
"""Return roman numeral"""
|
||||||
return item.value
|
return item[0]
|
||||||
|
|
||||||
def dur_change(self, items):
|
def dur_change(self, items):
|
||||||
"""Parses duration change"""
|
"""Parses duration change"""
|
||||||
|
|||||||
@ -3,8 +3,8 @@
|
|||||||
# pylint: disable=locally-disabled, no-name-in-module
|
# pylint: disable=locally-disabled, no-name-in-module
|
||||||
import re
|
import re
|
||||||
from math import floor
|
from math import floor
|
||||||
|
from .common import repeat_text
|
||||||
from .defaults import (
|
from .defaults import (
|
||||||
DEFAULT_OCTAVE,
|
|
||||||
SCALES,
|
SCALES,
|
||||||
MODIFIERS,
|
MODIFIERS,
|
||||||
NOTES_TO_INTERVALS,
|
NOTES_TO_INTERVALS,
|
||||||
@ -44,11 +44,13 @@ def note_name_to_interval(name: str) -> int:
|
|||||||
interval = NOTES_TO_INTERVALS[values[0].capitalize()]
|
interval = NOTES_TO_INTERVALS[values[0].capitalize()]
|
||||||
return interval + modifier
|
return interval + modifier
|
||||||
|
|
||||||
|
|
||||||
def midi_to_freq(note: int) -> float:
|
def midi_to_freq(note: int) -> float:
|
||||||
"""Transform midi to frequency"""
|
"""Transform midi to frequency"""
|
||||||
freq = 440 # Frequency of A
|
freq = 440 # Frequency of A
|
||||||
return (freq / 32) * (2 ** ((note - 9) / 12))
|
return (freq / 32) * (2 ** ((note - 9) / 12))
|
||||||
|
|
||||||
|
|
||||||
def note_name_to_midi(name: str) -> int:
|
def note_name_to_midi(name: str) -> int:
|
||||||
"""Parse note name to midi
|
"""Parse note name to midi
|
||||||
|
|
||||||
@ -80,6 +82,45 @@ def get_scale(name: str) -> list[int]:
|
|||||||
scale = SCALES.get(name.lower().capitalize(), SCALES["Ionian"])
|
scale = SCALES.get(name.lower().capitalize(), SCALES["Ionian"])
|
||||||
return scale
|
return scale
|
||||||
|
|
||||||
|
|
||||||
|
def get_scale_notes(name: str, root: int = 60, num_octaves: int = 1) -> list[int]:
|
||||||
|
"""Return notes for the scale
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name (str): Name of the scale
|
||||||
|
root (int, optional): Root note. Defaults to 60.
|
||||||
|
num_octaves (int, optional): Number of octaves. Defaults to 1.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list[int]: List of notes
|
||||||
|
"""
|
||||||
|
scale = get_scale(name)
|
||||||
|
scale_notes = [root]
|
||||||
|
for _ in range(num_octaves):
|
||||||
|
scale_notes = scale_notes + [root := root + semitone for semitone in scale]
|
||||||
|
return scale_notes
|
||||||
|
|
||||||
|
|
||||||
|
def get_chord_from_scale(
|
||||||
|
degree: int, root: int = 60, scale: str = "Major", num_notes: int = 3, skip: int = 2
|
||||||
|
) -> list[int]:
|
||||||
|
"""Generate chord from the scale by skipping notes
|
||||||
|
|
||||||
|
Args:
|
||||||
|
degree (int): Degree of scale to start on
|
||||||
|
root (int, optional): Root for the scale. Defaults to 60.
|
||||||
|
scale (str, optional): Name of the scale. Defaults to "Major".
|
||||||
|
num_notes (int, optional): Number of notes. Defaults to 3.
|
||||||
|
skip (int, optional): Takes every n from the scale. Defaults to 2.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list[int]: List of midi notes
|
||||||
|
"""
|
||||||
|
num_of_octaves = ((num_notes * skip + degree) // get_scale_length(scale)) + 1
|
||||||
|
scale_notes = get_scale_notes(scale, root, num_of_octaves)
|
||||||
|
return scale_notes[degree - 1 :: skip][:num_notes]
|
||||||
|
|
||||||
|
|
||||||
def get_scale_length(name: str) -> int:
|
def get_scale_length(name: str) -> int:
|
||||||
"""Get length of the scale
|
"""Get length of the scale
|
||||||
|
|
||||||
@ -92,6 +133,7 @@ def get_scale_length(name: str) -> int:
|
|||||||
scale = SCALES.get(name.lower().capitalize(), SCALES["Ionian"])
|
scale = SCALES.get(name.lower().capitalize(), SCALES["Ionian"])
|
||||||
return len(scale)
|
return len(scale)
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=locally-disabled, too-many-arguments
|
# pylint: disable=locally-disabled, too-many-arguments
|
||||||
def note_from_pc(
|
def note_from_pc(
|
||||||
root: int | str,
|
root: int | str,
|
||||||
@ -242,18 +284,33 @@ def midi_to_pitch_class(note: int, key: str | int, scale: str) -> dict:
|
|||||||
npc = sharps[pitch_class]
|
npc = sharps[pitch_class]
|
||||||
|
|
||||||
if len(npc) > 1:
|
if len(npc) > 1:
|
||||||
|
modifier = 1 if (npc[0] == "#") else -1
|
||||||
return {
|
return {
|
||||||
"text": npc,
|
"text": repeat_text("^", "_", octave)+npc,
|
||||||
"pitch_class": int(npc[1]),
|
"pitch_class": int(npc[1]),
|
||||||
"octave": octave,
|
"octave": octave,
|
||||||
"modifier": 1 if (npc[0] == "#") else -1,
|
"modifier": modifier,
|
||||||
}
|
}
|
||||||
|
|
||||||
return {"text": npc, "pitch_class": int(npc), "octave": octave}
|
return {
|
||||||
|
"text": repeat_text("^", "_", octave)+npc,
|
||||||
|
"pitch_class": int(npc),
|
||||||
|
"octave": octave,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def chord_from_roman_numeral(
|
def chord_from_degree(
|
||||||
roman: str, name: str = "major", num_octaves: int = 1
|
degree: int, name: str, scale: str, root: str | int, num_octaves: int = 1
|
||||||
|
):
|
||||||
|
root = note_name_to_midi(root) if isinstance(root, str) else root
|
||||||
|
if name:
|
||||||
|
return named_chord_from_degree(degree, name, root, scale, num_octaves)
|
||||||
|
else:
|
||||||
|
return get_chord_from_scale(degree, root, scale)
|
||||||
|
|
||||||
|
|
||||||
|
def named_chord_from_degree(
|
||||||
|
degree: int, name: str = "major", root: int = 60, scale: str="Major", num_octaves: int = 1
|
||||||
) -> list[int]:
|
) -> list[int]:
|
||||||
"""Generates chord from given roman numeral and chord name
|
"""Generates chord from given roman numeral and chord name
|
||||||
|
|
||||||
@ -265,11 +322,10 @@ def chord_from_roman_numeral(
|
|||||||
Returns:
|
Returns:
|
||||||
list[int]: _description_
|
list[int]: _description_
|
||||||
"""
|
"""
|
||||||
root = parse_roman(roman) - 1
|
|
||||||
tonic = (DEFAULT_OCTAVE * 12) + root + 12
|
|
||||||
intervals = CHORDS.get(name, CHORDS["major"])
|
intervals = CHORDS.get(name, CHORDS["major"])
|
||||||
|
scale_degree = get_scale_notes(scale, root)[degree-1]
|
||||||
notes = []
|
notes = []
|
||||||
for cur_oct in range(num_octaves):
|
for cur_oct in range(num_octaves):
|
||||||
for iterval in intervals:
|
for interval in intervals:
|
||||||
notes.append(tonic + iterval + (cur_oct * 12))
|
notes.append(scale_degree + interval + (cur_oct * 12))
|
||||||
return notes
|
return notes
|
||||||
|
|||||||
@ -31,7 +31,7 @@
|
|||||||
chord: pitch_class pitch_class+ invert?
|
chord: pitch_class pitch_class+ invert?
|
||||||
named_roman: roman_number (("^" chord_name))? invert? // TODO: Add | ("+" number)
|
named_roman: roman_number (("^" chord_name))? invert? // TODO: Add | ("+" number)
|
||||||
chord_name: /[a-zA-Z0-9]+/
|
chord_name: /[a-zA-Z0-9]+/
|
||||||
?roman_number: /iv|v|v?i{1,3}/
|
!roman_number: "i" | "ii" | "iii" | "iv" | "v" | "vi" | "vii"
|
||||||
|
|
||||||
invert: /%-?[0-9][0-9]*/
|
invert: /%-?[0-9][0-9]*/
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user