--- /dev/null
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2014 Parisson SARL
+# Copyright (c) 2014 Thomas Fillon <thomas@parisson.com>
+
+# This file is part of TimeSide.
+
+# TimeSide is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+
+# TimeSide is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with TimeSide. If not, see <http://www.gnu.org/licenses/>.
+#
+# Author : Thomas Fillon <thomas@parisson.com>
+
+import tables
+from tempfile import NamedTemporaryFile
+import numpy as np
+
+class BufferTable(object):
+ def __init__(self, array_names=None):
+ self._tempfile = NamedTemporaryFile(mode='w', suffix='.h5',
+ prefix='ts_buf_',
+ delete=True)
+ self.fileh = tables.openFile(self._tempfile.name, mode='w')
+
+ if not array_names:
+ array_names = []
+ if isinstance(array_names, list):
+ self.array_names = array_names
+ else:
+ self.array_names = [array_names]
+ for name in array_names:
+ if not isinstance(name, basestring):
+ raise(TypeError, 'String argument require in array_names')
+
+ def __getitem__(self, name):
+ return self.fileh.root.__getattr__(name)
+
+ #def __set_item__(self, name, value):
+ # self.fileh.root.__setattr__(name, value)
+
+ def append(self, name, new_array):
+ try:
+ if new_array.shape:
+ self.fileh.root.__getattr__(name).append(new_array[np.newaxis,
+ :])
+ else:
+ self.fileh.root.__getattr__(name).append([new_array])
+ except tables.exceptions.NoSuchNodeError:
+ if name not in self.array_names:
+ self.array_names.append(name)
+ # The following is compatible with pytables 3 only
+ #self.fileh.create_earray(where=self.fileh.root,
+ # name=name,
+ # obj=[new_array])
+ atom = tables.Atom.from_dtype(new_array.dtype)
+ dim_list = [0]
+ dim_list.extend([dim for dim in new_array.shape])
+ shape = tuple(dim_list)
+
+ self.fileh.createEArray(where=self.fileh.root,
+ name=name,
+ atom=atom,
+ shape=shape)
+ self.append(name, new_array)
+
+ def close(self):
+ for name in self.array_names:
+ self.fileh.removeNode(self.fileh.root, name)
+ self.fileh.close()
+ self._tempfile.close()
--- /dev/null
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2006-2010 Guillaume Pellerin
+
+# <yomguy@parisson.com>
+
+# This software is a computer program whose purpose is to stream audio
+# and video data through icecast2 servers.
+
+# This software is governed by the CeCILL license under French law and
+# abiding by the rules of distribution of free software. You can use,
+# modify and/ or redistribute the software under the terms of the CeCILL
+# license as circulated by CEA, CNRS and INRIA at the following URL
+# "http://www.cecill.info".
+
+# As a counterpart to the access to the source code and rights to copy,
+# modify and redistribute granted by the license, users are provided only
+# with a limited warranty and the software's author, the holder of the
+# economic rights, and the successive licensors have only limited
+# liability.
+
+# In this respect, the user's attention is drawn to the risks associated
+# with loading, using, modifying and/or developing or reproducing the
+# software by the user in light of its specific status of free software,
+# that may mean that it is complicated to manipulate, and that also
+# therefore means that it is reserved for developers and experienced
+# professionals having in-depth computer knowledge. Users are therefore
+# encouraged to load and test the software's suitability as regards their
+# requirements in conditions enabling the security of their systems and/or
+# data to be ensured and, more generally, to use and operate it in the
+# same conditions as regards security.
+
+# The fact that you are presently reading this means that you have had
+# knowledge of the CeCILL license and that you accept its terms.
+
+# Author: Guillaume Pellerin <yomguy@parisson.com>
+
+import os
+import xml.dom.minidom
+
+
+class Cache(object):
+
+ def __init__(self, dir, params=None):
+ self.dir = dir
+ self.params = params
+ self.files = self.get_files()
+
+ def get_files(self):
+ list = []
+ for root, dirs, files in os.walk(self.dir):
+ for file in files:
+ list.append(file)
+ return list
+
+ def exists(self, file):
+ self.files = self.get_files()
+ return file in self.files
+
+ def write_bin(self, data, file):
+ path = self.dir + os.sep + file
+ f = open(path, 'w')
+ f.write(data)
+ f.close()
+
+ def read_bin(self, file):
+ path = self.dir + os.sep + file
+ f = open(path, 'r')
+ data = f.read()
+ f.close()
+ return data
+
+ def read_stream_bin(self, file):
+ path = self.dir + os.sep + file
+ chunk_size = 0x1000
+ f = open(path, 'r')
+ while True:
+ _chunk = f.read(chunk_size)
+ if not len(_chunk):
+ break
+ yield _chunk
+ f.close()
+
+ def write_stream_bin(self, chunk, file_object):
+ file_object.write(chunk)
+
+ def read_analyzer_xml(self, file):
+ list = []
+ path = self.dir + os.sep + file
+ doc = xml.dom.minidom.parse(path)
+ for data in doc.documentElement.getElementsByTagName('data'):
+ name = data.getAttribute('name')
+ id = data.getAttribute('id')
+ unit = data.getAttribute('unit')
+ value = data.getAttribute('value')
+ list.append({'name': name, 'id': id, 'unit': unit, 'value': value})
+ return list
+
+ def write_analyzer_xml(self, data_list, file):
+ path = self.dir + os.sep + file
+ doc = xml.dom.minidom.Document()
+ root = doc.createElement('telemeta')
+ doc.appendChild(root)
+ for data in data_list:
+ name = data['name']
+ id = data['id']
+ unit = data['unit']
+ value = data['value']
+ node = doc.createElement('data')
+ node.setAttribute('name', name)
+ node.setAttribute('id', id)
+ node.setAttribute('unit', unit)
+ node.setAttribute('value', str(value))
+ root.appendChild(node)
+ f = open(path, "w")
+ f.write(xml.dom.minidom.Document.toprettyxml(doc))
+ f.close()
--- /dev/null
+from numpy import getbuffer, frombuffer
+
+import pygst
+pygst.require('0.10')
+import gst
+import gobject
+gobject.threads_init()
+
+import threading
+
+
+def numpy_array_to_gst_buffer(frames, chunk_size, num_samples, sample_rate):
+ from gst import Buffer
+ """ gstreamer buffer to numpy array conversion """
+ buf = Buffer(getbuffer(frames.astype("float32")))
+ # Set its timestamp and duration
+ buf.timestamp = gst.util_uint64_scale(num_samples, gst.SECOND, sample_rate)
+ buf.duration = gst.util_uint64_scale(chunk_size, gst.SECOND, sample_rate)
+ return buf
+
+
+def gst_buffer_to_numpy_array(buf, chan):
+ """ gstreamer buffer to numpy array conversion """
+ samples = frombuffer(buf.data, dtype='float32').reshape((-1, chan))
+ return samples
+
+
+class MainloopThread(threading.Thread):
+
+ def __init__(self, mainloop):
+ threading.Thread.__init__(self)
+ self.mainloop = mainloop
+
+ def run(self):
+ self.mainloop.run()
--- /dev/null
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2007-2013 Parisson SARL
+
+# This file is part of TimeSide.
+
+# TimeSide is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+
+# TimeSide is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with TimeSide. If not, see <http://www.gnu.org/licenses/>.
+
+# Author:
+# Thomas Fillon <thomas at parisson.com>
+
+
+def dict_to_hdf5(dict_like, h5group):
+ """
+ Save a dictionnary-like object inside a h5 file group
+ """
+ # Write attributes
+ attrs = dict_like.keys()
+ for name in attrs:
+ if dict_like[name] is not None:
+ h5group.attrs[str(name)] = dict_like[name]
+
+
+def dict_from_hdf5(dict_like, h5group):
+ """
+ Load a dictionnary-like object from a h5 file group
+ """
+ # Read attributes
+ for name, value in h5group.attrs.items():
+ dict_like[name] = value
--- /dev/null
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+import logging
+
+
+class Logger:
+
+ """A logging object"""
+
+ def __init__(self, file):
+ self.logger = logging.getLogger('myapp')
+ self.hdlr = logging.FileHandler(file)
+ self.formatter = logging.Formatter(
+ '%(asctime)s %(levelname)s %(message)s')
+ self.hdlr.setFormatter(self.formatter)
+ self.logger.addHandler(self.hdlr)
+ self.logger.setLevel(logging.INFO)
+
+ def write_info(self, message):
+ self.logger.info(message)
+
+ def write_error(self, message):
+ self.logger.error(message)
--- /dev/null
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2013-2014 Thomas Fillon <thomas.fillon@parisson.com>
+
+# This file is part of TimeSide.
+
+# TimeSide is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+
+# TimeSide is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with TimeSide. If not, see <http://www.gnu.org/licenses/>.
+
+# Author: Thomas Fillon <thomas.fillon@parisson.com>
+
+
+from importlib import import_module
+import warnings
+
+from ..core.exceptions import VampImportError
+
+def discover_modules(subpackage, package=None):
+ import pkgutil
+
+ if package:
+ try:
+ _pkg = import_module('.' + subpackage, package)
+ except ImportError as e:
+ raise e
+ else:
+ _pkg = import_module(subpackage)
+
+ pkg_path = _pkg.__path__
+ pkg_prefix = _pkg.__name__ + '.'
+
+ _list = [import_module_with_exceptions(modname)
+ for importer, modname, ispkg
+ in pkgutil.walk_packages(pkg_path, pkg_prefix)]
+
+ modules_list = [mod for mod in _list if mod is not None]
+ return modules_list
+
+
+def import_module_with_exceptions(name, package=None):
+ """Wrapper around importlib.import_module to import TimeSide subpackage
+ and ignoring ImportError if Aubio, Yaafe and Vamp Host are not available"""
+
+ from timeside.core import _WITH_AUBIO, _WITH_YAAFE, _WITH_VAMP
+
+ if name.count('.server.'):
+ # TODO:
+ # Temporary skip all timeside.server submodules before check dependencies
+ return
+ try:
+ import_module(name, package)
+ except VampImportError:
+ # No Vamp Host
+ if _WITH_VAMP:
+ raise VampImportError
+ else:
+ # Ignore Vamp ImportError
+ return
+ except ImportError as e:
+ if str(e).count('yaafelib') and not _WITH_YAAFE:
+ # Ignore Yaafe ImportError
+ return
+ elif str(e).count('aubio') and not _WITH_AUBIO:
+ # Ignore Aubio ImportError
+ return
+ elif str(e).count('DJANGO_SETTINGS_MODULE'):
+ # Ignore module requiring DJANGO_SETTINGS_MODULE in environnement
+ return
+ else:
+ print (name, package)
+ raise e
+ return name
+
+
+# Check Availability of external Audio feature extraction librairies
+def check_aubio():
+ "Check Aubio availability"
+ try:
+ import aubio
+ except ImportError:
+ warnings.warn('Aubio librairy is not available', ImportWarning,
+ stacklevel=2)
+ _WITH_AUBIO = False
+ else:
+ _WITH_AUBIO = True
+ del aubio
+
+ return _WITH_AUBIO
+
+
+def check_yaafe():
+ "Check Aubio availability"
+ try:
+ import yaafelib
+ except ImportError:
+ warnings.warn('Yaafe librairy is not available', ImportWarning,
+ stacklevel=2)
+ _WITH_YAAFE = False
+ else:
+ _WITH_YAAFE = True
+ del yaafelib
+ return _WITH_YAAFE
+
+
+def check_vamp():
+ "Check Vamp host availability"
+ from ..core.exceptions import VampImportError
+
+ try:
+ from timeside.plugins.analyzer.externals import vamp_plugin
+ except VampImportError:
+ warnings.warn('Vamp host is not available', ImportWarning,
+ stacklevel=2)
+ _WITH_VAMP = False
+ else:
+ _WITH_VAMP = True
+ del vamp_plugin
+
+ return _WITH_VAMP
+
+def add_gstreamer_packages():
+ import os
+ import sys
+ from distutils.sysconfig import get_python_lib
+
+ dest_dir = get_python_lib()
+
+ packages = ['gobject', 'glib', 'pygst', 'pygst.pyc', 'pygst.pth',
+ 'gst-0.10', 'pygtk.pth', 'pygtk.py', 'pygtk.pyc']
+
+ python_version = sys.version[:3]
+ global_path = os.path.join('/usr/lib', 'python' + python_version)
+ global_sitepackages = [os.path.join(global_path,
+ 'dist-packages'), # for Debian-based
+ os.path.join(global_path,
+ 'site-packages')] # for others
+
+ for package in packages:
+ for pack_dir in global_sitepackages:
+ src = os.path.join(pack_dir, package)
+ dest = os.path.join(dest_dir, package)
+ if not os.path.exists(dest) and os.path.exists(src):
+ os.symlink(src, dest)
+
+
+def check_gstreamer():
+ try:
+ import gobject, pygst
+ except ImportError:
+ print 'Add Gstreamer'
+ add_gstreamer_packages()
+
--- /dev/null
+# -*- coding: utf-8 -*-
+
+#
+# Copyright (c) 2007-2014 Parisson SARL
+
+# This file is part of TimeSide.
+
+# TimeSide is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+
+# TimeSide is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with TimeSide. If not, see <http://www.gnu.org/licenses/>.
+
+# Authors:
+# Thomas Fillon <thomas at parisson.com>
+
+
+from traits.api import HasTraits, Unicode, Int, Float, Range, Enum
+from traits.api import ListUnicode, List, Tuple
+from traits.api import TraitError
+
+import simplejson as json
+
+
+TRAIT_TYPES = {Unicode: 'str',
+ Int: 'int',
+ Float: 'float',
+ Range: 'range',
+ Enum: 'enum',
+ ListUnicode: 'list of str',
+ List: 'list'}
+
+
+class HasParam(object):
+ """
+ >>> class ParamClass(HasParam):
+ ... class _Param(HasTraits):
+ ... param1 = Unicode(desc='first or personal name',
+ ... label='First Name')
+ ... param2 = Int()
+ ... param3 = Float()
+ ... param4 = Range(low=0, high=10, value=3)
+ >>>
+ >>> p = ParamClass()
+ >>> param_json = p.get_parameters()
+ >>> print param_json
+ {"param4": 3, "param3": 0.0, "param2": 0, "param1": ""}
+ >>> new_param_json = '{"param1": "plop", "param2": 7, "param3": 0.5, \
+ "param4": 8}'
+ >>> p.set_parameters(new_param_json)
+ >>> print p.get_parameters()
+ {"param4": 8, "param3": 0.5, "param2": 7, "param1": "plop"}
+ >>> v = p.param_view()
+ >>> print v
+ {"param4": {"default": 3, "type": "range"}, \
+"param3": {"default": 0.0, "type": "float"}, \
+"param2": {"default": 0, "type": "int"}, \
+"param1": {"default": "", "type": "str"}}
+ """
+ class _Param(HasTraits):
+ pass
+
+ def __init__(self):
+ super(HasParam, self).__init__()
+ self._parameters = self._Param()
+
+ def __setattr__(self, name, value):
+ if name is '_parameters':
+ super(HasParam, self).__setattr__(name, value)
+ elif name in self._parameters.trait_names():
+ self._parameters.__setattr__(name, value)
+ # Copy attributes as a regular attribute at class level
+ _value = self._parameters.__getattribute__(name)
+ super(HasParam, self).__setattr__(name, _value)
+ else:
+ super(HasParam, self).__setattr__(name, value)
+
+ def get_parameters(self):
+ list_traits = self._parameters.editable_traits()
+ param_dict = self._parameters.get(list_traits)
+ return json.dumps(param_dict)
+
+ def set_parameters(self, parameters):
+ if isinstance(parameters, basestring):
+ self.set_parameters(json.loads(parameters))
+ else:
+ for name, value in parameters.items():
+ self.__setattr__(name, value)
+
+ def validate_parameters(self, parameters):
+ """Validate parameters format against Traits specification
+ Input can be either a dictionary or a JSON string
+ Returns the validated parameters or raises a ValueError"""
+
+ if isinstance(parameters, basestring):
+ return self.validate_parameters(json.loads(parameters))
+ # Check key against traits name
+ traits_name = self._parameters.editable_traits()
+ for name in parameters:
+ if name not in traits_name:
+ raise KeyError(name)
+
+ try:
+ valid_params = {name: self._parameters.validate_trait(name, value)
+ for name, value in parameters.items()}
+ except TraitError as e:
+ raise ValueError(str(e))
+
+ return valid_params
+
+ def param_view(self):
+ list_traits = self._parameters.editable_traits()
+ view = {}
+ for key in list_traits:
+ trait_type = self._parameters.trait(key).trait_type.__class__
+ default = self._parameters.trait(key).default
+ d = {'type': TRAIT_TYPES[trait_type],
+ 'default': default}
+ view[key] = d
+ return json.dumps(view)
+
+
+if __name__ == "__main__":
+ import doctest
+ doctest.testmod()
--- /dev/null
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+Created on Tue Oct 7 09:19:37 2014
+
+@author: thomas
+"""
+
+from __future__ import division
+
+import pygst
+pygst.require("0.10")
+import gobject
+import gst
+import numpy
+import scipy.signal.waveforms
+import os.path
+
+#import timeside
+
+
+class NumpySrc:
+ def __init__(self, array, samplerate):
+ self.appsrc = gst.element_factory_make("appsrc")
+ self.pos = 0
+ self.samplerate = samplerate
+ if array.ndim == 1:
+ array.resize((array.shape[0], 1))
+ self.length, self.channels = array.shape
+ self.array = array.astype("float32")
+ self.per_sample = gst.SECOND // samplerate
+ self.fac = self.channels * array.dtype.itemsize
+ #self.appsrc.set_property("size", (self.length * self.channels *
+ # array.dtype.itemsize))
+ self.appsrc.set_property("format", gst.FORMAT_TIME)
+ capstr = """audio/x-raw-float,
+ width=%d,
+ depth=%d,
+ rate=%d,
+ channels=%d,
+ endianness=(int)1234,
+ signed=true""" % (self.array.dtype.itemsize*8,
+ self.array.dtype.itemsize*8,
+ self.samplerate,
+ self.channels)
+ self.appsrc.set_property("caps", gst.caps_from_string(capstr))
+ self.appsrc.set_property("stream-type", 0) # Seekable
+ self.appsrc.set_property('block', True)
+
+ self.appsrc.connect("need-data", self.need_data)
+ self.appsrc.connect("seek-data", self.seek_data)
+ self.appsrc.connect("enough-data", self.enough_data)
+
+ def need_data(self, element, length):
+ #length = length // 64
+ if self.pos >= self.array.shape[0]:
+ element.emit("end-of-stream")
+ else:
+ avalaible_sample = self.length - self.pos
+ if avalaible_sample < length:
+ length = avalaible_sample
+ array = self.array[self.pos:self.pos+length]
+ buf = gst.Buffer(numpy.getbuffer(array.flatten()))
+
+ buf.timestamp = self.pos * self.per_sample
+ buf.duration = int(length*self.per_sample)
+ element.emit("push-buffer", buf)
+ self.pos += length
+
+ def seek_data(self, element, npos):
+ print 'seek %d' % npos
+ self.pos = npos // self.per_sample
+ return True
+
+ def enough_data(self, element):
+ print "----------- enough data ---------------"
+
+
+class SampleArray(object):
+ """Base Class for generating a data sample array"""
+
+ def __init__(self, duration=10, samplerate=44100):
+ self.samplerate = int(samplerate)
+ self.num_samples = int(numpy.ceil(duration * self.samplerate))
+ self.array = NotImplemented
+
+ @property
+ def time_samples(self):
+ return numpy.arange(0, self.num_samples)
+
+ @property
+ def duration(self):
+ return self.num_samples / self.samplerate
+
+ def __add__(self, other):
+ if not self.samplerate == other.samplerate:
+ raise ValueError("Samplerates mismatch")
+
+ sum_ = SampleArray(samplerate=self.samplerate)
+ sum_.num_samples = self.num_samples + other.num_samples
+ sum_.array = numpy.vstack([self.array, other.array])
+ return sum_
+
+ def __and__(self, other):
+ if not self.samplerate == other.samplerate:
+ raise ValueError("Samplerates mismatch")
+ if not self.num_samples == other.num_samples:
+ raise ValueError("Number of samples mismatch")
+
+ and_ = SampleArray(samplerate=self.samplerate)
+ and_.num_samples = self.num_samples
+ and_.array = numpy.hstack([self.array, other.array])
+ return and_
+
+
+class SineArray(SampleArray):
+ """Class for generating a Sine array"""
+ def __init__(self, frequency=440, duration=10, samplerate=44100,
+ channels=1):
+ super(SineArray, self).__init__(duration=duration,
+ samplerate=samplerate)
+ self.frequency = frequency
+ self.array = numpy.sin((2 * numpy.pi * self.frequency *
+ self.time_samples / self.samplerate))
+ self.array.resize(self.num_samples, 1)
+
+
+class SweepArray(SampleArray):
+ """Class for generating a Sweep array"""
+ def __init__(self, f0=20, f1=None, duration=10, samplerate=44100,
+ method='logarithmic'):
+ super(SweepArray, self).__init__(duration=duration,
+ samplerate=samplerate)
+
+ self.f0 = f0 / samplerate
+ if f1 is None:
+ self.f1 = 0.5
+ else:
+ self.f1 = f1 / samplerate
+ self.method = method
+ self.array = scipy.signal.waveforms.chirp(t=self.time_samples,
+ f0=self.f0,
+ t1=self.time_samples[-1],
+ f1=self.f1,
+ method=self.method)
+ self.array.resize(self.num_samples, 1)
+
+
+class WhiteNoiseArray(SampleArray):
+ """Class for generating a white noise array"""
+ def __init__(self, duration=10, samplerate=44100):
+ super(WhiteNoiseArray, self).__init__(duration=duration,
+ samplerate=samplerate)
+ array = numpy.random.randn(self.num_samples, 1)
+ self.array = array / abs(array).max()
+
+
+class SilenceArray(SampleArray):
+ """Class for generating a silence"""
+ def __init__(self, duration=10, samplerate=44100):
+ super(SilenceArray, self).__init__(duration=duration,
+ samplerate=samplerate)
+
+ self.array = numpy.zeros((self.num_samples, 1))
+
+
+class gst_BuildSample(object):
+ def __init__(self, sample_array, output_file, gst_audio_encoder):
+ if not isinstance(sample_array, SampleArray):
+ raise ValueError("array must be a SampleArray subclass")
+ self.sample_array = sample_array
+ self.samplerate = self.sample_array.samplerate
+ self.output_file = output_file
+ if not isinstance(gst_audio_encoder, list):
+ gst_audio_encoder = [gst_audio_encoder]
+ self.gst_audio_encoder = gst_audio_encoder
+
+ def run(self):
+ pipeline = gst.Pipeline("pipeline")
+ gobject.threads_init()
+ mainloop = gobject.MainLoop()
+
+ numpy_src = NumpySrc(array=self.sample_array.array,
+ samplerate=self.samplerate)
+
+ converter = gst.element_factory_make('audioconvert', 'converter')
+
+ encoder_muxer = []
+ for enc in self.gst_audio_encoder:
+ encoder_muxer.append(gst.element_factory_make(enc))
+
+ filesink = gst.element_factory_make('filesink', 'sink')
+ filesink.set_property('location', self.output_file)
+
+ pipe_elements = [numpy_src.appsrc, converter]
+ pipe_elements.extend(encoder_muxer)
+ pipe_elements.append(filesink)
+
+ pipeline.add(*pipe_elements)
+ gst.element_link_many(*pipe_elements)
+
+ def _on_new_pad(self, source, pad, target_pad):
+ print 'on_new_pad'
+ if not pad.is_linked():
+ if target_pad.is_linked():
+ target_pad.get_peer().unlink(target_pad)
+ pad.link(target_pad)
+
+ def on_eos(bus, msg):
+ pipeline.set_state(gst.STATE_NULL)
+ mainloop.quit()
+
+ def on_error(bus, msg):
+ err, debug_info = msg.parse_error()
+ print ("Error received from element %s: %s" % (msg.src.get_name(),
+ err))
+ print ("Debugging information: %s" % debug_info)
+ mainloop.quit()
+
+ pipeline.set_state(gst.STATE_PLAYING)
+ bus = pipeline.get_bus()
+ bus.add_signal_watch()
+ bus.connect('message::eos', on_eos)
+ bus.connect("message::error", on_error)
+
+ mainloop.run()
+
+
+def generate_sample_file(filename, samples_dir, gst_audio_encoder,
+ sample_array, overwrite=False):
+ sample_file = os.path.join(samples_dir, filename)
+
+ if overwrite or not os.path.exists(sample_file):
+ gst_builder = gst_BuildSample(sample_array=sample_array,
+ output_file=sample_file,
+ gst_audio_encoder=gst_audio_encoder)
+ gst_builder.run()
+ return sample_file
+
+
+def generateSamples(overwrite=False):
+ from timeside import __file__ as ts_file
+ ts_path = os.path.split(os.path.abspath(ts_file))[0]
+ tests_dir = os.path.abspath(os.path.join(ts_path, '../tests'))
+ if os.path.isdir(tests_dir):
+ samples_dir = os.path.abspath(os.path.join(tests_dir, 'samples'))
+ if not os.path.isdir(samples_dir):
+ os.makedirs(samples_dir)
+ else:
+ import tempfile
+ samples_dir = tempfile.mkdtemp(suffix="ts_samples")
+
+ samples = dict()
+
+ # --------- Sweeps ---------
+ # sweep 44100 mono wav
+ filename = 'sweep_mono.wav'
+ samplerate = 44100
+ gst_audio_encoder = 'wavenc'
+ sweep_mono = SweepArray(duration=8, samplerate=samplerate)
+ sample_file = generate_sample_file(filename, samples_dir,
+ gst_audio_encoder,
+ sample_array=sweep_mono,
+ overwrite=overwrite)
+ samples.update({filename: sample_file})
+
+ # sweep 44100 stereo wav
+ sweep_stereo = sweep_mono & sweep_mono
+ filename = 'sweep.wav'
+ gst_audio_encoder = 'wavenc'
+ sweep_mono = SweepArray(duration=8, samplerate=samplerate)
+ sample_file = generate_sample_file(filename, samples_dir,
+ gst_audio_encoder,
+ sample_array=sweep_stereo,
+ overwrite=overwrite)
+ samples.update({filename: sample_file})
+
+ # sweep 44100 stereo mp3
+ filename = 'sweep.mp3'
+ gst_audio_encoder = ['lamemp3enc', 'xingmux', 'id3v2mux']
+ sweep_mono = SweepArray(duration=8, samplerate=samplerate)
+ sample_file = generate_sample_file(filename, samples_dir,
+ gst_audio_encoder,
+ sample_array=sweep_stereo,
+ overwrite=overwrite)
+ samples.update({filename: sample_file})
+
+ # sweep 44100 stereo flac
+ filename = 'sweep.flac'
+ gst_audio_encoder = 'flacenc'
+ sweep_mono = SweepArray(duration=8, samplerate=samplerate)
+ sample_file = generate_sample_file(filename, samples_dir,
+ gst_audio_encoder,
+ sample_array=sweep_stereo,
+ overwrite=overwrite)
+ samples.update({filename: sample_file})
+
+ # sweep 44100 stereo ogg
+ filename = 'sweep.ogg'
+ gst_audio_encoder = ['vorbisenc', 'oggmux']
+ sweep_mono = SweepArray(duration=8, samplerate=samplerate)
+ sample_file = generate_sample_file(filename, samples_dir,
+ gst_audio_encoder,
+ sample_array=sweep_stereo,
+ overwrite=overwrite)
+ samples.update({filename: sample_file})
+
+ # sweep 32000 stereo wav
+ samplerate = 32000
+ sweep_mono = SweepArray(duration=8, samplerate=samplerate)
+ sweep_stereo = sweep_mono & sweep_mono
+
+ filename = 'sweep_32000.wav'
+ gst_audio_encoder = 'wavenc'
+ sweep_mono = SweepArray(duration=8, samplerate=samplerate)
+ sample_file = generate_sample_file(filename, samples_dir,
+ gst_audio_encoder,
+ sample_array=sweep_stereo,
+ overwrite=overwrite)
+ samples.update({filename: sample_file})
+
+ # --------- Sines ---------
+ # sine at 440Hz, 44100 mono wav
+ filename = 'sine440Hz_mono.wav'
+ samplerate = 44100
+ gst_audio_encoder = 'wavenc'
+ sweep_mono = SineArray(duration=8, samplerate=samplerate, frequency=440)
+ sample_file = generate_sample_file(filename, samples_dir,
+ gst_audio_encoder,
+ sample_array=sweep_mono,
+ overwrite=overwrite)
+ samples.update({filename: sample_file})
+
+ # Short 1s sine at 440Hz, 44100 mono wav
+ filename = 'sine440Hz_mono_1s.wav'
+ samplerate = 44100
+ gst_audio_encoder = 'wavenc'
+ sweep_mono = SineArray(duration=1, samplerate=samplerate, frequency=440)
+ sample_file = generate_sample_file(filename, samples_dir,
+ gst_audio_encoder,
+ sample_array=sweep_mono,
+ overwrite=overwrite)
+ samples.update({filename: sample_file})
+
+ # sine at 440Hz, 44100 stereo wav
+ filename = 'sine440Hz.wav'
+ sweep_stereo = sweep_mono & sweep_mono
+ gst_audio_encoder = 'wavenc'
+ sweep_mono = SineArray(duration=8, samplerate=samplerate, frequency=440)
+ sample_file = generate_sample_file(filename, samples_dir,
+ gst_audio_encoder,
+ sample_array=sweep_stereo,
+ overwrite=overwrite)
+ samples.update({filename: sample_file})
+
+ # sine at 440Hz, 44100 stereo mp3
+ filename = 'sine440Hz.mp3'
+ gst_audio_encoder = ['lamemp3enc', 'xingmux', 'id3v2mux']
+ sweep_mono = SineArray(duration=8, samplerate=samplerate, frequency=440)
+ sample_file = generate_sample_file(filename, samples_dir,
+ gst_audio_encoder,
+ sample_array=sweep_stereo,
+ overwrite=overwrite)
+ samples.update({filename: sample_file})
+
+ # sine at 440Hz, 44100 stereo ogg
+ filename = 'sine440Hz.ogg'
+ gst_audio_encoder = ['vorbisenc', 'oggmux']
+ sweep_mono = SineArray(duration=8, samplerate=samplerate, frequency=440)
+ sample_file = generate_sample_file(filename, samples_dir,
+ gst_audio_encoder,
+ sample_array=sweep_stereo,
+ overwrite=overwrite)
+ samples.update({filename: sample_file})
+
+ # --------- Equal tempered scale ---------
+ filename = 'C4_scale.wav'
+ samplerate = 44100
+ f_C4 = 261.63
+ f_D4 = 293.66
+ f_E4 = 329.63
+ f_F4 = 349.23
+ f_G4 = 392.00
+ f_A4 = 440.00
+ f_B4 = 493.88
+ f_C5 = 523.25
+ sineC4 = SineArray(duration=1, samplerate=samplerate, frequency=f_C4)
+ sineD4 = SineArray(duration=1, samplerate=samplerate, frequency=f_D4)
+ sineE4 = SineArray(duration=1, samplerate=samplerate, frequency=f_E4)
+ sineF4 = SineArray(duration=1, samplerate=samplerate, frequency=f_F4)
+ sineG4 = SineArray(duration=1, samplerate=samplerate, frequency=f_G4)
+ sineA4 = SineArray(duration=1, samplerate=samplerate, frequency=f_A4)
+ sineB4 = SineArray(duration=1, samplerate=samplerate, frequency=f_B4)
+ sineC5 = SineArray(duration=1, samplerate=samplerate, frequency=f_C5)
+
+ silence = SilenceArray(duration=0.2, samplerate=samplerate)
+
+ scale = (sineC4 + silence + sineD4 + silence + sineE4 + silence +
+ sineF4 + silence + sineG4 + silence + sineA4 + silence +
+ sineB4 + silence + sineC5)
+
+ gst_audio_encoder = 'wavenc'
+ sample_file = generate_sample_file(filename, samples_dir,
+ gst_audio_encoder,
+ sample_array=scale,
+ overwrite=overwrite)
+ samples.update({filename: sample_file})
+
+ # --------- White noise ---------
+ # white noise - 44100Hz mono
+ filename = 'white_noise_mono.wav'
+ samplerate = 44100
+ noise = WhiteNoiseArray(duration=8, samplerate=samplerate)
+ gst_audio_encoder = 'wavenc'
+ sample_file = generate_sample_file(filename, samples_dir,
+ gst_audio_encoder,
+ sample_array=noise,
+ overwrite=overwrite)
+ samples.update({filename: sample_file})
+
+ # white noise - 44100Hz stereo
+ filename = 'white_noise.wav'
+ samplerate = 44100
+ noise = WhiteNoiseArray(duration=8, samplerate=samplerate)
+ noise_stereo = noise & noise
+ gst_audio_encoder = 'wavenc'
+ sample_file = generate_sample_file(filename, samples_dir,
+ gst_audio_encoder,
+ sample_array=noise_stereo,
+ overwrite=overwrite)
+ samples.update({filename: sample_file})
+
+ # white noise - 32000Hz stereo
+ filename = 'white_noise_32000.wav'
+ samplerate = 32000
+ noise = WhiteNoiseArray(duration=8, samplerate=samplerate)
+ noise_stereo = noise & noise
+ gst_audio_encoder = 'wavenc'
+ sample_file = generate_sample_file(filename, samples_dir,
+ gst_audio_encoder,
+ sample_array=noise_stereo,
+ overwrite=overwrite)
+ samples.update({filename: sample_file})
+
+ return samples
+
+
+samples = generateSamples()
+
+
+if __name__ == '__main__':
+ generateSamples()
+++ /dev/null
-# -*- coding: utf-8 -*-
-
-# Copyright (c) 2014 Parisson SARL
-# Copyright (c) 2014 Thomas Fillon <thomas@parisson.com>
-
-# This file is part of TimeSide.
-
-# TimeSide is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 2 of the License, or
-# (at your option) any later version.
-
-# TimeSide is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-
-# You should have received a copy of the GNU General Public License
-# along with TimeSide. If not, see <http://www.gnu.org/licenses/>.
-#
-# Author : Thomas Fillon <thomas@parisson.com>
-
-import tables
-from tempfile import NamedTemporaryFile
-import numpy as np
-
-class BufferTable(object):
- def __init__(self, array_names=None):
- self._tempfile = NamedTemporaryFile(mode='w', suffix='.h5',
- prefix='ts_buf_',
- delete=True)
- self.fileh = tables.openFile(self._tempfile.name, mode='w')
-
- if not array_names:
- array_names = []
- if isinstance(array_names, list):
- self.array_names = array_names
- else:
- self.array_names = [array_names]
- for name in array_names:
- if not isinstance(name, basestring):
- raise(TypeError, 'String argument require in array_names')
-
- def __getitem__(self, name):
- return self.fileh.root.__getattr__(name)
-
- #def __set_item__(self, name, value):
- # self.fileh.root.__setattr__(name, value)
-
- def append(self, name, new_array):
- try:
- if new_array.shape:
- self.fileh.root.__getattr__(name).append(new_array[np.newaxis,
- :])
- else:
- self.fileh.root.__getattr__(name).append([new_array])
- except tables.exceptions.NoSuchNodeError:
- if name not in self.array_names:
- self.array_names.append(name)
- # The following is compatible with pytables 3 only
- #self.fileh.create_earray(where=self.fileh.root,
- # name=name,
- # obj=[new_array])
- atom = tables.Atom.from_dtype(new_array.dtype)
- dim_list = [0]
- dim_list.extend([dim for dim in new_array.shape])
- shape = tuple(dim_list)
-
- self.fileh.createEArray(where=self.fileh.root,
- name=name,
- atom=atom,
- shape=shape)
- self.append(name, new_array)
-
- def close(self):
- for name in self.array_names:
- self.fileh.removeNode(self.fileh.root, name)
- self.fileh.close()
- self._tempfile.close()
+++ /dev/null
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-#
-# Copyright (C) 2006-2010 Guillaume Pellerin
-
-# <yomguy@parisson.com>
-
-# This software is a computer program whose purpose is to stream audio
-# and video data through icecast2 servers.
-
-# This software is governed by the CeCILL license under French law and
-# abiding by the rules of distribution of free software. You can use,
-# modify and/ or redistribute the software under the terms of the CeCILL
-# license as circulated by CEA, CNRS and INRIA at the following URL
-# "http://www.cecill.info".
-
-# As a counterpart to the access to the source code and rights to copy,
-# modify and redistribute granted by the license, users are provided only
-# with a limited warranty and the software's author, the holder of the
-# economic rights, and the successive licensors have only limited
-# liability.
-
-# In this respect, the user's attention is drawn to the risks associated
-# with loading, using, modifying and/or developing or reproducing the
-# software by the user in light of its specific status of free software,
-# that may mean that it is complicated to manipulate, and that also
-# therefore means that it is reserved for developers and experienced
-# professionals having in-depth computer knowledge. Users are therefore
-# encouraged to load and test the software's suitability as regards their
-# requirements in conditions enabling the security of their systems and/or
-# data to be ensured and, more generally, to use and operate it in the
-# same conditions as regards security.
-
-# The fact that you are presently reading this means that you have had
-# knowledge of the CeCILL license and that you accept its terms.
-
-# Author: Guillaume Pellerin <yomguy@parisson.com>
-
-import os
-import xml.dom.minidom
-
-
-class Cache(object):
-
- def __init__(self, dir, params=None):
- self.dir = dir
- self.params = params
- self.files = self.get_files()
-
- def get_files(self):
- list = []
- for root, dirs, files in os.walk(self.dir):
- for file in files:
- list.append(file)
- return list
-
- def exists(self, file):
- self.files = self.get_files()
- return file in self.files
-
- def write_bin(self, data, file):
- path = self.dir + os.sep + file
- f = open(path, 'w')
- f.write(data)
- f.close()
-
- def read_bin(self, file):
- path = self.dir + os.sep + file
- f = open(path, 'r')
- data = f.read()
- f.close()
- return data
-
- def read_stream_bin(self, file):
- path = self.dir + os.sep + file
- chunk_size = 0x1000
- f = open(path, 'r')
- while True:
- _chunk = f.read(chunk_size)
- if not len(_chunk):
- break
- yield _chunk
- f.close()
-
- def write_stream_bin(self, chunk, file_object):
- file_object.write(chunk)
-
- def read_analyzer_xml(self, file):
- list = []
- path = self.dir + os.sep + file
- doc = xml.dom.minidom.parse(path)
- for data in doc.documentElement.getElementsByTagName('data'):
- name = data.getAttribute('name')
- id = data.getAttribute('id')
- unit = data.getAttribute('unit')
- value = data.getAttribute('value')
- list.append({'name': name, 'id': id, 'unit': unit, 'value': value})
- return list
-
- def write_analyzer_xml(self, data_list, file):
- path = self.dir + os.sep + file
- doc = xml.dom.minidom.Document()
- root = doc.createElement('telemeta')
- doc.appendChild(root)
- for data in data_list:
- name = data['name']
- id = data['id']
- unit = data['unit']
- value = data['value']
- node = doc.createElement('data')
- node.setAttribute('name', name)
- node.setAttribute('id', id)
- node.setAttribute('unit', unit)
- node.setAttribute('value', str(value))
- root.appendChild(node)
- f = open(path, "w")
- f.write(xml.dom.minidom.Document.toprettyxml(doc))
- f.close()
+++ /dev/null
-from numpy import getbuffer, frombuffer
-
-import pygst
-pygst.require('0.10')
-import gst
-import gobject
-gobject.threads_init()
-
-import threading
-
-
-def numpy_array_to_gst_buffer(frames, chunk_size, num_samples, sample_rate):
- from gst import Buffer
- """ gstreamer buffer to numpy array conversion """
- buf = Buffer(getbuffer(frames.astype("float32")))
- # Set its timestamp and duration
- buf.timestamp = gst.util_uint64_scale(num_samples, gst.SECOND, sample_rate)
- buf.duration = gst.util_uint64_scale(chunk_size, gst.SECOND, sample_rate)
- return buf
-
-
-def gst_buffer_to_numpy_array(buf, chan):
- """ gstreamer buffer to numpy array conversion """
- samples = frombuffer(buf.data, dtype='float32').reshape((-1, chan))
- return samples
-
-
-class MainloopThread(threading.Thread):
-
- def __init__(self, mainloop):
- threading.Thread.__init__(self)
- self.mainloop = mainloop
-
- def run(self):
- self.mainloop.run()
+++ /dev/null
-# -*- coding: utf-8 -*-
-#
-# Copyright (c) 2007-2013 Parisson SARL
-
-# This file is part of TimeSide.
-
-# TimeSide is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 2 of the License, or
-# (at your option) any later version.
-
-# TimeSide is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-
-# You should have received a copy of the GNU General Public License
-# along with TimeSide. If not, see <http://www.gnu.org/licenses/>.
-
-# Author:
-# Thomas Fillon <thomas at parisson.com>
-
-
-def dict_to_hdf5(dict_like, h5group):
- """
- Save a dictionnary-like object inside a h5 file group
- """
- # Write attributes
- attrs = dict_like.keys()
- for name in attrs:
- if dict_like[name] is not None:
- h5group.attrs[str(name)] = dict_like[name]
-
-
-def dict_from_hdf5(dict_like, h5group):
- """
- Load a dictionnary-like object from a h5 file group
- """
- # Read attributes
- for name, value in h5group.attrs.items():
- dict_like[name] = value
+++ /dev/null
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-
-import logging
-
-
-class Logger:
-
- """A logging object"""
-
- def __init__(self, file):
- self.logger = logging.getLogger('myapp')
- self.hdlr = logging.FileHandler(file)
- self.formatter = logging.Formatter(
- '%(asctime)s %(levelname)s %(message)s')
- self.hdlr.setFormatter(self.formatter)
- self.logger.addHandler(self.hdlr)
- self.logger.setLevel(logging.INFO)
-
- def write_info(self, message):
- self.logger.info(message)
-
- def write_error(self, message):
- self.logger.error(message)
+++ /dev/null
-# -*- coding: utf-8 -*-
-#
-# Copyright (c) 2013-2014 Thomas Fillon <thomas.fillon@parisson.com>
-
-# This file is part of TimeSide.
-
-# TimeSide is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 2 of the License, or
-# (at your option) any later version.
-
-# TimeSide is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-
-# You should have received a copy of the GNU General Public License
-# along with TimeSide. If not, see <http://www.gnu.org/licenses/>.
-
-# Author: Thomas Fillon <thomas.fillon@parisson.com>
-
-
-from importlib import import_module
-import warnings
-
-from ..core.exceptions import VampImportError
-
-def discover_modules(subpackage, package=None):
- import pkgutil
-
- if package:
- try:
- _pkg = import_module('.' + subpackage, package)
- except ImportError as e:
- raise e
- else:
- _pkg = import_module(subpackage)
-
- pkg_path = _pkg.__path__
- pkg_prefix = _pkg.__name__ + '.'
-
- _list = [import_module_with_exceptions(modname)
- for importer, modname, ispkg
- in pkgutil.walk_packages(pkg_path, pkg_prefix)]
-
- modules_list = [mod for mod in _list if mod is not None]
- return modules_list
-
-
-def import_module_with_exceptions(name, package=None):
- """Wrapper around importlib.import_module to import TimeSide subpackage
- and ignoring ImportError if Aubio, Yaafe and Vamp Host are not available"""
-
- from timeside.core import _WITH_AUBIO, _WITH_YAAFE, _WITH_VAMP
-
- if name.count('.server.'):
- # TODO:
- # Temporary skip all timeside.server submodules before check dependencies
- return
- try:
- import_module(name, package)
- except VampImportError:
- # No Vamp Host
- if _WITH_VAMP:
- raise VampImportError
- else:
- # Ignore Vamp ImportError
- return
- except ImportError as e:
- if str(e).count('yaafelib') and not _WITH_YAAFE:
- # Ignore Yaafe ImportError
- return
- elif str(e).count('aubio') and not _WITH_AUBIO:
- # Ignore Aubio ImportError
- return
- elif str(e).count('DJANGO_SETTINGS_MODULE'):
- # Ignore module requiring DJANGO_SETTINGS_MODULE in environnement
- return
- else:
- print (name, package)
- raise e
- return name
-
-
-# Check Availability of external Audio feature extraction librairies
-def check_aubio():
- "Check Aubio availability"
- try:
- import aubio
- except ImportError:
- warnings.warn('Aubio librairy is not available', ImportWarning,
- stacklevel=2)
- _WITH_AUBIO = False
- else:
- _WITH_AUBIO = True
- del aubio
-
- return _WITH_AUBIO
-
-
-def check_yaafe():
- "Check Aubio availability"
- try:
- import yaafelib
- except ImportError:
- warnings.warn('Yaafe librairy is not available', ImportWarning,
- stacklevel=2)
- _WITH_YAAFE = False
- else:
- _WITH_YAAFE = True
- del yaafelib
- return _WITH_YAAFE
-
-
-def check_vamp():
- "Check Vamp host availability"
- from ..core.exceptions import VampImportError
-
- try:
- from timeside.plugins.analyzer.externals import vamp_plugin
- except VampImportError:
- warnings.warn('Vamp host is not available', ImportWarning,
- stacklevel=2)
- _WITH_VAMP = False
- else:
- _WITH_VAMP = True
- del vamp_plugin
-
- return _WITH_VAMP
-
-def add_gstreamer_packages():
- import os
- import sys
- from distutils.sysconfig import get_python_lib
-
- dest_dir = get_python_lib()
-
- packages = ['gobject', 'glib', 'pygst', 'pygst.pyc', 'pygst.pth',
- 'gst-0.10', 'pygtk.pth', 'pygtk.py', 'pygtk.pyc']
-
- python_version = sys.version[:3]
- global_path = os.path.join('/usr/lib', 'python' + python_version)
- global_sitepackages = [os.path.join(global_path,
- 'dist-packages'), # for Debian-based
- os.path.join(global_path,
- 'site-packages')] # for others
-
- for package in packages:
- for pack_dir in global_sitepackages:
- src = os.path.join(pack_dir, package)
- dest = os.path.join(dest_dir, package)
- if not os.path.exists(dest) and os.path.exists(src):
- os.symlink(src, dest)
-
-
-def check_gstreamer():
- try:
- import gobject, pygst
- except ImportError:
- print 'Add Gstreamer'
- add_gstreamer_packages()
-
+++ /dev/null
-# -*- coding: utf-8 -*-
-
-#
-# Copyright (c) 2007-2014 Parisson SARL
-
-# This file is part of TimeSide.
-
-# TimeSide is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 2 of the License, or
-# (at your option) any later version.
-
-# TimeSide is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-
-# You should have received a copy of the GNU General Public License
-# along with TimeSide. If not, see <http://www.gnu.org/licenses/>.
-
-# Authors:
-# Thomas Fillon <thomas at parisson.com>
-
-
-from traits.api import HasTraits, Unicode, Int, Float, Range, Enum
-from traits.api import ListUnicode, List, Tuple
-from traits.api import TraitError
-
-import simplejson as json
-
-
-TRAIT_TYPES = {Unicode: 'str',
- Int: 'int',
- Float: 'float',
- Range: 'range',
- Enum: 'enum',
- ListUnicode: 'list of str',
- List: 'list'}
-
-
-class HasParam(object):
- """
- >>> class ParamClass(HasParam):
- ... class _Param(HasTraits):
- ... param1 = Unicode(desc='first or personal name',
- ... label='First Name')
- ... param2 = Int()
- ... param3 = Float()
- ... param4 = Range(low=0, high=10, value=3)
- >>>
- >>> p = ParamClass()
- >>> param_json = p.get_parameters()
- >>> print param_json
- {"param4": 3, "param3": 0.0, "param2": 0, "param1": ""}
- >>> new_param_json = '{"param1": "plop", "param2": 7, "param3": 0.5, \
- "param4": 8}'
- >>> p.set_parameters(new_param_json)
- >>> print p.get_parameters()
- {"param4": 8, "param3": 0.5, "param2": 7, "param1": "plop"}
- >>> v = p.param_view()
- >>> print v
- {"param4": {"default": 3, "type": "range"}, \
-"param3": {"default": 0.0, "type": "float"}, \
-"param2": {"default": 0, "type": "int"}, \
-"param1": {"default": "", "type": "str"}}
- """
- class _Param(HasTraits):
- pass
-
- def __init__(self):
- super(HasParam, self).__init__()
- self._parameters = self._Param()
-
- def __setattr__(self, name, value):
- if name is '_parameters':
- super(HasParam, self).__setattr__(name, value)
- elif name in self._parameters.trait_names():
- self._parameters.__setattr__(name, value)
- # Copy attributes as a regular attribute at class level
- _value = self._parameters.__getattribute__(name)
- super(HasParam, self).__setattr__(name, _value)
- else:
- super(HasParam, self).__setattr__(name, value)
-
- def get_parameters(self):
- list_traits = self._parameters.editable_traits()
- param_dict = self._parameters.get(list_traits)
- return json.dumps(param_dict)
-
- def set_parameters(self, parameters):
- if isinstance(parameters, basestring):
- self.set_parameters(json.loads(parameters))
- else:
- for name, value in parameters.items():
- self.__setattr__(name, value)
-
- def validate_parameters(self, parameters):
- """Validate parameters format against Traits specification
- Input can be either a dictionary or a JSON string
- Returns the validated parameters or raises a ValueError"""
-
- if isinstance(parameters, basestring):
- return self.validate_parameters(json.loads(parameters))
- # Check key against traits name
- traits_name = self._parameters.editable_traits()
- for name in parameters:
- if name not in traits_name:
- raise KeyError(name)
-
- try:
- valid_params = {name: self._parameters.validate_trait(name, value)
- for name, value in parameters.items()}
- except TraitError as e:
- raise ValueError(str(e))
-
- return valid_params
-
- def param_view(self):
- list_traits = self._parameters.editable_traits()
- view = {}
- for key in list_traits:
- trait_type = self._parameters.trait(key).trait_type.__class__
- default = self._parameters.trait(key).default
- d = {'type': TRAIT_TYPES[trait_type],
- 'default': default}
- view[key] = d
- return json.dumps(view)
-
-
-if __name__ == "__main__":
- import doctest
- doctest.testmod()
+++ /dev/null
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-"""
-Created on Tue Oct 7 09:19:37 2014
-
-@author: thomas
-"""
-
-from __future__ import division
-
-import pygst
-pygst.require("0.10")
-import gobject
-import gst
-import numpy
-import scipy.signal.waveforms
-import os.path
-
-#import timeside
-
-
-class NumpySrc:
- def __init__(self, array, samplerate):
- self.appsrc = gst.element_factory_make("appsrc")
- self.pos = 0
- self.samplerate = samplerate
- if array.ndim == 1:
- array.resize((array.shape[0], 1))
- self.length, self.channels = array.shape
- self.array = array.astype("float32")
- self.per_sample = gst.SECOND // samplerate
- self.fac = self.channels * array.dtype.itemsize
- #self.appsrc.set_property("size", (self.length * self.channels *
- # array.dtype.itemsize))
- self.appsrc.set_property("format", gst.FORMAT_TIME)
- capstr = """audio/x-raw-float,
- width=%d,
- depth=%d,
- rate=%d,
- channels=%d,
- endianness=(int)1234,
- signed=true""" % (self.array.dtype.itemsize*8,
- self.array.dtype.itemsize*8,
- self.samplerate,
- self.channels)
- self.appsrc.set_property("caps", gst.caps_from_string(capstr))
- self.appsrc.set_property("stream-type", 0) # Seekable
- self.appsrc.set_property('block', True)
-
- self.appsrc.connect("need-data", self.need_data)
- self.appsrc.connect("seek-data", self.seek_data)
- self.appsrc.connect("enough-data", self.enough_data)
-
- def need_data(self, element, length):
- #length = length // 64
- if self.pos >= self.array.shape[0]:
- element.emit("end-of-stream")
- else:
- avalaible_sample = self.length - self.pos
- if avalaible_sample < length:
- length = avalaible_sample
- array = self.array[self.pos:self.pos+length]
- buf = gst.Buffer(numpy.getbuffer(array.flatten()))
-
- buf.timestamp = self.pos * self.per_sample
- buf.duration = int(length*self.per_sample)
- element.emit("push-buffer", buf)
- self.pos += length
-
- def seek_data(self, element, npos):
- print 'seek %d' % npos
- self.pos = npos // self.per_sample
- return True
-
- def enough_data(self, element):
- print "----------- enough data ---------------"
-
-
-class SampleArray(object):
- """Base Class for generating a data sample array"""
-
- def __init__(self, duration=10, samplerate=44100):
- self.samplerate = int(samplerate)
- self.num_samples = int(numpy.ceil(duration * self.samplerate))
- self.array = NotImplemented
-
- @property
- def time_samples(self):
- return numpy.arange(0, self.num_samples)
-
- @property
- def duration(self):
- return self.num_samples / self.samplerate
-
- def __add__(self, other):
- if not self.samplerate == other.samplerate:
- raise ValueError("Samplerates mismatch")
-
- sum_ = SampleArray(samplerate=self.samplerate)
- sum_.num_samples = self.num_samples + other.num_samples
- sum_.array = numpy.vstack([self.array, other.array])
- return sum_
-
- def __and__(self, other):
- if not self.samplerate == other.samplerate:
- raise ValueError("Samplerates mismatch")
- if not self.num_samples == other.num_samples:
- raise ValueError("Number of samples mismatch")
-
- and_ = SampleArray(samplerate=self.samplerate)
- and_.num_samples = self.num_samples
- and_.array = numpy.hstack([self.array, other.array])
- return and_
-
-
-class SineArray(SampleArray):
- """Class for generating a Sine array"""
- def __init__(self, frequency=440, duration=10, samplerate=44100,
- channels=1):
- super(SineArray, self).__init__(duration=duration,
- samplerate=samplerate)
- self.frequency = frequency
- self.array = numpy.sin((2 * numpy.pi * self.frequency *
- self.time_samples / self.samplerate))
- self.array.resize(self.num_samples, 1)
-
-
-class SweepArray(SampleArray):
- """Class for generating a Sweep array"""
- def __init__(self, f0=20, f1=None, duration=10, samplerate=44100,
- method='logarithmic'):
- super(SweepArray, self).__init__(duration=duration,
- samplerate=samplerate)
-
- self.f0 = f0 / samplerate
- if f1 is None:
- self.f1 = 0.5
- else:
- self.f1 = f1 / samplerate
- self.method = method
- self.array = scipy.signal.waveforms.chirp(t=self.time_samples,
- f0=self.f0,
- t1=self.time_samples[-1],
- f1=self.f1,
- method=self.method)
- self.array.resize(self.num_samples, 1)
-
-
-class WhiteNoiseArray(SampleArray):
- """Class for generating a white noise array"""
- def __init__(self, duration=10, samplerate=44100):
- super(WhiteNoiseArray, self).__init__(duration=duration,
- samplerate=samplerate)
- array = numpy.random.randn(self.num_samples, 1)
- self.array = array / abs(array).max()
-
-
-class SilenceArray(SampleArray):
- """Class for generating a silence"""
- def __init__(self, duration=10, samplerate=44100):
- super(SilenceArray, self).__init__(duration=duration,
- samplerate=samplerate)
-
- self.array = numpy.zeros((self.num_samples, 1))
-
-
-class gst_BuildSample(object):
- def __init__(self, sample_array, output_file, gst_audio_encoder):
- if not isinstance(sample_array, SampleArray):
- raise ValueError("array must be a SampleArray subclass")
- self.sample_array = sample_array
- self.samplerate = self.sample_array.samplerate
- self.output_file = output_file
- if not isinstance(gst_audio_encoder, list):
- gst_audio_encoder = [gst_audio_encoder]
- self.gst_audio_encoder = gst_audio_encoder
-
- def run(self):
- pipeline = gst.Pipeline("pipeline")
- gobject.threads_init()
- mainloop = gobject.MainLoop()
-
- numpy_src = NumpySrc(array=self.sample_array.array,
- samplerate=self.samplerate)
-
- converter = gst.element_factory_make('audioconvert', 'converter')
-
- encoder_muxer = []
- for enc in self.gst_audio_encoder:
- encoder_muxer.append(gst.element_factory_make(enc))
-
- filesink = gst.element_factory_make('filesink', 'sink')
- filesink.set_property('location', self.output_file)
-
- pipe_elements = [numpy_src.appsrc, converter]
- pipe_elements.extend(encoder_muxer)
- pipe_elements.append(filesink)
-
- pipeline.add(*pipe_elements)
- gst.element_link_many(*pipe_elements)
-
- def _on_new_pad(self, source, pad, target_pad):
- print 'on_new_pad'
- if not pad.is_linked():
- if target_pad.is_linked():
- target_pad.get_peer().unlink(target_pad)
- pad.link(target_pad)
-
- def on_eos(bus, msg):
- pipeline.set_state(gst.STATE_NULL)
- mainloop.quit()
-
- def on_error(bus, msg):
- err, debug_info = msg.parse_error()
- print ("Error received from element %s: %s" % (msg.src.get_name(),
- err))
- print ("Debugging information: %s" % debug_info)
- mainloop.quit()
-
- pipeline.set_state(gst.STATE_PLAYING)
- bus = pipeline.get_bus()
- bus.add_signal_watch()
- bus.connect('message::eos', on_eos)
- bus.connect("message::error", on_error)
-
- mainloop.run()
-
-
-def generate_sample_file(filename, samples_dir, gst_audio_encoder,
- sample_array, overwrite=False):
- sample_file = os.path.join(samples_dir, filename)
-
- if overwrite or not os.path.exists(sample_file):
- gst_builder = gst_BuildSample(sample_array=sample_array,
- output_file=sample_file,
- gst_audio_encoder=gst_audio_encoder)
- gst_builder.run()
- return sample_file
-
-
-def generateSamples(overwrite=False):
- from timeside import __file__ as ts_file
- ts_path = os.path.split(os.path.abspath(ts_file))[0]
- tests_dir = os.path.abspath(os.path.join(ts_path, '../tests'))
- if os.path.isdir(tests_dir):
- samples_dir = os.path.abspath(os.path.join(tests_dir, 'samples'))
- if not os.path.isdir(samples_dir):
- os.makedirs(samples_dir)
- else:
- import tempfile
- samples_dir = tempfile.mkdtemp(suffix="ts_samples")
-
- samples = dict()
-
- # --------- Sweeps ---------
- # sweep 44100 mono wav
- filename = 'sweep_mono.wav'
- samplerate = 44100
- gst_audio_encoder = 'wavenc'
- sweep_mono = SweepArray(duration=8, samplerate=samplerate)
- sample_file = generate_sample_file(filename, samples_dir,
- gst_audio_encoder,
- sample_array=sweep_mono,
- overwrite=overwrite)
- samples.update({filename: sample_file})
-
- # sweep 44100 stereo wav
- sweep_stereo = sweep_mono & sweep_mono
- filename = 'sweep.wav'
- gst_audio_encoder = 'wavenc'
- sweep_mono = SweepArray(duration=8, samplerate=samplerate)
- sample_file = generate_sample_file(filename, samples_dir,
- gst_audio_encoder,
- sample_array=sweep_stereo,
- overwrite=overwrite)
- samples.update({filename: sample_file})
-
- # sweep 44100 stereo mp3
- filename = 'sweep.mp3'
- gst_audio_encoder = ['lamemp3enc', 'xingmux', 'id3v2mux']
- sweep_mono = SweepArray(duration=8, samplerate=samplerate)
- sample_file = generate_sample_file(filename, samples_dir,
- gst_audio_encoder,
- sample_array=sweep_stereo,
- overwrite=overwrite)
- samples.update({filename: sample_file})
-
- # sweep 44100 stereo flac
- filename = 'sweep.flac'
- gst_audio_encoder = 'flacenc'
- sweep_mono = SweepArray(duration=8, samplerate=samplerate)
- sample_file = generate_sample_file(filename, samples_dir,
- gst_audio_encoder,
- sample_array=sweep_stereo,
- overwrite=overwrite)
- samples.update({filename: sample_file})
-
- # sweep 44100 stereo ogg
- filename = 'sweep.ogg'
- gst_audio_encoder = ['vorbisenc', 'oggmux']
- sweep_mono = SweepArray(duration=8, samplerate=samplerate)
- sample_file = generate_sample_file(filename, samples_dir,
- gst_audio_encoder,
- sample_array=sweep_stereo,
- overwrite=overwrite)
- samples.update({filename: sample_file})
-
- # sweep 32000 stereo wav
- samplerate = 32000
- sweep_mono = SweepArray(duration=8, samplerate=samplerate)
- sweep_stereo = sweep_mono & sweep_mono
-
- filename = 'sweep_32000.wav'
- gst_audio_encoder = 'wavenc'
- sweep_mono = SweepArray(duration=8, samplerate=samplerate)
- sample_file = generate_sample_file(filename, samples_dir,
- gst_audio_encoder,
- sample_array=sweep_stereo,
- overwrite=overwrite)
- samples.update({filename: sample_file})
-
- # --------- Sines ---------
- # sine at 440Hz, 44100 mono wav
- filename = 'sine440Hz_mono.wav'
- samplerate = 44100
- gst_audio_encoder = 'wavenc'
- sweep_mono = SineArray(duration=8, samplerate=samplerate, frequency=440)
- sample_file = generate_sample_file(filename, samples_dir,
- gst_audio_encoder,
- sample_array=sweep_mono,
- overwrite=overwrite)
- samples.update({filename: sample_file})
-
- # Short 1s sine at 440Hz, 44100 mono wav
- filename = 'sine440Hz_mono_1s.wav'
- samplerate = 44100
- gst_audio_encoder = 'wavenc'
- sweep_mono = SineArray(duration=1, samplerate=samplerate, frequency=440)
- sample_file = generate_sample_file(filename, samples_dir,
- gst_audio_encoder,
- sample_array=sweep_mono,
- overwrite=overwrite)
- samples.update({filename: sample_file})
-
- # sine at 440Hz, 44100 stereo wav
- filename = 'sine440Hz.wav'
- sweep_stereo = sweep_mono & sweep_mono
- gst_audio_encoder = 'wavenc'
- sweep_mono = SineArray(duration=8, samplerate=samplerate, frequency=440)
- sample_file = generate_sample_file(filename, samples_dir,
- gst_audio_encoder,
- sample_array=sweep_stereo,
- overwrite=overwrite)
- samples.update({filename: sample_file})
-
- # sine at 440Hz, 44100 stereo mp3
- filename = 'sine440Hz.mp3'
- gst_audio_encoder = ['lamemp3enc', 'xingmux', 'id3v2mux']
- sweep_mono = SineArray(duration=8, samplerate=samplerate, frequency=440)
- sample_file = generate_sample_file(filename, samples_dir,
- gst_audio_encoder,
- sample_array=sweep_stereo,
- overwrite=overwrite)
- samples.update({filename: sample_file})
-
- # sine at 440Hz, 44100 stereo ogg
- filename = 'sine440Hz.ogg'
- gst_audio_encoder = ['vorbisenc', 'oggmux']
- sweep_mono = SineArray(duration=8, samplerate=samplerate, frequency=440)
- sample_file = generate_sample_file(filename, samples_dir,
- gst_audio_encoder,
- sample_array=sweep_stereo,
- overwrite=overwrite)
- samples.update({filename: sample_file})
-
- # --------- Equal tempered scale ---------
- filename = 'C4_scale.wav'
- samplerate = 44100
- f_C4 = 261.63
- f_D4 = 293.66
- f_E4 = 329.63
- f_F4 = 349.23
- f_G4 = 392.00
- f_A4 = 440.00
- f_B4 = 493.88
- f_C5 = 523.25
- sineC4 = SineArray(duration=1, samplerate=samplerate, frequency=f_C4)
- sineD4 = SineArray(duration=1, samplerate=samplerate, frequency=f_D4)
- sineE4 = SineArray(duration=1, samplerate=samplerate, frequency=f_E4)
- sineF4 = SineArray(duration=1, samplerate=samplerate, frequency=f_F4)
- sineG4 = SineArray(duration=1, samplerate=samplerate, frequency=f_G4)
- sineA4 = SineArray(duration=1, samplerate=samplerate, frequency=f_A4)
- sineB4 = SineArray(duration=1, samplerate=samplerate, frequency=f_B4)
- sineC5 = SineArray(duration=1, samplerate=samplerate, frequency=f_C5)
-
- silence = SilenceArray(duration=0.2, samplerate=samplerate)
-
- scale = (sineC4 + silence + sineD4 + silence + sineE4 + silence +
- sineF4 + silence + sineG4 + silence + sineA4 + silence +
- sineB4 + silence + sineC5)
-
- gst_audio_encoder = 'wavenc'
- sample_file = generate_sample_file(filename, samples_dir,
- gst_audio_encoder,
- sample_array=scale,
- overwrite=overwrite)
- samples.update({filename: sample_file})
-
- # --------- White noise ---------
- # white noise - 44100Hz mono
- filename = 'white_noise_mono.wav'
- samplerate = 44100
- noise = WhiteNoiseArray(duration=8, samplerate=samplerate)
- gst_audio_encoder = 'wavenc'
- sample_file = generate_sample_file(filename, samples_dir,
- gst_audio_encoder,
- sample_array=noise,
- overwrite=overwrite)
- samples.update({filename: sample_file})
-
- # white noise - 44100Hz stereo
- filename = 'white_noise.wav'
- samplerate = 44100
- noise = WhiteNoiseArray(duration=8, samplerate=samplerate)
- noise_stereo = noise & noise
- gst_audio_encoder = 'wavenc'
- sample_file = generate_sample_file(filename, samples_dir,
- gst_audio_encoder,
- sample_array=noise_stereo,
- overwrite=overwrite)
- samples.update({filename: sample_file})
-
- # white noise - 32000Hz stereo
- filename = 'white_noise_32000.wav'
- samplerate = 32000
- noise = WhiteNoiseArray(duration=8, samplerate=samplerate)
- noise_stereo = noise & noise
- gst_audio_encoder = 'wavenc'
- sample_file = generate_sample_file(filename, samples_dir,
- gst_audio_encoder,
- sample_array=noise_stereo,
- overwrite=overwrite)
- samples.update({filename: sample_file})
-
- return samples
-
-
-samples = generateSamples()
-
-
-if __name__ == '__main__':
- generateSamples()