import math 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 time as tm """--------------------------------------------------------------------------------------------------------------------- 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, ohr=0, l_rausch_ug=0, r_rausch_ug=0, l_rausch_og=0, r_rausch_og=0): 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 # float von 0-1 (0 ist aus) self.rechtsRauschenLautstaerke = r_rausch self.linksRauschenUntereGrenzfrequenz = l_rausch_ug self.rechtsRauschenUntereGrenzfrequenz = r_rausch_ug self.linksRauschenObereGrenzfrequenz = l_rausch_og self.rechtsRauschenObereGrenzfrequenz = r_rausch_og self.ohr = ohr # 0:both 1:left 2:right 3:links/rechts unterschiedlich 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, die zum erstellen einer .wav-Datei benötigt werden (siehe soun.wav_speichern()) Das 'sound_obj' ist für das dynamische abspielen zuständig (siehe sound.play()) Beim Initialisieren muss eine Tinnitus-Objekt übergeben werden ---------------------------------------------------------------------------------------------------------------------""" class Sound: def __init__(self, tinnitus, name="MeinTinnitus.wav", audio=None, nchannels=1, sampwidth=2, framerate=44100, comptype="NONE", compname="not compressed", mute=True): if audio is None: audio = [] self.tinnitus = tinnitus self.name = name self.audio = audio # ein Array, in das die Sound-Werte geschrieben werden (von -1, bis +1) self.nchannels = nchannels # Zahl der audio channels (1:mono 2:stereo) self.sampwidth = sampwidth # Größe eines einzelnen Sound-Werts (in bytes) self.framerate = framerate # Abtastrate self.nframes = len(audio) # Anzahl der Sound-Werte -> Muss bei jeder audio-Änderung aktuallisiert werden self.comptype = comptype self.compname = compname self.mute = mute # 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 def wav_speichern(self): # ezeugt/aktuallisiert die .wav-Datei # MOMENTAN NUR MONO print("Sound wird als .wav-Datei gespeichert. Bitte warten...\nDer Vorgang kann ca. 30 Sekunden dauern") wav_obj = wave.open("MeinTinnitus.wav", "w") # Die Callback-Funktion aufrufen, um die Audiodaten zu bekommen frames = 44100 # entspricht 1 Sekunde status = "" # für den Funktionsaufruf benötigt, sonst keine Funktion audio = np.ones((frames, 2)) audio = self.callback(audio, frames, self.sound_obj.time, status) # Rahmenparameter für die .wav-Datei setzen wav_obj.setnchannels(1) # mono wav_obj.setsampwidth(self.sampwidth) wav_obj.setframerate(self.framerate) for sample in audio[:, 0]: # geht gerade nur die Werte des linken Ohrs (0) durch #Die Audiodaten müssen von float in einen passenden int-Wert umgerechnet werden wav_obj.writeframes(struct.pack('h', int(sample * 32767.0))) #beschreibt die Datei mit diesen Werten wav_obj.close() print("Speichern beendet.") """Die Objekt-Funktion 'start()' 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. """ def play(self): 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 """Die Funktion callback() erzeugt bei jedem Aufruf die Audiodaten, abhängig von den aktuellen Tinnitus-Variablen. Die errechneten Werte werden in das NumPy-Array 'outdata' geschrieben """ def callback(self, outdata, frames, time, status): if status: # Warnungen, wenn das Soundobj. auf Fehler stößt (hauptsächlich over/underflow wg. Timingproblemen) print(status, file=sys.stderr) # Whitenoise erzeugen 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 (=second order sections = Filter 2. Ordnung) ist ein Array der Länge (filterOrder) und beinhaltet # die Koeffizienten der IIR Filter 2. Ordnung (b0, b1, b2 & a0, a1, a2) 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 (=second order sections = Filter 2. Ordnung) ist ein Array der Länge (filterOrder) und beinhaltet # die Koeffizienten der IIR Filter 2. Ordnung (b0, b1, b2 & a0, a1, a2) 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 ins Array schreiben: f(t) = A * sin(2 * pi * f * t) for x in range(len(outdata)): # links: outdata[x][0] += self.tinnitus.linksLautstaerke * np.sin(2 * np.pi * self.tinnitus.linksFrequenz * ((x + self.start_idx) / self.framerate)) # rechts: 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