comparison misc/yeti/cqt.yeti @ 116:6deec2a51d13

Moving to standalone library layout
author Chris Cannam <c.cannam@qmul.ac.uk>
date Thu, 15 May 2014 12:04:00 +0100
parents
children
comparison
equal deleted inserted replaced
115:93be4aa255e5 116:6deec2a51d13
1 /*
2 Constant-Q library
3 Copyright (c) 2013-2014 Queen Mary, University of London
4
5 Permission is hereby granted, free of charge, to any person
6 obtaining a copy of this software and associated documentation
7 files (the "Software"), to deal in the Software without
8 restriction, including without limitation the rights to use, copy,
9 modify, merge, publish, distribute, sublicense, and/or sell copies
10 of the Software, and to permit persons to whom the Software is
11 furnished to do so, subject to the following conditions:
12
13 The above copyright notice and this permission notice shall be
14 included in all copies or substantial portions of the Software.
15
16 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
20 CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
21 CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
24 Except as contained in this notice, the names of the Centre for
25 Digital Music; Queen Mary, University of London; and Chris Cannam
26 shall not be used in advertising or otherwise to promote the sale,
27 use or other dealings in this Software without prior written
28 authorization.
29 */
30
31 module cqt;
32
33 cqtkernel = load cqtkernel;
34 resample = load may.stream.resample;
35 manipulate = load may.stream.manipulate;
36 mat = load may.matrix;
37 cm = load may.matrix.complex;
38 framer = load may.stream.framer;
39 cplx = load may.complex;
40 fft = load may.transform.fft;
41 vec = load may.vector;
42 ch = load may.stream.channels;
43
44 { pow, round, floor, ceil, log2, nextPowerOfTwo } = load may.mathmisc;
45
46 cqt { maxFreq, minFreq, binsPerOctave } str =
47 (sampleRate = str.sampleRate;
48 octaves = ceil (log2 (maxFreq / minFreq));
49 // actualMinFreq = (maxFreq / (pow 2 octaves)) * (pow 2 (1/binsPerOctave));
50
51 kdata = cqtkernel.makeKernel { sampleRate, maxFreq, binsPerOctave };
52
53 // eprintln "sampleRate = \(sampleRate), maxFreq = \(maxFreq), minFreq = \(minFreq), actualMinFreq = \(actualMinFreq), octaves = \(octaves), binsPerOctave = \(binsPerOctave), fftSize = \(kdata.fftSize), hop = \(kdata.fftHop)";
54
55 // eprintln "atomsPerFrame = \(kdata.atomsPerFrame)";
56
57 padding = (kdata.fftSize * (pow 2 (octaves-1)));
58
59 // eprintln "padding = \(padding)";
60
61 str = manipulate.paddedBy padding str;
62
63 streams = manipulate.duplicated octaves str;
64
65 // forward transform uses the conjugate-transposed kernel, inverse
66 // uses the original
67 kernel = cm.transposed (cm.conjugateTransposed kdata.kernel);
68
69 // eprintln "have kernel";
70
71 fftFunc = fft.forward kdata.fftSize;
72
73 cqblocks =
74 map do octave:
75 frames = map ch.mixedDown //!!! mono for now
76 (framer.frames kdata.fftSize [ Hop kdata.fftHop, Padded false ]
77 (resample.decimated (pow 2 octave) streams[octave]));
78 map do frame:
79 freq = fftFunc (cplx.complexArray frame (vec.zeros kdata.fftSize));
80 // eprintln "octave = \(octave), frame = \(vec.list frame)";
81 // eprintln "octave = \(octave), freq = \(freq)";
82 cm.product kernel (cm.newComplexColumnVector freq);
83 done frames;
84 done [0..octaves-1];
85
86 // The cqblocks list is a list<list<matrix>>. Each top-level list
87 // corresponds to an octave, from highest to lowest, each having
88 // twice as many elements in its list as the next octave. The
89 // sub-lists are sampled in time with an effective spacing of
90 // fftSize * 2^(octave-1) audio frames, and the matrices are row
91 // vectors with atomsPerFrame * binsPerOctave complex elements.
92 //
93 // ***
94 //
95 // In a typical constant-Q structure, each (2^(octaves-1) *
96 // fftHop) input frames gives us an output structure conceptually
97 // like this:
98 //
99 // [][][][][][][][] <- fftHop frames per highest-octave output value
100 // [][][][][][][][] layered as many times as binsPerOctave (here 2)
101 // [--][--][--][--] <- fftHop*2 frames for the next lower octave
102 // [--][--][--][--] etc
103 // [------][------]
104 // [------][------]
105 // [--------------]
106 // [--------------]
107 //
108 // ***
109 //
110 // But the kernel we're using here has more than one temporally
111 // spaced atom; each individual cell is a row vector with
112 // atomsPerFrame * binsPerOctave elements, but that actually
113 // represents a rectangular matrix of result cells with width
114 // atomsPerFrame and height binsPerOctave. The columns of this
115 // matrix (the atoms) then need to be spaced by 2^(octave-1)
116 // relative to those from the highest octave.
117
118 // Reshape each row vector into the appropriate rectangular matrix
119 // and split into single-atom columns
120
121 emptyHops = kdata.firstCentre / kdata.atomSpacing; //!!! int? round?
122 // maxDrop = emptyHops * (pow 2 (octaves-1)) - emptyHops;
123 // eprintln "maxDrop = \(maxDrop)";
124
125 cqblocks =
126 map do octlist:
127 concatMap do rv:
128 cm.asColumns
129 (cm.generate do row col:
130 cm.at rv ((row * kdata.atomsPerFrame) + col) 0
131 done {
132 rows = kdata.binsPerOctave,
133 columns = kdata.atomsPerFrame
134 })
135 done octlist
136 done cqblocks;
137
138 cqblocks = array (map2 do octlist octave:
139 d = emptyHops * (pow 2 (octaves-octave)) - emptyHops;
140 // eprintln "dropping \(d)";
141 drop d octlist;
142 done cqblocks [1..octaves]);
143
144 assembleBlock bits =
145 (//eprintln "assembleBlock: structure of bits is:";
146 //eprintln (map length bits);
147
148 rows = octaves * kdata.binsPerOctave;
149 columns = (pow 2 (octaves - 1)) * kdata.atomsPerFrame;
150
151 cm.generate do row col:
152
153 // bits structure: [1,2,4,8,...]
154
155 // each elt of bits is a list of the chunks that should
156 // make up this block in that octave (lowest octave first)
157
158 // each chunk has atomsPerFrame * binsPerOctave elts in it
159
160 // row is disposed with 0 at the top, highest octave (in
161 // both pitch and index into bits structure)
162
163 oct = int (row / binsPerOctave);
164 binNo = row % kdata.binsPerOctave;
165
166 chunks = pow 2 oct;
167 colsPerAtom = int (columns / (chunks * kdata.atomsPerFrame));
168 atomNo = int (col / colsPerAtom);
169 atomOffset = col % colsPerAtom;
170
171 if atomOffset == 0 and atomNo < length bits[oct] then
172 bits[oct][atomNo][binNo];
173 else
174 cplx.zero
175 fi;
176
177 done { rows, columns };
178 );
179
180 assembleBlockSpectrogram bits =
181 (// As assembleBlock, but producing a dense magnitude
182 // spectrogram (rather than a complex output). (todo:
183 // interpolation, smoothing)
184
185 //eprintln "assembleBlockSpectrogram: structure of bits is:";
186 //eprintln (map length bits);
187
188 rows = octaves * kdata.binsPerOctave;
189 columns = (pow 2 (octaves - 1)) * kdata.atomsPerFrame;
190
191 mat.generate do row col:
192
193 oct = int (row / binsPerOctave);
194 binNo = row % kdata.binsPerOctave;
195
196 chunks = pow 2 oct;
197 colsPerAtom = int (columns / (chunks * kdata.atomsPerFrame));
198 atomNo = int (col / colsPerAtom);
199
200 if atomNo < length bits[oct] then
201 cplx.magnitude bits[oct][atomNo][binNo];
202 else
203 0
204 fi;
205
206 done { rows, columns };
207 );
208
209 processOctaveLists assembler octs =
210 case octs[0] of
211 block::rest:
212 (toAssemble = array
213 (map do oct:
214 n = kdata.atomsPerFrame * pow 2 oct;
215 if not empty? octs[oct] then
216 forBlock = array (take n octs[oct]);
217 octs[oct] := drop n octs[oct];
218 forBlock
219 else
220 array []
221 fi
222 done (keys octs));
223 assembler toAssemble :. \(processOctaveLists assembler octs));
224 _: []
225 esac;
226
227 //eprintln "cqblocks has \(length cqblocks) entries";
228
229 octaveLists = [:];
230
231 cqblocks = array cqblocks;
232 for [1..octaves] do oct:
233 octaveLists[octaves - oct] := cqblocks[oct-1];
234 done;
235 /*
236 \() (map2 do octlist octave:
237 println "oct \(octaves) - \(octave) = \(octaves - octave)";
238 octaveLists[octaves - octave] := octlist
239 done cqblocks [1..octaves]);
240 */
241 //eprintln "octaveLists keys are: \(keys octaveLists)";
242
243 {
244 kernel = kdata with {
245 binFrequencies = array
246 (concatMap do octave:
247 map do freq:
248 freq / (pow 2 octave);
249 done (reverse (list kdata.binFrequencies))
250 done [0..octaves-1])
251 },
252 sampleRate,
253 octaves,
254 get cqComplex () = processOctaveLists assembleBlock octaveLists,
255 get cqSpectrogram () = processOctaveLists assembleBlockSpectrogram octaveLists,
256 }
257 );
258
259 { cqt }
260