]> git.parisson.com Git - timeside.git/commitdiff
core: add processor buffersize (default 65536), required to be a power of 2 and ...
authorOlivier Guilyardi <olivier@samalyse.com>
Fri, 27 Nov 2009 19:51:36 +0000 (19:51 +0000)
committerOlivier Guilyardi <olivier@samalyse.com>
Fri, 27 Nov 2009 19:51:36 +0000 (19:51 +0000)
for proper support of fft-based analyzers and similar
refactor api:
- improve documentation
- remove IEncoder.finish(), same as process() with nframes < buffersize
- remove format(), description() and such from IDecoder, a decoder can handle multiple
  formats so this information is dynamic
- add duration(), samplerate(), channels() etc... to IDecoder, obsoleting all
  corresponding analyzers
- add IGrapher.process()
- require IGrapher.render() to return a PIL image instead of streaming binary data,
  and move the width/height arguments into the constructor
- add IAnalyzer.process()
- add IValueAnalyzer and let dc, maxlevel and meanlevel analyzers implement it
- move result() into IValueAnalyzer. This method may not be adequate for other types of analyzers

analyze/dc.py
analyze/max_level.py
analyze/mean_level.py
analyze/vamp/core.py
api.py
core.py
graph/spectrogram_audiolab.py
graph/waveform_audiolab.py

index 43cfdb5dbee8a7da2dd3675c4fe32bc976f278a5..ac31e6f7dfd5b85911ee334298e496d6dcfd94ec 100644 (file)
 # Author: Guillaume Pellerin <yomguy@parisson.com>
 
 from timeside.analyze.core import *
-from timeside.api import IAnalyzer
+from timeside.api import IValueAnalyzer
 import numpy
 
 class MeanDCShiftAnalyser(AudioProcessor):
     """Media item analyzer driver interface"""
 
-    implements(IAnalyzer)
+    implements(IValueAnalyzer)
 
     @staticmethod
     def id():
index fa173fbd3076a2a1b06e6c1e804cd13bc91eee4f..c40ae4d2083a7086dfdf5f6518b1049c73b8d505 100644 (file)
 # Author: Guillaume Pellerin <yomguy@parisson.com>
 
 from timeside.analyze.core import *
-from timeside.api import IAnalyzer
+from timeside.api import IValueAnalyzer
 import numpy
 
 class MaxLevelAnalyzer(AudioProcessor):
     """Media item analyzer driver interface"""
 
-    implements(IAnalyzer)
+    implements(IValueAnalyzer)
 
     @staticmethod
     def id():
index 7f18f37ce9d092e436b3ce62d837a499a0febe7c..0ab38ff773235ddcd2bd9044e2c5845b740cff85 100644 (file)
 # Author: Guillaume Pellerin <yomguy@parisson.com>
 
 from timeside.analyze.core import *
-from timeside.api import IAnalyzer
+from timeside.api import IValueAnalyzer
 import numpy
 
 class MeanLevelAnalyser(AudioProcessor):
     """Media item analyzer driver interface"""
 
-    implements(IAnalyzer)
+    implements(IValueAnalyzer)
 
     @staticmethod
     def id():
index fb0a3f440f25252e99481c5d02ba04facb573ee3..6248508bb4e5f0ae88a15e4b52b2df61c96d6954 100644 (file)
@@ -21,7 +21,6 @@
 
 from timeside.core import *
 from tempfile import NamedTemporaryFile
-from timeside.api import IAnalyzer
 from timeside.exceptions import SubProcessError
 import os
 import random
diff --git a/api.py b/api.py
index 344871e4276c4b3d0f9ff903458c7c1612112248..76746793c10d3723d67f637276d7b7d68993705d 100644 (file)
--- a/api.py
+++ b/api.py
@@ -35,7 +35,8 @@ class IProcessor(Interface):
         and be passed as a GET parameter. Thus it should be as short as possible."""
 
 class IEncoder(IProcessor):
-    """Encoder driver interface"""
+    """Encoder driver interface. Each encoder is expected to support a specific
+    format."""
 
     def __init__(self, output, nchannels, samplerate):
         """The constructor must always accept the output, nchannels and samplerate 
@@ -79,83 +80,105 @@ class IEncoder(IProcessor):
            mode."""
 
     def process(self, frames):
-        """Encode the frames passed as a numpy array, where columns are channels.
+        """Encode buffersize frames passed as a numpy array, where columns are channels.
         
+           A number of frames less than buffersize means that the end of the data
+           has been reached, and that the encoder should close the output file, stream,
+           etc...
+
            In streaming mode the callback passed to the constructor is called whenever
            a block of encoded data is ready."""
 
-    def finish(self):
-        """Flush the encoded data and close the output file/stream. Calling this method
-        may cause the streaming callback to be called if in streaming mode."""
-  
-
 class IDecoder(IProcessor):
-    """Decoder driver interface"""
+    """Decoder driver interface. Decoders are different of encoders in that
+    a given driver may support several input formats, hence this interface doesn't
+    export any static method, all informations are dynamic."""
 
-    @staticmethod
-    def format():
-        """Return the decode/encoding format as a short string 
-        Example: "MP3", "OGG", "AVI", ...
-        """
-   
-    @staticmethod
-    def description():
-        """Return a string describing what this decode format provides, is good 
-        for, etc... The description is meant to help the end user decide what 
-        format is good for him/her
-        """
+    def __init__(self, filename):
+        """Create a new decoder for filename. Implementations of this interface 
+        may accept optionnal arguments after filename."""
 
-    @staticmethod
-    def file_extension():
-        """Return the filename extension corresponding to this decode format"""
+    def channels():
+        """Return the number of channels"""
 
-    @staticmethod
-    def mime_type():
-        """Return the mime type corresponding to this decode format"""
+    def samplerate():
+        """Return the samplerate"""
 
-    def process(self, source, options=None):
-        """Perform the decoding process and stream the result through a generator
+    def duration():
+        """Return the duration in seconds"""
 
-        source is the audio/video source file absolute path.
+    def format():
+        """Return a user-friendly file format string"""
+   
+    def encoding():
+        """Return a user-friendly encoding string"""
 
-        It is highly recommended that decode drivers implement some sort of
-        cache instead of re-encoding each time process() is called.
+    def resolution():
+        """Return the sample depth"""
 
-        It should be possible to make subsequent calls to process() with
-        different items, using the same driver instance.
-        """
+    def process(self):
+        """Return a generator over the decoded data, as numpy arrays, where columns are
+        channels, each array containing buffersize frames or less if the end of file
+        has been reached."""
 
 class IGrapher(IProcessor):
     """Media item visualizer driver interface"""
 
+    def __init__(self, width, height):
+        """Create a new grapher. Implementations of this interface 
+        may accept optionnal arguments. width and height are generally
+        in pixels but could be something else for eg. svg rendering, etc.."""
+
     @staticmethod
     def name():
         """Return the graph name, such as "Waveform", "Spectral view",
-        etc..
-        """
+        etc..  """
 
     def set_colors(self, background=None, scheme=None):
         """Set the colors used for image generation. background is a RGB tuple,
         and scheme a a predefined color theme name"""
         pass
 
-    def render(self, media_item, width=None, height=None, options=None):
-        """Generator that streams the graph output as a PNG image"""
+    def process(self, frames):
+        """Process a block of buffersize frames passed as a numpy array, where 
+        columns are channels. Passing less than buffersize frames means that
+        the end of data has been reached"""
+
+    def render(self):
+        """Return a PIL Image object visually representing all of the data passed
+        by repeatedly calling process()"""
 
 class IAnalyzer(IProcessor):
-    """Media item analyzer driver interface"""
+    """Media item analyzer driver interface. This interface is abstract, it doesn't
+    describe a particular type of analyzer but is rather meant to group analyzers.
+    In particular, the way the result is returned may greatly vary from sub-interface
+    to sub-interface. For example the IValueAnalyzer returns a final single numeric
+    result at the end of the whole analysis. But some other analyzers may return
+    numpy arrays, and this, either at the end of the analysis, or from process()
+    for each block of data (as in Vamp)."""
+
+    def __init__(self):
+        """Create a new analyzer. Implementations of this interface 
+        may accept optionnal arguments."""
 
     @staticmethod
     def name():
         """Return the analyzer name, such as "Mean Level", "Max level",
-        "Total length, etc..
-        """
+        "Total length, etc..  """
 
     @staticmethod
     def unit():
-        """Return the unit of the data such as "dB", "seconds", etc...
-        """
+        """Return the unit of the data such as "dB", "seconds", etc...  """
+
+    def process(self, frames):
+        """Process a block of buffersize frames passed as a numpy array, where 
+        columns are channels. Passing less than buffersize frames means that
+        the end of data has been reached"""
+
+class IValueAnalyzer(IAnalyzer):
+    """Interface for analyzers which return a single numeric value from result()"""
 
-    def render(self, media, options=None):
-        """Return the result data of the process"""
+    def result():
+        """Return the final result of the analysis performed over the data passed by
+        repeatedly calling process()"""
 
diff --git a/core.py b/core.py
index f2c87b76c2e1c1f396112edda95701b9b933f751..c7a26c97256642b9d993a3d38c1356b454ca5358 100644 (file)
--- a/core.py
+++ b/core.py
@@ -52,6 +52,32 @@ class Processor(Component):
     """Base component class of all processors"""
     __metaclass__ = MetaProcessor
 
+    DEFAULT_BUFFERSIZE = 0x10000
+    MIN_BUFFERSIZE = 0x1000
+
+    __buffersize = DEFAULT_BUFFERSIZE
+
+    def buffersize(self):
+        """Get the current buffer size"""
+        return __buffersize
+
+    def set_buffersize(self, value):
+        """Set the buffer size used by this processor. The buffersize must be a 
+        power of 2 and greater than or equal to MIN_BUFFERSIZE or an exception will 
+        be raised."""
+        if value < self.MIN_BUFFERSIZE:
+            raise Error("Invalid buffer size: %d. Must be greater than or equal to %d",
+                        value, MIN_BUFFERSIZE);
+        v = value                        
+        while v > 1:
+            if v & 1:
+                raise Error("Invalid buffer size: %d. Must be a power of two",
+                            value, MIN_BUFFERSIZE);
+            v >>= 1                            
+
+        self.__buffersize = value
+
+
 def processors(interface=IProcessor, recurse=True):
     """Returns the processors implementing a given interface and, if recurse,
     any of the descendants of this interface."""
index 709d8d387f0d74bb291ea1ef123ab13b9b9b41f3..59c9c1dc44a1afbe920bdd1387ddf7c92fabb99e 100644 (file)
@@ -34,7 +34,6 @@ class SpectrogramGrapherAudiolab(Processor):
 
     @staticmethod
     def id():
-        #FIXME: too long + underscore discouraged
         return "spectrogram"
 
     def name(self):
index b11888c8418eb5a8d81ac786806d151fe4531930..513ec97d0c6d60023f343c479658465fa2a7fd09 100644 (file)
@@ -34,7 +34,6 @@ class WaveFormGrapherAudiolab(Processor):
 
     @staticmethod
     def id():
-        #FIXME: too long + underscore discouraged
         return "waveform"
 
     def name(self):