|
| 1 | +{ |
| 2 | + "cells": [ |
| 3 | + { |
| 4 | + "cell_type": "code", |
| 5 | + "execution_count": 40, |
| 6 | + "metadata": { |
| 7 | + "collapsed": true |
| 8 | + }, |
| 9 | + "outputs": [], |
| 10 | + "source": [ |
| 11 | + "\"\"\"\n", |
| 12 | + "Notebook for streaming data from a microphone in realtime\n", |
| 13 | + "\n", |
| 14 | + "audio is captured using pyaudio\n", |
| 15 | + "then converted from binary data to ints using struct\n", |
| 16 | + "then displayed using matplotlib\n", |
| 17 | + "\n", |
| 18 | + "scipy.fftpack computes the FFT\n", |
| 19 | + "\n", |
| 20 | + "if you don't have pyaudio, then run\n", |
| 21 | + "\n", |
| 22 | + ">>> pip install pyaudio\n", |
| 23 | + "\n", |
| 24 | + "note: with 2048 samples per chunk, I'm getting 20FPS\n", |
| 25 | + "when also running the spectrum, its about 15FPS\n", |
| 26 | + "\"\"\"\n", |
| 27 | + "\n", |
| 28 | + "import pyaudio\n", |
| 29 | + "import os\n", |
| 30 | + "import struct\n", |
| 31 | + "import numpy as np\n", |
| 32 | + "import matplotlib.pyplot as plt\n", |
| 33 | + "from scipy.fftpack import fft\n", |
| 34 | + "import time\n", |
| 35 | + "from tkinter import TclError\n", |
| 36 | + "\n", |
| 37 | + "# to display in separate Tk window\n", |
| 38 | + "%matplotlib tk\n", |
| 39 | + "\n", |
| 40 | + "# constants\n", |
| 41 | + "CHUNK = 1024 * 2 # samples per frame\n", |
| 42 | + "FORMAT = pyaudio.paInt16 # audio format (bytes per sample?)\n", |
| 43 | + "CHANNELS = 1 # single channel for microphone\n", |
| 44 | + "RATE = 44100 # samples per second" |
| 45 | + ] |
| 46 | + }, |
| 47 | + { |
| 48 | + "cell_type": "code", |
| 49 | + "execution_count": 54, |
| 50 | + "metadata": { |
| 51 | + "scrolled": false |
| 52 | + }, |
| 53 | + "outputs": [ |
| 54 | + { |
| 55 | + "name": "stdout", |
| 56 | + "output_type": "stream", |
| 57 | + "text": [ |
| 58 | + "stream started\n", |
| 59 | + "stream stopped\n", |
| 60 | + "average frame rate = 15 FPS\n" |
| 61 | + ] |
| 62 | + } |
| 63 | + ], |
| 64 | + "source": [ |
| 65 | + "# create matplotlib figure and axes\n", |
| 66 | + "fig, (ax1, ax2) = plt.subplots(2, figsize=(15, 7))\n", |
| 67 | + "\n", |
| 68 | + "# pyaudio class instance\n", |
| 69 | + "p = pyaudio.PyAudio()\n", |
| 70 | + "\n", |
| 71 | + "# stream object to get data from microphone\n", |
| 72 | + "stream = p.open(\n", |
| 73 | + " format=FORMAT,\n", |
| 74 | + " channels=CHANNELS,\n", |
| 75 | + " rate=RATE,\n", |
| 76 | + " input=True,\n", |
| 77 | + " output=True,\n", |
| 78 | + " frames_per_buffer=CHUNK\n", |
| 79 | + ")\n", |
| 80 | + "\n", |
| 81 | + "# variable for plotting\n", |
| 82 | + "x = np.arange(0, 2 * CHUNK, 2) # samples (waveform)\n", |
| 83 | + "xf = np.linspace(0, RATE, CHUNK) # frequencies (spectrum)\n", |
| 84 | + "\n", |
| 85 | + "# create a line object with random data\n", |
| 86 | + "line, = ax1.plot(x, np.random.rand(CHUNK), '-', lw=2)\n", |
| 87 | + "\n", |
| 88 | + "# create semilogx line for spectrum\n", |
| 89 | + "line_fft, = ax2.semilogx(xf, np.random.rand(CHUNK), '-', lw=2)\n", |
| 90 | + "\n", |
| 91 | + "# format waveform axes\n", |
| 92 | + "ax1.set_title('AUDIO WAVEFORM')\n", |
| 93 | + "ax1.set_xlabel('samples')\n", |
| 94 | + "ax1.set_ylabel('volume')\n", |
| 95 | + "ax1.set_ylim(0, 255)\n", |
| 96 | + "ax1.set_xlim(0, 2 * CHUNK)\n", |
| 97 | + "plt.setp(ax1, xticks=[0, CHUNK, 2 * CHUNK], yticks=[0, 128, 255])\n", |
| 98 | + "\n", |
| 99 | + "# format spectrum axes\n", |
| 100 | + "ax2.set_xlim(20, RATE / 2)\n", |
| 101 | + "\n", |
| 102 | + "print('stream started')\n", |
| 103 | + "\n", |
| 104 | + "# for measuring frame rate\n", |
| 105 | + "frame_count = 0\n", |
| 106 | + "start_time = time.time()\n", |
| 107 | + "\n", |
| 108 | + "while True:\n", |
| 109 | + " \n", |
| 110 | + " # binary data\n", |
| 111 | + " data = stream.read(CHUNK) \n", |
| 112 | + " \n", |
| 113 | + " # convert data to integers, make np array, then offset it by 127\n", |
| 114 | + " data_int = struct.unpack(str(2 * CHUNK) + 'B', data)\n", |
| 115 | + " \n", |
| 116 | + " # create np array and offset by 128\n", |
| 117 | + " data_np = np.array(data_int, dtype='b')[::2] + 128\n", |
| 118 | + " \n", |
| 119 | + " line.set_ydata(data_np)\n", |
| 120 | + " \n", |
| 121 | + " # compute FFT and update line\n", |
| 122 | + " yf = fft(data_int)\n", |
| 123 | + " line_fft.set_ydata(np.abs(yf[0:CHUNK]) / (128 * CHUNK))\n", |
| 124 | + " \n", |
| 125 | + " # update figure canvas\n", |
| 126 | + " try:\n", |
| 127 | + " fig.canvas.draw()\n", |
| 128 | + " fig.canvas.flush_events()\n", |
| 129 | + " frame_count += 1\n", |
| 130 | + " \n", |
| 131 | + " except TclError:\n", |
| 132 | + " \n", |
| 133 | + " # calculate average frame rate\n", |
| 134 | + " frame_rate = frame_count / (time.time() - start_time)\n", |
| 135 | + " \n", |
| 136 | + " print('stream stopped')\n", |
| 137 | + " print('average frame rate = {:.0f} FPS'.format(frame_rate))\n", |
| 138 | + " break" |
| 139 | + ] |
| 140 | + }, |
| 141 | + { |
| 142 | + "cell_type": "code", |
| 143 | + "execution_count": null, |
| 144 | + "metadata": { |
| 145 | + "collapsed": true |
| 146 | + }, |
| 147 | + "outputs": [], |
| 148 | + "source": [] |
| 149 | + } |
| 150 | + ], |
| 151 | + "metadata": { |
| 152 | + "anaconda-cloud": {}, |
| 153 | + "kernelspec": { |
| 154 | + "display_name": "Python [conda root]", |
| 155 | + "language": "python", |
| 156 | + "name": "conda-root-py" |
| 157 | + }, |
| 158 | + "language_info": { |
| 159 | + "codemirror_mode": { |
| 160 | + "name": "ipython", |
| 161 | + "version": 3 |
| 162 | + }, |
| 163 | + "file_extension": ".py", |
| 164 | + "mimetype": "text/x-python", |
| 165 | + "name": "python", |
| 166 | + "nbconvert_exporter": "python", |
| 167 | + "pygments_lexer": "ipython3", |
| 168 | + "version": "3.5.3" |
| 169 | + } |
| 170 | + }, |
| 171 | + "nbformat": 4, |
| 172 | + "nbformat_minor": 1 |
| 173 | +} |
0 commit comments