def __init__(self): # Parametry rytmu self.metre: Tuple[int, int] = (4, 4) self.bar_count: int = 4 # Parametry melodii self.start_note: Note = Note('c', OctaveType.LINE_1) self.end_note: Note = Note('c', OctaveType.LINE_1) self.ambitus: Dict[str, Note] = { 'lowest': Note('c', OctaveType.SMALL), 'highest': Note('c', OctaveType.LINE_4) } self.rest_probability: float = 0.5 self.max_consecutive_rests = math.inf # Prawdopodobieństwa wystąpień # Interwałów # Nut w obrębie oktawy # Długości nut self.intervals_probability: List[int] = [ 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7 ] self.notes_probability: List[int] = [ 9, 9, 9, 9, 8, 8, 8, 8, 8, 8, 8, 8 ] self.durations_probability: List[int] = [14, 14, 14, 14, 14, 15, 15] # Wygenerowane dane self.generated_data: List[Writeable] = [] # Zmienne pomocnicze self._consecutive_rests = 0
def test_set_ambitus_raises_value_error(self): with self.assertRaises(ValueError): self.generator.set_ambitus(lowest=Note('c', OctaveType.LINE_6)) with self.assertRaises(ValueError): self.generator.set_ambitus( highest=Note('c', OctaveType.SUB_CONTRA))
def main(): generator = Generator() # set_metre przyjmuje każde metrum, którego wartością rytmiczną jest półnuta, ćwierćnuta, ósemka lub szesnastka # ilość nut w metrum jest dowolna jeśli tylko większa od 1 # set_bar_count przyjmuje liczbę taktów, którą chcemy wygenerować # set_start_note oraz set_end_note ustalają nuty początkowe i końcowe i przyjmują tylko nuty które znajdują się w # ambitusie, który można ustawić za pomocą metody set_ambitus # set_rest_probability ustawia prawdopodobieństwa wystąpienia pauzy. Podawana wartość musi być znormalizowana do 1 # prawdopodobieństwo wystąpienia nuty będzie wynosiło 1 - p, gdzie p to podana wartość # set_max_consecutive_rests ustala ile maksymalnie pauz może nastąpić po sobie (UWAGA! W nutach niepogrupowanych) # po dzieleniu na takty i grupowaniu, może pojawić się większa liczba pauz # set_intervals_probability, set_notes_probability, set_durations_probability to metody, które umożliwiają # ustawienie prawdopodobieństw wystąpienia odpowiednio: # 1. Konkretnych interwałów - lista interwałów to: [1cz, 2m, 2w, 3m, 3w, 4cz, 4zw, 5zm, 5cz, 6m, 6w, 7m, 7w, 8cz] # 2. Konkretnych nut w obrębie oktawy (12 dźwięków począwszy od c do b) - wykorzystywane w algorytmie generującym # gdy nuty wygenerowane po wylosowaniu interwału obie mieszczą się w zadanym ambitusie. Wtedy wykorzystywane jest # prawdopodobieństwo ich wystąpienia podczas losowania, która z nich trafi do końcowej melodii # 3. Konkretnych wartości rytmicznych - kolejno: [cała nuta, półnuta, ... aż do 64] # Prawdopodobieństwa podawane do tych funkcji muszą być znormalizowane do 100, a każda pojedyncza wartość musi być # liczbą całkowitą. # set_shortest_note_duration - ustala jaką najkrótszą wartość rytmiczną chcemy zobaczyć w nutach. # Skrypt obsługuje aktualnie aż do 64, ale wystarczy zmienić zakres w pliku Generator.py zmiennej # correct_note_lengths, aby skrypt pozwolił na wykorzystanie 128, lub innych nut, ponieważ obliczenia są pisane # uniwersalnie. generator\ .set_metre(4, 4) \ .set_bar_count(100) \ .set_start_note(Note('c', OctaveType.SMALL)) \ .set_end_note(Note('c', OctaveType.SMALL)) \ .set_ambitus(lowest=Note('c', OctaveType.SMALL), highest=Note('c', OctaveType.LINE_2)) \ .set_rest_probability(0.1) \ .set_max_consecutive_rests(1) \ .set_intervals_probability([8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7]) \ .set_notes_probability([9, 9, 9, 9, 8, 8, 8, 8, 8, 8, 8, 8]) \ .set_durations_probability([14, 14, 14, 14, 14, 15, 15]) \ .set_shortest_note_duration(16) # w konstruktorze należy podać nazwę pliku, pod którą będą generowane pliki lilyponda oraz pliki wynikowe # można zmienić wiele innych parametrów, wtedy odsyłam do pliku Writer.py writer = Writer('generated') # from_generator - na podstawie obiektu generatora z określonymi parametrami przetwarza dane, które później mogą # zostać wyeksportowane do pliku lilyponda writer.from_generator(generator, midi=True) # export - eksportuje plik lilyponda writer.export() # compile - uruchamia lilyponda z odpowiednimi parametrami, tak aby powstał plik wynikowy, dozwolone rozszerzenia # to pdf, png oraz ps (tak jak zezwala lilypond) writer.compile(ext='png')
def test_split_note_long(self): self.generator.set_shortest_note_duration(16) note = Note('c', base_duration=1, modifiers=[NoteModifier.DOUBLE_DOT]) actual = self.generator.split_note(note, 16) expected = [[Note('c', base_duration=1, modifiers=[NoteModifier.TIE])], [Note('c', base_duration=2, modifiers=[NoteModifier.DOT])]] self.assertEqual(expected, actual)
def test_split_note(self): Generator.set_shortest_note_duration(16) note = Note('c', base_duration=4) actual = self.generator.split_note(note, 2) expected = [[Note('c', base_duration=8, modifiers=[NoteModifier.TIE])], [Note('c', base_duration=8)]] self.assertEqual(expected, actual)
def test_ge(self): note_1 = Note('c') note_2 = Note('d') self.assertTrue(note_2 >= note_1) note_2 = Note('c') self.assertTrue(note_2 >= note_1) note_1 = Note('d', OctaveType.CONTRA) note_2 = Note('d', OctaveType.SMALL) self.assertTrue(note_2 >= note_1)
def test_split_note_multiple_notes_on_left_side(self): Generator.set_shortest_note_duration(16) note = Note('c', base_duration=2) actual = self.generator.split_note(note, 5) expected = [[ Note('c', base_duration=4, modifiers=[NoteModifier.TIE]), Note('c', base_duration=16, modifiers=[NoteModifier.TIE]) ], [Note('c', base_duration=8, modifiers=[NoteModifier.DOT])]] self.assertEqual(expected, actual)
def test_lt(self): note_1 = Note('d') note_2 = Note('c') self.assertTrue(note_2 < note_1) note_2 = Note('d') self.assertFalse(note_2 < note_1) note_1 = Note('d', OctaveType.CONTRA) note_2 = Note('d', OctaveType.SMALL) self.assertFalse(note_2 < note_1)
def test_split_note_expects_double_dot_and_note(self): Generator.set_shortest_note_duration(16) note = Note('c', base_duration=1) actual = self.generator.split_note(note, 15) expected = [[ Note('c', base_duration=2, modifiers=[NoteModifier.DOUBLE_DOT, NoteModifier.TIE]), Note('c', base_duration=16, modifiers=[NoteModifier.TIE]) ], [Note('c', base_duration=16)]] self.assertEqual(expected, actual)
def test_init(self): self.assertEqual((4, 4), self.generator.metre) self.assertEqual(4, self.generator.bar_count) self.assertEqual(Note('c', OctaveType.LINE_1), self.generator.start_note) self.assertEqual(Note('c', OctaveType.LINE_1), self.generator.end_note) self.assertEqual(Note('c', OctaveType.SMALL), self.generator.ambitus['lowest']) self.assertEqual(Note('c', OctaveType.LINE_4), self.generator.ambitus['highest']) self.assertEqual(0.5, self.generator.rest_probability) self.assertEqual(100, sum(self.generator.intervals_probability))
def test_add_remove_modifier(self): note = Note('c') note.add_modifier(NoteModifier.DOT) self.assertTrue(NoteModifier.DOT in note.modifiers) note.remove_modifier(NoteModifier.DOT) self.assertTrue(NoteModifier.DOT not in note.modifiers)
def test_str_with_modifiers(self): note = Note('d', OctaveType.GREAT, 16) note.add_modifier(NoteModifier.TIE) self.assertEqual('d,16~', str(note)) note.add_modifier(NoteModifier.DOT) self.assertEqual('d,16.~', str(note))
def test_add_modifier_dot_removes_double_dot(self): note = Note('c') note.add_modifier(NoteModifier.DOUBLE_DOT) note.add_modifier(NoteModifier.DOT) self.assertTrue(NoteModifier.DOT in note.modifiers) self.assertTrue(NoteModifier.DOUBLE_DOT not in note.modifiers)
def test_modifiers_order(self): note = Note('c') note.add_modifier(NoteModifier.TIE) note.add_modifier(NoteModifier.DOT) self.assertEqual(2, len(note.modifiers)) self.assertEqual(NoteModifier.TIE, note.modifiers[1]) self.assertEqual(NoteModifier.DOT, note.modifiers[0])
def test_split_to_bars(self): self.generator.set_bar_count(2) self.generator.set_shortest_note_duration(16) notes: List[Writeable] = [Note('c')] * 7 notes.append(Rest()) expected: List[List[Writeable]] = [[Note('c')] * 4, [ Note('c'), Note('c'), Note('c'), Rest() ]] bars: List[List[Writeable]] = self.generator.split_to_bars(notes) self.assertEqual(expected, bars)
def test_get_duration_with_double_dot(self): note = Note('c', base_duration=4) note.add_modifier(NoteModifier.DOUBLE_DOT) lengths = [64, 32, 16] for length in lengths: self.assertEqual(length / 4 + length / 8 + length / 16, note.get_duration(length))
def test_get_next_writeable(self): self.generator.generated_data.append(Note('c')) for i in range(10): writeable = self.generator.get_next_writeable(8) self.assertLessEqual( writeable.get_duration(self.generator.shortest_note_duration), 8)
def test_split_to_bars_long(self): self.generator.set_bar_count(2) self.generator.set_shortest_note_duration(16) notes: List[Writeable] = [ Note('c', base_duration=1, modifiers=[NoteModifier.DOUBLE_DOT]), Note('c') ] expected: List[List[Writeable]] = [[ Note('c', base_duration=1, modifiers=[NoteModifier.TIE]) ], [ Note('c', base_duration=2, modifiers=[NoteModifier.DOT]), Note('c') ]] bars: List[List[Writeable]] = self.generator.split_to_bars(notes) self.assertEqual(expected, bars)
def test_sub_interval(self): note = Note('c') intervals = Interval.names() expected = [ 'c', 'b', 'bes', 'a', 'aes', 'g', 'ges', 'fis', 'f', 'e', 'ees', 'd', 'des', 'c' ] for i in range(len(expected)): new_note: Note = note - Interval(intervals[i]) self.assertEqual(expected[i], new_note.note) note = Note('f') expected = [ 'f', 'e', 'ees', 'd', 'des', 'c', 'ces', 'b', 'bes', 'a', 'aes', 'g', 'ges', 'f' ] for i in range(len(expected)): new_note: Note = note - Interval(intervals[i]) self.assertEqual(expected[i], new_note.note)
def test_split_to_bars_long_note(self): self.generator.set_bar_count(3) data: List[Writeable] = [ Rest(base_duration=2, modifiers=[RestModifier.DOT]), Note('c', base_duration=1, modifiers=[NoteModifier.DOT]), Rest(base_duration=2, modifiers=[RestModifier.DOT]) ] expected: List[List[Writeable]] = [[ Rest(base_duration=2, modifiers=[RestModifier.DOT]), Note('c', base_duration=4, modifiers=[NoteModifier.TIE]) ], [Note('c', base_duration=1, modifiers=[NoteModifier.TIE])], [ Note('c', base_duration=4), Rest(base_duration=2, modifiers=[ RestModifier.DOT ]) ]] bars = self.generator.split_to_bars(data) self.assertEqual(expected, bars)
def get_random_note(self, longest_duration: Optional[int] = None) -> Note: """ Wygeneruj nutę z losowymi parametrami o pewnej maksymalnej długości podanej w parametrze. Args: longest_duration: Najdłuższa możliwa wartość rytmiczna, która może wystąpić podana w ilości shortest_note_duration. Jeśli nie podano, skrypt zakłada że nuta o każdej długości jest dozwolona. Returns: Nuta z losowymi parametrami o maksymalnej długości wynoszącej longest_duration """ # Jeśli nie był podany parametr najdłuższej możliwej wartości rytmicznej, to zakładamy że nuta o każdej długości # jest dozwolona do wygenerowania if longest_duration is None: longest_duration = self.shortest_note_duration available_mods = [] base_note = np.random.choice(Note.base_notes) octave = OctaveType.random() base_duration = self.get_random_duration( longest_duration=longest_duration) has_mod = np.random.choice([True, False]) note = Note(note=base_note, octave=octave, base_duration=base_duration) # Jeśli długość nuty jest najkrótsza jaką możemy uzyskać, to nie możemy dodać modyfikatora wydłużającego, # gdyż kropka lub podwójna kropka doda mniejszą wartość rytmiczną if base_duration >= self.shortest_note_duration: has_mod = False # Jeśli dostępne miejsce jest większej lub równej długości niż potencjalna nuta z kropką, to do dostępnych # modyfikatorów możemy dodać przedłużenie w postaci kropki if longest_duration >= note.get_duration( self.shortest_note_duration) * 1.5: available_mods.append(NoteModifier.DOT) # Jeśli dostępne miejsce jest większej lub równej długości niż potencjalna nuta z podwójną kropką, to do # dostępnych modyfikatorów możemy dodać przedłużenie w postaci podwójnej kropki. # Sprawdzamy również, czy nie jest to przedostatnia dostępna wartość rytmiczna. Jeśli tak jest, to nie możemy # dodać podwójnej kropki, gdyż skutkowałoby to dodaniem nuty o połowę mniejszej wartości rytmicznej niż # dozwolona if longest_duration >= note.get_duration(self.shortest_note_duration) * 1.75 \ and note.base_duration > 2 * self.shortest_note_duration: available_mods.append(NoteModifier.DOUBLE_DOT) if has_mod and len(available_mods) > 0: note.add_modifier(np.random.choice(available_mods)) return note
def test_split_to_bars_break(self): self.generator.set_bar_count(2) self.generator.set_shortest_note_duration(16) notes: List[Writeable] = [ Note('c'), Note('c', base_duration=2), Note('c', base_duration=2), Note('c', base_duration=2), Rest() ] with_tie = Note('c') with_tie.add_modifier(NoteModifier.TIE) expected: List[List[Writeable]] = [[ Note('c'), Note('c', base_duration=2), with_tie ], [Note('c'), Note('c', base_duration=2), Rest()]] bars: List[List[Writeable]] = self.generator.split_to_bars(notes) self.assertEqual(expected, bars)
def test_add_interval(self): notes = ['c', 'f', 'fes', 'feses', 'fis', 'eisis'] expected = [[ 'c', 'des', 'd', 'ees', 'e', 'f', 'fis', 'ges', 'g', 'aes', 'a', 'bes', 'b', 'c' ], [ 'f', 'ges', 'g', 'aes', 'a', 'bes', 'b', 'ces', 'c', 'des', 'd', 'ees', 'e', 'f' ], [ 'fes', 'geses', 'ges', 'aeses', 'aes', 'beses', 'bes', 'ceses', 'ces', 'deses', 'des', 'eeses', 'ees', 'fes' ], [ 'feses', 'fes', 'geses', 'ges', 'aeses', 'aes', 'beses', 'beses', 'ceses', 'ces', 'deses', 'des', 'eeses', 'feses' ], [ 'fis', 'g', 'gis', 'a', 'ais', 'b', 'bis', 'c', 'cis', 'd', 'dis', 'e', 'eis', 'fis' ], [ 'eisis', 'fisis', 'gis', 'gisis', 'ais', 'aisis', 'bis', 'bis', 'bisis', 'cisis', 'dis', 'disis', 'eis', 'eisis' ]] intervals = Interval.names() for i in range(len(notes)): note = Note(notes[i]) for j in range(len(expected[i])): new_note = note + Interval(intervals[j]) self.assertEqual(expected[i][j], new_note.note)
def test_split_to_bars_break_rest(self): self.generator.set_bar_count(2) self.generator.set_shortest_note_duration(16) data: List[Writeable] = [ Note('c'), Note('c', base_duration=2), Rest(base_duration=2), Note('c', base_duration=2), Rest() ] expected: List[List[Writeable]] = [[ Note('c'), Note('c', base_duration=2), Rest() ], [Rest(), Note('c', base_duration=2), Rest()]] bars: List[List[Writeable]] = self.generator.split_to_bars(data) self.assertEqual(expected, bars)
def test_between_swap_low_and_high(self): low = Note('c', OctaveType.LINE_1) mid = Note('c', OctaveType.LINE_2) high = Note('c', OctaveType.LINE_3) self.assertTrue(mid.between(high, low))
def test_add_modifier_unique(self): note = Note('c') note.add_modifier(NoteModifier.DOT) note.add_modifier(NoteModifier.DOT) self.assertEqual(1, len(note.modifiers))
def test_between(self): low = Note('c', OctaveType.LINE_1) mid = Note('c', OctaveType.LINE_2) high = Note('c', OctaveType.LINE_3) self.assertTrue(mid.between(low, high))
def test_repr(self): note = Note('c') self.assertEqual('Note <c4>', repr(note))
def test_str_full(self): note = Note('d', OctaveType.GREAT, 16) self.assertEqual('d,16', str(note))
def test_str(self): note = Note('c') self.assertEqual('c4', str(note))