annotate constant-q-cpp/misc/yeti/cqt.yeti @ 372:af71cbdab621 tip

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