annotate yeti/cqt.yeti @ 46:9c47e5beaebf

Make additional latency at start a multiple of the big blocksize
author Chris Cannam <c.cannam@qmul.ac.uk>
date Fri, 22 Nov 2013 17:56:20 +0000
parents 337d3b324c75
children 3d6397e43671
rev   line source
c@10 1
c@37 2 module cqt;
c@10 3
c@10 4 cqtkernel = load cqtkernel;
c@10 5 resample = load may.stream.resample;
c@10 6 manipulate = load may.stream.manipulate;
c@10 7 cm = load may.matrix.complex;
c@10 8 framer = load may.stream.framer;
c@10 9 cplx = load may.complex;
c@10 10 fft = load may.transform.fft;
c@10 11 vec = load may.vector;
c@42 12 ch = load may.stream.channels;
c@10 13
c@10 14 { pow, round, floor, ceil, log2, nextPowerOfTwo } = load may.mathmisc;
c@10 15
c@37 16 cqt { maxFreq, minFreq, binsPerOctave } str =
c@10 17 (sampleRate = str.sampleRate;
c@10 18 octaves = ceil (log2 (maxFreq / minFreq));
c@10 19 actualMinFreq = (maxFreq / (pow 2 octaves)) * (pow 2 (1/binsPerOctave));
c@10 20
c@41 21 kdata = cqtkernel.makeKernel { sampleRate, maxFreq, binsPerOctave };
c@10 22
c@41 23 eprintln "sampleRate = \(sampleRate), maxFreq = \(maxFreq), minFreq = \(minFreq), actualMinFreq = \(actualMinFreq), octaves = \(octaves), binsPerOctave = \(binsPerOctave), fftSize = \(kdata.fftSize), hop = \(kdata.fftHop)";
c@10 24
c@16 25 eprintln "atomsPerFrame = \(kdata.atomsPerFrame)";
c@11 26
c@41 27 padding = (kdata.fftSize * (pow 2 (octaves-1)));
c@40 28
c@40 29 eprintln "padding = \(padding)";
c@40 30
c@40 31 str = manipulate.paddedBy padding str;
c@40 32
c@10 33 streams = manipulate.duplicated octaves str;
c@10 34
c@10 35 //!!! can't be right!
c@10 36 kernel = cm.transposed (cm.conjugateTransposed kdata.kernel);
c@10 37
c@16 38 eprintln "have kernel";
c@10 39
c@10 40 fftFunc = fft.forward kdata.fftSize;
c@10 41
c@10 42 cqblocks =
c@10 43 map do octave:
c@42 44 frames = map ch.mixedDown //!!! mono for now
c@42 45 (framer.frames kdata.fftSize [ Hop kdata.fftHop, Padded false ]
c@42 46 (resample.decimated (pow 2 octave) streams[octave]));
c@10 47 map do frame:
c@10 48 freq = fftFunc (cplx.complexArray frame (vec.zeros kdata.fftSize));
c@43 49 // eprintln "octave = \(octave), frame = \(vec.list frame)";
c@43 50 // eprintln "octave = \(octave), freq = \(freq)";
c@10 51 cm.product kernel (cm.newComplexColumnVector freq);
c@10 52 done frames;
c@10 53 done [0..octaves-1];
c@10 54
c@13 55 // The cqblocks list is a list<list<matrix>>. Each top-level list
c@11 56 // corresponds to an octave, from highest to lowest, each having
c@11 57 // twice as many elements in its list as the next octave. The
c@11 58 // sub-lists are sampled in time with an effective spacing of
c@11 59 // fftSize * 2^(octave-1) audio frames, and the matrices are row
c@11 60 // vectors with atomsPerFrame * binsPerOctave complex elements.
c@13 61 //
c@13 62 // ***
c@13 63 //
c@13 64 // In a typical constant-Q structure, each (2^(octaves-1) *
c@13 65 // fftHop) input frames gives us an output structure conceptually
c@13 66 // like this:
c@10 67 //
c@10 68 // [][][][][][][][] <- fftHop frames per highest-octave output value
c@10 69 // [][][][][][][][] layered as many times as binsPerOctave (here 2)
c@10 70 // [--][--][--][--] <- fftHop*2 frames for the next lower octave
c@10 71 // [--][--][--][--] etc
c@10 72 // [------][------]
c@10 73 // [------][------]
c@10 74 // [--------------]
c@10 75 // [--------------]
c@10 76 //
c@13 77 // ***
c@13 78 //
c@13 79 // But the kernel we're using here has more than one temporally
c@13 80 // spaced atom; each individual cell is a row vector with
c@13 81 // atomsPerFrame * binsPerOctave elements, but that actually
c@13 82 // represents a rectangular matrix of result cells with width
c@13 83 // atomsPerFrame and height binsPerOctave. The columns of this
c@13 84 // matrix (the atoms) then need to be spaced by 2^(octave-1)
c@13 85 // relative to those from the highest octave.
c@10 86
c@15 87 // Reshape each row vector into the appropriate rectangular matrix
c@21 88 // and split into single-atom columns
c@19 89
c@44 90 emptyHops = kdata.firstCentre / kdata.atomSpacing; //!!! int? round?
c@21 91 maxDrop = emptyHops * (pow 2 (octaves-1)) - emptyHops;
c@21 92 eprintln "maxDrop = \(maxDrop)";
c@21 93
c@21 94 cqblocks = map do octlist:
c@21 95 concat
c@21 96 (map do rv:
c@21 97 cm.asColumns
c@21 98 (cm.generate do row col:
c@21 99 cm.at rv ((row * kdata.atomsPerFrame) + col) 0
c@21 100 done {
c@21 101 rows = kdata.binsPerOctave,
c@21 102 columns = kdata.atomsPerFrame
c@21 103 })
c@21 104 done octlist)
c@21 105 done cqblocks;
c@21 106
c@21 107 cqblocks = array (map2 do octlist octave:
c@21 108 d = emptyHops * (pow 2 (octaves-octave)) - emptyHops;
c@22 109
c@41 110 // d = 0; //!!!
c@22 111
c@21 112 eprintln "dropping \(d)";
c@21 113 drop d octlist;
c@21 114 done cqblocks [1..octaves]);
c@14 115
c@17 116 assembleBlock bits =
c@19 117 (eprintln "assembleBlock: structure of bits is:";
c@19 118 eprintln (map length bits);
c@19 119
c@19 120 rows = octaves * kdata.binsPerOctave;
c@19 121 columns = (pow 2 (octaves - 1)) * kdata.atomsPerFrame;
c@19 122
c@18 123 cm.generate do row col:
c@19 124
c@19 125 // bits structure: [1,2,4,8,...]
c@19 126
c@19 127 // each elt of bits is a list of the chunks that should
c@19 128 // make up this block in that octave (lowest octave first)
c@19 129
c@19 130 // each chunk has atomsPerFrame * binsPerOctave elts in it
c@19 131
c@19 132 // row is disposed with 0 at the top, highest octave (in
c@19 133 // both pitch and index into bits structure)
c@19 134
c@18 135 oct = int (row / binsPerOctave);
c@19 136 binNo = row % kdata.binsPerOctave;
c@21 137
c@19 138 chunks = pow 2 oct;
c@21 139 colsPerAtom = int (columns / (chunks * kdata.atomsPerFrame));
c@21 140 atomNo = int (col / colsPerAtom);
c@21 141 atomOffset = col % colsPerAtom;
c@18 142
c@40 143 if atomOffset == 0 and atomNo < length bits[oct] then
c@21 144 bits[oct][atomNo][binNo];
c@20 145 else
c@20 146 cplx.zero
c@20 147 fi;
c@19 148
c@19 149 done { rows, columns };
c@19 150 );
c@15 151
c@17 152 processOctaveLists octs =
c@17 153 case octs[0] of
c@17 154 block::rest:
c@19 155 (toAssemble = array
c@19 156 (map do oct:
c@21 157 n = kdata.atomsPerFrame * pow 2 oct;
c@17 158 if not empty? octs[oct] then
c@19 159 forBlock = array (take n octs[oct]);
c@17 160 octs[oct] := drop n octs[oct];
c@17 161 forBlock
c@17 162 else
c@19 163 array []
c@17 164 fi
c@19 165 done (keys octs));
c@17 166 assembleBlock toAssemble :. \(processOctaveLists octs));
c@17 167 _: []
c@15 168 esac;
c@15 169
c@19 170 eprintln "cqblocks has \(length cqblocks) entries";
c@15 171
c@17 172 octaveLists = [:];
c@19 173
c@19 174 cqblocks = array cqblocks;
c@17 175 for [1..octaves] do oct:
c@17 176 octaveLists[octaves - oct] := cqblocks[oct-1];
c@17 177 done;
c@17 178 /*
c@17 179 \() (map2 do octlist octave:
c@17 180 println "oct \(octaves) - \(octave) = \(octaves - octave)";
c@17 181 octaveLists[octaves - octave] := octlist
c@17 182 done cqblocks [1..octaves]);
c@17 183 */
c@19 184 eprintln "octaveLists keys are: \(keys octaveLists)";
c@15 185
c@40 186 {
c@40 187 kernel = kdata with {
c@40 188 binFrequencies = array (concat
c@40 189 (map do octave:
c@40 190 map do freq:
c@40 191 freq / (pow 2 octave);
c@40 192 done (reverse (list kdata.binFrequencies))
c@40 193 done [0..octaves-1]))
c@40 194 },
c@40 195 output = processOctaveLists octaveLists
c@40 196 }
c@10 197 );
c@10 198
c@37 199 { cqt }
c@10 200