From bb928f7c6408ed68d2414c58ec418169a0f3868c Mon Sep 17 00:00:00 2001 From: Miika Alonen Date: Wed, 8 Feb 2023 21:47:44 +0200 Subject: [PATCH] Added pitch resolving to roman numeral chords --- ziffers/classes.py | 19 +++++++++++++++++-- ziffers/mapper.py | 5 +++-- ziffers/scale.py | 38 +++++++++++++++++--------------------- 3 files changed, 37 insertions(+), 25 deletions(-) diff --git a/ziffers/classes.py b/ziffers/classes.py index 611a47b..d5852a5 100644 --- a/ziffers/classes.py +++ b/ziffers/classes.py @@ -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 diff --git a/ziffers/mapper.py b/ziffers/mapper.py index 312ee7b..cf9da26 100644 --- a/ziffers/mapper.py +++ b/ziffers/mapper.py @@ -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): diff --git a/ziffers/scale.py b/ziffers/scale.py index ef43b8f..bc24428 100644 --- a/ziffers/scale.py +++ b/ziffers/scale.py @@ -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: