From: Thomas Fillon Date: Wed, 14 Jan 2015 12:16:14 +0000 (+0100) Subject: Refactoring: move tools to timeside.core X-Git-Tag: 0.7^2~29 X-Git-Url: https://git.parisson.com/?a=commitdiff_plain;h=d3554677eda1d6d99c949a84d221b692671f6da9;p=timeside.git Refactoring: move tools to timeside.core --- diff --git a/timeside/core/tools/__init__.py b/timeside/core/tools/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/timeside/core/tools/buffering.py b/timeside/core/tools/buffering.py new file mode 100644 index 0000000..bcc4e0e --- /dev/null +++ b/timeside/core/tools/buffering.py @@ -0,0 +1,79 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2014 Parisson SARL +# Copyright (c) 2014 Thomas Fillon + +# 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 . +# +# Author : Thomas Fillon + +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() diff --git a/timeside/core/tools/cache.py b/timeside/core/tools/cache.py new file mode 100644 index 0000000..592524a --- /dev/null +++ b/timeside/core/tools/cache.py @@ -0,0 +1,118 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (C) 2006-2010 Guillaume Pellerin + +# + +# 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 + +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() diff --git a/timeside/core/tools/gstutils.py b/timeside/core/tools/gstutils.py new file mode 100644 index 0000000..6d06a3e --- /dev/null +++ b/timeside/core/tools/gstutils.py @@ -0,0 +1,35 @@ +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() diff --git a/timeside/core/tools/hdf5.py b/timeside/core/tools/hdf5.py new file mode 100644 index 0000000..2db446e --- /dev/null +++ b/timeside/core/tools/hdf5.py @@ -0,0 +1,41 @@ +# -*- 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 . + +# Author: +# Thomas Fillon + + +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 diff --git a/timeside/core/tools/logger.py b/timeside/core/tools/logger.py new file mode 100644 index 0000000..1405627 --- /dev/null +++ b/timeside/core/tools/logger.py @@ -0,0 +1,24 @@ +#!/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) diff --git a/timeside/core/tools/package.py b/timeside/core/tools/package.py new file mode 100644 index 0000000..a31b887 --- /dev/null +++ b/timeside/core/tools/package.py @@ -0,0 +1,162 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) 2013-2014 Thomas Fillon + +# 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 . + +# Author: Thomas Fillon + + +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() + diff --git a/timeside/core/tools/parameters.py b/timeside/core/tools/parameters.py new file mode 100644 index 0000000..3101abb --- /dev/null +++ b/timeside/core/tools/parameters.py @@ -0,0 +1,132 @@ +# -*- 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 . + +# Authors: +# Thomas Fillon + + +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() diff --git a/timeside/core/tools/test_samples.py b/timeside/core/tools/test_samples.py new file mode 100644 index 0000000..ca202b1 --- /dev/null +++ b/timeside/core/tools/test_samples.py @@ -0,0 +1,452 @@ +#!/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() diff --git a/timeside/tools/__init__.py b/timeside/tools/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/timeside/tools/buffering.py b/timeside/tools/buffering.py deleted file mode 100644 index bcc4e0e..0000000 --- a/timeside/tools/buffering.py +++ /dev/null @@ -1,79 +0,0 @@ -# -*- coding: utf-8 -*- - -# Copyright (c) 2014 Parisson SARL -# Copyright (c) 2014 Thomas Fillon - -# 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 . -# -# Author : Thomas Fillon - -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() diff --git a/timeside/tools/cache.py b/timeside/tools/cache.py deleted file mode 100644 index 592524a..0000000 --- a/timeside/tools/cache.py +++ /dev/null @@ -1,118 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- -# -# Copyright (C) 2006-2010 Guillaume Pellerin - -# - -# 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 - -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() diff --git a/timeside/tools/gstutils.py b/timeside/tools/gstutils.py deleted file mode 100644 index 6d06a3e..0000000 --- a/timeside/tools/gstutils.py +++ /dev/null @@ -1,35 +0,0 @@ -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() diff --git a/timeside/tools/hdf5.py b/timeside/tools/hdf5.py deleted file mode 100644 index 2db446e..0000000 --- a/timeside/tools/hdf5.py +++ /dev/null @@ -1,41 +0,0 @@ -# -*- 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 . - -# Author: -# Thomas Fillon - - -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 diff --git a/timeside/tools/logger.py b/timeside/tools/logger.py deleted file mode 100644 index 1405627..0000000 --- a/timeside/tools/logger.py +++ /dev/null @@ -1,24 +0,0 @@ -#!/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) diff --git a/timeside/tools/package.py b/timeside/tools/package.py deleted file mode 100644 index a31b887..0000000 --- a/timeside/tools/package.py +++ /dev/null @@ -1,162 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (c) 2013-2014 Thomas Fillon - -# 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 . - -# Author: Thomas Fillon - - -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() - diff --git a/timeside/tools/parameters.py b/timeside/tools/parameters.py deleted file mode 100644 index 3101abb..0000000 --- a/timeside/tools/parameters.py +++ /dev/null @@ -1,132 +0,0 @@ -# -*- 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 . - -# Authors: -# Thomas Fillon - - -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() diff --git a/timeside/tools/test_samples.py b/timeside/tools/test_samples.py deleted file mode 100644 index ca202b1..0000000 --- a/timeside/tools/test_samples.py +++ /dev/null @@ -1,452 +0,0 @@ -#!/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()