def from_name(cls, name: str, octave: int = 4) -> "ChordWithRoot": """Creates a ChordWithRoot from a name. `semitones_to_chord_name_options` is used to guess a good name for the chord. """ root = None for letter in LETTERS: if name.startswith(letter): root_no_accidental = letter break else: raise InvalidChord(name) # Very special case: major chord with no accidentals if len(name) == len(root_no_accidental): return cls(name, Note(root_no_accidental, octave), Chord.from_name("major")) possibly_accidental = name[len(root_no_accidental)] if possibly_accidental in ACCIDENTALS: root = Note(root_no_accidental + possibly_accidental, octave) else: root = Note(root_no_accidental, octave) name_without_root = name[len(root.name):] has_slash = "/" in name_without_root if has_slash: name_without_root, bass = name_without_root.split("/") chord = cls(name, root, Chord.from_name(name_without_root)) chord.chord.add_semitone(-note_diff(bass, root.name)) return chord else: return cls(name, root, Chord.from_name(name_without_root))
def from_name(cls, name: str) -> "Chord": # First determine the octave octave, name = _separate_octave(name) if octave is None: octave = 4 root = None for letter in LETTERS: if name.startswith(letter): root_no_accidental = letter break else: raise InvalidChord(name) # Very special case: major chord with no accidentals if len(name) == len(root_no_accidental): return cls(name, Note(root_no_accidental, octave), Intervals.from_name("")) possibly_accidental = name[len(root_no_accidental)] if possibly_accidental in ACCIDENTALS: root = Note(root_no_accidental + possibly_accidental, octave) else: root = Note(root_no_accidental, octave) name_without_root = name[len(root.name) :] has_slash = "/" in name_without_root if has_slash: name_without_root, bass = name_without_root.split("/") chord = cls(name, root, Intervals.from_name(name_without_root)) chord.intervals = chord.intervals.add_semitone(-note_diff(bass, root.name)) return chord else: return cls(name, root, Intervals.from_name(name_without_root))
def test_github_issue_8_sloppy_midi(): prog = ChordProgression.from_midi_file( os.path.join(os.path.dirname(__file__), "test_data", "issue_8.mid")) assert prog == ChordProgression([ Chord( name="A#min", root=Note("A#", 3), intervals=Intervals(name="min", semitones=[0, 3, 7]), ), Chord( name="D#", root=Note("D#", 4), intervals=Intervals(name="", semitones=[0, 4, 7]), ), Chord( name="D#min", root=Note("D#", 4), intervals=Intervals(name="min", semitones=[0, 3, 7]), ), Chord( name="A#min/G#", root=Note("G#", 3), intervals=Intervals(name="min/b7", semitones=[0, 5, 9, 14]), ), ])
def test_github_issue_61_slash_chord_with_octave(): """https://github.com/jonathangjertsen/jchord/issues/61#issuecomment-777625321""" chord = Chord.from_name("5C/E") assert chord == Chord( name="C/E", root=Note("C", 5), intervals=Intervals(name="major", semitones=[-8, 0, 4, 7]), ) assert chord.bass == Note("E", 4)
def from_root_and_semitones(cls, root: Note, semitones: List[int]) -> "Chord": chord = Intervals.from_semitones(semitones) if "/" in chord.name: chord_name, bass_degree = chord.name.split("/") new_root = ( Note(root.name, 0).transpose(12 - degree_to_semitone(bass_degree)).name ) name = f"{new_root}{chord_name}/{root.name}" else: name = root.name + chord.name return cls(name, root, chord)
def test_github_issue_61_progression(): """https://github.com/jonathangjertsen/jchord/issues/61#issuecomment-777575298""" prog = ChordProgression.from_string("4F -- 3Am -- 4Dm7 -- 4F --") assert prog == ChordProgression([ Chord( name="F", root=Note("F", 4), intervals=Intervals(name="major", semitones=[0, 4, 7]), ), Chord( name="F", root=Note("F", 4), intervals=Intervals(name="major", semitones=[0, 4, 7]), ), Chord( name="Am", root=Note("A", 3), intervals=Intervals(name="m", semitones=[0, 3, 7]), ), Chord( name="Am", root=Note("A", 3), intervals=Intervals(name="m", semitones=[0, 3, 7]), ), Chord( name="Dm7", root=Note("D", 4), intervals=Intervals(name="m7", semitones=[0, 3, 7, 10]), ), Chord( name="Dm7", root=Note("D", 4), intervals=Intervals(name="m7", semitones=[0, 3, 7, 10]), ), Chord( name="F", root=Note("F", 4), intervals=Intervals(name="major", semitones=[0, 4, 7]), ), Chord( name="F", root=Note("F", 4), intervals=Intervals(name="major", semitones=[0, 4, 7]), ), ])
def from_root_and_semitones(cls, root: Note, semitones: List[int]) -> "ChordWithRoot": """Creates a ChordWithRoot from a root `Note` and a list of semitones from the root. `semitones_to_chord_name_options` is used to guess a good name for the chord. """ chord = Chord.from_semitones(None, semitones) if "/" in chord.name: chord_name, bass_degree = chord.name.split("/") new_root = (Note( root.name, 0).transpose(12 - degree_to_semitone(bass_degree)).name) name = "{}{}/{}".format(new_root, chord_name, root.name) else: name = root.name + chord.name return cls(name, root, chord)
def midi_to_note(midi: int) -> Note: """Returns the `Note` corresponding to the given MIDI note value.""" return Note(CHROMATIC[midi % 12], (midi - 12) // 12)
def test_note_pitch(name, octave, pitch): assert Note(name, octave).pitch() == pytest.approx(pitch)
def test_transpose_degree(note_in, octave_in, shift, note_out, octave_out): assert Note(note_in, octave_in).transpose_degree(shift) == (note_out, octave_out)
def test_note_neq(note, octave, other): assert Note(note, octave) != other
def test_note_to_midi(note, octave, midi): assert note_to_midi(Note(note, octave)) == midi assert midi_to_note(midi) == Note(note, octave)
def test_note_repr(name, octave, the_repr): assert repr(Note(name, octave)) == the_repr assert Note(name, octave) == eval(the_repr)
def test_chord_from_root_and_semitone(root, semitones, name): assert Chord.from_root_and_semitones(Note(root, 4), semitones).name == name
def test_chord_add_root(name_in, octave): name_then_root = Intervals.from_name(name_in).with_root(Note("A#", octave)) name_and_root = Chord.from_name(f"{octave}A#{name_in}") assert name_then_root == name_and_root
def test_chord_add_root(name_in, octave): name_then_root = Chord.from_name(name_in).with_root(Note("A#", octave)) name_and_root = ChordWithRoot.from_name("A#" + name_in, octave=octave) print(name_and_root._keys()) assert name_then_root == name_and_root
def __init__(self, scale, degrees, root): self.scale = scale self.degrees = degrees self.root = Note(root, octave=4) assert scale[0] == 0 assert all(degree >= 1 for degree in degrees)
def test_note_eq(sharp, flat, octave): assert Note(sharp, octave) == Note(flat, octave) assert Note(flat, octave) == Note(sharp, octave)
def test_note_to_midi_invalid_note(): with pytest.raises(InvalidNote): note_to_midi(Note("bA", 0))
def test_note_eq_tuple(note, octave): assert Note(note, octave) == (note, octave)