class FakePlayer1(BasePlayer): NOTE_DURATION = Whole NOTE_RANGE = [Note("C3"), Note("C5")] def play_measure(self, **kwargs): for self.part in self.time_signature.gen(self.NOTE_DURATION): yield self.play(*self.chord_voicer.get()[0:2], duration=Whole, velocity=127)
class FakePlayer2(BasePlayer): NOTE_DURATION = Half NOTE_RANGE = [Note("D3"), Note("C5")] def play_measure(self, **kwargs): for self.part in self.time_signature.gen(self.NOTE_DURATION): yield self.play(self.arpeggiator.get()[0], duration=Half, velocity=63)
class Piano(BasePlayer): NOTE_DURATION = Sixteenths NOTE_RANGE = [Note("C2"), Note("C5")] NORMALIZE_TS = True def play_measure(self, **kwargs): for self.part in self.time_signature.gen(self.NOTE_DURATION, self.duration): if self.check(bar=1, measure=1, submeasure=1): yield self.play(*self.chord_voicer.get(), duration=Whole, velocity=76)
def test_base_player_capabilities(): player = FakePlayer() assert repr(player) == "<Player FakePlayer>" player.part = TimeContainer(1, 1, 1) assert player.play("F4", "F5", velocity=63) == { "part": TimeContainer(1, 1, 1), "notes": [Note("F4"), Note("F5")], "duration": Whole, "velocity": 63 }
def __call__(cls, root_note=None, chord_name=None, inversion=None, base_note=None, extensions=None): if root_note is None and chord_name is None: raise ValueError("Chord name and root note must be set") elif base_note and isinstance(base_note, str): try: base_note = Note(base_note) except Exception: base_note = TheoryNote(base_note) extensions = frozenset([ extension if isinstance(extension, Interval) else Interval(extension) for extension in extensions or [] ]) args = frozenset( [root_note, chord_name, inversion, base_note, extensions]) if args not in cls._INSTANCES: instance = super().__call__(root_note, chord_name, inversion, base_note, extensions) cls._INSTANCES[args] = instance return cls._INSTANCES[args]
def _load_attributes(self, root_note, chord_name, inversion, base_note, extensions): if not isinstance(root_note, Note): root_note = Note(root_note) if not (data := self._DIRECTORY.get(chord_name)): raise ValueError("Chord name does not exists")
class Chord(BaseChord, metaclass=SequencerChordSingletonMeta): def __init__(self, root, name, inversion, base_note, extensions): self._load_attributes(root, name, inversion, base_note, extensions) def _load_attributes(self, root_note, chord_name, inversion, base_note, extensions): if not isinstance(root_note, Note): root_note = Note(root_note) if not (data := self._DIRECTORY.get(chord_name)): raise ValueError("Chord name does not exists") self.root = root_note self.inversion = inversion or 0 self.base_note = None if base_note and base_note != root_note: self.base_note = Note.cast_from_theory( base_note, octave=(self.root.octave if (base_note < self.root or self.root.octave == 0) else self.root.octave - 1)) self.name, intervals = data self.intervals = copy(intervals) if inversion and (inversion < 0 or inversion >= len(self.intervals)): raise ValueError("Chord inversion out of range") notes = [] if self.base_note: notes.append(self.base_note) base_chord_notes = [self.root] + [ self.root + interval for interval in self.intervals[1:] ] notes += base_chord_notes[ self.inversion:] + base_chord_notes[:self.inversion] self.notes = [notes[0]] last_note = notes[0] for note in notes[1:]: # AU CAS OU IL Y AURAIT DES INTERVALES ETENDUS while note < last_note: note += OCTAVE self.notes.append(note) last_note = note self.extensions = [] for extension in sorted(extensions): if extension in self.intervals: continue self.notes.append(self.root + extension) self.intervals.append(extension) self.extensions.append(extension) self.notes = list(sorted(self.notes)) self.intervals = list(sorted(self.intervals))
def _load_attributes(self, tonic, scale_name): if not isinstance(tonic, Note): tonic = Note(tonic) self.tonic = tonic if not (data := self._DIRECTORY.get(scale_name)): raise ValueError("Scale name does not exists")
def test_note_check_display_note_name_change(anglosaxon, solfege): note = Note(anglosaxon) assert str(note) == f"<Note {anglosaxon}>" NoteNameContainer.set(1) assert str(note) == f"<Note {solfege}>" NoteNameContainer.set(0)
def adapt_chord_to_sequencer(chord, range): low_range, high_range = range chord_data = chord.to_dict() root_note = chord_data.pop("root_note") if isinstance(chord, TheoryChord): root_note = Note.cast_from_theory(root_note) if root_note < low_range: while root_note < low_range: root_note += OCTAVE chord = Chord(root_note=root_note, **chord_data) return [note for note in chord.notes if note <= high_range]
def test_chord_instanciation_with_base_note(root_note, chord_name, base_note, expected_str): chord = Chord(root_note, chord_name, base_note=base_note) assert chord.base_note == Note(base_note) assert repr(chord) == f"<Chord {expected_str}>"
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))
def test_get_all_notes(scale, note_range, expected): assert (get_all_notes(scale, Note.to_list(note_range)) == Note.to_list(expected))
def test_note_add_interval_with_non_interval_string(): with raises(ValueError, match="Interval name does not exists"): Note("A4").add_interval("test")
def test_chord_instanciation_with_inversion(): normal = Chord("A4", "power", inversion=0) inverted = Chord("A4", "power", inversion=1) assert normal.notes == Note.to_list("A4,E5") assert inverted.notes == Note.to_list("E5,A5")
def test_interval_check_display(start_note, interval_name, expected_note): assert Note(start_note) + Interval(interval_name) == Note(expected_note)
def test_note_with_empty_note_name(): with raises(ValueError, match="Note could not be parsed"): Note("")
def test_note_add_interval_method(note_name, interval_name, result_note_name): assert Note(note_name) + Interval(interval_name) == Note(result_note_name)
def test_note_substract_interval_method(note_name, interval_name, result_note_name): assert Note(note_name) - Interval(interval_name) == Note(result_note_name)
def test_note_add_interval_alteration_normalization(): assert Note("A4") + Interval("1aaaaaaaaaaa") == Note("Ab4") assert Note("A4") + Interval("1ddddddddddd") == Note("A#4")
def test_note_with_sharps_and_flats_raise_exception(note_name): with raises(ValueError, match="Note name shouldn't contain sharps AND flats"): Note(note_name)
def test_note_with_wrong_note_name(note_name): with raises(ValueError, match="Note could not be parsed"): Note(note_name)
def test_note_semitones_property(note_name, expected_semitones): assert Note(note_name).semitones == expected_semitones
def test_note_instanciation_without_attributes(): with raises(ValueError, match="Note name must be set"): Note()
def test_note_instanciation(note_name): assert Note(note_name)
def test_note_add_interval_with_non_interval_instance(): assert Note("A3").add_interval("3") == Note("C#4") assert Note("G3").add_interval("5", reverse=True) == Note("C3")
def test_chord_instanciation(root_note, chord_name, result_notes): chord = Chord(root_note, chord_name) assert chord.notes == Note.to_list(result_notes)
def test_note_comparison(): assert Note("A4") == Note("A4") assert Note("A2") < Note("B2") assert Note("B3") > Note("C2")
def test_note_is_the_same_between_notation_systems_with_sharps(anglosaxon, solfege): note_a, note_s = Note(anglosaxon), Note(solfege) assert note_a == note_s assert note_a is not None
def test_note_casting_to_theory_self(): return BaseNote("B") == Note("B4").get_theory_self()