Added pitch resolving to roman numeral chords
This commit is contained in:
@ -4,7 +4,7 @@ import itertools
|
|||||||
import operator
|
import operator
|
||||||
import random
|
import random
|
||||||
from .defaults import DEFAULT_OPTIONS
|
from .defaults import DEFAULT_OPTIONS
|
||||||
from .scale import note_from_pc
|
from .scale import note_from_pc, midi_to_pitch_class
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@ -79,6 +79,7 @@ class Pitch(Event):
|
|||||||
|
|
||||||
pitch_class: int = field(default=None)
|
pitch_class: int = field(default=None)
|
||||||
octave: int = field(default=None)
|
octave: int = field(default=None)
|
||||||
|
modifier: int = field(default=None)
|
||||||
note: int = field(default=None)
|
note: int = field(default=None)
|
||||||
|
|
||||||
def set_note(self, note: int):
|
def set_note(self, note: int):
|
||||||
@ -118,6 +119,16 @@ 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_factory=[])
|
||||||
|
pitch_classes: list = None
|
||||||
|
|
||||||
|
def set_notes(self, chord_notes: list[int]):
|
||||||
|
self.notes = chord_notes
|
||||||
|
|
||||||
|
def set_pitch_classes(self, pitches: list[tuple]):
|
||||||
|
if self.pitch_classes == None:
|
||||||
|
self.pitch_classes = []
|
||||||
|
for pitch in pitches:
|
||||||
|
self.pitch_classes.append(Pitch(**pitch))
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@ -212,7 +223,7 @@ class Ziffers(Sequence):
|
|||||||
# Update collected options & default options
|
# Update collected options & default options
|
||||||
self.current.update_new(self.options)
|
self.current.update_new(self.options)
|
||||||
|
|
||||||
# Resolve note from scale
|
# Resolve note(s) from scale
|
||||||
if set(("key", "scale")) <= self.options.keys():
|
if set(("key", "scale")) <= self.options.keys():
|
||||||
key = self.options["key"]
|
key = self.options["key"]
|
||||||
scale = self.options["scale"]
|
scale = self.options["scale"]
|
||||||
@ -223,6 +234,10 @@ class Ziffers(Sequence):
|
|||||||
pcs = self.current.pitch_classes
|
pcs = self.current.pitch_classes
|
||||||
notes = [pc.set_note(note_from_pc(key, pc.pitch_class, scale)) for pc in pcs]
|
notes = [pc.set_note(note_from_pc(key, pc.pitch_class, scale)) for pc in pcs]
|
||||||
self.current.set_notes(notes)
|
self.current.set_notes(notes)
|
||||||
|
elif isinstance(self.current,RomanNumeral):
|
||||||
|
pitch_classes = [midi_to_pitch_class(note, key, scale) for note in self.current.notes]
|
||||||
|
self.current.set_pitch_classes(pitch_classes)
|
||||||
|
|
||||||
|
|
||||||
self.loop_i += 1
|
self.loop_i += 1
|
||||||
return self.current
|
return self.current
|
||||||
|
|||||||
@ -114,8 +114,9 @@ class ZiffersTransformer(Transformer):
|
|||||||
numeral = items[0].value
|
numeral = items[0].value
|
||||||
if len(items)>1:
|
if len(items)>1:
|
||||||
name = items[1]
|
name = items[1]
|
||||||
notes = chord_from_roman_numeral(numeral,name)
|
chord_notes = chord_from_roman_numeral(numeral,name)
|
||||||
return RomanNumeral(text=numeral, value=parse_roman(numeral), chord_type=name, notes=notes)
|
parsed_number = parse_roman(numeral)
|
||||||
|
return RomanNumeral(text=numeral, value=parsed_number, chord_type=name, notes=chord_notes)
|
||||||
return RomanNumeral(value=parse_roman(numeral), text=numeral, notes=chord_from_roman_numeral(numeral))
|
return RomanNumeral(value=parse_roman(numeral), text=numeral, notes=chord_from_roman_numeral(numeral))
|
||||||
|
|
||||||
def chord_name(self,item):
|
def chord_name(self,item):
|
||||||
|
|||||||
@ -188,18 +188,6 @@ def midi_to_tpc(note: int, key: str | int):
|
|||||||
return (note * 7 + 26 - (11 + acc)) % 12 + (11 + acc)
|
return (note * 7 + 26 - (11 + acc)) % 12 + (11 + acc)
|
||||||
|
|
||||||
|
|
||||||
def midi_to_pitch_class(note: int) -> int:
|
|
||||||
"""Return pitch class from midi
|
|
||||||
|
|
||||||
Args:
|
|
||||||
note (int): Note in midi
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
int: Returns note % 12
|
|
||||||
"""
|
|
||||||
return note % 12
|
|
||||||
|
|
||||||
|
|
||||||
def midi_to_octave(note: int) -> int:
|
def midi_to_octave(note: int) -> int:
|
||||||
"""Return octave for the midi note
|
"""Return octave for the midi note
|
||||||
|
|
||||||
@ -212,7 +200,7 @@ def midi_to_octave(note: int) -> int:
|
|||||||
return 0 if note <= 0 else floor(note / 12)
|
return 0 if note <= 0 else floor(note / 12)
|
||||||
|
|
||||||
|
|
||||||
def midi_to_pc(note: int, key: str | int, scale: str) -> tuple:
|
def midi_to_pitch_class(note: int, key: str | int, scale: str) -> dict:
|
||||||
"""Return pitch class and octave from given midi note, key and scale
|
"""Return pitch class and octave from given midi note, key and scale
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -223,13 +211,14 @@ def midi_to_pc(note: int, key: str | int, scale: str) -> tuple:
|
|||||||
Returns:
|
Returns:
|
||||||
tuple: Returns tuple containing (pitch class as string, pitch class, octave, optional modifier)
|
tuple: Returns tuple containing (pitch class as string, pitch class, octave, optional modifier)
|
||||||
"""
|
"""
|
||||||
sharps = ["0", "#0", "1", "#1", "2", "3", "#3", "4", "#4", "5", "#5", "6"]
|
pitch_class = note % 12
|
||||||
flats = ["0", "b1", "1", "b2", "2", "3", "b4", "4", "b5", "5", "b6", "6"]
|
|
||||||
tpc = midi_to_tpc(note, key)
|
|
||||||
pitch_class = midi_to_pitch_class(note)
|
|
||||||
octave = midi_to_octave(note) - 5
|
octave = midi_to_octave(note) - 5
|
||||||
if scale.upper() == "CHROMATIC":
|
if scale.upper() == "CHROMATIC":
|
||||||
return (str(pitch_class), pitch_class, octave)
|
return (str(pitch_class), pitch_class, octave)
|
||||||
|
|
||||||
|
sharps = ["0", "#0", "1", "#1", "2", "3", "#3", "4", "#4", "5", "#5", "6"]
|
||||||
|
flats = ["0", "b1", "1", "b2", "2", "3", "b4", "4", "b5", "5", "b6", "6"]
|
||||||
|
tpc = midi_to_tpc(note, key)
|
||||||
if tpc >= 6 and tpc <= 12 and len(flats[pitch_class]) == 2:
|
if tpc >= 6 and tpc <= 12 and len(flats[pitch_class]) == 2:
|
||||||
npc = flats[pitch_class]
|
npc = flats[pitch_class]
|
||||||
elif tpc >= 20 and tpc <= 26 and len(sharps[pitch_class]) == 2:
|
elif tpc >= 20 and tpc <= 26 and len(sharps[pitch_class]) == 2:
|
||||||
@ -238,12 +227,19 @@ def midi_to_pc(note: int, key: str | int, scale: str) -> tuple:
|
|||||||
npc = sharps[pitch_class]
|
npc = sharps[pitch_class]
|
||||||
|
|
||||||
if len(npc) > 1:
|
if len(npc) > 1:
|
||||||
return (npc, int(npc[1]), octave, 1 if (npc[0] == "#") else -1)
|
return {
|
||||||
|
"text": npc,
|
||||||
|
"pitch_class": int(npc[1]),
|
||||||
|
"octave": octave,
|
||||||
|
"modifier": 1 if (npc[0] == "#") else -1,
|
||||||
|
}
|
||||||
|
|
||||||
return (npc, int(npc), octave)
|
return {"text": npc, "pitch_class": int(npc), "octave": octave}
|
||||||
|
|
||||||
|
|
||||||
def chord_from_roman_numeral(roman: str, name: str = "major", num_octaves: int = 1) -> list[int]:
|
def chord_from_roman_numeral(
|
||||||
|
roman: str, name: str = "major", num_octaves: int = 1
|
||||||
|
) -> list[int]:
|
||||||
"""Generates chord from given roman numeral and chord name
|
"""Generates chord from given roman numeral and chord name
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -256,7 +252,7 @@ def chord_from_roman_numeral(roman: str, name: str = "major", num_octaves: int =
|
|||||||
"""
|
"""
|
||||||
root = parse_roman(roman) - 1
|
root = parse_roman(roman) - 1
|
||||||
tonic = (DEFAULT_OCTAVE * 12) + root + 12
|
tonic = (DEFAULT_OCTAVE * 12) + root + 12
|
||||||
intervals = CHORDS.get(name,CHORDS["major"])
|
intervals = CHORDS.get(name, CHORDS["major"])
|
||||||
notes = []
|
notes = []
|
||||||
for cur_oct in range(num_octaves):
|
for cur_oct in range(num_octaves):
|
||||||
for iterval in intervals:
|
for iterval in intervals:
|
||||||
|
|||||||
Reference in New Issue
Block a user