1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 import optparse, math, sys, numpy
26
27 try:
28 from PIL import ImageFilter, ImageChops, Image, ImageDraw, ImageColor, ImageEnhance
29 except ImportError:
30 import ImageFilter, ImageChops, Image, ImageDraw, ImageColor, ImageEnhance
31
32 from timeside.core import *
33 from timeside.api import IGrapher
34 from timeside.grapher.color_schemes import default_color_schemes
35 from utils import *
39 """ FFT based frequency analysis of audio frames."""
40
41 - def __init__(self, fft_size, samplerate, blocksize, totalframes, lower, higher, window_function=None):
42 self.fft_size = fft_size
43 self.window = window_function(self.fft_size)
44 self.window_function = window_function
45 self.spectrum_range = None
46 self.lower = lower
47 self.higher = higher
48 self.blocksize = blocksize
49 self.lower_log = math.log10(self.lower)
50 self.higher_log = math.log10(self.higher)
51 self.clip = lambda val, low, high: min(high, max(low, val))
52 self.totalframes = totalframes
53 self.samplerate = samplerate
54 self.window_function = window_function
55 self.window = self.window_function(self.blocksize)
56
57 if self.window_function:
58 self.window = self.window_function(self.blocksize)
59 else:
60 self.window_function = numpy.hanning
61 self.window = self.window_function(self.blocksize)
62
63
64 - def process(self, frames, eod, spec_range=120.0):
65 """ Returns a tuple containing the spectral centroid and the spectrum (dB scales) of the input audio frames.
66 FFT window sizes are adatable to the input frame size."""
67
68 samples = frames[:,0]
69 nsamples = len(frames[:,0])
70 if nsamples != self.blocksize:
71 self.window = self.window_function(nsamples)
72 samples *= self.window
73
74 while nsamples > self.fft_size:
75 self.fft_size = 2 * self.fft_size
76
77 zeros_p = numpy.zeros(self.fft_size/2-int(nsamples/2))
78 if nsamples % 2:
79 zeros_n = numpy.zeros(self.fft_size/2-int(nsamples/2)-1)
80 else:
81 zeros_n = numpy.zeros(self.fft_size/2-int(nsamples/2))
82 samples = numpy.concatenate((zeros_p, samples, zeros_n), axis=0)
83
84 fft = numpy.fft.fft(samples)
85
86 spectrum = numpy.abs(fft[:fft.shape[0] / 2 + 1]) / float(nsamples)
87 length = numpy.float64(spectrum.shape[0])
88
89
90 db_spectrum = ((20*(numpy.log10(spectrum + 1e-30))).clip(-spec_range, 0.0) + spec_range)/spec_range
91 energy = spectrum.sum()
92 spectral_centroid = 0
93
94 if energy > 1e-20:
95
96 if self.spectrum_range == None:
97 self.spectrum_range = numpy.arange(length)
98 spectral_centroid = (spectrum * self.spectrum_range).sum() / (energy * (length - 1)) * self.samplerate * 0.5
99
100 spectral_centroid = (math.log10(self.clip(spectral_centroid, self.lower, self.higher)) - \
101 self.lower_log) / (self.higher_log - self.lower_log)
102
103 return (spectral_centroid, db_spectrum)
104
107 '''
108 Generic abstract class for the graphers
109 '''
110
111 fft_size = 0x1000
112 frame_cursor = 0
113 pixel_cursor = 0
114 lower_freq = 20
115
116 implements(IGrapher)
117 abstract()
118
119 - def __init__(self, width=1024, height=256, bg_color=None, color_scheme='default'):
120 super(Grapher, self).__init__()
121 self.bg_color = bg_color
122 self.color_scheme = color_scheme
123 self.graph = None
124 self.image_width = width
125 self.image_height = height
126 self.bg_color = bg_color
127 self.color_scheme = color_scheme
128 self.previous_x, self.previous_y = None, None
129
130 @staticmethod
132 return "generic_grapher"
133
134 @staticmethod
136 return "Generic grapher"
137
139 self.bg_color = bg_color
140 self.color_color_scheme = color_scheme
141
142 - def setup(self, channels=None, samplerate=None, blocksize=None, totalframes=None):
143 super(Grapher, self).setup(channels, samplerate, blocksize, totalframes)
144 self.sample_rate = samplerate
145 self.higher_freq = self.sample_rate/2
146 self.block_size = blocksize
147 self.total_frames = totalframes
148 self.image = Image.new("RGBA", (self.image_width, self.image_height), self.bg_color)
149 self.samples_per_pixel = self.total_frames / float(self.image_width)
150 self.buffer_size = int(round(self.samples_per_pixel, 0))
151 self.pixels_adapter = FixedSizeInputAdapter(self.buffer_size, 1, pad=False)
152 self.pixels_adapter_totalframes = self.pixels_adapter.blocksize(self.total_frames)
153 self.spectrum = Spectrum(self.fft_size, self.sample_rate, self.block_size, self.total_frames,
154 self.lower_freq, self.higher_freq, numpy.hanning)
155 self.pixel = self.image.load()
156 self.draw = ImageDraw.Draw(self.image)
157
158 @interfacedoc
159 - def render(self, output=None):
160 if output:
161 self.image.save(output)
162 return
163 return self.image
164
165 - def watermark(self, text, font=None, color=(255, 255, 255), opacity=.6, margin=(5,5)):
166 self.image = im_watermark(self.image, text, color=color, opacity=opacity, margin=margin)
167
169 """Draw 2 peaks at x"""
170
171 y1 = self.image_height * 0.5 - peaks[0] * (self.image_height - 4) * 0.5
172 y2 = self.image_height * 0.5 - peaks[1] * (self.image_height - 4) * 0.5
173
174 if self.previous_y:
175 self.draw.line([self.previous_x, self.previous_y, x, y1, x, y2], line_color)
176 else:
177 self.draw.line([x, y1, x, y2], line_color)
178
179 self.draw_anti_aliased_pixels(x, y1, y2, line_color)
180 self.previous_x, self.previous_y = x, y2
181
183 """Draw 2 inverted peaks at x"""
184
185 y1 = self.image_height * 0.5 - peaks[0] * (self.image_height - 4) * 0.5
186 y2 = self.image_height * 0.5 - peaks[1] * (self.image_height - 4) * 0.5
187
188 if self.previous_y and x < self.image_width-1:
189 if y1 < y2:
190 self.draw.line((x, 0, x, y1), line_color)
191 self.draw.line((x, self.image_height , x, y2), line_color)
192 else:
193 self.draw.line((x, 0, x, y2), line_color)
194 self.draw.line((x, self.image_height , x, y1), line_color)
195 else:
196 self.draw.line((x, 0, x, self.image_height), line_color)
197 self.draw_anti_aliased_pixels(x, y1, y2, line_color)
198 self.previous_x, self.previous_y = x, y1
199
201 """ vertical anti-aliasing at y1 and y2 """
202
203 y_max = max(y1, y2)
204 y_max_int = int(y_max)
205 alpha = y_max - y_max_int
206
207 if alpha > 0.0 and alpha < 1.0 and y_max_int + 1 < self.image_height:
208 current_pix = self.pixel[int(x), y_max_int + 1]
209 r = int((1-alpha)*current_pix[0] + alpha*color[0])
210 g = int((1-alpha)*current_pix[1] + alpha*color[1])
211 b = int((1-alpha)*current_pix[2] + alpha*color[2])
212 self.pixel[x, y_max_int + 1] = (r,g,b)
213
214 y_min = min(y1, y2)
215 y_min_int = int(y_min)
216 alpha = 1.0 - (y_min - y_min_int)
217
218 if alpha > 0.0 and alpha < 1.0 and y_min_int - 1 >= 0:
219 current_pix = self.pixel[x, y_min_int - 1]
220 r = int((1-alpha)*current_pix[0] + alpha*color[0])
221 g = int((1-alpha)*current_pix[1] + alpha*color[1])
222 b = int((1-alpha)*current_pix[2] + alpha*color[2])
223 self.pixel[x, y_min_int - 1] = (r,g,b)
224
226 contour = self.contour.copy()
227 contour = smooth(contour, window_len=16)
228 contour = normalize(contour)
229
230
231
232 ratio = 1
233 contour = normalize(numpy.expm1(contour/ratio))*(1-10**-6)
234
235
236
237
238
239 if self.symetry:
240 height = int(self.image_height/2)
241 else:
242 height = self.image_height
243
244
245 for i in range(0,self.ndiv):
246 self.previous_x, self.previous_y = None, None
247
248 bright_color = int(255*(1-float(i)/(self.ndiv*2)))
249 bright_color = 255-bright_color+self.color_offset
250
251 line_color = (bright_color,bright_color,bright_color)
252
253
254
255
256
257
258 contour = contour*numpy.arccos(float(i)/self.ndiv)*2/numpy.pi
259
260
261
262 curve = (height-1)*contour
263
264
265 for x in self.x:
266 x = int(x)
267 y = curve[x]
268 if not x == 0:
269 if not self.symetry:
270 self.draw.line([self.previous_x, self.previous_y, x, y], line_color)
271 self.draw_anti_aliased_pixels(x, y, y, line_color)
272 else:
273 self.draw.line([self.previous_x, self.previous_y+height, x, y+height], line_color)
274 self.draw_anti_aliased_pixels(x, y+height, y+height, line_color)
275 self.draw.line([self.previous_x, -self.previous_y+height, x, -y+height], line_color)
276 self.draw_anti_aliased_pixels(x, -y+height, -y+height, line_color)
277 else:
278 if not self.symetry:
279 self.draw.point((x, y), line_color)
280 else:
281 self.draw.point((x, y+height), line_color)
282 self.previous_x, self.previous_y = x, y
283
284
285
286 if __name__ == "__main__":
287 import doctest
288 doctest.testmod()
289