From: yomguy <> Date: Sun, 27 May 2007 16:58:35 +0000 (+0000) Subject: - Added Waveform2 and Spectrogram2 visualization components X-Git-Tag: 1.1~902 X-Git-Url: https://git.parisson.com/?a=commitdiff_plain;h=af09042fab959a97fc0c016e19dde5dd8202b434;p=telemeta.git - Added Waveform2 and Spectrogram2 visualization components - Clean up --- diff --git a/telemeta/export/core.py b/telemeta/export/core.py index d01139e8..9fd7658e 100644 --- a/telemeta/export/core.py +++ b/telemeta/export/core.py @@ -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, diff --git a/telemeta/visualization/__init__.py b/telemeta/visualization/__init__.py index 289a4f29..4fe60935 100644 --- a/telemeta/visualization/__init__.py +++ b/telemeta/visualization/__init__.py @@ -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 index 00000000..ba9f742e --- /dev/null +++ b/telemeta/visualization/octave/jet.m @@ -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 index 00000000..3b9e071b --- /dev/null +++ b/telemeta/visualization/octave/spectrogram.m @@ -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 index 00000000..16abe06d --- /dev/null +++ b/telemeta/visualization/octave/spectrogram2img.m @@ -0,0 +1,57 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2007 Parisson SARL +# Copyright (c) 2006-2007 Guillaume Pellerin +# 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 +# +# 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 index 00000000..4b917123 --- /dev/null +++ b/telemeta/visualization/octave/waveform2img.m @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2007 Parisson SARL +# Copyright (c) 2006-2007 Guillaume Pellerin +# 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 +# +# 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 index 00000000..edff7b4f --- /dev/null +++ b/telemeta/visualization/octave_core.py @@ -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() diff --git a/telemeta/visualization/snack_core.py b/telemeta/visualization/snack_core.py index edeae355..888ab998 100644 --- a/telemeta/visualization/snack_core.py +++ b/telemeta/visualization/snack_core.py @@ -44,3 +44,4 @@ class SnackCoreVisualizer(Component): def cleanup(self): self.snd.destroy() self.tk_root.destroy() + diff --git a/telemeta/visualization/spectrogram.py b/telemeta/visualization/spectrogram.py index e2057267..92d7c2dc 100644 --- a/telemeta/visualization/spectrogram.py +++ b/telemeta/visualization/spectrogram.py @@ -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 index 00000000..833091dd --- /dev/null +++ b/telemeta/visualization/spectrogram2.py @@ -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 +# Guillaume Pellerin + +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 + diff --git a/telemeta/visualization/waveform.py b/telemeta/visualization/waveform.py index 7c9a7a47..910bac15 100644 --- a/telemeta/visualization/waveform.py +++ b/telemeta/visualization/waveform.py @@ -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 index 00000000..bcc848fa --- /dev/null +++ b/telemeta/visualization/waveform2.py @@ -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 +# Guillaume Pellerin + +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 +