Ejemplo n.º 1
0
def test_scale_get_chord_with_alteration(start_degree_name, alteration,
                                         chord_basic, chord_seventh):
    scale = Scale("A", "major")

    assert scale.get_chord(start_degree_name, "1,3,5",
                           alteration=alteration) == chord_basic
    assert scale.get_chord(start_degree_name, "1,3,5,7",
                           alteration=alteration) == chord_seventh
Ejemplo n.º 2
0
def test_scale_notation_on_initialization(name, alt_name):
    root_note = "A"

    notation1 = Scale(root_note, name)
    notation2 = Scale(root_note, alt_name)

    assert notation1 == notation2
    assert repr(notation1) == repr(notation2)
Ejemplo n.º 3
0
def test_grid_part_repr():
    grid_part = GridPart(
        Scale("C", "ionian"),
        Chord("C", "maj7"),
        Whole,
        TimeSignature(4, 4),
        Tempo(60)
    )

    assert repr(grid_part) == "<GridPart : C ionian / Cmaj7 / Whole / 4/4 / 60>"
Ejemplo n.º 4
0
    def _get(self,
             degree,
             inversion=None,
             base_note=None,
             base_degree=None,
             default_degrees=None,
             seventh=True,
             strict=True,
             extensions=None):
        if not default_degrees:
            default_degrees = self._DEFAULT_DEGREES

        base_degree_interval = None
        note, alteration, index, chord_name = self._parse_degree(
            degree, seventh=seventh, strict=strict)

        if base_degree:
            base_degree_interval = self.get_base_degree_interval(base_degree)
            note += base_degree_interval

        if chord_name:
            chord = Chord(note,
                          chord_name,
                          inversion=inversion,
                          base_note=base_note,
                          extensions=extensions)

        else:
            scale = self.scale

            if base_degree_interval:
                scale = Scale(scale.tonic + base_degree_interval,
                              str(scale.name))

            chord = scale.get_chord(index,
                                    default_degrees,
                                    alteration=alteration,
                                    inversion=inversion,
                                    base_note=base_note,
                                    extensions=extensions)

        return chord
Ejemplo n.º 5
0
def test_create_grid_with_parts():
    parts = [
        {"scale": Scale("A", "major"), "chord": Chord("A", "maj")},
        {"chord": Chord("C", "min"), "tempo": Tempo(120)},
        {"chord": Chord("E", "maj"), "duration": Half},
        {"chord": Chord("G#", "dim"), "time_signature": TimeSignature(3, 2)}
    ]
    time_signature = TimeSignature(4, 4)
    tempo = Tempo(90)

    grid = Grid(
        time_signature=time_signature,
        tempo=tempo,
        duration=Whole
    )
    grid.set_parts(*parts)

    assert repr(grid) == "<Grid : 4 parts>"

    assert len(grid.parts) == 4

    assert grid.parts[0].tempo == tempo
    assert grid.parts[0].time_signature == time_signature
Ejemplo n.º 6
0
def test_scale_instanciation_without_attributes():
    with raises(ValueError, match="Scale name and tonic note must be set"):
        Scale()
Ejemplo n.º 7
0
def test_scale(root_note, scale_name, result_notes):
    scale = Scale(root_note, scale_name)

    assert scale.notes == Note.to_list(result_notes)
Ejemplo n.º 8
0
    root_note = "A"

    notation1 = Scale(root_note, name)
    notation2 = Scale(root_note, alt_name)

    assert notation1 == notation2
    assert repr(notation1) == repr(notation2)


def test_scale_instanciation_through_to_dict():
    scale = Scale("A", "major")

    assert scale == Scale(**scale.to_dict())


@mark.parametrize("base_scale,mode_switch,target_scale", [
    (Scale("A", "major"), 2, Scale("A", "phrygian")),
    (Scale("A", "major"), 4, Scale("A", "mixolydian")),
    (Scale("A", "major"), 6, Scale("A", "locrian")),
    (Scale("A", "melodic minor"), 3, Scale("A", "lydian dominant")),
    (Scale("A", "harmonic minor"), 6, Scale("A", "altered diminished")),
    (Scale("A", "double harmonic minor"), 1, Scale("A", "lydian #2 #6")),
])
def test_scale_switch_mode(base_scale, mode_switch, target_scale):
    assert base_scale.switch_mode(mode_switch) == target_scale


def test_scale_raise_on_switch_mode():
    with raises(ValueError, match="Scale doesnt have modes"):
        Scale("A", "pentatonic").switch_mode(2)
Ejemplo n.º 9
0
def validate_chord_item(string, loc, expr):
    if not (Chord.get_from_fullname(expr[0])
            or Harmony(Scale("A", "major")).get(expr[0])):
        raise Exception("Couldn't parse chord item")
Ejemplo n.º 10
0
def validate_note_or_degree(string, loc, expr):
    try:
        Note(expr[0])
    except Exception:
        Harmony(Scale("A", "major")).get(expr[0])
Ejemplo n.º 11
0
def test_harmony_parse_degree_non_strictly(degree_name, expected_chord):
    harmony = Harmony(Scale("A", "major"))

    assert harmony.get(degree_name,
                       strict=False) == Chord.get_from_fullname(expected_chord)
Ejemplo n.º 12
0
def test_harmony_instanciation_through_to_dict():
    harmony = Harmony(Scale("A", "major"))

    assert harmony == Harmony(**harmony.to_dict())
Ejemplo n.º 13
0
def test_scale_instanciation_through_to_dict():
    scale = Scale("A", "major")

    assert scale == Scale(**scale.to_dict())
Ejemplo n.º 14
0
def test_harmony_instanciation_on_non_diatonic_scale(scale_name):
    with raises(ValueError, match="Scale given to harmony must be diatonic"):
        Harmony(Scale("A", scale_name))
Ejemplo n.º 15
0
    scale_name = None
    tonic_note = None
    if current_scale:
        scale_name = str(current_scale.name)
        tonic_note = current_scale.tonic

    if scale_name_value := parsed_config.get("scale"):
        scale_name = scale_name_value
        scale_updated = True

    if tonic_name := parsed_config.get("note"):
        tonic_note = Note(tonic_name)
        scale_updated = True

    if scale_updated and scale_name and tonic_note:
        parsed["scale"] = Scale(tonic_note, scale_name)
    elif not current_scale:
        return parsed

    if progression := parsed_config.get("progression"):
        parsed["progression"] = progression

    if time_signature := parsed_config.get("time_signature"):
        parsed["time_signature"] = TimeSignature(*time_signature)

    if tempo := parsed_config.get("tempo"):
        parsed["tempo"] = Tempo(tempo)

    return parsed

Ejemplo n.º 16
0
from beethoven.theory.note import Note
from beethoven.theory.scale import Scale


@mark.parametrize(
    "tuning,fretboard_kwargs,call_kwargs,expected_ascii",
    [
        # Test without any scale/chord
        (Tuning(Note("A")), {}, {},
         ("     ║     |     |     |     |     |     |     |     |     |     |     |     |\n"
          "\n"
          "  0  ║     |     |  3  |     |  5  |     |  7  |     |  9  |     |     | 12  |"
          )),
        # Test with a scale
        (Tuning(Note("A")), {}, {
            "scale": Scale("A", "major")
        },
         ("  A  ║     |  B  |     | C#  |  D  |     |  E  |     | F#  |     | G#  |  A  |\n"
          "\n"
          "  0  ║     |     |  3  |     |  5  |     |  7  |     |  9  |     |     | 12  |"
          )),
        # Test with a chord
        (Tuning(Note("A")), {}, {
            "chord": Chord("A", "maj7")
        },
         ("  A  ║     |     |     | C#  |     |     |  E  |     |     |     | G#  |  A  |\n"
          "\n"
          "  0  ║     |     |  3  |     |  5  |     |  7  |     |  9  |     |     | 12  |"
          )),
        # Test shifting first and last frets on initialization
        (Tuning(Note("A")), {
Ejemplo n.º 17
0
def test_harmony_degrees_and_noteslmao():
    harmony = Harmony(Scale("A", "ionian"))

    assert harmony.get("i", seventh=False) == Chord.get_from_fullname("Amin")
    assert harmony.get("II", seventh=False) == Chord.get_from_fullname("Bmaj")
Ejemplo n.º 18
0
def test_harmony_degrees_and_notes(scale_name, expected_degrees):
    harmony = Harmony(Scale("A", scale_name))

    assert harmony.degrees == expected_degrees.split(",")
Ejemplo n.º 19
0
def test_scale_with_wrong_scale_name(scale_name):
    with raises(ValueError, match="Scale name does not exists"):
        Scale("A", scale_name)
Ejemplo n.º 20
0
class Harmony(metaclass=HarmonySingletonMeta):
    _DIRECTORY = {}
    _MAJOR_INTERVALS = {
        i: interval
        for i, interval in enumerate(Scale("A", "major").intervals, start=1)
    }
    _DEGREES = ("I", "II", "III", "IV", "V", "VI", "VII")
    _DEFAULT_DEGREES = "1,3,5,7"

    def __init__(self, scale):
        self._load_attributes(scale)

    def __repr__(self):
        return f"<Harmony {str(self)}>"

    def __str__(self):
        return str(self.scale)

    def _load_attributes(self, scale):
        if not len(scale.intervals) == 7:
            raise ValueError("Scale given to harmony must be diatonic")

        self.scale = scale
        self.degrees = []

        default_degrees = [1, 3, 5, 7]
        scale_notes = self.scale.notes

        for i, degree_name in enumerate(self._DEGREES):
            # TODO refactor with intervals for scale object instead of notes
            notes = list(
                map(lambda x: scale_notes[(x + i - 1) % 7], default_degrees))

            intervals = [notes[0] // note for note in notes]

            chord_name = Chord.get_chord_name_from_intervals(intervals).short

            if "min" in chord_name or "dim" in chord_name:
                degree_name = degree_name.lower()

            self.degrees.append(degree_name)

    def get(self, *args, **kwargs):
        try:
            return self._get(*args, **kwargs)
        except ValueError:
            return

    def get_base_degree_interval(self, degree):
        note = self._parse_degree(degree)[0]

        return self.scale.notes[0] // note

    def _get(self,
             degree,
             inversion=None,
             base_note=None,
             base_degree=None,
             default_degrees=None,
             seventh=True,
             strict=True,
             extensions=None):
        if not default_degrees:
            default_degrees = self._DEFAULT_DEGREES

        base_degree_interval = None
        note, alteration, index, chord_name = self._parse_degree(
            degree, seventh=seventh, strict=strict)

        if base_degree:
            base_degree_interval = self.get_base_degree_interval(base_degree)
            note += base_degree_interval

        if chord_name:
            chord = Chord(note,
                          chord_name,
                          inversion=inversion,
                          base_note=base_note,
                          extensions=extensions)

        else:
            scale = self.scale

            if base_degree_interval:
                scale = Scale(scale.tonic + base_degree_interval,
                              str(scale.name))

            chord = scale.get_chord(index,
                                    default_degrees,
                                    alteration=alteration,
                                    inversion=inversion,
                                    base_note=base_note,
                                    extensions=extensions)

        return chord

    def _parse_degree(self, degree, seventh=True, strict=True):
        matched = HARMONY_PARSER.match(degree)
        if not matched:
            raise ValueError("Degree could not be parsed")

        parsed = matched.groupdict()
        final_alteration = 0

        degree_name = parsed.get("degree_name")
        alteration = parsed.get("alteration")
        chord_name = parsed.get("chord_name")

        index = self._DEGREES.index(degree_name.upper())
        note = self.scale.notes[index]

        if degree_name not in self.degrees and not chord_name:
            if strict and degree_name.isupper():
                chord_name = "maj"
            elif degree_name.islower():
                chord_name = "min"

            if chord_name and seventh:
                chord_name += "7"

        flats = alteration.count("b")
        sharps = alteration.count("#")
        if flats:
            final_alteration -= flats
            for _ in range(flats):
                note += DIMINISHED
        elif sharps:
            final_alteration += sharps
            for _ in range(sharps):
                note += AUGMENTED

        return note, final_alteration, index, chord_name

    def to_dict(self):
        return {"scale": self.scale}
Ejemplo n.º 21
0
def test_scale_get_chord(start_degree_name, chord_basic, chord_seventh):
    scale = Scale("A", "major")

    assert scale.get_chord(start_degree_name, "1,3,5") == chord_basic
    assert scale.get_chord(start_degree_name, "1,3,5,7") == chord_seventh
Ejemplo n.º 22
0
def test_harmony_instanciation_on_diatonic_scale(scale_name):
    Harmony(Scale("A", scale_name))
Ejemplo n.º 23
0
from beethoven.sequencer.chord import Chord
from beethoven.sequencer.note import Note
from beethoven.sequencer.utils import adapt_chord_to_sequencer, get_all_notes
from beethoven.theory.scale import Scale


@mark.parametrize("chord,note_range,expected", [
    (Chord("C1", "maj"), "C3,C6", "C3,E3,G3"),
    (Chord("C2", "maj"), "C3,C6", "C3,E3,G3"),
    (Chord("C3", "maj"), "C4,C6", "C4,E4,G4"),
    (Chord("C4", "maj"), "C4,C6", "C4,E4,G4"),
    (Chord("C5", "maj"), "C4,C6", "C5,E5,G5"),
    (Chord("C6", "maj"), "C5,F6", "C6,E6"),
    (Chord("A4", "maj7"), "C4,C6", "A4,C#5,E5,G#5"),
    (Chord("A4", "maj7"), "C4,E5", "A4,C#5,E5"),
])
def test_adapt_chord_to_sequencer(chord, note_range, expected):
    assert (adapt_chord_to_sequencer(
        chord, Note.to_list(note_range)) == Note.to_list(expected))


@mark.parametrize("scale,note_range,expected", [
    (Scale("C3", "major"), "C3,C4", "C3,D3,E3,F3,G3,A3,B3,C4"),
    (Scale("C3", "major"), "E3,E4", "E3,F3,G3,A3,B3,C4,D4,E4"),
    (Scale("C3",
           "major"), "C3,C5", "C3,D3,E3,F3,G3,A3,B3,C4,D4,E4,F4,G4,A4,B4,C5"),
])
def test_get_all_notes(scale, note_range, expected):
    assert (get_all_notes(scale,
                          Note.to_list(note_range)) == Note.to_list(expected))
Ejemplo n.º 24
0
def test_harmony_repr():
    assert repr(Harmony(Scale("A", "ionian"))) == "<Harmony A ionian>"
Ejemplo n.º 25
0
def test_scale_raise_on_switch_mode():
    with raises(ValueError, match="Scale doesnt have modes"):
        Scale("A", "pentatonic").switch_mode(2)
Ejemplo n.º 26
0
from os import environ

from beethoven.common.tuning import Tuning
from beethoven.models import GridPart
from beethoven.players.drum import Drum
from beethoven.players.piano import Piano
from beethoven.sequencer.jam_room import JamRoom
from beethoven.sequencer.note_duration import Whole
from beethoven.sequencer.tempo import Tempo
from beethoven.sequencer.time_signature import TimeSignature
from beethoven.theory.scale import Scale

DEFAULT_CONFIG = {
    "scale": Scale("C", "major"),
    "duration": Whole,
    "tempo": Tempo(60),
    "time_signature": TimeSignature(4, 4)
}

DEFAULT_PROMPT_CONFIG = {"strict": True}


class State:
    def __init__(self, prompt_config=None):
        self.jam_room = JamRoom()

        self.grid_parts = {gp.name: gp for gp in GridPart.list()}

        self.jam_room.players.add(Piano())
        self.jam_room.players.add(Drum())
Ejemplo n.º 27
0
def validate_scale(string, loc, expr):
    Scale("A", expr[0])
Ejemplo n.º 28
0
from pytest import mark

from beethoven.prompt.parser import prompt_harmony_list_parser
from beethoven.sequencer.note_duration import Half
from beethoven.sequencer.tempo import Tempo
from beethoven.sequencer.time_signature import TimeSignature
from beethoven.theory.chord import Chord
from beethoven.theory.scale import Scale


@mark.parametrize(
    "input_string,expected",
    [("", []), ("n=A sc=major p=", []), ("n=A p=I", []),
     ("n=A sc=major p=I,i", [{
         "chord": Chord("A", "maj7"),
         "scale": Scale("A", "ionian"),
     }, {
         "chord": Chord("A", "min7")
     }]),
     ("n=A sc=major p=Amin7,Cmin7", [{
         "chord": Chord("A", "min7"),
         "scale": Scale("A", "ionian"),
     }, {
         "chord": Chord("C", "min7")
     }]),
     ("n=A sc=major p=I;n=B sc=minor p=I", [{
         "chord": Chord("A", "maj7"),
         "scale": Scale("A", "ionian"),
     }, {
         "chord": Chord("B", "maj7"),
         "scale": Scale("B", "aeolian")