Mercurial > hg > constant-q-cpp
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 |