Source code for pychord.chord

from typing import List, Union

from .constants import NOTE_VAL_DICT, VAL_NOTE_DICT
from .constants.scales import RELATIVE_KEY_DICT
from .parser import parse
from .quality import QualityManager, Quality
from .utils import transpose_note, display_appended, display_on, note_to_val, val_to_note

[docs]class Chord: """ Class to handle a chord. Attributes: _chord: Name of the chord. (e.g. C, Am7, F#m7-5/A) _root: The root note of chord. (e.g. C, A, F#) _quality: The quality of chord. (e.g. maj, m7, m7-5) _appended: The appended notes on chord. _on: The base note of slash chord. """ def __init__(self, chord: str): """ Constructor of Chord instance :param chord: Name of chord (e.g. C, Am7, F#m7-5/A). """ root, quality, appended, on = parse(chord) self._chord: str = chord self._root: str = root self._quality: Quality = quality self._appended: List[str] = appended self._on: str = on def __unicode__(self): return self._chord def __str__(self): return self._chord def __repr__(self): return f"<Chord: {self._chord}>" def __eq__(self, other): if not isinstance(other, Chord): raise TypeError(f"Cannot compare Chord object with {type(other)} object") if note_to_val(self._root) != note_to_val(other.root): return False if self._quality != other.quality: return False if self._appended != other.appended: return False if self._on and other.on: if note_to_val(self._on) != note_to_val(other.on): return False return True def __ne__(self, other): return not self.__eq__(other)
[docs] @classmethod def from_note_index(cls, note: int, quality: str, scale: str, diatonic: bool = False) -> 'Chord': """ Create a Chord from note index in a scale Chord.from_note_index(1, "", "Cmaj") returns I of C major => Chord("C") Chord.from_note_index(3, "m7", "Fmaj") returns IIImin of F major => Chord("Am7") Chord.from_note_index(5, "7", "Amin") returns Vmin of A minor => Chord("E7") :param note: Note index in a Scale I, II, ..., VIII :param quality: Quality of a chord (m7, sus4, ...) :param scale: Base scale (Cmaj, Amin, F#maj, Ebmin, ...) :param diatonic: Adjust certain chord qualities according to the scale """ if not 1 <= note <= 8: raise ValueError(f"Invalid note {note}") relative_key = RELATIVE_KEY_DICT[scale[-3:]][note - 1] root_num = NOTE_VAL_DICT[scale[:-3]] root = VAL_NOTE_DICT[(root_num + relative_key) % 12][0] scale_degrees = RELATIVE_KEY_DICT[scale[-3:]] if diatonic: # construct the chord based on scale degrees, within 1 octave third = scale_degrees[(note + 1) % 7] fifth = scale_degrees[(note + 3) % 7] seventh = scale_degrees[(note + 5) % 7] # adjust the chord to its root position (as a stack of thirds), # then set the root to 0 def get_diatonic_chord(chord): uninverted = [] for note in chord: if not uninverted: uninverted.append(note) elif note > uninverted[-1]: uninverted.append(note) else: uninverted.append(note + 12) uninverted = [x - uninverted[0] for x in uninverted] return uninverted if quality in ["", "-", "maj", "m", "min"]: triad = (relative_key, third, fifth) q = get_diatonic_chord(triad) elif quality in ["7", "M7", "maj7", "m7"]: seventh_chord = (relative_key, third, fifth, seventh) q = get_diatonic_chord(seventh_chord) else: raise NotImplementedError("Only generic chords (triads, sevenths) are supported") # look up QualityManager to determine chord quality quality_manager = QualityManager() quality = quality_manager.find_quality_from_components(q) if not quality: raise RuntimeError(f"Quality with components {q} not found") return cls(f"{root}{quality}")
@property def chord(self): """ The name of chord """ return self._chord @property def root(self): """ The root note of chord """ return self._root @property def quality(self): """ The quality of chord """ return self._quality @property def appended(self): """ The appended notes on chord """ return self._appended @property def on(self): """ The base note of slash chord """ return self._on
[docs] def info(self): """ Return information of chord to display """ return f"""{self._chord} root={self._root} quality={self._quality} appended={self._appended} on={self._on}"""
[docs] def transpose(self, trans: int, scale: str = "C") -> None: """ Transpose the chord :param trans: Transpose key :param scale: key scale """ if not isinstance(trans, int): raise TypeError(f"Expected integers, not {type(trans)}") self._root = transpose_note(self._root, trans, scale) if self._on: self._on = transpose_note(self._on, trans, scale) self._reconfigure_chord()
[docs] def components(self, visible: bool = True) -> Union[List[str], List[int]]: """ Return the component notes of chord :param visible: returns the name of notes if True else list of int :return: component notes of chord """ if self._on: self._quality.append_on_chord(self.on, self.root) return self._quality.get_components(root=self._root, visible=visible)
[docs] def components_with_pitch(self, root_pitch: int) -> List[str]: """ Return the component notes of chord formatted like ["C4", "E4", "G4"] :param root_pitch: the pitch of the root note :return: component notes of chord """ if self._on: self._quality.append_on_chord(self.on, self.root) components = self._quality.get_components(root=self._root) if components[0] < 0: components = [c + 12 for c in components] return [f"{val_to_note(c, scale=self._root)}{root_pitch + c // 12}" for c in components]
def _reconfigure_chord(self): # TODO: Use appended self._chord = "{}{}{}{}".format(self._root, self._quality.quality, display_appended(self._appended), display_on(self._on))