Benutzer-Werkzeuge

Webseiten-Werkzeuge


Seitenleiste

ws1516:melodiegenerator

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.

Akkorde

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:

  1. Als Datentyp String: In diesem Fall muss der Ton in der Form „Ton_Oktave“ eingegeben werden. Also zum Beispiel „Fis_1“. Dieser String wird dann mit Hilfe der Methode „hoehenrechner“ in eine Tonhöhe umgewandelt.
  2. Als Datentyp Integer: In diesem Fall wird die natürliche Zahl als Tonhöhe aufgefasst, wie es auch im restlichen Programmierumfeld geschieht.
  3. Als Objekt der Klasse Ton: Hier wird ausschließlich die „tonhoehe“ übertragen, denn die anderen Parameter „lautstaerke“ und „dauer“ sind für den gesamten Akkord geregelt

Sitzung 7

Datum: 14.01.16

Sitzung 8

Anwesend: Sebastian, Svenja Datum: 21.01.2015

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 Datum: 28.01.2016

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

Sitzung 10

Anwesend: Sebastian, Svenja, Csaba, Henry Datum: 04.02.2016

Heute hat Henry sich daran gemacht, die Markov-Analyse in die aktuell bestehende Programmierugebung zu integrieren. Hierzu wurde ein neues Dokument mit dem Titel „Analyse“ erstellt.

from __future__ import division
from Kompositionen import Stimme
from Kompositionen import Ton
from Kompositionen import hoehenrechner
from pickle import Pickler
from pickle import Unpickler
 
def speicher(wb, datei):
    with open(datei,"w")as f:
        p=Pickler(f)
        p.dump(wb)
 
def oeffne(datei):
    with open(datei, "r")as f:
        u=Unpickler(f)
        wb=u.load
        return wb
 
def build_markov(melodie, tonstat={}):    
    '''Diese Methode erwartet eine melodie als Objekt der Klasse Stimme. Aus dieser legt 
    sie ein Wörterbuch an, welches Für jeden Ton speichert, ob und wenn ja,
    wie häufig ein anderer Ton nach diesem Ton vorkam. Nur die Tonhöhe ist relevant.
    Ebenfalls erwartet es ein Wörterbuch, in dem die Tonfolgehäufigkeiten gespeichert sind. Gibt es keins, wird ein leeres erzeugt.'''
    for i,ton in enumerate (melodie.toene):
        if i==len(melodie.toene)-1:
            break
        if ton.hoehe not in tonstat:
            wb={} #Falls der Ton noch nicht vorkam, lege ein Wörterbuch an, speichere den Folgeton und 
            wb[melodie.toene[i+1].hoehe]=1 # füge das Wörterbuch dem großen Wörterbuch hinzu.
            tonstat[ton.hoehe]=wb
        else:#Ist der Folgeton für diesen Ton noch nicht aufgetauch, lege einen neuen Eintrag im Wörterbuch an.
            if melodie.toene[i+1].hoehe not in tonstat[ton.hoehe]:
                tonstat[ton.hoehe][melodie.toene[i+1].hoehe]=1
            else:#Ansonsten erhöhe den Häufigkeitswert um 1.
                tonstat[ton.hoehe][melodie.toene[i+1].hoehe]+=1
    return tonstat

Die Methode build_markov liefert also die absolut statistische Grundlage für eine Analyse. Lediglich kann die Methode nur eine einzelne Datei zur Zeit analysieren. Zur Analyse von einer ganzen Menge Dateien dient die Methode „analyse“:

def analyse (Ordner):
    '''Diese Methode analysiert alle Dateien im Verzeichnis Ordner (als String, also bspw. "Mathesis"), die auf .mid enden.
    Sie gibt ein Wörterbuch mit einem Tonhäufigkeitswörterbuch für alle Dateien zusammen zurück.'''    
    tonstat={}
    for root,dirs,files in os.walk(Ordner):
        for file in files:
            if str(file)[-4:]=='.mid':
                aktmelodie=Stimme(0)#legt eine neue Stimme an
                aktmelodie.add_melodie(os.path.join(Ordner,file))#liest die aktuelle Melodie in die Stimme ein
                tonstat=build_markov(aktmelodie,tonstat)#sexy Rekursion; fügt die Tonhäufigkeiten der aktuellen Melodie dem bestehenden Wörterbuch hinzu
    return tonstat

Als letztes wurde eine Methode geschrieben, die aus einem Wörterbuch, welches absolute Häufigkeiten gespeichert hat, ein Wörterbuch erzeugt, welches für jeden Ton die Wahrscheinlichkeiten für die Folgetöne speichert. Dies geschieht in der Methode „build_markovp“:

def build_markovp(tonstat):
    '''Diese Methode erwartet ein absolute Häufigkeiten und wandelt diese in relative Häufigkeiten um.'''
    tonstatp={}    
    for ton in tonstat:
        tonstatp[ton]={}
        summe=0        
        for folgeton in tonstat[ton]:#Zählt, wie viele Folgetöne für den Ton (absolut) vorkamen.
            summe=summe+tonstat[ton][folgeton]
        for folgeton in tonstat[ton]:#Weist jedem Folgeton seinen relativen Häufigkeitswert zu.
            tonstatp[ton][folgeton]=(tonstat[ton][folgeton])/summe
        neue_summe=0
        for folgeton in tonstatp[ton]:#Weist den Folgetönen Bereiche zwischen 0 und 1 zu, deren Länge ihrer Häufigkeit entsprechen.
            neue_summe=neue_summe+tonstatp[ton][folgeton]#Wird später eine Zufallszahl ziwschen 0 und 1 erzeugt, wird der Ton gewählt, in dessen Bereich diese Zufallszahl trifft.        
            tonstatp[ton][folgeton]=neue_summe
    return tonstatp

Sitzung 11

Anwesend: Sebastian, Svenja, Csaba, Henry Datum: 11.02.2016

Heute wurde an der Datei „Analyse“ weitergearbeitet. Es wurde eine Methode hinzugefügt, welche aus einem „Folgetonwahrscheinlichkeitswörterbuch“ (so wie tonstatp, siehe oben) eine zufällige Melodie erzeugt. Diese Methode heißt buid_melodie:

import random
 
def build_melodie(tonstatp, anfangston, laenge=16,):
    '''Erzeugt eine Zufallsmelodie, die ein relatives Folgetonhäufigkeitswörterbuch tonstatp benötigt. Der Anfangston kann selbst gewählt werden.
    '''
    neue_melodie=Stimme(0)
    anfangston=hoehenrechner(anfangston)  
    i=0
    while i<laenge:
        rdauer=random.randint(1,4)
        if i==0:
            neuer_ton=Ton(rdauer,anfangston,0.4)
        else:
            rhoehe=random.random()
            for folgeton in tonstatp[neue_melodie.toene[len(neue_melodie.toene)-1].hoehe]:
                #Geht die relativen Häufigkeitswerte des Eintrages des aktuell letzten Tones der Melodie im Wörterbuch durch. 
                if rhoehe<tonstatp[neue_melodie.toene[len(neue_melodie.toene)-1].hoehe][folgeton]:
                    #Ist der Bereich gefunden, in dem die Zufallsvariable rhoehe liegt, wird dieser Ton ausgewählt.
                    neuer_ton=Ton(rdauer,folgeton,0.4)
                    break
        neue_melodie.add_ton(neuer_ton)
        i+=1
    return neue_melodie

Außerdem wurde nun der bestehende Code so umgeschrieben, dass zwischen Moll und Dur differenziert wird und alle eingelesenen Melodien auf Tonarten ohne Vorzeichen transponiert werden (also C-Dur oder A-Moll). Die relevanten Passagen finden sich in der Methode „add_melodie“ und der Methode „trans“:

    def add_melodie(datei)
 
    [...]
 
        #Falls in der .mid Datei die Tonart gespeichert ist, wird sie der Stimme hinzugefügt.         
        for event in melodie:
            if isinstance(event, midi.KeySignatureEvent):
                if event.data[1]==0:
                    self.tonart=vorzeichendur[event.data[0]] +"_"+"dur"
                elif event.data[1]==1:
                    self.tonart=vorzeichendur[(event.data[0]+3)%256]+"_"+"moll"
 
        #Die Melodie wird nun in eine Tonart ohne Vorzeichen transponiert.
        if (not self.tonart=="c_dur") and (not self.tonart=="a_moll") and (not self.tonart==""):
            aktgeschlecht=(self.tonart.split('_'))[1]
            if aktgeschlecht=='moll':
                self.trans('a_moll')
            elif aktgeschlecht=='dur':
                self.trans('c_dur')
 
    def trans(self, zieltonart):
        '''Diese Methode transponiert die Stimme von der aktuellen in die Zieltonart. zieltonart liegt dabei als String der Form "Ton_Tongeschlecht" vor, 
        also zum Beispiel "Fis_dur"'''
        zielgrundton=zieltonart.lower()
        zielgrundton=zielgrundton.split('_')
        zielgrundton=tonleiter[zielgrundton[0]]
        aktgrundton=self.tonart.lower()
        aktgrundton=aktgrundton.split('_')
        aktgrundton=tonleiter[aktgrundton[0]]
        for ton in self.toene:
            ton.hoehe=ton.hoehe+(zielgrundton-aktgrundton)
        self.tonart=zieltonart

Auch wurde in der Datei „analyse“ einiges umgeschrieben. So wurde in der Methode „analyse“ zwischen Dur und Moll differenziert:

def analyse (Ordner):
    '''Diese Methode analysiert alle Dateien im Verzeichnis Ordner (als String, also bspw. "Mathesis"), die auf .mid enden.
    Sie gibt ein Wörterbuch mit zwei Tonhäufigkeitswörterbüchern (eines für Dur , eines für Moll) zurück.'''    
    tonstatd={}#dur
    tonstatm={}#moll
    for root,dirs,files in os.walk(Ordner):
        for file in files:
            if str(file)[-4:]=='.mid':
                aktmelodie=Stimme(0)#legt eine neue Stimme an
                aktmelodie.add_melodie(os.path.join(Ordner,file))#liest die aktuelle Melodie in die Stimme ein
                if not aktmelodie.tonart=="":#Wenn eine Tonart in der Datei gefunden wurde
                    aktgeschlecht=aktmelodie.tonart[-4:]
                    if aktgeschlecht=='_dur':
                        tonstatd=build_markov(aktmelodie,tonstatd)#sexy Rekursion; fügt die Tonhäufigkeiten der aktuellen Melodie dem bestehenden Wörterbuch hinzu
                    elif aktgeschlecht=='moll':
                        tonstatm=build_markov(aktmelodie,tonstatm)
    tonstat={'dur':tonstatd, 'moll':tonstatm}
    return tonstat

Für ein schnelleres Arbeiten mit dem Programm wurden die Markovtabellen, in denen die Wahrscheinlichkeiten gespeichert sind (tonstatp, tonstatpm, tonstatpd) per Pickle gespeichert und müssen somit nicht mehr bei jedem Aufruf des Programms neu eingelesen werden:

with open ("tonstatp.pickle","r")as f:
    u=Unpickler(f)
    tonstatp=u.load()
 
with open ("tonstatpd.pickle","r")as f:
    u=Unpickler(f)
    tonstatpd=u.load()
 
with open ("tonstatpm.pickle","r")as f:
    u=Unpickler(f)
    tonstatpm=u.load()
ws1516/melodiegenerator.txt · Zuletzt geändert: 2016/05/10 14:46 (Externe Bearbeitung)