Benutzer-Werkzeuge

Webseiten-Werkzeuge


Seitenleiste

ws1516:melodiegenerator

Dies ist eine alte Version des Dokuments!


Melodiegenerator

Das Team

  • Csaba Karsai
  • Sebastian Eberlein
  • Henry Schoeller
  • Svenja Kern

Projektidee

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.

Hilfsmittel

Equipment

  • Computer
  • Lautsprecher
  • Kopfhörer

Programmiertechnische Hilfmittel

  • music21
  • pythonmidi
  • pygame

Mathematische Hilfsmittel

  • Schwingungen
  • Kreisfunktionen
  • schriftliche Division
  • Markov-Modelle

Protokolle

Sitzung 1

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:

  • Das Dateiformat .mid
  • Python MIDI
  • PyGame
  • Internetquellen für .mid Dateien
  • Ähnliche Programme/Projekte im Internet (z.B. Emily Howell)

Sitzung 2

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:

  • Rahmenprogrammstrukturen (Eingabe, Ausgabe, GUI etc.): Henry
  • Datenbankorganisation: Sebastian
  • Entwicklung des Algorithmus zur Analyse und Generierung: Svenja und Csaba

In Einzelarbeit wurden anschließend folgende Themen bearbeitet:

Sebastian:

  • Listen und Dictionaries in Python
  • Datenbanksysteme
  • insbesondere sqlite 3

Svenja:

  • Überlegungen zur Herangehensweise (Was soll das Ergebnis sein? Ein Programm, das ein Solo im Stile der eingespielten Soli ausgibt)
  • zu jedem Ton muss es eine Wahrscheinlichkeit für den nächsten folgenden Ton geben
  • dies lässt sich mithilfe der Datenbank herausfinden
  • mithilfe einer Übergangsmatrix kann dies in eine im Programm verwertbare Form gebracht werden, der nächste Ton wird dann in Abhängigkeit dieser Wahrscheinlichkeit erstellt → verwende numpy-arrays und .random
  • ähnliches gilt für die Tonlänge
  • einige Faktoren müssen wahrscheinlich erst einmal vernachlässigt werden, um erst einmal grundsätzliche Funktionen zu erreichen, Programm sollte also erweiterbar sein

Henry:

  • Dateiformat .mid
  • Recherche von vorhandenen .mid Dateien im Internet
  • Einlesen und Bearbeiten der gefundenen Dateien
  • Erstellung eines ersten Katalogs an .mid Dateien, die die für uns ausschließlich interessanten „Solo“-Teile der Songs enthält
  • Hochladen der selben in die „tubcloud“

Es wurde sich mit Ausblick auf den nächsten Termin darauf geeinigt, sich mit Themen zu beschäftigen, die im eigenen Aufgabenbereich liegen.

Sitzung 3

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:

  • Anfangszeit
  • Tondauer
  • Tonhöhe
  • Lautsärke

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:

  • Fertigstellen des Übersetzers
  • Erstellen einer Datenbank von Melodien

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.

Sitzung 4

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“.

Sitzung 5

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:

  • Optimieren des Codes der „Komposition.py“-Datei

Svenja, Sebastian, Csaba:

  • Lesen und verstehen der von Stefan ausgedruckten Blätter über Markov-Ketten

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.

Sitzung 6

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.

Zwischenprotokoll: Kompositionen.py

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.

Programmstruktur

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.

Abspielbarkeit

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.

MIDI-Übersetzer

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]):

  • dauer: 25 ticks entsprechen 1/32-Note, also entsprechen 200 ticks einer Viertelnote
  • hoehe: data ist ein Parameter der Objekte NoteOnEvent und NoteOffEvent und legt Tonhöhe und Lautstärke fest. Ist data[0] die Zahl x, heißt das, dass der
  • entsprechende Ton der xte Halbton über dem tiefsten Ton C−1 (oder ,,,C oder Sub Sub Contra C). Ein data[0] Wert von 69 beipielsweise entspricht also dem Kammerton a
  • oder 440 hz. Bezogen auf den von uns genutzten Sequencer entspricht ein Wert von 60 einem c und damit einer hoehe von 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.

Sitzung 7

Datum: 14.01.16

Sitzung 8

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.

Sitzung 9

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
ws1516/melodiegenerator.1454412808.txt.gz · Zuletzt geändert: 2016/05/10 14:46 (Externe Bearbeitung)