123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335 |
- import wave # bearbeiten von .wav-Dateien
- import struct
- import sounddevice as sd
- import numpy as np
- import sys # für Fehlermeldungen
- from scipy import signal
- import csv
- import time
- import matplotlib.pyplot as plt
-
- """---------------------------------------------------------------------------------------------------------------------
- In .wav-Dateien wird der Ton in absoluten Werte eingetragen. Die Standart-framerate ist 44100
- das heißt für jede Sekunde an Ton gibt es 44100 Werte, die die Tonwelle über die Zeit beschreiben
- ---------------------------------------------------------------------------------------------------------------------"""
-
-
- class Tinnitus: # beinhaltet alle Werte, die vom Nutzer eingestellt werden
- def __init__(self, l_freq=0, r_freq=0, l_amp=0, r_amp=0, l_rausch=0, r_rausch=0, l_rausch_ug=10, r_rausch_ug=10, l_rausch_og=20000, r_rausch_og=20000):
- self.vorname = ""
- self.nachname = ""
- self.kommentar = ""
- # [Alle Frequenzangaben in Hz, alle Amplitudenangaben von 0-1 (float)]
- self.linksFrequenz = l_freq
- self.rechtsFrequenz = r_freq
- self.linksLautstaerke = l_amp
- self.rechtsLautstaerke = r_amp
- self.linksRauschenLautstaerke = l_rausch
- self.rechtsRauschenLautstaerke = r_rausch
- self.linksRauschenUntereGrenzfrequenz = l_rausch_ug
- self.rechtsRauschenUntereGrenzfrequenz = r_rausch_ug
- self.linksRauschenObereGrenzfrequenz = l_rausch_og
- self.rechtsRauschenObereGrenzfrequenz = r_rausch_og
-
- def speichern(self): # speichert die Nutzerdaten in eine .csv-Datei
- datei = open("TinnitusDaten.csv", "w")
-
- daten = "Vorname;" + self.vorname + "\n"
- daten += "Nachname;" + self.nachname + "\n"
- daten += "linke Frequenz;" + str(self.linksFrequenz) + "\n"
- daten += "linke Lautstärke;" + str(self.linksLautstaerke) + "\n"
- daten += "linkes Rauschen Lautstärke;" + str(self.linksRauschenLautstaerke) + "\n"
- daten += "linkes Rauschen untere Grenzfrequenz;" + str(self.linksRauschenUntereGrenzfrequenz) + "\n"
- daten += "linkes Rauschen obere Grenzfrequenz;" + str(self.linksRauschenObereGrenzfrequenz) + "\n"
-
- daten += "rechte Frequenz;" + str(self.rechtsFrequenz) + "\n"
- daten += "rechte Lautstärke;" + str(self.rechtsLautstaerke) + "\n"
- daten += "rechtes Rauschen Lautstärke;" + str(self.rechtsRauschenLautstaerke) + "\n"
- daten += "rechtes Rauschen untere Grenzfrequenz;" + str(self.rechtsRauschenUntereGrenzfrequenz) + "\n"
- daten += "rechtes Rauschen obere Grenzfrequenz;" + str(self.rechtsRauschenObereGrenzfrequenz) + "\n"
-
- daten += "Kommentar;" + str(self.kommentar) + "\n"
-
- datei.write(daten)
- datei.close()
-
-
- """---------------------------------KLASSE: SOUND-----------------------------------------------------------------------
- Sound beinhaltet alle Variablen und Funktionen zum bearbeiten der wav-Dateien und zum abspielen des Sounds in Echtzeit
- Beim Initialisieren muss ein Tinnitus-Objekt übergeben werden
- ---------------------------------------------------------------------------------------------------------------------"""
-
-
- class Sound:
- def __init__(self, tinnitus):
-
- self.tinnitus = tinnitus
-
- # Variablen für das Abspeichern und Abspielen des Tinnitus-Geräuschs:
- self.wav_name = "MeinTinnitus.wav" #Der Dateiname
- self.audio = [] # ein Array, in das die Sound-Werte geschrieben werden (von -1, bis +1)
- self.nchannels = 2 # Zahl der audio channels (1:mono 2:stereo)
- self.sampwidth = 2 # Größe eines einzelnen Sound-Werts (in bytes)
- self.framerate = 44100 # Abtastrate
- self.nframes = len(self.audio) # Anzahl der Sound-Werte -> Muss bei jeder audio-Änderung aktuallisiert werden
- self.comptype = "NONE"
- self.compname = "not compressed"
- self.mute = True # wenn der mute boolean auf true gesetzt ist, sollte kein Ton ausgegeben werden
- self.sound_obj = sd.OutputStream(channels=2, callback=self.callback,
- samplerate=self.framerate) # Objekt fürs Abspielen (siehe sound.play())
- self.start_idx = 0 # wird für sound_obj benötigt
-
- #Variablen für das Filtern der Musikdatei:
- self.music_samplerate = 0 # die samplerate der ausgewählten Musikdatei
- self.music_data = 0 # das Numpy Array der ausgewählten Musikdatei
- self.filterfortschritt = 0, 0. # für feedback-fkt, 1.Position: Abschnitt-Nmr, 2.Positon: Speicherfortschritt
-
-
- def wav_speichern(self): # ezeugt/aktuallisiert die .wav-Datei
- print("Sound wird als .wav-Datei gespeichert. Bitte warten...\nDer Vorgang kann ca. 10 Sekunden dauern")
-
- wav_obj = wave.open(self.wav_name, "w")
-
- # Die Callback-Funktion aufrufen, um die Audiodaten zu bekommen
- nframes = self.framerate * 10 # entspricht 10 Sekunden
- status = "" # für den Funktionsaufruf benötigt, sonst keine Funktion
- audio = np.ones((nframes, 2)) # Audio-Array initialisieren
- audio = self.callback(audio, nframes, self.sound_obj.time, status)
-
- #Audio-Samples auf 1 normieren. Samples müssen zum speichern zwischen -1 und 1 liegen
- #Maximum finden (Funktion max(...) ist minimal schneller, macht aber Probleme beim Feedback)
- max_ges = 1
- for i in range(nframes):
- if max_ges < abs(audio[i][0]):
- max_ges = abs(audio[i][0])
- if max_ges < abs(audio[i][1]):
- max_ges = abs(audio[i][1])
-
- #auf 4 Nachkommastellen aufrunden
- max_ges = int(max_ges * 10000)
- max_ges += 1
- max_ges /= 10000
-
- print("X_GES: ", max_ges)
-
- #Alle samples normieren
- audio[:, 0] /= max_ges
- audio[:, 1] /= max_ges
-
-
- # Rahmenparameter für die .wav-Datei setzen
- self.nframes = len(audio)
- wav_obj.setparams((self.nchannels, self.sampwidth, self.framerate, self.nframes, self.comptype, self.compname))
-
- packedMusic = [] # Liste an die wir die einzelnen frames bereits in binär umgewandelt aneinanderreihen
-
- #Die Audiosamples schreiben
- for x in range(self.nframes): # geht jeden Sample-Wert der Musikdatei einzeln durch
- # Die Audiodaten müssen von float in einen passenden int-Wert umgerechnet werden
- packedMusic.append(struct.pack('h', int(audio[x][0] * 32767.0)))
- packedMusic.append(struct.pack('h', int(audio[x][1] * 32767.0)))
-
- value_str = b"".join(packedMusic)
- wav_obj.writeframes(value_str)
-
- wav_obj.close()
- print("Speichern beendet.")
-
-
- def play(self):
- """sound.play()' startet die asynchrone Soundwiedergabe. Sie ruft dabei immer wieder die Funktion
- sound.callback() auf. Daher können die dort genutzten Variablen dynamisch geändert werden. """
- if not self.mute: # Nie abspielen, wenn die GUI auf stumm geschaltet ist
- self.sound_obj.start() # öffnet thread der immer wieder callback funktion aufruft und diese daten abspielt
-
-
- def stop(self):
- self.sound_obj.stop() # beendet die asynchrone Soundwiedergabe
-
-
- def callback(self, outdata, frames, time, status):
- """erzeugt bei jedem Aufruf die Audiodaten, abhängig von den aktuellen Tinnitus-Variablen.
- Die errechneten Werte werden in das NumPy-Array 'outdata' geschrieben """
- if status: # Warnungen, wenn das Soundobj. auf Fehler stößt (hauptsächlich over/underflow wg. Timingproblemen)
- print(status, file=sys.stderr)
-
-
- # Whitenoise erzeugen
- # Wird auch durchlaufen, wenn es kein Rauschen gibt, damit die alten Daten mit 0 überschrieben werden
- for x in range(len(outdata)):
- rand = (np.random.rand() - 0.5) # Zufallszahl zwischen -0.5 und 0.5
- # links:
- outdata[x][0] = rand * self.tinnitus.linksRauschenLautstaerke
- # rechts:
- outdata[x][1] = rand * self.tinnitus.rechtsRauschenLautstaerke
-
- # Whitenoise durch Bandpass laufen lassen
- if self.tinnitus.linksRauschenLautstaerke:
- # (-3dB Grenzen) bzw was der Bandpass durchlässt
- fGrenz = [self.tinnitus.linksRauschenUntereGrenzfrequenz, self.tinnitus.linksRauschenObereGrenzfrequenz]
- sos = signal.butter(5, fGrenz, 'bandpass', fs=self.framerate, output='sos')
- # sosfilt filtert das Signal mittels mehrerer 'second order sections' (= Filter 2. Ordnung) die über sos definiert sind
- outdata[:, 0] = signal.sosfilt(sos, outdata[:, 0])
-
- if self.tinnitus.rechtsRauschenLautstaerke:
- # (-3dB Grenzen) bzw was der Bandpass durchlässt
- fGrenz = [self.tinnitus.rechtsRauschenUntereGrenzfrequenz, self.tinnitus.rechtsRauschenObereGrenzfrequenz]
- sos = signal.butter(5, fGrenz, 'bandpass', fs=self.framerate, output='sos')
- # sosfilt filtert das Signal mittels mehrerer 'second order sections' (= Filter 2. Ordnung) die über sos definiert sind
- outdata[:, 1] = signal.sosfilt(sos, outdata[:, 1])
-
-
- # Sinus addieren: f(t) = A * sin(2 * pi * f * t)
- for x in range(len(outdata)):
- # links: rauschen und sinus wieder zusammen addieren
- outdata[x][0] += self.tinnitus.linksLautstaerke * np.sin(2 * np.pi * self.tinnitus.linksFrequenz *
- ((x + self.start_idx) / self.framerate))
- # rechts: rauschen und sinus wieder zusammen addieren
- outdata[x][1] += self.tinnitus.rechtsLautstaerke * np.sin(2 * np.pi * self.tinnitus.rechtsFrequenz *
- ((x + self.start_idx) / self.framerate))
-
- self.start_idx += frames
- return outdata
-
-
- def musik_filtern(self):
- """
- Diese Funktion filtert die Tinnitus Frequenz aus einer gewählten Musikdatei. Dabei geht sie in 4 großen
- Schritten vor:
- 1. Die nötigen Informationen über den Tinnitus aus der .csv Datei herausholen
- 2. Die digitalen Filter erstellen und die Tinnitus Frequenz aus der Audiodatei "herausschneiden"
- 3. Überschwinger finden und alle Audiosamples wieder auf 1 normieren
- 4. Die fertigen Audiodatei als .wav Datei speichern
- """
- nframes = len(self.music_data) # Gesamtanzahl der Frames in der Musikdatei
-
- # ------------1. Die nötigen Informationen über den Tinnitus aus der .csv Datei herausholen---------------------
- self.filterfortschritt = 1, 0 # der erste schritt
- csvstring = open("TinnitusDaten.csv").read()
- tinnitus_data = csvstring.split("\n")
-
- # linke Frequenz aus csv Datei holen
- lf = tinnitus_data[2]
- lf = lf.split(";")
- lf = float(lf[1])
-
-
- # rechte Frequenz aus csv Datei holen
- rf = tinnitus_data[7]
- rf = rf.split(";")
- rf = float(rf[1])
-
-
- # linke Lautstärke aus cvs Datei holen
- ll = tinnitus_data[3]
- ll = ll.split(";")
- ll = float(ll[1])
-
- # rechte Lautstärke aus cvs Datei holen
- rl = tinnitus_data[8]
- rl = rl.split(";")
- rl = float(rl[1])
-
- # -------- 2. Die digitalen Filter erstellen und die Tinnitus Frequenz aus der Audiodatei "herausschneiden------
- self.filterfortschritt = 2, 0 # der zweite schritt im Feedback
- start_time = time.time() # einen Timer laufen lassen um zu sehen wie lange Filterung dauert
-
- #Parameter festlegen
- order = 501 # Filterordnung
- bandwidth = 1000 # Bandbreite des Sperrbereichs in Hz
- self.music_data = self.music_data/32767 # convert array from int16 to float
-
- # ------------------------------------------LEFT EAR FILTERING-------------------------------------------------
- if ll != 0.0: # nur wenn die Lautstärke des linken Tinnitus ungleich 0 ist, wird auf diesem Ohr auch gefiltert
- cutoff_frequencies = [(lf - (bandwidth / 2)),
- (lf + (bandwidth / 2))] # the cutoff frequencies (lower and upper)
- h = signal.firwin(order, [cutoff_frequencies[0], cutoff_frequencies[1]], fs=self.music_samplerate)
- music_links = signal.lfilter(h, 1.0, self.music_data[:, 0])
- else:
- music_links = self.music_data[:, 0] # ungefiltert, wenn kein Tinnitus angegeben wurde
-
- # ------------------------------------- RIGHT EAR FILTERING ------------------------------------------------
- if rl != 0.0: # nur wenn die Lautstärke des rechten Tinnitus ungleich 0 ist, wird auf diesem Ohr auch gefiltert
- cutoff_frequencies = [(lf - (bandwidth / 2)),
- (lf + (bandwidth / 2))] # the cutoff frequencies (lower and upper)
- h = signal.firwin(order, [cutoff_frequencies[0], cutoff_frequencies[1]], fs=self.music_samplerate)
- music_rechts = signal.lfilter(h, 1.0, self.music_data[:, 1])
- else:
- music_rechts = self.music_data[:, 1] # ungefiltert, wenn kein Tinnitus angegeben wurde
-
- endTimeFiltering = time.time()
- print("benötigte Zeit zum Filtern rechts Ohr =", endTimeFiltering - start_time, "s")
-
- #----------------------- 3. Maxima finden und Samples auf 1 normieren ----------------------------
- self.filterfortschritt = 3, 0 # der dritte schritt
-
- #Maximum finden (Funktion max(...) ist minimal schneller, macht aber Probleme beim Feedback)
- start_time = time.time()
- max_ges = 1
- fortschritt = 0
- for i in range(nframes):
- if max_ges < abs(music_links[i]):
- max_ges = abs(music_links[i])
- if max_ges < abs(music_rechts[i]):
- max_ges = abs(music_rechts[i])
- if i % int(nframes/10) == 0: # gibt Fortschritt in 10%-Schritten an
- fortschritt += 10
- self.filterfortschritt = 3, round(fortschritt, 1)
- print(" max: ", self.filterfortschritt[1], "%")
- end_time = time.time()
- print("Zeitaufwand Maxima-Suche: ", end_time - start_time)
-
- #auf 4 Nachkommastellen aufrunden
- start_time = time.time()
- max_ges = int(max_ges * 10000)
- max_ges += 1
- max_ges /= 10000
- end_time = time.time()
- print("Zeitaufwand Maximum runden: ", end_time - start_time)
-
- #Alle samples normieren
- start_time = time.time()
- music_links /= max_ges
- music_rechts /= max_ges
- end_time = time.time()
- print("Zeitaufwand samples normieren: ", end_time - start_time)
-
- # ------------------------- 4. Die fertigen Audiodatei als .wav Datei speichern --------------------------------
- self.filterfortschritt = [4, 0] # der vierte Schritt
-
- start_time = time.time()
- wav_obj = wave.open("MyTinnitusFreeSong.wav", "w")
-
- # Rahmenparameter für die .wav-Datei setzen
- wav_obj.setparams((self.nchannels, self.sampwidth, self.music_samplerate, nframes, self.comptype, self.compname))
-
- """The values are stored in a temporary list, and when the process is finished, they are joined together into
- an string which is then sent to the output with the traditional writeframes method."""
-
- packedMusic = [] # Liste an die wir die einzelnen frames bereits in binär umgewandelt aneinanderreihen
-
- # Die Audiosamples schreiben
- print("Musikdatei wird erstellt...")
- fortschritt = 0
- for tinnitus_data in range(nframes): #geht jeden Sample-Wert der Musikdatei einzeln durch
- # Die Audiodaten müssen von float in einen passenden int-Wert umgerechnet werden
- packedMusic.append(struct.pack('h', int(music_links[tinnitus_data] * 32767.0)))
- packedMusic.append(struct.pack('h', int(music_rechts[tinnitus_data] * 32767.0)))
- if tinnitus_data % int(nframes/10) == 0: # gibt Fortschritt in 10%-Schritten an
- fortschritt += 10
- self.filterfortschritt = 4, round(fortschritt, 1)
- print(" samples: ", self.filterfortschritt[1], "%")
-
- end_time = time.time()
- print("Zeitaufwand für das packen der einzelnen Samples =", end_time - start_time, "s")
-
- value_str = b"".join(packedMusic)
-
- start = time.time()
- wav_obj.writeframes(value_str)
- end = time.time()
- print("Zeitaufwand für das schreiben aller Frames in die wav Datei =", end - start, "s")
- wav_obj.close()
-
- print("Speichern beendet.")
- self.filterfortschritt = 5, 0 #Nach erfolgreichem Filtern Fortschritt zur Bestätigung auf 5 setzen
|