{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Wavelettransformation zur Rauschreduktion in Audiosignalen\n",
    "\n",
    "Dieser Beleg ist eine Zusammenarbeit von Johannes Müller und Willy Müller. Er wurde für die Hochschule Anhalt, Fachbereich 6 bearbeitet und erstellt. Für den erfolgreichen Abschluss des Moduls Signaltheorie sollte ein tangierendes Thema für einen Beleg ausgesucht werden, welches die Kenntnisse der Studierenden in den gelehrten Themen reflektiert.\n",
    "\n",
    "## Einleitung und Motivation\n",
    "\n",
    "Wir haben uns daher gefragt: Inwiefern kann die Wavelettransformation als Mittel zur Reduktion von Hintergrundgeräuschen in Audiosignalen verwendet werden? In der Zeitdomäne ist das sogennante *Ducking* eine häufig verwendete Methode und in der Frequenzdomäne wird oft das Spektrum des Störsignals von dem des Eingangssignals abgezogen. Mit Wavelets könnte eine Kombination dieser Effekte erreicht werden.\n",
    "\n",
    "Um diese Hypothese zu überprüfen, müssen zuerst die Rahmenbedingungen gesetzt werden.\n",
    "\n",
    "## Analyse der Problemstellung\n",
    "\n",
    "Es soll ein System erstellt werden, dass ein zeitdiskretes Eingangssignal $x(t)$ entgegennimmt und ein Signal $y(t)$ ausgibt. Die Amplitude der Signale ist über das Interval $$-1 \\leq x \\leq 1; -1 \\leq y \\leq 1$$ begrenzt. Die Diskretisierung in der Zeitachse ist auf die übliche Rate von $\\mathsf{sr} = 22050 \\ \\mathrm{Hz}$ festgelegt, so dass Audiofrequenzen bis ca. $10 \\ \\mathrm{kHz}$ abgebildet werden können. Die Dauer des Eingangssignals ist in folgeden Tests auf $T = 5 \\ \\mathrm{s}$ festgelegt, wobei das System dieses Signal in noch kleineren zeitlichen Abschnitten bearbeiten wird und somit auch ein zeitlich unbeschränktes Signal verarbeiten kann.\n",
    "\n",
    "Das Eingangssignal $x(t)$ setzt sich hier beispielsweise aus dem Nutzsignal $source(t)$ und dem weißen Rauschen $r(t)$ zusammen, wobei das Rauschen nur zu bestimmten Segmenten des Nutzsignals addiert wird.\n",
    "Zusätzlich zum Eingangssignal $x(t)$ steht dem System das Referenzsample $r(t)$ zur Verfügung, welches zur Bestimmung des Pegels des Störsignals herangezogen werden kann."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/markdown": [
       "[output/r.wav](output/r.wav)"
      ],
      "text/plain": [
       "<IPython.core.display.Markdown object>"
      ]
     },
     "execution_count": 1,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Erzeugung des Störsignals\n",
    "\n",
    "import numpy\n",
    "sr = 22050 # sample rate\n",
    "T = 5.0    # seconds\n",
    "t = numpy.linspace(0, T, int(T*sr), endpoint=False) # time variable\n",
    "numpy.random.seed(seed=0xdeadbeef)\n",
    "r = numpy.random.normal(loc=0, scale=2e-2, size=int(T*sr)) # white noise\n",
    "\n",
    "from audio import Audio\n",
    "import librosa\n",
    "import librosa.display\n",
    "Audio(r, rate=sr, name=\"r\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/markdown": [
       "[output/x.wav](output/x.wav)"
      ],
      "text/plain": [
       "<IPython.core.display.Markdown object>"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Erzeugung des Eingangssignals\n",
    "\n",
    "def load_source(file):\n",
    "  source, _ = librosa.load(file, sr=sr, duration=T)\n",
    "  x = source + r * (numpy.cos(t*2) < 0)\n",
    "  return x\n",
    "\n",
    "# Quelle: https://ccrma.stanford.edu/workshops/mir2014/audio/\n",
    "x = load_source('audio/T39-piano.wav')\n",
    "Audio(x, rate=sr, name=\"x\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Zeitliche Unterteilung\n",
    "\n",
    "Das Eingangssignal wird nun in kleinere überlappende Abschnitte unterteilt, welche anschließend zur Rauschminderung weiterverarbeitet werden. Die Länge der Abschnitte so gewählt, dass für die minimalste Frequenz des Signals mindestens eine volle Sinuskurve im Abschnitt enthalten ist. Sie darf aber auch nicht zu lang sein, denn in der Echtzeitverarbeitung müssten alle enthaltenen Werte gepuffert werden, was zusätzliche Latenz herbeibringt. Schließlich sollte sie eine volle Zweierpotenz betragen, um die Abschnitte in optimierten Algorithmen, wie folgend FFT- oder Waveletalgorithmen, effizient verarbeitet zu können. Die Länge der Abschnitte ist hier auf 2048 Sample festgelegt, was einem Zeitinterval von $$\\frac{\\mathsf{win\\_size}}{\\mathsf{sr}} = \\frac{2048}{22500} = 91.02\\ \\mathbf{ms}$$ entspricht und somit alle Kriterien ausreichend erfüllt."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "%matplotlib inline\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "win_size = 2048\n",
    "stride = win_size >> 1\n",
    "\n",
    "window = numpy.hanning(win_size)\n",
    "\n",
    "def signal_to_frames(signal):\n",
    "    frames = librosa.util.frame(signal, frame_length=win_size, hop_length=stride)\n",
    "    return numpy.swapaxes(frames, 0, 1)\n",
    "\n",
    "def frames_to_signal(frames):\n",
    "    count = len(frames) * stride + win_size\n",
    "    ret = numpy.ndarray(count)\n",
    "    ret.fill(0)\n",
    "    offset = 0\n",
    "    for frame in frames:\n",
    "        ret[offset:offset+len(frame)] += frame * window\n",
    "        offset += stride\n",
    "    return ret[:int(sr*T)]\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Die Abschitte überlappen sich zu 50%. Die nachfolgende Grafik zeigt, wie die überlappenden Abschnitte mit Hilfe der Hanning-Fensterfunktion rekombiniert werden."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 60104 characters]",
      "text/plain": [
       "<Figure size 1008x360 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "frames = signal_to_frames(numpy.ones(stride*8))\n",
    "plt.figure(figsize=(14, 5))\n",
    "plt.xlabel(\"Zeit in s\")\n",
    "plt.ylabel(\"Amplitude\")\n",
    "for i, frame in enumerate(frames):\n",
    "    plt.plot(t[i*stride:i*stride+win_size], frame * window, '--')\n",
    "plt.plot(t[0:stride*9], frames_to_signal(frames));\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Methoden der Rauschkategorisierung\n",
    "\n",
    "Die Amplitude des Rauschens im Eingangssignal ist im betrachteten Zeitraum nicht konstant. Jedoch ist die Energie des Rauschens im gesamten Frequenzspektrum des Signals verteilt und kann damit von dem Nutzsignal unterschieden werden. Wenn der Grenzwert zwischen Störsignal und Nutzsignal bekannt ist, dann kommen unabhängig von der Domäne zwei Arten von Grenzwertfunktionen in Betracht:\n",
    "- Ein harter Filter $f1$, bei dem alle Werte unter einem Grenzwert auf Null gesetzt werden, und\n",
    "- Ein weicher Filter $f2$, bei dem der Bereich außerhalb des Grenzwertes in Richtung Null skaliert wird.\n",
    "\n",
    "Diese Filter sind folgend für Werte $arr$ mit dem Grenzwert $g$ mit $0 < g <= max(arr)$ definiert:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "\n",
    "# Harter Filter\n",
    "def f1(arr, g):\n",
    "    return (abs(arr) >= g) * arr\n",
    "\n",
    "# Weicher Filter\n",
    "def f2(arr, g, maximum=1):\n",
    "    return numpy.sign(arr) * numpy.clip(abs(arr) - g, 0, maximum - g) * (1 / (maximum-g))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Wahl des Waveletalgorithmus\n",
    "\n",
    "Für diese Anwendung wurde die *diskrete Wavelettransformation* (DWT) gewählt, denn sie bietet gegenüber der *kontinuierlichen Wavelettransformation* (CWT) Vorteile in der Verarbeitungsgeschwindigkeit und in der Art der erzeugten Koeffizienten.\n",
    "Während die CWT mit kontinuierlichen Signalen arbeitet, können der DWT direkt die oben erzeugten Abschnitte übergeben werden. Die DWT kann rekursiv angewendet werden, wobei bei der Analyse pro Schritt jeweils $n$ Samples zu $\\frac n 2$ niederfrequente Annäherungs- und $\\frac n 2$ hochfrequente Detailkoeffizienten erzeugt werden. Die nächsten Schritte werden dann bis einem bestimmten Level auf die Annäherungskoeffizienten des vorherigen Schrittes angewandt.\n",
    "$$ x = A_1 + D_1 = A_2 + D_2 + D_1 = A_3 + D_3 + D_2 + D_1 $$\n",
    "\n",
    "Rückwirkend können Abschnitte auch wieder mit Hilfe der Waveletsynthese (iDWT) wiederhergestellt werden."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "import pywt\n",
    "\n",
    "def frames_to_coeffs(frames, wavelet):\n",
    "    maxlevel = pywt.dwt_max_level(win_size, wavelet)\n",
    "    return pywt.wavedec(frames, wavelet, level=maxlevel)\n",
    "\n",
    "def coeffs_to_frames(coeffs, wavelet):\n",
    "    return pywt.waverec(coeffs, wavelet)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Wahl des Wavelets\n",
    "\n",
    "Bei der Wavelettransformation wird wie bei der Konvolution ein bestimmter Filter $\\psi(t)$ (das Wavelet) über das Eingangssignal $x(t)$ gelegt.\n",
    "Durch die zeitliche Verschiebung des Wavelets über dem Signal werden Koeffizienten erzeugt, wessen Charakteristik stark von dem verwendeten Wavelet abhängt. In dieser Anwendung wurde deshalb ein Wavelet gewählt, welches einen geringen akustischen Störeffekt nach der Anwendung des Filters $f2$ hat. Nachfolgend sind auch alternative Kandidaten aufgelistet."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "bior1.1 bior1.3 bior1.5 bior2.2 bior2.4 bior2.6 bior2.8 bior3.1 bior3.3 bior3.5 bior3.7 bior3.9 bior4.4 bior5.5 bior6.8 coif1 coif2 coif3 coif4 coif5 coif6 coif7 coif8 coif9 coif10 coif11 coif12 coif13 coif14 coif15 coif16 coif17 db1 db2 db3 db4 db5 db6 db7 db8 db9 db10 db11 db12 db13 db14 db15 db16 db17 db18 db19 db20 db21 db22 db23 db24 db25 db26 db27 db28 db29 db30 db31 db32 db33 db34 db35 db36 db37 db38 dmey haar rbio1.1 rbio1.3 rbio1.5 rbio2.2 rbio2.4 rbio2.6 rbio2.8 rbio3.1 rbio3.3 rbio3.5 rbio3.7 rbio3.9 rbio4.4 rbio5.5 rbio6.8 sym2 sym3 sym4 sym5 sym6 sym7 sym8 sym9 sym10 sym11 sym12 sym13 sym14 sym15 sym16 sym17 sym18 sym19 sym20\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 27520 characters]",
      "text/plain": [
       "<Figure size 1008x360 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "\n",
    "print(' '.join(pywt.wavelist(kind='discrete')))\n",
    "wavelet = pywt.Wavelet('coif4')\n",
    "plt.figure(figsize=(14, 5))\n",
    "plt.title(wavelet.name)\n",
    "plt.plot(wavelet.dec_lo)\n",
    "plt.plot(wavelet.dec_hi);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Die so erzeugten Koeffizienten können nun gemäßt der oben definierten Filterfunktion gefiltert werden, um die kleinen Rauschanteile zu unterdrücken. Dazu wird zuerst der Grenzwert des Filters definiert. Dieser setzt sich aus den Maxima der Koeffizienten des Störsignals nach der Wavelettransformation zusammen. Jetzt können alle bereits definierten Teilstücke kombiniert werden, um ein Signal $x(t)$ in seiner Wavelet-transformierten Form zu filtern, um schließlich $y(t)$ als Funktion von $x(t)$, des Wavelets und des Filters zurückzugeben."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 75688 characters]",
      "text/plain": [
       "<Figure size 1008x360 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Koeffizienten des Rauschens bestimmen und Maxima bilden\n",
    "\n",
    "r_frames = signal_to_frames(r)\n",
    "\n",
    "def noise_coeffs(wavelet):\n",
    "    r_coeffs = frames_to_coeffs(r_frames, wavelet)\n",
    "    for i, coeff in enumerate(r_coeffs):\n",
    "        r_coeffs[i] = numpy.max(numpy.abs(r_coeffs[i]), axis=0)\n",
    "    return r_coeffs\n",
    "\n",
    "\n",
    "plt.figure(figsize=(14, 5))\n",
    "plt.title(f\"Maxima der Rauschkoeffizienten aus {len(r_frames)} Abschnitten\")\n",
    "r_coeffs = noise_coeffs(wavelet)\n",
    "for i, coeff in enumerate(r_coeffs):\n",
    "    plt.plot(r_coeffs[i], label=f\"A{len(r_coeffs)-1}\" if i==0 else f\"D{len(r_coeffs)-i}\")\n",
    "plt.legend()\n",
    "\n",
    "# Filterfunktion konstruieren\n",
    "\n",
    "def filter_signal(x, wavelet, filter):\n",
    "    x_frames = signal_to_frames(x)\n",
    "    r_coeffs = noise_coeffs(wavelet)\n",
    "\n",
    "    coeffs = frames_to_coeffs(x_frames, wavelet)\n",
    "    for i, coeff in enumerate(coeffs):\n",
    "        coeffs[i] = filter(coeff, r_coeffs[i])\n",
    "\n",
    "    y_frames = coeffs_to_frames(coeffs, wavelet)\n",
    "    return frames_to_signal(y_frames)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Anwendung\n",
    "\n",
    "Nach der Applikation des Filters *f2* auf die Koeffizienten der Wavelettransformation mit *coif4* ist das Rauschen im Ausgangssignal $y(t)$ nicht mehr aufzufinden, jedoch nimmt die Klarheit des Signals drastisch ab. Damit ist Bewiesen, dass mit der aufgeführten Methode tatsächlich das Hintergrundrauschen in Audiosignalen reduziert werden kann; das Resultat ist aber noch nicht optimal."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/markdown": [
       "[output/coif4_f2.wav](output/coif4_f2.wav)"
      ],
      "text/plain": [
       "<IPython.core.display.Markdown object>"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "[encoded data, 64468 characters]",
      "text/plain": [
       "<Figure size 1008x360 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "y = filter_signal(x, wavelet, f2)\n",
    "\n",
    "plt.figure(figsize=(14, 5))\n",
    "librosa.display.waveshow(x, sr=sr)\n",
    "librosa.display.waveshow(y, sr=sr)\n",
    "plt.xlabel(\"Zeit in s\")\n",
    "plt.ylabel(\"Amplitude\")\n",
    "Audio(y, rate=sr, name=f\"{wavelet.name}_f2\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Vergleich mit alternativen Filteranwendungen\n",
    "\n",
    "Nachfolgend wird der Filter *f2* direkt am Eingssignal angewandt. Das Ergebnis ist nicht befriedigend, da das Audiosignal stark gestört wird und der Rauschanteil trotzdem hörbar ist. Die Wavelet-Methode schneidet im Vergleich viel besser ab. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 57328 characters]",
      "text/plain": [
       "<Figure size 1008x360 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "yt = f2(x, 0.1)\n",
    "Audio(yt, rate=sr, name=\"t_f2\")\n",
    "\n",
    "plt.figure(figsize=(14, 5))\n",
    "librosa.display.waveshow(x, sr=sr)\n",
    "librosa.display.waveshow(yt, sr=sr)\n",
    "plt.xlabel(\"Zeit in s\")\n",
    "plt.ylabel(\"Amplitude\");"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Hier wird der Filter auf die Koeffizienten nach der Fouriertransformation der Abschnitte angewandt. Bei dem Piano-Eingangssignal schneidet die Rauschentfernung via *Short-time Fourier transform* (STFT) subjektiv besser ab als die Wavelet-Methode. Obwohl hier auch ein paar hohe Störfrequenzen übrig bleiben, kann das auf seine Grundfrequenz und deren Obertöne reduzierte Instrument besser wahrgenommen werden. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 116092 characters]",
      "text/plain": [
       "<Figure size 1008x360 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    },
    {
     "data": {
      "image/png": "[encoded data, 30784 characters]",
      "text/plain": [
       "<Figure size 1008x360 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "X = librosa.stft(x, n_fft=win_size, hop_length=stride)\n",
    "Y = f2(abs(X), 0.9, numpy.max(abs(X))) * numpy.exp(1j * numpy.angle(X))\n",
    "\n",
    "y = librosa.istft(Y, hop_length=stride)\n",
    "y = y * max(x) / max(y)\n",
    "\n",
    "\n",
    "Xdb = librosa.amplitude_to_db(abs(X))\n",
    "Ydb = librosa.amplitude_to_db(abs(Y))\n",
    "\n",
    "plt.figure(figsize=(14, 5))\n",
    "librosa.display.specshow(Xdb, sr=sr, x_axis='time', y_axis='hz')\n",
    "plt.figure(figsize=(14, 5))\n",
    "\n",
    "librosa.display.specshow(Ydb, sr=sr, x_axis='time', y_axis='hz');"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In der hier dargestellten Frequenzdomäne kann man deutlich den Effekt des Filters *f2* erkennen. Der breitbandige Rauschanteil ist visuell kaum mehr erkennbar.\n",
    "Folgend ist der Amplitudenverlauf und das Ergebnis der STFT-Filterung aufgelistet."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 57344 characters]",
      "text/plain": [
       "<Figure size 1008x360 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "plt.figure(figsize=(14, 5))\n",
    "librosa.display.waveshow(x, sr=sr)\n",
    "librosa.display.waveshow(y, sr=sr)\n",
    "plt.xlabel(\"Zeit in s\")\n",
    "plt.ylabel(\"Amplitude\");"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/markdown": [
       "[output/stft_f2.wav](output/stft_f2.wav)"
      ],
      "text/plain": [
       "<IPython.core.display.Markdown object>"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "Audio(y, rate=sr, name=\"stft_f2\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Zum Abschluss sind noch einmal die Wavelet- und STFT-Algorithmen für ein Sprachsignal implementiert.\n",
    "\n",
    "Quelle: https://commons.wikimedia.org/wiki/File:Frase_de_Neil_Armstrong.ogg"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {
    "collapsed": true
   },
   "outputs": [
    {
     "data": {
      "text/markdown": [
       "[output/voice.wav](output/voice.wav)"
      ],
      "text/plain": [
       "<IPython.core.display.Markdown object>"
      ]
     },
     "execution_count": 14,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "voice_x = load_source(\"audio/Frase_de_Neil_Armstrong.ogg\")\n",
    "Audio(voice_x, rate=sr, name=\"voice\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/markdown": [
       "[output/voice_coif4_f2.wav](output/voice_coif4_f2.wav)"
      ],
      "text/plain": [
       "<IPython.core.display.Markdown object>"
      ]
     },
     "execution_count": 15,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "voice_y = filter_signal(voice_x, wavelet, f2)\n",
    "Audio(voice_y, rate=sr, name=f\"voice_{wavelet.name}_f2\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/markdown": [
       "[output/voice_stft_f2.wav](output/voice_stft_f2.wav)"
      ],
      "text/plain": [
       "<IPython.core.display.Markdown object>"
      ]
     },
     "execution_count": 16,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "voice_X = librosa.stft(voice_x, n_fft=win_size, hop_length=stride)\n",
    "voice_Y = f2(abs(voice_X), 0.9, numpy.max(abs(voice_X))) * numpy.exp(1j * numpy.angle(voice_X))\n",
    "\n",
    "voice_stft = librosa.istft(voice_Y, hop_length=stride)\n",
    "voice_stft = voice_stft * max(voice_x) / max(voice_stft)\n",
    "\n",
    "Audio(voice_stft, rate=sr, name=\"voice_stft_f2\")"
   ]
  }
 ],
 "metadata": {
  "interpreter": {
   "hash": "31f2aee4e71d21fbe5cf8b01ff0e069b9275f58929596ceb00d14d90e3e16cd6"
  },
  "kernelspec": {
   "display_name": "Python 3.6.9 64-bit",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
