]> git.parisson.com Git - telemeta.git/commitdiff
- Added Waveform2 and Spectrogram2 visualization components
authoryomguy <>
Sun, 27 May 2007 16:58:35 +0000 (16:58 +0000)
committeryomguy <>
Sun, 27 May 2007 16:58:35 +0000 (16:58 +0000)
- Clean up

12 files changed:
telemeta/export/core.py
telemeta/visualization/__init__.py
telemeta/visualization/octave/jet.m [new file with mode: 0644]
telemeta/visualization/octave/spectrogram.m [new file with mode: 0644]
telemeta/visualization/octave/spectrogram2img.m [new file with mode: 0644]
telemeta/visualization/octave/waveform2img.m [new file with mode: 0644]
telemeta/visualization/octave_core.py [new file with mode: 0644]
telemeta/visualization/snack_core.py
telemeta/visualization/spectrogram.py
telemeta/visualization/spectrogram2.py [new file with mode: 0644]
telemeta/visualization/waveform.py
telemeta/visualization/waveform2.py [new file with mode: 0644]

index d01139e8e623eccce54dd1400318c4f93f6d89f9..9fd7658eba96f58c5a90ead22e67a8e71f542172 100644 (file)
@@ -112,11 +112,6 @@ class ExporterCore(Component):
         #self.artist = self.metadata['Artist']
         #self.title = self.metadata['Title']
 
-        # Decode the source if needed
-        #if os.path.exists(self.source) and not iswav16(self.source):
-            # TO FIX !
-        #    self.source = self.export.decode()
-
         # Normalize if demanded
         if not options is None:
             self.options = options
@@ -124,15 +119,8 @@ class ExporterCore(Component):
                 self.options['normalize'] == True:
                 self.normalize()
 
-        # Define the cache directory
+        # Define the export directory
         self.ext = self.get_file_extension()
-
-        # Define and create the destination path
-        # At the moment, the target directory is built with this scheme in
-        # the cache directory : ./%Format/%Collection/%Artist/
-        #self.cache_dir = os.path.join(self.cache_dir,'cache')
-
-        #export_dir = os.path.join(self.ext,self.collection,self.artist)
         export_dir = os.path.join(self.cache_dir,self.ext)
 
         if not os.path.exists(export_dir):
@@ -146,7 +134,6 @@ class ExporterCore(Component):
             path = export_dir
 
         # Set the target file
-        #target_file = file_name_wo_ext+'.'+self.ext
         target_file = self.item_id+'.'+self.ext
         dest = os.path.join(path,target_file)
         return dest
@@ -179,7 +166,6 @@ class ExporterCore(Component):
             yield __chunk
             file_out.write(__chunk)
 
-        #file_in.close()
         file_out.close()
 
     def post_process(self, item_id, source, metadata, ext, 
index 289a4f29c18165f3496d312ebd7f4346698908ea..4fe60935c75750b466579e968b8fe0887851ec88 100644 (file)
@@ -1,3 +1,5 @@
 from telemeta.visualization.api import *
 from telemeta.visualization.waveform import *
 from telemeta.visualization.spectrogram import *
+from telemeta.visualization.spectrogram2 import *
+from telemeta.visualization.waveform2 import *
\ No newline at end of file
diff --git a/telemeta/visualization/octave/jet.m b/telemeta/visualization/octave/jet.m
new file mode 100644 (file)
index 0000000..ba9f742
--- /dev/null
@@ -0,0 +1,29 @@
+function J = jet(m)
+%JET    Variant of HSV
+%   JET(M), a variant of HSV(M), is an M-by-3 matrix containing
+%   the default colormap used by CONTOUR, SURF and PCOLOR.
+%   The colors begin with dark blue, range through shades of
+%   blue, cyan, green, yellow and red, and end with dark red.
+%   JET, by itself, is the same length as the current figure's
+%   colormap. If no figure exists, MATLAB creates one.
+%
+%   See also HSV, HOT, PINK, FLAG, COLORMAP, RGBPLOT.
+
+%   Copyright 1984-2004 The MathWorks, Inc.
+%   $Revision: 5.7.4.2 $  $Date: 2005/06/21 19:31:40 $
+
+if nargin < 1
+   m = size(get(gcf,'colormap'),1);
+end
+n = ceil(m/4);
+u = [(1:1:n)/n ones(1,n-1) (n:-1:1)/n]';
+g = ceil(n/2) - (mod(m,4)==1) + (1:length(u))';
+r = g + n;
+b = g - n;
+g(g>m) = [];
+r(r>m) = [];
+b(b<1) = [];
+J = zeros(m,3);
+J(r,1) = u(1:length(r));
+J(g,2) = u(1:length(g));
+J(b,3) = u(end-length(b)+1:end);
\ No newline at end of file
diff --git a/telemeta/visualization/octave/spectrogram.m b/telemeta/visualization/octave/spectrogram.m
new file mode 100644 (file)
index 0000000..3b9e071
--- /dev/null
@@ -0,0 +1,198 @@
+## Copyright (C) 2000 Paul Kienzle
+##
+## This program is free software and may be used for any purpose.  This
+## copyright notice must be maintained. Paul Kienzle is not responsible
+## for the consequences of using this software.
+
+## usage: [S, f, t] = spectrogram(x, Fs, window, step, maxF, shape, minE)
+##
+## Generate a spectrogram for the signal. This chops the signal into
+## overlapping slices, windows each slice and applies a Fourier
+## transform to determine the frequency components at that slice. 
+##
+## x:      signal to analyse
+## Fs:     sampling rate for the signal
+## window: analysis window length (default 30 msec)
+## step:   time between windows, start to start (default 5 ms)
+## maxF:   maximum frequency to display (default 4000 Hz)
+##    Alternatively, use [maxF, nF], where nF is the minimum
+##    of frequency points to display.  If nF is greater than
+##    what it would normally be for the given window size and
+##    maximum displayed frequency, the FFT is zero-padded until
+##    it at least nF points are displayed on the y axis.
+## shape:  window analysis function (default 'hanning')
+##    Shape is any function which takes an integer n and returns
+##    a vector of length n.  If shape contains %d and ends with
+##    ')', as for example '(1:%d)' or 'kaiser(%d,0.5)' do, then 
+##    %d is replaced with the desired window length, and the
+##    expression is evaluated.
+## minE:   noise floor (default -40dB)
+##    Any value less than the noise floor is clipped before the
+##    spectrogram is displayed.  This limits the dynamic range
+##    that your spectrogram must accomodate.  Alternatively,
+##    use [minE, maxE], where maxE is the clipping ceiling, also
+##    in decibels.
+##
+## Return values
+##    S is the spectrogram in S with linear magnitude normalized to 1.
+##    f is the frequency indices corresponding to the rows of S.
+##    t is the time indices corresponding to the columns of S.
+##    If no return value is requested, the spectrogram is displayed instead.
+##
+## Global variables
+##    spectrogram_{window,step,maxF,nF,shape,minE,maxE} can override
+##    the default values with your own.
+##
+## To make a good spectrogram, generating spectral slices is only half
+## the problem.  Before you generate them, you must first choose your
+## window size, step size and FFT size.  A wide window shows more
+## harmonic detail, a narrow window shows more formant structure.  This
+## defines your time-frequency resolution. Step size controls the
+## horizontal scale of the spectrogram. Decrease it to stretch, or
+## increase it to compress. Certainly, increasing step size will reduce
+## time resolution, but decreasing it will not improve it much beyond
+## the limits imposed by the window size (you do gain a little bit,
+## depending on the shape of your window, as the peak of the window
+## slides over peaks in the signal energy).  The range 1-5 msec is good
+## for speech. Finally, FFT length controls the vertical scale, with
+## larger values stretching the frequency range.  Clearly, padding with
+## zeros does not add any information to the spectrum, but it is a
+## cheap, easy and good way to interpolate between frequency points, and
+## can make for prettier spectrograms.
+##
+## After you have generated the spectral slices, there are a number of
+## decisions for displaying them.  Firstly, the entire frequency range
+## does not need to be displayed.  The frequency range of the FFT is
+## determined by sampling rate.  If most of your signal is below 4 kHz
+## (in speech for example), there is no reason to display up to the
+## Nyquist frequency of 10 kHz for a 20 kHz sampling rate.  Next, there
+## is the dynamic range of the signal.  Since the information in speech
+## is well above the noise floor, it makes sense to eliminate any
+## dynamic range at the bottom end.  This is done by taking the max of
+## the normalized magnitude and some lower limit such as -40 dB.
+## Similarly, there is not much information in the very top of the
+## range, so clipping to -3 dB makes sense there. Finally, there is the
+## choice of colormap.  A brightness varying colormap such as copper or
+## bone gives good shape to the ridges and valleys.  A hue varying
+## colormap such as jet or hsv gives an indication of the steepness of
+## the slopes.
+
+## TODO: Accept vector of frequencies at which to sample the signal.
+## TODO: Consider accepting maxF (values > 0), shape (value is string)
+## TODO:    and dynamic range (values <= 0) in any order.
+## TODO: Consider defaulting step and maxF so that the spectrogram is
+## TODO:    an appropriate size for the screen (eg, 600x100).
+## TODO: Consider drawing in frequency/time grid; 
+## TODO:    (necessary with automatic sizing as suggested above)
+## TODO: Consider using step vs. [nT, nF] rather than maxF vs [maxF, nF]
+## TODO: Figure out why exist() is so slow: 50 ms vs 1 ms for lookup. 
+
+function [S_r, f_r, t_r] = spectrogram(x, Fs, window, step, maxF, shape, minE)
+  global spectrogram_window=30;
+  global spectrogram_step=5;
+  global spectrogram_maxF=4000;
+  global spectrogram_shape="hanning";
+  global spectrogram_minE=-40;
+  global spectrogram_maxE=0;
+  global spectrogram_nF=[];
+
+  if nargin < 2 || nargin > 7
+    usage ("[S, f, t] = spectrogram(x, fs, window, step, maxF, shape, minE)");
+  end
+
+  if nargin<3 || isempty(window), 
+    window=spectrogram_window; 
+  endif
+  if nargin<4 || isempty(step), 
+    step=spectrogram_step; 
+  endif
+  if nargin<5 || isempty(maxF), 
+    maxF=spectrogram_maxF; 
+  endif
+  if nargin<6 || isempty(shape), 
+    shape=spectrogram_shape;
+  endif
+  if nargin<7 || isempty(minE), 
+    minE=spectrogram_minE; 
+  endif
+  if any(minE>0)
+    error ("spectrogram clipping range must use values less than 0 dB");
+  endif
+  if length(minE)>1,
+    maxE=minE(2); 
+    minE=minE(1); 
+  else
+    maxE = spectrogram_maxE;
+  endif
+  if length(maxF)>1,
+    min_nF=maxF(2);
+    maxF=maxF(1);
+  else
+    min_nF=spectrogram_nF;
+  endif
+
+  ## make sure x is a column vector
+  if size(x,2) != 1 && size(x,1) != 1
+    error ("spectrogram data must be a vector");
+  end
+  if size(x,2) != 1, x = x'; end
+
+  if (maxF>Fs/2)
+    ## warning("spectrogram: cannot display frequencies greater than Fs/2");
+    maxF = Fs/2;
+  endif
+
+  step_n = fix(step*Fs/1000);    # one spectral slice every step ms
+
+  ## generate window from duration and shape function name
+  win_n = fix(window*Fs/1000);
+  if shape(length(shape)) == ')' 
+    shape = sprintf(shape, win_n);
+  else
+    shape = sprintf("%s(%d)", shape, win_n);
+  endif
+  win_vec = eval(strcat(shape,";"));
+  if size(win_vec,2) != 1, win_vec = win_vec'; endif
+  if size(win_vec,2) != 1 || size(win_vec,1) != win_n,
+    error("spectrogram %s did not return a window of length %d", \
+         shape, win_n);
+  endif
+         
+  ## FFT length from size of window and number of freq. pts requested
+  fft_n = 2^nextpow2(win_n);    # next highest power of 2
+  dF = Fs/fft_n;                # freq. step with current fft_n
+  nF = ceil(maxF(1)/dF);        # freq. pts with current fft_n,maxF
+  if !isempty(min_nF)           # make sure there are at least n freq. pts
+    if min_nF > nF,             # if not enough
+      dF = maxF/min_nF;            # figure out what freq. step we need
+      fft_n = 2^nextpow2(Fs/dF);   # figure out what fft_n this requires
+      dF = Fs/fft_n;               # freq. step with new fft_n
+      nF = ceil(maxF/dF);          # freq. pts with new fft_n,maxF
+    endif
+  endif
+
+  ## build matrix of windowed data slices
+  offset = 1:step_n:length(x)-win_n;
+  S = zeros (fft_n, length(offset));
+  for i=1:length(offset)
+    S(1:win_n, i) = x(offset(i):offset(i)+win_n-1) .* win_vec;
+  endfor
+
+  ## compute fourier transform
+  S = fft (S);
+  S = abs(S(1:nF,:));        # select the desired frequencies
+  S = S/max(S(:));           # normalize magnitude so that max is 0 dB.
+  S = max(S, 10^(minE/10));  # clip below minF dB.
+  S = min(S, 10^(maxE/10));  # clip above maxF dB.
+
+  f = [0:nF-1]*Fs/fft_n;
+  t = offset/Fs;
+  if nargout==0
+    imagesc(f,t,20*log10(flipud(S)));
+  else
+    S_r = S;
+    f_r = f;
+    t_r = t;
+  endif
+
+endfunction
\ No newline at end of file
diff --git a/telemeta/visualization/octave/spectrogram2img.m b/telemeta/visualization/octave/spectrogram2img.m
new file mode 100644 (file)
index 0000000..16abe06
--- /dev/null
@@ -0,0 +1,57 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2007 Parisson SARL
+# Copyright (c) 2006-2007 Guillaume Pellerin <pellerin@parisson.com>
+# All rights reserved.
+#
+# This software is licensed as described in the file COPYING, which
+# you should have received as part of this distribution. The terms
+# are also available at http://svn.parisson.org/telemeta/TelemetaLicense.
+#
+# Author: Guillaume Pellerin <pellerin@parisson.com>
+#
+# SpectrogramVisualizer2.m
+#
+# Depends: octave2.9, spectrogram.m, xloadimage, imagemagick
+
+clear all;
+close all;
+
+dest_image = $IMGFILE;
+wav_file = $WAVFILE;
+octave_path = $OCTAVEPATH;
+
+cd(octave_path);
+ncmap = 128; % number of points for colormap
+step = 6;   % spectral slice period (ms)
+% step_length = fix(5*Fs/1000);
+window = 30;   % filter window (ms)
+% window = fix(40*Fs/1000);
+lim_x_length = 10; % (s)
+
+[x, Fs] = wavread(wav_file);
+x = x(:,1);  % mono
+lim_x_samples = Fs.*lim_x_length;
+if length(x) > lim_x_samples;
+ x = x(1:lim_x_samples)
+end
+
+%fftn = 2^nextpow2(window); % next highest power of 2
+[S, f, t] = spectrogram(x, Fs, window, step, 4000, 'hanning', -30);
+S = flipud(20*log10(S));
+%  
+%  cmap = [0:1:ncmap-1];
+%  map_cos = cos(cmap*3.141/(2*ncmap));
+%  map_lin = cmap./ncmap;
+%  map_one = ones(1,ncmap);
+%
+%  cmap = [ [map_cos]' [map_cos]' [fliplr(map_cos)]' ];
+%  colormap(jet(ncmap));
+cmap = colormap(jet(ncmap));
+
+img = imagesc(t, f, S);
+%stdin(imagesc(t, f, S));
+saveimage(dest_image, img, 'ppm', cmap);
+%print([img_dir wav_file '.eps'], '-depsc');
+
+quit
diff --git a/telemeta/visualization/octave/waveform2img.m b/telemeta/visualization/octave/waveform2img.m
new file mode 100644 (file)
index 0000000..4b91712
--- /dev/null
@@ -0,0 +1,46 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2007 Parisson SARL
+# Copyright (c) 2006-2007 Guillaume Pellerin <pellerin@parisson.com>
+# All rights reserved.
+#
+# This software is licensed as described in the file COPYING, which
+# you should have received as part of this distribution. The terms
+# are also available at http://svn.parisson.org/telemeta/TelemetaLicense.
+#
+# Author: Guillaume Pellerin <pellerin@parisson.com>
+#
+# SpectrogramVisualizer2.m
+#
+# Depends: octave2.9, spectrogram.m, xloadimage, imagemagick
+
+clear all;
+close all;
+
+dest_image = $IMGFILE;
+wav_file = $WAVFILE;
+octave_path = $OCTAVEPATH;
+
+cd(octave_path);
+ncmap = 128; % number of points for colormap
+step = 6;   % spectral slice period (ms)
+% step_length = fix(5*Fs/1000);
+window = 30;   % filter window (ms)
+% window = fix(40*Fs/1000);
+lim_x_length = 10; % (s)
+
+[x, Fs] = wavread(wav_file);
+x = x(:,1);  % mono
+lx = length(x);
+lim_x_samples = Fs.*lim_x_length;
+
+if lx > lim_x_samples;
+ x = x(1:lim_x_samples)
+end
+
+t = [1:1:lx]./Fs;
+
+img = plot(t,x);
+print(dest_image, '-dpng')
+
+quit
diff --git a/telemeta/visualization/octave_core.py b/telemeta/visualization/octave_core.py
new file mode 100644 (file)
index 0000000..edff7b4
--- /dev/null
@@ -0,0 +1,81 @@
+
+from telemeta.core import *
+from telemeta.export import *
+from telemeta.visualization.api import IMediaItemVisualizer
+from django.conf import settings
+from tempfile import NamedTemporaryFile
+import os
+import random
+import subprocess
+import signal
+import time
+
+class OctaveCoreVisualizer(Component):
+    """Parent class for Octave visualization drivers"""
+
+    def get_mFile_line(self):
+        octave_path = os.path.dirname(__file__) + '/octave/'
+        mFile_path = os.path.dirname(__file__) + '/octave/' + self.mFile
+        mFile = open(mFile_path,'r')
+        
+        while True:
+            line = mFile.readline()
+            if 'quit' in line:
+                break
+            if '$OCTAVEPATH' in line:
+                line = line.replace('$OCTAVEPATH','"'+octave_path+'"')
+            if '$WAVFILE' in line:
+                line = line.replace('$WAVFILE','"'+self.wavFile_path+'"')
+            if '$IMGFILE' in line:
+                line = line.replace('$IMGFILE','"'+self.ppmFile.name+'"')
+            yield line
+            
+        mFile.close()
+
+    def set_m_file(self,mFile):
+        self.mFile = mFile
+
+    def get_wav_path(self, media_item):
+        self.wavFile_path = settings.MEDIA_ROOT + '/' + media_item.file
+        
+    def octave_to_png_stream(self, media_item):
+
+        self.pngFile = NamedTemporaryFile(suffix='.png')
+        self.ppmFile = NamedTemporaryFile(suffix='.'+self.dest_type)
+        self.wavFile = self.get_wav_path(media_item)
+        #command = 'octave2.9 ' + self.mFile_tmp.name
+        command = 'octave2.9'
+                
+        proc = subprocess.Popen(command,
+                                shell = True,
+                                #bufsize = buffer_size,
+                                stdin = subprocess.PIPE,
+                                stdout = subprocess.PIPE,
+                                close_fds = True)
+
+        for line in self.get_mFile_line():
+            proc.stdin.write(line)
+
+        # Wait for ppm
+        status = os.stat(self.ppmFile.name).st_size
+        while True:
+            if status == os.stat(self.ppmFile.name).st_size and status != 0:
+                break
+            status = os.stat(self.ppmFile.name).st_size
+            #print status
+        time.sleep(1)
+
+        # Convert
+        os.system('convert ' + self.ppmFile.name + ' -scale 300x300 ' + self.pngFile.name)
+
+        os.kill(proc.pid, signal.SIGKILL)
+
+        # Stream
+        while True  :
+            buffer = self.pngFile.read(self.buffer_size)
+            if len(buffer) == 0:
+                break
+            yield buffer
+
+        self.ppmFile.close()
+        self.pngFile.close()
index edeae355d997ec5754471f527071a3324e3b9eb0..888ab998dac618f041ee4b39387d004554ac2f2a 100644 (file)
@@ -44,3 +44,4 @@ class SnackCoreVisualizer(Component):
     def cleanup(self): 
         self.snd.destroy()
         self.tk_root.destroy()
+
index e20572671b8a7fd42c4025fb92e9631c41a674b2..92d7c2dc5bf21ac1cd316c51d154d64a02e23ac6 100644 (file)
@@ -34,7 +34,6 @@ class SpectrogramVisualizer(SnackCoreVisualizer):
         canvas.create_spectrogram(0, 10, sound=snd, height=180, width=300 ,
             windowtype="hamming", fftlength=1024, topfrequency=5000, channel="all", winlength=64)
 
-
         stream = self.canvas_to_png_stream(canvas)
 
         return stream
diff --git a/telemeta/visualization/spectrogram2.py b/telemeta/visualization/spectrogram2.py
new file mode 100644 (file)
index 0000000..833091d
--- /dev/null
@@ -0,0 +1,36 @@
+# Copyright (C) 2007 Samalyse SARL
+# All rights reserved.
+#
+# This software is licensed as described in the file COPYING, which
+# you should have received as part of this distribution. The terms
+# are also available at http://svn.parisson.org/telemeta/TelemetaLicense.
+#
+# Authors: Olivier Guilyardi <olivier@samalyse.com>
+#          Guillaume Pellerin <pellerin@parisson.com>
+
+from telemeta.core import *
+from telemeta.visualization.api import IMediaItemVisualizer
+from telemeta.visualization.octave_core import OctaveCoreVisualizer
+
+class SpectrogramVisualizer2(OctaveCoreVisualizer):
+    """Octave spectral view visualization driver"""
+    
+    implements(IMediaItemVisualizer)
+
+    def __init__(self):
+        self.set_m_file('spectrogram2img.m')
+        self.buffer_size = 0xFFFF
+        self.dest_type = 'ppm'
+        
+    def get_id(self):
+        return "spectrogram2"
+
+    def get_name(self):
+        return "Spectrogram2"
+    
+    def render(self, media_item, options=None):
+        """Generator that streams the spectral view as a PNG image"""
+
+        stream = self.octave_to_png_stream(media_item)
+        return stream
+        
index 7c9a7a479a81f4dbe81664b6f62e4e33ac98c147..910bac1583877fb96939ca979f456244d18d5fd8 100644 (file)
@@ -49,11 +49,4 @@ class WaveFormVisualizer(Component):
             buffer = pngFile.read(0xFFFF)
 
         pngFile.close()            
-        
 
-
-
-
-        
-
-            
diff --git a/telemeta/visualization/waveform2.py b/telemeta/visualization/waveform2.py
new file mode 100644 (file)
index 0000000..bcc848f
--- /dev/null
@@ -0,0 +1,36 @@
+# Copyright (C) 2007 Samalyse SARL
+# All rights reserved.
+#
+# This software is licensed as described in the file COPYING, which
+# you should have received as part of this distribution. The terms
+# are also available at http://svn.parisson.org/telemeta/TelemetaLicense.
+#
+# Authors: Olivier Guilyardi <olivier@samalyse.com>
+#          Guillaume Pellerin <pellerin@parisson.com>
+
+from telemeta.core import *
+from telemeta.visualization.api import IMediaItemVisualizer
+from telemeta.visualization.octave_core import OctaveCoreVisualizer
+
+class WaveformVisualizer2(OctaveCoreVisualizer):
+    """Octave temporal view visualization driver"""
+    
+    implements(IMediaItemVisualizer)
+
+    def __init__(self):
+        self.set_m_file('waveform2img.m')
+        self.buffer_size = 0xFFFF
+        self.dest_type = 'png'
+        
+    def get_id(self):
+        return "waveform2"
+
+    def get_name(self):
+        return "Waveform2"
+    
+    def render(self, media_item, options=None):
+        """Generator that streams the temporal view as a PNG image"""
+
+        stream = self.octave_to_png_stream(media_item)
+        return stream
+