Added pitch resolving to roman numeral chords

This commit is contained in:
2023-02-08 21:47:44 +02:00
parent 8d49e5d5c2
commit bb928f7c64
3 changed files with 37 additions and 25 deletions

View File

@ -4,7 +4,7 @@ import itertools
import operator
import random
from .defaults import DEFAULT_OPTIONS
from .scale import note_from_pc
from .scale import note_from_pc, midi_to_pitch_class
@dataclass
@ -79,6 +79,7 @@ class Pitch(Event):
pitch_class: int = field(default=None)
octave: int = field(default=None)
modifier: int = field(default=None)
note: int = field(default=None)
def set_note(self, note: int):
@ -118,6 +119,16 @@ class RomanNumeral(Event):
value: str = field(default=None)
chord_type: str = field(default=None)
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
@ -212,7 +223,7 @@ class Ziffers(Sequence):
# Update collected options & default options
self.current.update_new(self.options)
# Resolve note from scale
# Resolve note(s) from scale
if set(("key", "scale")) <= self.options.keys():
key = self.options["key"]
scale = self.options["scale"]
@ -223,6 +234,10 @@ class Ziffers(Sequence):
pcs = self.current.pitch_classes
notes = [pc.set_note(note_from_pc(key, pc.pitch_class, scale)) for pc in pcs]
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
return self.current

View File

@ -114,8 +114,9 @@ class ZiffersTransformer(Transformer):
numeral = items[0].value
if len(items)>1:
name = items[1]
notes = chord_from_roman_numeral(numeral,name)
return RomanNumeral(text=numeral, value=parse_roman(numeral), chord_type=name, notes=notes)
chord_notes = chord_from_roman_numeral(numeral,name)
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))
def chord_name(self,item):

View File

@ -188,18 +188,6 @@ def midi_to_tpc(note: int, key: str | int):
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:
"""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)
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
Args:
@ -223,13 +211,14 @@ def midi_to_pc(note: int, key: str | int, scale: str) -> tuple:
Returns:
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"]
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)
pitch_class = note % 12
octave = midi_to_octave(note) - 5
if scale.upper() == "CHROMATIC":
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:
npc = flats[pitch_class]
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]
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
Args:
@ -256,7 +252,7 @@ def chord_from_roman_numeral(roman: str, name: str = "major", num_octaves: int =
"""
root = parse_roman(roman) - 1
tonic = (DEFAULT_OCTAVE * 12) + root + 12
intervals = CHORDS.get(name,CHORDS["major"])
intervals = CHORDS.get(name, CHORDS["major"])
notes = []
for cur_oct in range(num_octaves):
for iterval in intervals: