diff --git a/dokumentaatio/muunnos1.mp3 b/dokumentaatio/muunnos1.mp3 new file mode 100755 index 0000000..ad5d2d3 Binary files /dev/null and b/dokumentaatio/muunnos1.mp3 differ diff --git a/dokumentaatio/muunnos2.mp3 b/dokumentaatio/muunnos2.mp3 new file mode 100755 index 0000000..1da1d0f Binary files /dev/null and b/dokumentaatio/muunnos2.mp3 differ diff --git a/dokumentaatio/testausdokumentti.md b/dokumentaatio/testausdokumentti.md index 77f6af9..eda25de 100644 --- a/dokumentaatio/testausdokumentti.md +++ b/dokumentaatio/testausdokumentti.md @@ -23,7 +23,7 @@ Viikko 4: Generoin koodin nykyisellä versiolla 4:nnen asteen Markovin ketjulla Viikko 5: Toteutin tällä viikolla manuaalisen rytmin määrittämisen. Generoin 80 lyhyen melodian yksinkertaisella kahden tahdin välein toistuvalla rytmillä. Yllätin siitä kuinka paljon yksinkertainenkin rytmi "elävöitti" generoitua melodiaa. -Viikko 6: Testasin tällä viikolla kattavammin eri asteilla. Rajoitin asteen alle 5, sillä huomasin että tätä suuremmilla asteilla monessa tilanteessa oli vain yksi vaihtoehto seuraavalle nuotille, joka viittaa siihen että opetusdatan määrä ei ole riittävän suuri. Poistin myös tämän takia aiemmat testit. +Viikko 6: Testasin tällä viikolla kattavammin eri asteilla. Rajoitin asteen alle 5, sillä huomasin että tätä suuremmilla asteilla monessa tilanteessa oli vain yksi vaihtoehto seuraavalle nuotille, joka viittaa siihen että opetusdatan määrä ei ole riittävän suuri. Poistin myös tämän takia aiemmat testit. Lisäksi toteutin mekanismin, jolla olemassaolevan MIDI tiedoston 1. raidan nuotin voidaan korvata Markovin ketjun antamilla nuoteilla. Nämä "muunnokset" löytyvät myös alta. Näissä oli käytössä 4. asteen ketju. [Aste 1](https://github.com/ArcticCoder/markov-music-generator/blob/main/dokumentaatio/aste_1.mp3) @@ -35,4 +35,8 @@ Viikko 6: Testasin tällä viikolla kattavammin eri asteilla. Rajoitin asteen al [Aste 4, ei rytmiä](https://github.com/ArcticCoder/markov-music-generator/blob/main/dokumentaatio/aste_4_rytmiton.mp3) +[MIDI muunnos 1](https://github.com/ArcticCoder/markov-music-generator/blob/main/dokumentaatio/muunnos1.mp3) + +[MIDI muunnos 2](https://github.com/ArcticCoder/markov-music-generator/blob/main/dokumentaatio/muunnos2.mp3) + [Sattuma](https://github.com/ArcticCoder/markov-music-generator/blob/main/dokumentaatio/testi-sattuma.mp3) diff --git a/opetusdata/lisenssit.txt b/opetusdata/lisenssit.txt index 4181745..17c185a 100644 --- a/opetusdata/lisenssit.txt +++ b/opetusdata/lisenssit.txt @@ -15,3 +15,5 @@ https://musescore.com/user/38606/scores/6082483 https://musescore.com/user/110059/scores/980236 https://musescore.com/user/36583375/scores/6502523 https://musescore.com/r_d/scores/5449385 +https://musescore.com/user/285631/scores/287116 +https://musescore.com/user/33617769/scores/6363495 diff --git a/opetusdata/muunnos1.mid b/opetusdata/muunnos1.mid new file mode 100644 index 0000000..2b8ae78 Binary files /dev/null and b/opetusdata/muunnos1.mid differ diff --git a/opetusdata/muunnos2.mid b/opetusdata/muunnos2.mid new file mode 100644 index 0000000..f4b7fa8 Binary files /dev/null and b/opetusdata/muunnos2.mid differ diff --git a/src/midi_kasittelija.py b/src/midi_kasittelija.py index 0c1b7eb..ce35818 100644 --- a/src/midi_kasittelija.py +++ b/src/midi_kasittelija.py @@ -45,31 +45,58 @@ def lue_midi(tiedostopolku): return tulos -def kirjoita_midi(tiedostopolku, nuotit, tempo=120, rytmi="1/4"): - """Kirjoittaa halutut nuotit valittuun MIDI-tiedostoon annetulla tempolla""" +def kirjoita_midi(tiedostopolku, nuotit, muunnettava_midi=None, tempo=120, rytmi="1/4"): + """Kirjoittaa nuotit MIDI-tiedostoon halutulla tempolla ja rytmillä. + Vaihtoehtoisesti ottaa valitun MIDI-tiedoston ja vaihtaa sävelkorkeudet + """ midi = mido.MidiFile() raita = mido.MidiTrack() - rytmi = rytmi_taulukoksi(rytmi) - iskun_kesto = midi.ticks_per_beat - rytmi = [round(x*4*iskun_kesto) for x in rytmi] + if muunnettava_midi: + muutettava = mido.MidiFile(muunnettava_midi) + muutettava_raita = muutettava.tracks[0] - raita.append(mido.MetaMessage("set_tempo", tempo=mido.bpm2tempo(tempo), time=0)) - raita.append(mido.MetaMessage("time_signature")) - raita.append(mido.Message("program_change", program=0, time=0)) + muunnokset = {} - rytmi_i = 0 + nuotti_i = 0 - for nuotti in nuotit: - if nuotti: - raita.append(mido.Message("note_on", note=nuotti, velocity=64, time=0)) - raita.append(mido.Message("note_on", note=nuotti, velocity=0, time=rytmi[rytmi_i])) - rytmi_i = (rytmi_i + 1) % len(rytmi) + for viesti in muutettava_raita: + if viesti.type == "note_on" and viesti.velocity > 0: + muunnokset[viesti.note] = nuotit[nuotti_i] + raita.append(mido.Message("note_on", + note=muunnokset[viesti.note], velocity=64, time=viesti.time)) + + nuotti_i += 1 + if nuotti_i == len(nuotit): + break + elif viesti.type == "note_off" or (viesti.type == "note_on" and viesti.velocity == 0): + raita.append(mido.Message("note_on", + note=muunnokset[viesti.note], velocity=0, time=viesti.time)) + + else: + raita.append(viesti) + else: + iskun_kesto = midi.ticks_per_beat + rytmi = rytmi_taulukoksi(rytmi) + rytmi = [round(x*4*iskun_kesto) for x in rytmi] + + raita.append(mido.MetaMessage("set_tempo", tempo=mido.bpm2tempo(tempo), time=0)) + raita.append(mido.MetaMessage("time_signature")) + raita.append(mido.Message("program_change", program=0, time=0)) + + rytmi_i = 0 + + for nuotti in nuotit: + if nuotti: + raita.append(mido.Message("note_on", note=nuotti, velocity=64, time=0)) + raita.append(mido.Message("note_on", note=nuotti, velocity=0, time=rytmi[rytmi_i])) + rytmi_i = (rytmi_i + 1) % len(rytmi) midi.tracks.append(raita) midi.save(tiedostopolku) def rytmi_taulukoksi(rytmi): + """Muuntaa annetun rytmin numeeriseksi taulukoksi""" rytmi = rytmi.split("|") tulos = [] for alkio in rytmi: diff --git a/src/musiikki_generaattori.py b/src/musiikki_generaattori.py index 6358a45..e9d31bf 100644 --- a/src/musiikki_generaattori.py +++ b/src/musiikki_generaattori.py @@ -45,7 +45,7 @@ class MusiikkiGeneraattori: for jono in lue_midi(tiedosto): self._opetusdata.append(jono) # Kaikki virheet halutaan ohittaa - except: # pylint: disable=bare-except + except Exception: # pylint: disable=broad-except continue def valmistele_ketju(self, alkuosa, aste=1): @@ -95,9 +95,14 @@ class MusiikkiGeneraattori: return len(self._nuotit) - def kirjoita_midi(self, tiedostopolku, tempo=120, rytmi="1/4"): - """Kirjoittaa nuotit MIDI-tiedostoon halutulla tempolla ja rytmillä""" - kirjoita_midi(tiedostopolku, self._nuotit, tempo, rytmi) + def kirjoita_midi(self, tiedostopolku, muunnettava_midi=None, tempo=120, rytmi="1/4"): + """Kirjoittaa nuotit MIDI-tiedostoon halutulla tempolla ja rytmillä. + Vaihtoehtoisesti ottaa valitun MIDI-tiedoston ja vaihtaa sävelkorkeudet + """ + if muunnettava_midi: + kirjoita_midi(tiedostopolku, self._nuotit, muunnettava_midi) + else: + kirjoita_midi(tiedostopolku, self._nuotit, None, tempo, rytmi) def _nuotit_midiksi(self, nuotit: str): """Muuttaa perinteiset nuottimerkinnät kuten C#4 MIDI-arvoiksi""" diff --git a/src/ui.py b/src/ui.py index 37dceb1..a88b602 100644 --- a/src/ui.py +++ b/src/ui.py @@ -1,6 +1,7 @@ """Musiikki generaattorin tekstipohjainen käyttöliittymä from ui import UI""" from os import system, name +from os.path import exists from re import search from sys import exit as sys_exit from musiikki_generaattori import musiikki_generaattori @@ -15,6 +16,7 @@ class UI: # pylint: disable=too-few-public-methods, too-many-instance-attributes self._nuottien_maara = 120 self._tempo = 120 self._rytmi = "1/4" + self._muunnettava_midi_polku = "" self._toiminnot = { "A": ("(A)pu", self._tulosta_apu, 0, @@ -25,14 +27,16 @@ class UI: # pylint: disable=too-few-public-methods, too-many-instance-attributes "Valitse valmiin sävelmän polku"), "K": ("(K)etjun aste", self._aseta_aste, 1, "Aseta Markovin ketjun aste valittuun kokonaislukuun"), + "L": ("a(L)kuosa [merkkijono]", self._aseta_alkuosa, 1, + "Aseta alkuosa. Esim. 'C4|D#5|Gb3'"), "N": ("(N)uotteja [luku]", self._aseta_nuottien_maara, 1, "Aseta sävelmän nuottien määrä valittuun kokonaislukuun"), "T": ("(T)empo [luku]", self._aseta_tempo, 1, "Aseta tempo valittuun kokonaislukuun"), "R": ("(R)ytmi [merkkijono]", self._aseta_rytmi, 1, "Aseta rytmi. Esim. '1/4|1/4|1/2'"), - "L": ("a(L)kuosa [merkkijono]", self._aseta_alkuosa, 1, - "Aseta alkuosa. Esim. 'C4|D#5|Gb3'"), + "M": ("(M)uunnettava MIDI [polku]", self._aseta_muunnettava_midi, 1, + "Valitse MIDI, jonka sävelkorkeudet vaihdetaan generoituihin"), "G": ("(G)eneroi sävelmä", self._generoi_savelma, 0, "Generoi sävelmä valituilla asetuksilla"), "S": ("(S)ulje", self._sulje, 0, @@ -69,9 +73,11 @@ class UI: # pylint: disable=too-few-public-methods, too-many-instance-attributes for i in range(1, 1+parametrien_maara): parametrit.append(komento[i]) funktio(parametrit) - # Kaikki virheet halutaan ohittaa - except: # pylint: disable=bare-except - self._virheet.append("KOMENNON MUOTO VÄÄRÄ!") + except IndexError: + self._virheet.append("ARGUMENTTIEN MÄÄRÄ VÄÄRÄ!") + # Kaikki muut virheet halutaan ohittaa + except Exception as exception: # pylint: disable=broad-except + self._virheet.append(f"TUNTEMATON VIRHE! {str(exception)}") def _tyhjenna(self): # windows @@ -92,12 +98,14 @@ class UI: # pylint: disable=too-few-public-methods, too-many-instance-attributes def _tulosta_arvot(self): print(f"Opetusdata: '{self._opetusdata_polku}'") print(f"Generoidun sävelmän polku: '{self._tulos_polku}'") - print() print(f"Markovin ketjun aste: {self._aste}") + print(f"Alkuosa: '{self._alku}'") print(f"Sävelmän pituus nuotteina: {self._nuottien_maara}") + print() print(f"Tempo: {self._tempo}") print(f"Rytmi: '{self._rytmi}'") - print(f"Alkuosa: '{self._alku}'") + print("TAI") + print(f"Muunnettava MIDI: '{self._muunnettava_midi_polku}'") def _tulosta_apu(self, _): print("OHJEET") @@ -117,7 +125,7 @@ class UI: # pylint: disable=too-few-public-methods, too-many-instance-attributes try: aste = int(aste[0]) if aste < 1: - raise ValueError; + raise ValueError self._aste = aste except ValueError: self._virheet.append("ANNETTU ASTE EI MUUTETTAVISSA KOKONAISLUVUKSI!") @@ -143,7 +151,7 @@ class UI: # pylint: disable=too-few-public-methods, too-many-instance-attributes def _aseta_rytmi(self, rytmi): rytmi = rytmi[0] if search(r"^\|?((([1-9]\/[1-9])|[1-9])\|)*(([1-9]/[1-9])|[1-9])\|?$", rytmi): - self._rytmi = rytmi + self._rytmi = rytmi else: self._virheet.append("ANNETTU RYTMI EI KELPAA!") @@ -154,13 +162,20 @@ class UI: # pylint: disable=too-few-public-methods, too-many-instance-attributes else: self._virheet.append("ANNETTU ALKUOSA EI KELPAA!") + def _aseta_muunnettava_midi(self, polku): + self._muunnettava_midi_polku = polku[0] + def _generoi_savelma(self, _): musiikki_generaattori.lue_opetusdata(self._opetusdata_polku) musiikki_generaattori.valmistele_ketju(self._alku, self._aste) generoitu_maara = musiikki_generaattori.generoi_nuotteja(self._nuottien_maara) + + if exists(self._muunnettava_midi_polku): + musiikki_generaattori.kirjoita_midi(self._tulos_polku, self._muunnettava_midi_polku) + else: + musiikki_generaattori.kirjoita_midi(self._tulos_polku, None, self._tempo, self._rytmi) print(f"Generoitiin {generoitu_maara} nuottia!\n") - musiikki_generaattori.kirjoita_midi(self._tulos_polku, self._tempo, self._rytmi) def _sulje(self): sys_exit(0)