]> git.parisson.com Git - timeside.git/commitdiff
Add a parents attribute to Processor. A Processor can access its parents results...
authorThomas Fillon <thomas@parisson.com>
Thu, 31 Oct 2013 16:49:01 +0000 (17:49 +0100)
committerThomas Fillon <thomas@parisson.com>
Thu, 31 Oct 2013 16:49:01 +0000 (17:49 +0100)
19 files changed:
timeside/analyzer/__init__.py
timeside/analyzer/aubio_melenergy.py
timeside/analyzer/aubio_mfcc.py
timeside/analyzer/aubio_pitch.py
timeside/analyzer/aubio_specdesc.py
timeside/analyzer/aubio_temporal.py
timeside/analyzer/core.py
timeside/analyzer/dc.py
timeside/analyzer/level.py
timeside/analyzer/odf.py [new file with mode: 0644]
timeside/analyzer/spectrogram.py
timeside/analyzer/vamp_plugin.py
timeside/analyzer/waveform.py
timeside/analyzer/yaafe.py
timeside/api.py
timeside/component.py
timeside/core.py
timeside/decoder/core.py
timeside/grapher/core.py

index 663ce6497cd7804d76f115053cfe318d97aac86e..4c293f17ede44fd6219c106a58e595ab64fb4d81 100644 (file)
@@ -13,3 +13,4 @@ from waveform import Waveform
 from vamp_plugin import VampSimpleHost
 from irit_speech_entropy import IRITSpeechEntropy
 from irit_speech_4hz import IRITSpeech4Hz
+from odf import OnsetDetectionFunction
index b353079eacd0c45eac728ffcddc155cc88f5c2b1..e8629347c065c2caf36f1c13adc4ae7e68dc0465 100644 (file)
@@ -32,6 +32,7 @@ class AubioMelEnergy(Analyzer):
     implements(IAnalyzer)
 
     def __init__(self):
+        super(AubioMelEnergy, self).__init__()
         self.input_blocksize = 1024
         self.input_stepsize = self.input_blocksize / 4
 
@@ -77,4 +78,4 @@ class AubioMelEnergy(Analyzer):
         melenergy.parameters = dict(n_filters=self.n_filters,
                                     n_coeffs=self.n_coeffs)
         melenergy.data_object.value = self.melenergy_results
-        self._results.add(melenergy)
+        self.pipe.results.add(melenergy)
index 3697b8173c2ef3a1f85b1980f5f6397e12810458..2fa8c2357a26670b2f51418c736b41a7ed591737 100644 (file)
@@ -32,6 +32,7 @@ class AubioMfcc(Analyzer):
     implements(IAnalyzer)
 
     def __init__(self):
+        super(AubioMfcc, self).__init__()
         self.input_blocksize = 1024
         self.input_stepsize = self.input_blocksize / 4
 
@@ -78,4 +79,4 @@ class AubioMfcc(Analyzer):
         mfcc.parameters = dict(n_filters=self.n_filters,
                                n_coeffs=self.n_coeffs)
         mfcc.data_object.value = self.mfcc_results
-        self._results.add(mfcc)
+        self.pipe.results.add(mfcc)
index 899d64de23fc6a2a960d3fafd56625a65e853cfb..b358bb30cbe245bbaf6c4fb9de1914cb582a5efd 100644 (file)
@@ -30,6 +30,7 @@ class AubioPitch(Analyzer):
     implements(IAnalyzer)  # TODO check if needed with inheritance
 
     def __init__(self):
+        super(AubioPitch, self).__init__()
         self.input_blocksize = 2048
         self.input_stepsize = self.input_blocksize / 2
 
@@ -78,4 +79,4 @@ class AubioPitch(Analyzer):
         # setup
 
         pitch.data_object.value = self.pitches
-        self._results.add(pitch)
+        self.pipe.results.add(pitch)
index b23d58b4ad869e9f77bd9c66713498a04698865b..ce4d04871259d7dd38e456bb7d6c11ac99deb5da 100644 (file)
@@ -31,6 +31,7 @@ class AubioSpecdesc(Analyzer):
     implements(IAnalyzer)
 
     def __init__(self):
+        super(AubioSpecdesc, self).__init__()
         self.input_blocksize = 1024
         self.input_stepsize = self.input_blocksize / 4
 
@@ -90,4 +91,4 @@ class AubioSpecdesc(Analyzer):
             res_specdesc.id_metadata.name = ' ' + method
             res_specdesc.data_object.value = self.specdesc_results[method]
 
-            self._results.add(res_specdesc)
+            self.pipe.results.add(res_specdesc)
index e6c0a9c2c20b4769e7d256ee41e1705a5010b59a..71d5f604804739d1d8dc232bc5025d5e5dffb08d 100644 (file)
@@ -32,6 +32,7 @@ class AubioTemporal(Analyzer):
     implements(IAnalyzer)
 
     def __init__(self):
+        super(AubioTemporal, self).__init__()
         self.input_blocksize = 1024
         self.input_stepsize = 256
 
@@ -96,7 +97,7 @@ class AubioTemporal(Analyzer):
         onsets.data_object.time = self.onsets
         onsets.label_metadata.label = {1: 'Onset'}
 
-        self._results.add(onsets)
+        self.pipe.results.add(onsets)
 
         #---------------------------------
         #  Onset Rate
@@ -116,7 +117,7 @@ class AubioTemporal(Analyzer):
         else:
             onsetrate.data_object.value = []
 
-        self._results.add(onsetrate)
+        self.pipe.results.add(onsetrate)
 
         #---------------------------------
         #  Beats
@@ -140,7 +141,7 @@ class AubioTemporal(Analyzer):
 
         beats.label_metadata.label = {1: 'Beat'}
 
-        self._results.add(beats)
+        self.pipe.results.add(beats)
 
         #---------------------------------
         #  BPM
@@ -163,4 +164,4 @@ class AubioTemporal(Analyzer):
         else:
             bpm.data_object.value = []
 
-        self._results.add(bpm)
+        self.pipe.results.add(bpm)
index 58a415d0288e4ec41ca59ed02bb3ef739f6a707e..d42f15c74fa7ca91c01595694e9a61054020fdd8 100644 (file)
@@ -518,7 +518,7 @@ class AnalyzerResult(MetadataObject):
         data_mode_child = root.find('data_mode')
         time_mode_child = root.find('time_mode')
         result = analyzer_result_factory(data_mode=data_mode_child.text,
-                                time_mode=time_mode_child.text)
+                                         time_mode=time_mode_child.text)
         for child in root:
             key = child.tag
             if key not in ['data_mode', 'time_mode']:
@@ -560,7 +560,6 @@ class AnalyzerResult(MetadataObject):
         return self.id_metadata.unit
 
 
-
 class ValueObject(AnalyzerResult):
 
     def __init__(self):
@@ -955,6 +954,9 @@ class Analyzer(Processor):
     Generic class for the analyzers
     '''
 
+    def __init__(self):
+        super(Analyzer, self).__init__()
+
     def setup(self, channels=None, samplerate=None,
               blocksize=None, totalframes=None):
         super(Analyzer, self).setup(channels, samplerate,
@@ -971,7 +973,7 @@ class Analyzer(Processor):
     def results(self):
 
         return AnalyzerResultContainer(
-            [self._results[key] for key in self._results.keys()
+            [self.pipe.results[key] for key in self.pipe.results.keys()
              if key.split('.')[0] == self.id()])
 
     @staticmethod
@@ -1003,7 +1005,8 @@ class Analyzer(Processor):
 
         from datetime import datetime
 
-        result = analyzer_result_factory(data_mode=data_mode, time_mode=time_mode)
+        result = analyzer_result_factory(data_mode=data_mode,
+                                         time_mode=time_mode)
 
         # Automatically write known metadata
         result.id_metadata.date = datetime.now().replace(
index 8ce3290f574fa16d3affe2f1c0905442dc516cd6..bee0a212290b524636085f312fd711c44ecf1cbb 100644 (file)
@@ -61,4 +61,4 @@ class MeanDCShift(Analyzer):
         dc_result = self.new_result(data_mode='value', time_mode='global')
         dc_result.data_object.value = numpy.round(
             numpy.mean(100 * self.values), 3)
-        self._results.add(dc_result)
+        self.pipe.results.add(dc_result)
index 29f0237a3cc09b5e69011e68ac37f06ba8f05e6f..007d526f640692a8a5ae933d05c6c3133dd7899f 100644 (file)
@@ -72,7 +72,7 @@ class Level(Analyzer):
         max_level.id_metadata.name += ' ' + "Max"
 
         max_level.data_object.value = np.round(20*np.log10(self.max_value), 3)
-        self._results.add(max_level)
+        self.pipe.results.add(max_level)
 
         # RMS level
         rms_level = self.new_result(data_mode='value', time_mode='global')
@@ -81,5 +81,5 @@ class Level(Analyzer):
 
         rms_level.data_object.value = np.round(20*np.log10(
                                 np.sqrt(np.mean(self.mean_values))), 3)
-        self._results.add(rms_level)
+        self.pipe.results.add(rms_level)
 
diff --git a/timeside/analyzer/odf.py b/timeside/analyzer/odf.py
new file mode 100644 (file)
index 0000000..06c91ae
--- /dev/null
@@ -0,0 +1,99 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2013 Paul Brossier <piem@piem.org>
+
+# 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: Paul Brossier <piem@piem.org>
+
+from timeside.core import implements, interfacedoc
+from timeside.analyzer.core import Analyzer
+from timeside.analyzer import Spectrogram
+from timeside.api import IAnalyzer
+import numpy as np
+from numpy import pi as Pi
+from scipy import signal
+
+
+class OnsetDetectionFunction(Analyzer):
+    implements(IAnalyzer)
+
+    def __init__(self, blocksize=1024, stepsize=None):
+        super(OnsetDetectionFunction, self).__init__()
+
+        self.input_blocksize = blocksize
+        if stepsize:
+            self.input_stepsize = stepsize
+        else:
+            self.input_stepsize = blocksize / 2
+
+        self.parents.append(Spectrogram(blocksize=self.input_blocksize,
+                            stepsize=self.input_stepsize))
+
+    @interfacedoc
+    def setup(self, channels=None, samplerate=None,
+              blocksize=None, totalframes=None):
+        super(OnsetDetectionFunction, self).setup(channels, samplerate,
+                                                  blocksize, totalframes)
+
+    @staticmethod
+    @interfacedoc
+    def id():
+        return "odf"
+
+    @staticmethod
+    @interfacedoc
+    def name():
+        return "Onset Detection Function"
+
+    @staticmethod
+    @interfacedoc
+    def unit():
+        return ""
+
+    def process(self, frames, eod=False):
+        return frames, eod
+
+    def post_process(self):
+
+        #spectrogram = self.parents()[0]['spectrogram_analyzer'].data
+        spectrogram = self.pipe._results['spectrogram_analyzer'].data
+        #spectrogram = self.pipe._results[self.parents()[0].id]
+
+        # Low-pass filtering of the spectrogram amplitude along the time axis
+        S = signal.lfilter(signal.hann(15), 1, abs(spectrogram), axis=0)
+        # Clip small value to a minimal threshold
+        np.maximum(S, 1e-9, out=S)
+
+        S = np.log10(S)
+        # S[S<1e-3]=0
+        np.maximum(S, 1e-3, out=S)
+
+        # Differentiator filter
+        df_filter = signal.fir_filter_design.remez(31, [0, 0.5], [Pi],
+                                                   type='differentiator')
+
+        S_diff = signal.lfilter(df_filter, 1, S, axis=0)
+        S_diff[S_diff < 1e-10] = 0
+
+        # Summation along the frequency axis
+        odf_diff = S_diff.sum(axis=1)
+        odf_diff = odf_diff / np.median(odf_diff)  # Normalize
+
+        odf = self.new_result(data_mode='value', time_mode='framewise')
+        #odf.parameters = {'FFT_SIZE': self.FFT_SIZE}
+        odf.data_object.value = odf_diff
+        self.pipe.results.add(odf)
index 0597e311407da5a3d3e06af331dccbbb8d8279aa..adc2e555336ec9d2469b8c72ac9fb8b30d66b87e 100644 (file)
@@ -27,11 +27,16 @@ import numpy as np
 
 
 class Spectrogram(Analyzer):
-    implements(IAnalyzer)  # TODO check if needed with inheritance
+    implements(IAnalyzer)
 
-    def __init__(self):
-        self.input_blocksize = 2048
-        self.input_stepsize = self.input_blocksize / 2
+    def __init__(self, blocksize=2048, stepsize=None):
+        super(Spectrogram, self).__init__()
+
+        self.input_blocksize = blocksize
+        if stepsize:
+            self.input_stepsize = stepsize
+        else:
+            self.input_stepsize = blocksize / 2
 
     @interfacedoc
     def setup(self, channels=None, samplerate=None,
@@ -67,4 +72,4 @@ class Spectrogram(Analyzer):
         spectrogram = self.new_result(data_mode='value', time_mode='framewise')
         spectrogram.parameters = {'FFT_SIZE': self.FFT_SIZE}
         spectrogram.data_object.value = self.values
-        self._results.add(spectrogram)
+        self.pipe.results.add(spectrogram)
index 057ffa5f20c4700f17cf8eccf5890ec8e9910709..68d1eb931625aa6be7aa0f3b5da7e7b952be4c9d 100644 (file)
@@ -31,7 +31,7 @@ class VampSimpleHost(Analyzer):
     implements(IAnalyzer)
 
     def __init__(self, plugin_list=None):
-
+        super(VampSimpleHost, self).__init__()
         if plugin_list is None:
             plugin_list = self.get_plugins_list()
             #plugin_list = [['vamp-example-plugins', 'percussiononsets', 'detectionfunction']]
@@ -110,7 +110,7 @@ class VampSimpleHost(Analyzer):
             plugin_res.id_metadata.name += ' ' + \
                 ' '.join(plugin_line[1:])
 
-            self._results.add(plugin_res)
+            self.pipe.results.add(plugin_res)
 
     @staticmethod
     def vamp_plugin(plugin, wavfile):
index ace57c801fae7f644b0fb9c61d76879aac6b0197..eb8b09f1c2de66860d3dc2b7aa30d6ffd6d9d9be 100644 (file)
@@ -65,4 +65,4 @@ class Waveform(Analyzer):
     def post_process(self):
         waveform = self.new_result(data_mode='value', time_mode='framewise')
         waveform.data_object.value = np.asarray(self.values).flatten()
-        self._results.add(waveform)
+        self.pipe.results.add(waveform)
index 0ad8b3a201ed605ef70054750ae18d4890e4c414..45af5172f2e97aeca55dcb84f94f89e932a5fc52 100644 (file)
@@ -35,6 +35,8 @@ class Yaafe(Analyzer):
     implements(IAnalyzer)
 
     def __init__(self, yaafeSpecification=None):
+        super(Yaafe,self).__init__()
+
         # Check arguments
         if yaafeSpecification is None:
             yaafeSpecification = FeaturePlan(sample_rate=32000)
@@ -112,4 +114,4 @@ class Yaafe(Analyzer):
             result.data_object.value = self.yaafe_engine.readOutput(featName)
             # Store results in Container
             if len(result.data_object.value):
-                self._results.add(result)
+                self.pipe.results.add(result)
index ad2b3748a1eaf43013c7006af84ecf0294ee28ac..04d86d8e17941e7f4c1c774ac3c52b37fe47dc83 100644 (file)
@@ -88,7 +88,7 @@ class IProcessor(Interface):
 
         # implementations should always call the parent method
 
-    def mediainfo():
+    def mediainfo(self):
         """
         Information about the media object
         uri
@@ -96,6 +96,13 @@ class IProcessor(Interface):
         duration
         """
 
+    @property
+    def parents(self):
+        """
+        Return the processor's parents
+        """
+
+
 class IEncoder(IProcessor):
     """Encoder driver interface. Each encoder is expected to support a specific
     format."""
index 25ddec6e922138ade7f0b3e475b83a7126c94f7e..d5daa3fb767c133d1978a6dec6c8c028528f6051 100644 (file)
@@ -19,7 +19,7 @@
 
 
 # This file defines a generic object interface mechanism and
-# a way to determine which components implements a given interface.
+# a way to determine which components implements a given interface.
 #
 # For example, the following defines the Music class as implementing the
 # listenable interface.
index 06323cbbd95dbf7b4232251ba49555404e5969a5..ef7f4eae32a025bdad98cd3d4453e8cf7f84d887 100644 (file)
@@ -62,12 +62,26 @@ class MetaProcessor(MetaComponent):
 
 
 class Processor(Component):
-    """Base component class of all processors"""
+    """Base component class of all processors
+
+
+    Attributes:
+              parents :  List of parent Processors that must be processed
+                         before the current Processor
+              pipe :     The current ProcessPipe in which the Processor will run
+        """
     __metaclass__ = MetaProcessor
 
     abstract()
     implements(IProcessor)
 
+    def __init__(self):
+        super(Processor, self).__init__()
+
+        self.parents = []
+        self.source_mediainfo = None
+        self.pipe = None
+
     @interfacedoc
     def setup(self, channels=None, samplerate=None, blocksize=None,
               totalframes=None):
@@ -204,23 +218,35 @@ def get_processor(processor_id):
     """Return a processor by its id"""
     if not _processors.has_key(processor_id):
         raise Error("No processor registered with id: '%s'"
-              % processor_id)
+                      % processor_id)
 
     return _processors[processor_id]
 
 
 class ProcessPipe(object):
-    """Handle a pipe of processors"""
+    """Handle a pipe of processors
+
+    Attributes:
+        processor: List of all processors in the Process pipe
+        results : Results Container for all the analyzers of the Pipe process
+"""
 
     def __init__(self, *others):
         self.processors = []
         self |= others
 
+        from timeside.analyzer.core import AnalyzerResultContainer
+        self.results = AnalyzerResultContainer()
+
+        for proc in self.processors:
+            proc.pipe = self
+
     def __or__(self, other):
         return ProcessPipe(self, other)
 
     def __ior__(self, other):
         if isinstance(other, Processor):
+            self |= other.parents
             self.processors.append(other)
         elif isinstance(other, ProcessPipe):
             self.processors.extend(other.processors)
@@ -248,22 +274,18 @@ class ProcessPipe(object):
         the pipe. Also returns the pipe itself."""
 
         source = self.processors[0]
-        items  = self.processors[1:]
+        items = self.processors[1:]
         source.setup()
 
         last = source
 
-        from timeside.analyzer.core import AnalyzerResultContainer
-        self._results = AnalyzerResultContainer()
-
         # setup/reset processors and configure properties throughout the pipe
         for item in items:
-            item.setup(channels = last.channels(),
-                       samplerate = last.samplerate(),
-                       blocksize = last.blocksize(),
-                       totalframes = last.totalframes())
+            item.setup(channels=last.channels(),
+                       samplerate=last.samplerate(),
+                       blocksize=last.blocksize(),
+                       totalframes=last.totalframes())
             item.source_mediainfo = source.mediainfo()
-            item._results = self._results
             last = item
 
         # now stream audio data along the pipe
@@ -279,10 +301,3 @@ class ProcessPipe(object):
         for item in items:
             item.release()
 
-    @property
-    def results(self):
-        """
-        Results Container for all the analyzers of the Pipe process
-        """
-        if hasattr(self, '_results'):
-            return self._results
\ No newline at end of file
index bb7af5e47297589bc021f4556bf32d1d06de518a..d4cff1cf34357e6aaa22e20d103b40cf83e04f31 100644 (file)
@@ -69,6 +69,7 @@ class FileDecoder(Processor):
             duration : float
                 duration of the segment in seconds
         '''
+        super(FileDecoder, self).__init__()
 
         # is this a file?
         import os.path
index 38c5f8164709058081df8eef998d389efc0ca0d0..822fa2170e3e39605aa119bfe9d3eba6a9bb19e7 100644 (file)
@@ -106,6 +106,7 @@ class Grapher(Processor):
     lower_freq = 20
 
     def __init__(self, width=1024, height=256, bg_color=None, color_scheme='default'):
+        super(Grapher, self).__init__()
         self.bg_color = bg_color
         self.color_scheme = color_scheme
         self.graph = None