Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Audio widget uses numpy array and sample_rate as input #703

Merged
merged 7 commits into from
Oct 15, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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