{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Tqdm audio progress ticker and Ricker for Jupyter notebooks\n", "\n", "Some ideas drawn from https://github.com/simonm3/nbextensions/blob/master/cellevents.py \n", "\n", "Actual repo for this as a library at https://github.com/robclewley/TqdmAudioRicker" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " from importnb import remote\n", "\n", " url = 'https://gist.githubusercontent.com/robclewley/a5730e22592724dbd11c67adf1eb0a05/raw/f7e99c4b32c803ca73d173ee52ffd4e76031ccd7/tqdm_audio_ticker.ipynb'\n", " ricker = remote.Remote(url).load(url)" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "import tqdm\n", "import urllib\n", "import os\n", "from IPython import display as disp\n", "from IPython.core.display import HTML\n", "import numpy as np\n", "from requests import get\n", "import io\n", "from scipy.io.wavfile import read\n", "import time" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Early interactive testing ..." ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# we won't need this later\n", "def hide_audio():\n", " \"\"\" hide the audio control \"\"\"\n", " disp.display(HTML(\"\"))\n", "\n", "hide_audio()\n", "# notice there's still blank space after this :(" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "audio = disp.Audio(url='https://sound.peal.io/ps/audios/000/000/537/original/woo_vu_luvub_dub_dub.wav', autoplay=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "disp.display(disp.Audio(url='https://sound.peal.io/ps/audios/000/000/537/original/woo_vu_luvub_dub_dub.wav', autoplay=True))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "d = disp.display(display_id='tqdm_alerter')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "d.update(audio)" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "def alert(f=1000):\n", " \"\"\" makes sound on client using javascript (works with remote server) \"\"\"\n", " framerate = 44100 # Hz sample rate\n", " duration=.04 # seconds\n", " freq = float(f)\n", " t = np.linspace(0, duration, int(framerate*duration))\n", " # fade in and out so no click\n", " qtr_t_ix = int(len(t)/4)\n", " qtr_fac = np.array(list(range(qtr_t_ix)))/qtr_t_ix\n", " mid_fac = np.ones(len(t)-2*qtr_t_ix)\n", " data = np.sin(2*np.pi*freq*t) * np.concatenate((qtr_fac, mid_fac, qtr_fac[::-1]), axis=0)\n", " disp.display(disp.Audio(data, rate=framerate, autoplay=True))\n", " hide_audio()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "alert() # space follows ... :(" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Use this class for playing multiple sounds in the same cell, avoiding space added in the output" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "class Alert(object):\n", " def __init__(self, duration=0.04, volume=0.25):\n", " self.volume = volume # currently not implemented\n", " self.framerate = 44100\n", " self.duration = duration\n", " temp_dur = .0001\n", " temp_t = np.linspace(0, temp_dur, int(self.framerate*temp_dur))\n", " self.t = np.linspace(0, self.duration, int(self.framerate*self.duration))\n", " qtr_t_ix = int(len(temp_t)/4)\n", " qtr_fac = np.array(list(range(qtr_t_ix)))/qtr_t_ix\n", " mid_fac = np.ones(len(temp_t)-2*qtr_t_ix)\n", " data = np.sin(2*np.pi*100.0*temp_t) * np.concatenate((qtr_fac, mid_fac, qtr_fac[::-1]), axis=0) #* self.volume\n", "\n", " # activate the audio object\n", " self.display = disp.display(disp.Audio(data, rate=self.framerate, autoplay=True),\n", " display_id='tqdm_alerter');\n", " #hide_audio(); # not needed inside here\n", " \n", " def alert(self, freq=400):\n", " # freq in Hz\n", " # fade in and out so no click\n", " qtr_t_ix = int(len(self.t)/4)\n", " qtr_fac = np.array(list(range(qtr_t_ix)))/qtr_t_ix\n", " mid_fac = np.ones(len(self.t)-2*qtr_t_ix)\n", " data = np.sin(2*np.pi*float(freq)*self.t) * np.concatenate((qtr_fac, mid_fac, qtr_fac[::-1]), axis=0) #* self.volume\n", " # norm=False parameter to Audio only exists in this PR https://github.com/ipython/ipython/pull/11161\n", " self.display.update(disp.Audio(data, rate=self.framerate, autoplay=True));" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Testing the basic alert (not tqdm)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "a = Alert() # space only occurs when the class is made\n", "a.alert(400)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "a.alert(1500) # no more space thereafter!" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Careful not to let the frequency get above 2kHz, as there's no volume control and it will be piercing\n", "\n", "You could make a logarithmic saturation so the freq doesn't get above say 1.5kHz" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Main tqdm-overloading classes" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "import tqdm._tqdm_notebook" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", " \n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# adapted from tqdm docs: https://github.com/tqdm/tqdm#hooks-and-callbacks\n", "class TqdmAudioTicker(tqdm._tqdm_notebook.tqdm_notebook):\n", " A = Alert(duration=0.02) # will make a tiny click when instantiated in class, one time only!\n", " \n", " def __init__(self, *args, freq_step=10, **kwargs):\n", " self.freq_step = freq_step\n", " super(TqdmAudioTicker, self).__init__(*args, **kwargs)\n", "\n", " def update_to(self, b=1, bsize=1, tsize=None):\n", " \"\"\"\n", " b : int, optional\n", " Number of blocks transferred so far [default: 1].\n", " bsize : int, optional\n", " Size of each block (in tqdm units) [default: 1].\n", " tsize : int, optional\n", " Total size (in tqdm units). If [default: None] remains unchanged.\n", " \"\"\"\n", " if tsize is not None:\n", " self.total = tsize\n", " self.update(b * bsize - self.n, True) # will also set self.n = b * bsize\n", " #if b % 2 == 0:\n", " self.A.alert(200+b*10)\n", " \n", " def update(self, n=1, from_update_to=False):\n", " if not from_update_to:\n", " self.A.alert(200 + self.n * n) # base of 200Hz\n", " super(TqdmAudioTicker, self).update()\n", " \n", " def __iter__(self):\n", " \"\"\"Backward-compatibility to use: for x in tqdm(iterable)\"\"\"\n", "\n", " # Inlining instance variables as locals (speed optimisation)\n", " iterable = self.iterable\n", "\n", " # If the bar is disabled, then just walk the iterable\n", " # (note: keep this check outside the loop for performance)\n", " if self.disable:\n", " for obj in iterable:\n", " yield obj\n", " else:\n", " mininterval = self.mininterval\n", " maxinterval = self.maxinterval\n", " miniters = self.miniters\n", " dynamic_miniters = self.dynamic_miniters\n", " last_print_t = self.last_print_t\n", " last_print_n = self.last_print_n\n", " n = self.n\n", " smoothing = self.smoothing\n", " avg_time = self.avg_time\n", " _time = self._time\n", "\n", " try:\n", " sp = self.sp\n", " except AttributeError:\n", " raise TqdmDeprecationWarning(\"\"\"\\\n", "Please use `tqdm_gui(...)` instead of `tqdm(..., gui=True)`\n", "\"\"\", fp_write=getattr(self.fp, 'write', sys.stderr.write))\n", "\n", " for obj in iterable:\n", " yield obj\n", " # Update and possibly print the progressbar.\n", " # Note: does not call self.update(1) for speed optimisation.\n", " n += 1\n", " # check counter first to avoid calls to time()\n", " if n - last_print_n >= self.miniters:\n", " miniters = self.miniters # watch monitoring thread changes\n", " delta_t = _time() - last_print_t\n", " if delta_t >= mininterval:\n", " cur_t = _time()\n", " delta_it = n - last_print_n\n", " # EMA (not just overall average)\n", " if smoothing and delta_t and delta_it:\n", " avg_time = delta_t / delta_it \\\n", " if avg_time is None \\\n", " else smoothing * delta_t / delta_it + \\\n", " (1 - smoothing) * avg_time\n", "\n", " self.n = n\n", " self.A.alert(200 + self.n * self.freq_step)\n", " with self._lock:\n", " if self.pos:\n", " self.moveto(abs(self.pos))\n", " # Print bar update\n", " sp(self.__repr__())\n", " if self.pos:\n", " self.moveto(-abs(self.pos))\n", "\n", " # If no `miniters` was specified, adjust automatically\n", " # to the max iteration rate seen so far between 2 prints\n", " if dynamic_miniters:\n", " if maxinterval and delta_t >= maxinterval:\n", " # Adjust miniters to time interval by rule of 3\n", " if mininterval:\n", " # Set miniters to correspond to mininterval\n", " miniters = delta_it * mininterval / delta_t\n", " else:\n", " # Set miniters to correspond to maxinterval\n", " miniters = delta_it * maxinterval / delta_t\n", " elif smoothing:\n", " # EMA-weight miniters to converge\n", " # towards the timeframe of mininterval\n", " miniters = smoothing * delta_it * \\\n", " (mininterval / delta_t\n", " if mininterval and delta_t else 1) + \\\n", " (1 - smoothing) * miniters\n", " else:\n", " # Maximum nb of iterations between 2 prints\n", " miniters = max(miniters, delta_it)\n", "\n", " # Store old values for next call\n", " self.n = self.last_print_n = last_print_n = n\n", " self.last_print_t = last_print_t = cur_t\n", " self.miniters = miniters\n", "\n", " # Closing the progress bar.\n", " # Update some internal variables for close().\n", " self.last_print_n = last_print_n\n", " self.n = n\n", " self.miniters = miniters\n", " self.close()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# For playing iterated clips through a wave file, let's get Rick's catchphrase" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", " \n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "class Ricker(object):\n", " def __init__(self, total=100):\n", " hdr = {'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.64 Safari/537.11',\n", " 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',\n", " 'Accept-Charset': 'ISO-8859-1,utf-8;q=0.7,*;q=0.3',\n", " 'Accept-Encoding': 'none',\n", " 'Accept-Language': 'en-US,en;q=0.8',\n", " 'Connection': 'keep-alive'}\n", " data = get('http://sound.peal.io/ps/audios/000/000/537/original/woo_vu_luvub_dub_dub.wav',\n", " headers=hdr).content\n", " wav_data = read(io.BytesIO(data))\n", " #wav_data = read('woo_vu_luvub_dub_dub.wav')\n", " self.sample_rate = wav_data[0]\n", " self.wav = np.array(wav_data[1], dtype=float)[:,0] # make it mono\n", " self.set_total(total)\n", " temp_dur = .0001\n", " temp_t = np.linspace(0, temp_dur, int(self.sample_rate*temp_dur))\n", " qtr_t_ix = int(len(temp_t)/4)\n", " qtr_fac = np.array(list(range(qtr_t_ix)))/qtr_t_ix\n", " mid_fac = np.ones(len(temp_t)-2*qtr_t_ix)\n", " data = np.sin(2*np.pi*100.0*temp_t) * np.concatenate((qtr_fac, mid_fac, qtr_fac[::-1]), axis=0) #* self.volume\n", " # activate the audio object\n", " self.display = disp.display(disp.Audio(data, rate=self.sample_rate, autoplay=True),\n", " display_id='tqdm_alerter');\n", " \n", " def set_total(self, total):\n", " self.total = total\n", " self.chunk_size = int(len(self.wav)/total)\n", " \n", " def alert(self, n):\n", " if n >= self.total-1:\n", " n = self.total-1\n", " data_chunk = self.wav[n*self.chunk_size:(n+1)*self.chunk_size] \n", " # fade in and out so no click\n", " qtr_t_ix = int(self.chunk_size/8)\n", " qtr_fac = np.array(list(range(qtr_t_ix)))/qtr_t_ix\n", " mid_fac = np.ones(self.chunk_size-2*qtr_t_ix)\n", " self.display.update(disp.Audio(data_chunk * np.concatenate((qtr_fac, mid_fac, qtr_fac[::-1]), axis=0), #* self.volume\n", " rate=self.sample_rate, autoplay=True));\n", " #hide_audio()\n", " # norm=False parameter to Audio only exists in this PR https://github.com/ipython/ipython/pull/11161\n", " \n", "class TqdmAudioRicker(tqdm._tqdm_notebook.tqdm_notebook):\n", " R = Ricker() # will make a tiny click when instantiated in class, one time only!\n", " \n", " def __init__(self, *args, total=100, **kwargs):\n", " self.R.set_total(total)\n", " super(TqdmAudioRicker, self).__init__(*args, **kwargs)\n", "\n", " def update_to(self, b=1, bsize=1, tsize=None):\n", " \"\"\"\n", " b : int, optional\n", " Number of blocks transferred so far [default: 1].\n", " bsize : int, optional\n", " Size of each block (in tqdm units) [default: 1].\n", " tsize : int, optional\n", " Total size (in tqdm units). If [default: None] remains unchanged.\n", " \"\"\"\n", " if tsize is not None:\n", " self.total = tsize\n", " self.update(b * bsize - self.n, True) # will also set self.n = b * bsize\n", " #if b % 2 == 0:\n", " self.R.alert(b)\n", " \n", " def update(self, n=1, from_update_to=False):\n", " super(TqdmAudioRicker, self).update()\n", " if not from_update_to:\n", " self.R.alert(self.n) # base of 200Hz\n", " \n", " def __iter__(self):\n", " \"\"\"Backward-compatibility to use: for x in tqdm(iterable)\"\"\"\n", "\n", " # Inlining instance variables as locals (speed optimisation)\n", " iterable = self.iterable\n", "\n", " # If the bar is disabled, then just walk the iterable\n", " # (note: keep this check outside the loop for performance)\n", " if self.disable:\n", " for obj in iterable:\n", " yield obj\n", " else:\n", " mininterval = self.mininterval\n", " maxinterval = self.maxinterval\n", " miniters = self.miniters\n", " dynamic_miniters = self.dynamic_miniters\n", " last_print_t = self.last_print_t\n", " last_print_n = self.last_print_n\n", " n = self.n\n", " smoothing = self.smoothing\n", " avg_time = self.avg_time\n", " _time = self._time\n", "\n", " try:\n", " sp = self.sp\n", " except AttributeError:\n", " raise TqdmDeprecationWarning(\"\"\"\\\n", "Please use `tqdm_gui(...)` instead of `tqdm(..., gui=True)`\n", "\"\"\", fp_write=getattr(self.fp, 'write', sys.stderr.write))\n", "\n", " for obj in iterable:\n", " yield obj\n", " # Update and possibly print the progressbar.\n", " # Note: does not call self.update(1) for speed optimisation.\n", " n += 1\n", " # check counter first to avoid calls to time()\n", " if n - last_print_n >= self.miniters:\n", " miniters = self.miniters # watch monitoring thread changes\n", " delta_t = _time() - last_print_t\n", " if delta_t >= mininterval:\n", " cur_t = _time()\n", " delta_it = n - last_print_n\n", " # EMA (not just overall average)\n", " if smoothing and delta_t and delta_it:\n", " avg_time = delta_t / delta_it \\\n", " if avg_time is None \\\n", " else smoothing * delta_t / delta_it + \\\n", " (1 - smoothing) * avg_time\n", "\n", " self.n = n\n", " self.R.alert(self.n)\n", " with self._lock:\n", " if self.pos:\n", " self.moveto(abs(self.pos))\n", " # Print bar update\n", " sp(self.__repr__())\n", " if self.pos:\n", " self.moveto(-abs(self.pos))\n", "\n", " # If no `miniters` was specified, adjust automatically\n", " # to the max iteration rate seen so far between 2 prints\n", " if dynamic_miniters:\n", " if maxinterval and delta_t >= maxinterval:\n", " # Adjust miniters to time interval by rule of 3\n", " if mininterval:\n", " # Set miniters to correspond to mininterval\n", " miniters = delta_it * mininterval / delta_t\n", " else:\n", " # Set miniters to correspond to maxinterval\n", " miniters = delta_it * maxinterval / delta_t\n", " elif smoothing:\n", " # EMA-weight miniters to converge\n", " # towards the timeframe of mininterval\n", " miniters = smoothing * delta_it * \\\n", " (mininterval / delta_t\n", " if mininterval and delta_t else 1) + \\\n", " (1 - smoothing) * miniters\n", " else:\n", " # Maximum nb of iterations between 2 prints\n", " miniters = max(miniters, delta_it)\n", "\n", " # Store old values for next call\n", " self.n = self.last_print_n = last_print_n = n\n", " self.last_print_t = last_print_t = cur_t\n", " self.miniters = miniters\n", "\n", " # Closing the progress bar.\n", " # Update some internal variables for close().\n", " self.last_print_n = last_print_n\n", " self.n = n\n", " self.miniters = miniters\n", " self.close()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Testing" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "eg_link = \"https://caspersci.uk.to/matryoshka.zip\"\n", "with TqdmAudioTicker(unit='B', unit_scale=True, miniters=1,\n", " desc=eg_link.split('/')[-1]) as t: # all optional kwargs\n", " urllib.request.urlretrieve(eg_link, filename=os.devnull,\n", " reporthook=t.update_to, data=None)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "with TqdmAudioTicker([\"a\", \"b\", \"c\", \"d\", \"e\", \"f\", \"g\", \"h\", \"i\", \"j\", \"k\", \"l\", \"m\", \"n\", \"o\", \"p\", \"q\",\n", " \"r\", \"s\", \"t\", \"u\", \"v\", \"w\", \"x\", \"y\", \"z\"], freq_step=20) as pbar:\n", " for char in pbar:\n", " pbar.set_description(\"Processing %s\" % char)\n", " time.sleep(0.1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "eg_link = \"https://caspersci.uk.to/matryoshka.zip\"\n", "with TqdmAudioRicker(unit='B', unit_scale=True, miniters=1,\n", " desc=eg_link.split('/')[-1], total=50) as t: # all optional kwargs\n", " urllib.request.urlretrieve(eg_link, filename=os.devnull,\n", " reporthook=t.update_to, data=None)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "with TqdmAudioRicker([\"a\", \"b\", \"c\", \"d\", \"e\", \"f\", \"g\", \"h\", \"i\", \"j\", \"k\", \"l\", \"m\", \"n\", \"o\", \"p\", \"q\",\n", " \"r\", \"s\", \"t\", \"u\", \"v\", \"w\", \"x\", \"y\", \"z\"],\n", " total=26) as pbar:\n", " for char in pbar:\n", " pbar.set_description(\"Processing %s\" % char)\n", " time.sleep(0.12)" ] }, { "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.6.5" } }, "nbformat": 4, "nbformat_minor": 2 }