import math import wave #bearbeiten von .wav-Dateien import struct import sounddevice as sd #zum abspielen des audio-arrays import numpy as np import sys #für Fehlermeldungen #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=600, r_freq=600, l_amp=0, r_amp=0, l_rausch=0, r_rausch=0, ear=0): self.vorname = "" self.nachname = "" self.kommentar = "" 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.ear = ear # 0:both 1:left 2:right 3:links/rechts unterschiedlich return 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;" + str(self.linksRauschenLautstaerke) + "\n" daten += "rechte Frequenz;" + str(self.rechtsFrequenz) + "\n" daten += "rechte Lautstärke;" + str(self.rechtsLautstaerke) + "\n" daten += "rechtes Rauschen;" + str(self.rechtsRauschenLautstaerke) + "\n" datei.write(daten) datei.close() return """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="sound.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 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 = 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 return def wav_speichern(self): #ezeugt/aktuallisiert die .wav-Datei wav_file = wave.open(self.name, "w") print("Sound wird als .wav-Datei gespeichert. Bitte warten...\nDer Vorgang kann ca. 30 Sekunden dauern") #zuerst muss ein Array mit Audiodaten gefüllt werden audio = [] freq = self.tinnitus.linksFrequenz dauer_ms = 10000.0 #10 Sekunden amp = self.tinnitus.linksLautstaerke rauschen = self.tinnitus.linksRauschenLautstaerke num_samples = dauer_ms * (self.framerate / 1000.0) #framerate -pro Sekunde- umgerechnet in -pro Millisekunde- for x in range(int(num_samples)): #einen einfachen Sinus ins array schreiben audio.append(amp * math.sin(2 * math.pi * freq * (x / self.framerate))) if(rauschen): #das Rauschen addieren for x in range(int(num_samples)): audio[x] += (np.random.rand() - 0.5) * rauschen #erst werden die Rahmen-Parameter gesetzt self.nframes = len(audio) # Anpassen des nframes-Parameters wav_file.setparams((self.nchannels, self.sampwidth, self.framerate, self.nframes, self.comptype, self.compname)) #dann wird das audio-Array an die Datei übertragen for sample in audio: wav_file.writeframes(struct.pack('h', int(sample * 32767.0))) wav_file.close() print("Speichern beendet.") return """Die Objekt-Funktion __enter__() 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: # NEVER play sound when patient mutes GUI self.sound_obj.__enter__() return def stop(self): self.sound_obj.__exit__() #beendet die asynchrone Soundwiedergabe return """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 """ #momentan nur Mono. Es werden die Werte des linken Ohrs genutzt def callback(self, outdata, frames, time, status): if status: #gibt Warnungen aus, wenn das Soundobjekt auf Fehler stößt (hauptsächlich over/underflow wegen timingproblemen) print(status, file=sys.stderr) t = (self.start_idx + np.arange(frames)) / self.framerate t = t.reshape(-1, 1) #Sinuston ins Array schreiben outdata[:] = self.tinnitus.linksLautstaerke * np.sin(2 * np.pi * self.tinnitus.linksFrequenz * t) #Rauschen addieren if(self.tinnitus.linksRauschenLautstaerke): for x in range(len(outdata)): rand = (np.random.rand() - 0.5) * self.tinnitus.linksRauschenLautstaerke outdata[x] += rand self.start_idx += frames