]> git.parisson.com Git - timeside.git/commitdiff
- add a spectrogram example
authoryomguy <yomguy@parisson.com>
Tue, 2 Mar 2010 16:18:28 +0000 (16:18 +0000)
committeryomguy <yomguy@parisson.com>
Tue, 2 Mar 2010 16:18:28 +0000 (16:18 +0000)
grapher/core.py
tests/api/examples.py
tests/api/test_pipe_spectrogram.py [new file with mode: 0644]

index fc277c67d6be8b7f8e8cc2cf68f9c800b9aba913..a4cd641b8f5918dd00dd749a5c79c2706bda2cf8 100644 (file)
@@ -250,23 +250,26 @@ class WaveformImage(object):
 
 
 class SpectrogramImage(object):
-    def __init__(self, image_width, image_height, fft_size, bg_color = None, color_scheme = None):
-
-        #FIXME: bg_color is ignored
-
-        if not color_scheme:
-            color_scheme = 'default'
-
-        self.image = Image.new("P", (image_height, image_width))
+    def __init__(self, image_width, image_height, nframes, samplerate, buffer_size, bg_color=None, color_scheme=None, filename=None):
 
         self.image_width = image_width
         self.image_height = image_height
-        self.fft_size = fft_size
-
+        self.nframes = nframes
+        self.samplerate = samplerate
+        self.fft_size = buffer_size
+        self.buffer_size = buffer_size
+        self.filename = filename
+        if not color_scheme:
+            color_scheme = 'default'
+        self.image = Image.new("P", (self.image_height, self.image_width))
         colors = color_schemes[color_scheme]['spectrogram']
-
         self.image.putpalette(interpolate_colors(colors, True))
 
+        self.samples_per_pixel = self.nframes / float(self.image_width)
+        self.peaks_adapter = FixedSizeInputAdapter(int(self.samples_per_pixel), 1, pad=False)
+        self.peaks_adapter_nframes = self.peaks_adapter.nframes(self.nframes)
+        self.spectral_centroid = SpectralCentroid(self.fft_size, self.nframes, self.samplerate, numpy.hanning)
+
         # generate the lookup which translates y-coordinate to fft-bin
         self.y_to_bin = []
         f_min = 100.0
@@ -286,6 +289,7 @@ class SpectrogramImage(object):
         # a lot slower than using image.putadata and then rotating the image
         # so we store all the pixels in an array and then create the image when saving
         self.pixels = []
+        self.pixel_cursor = 0
 
     def draw_spectrum(self, x, spectrum):
         for (index, alpha) in self.y_to_bin:
@@ -294,34 +298,25 @@ class SpectrogramImage(object):
         for y in range(len(self.y_to_bin), self.image_height):
             self.pixels.append(0)
 
-    def save(self, filename):
-        self.image.putdata(self.pixels)
-        self.image.transpose(Image.ROTATE_90).save(filename)
-
-
-def create_spectrogram_png(input_filename, output_filename_s, image_width, image_height, fft_size,
-                           bg_color = None, color_scheme = None):
-    audio_file = audiolab.sndfile(input_filename, 'read')
-
-    samples_per_pixel = audio_file.get_nframes() / float(image_width)
-    processor = AudioProcessor(audio_file, fft_size, numpy.hanning)
-
-    spectrogram = SpectrogramImage(image_width, image_height, fft_size, bg_color, color_scheme)
-
-    for x in range(image_width):
-
-        if x % (image_width/10) == 0:
-            sys.stdout.write('.')
-            sys.stdout.flush()
-
-        seek_point = int(x * samples_per_pixel)
-        next_seek_point = int((x + 1) * samples_per_pixel)
-        (spectral_centroid, db_spectrum) = processor.spectral_centroid(seek_point)
-        spectrogram.draw_spectrum(x, db_spectrum)
+    def process(self, frames, eod):
+        if len(frames) == 1:
+            frames.shape = (len(frames),1)
+            buffer = frames.copy()
+        else:
+            buffer = frames[:,0].copy()
+            buffer.shape = (len(buffer),1)
 
-    spectrogram.save(output_filename_s)
+        for samples, end in self.peaks_adapter.process(buffer, eod):
+            if self.pixel_cursor == self.image_width:
+                break
+            # FIXME : too much repeated spectrum computation cycle
+            (spectral_centroid, db_spectrum) = self.spectral_centroid.process(buffer, True)
+            self.draw_spectrum(self.pixel_cursor, db_spectrum)
+            self.pixel_cursor += 1
 
-    print " done"
+    def save(self):
+        self.image.putdata(self.pixels)
+        self.image.transpose(Image.ROTATE_90).save(self.filename)
 
 
 class Noise(object):
index 506b2092691d14acf3bbf2ac9bd745e93b4c3b70..3a9f582d5d19043472fbb70e78a5639d9a35095c 100644 (file)
@@ -264,6 +264,63 @@ class Waveform(Processor):
         return self.graph.image
 
 
+class Spectrogram(Processor):
+    implements(IGrapher)
+
+    BUFFER_SIZE = 512
+
+    @interfacedoc
+    def __init__(self, width=None, height=None, output=None, bg_color=None, color_scheme=None):
+        if width:
+            self.width = width
+        else:
+            self.width = 1500
+        if height:
+            self.height = height
+        else:
+            self.height = 200
+        self.bg_color = bg_color
+        self.color_scheme = color_scheme
+        self.filename = output
+        self.graph = None
+
+    @staticmethod
+    @interfacedoc
+    def id():
+        return "test_spectrogram"
+
+    @staticmethod
+    @interfacedoc
+    def name():
+        return "Spectrogram test"
+
+    @interfacedoc
+    def set_colors(self, background, scheme):
+        self.bg_color = background
+        self.color_scheme = scheme
+
+    @interfacedoc
+    def setup(self, channels=None, samplerate=None, nframes=None):
+        super(Spectrogram, self).setup(channels, samplerate, nframes)
+        if self.graph:
+            self.graph = None
+        self.adapter = FixedSizeInputAdapter(self.BUFFER_SIZE, channels, pad=True)
+        self.graph = SpectrogramImage(self.width, self.height, self.nframes(), self.samplerate(), self.BUFFER_SIZE,
+                                    bg_color=self.bg_color, color_scheme=self.color_scheme, filename=self.filename)
+
+    @interfacedoc
+    def process(self, frames, eod=False):
+        for buffer, end in self.adapter.process(frames, eod):
+            self.graph.process(buffer, end)
+        return frames, eod
+
+    @interfacedoc
+    def render(self):
+        if self.filename:
+            self.graph.save()
+        return self.graph.image
+
+
 class Duration(Processor):
     """A rather useless duration analyzer. Its only purpose is to test the
        nframes characteristic."""
diff --git a/tests/api/test_pipe_spectrogram.py b/tests/api/test_pipe_spectrogram.py
new file mode 100644 (file)
index 0000000..742e4c1
--- /dev/null
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+from timeside.tests.api import examples
+from timeside.core import *
+from timeside.api import *
+import os.path
+
+use_gst = 1
+if use_gst:
+    from timeside.tests.api.gstreamer import FileDecoder, WavEncoder
+else:
+    from timeside.tests.api.examples import FileDecoder, WavEncoder
+
+image_file = './spectrogram.png'
+source = os.path.join(os.path.dirname(__file__), "../samples/sweep_source.mp3")
+
+decoder  = FileDecoder(source)
+spectrogram = examples.Spectrogram(width=1024, height=256, output=image_file, bg_color=(0,0,0), color_scheme='default')
+
+(decoder | spectrogram).run()
+
+print 'frames per pixel = ', spectrogram.graph.samples_per_pixel
+print "render spectrogram to: %s" % image_file
+spectrogram.render()
+
+