{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np                        # Import numpy module\n",
    "import sounddevice as sd                  # Import sounddevice module for \"real-time\" sound playback\n",
    "import os, time\n",
    "import matplotlib.pyplot as plt"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "scrolled": false
   },
   "outputs": [],
   "source": [
    "# Set sample rate for audio output\n",
    "sd.default.samplerate = 44100\n",
    "\n",
    "# Audio stream\n",
    "stream = sd.OutputStream(samplerate=sd.default.samplerate, channels=2, dtype='float32')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "def get_pure_tone(freq, duration, sample_rate=44100):\n",
    "    x = np.linspace(0, duration * freq * 2*np.pi, int(duration*sample_rate), dtype=np.float32)\n",
    "    return np.sin(x)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### List available devices"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "   0 HDA Intel PCH: CX20632 Analog (hw:0,0), ALSA (2 in, 2 out)\n",
       "   1 HDA Intel PCH: CX20632 Alt Analog (hw:0,2), ALSA (2 in, 0 out)\n",
       "   2 HDA Intel PCH: HDMI 0 (hw:0,3), ALSA (0 in, 8 out)\n",
       "   3 HDA Intel PCH: HDMI 1 (hw:0,7), ALSA (0 in, 8 out)\n",
       "   4 HDA Intel PCH: HDMI 2 (hw:0,8), ALSA (0 in, 8 out)\n",
       "   5 HDA Intel PCH: HDMI 3 (hw:0,9), ALSA (0 in, 8 out)\n",
       "   6 HDA Intel PCH: HDMI 4 (hw:0,10), ALSA (0 in, 8 out)\n",
       "   7 HDA NVidia: HDMI 0 (hw:1,3), ALSA (0 in, 8 out)\n",
       "   8 HDA NVidia: HDMI 1 (hw:1,7), ALSA (0 in, 2 out)\n",
       "   9 HDA NVidia: HDMI 2 (hw:1,8), ALSA (0 in, 8 out)\n",
       "  10 HDA NVidia: HDMI 3 (hw:1,9), ALSA (0 in, 8 out)\n",
       "  11 HDA NVidia: HDMI 4 (hw:1,10), ALSA (0 in, 8 out)\n",
       "  12 USB Device 0x46d:0x821: Audio (hw:2,0), ALSA (2 in, 0 out)\n",
       "  13 sysdefault, ALSA (128 in, 128 out)\n",
       "  14 front, ALSA (0 in, 2 out)\n",
       "  15 surround40, ALSA (0 in, 2 out)\n",
       "  16 surround51, ALSA (0 in, 2 out)\n",
       "  17 surround71, ALSA (0 in, 2 out)\n",
       "  18 hdmi, ALSA (0 in, 8 out)\n",
       "  19 samplerate, ALSA (128 in, 128 out)\n",
       "  20 speexrate, ALSA (128 in, 128 out)\n",
       "  21 pulse, ALSA (32 in, 32 out)\n",
       "  22 upmix, ALSA (8 in, 8 out)\n",
       "  23 vdownmix, ALSA (6 in, 6 out)\n",
       "  24 dmix, ALSA (0 in, 2 out)\n",
       "* 25 default, ALSA (32 in, 32 out)"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "sd.query_devices()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[25, 25]"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "sd.default.device  # input, output"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Play pure tone of a sample frequency"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "freq = 440     # in Hz\n",
    "duration = 1   # in sec\n",
    "sample_rate = sd.default.samplerate  # usually 44100\n",
    "volume = 0.5   # percent 0 - 1\n",
    "\n",
    "sound = get_pure_tone(freq, duration, sample_rate) * volume\n",
    "\n",
    "stream.start()\n",
    "stream.write(get_pure_tone(freq, duration, sample_rate) * volume)\n",
    "stream.stop()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "duration = 0.2  # in sec\n",
    "volume = 0.5    # percent 0 - 1\n",
    "\n",
    "# C-major scale frequencies\n",
    "C_major = (261, 293, 329, 349, 392, 440, 494, 523)\n",
    "\n",
    "stream.start()\n",
    "for freq in C_major:\n",
    "    x = np.linspace(0, duration * freq * 2*np.pi, int(duration * sd.default.samplerate), dtype=np.float32)\n",
    "    pure_tone = np.sin(x)\n",
    "    stream.write(pure_tone * volume)\n",
    "    \n",
    "stream.stop()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [],
   "source": [
    "# play different speakers with play\n",
    "sd.play(sound, mapping=[2])  # blocking=False by default"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [],
   "source": [
    "# play different speakers with stereo array and Stream\n",
    "stereo = np.column_stack((sound, sound))\n",
    "left  = np.column_stack((sound, np.zeros(len(sound), dtype=np.float32)))\n",
    "right = np.column_stack((np.zeros(len(sound), dtype=np.float32), sound))\n",
    "\n",
    "stream.start()\n",
    "stream.write(right)\n",
    "stream.stop()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Plot pure tone"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[<matplotlib.lines.Line2D at 0x1ddcec01dd8>]"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "[encoded data, 26216 characters]\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "A_pure_tone = get_pure_tone(440, 1)\n",
    "plt.plot(np.linspace(0, 1000*(500./44100.), 500), A_pure_tone[:500])  # first 500 samples"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [],
   "source": [
    "tone1 = get_pure_tone(440, 0.05, 44100) * 0.5\n",
    "tone2 = get_pure_tone(880, 0.05, 44100) * 0.5\n",
    "silence = np.zeros(len(tone1), dtype='float32')\n",
    "silence = np.zeros(44, dtype='float32')\n",
    "\n",
    "tone1_stereo = np.column_stack((tone1, tone1))\n",
    "tone2_stereo = np.column_stack((tone2, tone2))\n",
    "silence_stereo = np.column_stack((silence, silence))\n",
    "\n",
    "blocks = int(latency / pulse_duration)\n",
    "sounds = {\n",
    "    0: np.concatenate([silence_stereo for x in range(blocks)]),\n",
    "    1: np.concatenate([tone1_stereo] + [silence_stereo for x in range(blocks - 1)]),\n",
    "    2: np.concatenate([tone2_stereo] + [silence_stereo for x in range(blocks - 1)])\n",
    "}\n",
    "\n",
    "latency = 0.25\n",
    "pulse_duration = 0.05\n",
    "\n",
    "stream = sd.OutputStream(samplerate=sd.default.samplerate, channels=2, dtype='float32')\n",
    "stream.start()\n",
    "\n",
    "t_start = time.time()\n",
    "next_beat = time.time() + latency\n",
    "while time.time() < t_start + 2:\n",
    "    t0 = time.time()\n",
    "    if t0 < next_beat:\n",
    "        #time.sleep(0.0001)        # not to spin the wheels too much\n",
    "        #stream.write(sounds[0])  # silence\n",
    "        if stream.write_available > 0:\n",
    "            stream.write(silence_stereo)\n",
    "        continue\n",
    "\n",
    "    #stream.write(sounds[1])\n",
    "    stream.write(tone1_stereo)\n",
    "    next_beat += latency\n",
    "        \n",
    "stream.stop()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "5"
      ]
     },
     "execution_count": 17,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "blocks"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[<matplotlib.lines.Line2D at 0x7f6bf5d40bb0>]"
      ]
     },
     "execution_count": 23,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "[encoded data, 8880 characters]\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "plt.plot(np.diff(np.array(fps)))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([0.25000024, 0.25000024, 0.25000024, 0.25000024, 0.25000024,\n",
       "       0.25000024, 0.25000024, 0.25000024, 0.25000024, 0.25000024,\n",
       "       0.25000024, 0.25000024, 0.25000024, 0.25000024, 0.25000024,\n",
       "       0.25000024, 0.25000024, 0.25000024, 0.25000024, 0.25000048,\n",
       "       0.25000024, 0.25000024, 0.25000024, 0.25000024, 0.25000024,\n",
       "       0.25000024, 0.25000024, 0.25000024, 0.25000024, 0.25000024,\n",
       "       0.25000024, 0.25000024, 0.25000024, 0.25000024, 0.25000024,\n",
       "       0.25000024, 0.25000024, 0.25000024])"
      ]
     },
     "execution_count": 25,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.diff(np.array(fps))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "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.8.10"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
