Dies ist eine alte Version des Dokuments!
Wir wollen ein Programm schreiben, welches zu einer gegebenen Akkordfolge/Harmoniefolge eine musikalisch sinnvolle Melodie erzeugt. Wie haben uns entschieden nicht, wie bisher geplant, die Melodie nach Regeln der Musiktheorie generieren zu lassen, sondern zuerst eine Reihe an Melodien einzuspielen, um aus dieser Datenbank eine Melodie aus analysierten Sequenzen zusammenzusetzen.
Anwesend: Svenja, Csaba, Sebastian, Henry Datum: 18.11.2015
Wir haben uns auf eine Projektidee geeinigt. Anschließend haben wir Überlegungen angestellt, welche Hilfsmittel wir benötigen und wie wir die programmiertechnische Umsetzung der Idee gestalten. In diesem Zusammenhang haben wir uns einen Überblick über die Möglichkeit verschafft, vorhandene Melodien in ein Pythonprogramm einzupflegen. Am vielversprechendsen erscheint uns bis jetzt „Python MIDI“, also das Nutzen von .mid Dateien. Wir haben erste Versuche mit dem Package Python MIDI gemacht. Recherche bestätigte uns, dass .mid Datein durchaus im Internet zur Verfügung stehen. Ebenfalls wurde eine TubCloud für unser Projekt angelegt. Es wurde sich geeinigt, dass die Gruppenkommunikation über „WhatsApp“ läuft.
Es wurde sich mit Ausblick auf den nächsten Termin darauf geeinigt, sich mit folgenden Themen zu beschäftigen:
Anwesend: Svenja, Sebastian, Henry Datum: 25.11.2015
Wir haben uns im Rahmen der heutigen Gruppenarbeit über die Ergebnisse unserer Recherchen der letzten Woche ausgetauscht (siehe oben). Insbesondere die Struktur des Python Paketes „Python MIDI“ war ein Gesprächsthema. Wir haben ebenfalls beschlossen, unser Projekt so aufzuteilen, dass jedes Gruppenmitglied bis auf Weiteres einen Themenbereich, also auch eine Aufgabe hat, dem/die er sich vornehmlich widmet. Wir erachteten es als sinnvoll die Aufgaben wie folgt zu verteilen (die Verteilung ist vorläufig, da die Meinung des Gruppenmitglieds Csaba noch nicht eingeholt wurde:
In Einzelarbeit wurden anschließend folgende Themen bearbeitet:
Sebastian:
Svenja:
Henry:
Es wurde sich mit Ausblick auf den nächsten Termin darauf geeinigt, sich mit Themen zu beschäftigen, die im eigenen Aufgabenbereich liegen.
Anwesend: Sebastian, Henry, Csaba Datum: 03.12.2015
In dieser Sitzung haben wir uns entschlossen, die Verwendung von Midi-Dateien aufgrund der komplizierten Handhabung auf das Einlesen und Ausgeben von Musikstücken zu beschränken. Für das Arbeiten mit Melodien werden wir einen eigenes Dateiformat entwerfen. Da dieses Dateiformat möglichst einfach mit Melodien umgehen soll, haben wir uns auf folgende Kriterien zur Beschreibung von Melodien beschränkt:
Da wir allerdings im Midi-format Einlesen und Ausgeben wollen, ist eine Art Übersetzer nötig. Dieser war wesentlicher Bestandteil der heutigen Projektarbeit. Wir haben versucht die Kriterien für unser Dateiformat aus Midi-Code herauszulesen und zu manipulieren. Bisher ist uns das bei der Tonhöhe gelungen, Tondauer gestaltet sich etwas schwieriger. Unsere Ziele für die kommende Projektarbeit wären:
Zudem hielten wir es für sinnvoll, gemeinsam an der Datenbank zu arbeiten, da diese als Quelle für alle Gruppenmitglieder dienen wird, und alle Teilprogramme mit der Datenbank kompatibel sein sollen.
Anwesend: Henry, Svenja Datum: 10.12.15
Da wir relativ dezimiert waren, wurde beschlossen, sich mit der Zusatzaufgabe von Hausaufgabenblatt 4 zu beschäftigen und somit eine mögliche Struktur für eine Klasse „Kompositionen“ zu schreiben. Hierbei wurde die darin enthaltene Methode „spiele“, mithilfe derer Töne ausgegeben werden, aus der test-sequencer_2.py-Datei von Stefan übernommen, welche er in der ownCloud zur Verfügung gestellt hatte. Die Klasse „Ton“ wurde unabhängig von der Klasse „Stimme“ geschrieben und für die Klasse „Ton in Stimme“ ist im Gegensatz zur „Ton“-Klasse nicht nur Dauer, sondern auch der Anfangs- und Endzeitpunkt vorgesehen und sie hat Zugriff auf die Klasse „Ton“.
Anwesend: Henry, Svenja, Sebastian, Csaba Datum: 17.12.15
In der heutigen Sitzung wurde die zunächst die „Komposition.py“-Datei, welche das Ergebnis der vorherigen Sitzung war, in die TUB-cloud gestellt. Anschließend wurden die in letzter Sitzung fehlenden Gruppenmitglieder auf den aktuellen Stand, und die Datei „Kompositionen.py“ bei allen zum Laufen gebracht. Wir teilten uns in 2 Gruppen mit folgenden Aufgaben auf:
Henry:
Svenja, Sebastian, Csaba:
Es wurde beschlossen dass Stefan uns eine Art Übersetzer vom Modul „music21“ zu dem „Sequencer“ in der „Kompositionen.py“-Datei schreibt, um Zeit zu sparen, da das wenig mit dem eigentlichen Projekt zu tun hat. Aufgrund von Schwierigkeiten bei dem Verstehen des Textes in der Svenja-Sebastian-Csaba-Gruppe gab uns Stefan eine Einführung zu Markov-Ketten, die sich Henry ebenfalls anhörte. Zum Schluss lud Henry die Optimierte Version der „Kompositionen.py“-Datei bei der TUB-cloud hoch.
Anwesend: Henry, Svenja, Csaba, Sebastian Datum: 07.01.16
Alle Mitglieder der Gruppe versuchten music21 und pygame auf ihren PC's zu installieren. Es gab Probleme bei dem Installieren von pygame, da pygame nicht für Mac und die 64-bit Version von Windows ausgelegt ist. Doch ließ sich pygame letztendlich durch ein extension pack (http://www.lfd.uci.edu/~gohlke/pythonlibs/#pygame) auf Windows installieren.
Zuletzt präsentierten alle einzelnen Gruppen ihre jeweiligen Projekte.
Hierum geht es:kompositionen.py
Die zum Ausführen des Programmes notwendigen Unterstützungspakete findet ihr auf der https://owncloud.innocampus.tu-berlin.de/index.php/s/KxF53uMTSi5rkfE/authenticate bzw. unter https://github.com/vishnubob/python-midi.
Das Programm ist modular aufgebaut, das heißt, konkret Objekte der Klasse „Komposition“ bestehen im Grunde aus einer Liste von Stimmen (vgl. Z.20-24 (Die Zeilenangaben beziehen sich auf die .py /Python Datei bzw. auf die Bildschirmfotos)). Stimmen sind wiederum Listen von Tönen (vgl. Z. 56-60). Neben den enthaltenen Objekten haben die einzelnen Objekte zusätzliche Parameter, die nicht in den Teilobjekten gespeichert sind. Beispielsweise sind dies „ende“ (vgl.Z.59), „tonart“ (vgl.Z.22) oder „anfang“ und „ende“ (vgl.Z.143, 144). Bei der Klasse „Ton_in_Stimme“ handelt es sich um eine Klasse, die von der Klasse „Ton“ erbt, also alle ihre Parameter und Mehtoden übernimmt allerdings zusätzlich die Parameter „anfang“ und „ende“ enthält(vgl.Z.134-144). Diese Struktur wird hier angewandt, da aus der Logik der Klasse folgt, dass Töne als solches existieren sollen und abspielbar sind, jedoch noch nicht zwangsläufig in einer Stimme existieren müssen und daher auch keine Eigenschaften, wie den Anfangszeitpunkt und den Endzeitpunkt haben.
Komposition.spiele: def spiele(self, art="standard"): sound=Sound() # das Objekt, das sich um die Tonausgabe kümmert sound.klanggen=Sequencer(6000,len(self.stimmen)) kompende=0.0 for stimme in self.stimmen: for ton in stimme.toene: if art=="standard": sound.klanggen.add_note(stimme.stimmennummer, grundton*HALBTON**ton.hoehe, ton.anfang*viertelnote, ton.ende*viertelnote, ton.lautstaerke, obertoene, gewichte) elif art=="dreieck": sound.klanggen.add_note_dreieck(stimme.stimmennummer,grundton*HALBTON**ton.hoehe, ton.anfang*viertelnote, ton.ende*viertelnote,viertelnote/16, ton.lautstaerke,obertoene2, gewichte2) if stimme.ende>kompende: kompende=stimme.ende threading.Thread(target=sound.play).start() # Tonausgabe gestartet time.sleep(1) jetzt=time.time() while jetzt+kompende*viertelnote+1>time.time(): pass sound.outStream.close()
Um Töne abspielen zu können stützt sich das Programm (zur Zeit) auf einen einfachen von Stefan Born beigesteuerten Sequencer (siehe owncloud). Wir behandeln die respektiven Programmteile weitestgehend als „black box“ also als Werkzeug, welches wir zwar nutzen, allerdings nicht komplett verstehen. Relevant ist, dass es zum jetzigen Zeitpunkt zwei Möglichkeiten, Töne bzw. Stimmen oder Kompositionen abzuspielen: mit dem Schlüsselwort „standard“ als einfache Sinustöne oder mit dem Schlüsselwort „dreieck“ als Dreieckskurve (klingt etwas natürlicher)(vgl. Z.120-132). Die scheinbare Redundanz der „spiele“-Methode in den Klassen Ton, Stimme und Komposition (Könnte man nicht bei einer Stimme einfach jeden Ton hintereinander mit der Ton.spiele-Methode aufrufen und dasselbe mit Komposition?) erklärt sich aus dem Aufruf des Sequencers. So gäbe es nach jedem Mal, wenn ein Ton aus der „toene“-Liste einer Stimme mit der „spiele“-Mehtode der Klasse „Ton“ aufgerufen wurde eine kurze Pause bevor der nächste Ton abgetastet wird. Aus diesem Grund findet sich ein ähnlicher Code in allen „spiele“-Methoden.
Stimme.add_melodie: def add_melodie(self, datei): '''Diese Methode fügt der Stimme die Melodie hinzu, welche in der "datei" in .mid Form gespeichert ist.''' stueck=midi.read_midifile(datei) melodie=stueck[0] letzter_ton_tick=0 akt_ton_tick=0 akt_ton=0 i=0 akt_tick=0 while i<len(melodie): try: akt_tick=akt_tick+melodie[i].tick except: pass if isinstance(melodie[i], midi.NoteOnEvent): if letzter_ton_tick!=0 and letzter_ton_tick!=akt_tick: self.add_pause((akt_tick-letzter_ton_tick)/200) akt_ton_tick=akt_tick akt_ton=i elif isinstance(melodie[i], midi.NoteOffEvent): neuer_ton=Ton((akt_tick-akt_ton_tick)/200, melodie[akt_ton].data[0]-60, (melodie[akt_ton].data[1])/220) self.add_ton(neuer_ton) letzter_ton_tick=akt_tick i+=1
Auf Grund der Tatsache, dass wir uns bei der Analyse von Melodien auf midi-Dateien stützen wollen, benötigen wir eine Art Übersetzer, der aus den uns gegebenen .mid-Dateien relevante Informationen in unser Dateiformat überführt. Diese Aufgabe soll die Methode „add_melodie“ erfüllen (vgl.Z.87-111). Die Übersetzer ist eine Methode der Klasse „Stimme“, was vielleicht nicht besonders intuitiv ist. Die Logik dahinter ist, dass alle Töne, die in der Melodie, die in der .mid-Datei gespeichert sind, als Objekte der Klasse „Ton“ erstellt werden und anschließend dem jeweiligen Objekt der Klasse „Stimme“ hinzugefügt wird. Nach dem Öffnen der Datei (vgl.Z.90-91)wird folgender Algorithmus angewandt (.mid-Dateien sind Listen verschiedener Stimmen, für den Spezialfall eines Solos liegt die relevante Stimme allerdings immer an der ersten Stelle dieser Liste Stueck[0]):
Zum jetzigen Zeitpunkt ist der Überstzer eine funktionsfähige Methode, allerdings wird jedem eingelesenen Ton aus noch unbekannten Gründen eine „dauer“ von 0.0 zugeordnet.
Update: Übersetzer repariert
Zwei Probleme des Übersetzers wurden jetzt behoben:
Einerseits wurde die Zeile
try: akt_tick=akt_tick+melodie[i]
in
try: akt_tick=akt_tick+melodie[i].tick
geändert. In der ersten Version wird nicht der tick, also die Variable, die für das timing zuständig ist, sondern das Event insgesamt abgerufen. Da dies jedoch kein Zahl, also kein Objekt ist, mit der sich eine Addition durchführen lässt, ist, wurde vor der Verbesserung dieser Schritt auf Grund der Tatsache, dass er innerhalb einer try-Abfrage steht nicht durchgeführt, sondern übersprungen. In anderen Worten: Ein Event lässt sich nicht addieren, daher gibt es ein Fehler, also wird der Teil übersprungen. Die Verbesserung sieht vor, dass auf die tick-Variable zugegriffen wird.
Andererseits wurde die Zeile
self.add_pause(akt_tick-letzter_ton_tick)
durch die Zeile
self.add_pause( (akt_tick-letzter_ton_tick)/200)
ersetzt. Durch die fehlende Division waren alle übertragenen Pausen 200-mal so lang wie sie eigentlich sein sollten.
class Akkord(object): def __init__(self, dauer, lautstaerke, name=""): self.name=name self.dauer=dauer self.lautstaerke=lautstaerke self.toene=[] def add_ton(self, ton): if isinstance(ton, str): neuer_ton=Ton(self.dauer, hoehenrechner(ton), self.lautstaerke) elif isinstance(ton,int): neuer_ton=Ton(self.dauer, ton, self.lautstaerke) elif isinstance(ton, Ton): neuer_ton=Ton(self.dauer, ton.hoehe, self.lautstaerke) self.toene.append(neuer_ton) def spiele(self,art="standard"): sound=Sound() # das Objekt, das sich um die Tonausgabe kümmert sound.klanggen=Sequencer(6000,len(self.toene)) i=0 for ton in self.toene: if art=="standard": sound.klanggen.add_note(i, grundton*HALBTON**ton.hoehe, 0*viertelnote, self.dauer*viertelnote, self.lautstaerke, obertoene, gewichte) i+=1 elif art=="dreieck": sound.klanggen.add_note_dreieck(i,grundton*HALBTON**ton.hoehe,0*viertelnote, self.dauer*viertelnote,viertelnote/16, self.lautstaerke,obertoene2, gewichte2) i+=1 threading.Thread(target=sound.play).start() # Tonausgabe gestartet time.sleep(1) jetzt=time.time() while jetzt+self.dauer*viertelnote+1>time.time(): pass sound.outStream.close() tonleiter= {'ces':-1,'c':0,'cis':1,'des':1,'d':2,'dis':3,'es':3,'e':4,'fes':4,'eis':5,'f':5,'fis':6,'ges':6,'g':7,'gis':8,'as':8,'a':9,'ais':10,'b':10,'h':11,'his':12} def hoehenrechner(tonname): '''Erwartet den Namen eines Tones als string in der Form "Ton_Oktave" also zum Beispiel "Fis_3"und gibt die entsprechende Tonhöhe zurück. Das mittlere C entspricht dabei "C_0"''' tonname=tonname.lower() tonname=tonname.split('_') hoehe=tonleiter[tonname[0]] hoehe=hoehe+12*int(tonname[1]) return hoehe
Wir haben uns überlegt, dass es sinnvoll wäre Akkorde als Begleitung der generierten Stimmen erstellen zu können. Dies übernimmt der obige Programmteil. Im Grunde ist das meiste den übrigen Klassen sehr ähnlich, mit dem Unterschied, dass in der Klasse Akkord die verschiedenen Töne nicht hinterheinander (wie bei „Stimme“) gespeichert werden, sondern parallel abgespielt werden. Ein Ton kann dem Akkord in verschiedenen Formen übergeben werden:
Datum: 14.01.16
Anwesend: Sebastian, Svenja
Auf die Hälfte reduziert, hat sich Sebastian damit beschäftigt, wie man möglicherweise die Markovketten in Programm schreiben könnte, außerdem wurde ein Programmschnipsel geschrieben, der in Abhängigkeit von Wahrscheinlichkeiten neue Tonhöhen, Lautstärken und Dauern ausgeben kann. Die Wahrscheinlichkeiten und die Möglichkeiten müssen in zwei zueinander passenden Listen vorliegen, was zugegebenermaßen nicht sehr komfortabel ist und vielleicht noch verbessert werden kann. Möglichkeiten sind bei der Tonhöhe dann eben alle Töne, die auf einen bestimmten Ton folgen könnten, denen nach der Analyse der Soli eben eine Wahrscheinlichkeit zugeordnet ist.
Das Ganze sieht dann so aus:
def aufteilungen(wahrscheinlichkeit):#Wahrscheinlichkeit ist eine liste obergrenze=[0] summe=0 for i in range(len(wahrscheinlichkeit)): obergrenze.append(wahrscheinlichkeit[i-1]+summe) summe+=wahrscheinlichkeit[i-1] print obergrenze return obergrenze
Um einen Wert in Abhängigkeit von einer Wahrscheinlichkeit zu bekommen, kann man einen zufälligen Wert erzeugen und schauen, ob dieser in einem bestimmten Bereich liegt, dessen Größe von der zugehörigen Wahrscheinlichkeit abhängt. Für diese Bereiche erstellt diese Funktion eine Liste mit den Grenzen.
def neue_dauer(grenzen, dauerliste): rndzahl=np.random.random() dauer=0 for index in range(len(grenzen)): if rndzahl>=grenzen[index] and rndzahl<grenzen[index+1]: dauer=dauerliste[index] return dauer def neue_lautstaerke(grenzen, lautstaerkeliste): rndzahl=np.random.random() lautstaerke=0 for index in range(len(grenzen)): if rndzahl>=grenzen[index] and rndzahl<grenzen[index+1]: dauer=lautstaerkeliste[index] return lautstaerke def neue_tonhoehe(grenzen, tonhoehenliste): rndzahl=np.random.random() lautstaerke=0 for index in range(len(grenzen)): if rndzahl>=grenzen[index] and rndzahl<grenzen[index+1]: dauer=tonhoehenliste[index] return tonhoehe
Diese drei Funktionen spucken dann durch Iteration über die Listen die Tonattribute entsprechend ihrer Wahrscheinlichkeit aus.
Anwesend: Sebastian, Svenja, Csaba, Henry
Heute haben sich Csaba un Henry mit einer Methode beschäftigt, die Akkorde generiert. Hierzu benötigt die Methode lediglich den Grundton, die Tonart und eventuell eine Umkehrung. Der Code sieht wie folgt aus:
tonarten= {'dur':[0,4,7], 'moll':[0,3,7], 'verm':[0,3,6], 'überm':[0,4,8]} def build_akkord(grundton, art, umkehrung=[0,0,0], dauer=4, lautstaerke=0.1): '''Diese Methode gibt einen Akkord zurück, der nach den Parametern grundton (als Typ int, str oder Ton), art (als Typ 'typ'), umkehrung (als Typ [Grundton, Terz, Quinte], lautstaerke nicht über 0.23 ''' if isinstance(grundton, str): neuer_akkord=Akkord(dauer,lautstaerke, grundton+"_"+art) grundton=hoehenrechner(grundton) elif isinstance(grundton, Ton): neuer_akkord=Akkord(dauer, lautstaerke, str(grundton)+"_"+art) grundton=grundton.hoehe #Grundton neuer_akkord.add_ton(grundton+tonarten[art][0]+12*umkehrung[0]) #Terz neuer_akkord.add_ton(grundton+tonarten[art][1]+12*umkehrung[1]) #Quinte neuer_akkord.add_ton(grundton+tonarten[art][2]+12*umkehrung[2]) return neuer_akkord
Im Gegensatz zu dem Vorgehen in der Klasse Akkord, haben wir uns hier entschieden, keine Zahlen als Eingangsvariablen akzeptieren, weil aus einer bestimmten Tonhöhe noch nicht eindeutig der Tonname hervorgeht und somit musiktheoretisch nicht eindeutig ein Akkord gebaut werden kann (Fis und Ges z.B. haben die Selbe Tonhöhe).