|
|
@@ -15,7 +15,7 @@ das heißt für jede Sekunde an Ton gibt es 44100 Werte, die die Tonwelle über |
|
|
|
|
|
|
|
|
|
|
|
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=10, r_rausch_ug=10, l_rausch_og=20000, r_rausch_og=20000): |
|
|
|
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 = "" |
|
|
@@ -55,30 +55,31 @@ class Tinnitus: # beinhaltet alle Werte, die vom Nutzer eingestellt werden |
|
|
|
|
|
|
|
|
|
|
|
"""---------------------------------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()) |
|
|
|
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, wav_name="MeinTinnitus.wav", audio=None, nchannels=2, sampwidth=2, framerate=44100, |
|
|
|
comptype="NONE", compname="not compressed", mute=True): |
|
|
|
if audio is None: |
|
|
|
audio = [] |
|
|
|
def __init__(self, tinnitus): |
|
|
|
|
|
|
|
self.tinnitus = tinnitus |
|
|
|
self.wav_name = wav_name #Der Dateiname |
|
|
|
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 |
|
|
|
|
|
|
|
# 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 |
|
|
@@ -90,10 +91,31 @@ class Sound: |
|
|
|
wav_obj = wave.open(self.wav_name, "w") |
|
|
|
|
|
|
|
# Die Callback-Funktion aufrufen, um die Audiodaten zu bekommen |
|
|
|
frames = self.framerate * 10 # entspricht 10 Sekunden |
|
|
|
nframes = self.framerate * 10 # entspricht 10 Sekunden |
|
|
|
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) |
|
|
|
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) |
|
|
@@ -113,10 +135,10 @@ class Sound: |
|
|
|
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): |
|
|
|
"""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 |
|
|
|
|
|
|
@@ -124,15 +146,16 @@ class Sound: |
|
|
|
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): |
|
|
|
"""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: |
|
|
@@ -143,39 +166,14 @@ class Sound: |
|
|
|
# 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) |
|
|
|
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]) |
|
|
|
|
|
|
|
# Plotten des Filters für Filterentwicklung und Dokumentation nützlich--------- |
|
|
|
# w, h = signal.sosfreqz(sos, worN=1500) |
|
|
|
# plt.subplot(2, 1, 1) |
|
|
|
# db = 20 * np.log10(np.maximum(np.abs(h), 1e-5)) |
|
|
|
# plt.plot(w / np.pi, db) |
|
|
|
# plt.ylim(-75, 5) |
|
|
|
# plt.grid(True) |
|
|
|
# plt.yticks([0, -20, -40, -60]) |
|
|
|
# plt.ylabel('Gain [dB]') |
|
|
|
# plt.title('Frequency Response') |
|
|
|
# plt.subplot(2, 1, 2) |
|
|
|
# plt.plot(w / np.pi, np.angle(h)) |
|
|
|
# plt.grid(True) |
|
|
|
# plt.yticks([-np.pi, -0.5 * np.pi, 0, 0.5 * np.pi, np.pi], |
|
|
|
# [r'$-\pi$', r'$-\pi/2$', '0', r'$\pi/2$', r'$\pi$']) |
|
|
|
# plt.ylabel('Phase [rad]') |
|
|
|
# plt.xlabel('Normalized frequency (1.0 = Nyquist)') |
|
|
|
# plt.show() |
|
|
|
# ------------------------------------------------------------------------------- |
|
|
|
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) |
|
|
|
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]) |
|
|
@@ -191,16 +189,17 @@ class Sound: |
|
|
|
((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 3 großen |
|
|
|
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. Die fertigen Audiodatei als .wav Datei speichern |
|
|
|
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 |
|
|
|
|
|
|
@@ -235,53 +234,28 @@ class Sound: |
|
|
|
self.filterfortschritt = 2, 0 # der zweite schritt im Feedback |
|
|
|
start_time = time.time() # einen Timer laufen lassen um zu sehen wie lange Filterung dauert |
|
|
|
|
|
|
|
self.music_data = self.music_data/32767 # convert array from int16 to float |
|
|
|
|
|
|
|
# ------------------------------------------LEFT EAR FILTERING------------------------------------------------- |
|
|
|
# Filterparameter festlegen------------ |
|
|
|
#Parameter festlegen |
|
|
|
order = 501 # Filterordnung |
|
|
|
bandwidth = 1000 # Bandbreite des Sperrbereichs in Hz |
|
|
|
#stop_attenuation = 100 # minimum Attenuation (Damping, Reduction) in stop Band [only for elliptic filter necessary] |
|
|
|
cutoff_frequencies = [(lf - (bandwidth / 2)), (lf + (bandwidth / 2))] # the cutoff frequencies (lower and upper) |
|
|
|
max_ripple_passband = 50 # Maximal erlaubte Welligkeit im Passbereich |
|
|
|
# ------------------------------------- |
|
|
|
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 |
|
|
|
# b, a = signal.iirfilter(order, cutoff_frequencies, rp=max_ripple_passband, btype='bandstop', ftype='butter', |
|
|
|
# fs=self.music_samplerate) # Diese Funktion erstellt den IIR-Bandpassfilter (links) |
|
|
|
# |
|
|
|
# music_links = signal.lfilter(b, a, self.music_data[:, 0]) # diese Funktion wendet den Filter an |
|
|
|
# |
|
|
|
# print("b=", b) |
|
|
|
# print("a=", a) |
|
|
|
|
|
|
|
# FIR Filterversuch |
|
|
|
#h = signal.firwin(order, cutoff_frequencies, pass_zero="bandstop", fs=self.music_samplerate, width=bandwidth, |
|
|
|
# window="hamming") |
|
|
|
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) |
|
|
|
print("h= ", h) |
|
|
|
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 = [(rf - (bandwidth / 2)), ( |
|
|
|
rf + (bandwidth / 2))] # change the cutoff frequencies to the tinnitus of the RIGHT EAR |
|
|
|
|
|
|
|
# h = signal.iirfilter(order, cutoff_frequencies, rp=max_ripple_passband, btype='bandstop', ftype='butter', |
|
|
|
# fs=self.music_samplerate) # Diese Funktion erstellt den IIR-Bandpassfilter (rechts) |
|
|
|
# |
|
|
|
# music_rechts = signal.lfilter(b, a, self.music_data[:, 1]) # rechts |
|
|
|
|
|
|
|
# FIR Filterversuch |
|
|
|
print("UG Freq = ", cutoff_frequencies[0]/(self.music_samplerate/2)) |
|
|
|
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]) |
|
|
|
music_rechts = signal.lfilter(h, 1.0, self.music_data[:, 1]) |
|
|
|
else: |
|
|
|
music_rechts = self.music_data[:, 1] # diese Funktion filtert die Audiodaten(die Tinnitusfreq wird entfernt) |
|
|
|
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") |
|
|
@@ -291,14 +265,14 @@ class Sound: |
|
|
|
|
|
|
|
#Maximum finden (Funktion max(...) ist minimal schneller, macht aber Probleme beim Feedback) |
|
|
|
start_time = time.time() |
|
|
|
max_ges = 0 |
|
|
|
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: # glaub hier stand 10000 davor oder 50000 |
|
|
|
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], "%") |
|
|
@@ -320,9 +294,9 @@ class Sound: |
|
|
|
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") |
|
|
|
|
|
|
@@ -341,10 +315,7 @@ class Sound: |
|
|
|
# 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))) |
|
|
|
|
|
|
|
# wav_obj.writeframes(struct.pack('h', int(music_links[x] * 32767.0))) # Werte für links und rechts werden bei |
|
|
|
# wav_obj.writeframes(struct.pack('h', int(musicRechts[x] * 32767.0))) # wav abwechselnd eingetragen |
|
|
|
if tinnitus_data % int(nframes/10) == 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], "%") |
|
|
@@ -352,7 +323,6 @@ class Sound: |
|
|
|
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() |
|
|
@@ -363,20 +333,3 @@ class Sound: |
|
|
|
|
|
|
|
print("Speichern beendet.") |
|
|
|
self.filterfortschritt = 5, 0 #Nach erfolgreichem Filtern Fortschritt zur Bestätigung auf 5 setzen |
|
|
|
# Plot (hilfreich für Filterentwurf) |
|
|
|
# freq, h = signal.freqz(b, a, fs=self.music_samplerate) |
|
|
|
# fig, ax = plt.subplots(2, 1, figsize=(8, 6)) |
|
|
|
# ax[0].plot(freq, 20 * np.log10(abs(h)), color='blue') |
|
|
|
# ax[0].set_title("Frequency Response") |
|
|
|
# ax[0].set_ylabel("Amplitude (dB)", color='blue') |
|
|
|
# ax[0].set_xlim([0, 10000]) |
|
|
|
# ax[0].set_ylim([-120, 10]) |
|
|
|
# ax[0].grid() |
|
|
|
# ax[1].plot(freq, np.unwrap(np.angle(h)) * 180 / np.pi, color='green') |
|
|
|
# ax[1].set_ylabel("Angle (degrees)", color='green') |
|
|
|
# ax[1].set_xlabel("Frequency (Hz)") |
|
|
|
# ax[1].set_xlim([0, 10000]) |
|
|
|
# ax[1].set_yticks([-90, -60, -30, 0, 30, 60, 90]) |
|
|
|
# ax[1].set_ylim([-90, 90]) |
|
|
|
# ax[1].grid() |
|
|
|
# plt.show() |