From c934c1da8778eea82aa7e7ac4e959cc58d0a59e2 2014-05-04 19:29:58 From: Erik Tollerud Date: 2014-05-04 19:29:58 Subject: [PATCH] implemented support for multiple channels in Audio --- diff --git a/IPython/lib/display.py b/IPython/lib/display.py index 22d4f63..7ab27b4 100644 --- a/IPython/lib/display.py +++ b/IPython/lib/display.py @@ -14,7 +14,7 @@ class Audio(DisplayObject): When this object is returned by an input cell or passed to the display function, it will result in Audio controls being displayed in the frontend (only works in the notebook). - + Parameters ---------- data : numpy array, list, unicode, str or bytes @@ -23,19 +23,19 @@ class Audio(DisplayObject): * List of float or integer representing the waveform (mono) * String containing the filename * Bytestring containing raw PCM data or - * URL pointing to a file on the web. - + * URL pointing to a file on the web. + If the array option is used the waveform will be normalized. - - If a filename or url is used the format support will be browser - dependent. + + If a filename or url is used the format support will be browser + dependent. url : unicode A URL to download the data from. filename : unicode Path to a local file to load the data from. embed : boolean Should the image data be embedded using a data URI (True) or should - the original source be referenced. Set this to True if you want the + the original source be referenced. Set this to True if you want the audio to playable later with no internet connection in the notebook. Default is `True`, unless the keyword argument `url` is set, then @@ -75,17 +75,17 @@ class Audio(DisplayObject): raise ValueError("No image data found. Expecting filename, url, or data.") if embed is False and url is None: raise ValueError("No url found. Expecting url when embed=False") - + if url is not None and embed is not True: self.embed = False else: self.embed = True self.autoplay = autoplay super(Audio, self).__init__(data=data, url=url, filename=filename) - + if self.data is not None and not isinstance(self.data, bytes): self.data = self._make_wav(data,rate) - + def reload(self): """Reload the raw data from file or URL.""" import mimetypes @@ -98,32 +98,55 @@ class Audio(DisplayObject): self.mimetype = mimetypes.guess_type(self.url)[0] else: self.mimetype = "audio/wav" - + def _make_wav(self, data, rate): """ Transform a numpy array to a PCM bytestring """ import struct from io import BytesIO import wave + try: import numpy as np - data = np.array(data,dtype=float) - if len(data.shape) > 1: - raise ValueError("encoding of stereo PCM signals are unsupported") + + data = np.array(data, dtype=float) + if len(data.shape) == 1: + nchan = 1 + elif len(data.shape) == 2: + # In wave files,channels are interleaved. E.g., + # "L1R1L2R2..." for stereo. See + # http://msdn.microsoft.com/en-us/library/windows/hardware/dn653308(v=vs.85).aspx + # for channel ordering + nchan = data.shape[0] + data = data.T.ravel() + else: + raise ValueError('Array audio input must be a 1D or 2D array') scaled = np.int16(data/np.max(np.abs(data))*32767).tolist() except ImportError: + # check that it is a "1D" list + idata = iter(data) # fails if not an iterable + try: + iter(idata.next()) + raise TypeError('Only lists of mono audio are ' + 'supported if numpy is not installed') + except TypeError: + # this means it's not a nested list, which is what we want + pass maxabsvalue = float(max([abs(x) for x in data])) - scaled = [int(x/maxabsvalue*32767) for x in data] + scaled = [int(x/maxabsvalue*32767) for x in data] + nchan = 1 + fp = BytesIO() waveobj = wave.open(fp,mode='wb') - waveobj.setnchannels(1) + waveobj.setnchannels(nchan) waveobj.setframerate(rate) waveobj.setsampwidth(2) waveobj.setcomptype('NONE','NONE') waveobj.writeframes(b''.join([struct.pack('