1 """
2 Pattern generators for audio signals.
3
4
5 $Id: audio.py 11155 2010-07-09 23:21:06Z jbednar $
6 """
7 __version__='$Revision: 11155 $'
8
9
10
11 import numpy
12 import param
13 import os
14
15 from param.parameterized import ParamOverrides
16 from topo.base.sheetcoords import SheetCoordinateSystem
17
18 from topo.pattern.basic import Spectrogram
19 from numpy import float32, multiply, round, shape, hstack
20 from numpy import hanning, fft, log10, logspace
21
22 try:
23 import scikits.audiolab as pyaudiolab
24
25 except ImportError:
26 param.Parameterized().warning("audio.py classes will not be usable; scikits.audiolab (pyaudiolab) is not available.")
27
28
29
30
32 """
33 Returns a spectrogram, i.e. the spectral density over time
34 of a rolling window of the input audio signal.
35 """
36
37 filename=param.Filename(default='sounds/complex/daisy.wav', doc="""
38 File path (can be relative to Topographica's base path) to an
39 audio file. The audio can be in any format accepted by pyaudiolab,
40 e.g. WAV, AIFF, or FLAC.""")
41
42 amplify_from_frequency=param.Number(default=1500.0, doc="""
43 The lower bound of the frequency range to be amplified.""")
44
45 amplify_till_frequency=param.Number(default=7000.0, doc="""
46 The upper bound of the frequency range to be amplified.""")
47
48 amplify_by=param.Number(default=5.0, doc="""
49 The percentage by which to amplify the signal between the specified
50 frequency range.""")
51
53 for parameter,value in params.items():
54 if parameter == "filename" or\
55 parameter == "amplify_from_frequency" or \
56 parameter == "amplify_till_frequency" or \
57 parameter == "amplify_by":
58 setattr(self,parameter,value)
59
60 self._source = pyaudiolab.Sndfile(self.filename, 'r')
61 super(AudioFile, self).__init__(signal=self._source.read_frames(self._source.nframes, dtype=float32),
62 sample_rate=self._source.samplerate, **params)
63
65
66 self._frequency_indices = round(logspace(log10(maxi), log10(mini), num=(maxi-mini),
67 endpoint=True, base=10)).astype(int)
68
69 - def __call__(self, **params_to_override):
70
71 p = ParamOverrides(self, params_to_override)
72
73
74 self._sheet_dimensions = SheetCoordinateSystem(p.bounds, p.xdensity, p.ydensity).shape
75
76
77 self._create_indices(p)
78
79
80 amplitudes = self._get_amplitudes(p)
81
82
83 for frequency in range(shape(amplitudes)[0]):
84 if amplitudes[frequency] > 0.0:
85 multiply(20.0,log10(amplitudes[frequency]))
86
87
88 if self.amplify_by > 0.0:
89 if (self.amplify_from_frequency < self.min_frequency) or (self.amplify_from_frequency > self.max_frequency):
90 raise ValueError("Lower bound of frequency to amplify is outside the global frequency range.")
91
92 if (self.amplify_till_frequency < self.min_frequency) or (self.amplify_till_frequency > self.max_frequency):
93 raise ValueError("Upper bound of frequency to amplify is outside the global frequency range.")
94
95 amplify_indices = [0,0]
96 frequency_bins = logspace(log10(self.max_frequency), log10(self.min_frequency),
97 num=shape(amplitudes)[0], endpoint=True, base=10)
98 frequency_indices = range(shape(frequency_bins)[0])
99
100
101 for index in frequency_indices:
102 if frequency_bins[index] <= self.amplify_till_frequency:
103 amplify_indices[1] = index; break
104
105
106 for index in reversed(frequency_indices):
107 if frequency_bins[index] >= self.amplify_from_frequency:
108 amplify_indices[0] = index; break
109
110
111 amplify_indices.sort()
112 assert amplify_indices[1] > amplify_indices[0]
113 amplification = hanning(amplify_indices[1]-amplify_indices[0])*self.amplify_by
114
115
116
117 for unit in range(shape(amplification)[0]):
118 amplitudes[amplify_indices[0]+unit] *= amplification[unit]+1.0
119
120
121
122 assert shape(amplitudes)[0] == shape(self._spectrogram)[0]
123 self._spectrogram = hstack((amplitudes, self._spectrogram))
124
125
126
127 self._spectrogram = self._spectrogram[0:, 0:self._spectrogram.shape[1]-1]
128
129
130
131
132
133
134 return self._spectrogram
135
136
137
139 """
140 Returns a spectrogram, i.e. the spectral density over time
141 of a rolling window of the input audio signal, for all files
142 in the specified folder.
143 """
144
145 folderpath=param.String(default='sounds/complex/', doc="""
146 Folder path (can be relative to Topographica's base path) to a
147 folder containing audio files. The audio can be in any format
148 accepted by pyaudiolab, e.g. WAV, AIFF, or FLAC.""")
149
150 gap_between_sounds=param.Number(default=0.0, doc="""
151 The gap in seconds to insert between consecutive soundfiles.""")
152
154 for parameter,value in params.items():
155 if parameter == "folderpath" or \
156 parameter == "gap_between_sounds":
157 setattr(self,parameter,value)
158
159 all_files = os.listdir(self.folderpath)
160 self._sound_files = []
161 for file in all_files:
162 if file[-4:]==".wav" or file[-3:]==".wv" or \
163 file[-5:]==".aiff" or file[-4:]==".aif" or \
164 file[-5:]==".flac":
165 self._sound_files.append(self.folderpath+file)
166
167 self._next_file = 1
168 super(AudioFolder, self).__init__(filename=self._sound_files[0], **params)
169
171
172 if self._window_start == 0:
173 self.signal = hstack((self.signal, [0.0]*int(self.gap_between_sounds*self.sample_rate)))
174
175 start = self._window_start
176 end = start+self._samples_per_window
177
178
179 self._window_start += int(self.window_increment * self.sample_rate)
180
181 if (end > self.signal.size) and (self._next_file < len(self._sound_files)):
182 next_source = pyaudiolab.Sndfile(self._sound_files[self._next_file], 'r')
183 self._next_file = self._next_file + 1
184
185 if next_source.samplerate != self.sample_rate:
186 raise ValueError("All sound files must be of the same sample rate")
187
188 next_signal = next_source.read_frames(next_source.nframes, dtype=float32)
189 self.signal = hstack((self.signal[start:len(self.signal)], next_signal))
190
191 self._window_start = 0
192 return self.signal[0:end-start]
193
194 elif end > self.signal.size:
195 raise ValueError("Reached the end of the signal.")
196
197 return self.signal[start:end]
198
199
200
201
202 if __name__=='__main__' or __name__=='__mynamespace__':
203
204 from topo import sheet
205 import topo
206
207 topo.sim['C']=sheet.GeneratorSheet(
208 input_generator=AudioFile(filename='sounds/sine_waves/20000.wav',sample_window=0.3,
209 seconds_per_timestep=0.1,min_frequency=20,max_frequency=20000),
210 nominal_bounds=sheet.BoundingBox(points=((-0.1,-0.5),(0.0,0.5))),
211 nominal_density=10,period=1.0,phase=0.05)
212