Skip to content

Commit

Permalink
Audio widget uses numpy array and sample_rate as input (#703)
Browse files Browse the repository at this point in the history
  • Loading branch information
Karamya authored and philippjfr committed Oct 15, 2019
1 parent 9a94fd6 commit 72d9bb1
Show file tree
Hide file tree
Showing 3 changed files with 107 additions and 9 deletions.
44 changes: 42 additions & 2 deletions examples/reference/widgets/Audio.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"import panel as pn\n",
"\n",
"pn.extension()"
]
},
Expand All @@ -33,13 +35,51 @@
"___"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The `Audio` widget can be constructed with a URL pointing to a remote audio file or a local audio file (in which case the data is embedded)."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"pn.widgets.Audio(name='Audio', value='http://ccrma.stanford.edu/~jos/mp3/pno-cs.mp3')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Alternatively, if SciPy is available, the widget may also be constructed from a NumPy array containing int16 values and a `sample_rate`, e.g. in this example we plot a frequency modulated signal:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"audio = pn.widgets.Audio(name='Audio', value='http://ccrma.stanford.edu/~jos/mp3/pno-cs.mp3')\n",
"sps = 44100 # Samples per second\n",
"duration = 10 # Duration in seconds\n",
"\n",
"modulator_frequency = 2.0\n",
"carrier_frequency = 120.0\n",
"modulation_index = 2.0\n",
"\n",
"time = np.arange(sps*duration) / sps\n",
"modulator = np.sin(2.0 * np.pi * modulator_frequency * time) * modulation_index\n",
"carrier = np.sin(2.0 * np.pi * carrier_frequency * time)\n",
"waveform = np.sin(2. * np.pi * (carrier_frequency * time + modulator))\n",
" \n",
"waveform_quiet = waveform * 0.3\n",
"waveform_int = np.int16(waveform_quiet * 32767)\n",
"\n",
"audio = pn.widgets.Audio(value=waveform_int, sample_rate=sps)\n",
"audio"
]
},
Expand Down Expand Up @@ -99,5 +139,5 @@
}
},
"nbformat": 4,
"nbformat_minor": 2
"nbformat_minor": 4
}
32 changes: 32 additions & 0 deletions panel/tests/widgets/test_misc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from __future__ import absolute_import, division, unicode_literals

from io import BytesIO
from base64 import b64encode

import numpy as np
from scipy.io import wavfile

from panel.widgets import Audio

def test_audio_array(document, comm):
data = np.random.randint(-100,100, 100).astype('int16')
sample_rate = 10
buffer = BytesIO()
wavfile.write(buffer, sample_rate, data)
b64_encoded = b64encode(buffer.getvalue()).decode('utf-8')

audio = Audio(name='Button', value=data, sample_rate=sample_rate)
widget = audio.get_root(document, comm=comm)
widget_value = widget.value

assert widget_value.split(',')[1] == b64_encoded
assert widget.value.startswith('data:audio/wav;base64')


def test_audio_url(document, comm):
audio_url = 'http://ccrma.stanford.edu/~jos/mp3/pno-cs.mp3'
audio2 = Audio(name='Audio', value=audio_url)
url_widget = audio2.get_root(document, comm=comm)

assert audio_url == url_widget.value

40 changes: 33 additions & 7 deletions panel/widgets/misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,13 @@

import os

from io import BytesIO
from base64 import b64encode
from six import string_types

import param
import numpy as np
from scipy.io import wavfile

from ..io.notebook import push
from ..io.state import state
Expand All @@ -30,29 +34,51 @@ class Audio(Widget):
paused = param.Boolean(default=True, doc="""
Whether the audio is currently paused""")

value = param.String(default='', doc="""
sample_rate = param.Integer(default=44100, doc="""
The sample_rate of the audio when given a NumPy array.""")

value = param.ClassSelector(default='', class_=(string_types + (np.ndarray,)), doc="""
The audio file either local or remote.""")

volume = param.Number(default=None, bounds=(0, 100), doc="""
The volume of the audio player.""")

_widget_type = _BkAudio

_rename = {'name': None}
_rename = {'name': None, 'sample_rate': None}

def _from_numpy(self, data):
buffer = BytesIO()
wavfile.write(buffer, self.sample_rate, data)
return buffer

def _process_param_change(self, msg):
msg = super(Audio, self)._process_param_change(msg)
if 'value' in msg and os.path.isfile(msg['value']):
fmt = msg['value'].split('.')[-1]
with open(msg['value'], 'rb') as f:
data = f.read()

if 'value' in msg:
value = msg['value']
if isinstance(value, np.ndarray):
fmt = 'wav'
buffer = self._from_numpy(value)
data = b64encode(buffer.getvalue())
elif os.path.isfile(value):
fmt = value.split('.')[-1]
with open(value, 'rb') as f:
data = f.read()
data = b64encode(data)
elif value.lower().startswith('http'):
return msg
elif not value:
data, fmt = b'', 'wav'
else:
raise ValueError('Value should be either path to a sound file or numpy array')
template = 'data:audio/{mime};base64,{data}'
data = b64encode(data)
msg['value'] = template.format(data=data.decode('utf-8'),
mime=fmt)
return msg



class VideoStream(Widget):

format = param.ObjectSelector(default='png', objects=['png', 'jpeg'],
Expand Down

0 comments on commit 72d9bb1

Please sign in to comment.