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
|