c@10
|
1
|
c@10
|
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 syn = load may.stream.syntheticstream;
|
c@10
|
8 cm = load may.matrix.complex;
|
c@10
|
9 mat = load may.matrix;
|
c@10
|
10 framer = load may.stream.framer;
|
c@10
|
11 cplx = load may.complex;
|
c@10
|
12 fft = load may.transform.fft;
|
c@10
|
13 vec = load may.vector;
|
c@10
|
14
|
c@10
|
15 { pow, round, floor, ceil, log2, nextPowerOfTwo } = load may.mathmisc;
|
c@10
|
16
|
c@10
|
17 cqt str =
|
c@10
|
18 (sampleRate = str.sampleRate;
|
c@10
|
19 maxFreq = sampleRate/2;
|
c@10
|
20 minFreq = 40;
|
c@10
|
21 binsPerOctave = 24;
|
c@10
|
22
|
c@10
|
23 println "Here";
|
c@10
|
24
|
c@10
|
25 octaves = ceil (log2 (maxFreq / minFreq));
|
c@10
|
26
|
c@10
|
27 println "Here: about to calculate stuff with \(octaves)";
|
c@10
|
28
|
c@10
|
29 actualMinFreq = (maxFreq / (pow 2 octaves)) * (pow 2 (1/binsPerOctave));
|
c@10
|
30
|
c@10
|
31 println "sampleRate = \(sampleRate), maxFreq = \(maxFreq), minFreq = \(minFreq), actualMinFreq = \(actualMinFreq), octaves = \(octaves), binsPerOctave = \(binsPerOctave)";
|
c@10
|
32
|
c@10
|
33 kdata = cqtkernel.makeKernel { sampleRate, maxFreq, binsPerOctave };
|
c@10
|
34
|
c@11
|
35 println "atomsPerFrame = \(kdata.atomsPerFrame)";
|
c@11
|
36
|
c@10
|
37 streams = manipulate.duplicated octaves str;
|
c@10
|
38
|
c@10
|
39 //!!! can't be right!
|
c@10
|
40 kernel = cm.transposed (cm.conjugateTransposed kdata.kernel);
|
c@10
|
41
|
c@10
|
42 println "have kernel";
|
c@10
|
43
|
c@10
|
44 fftFunc = fft.forward kdata.fftSize;
|
c@10
|
45
|
c@10
|
46 cqblocks =
|
c@10
|
47 map do octave:
|
c@10
|
48 frames = framer.monoFrames //!!! mono for now
|
c@10
|
49 { framesize = kdata.fftSize, hop = kdata.fftHop }
|
c@10
|
50 (resample.decimated (pow 2 octave) streams[octave]);
|
c@10
|
51 map do frame:
|
c@10
|
52 freq = fftFunc (cplx.complexArray frame (vec.zeros kdata.fftSize));
|
c@10
|
53 cm.product kernel (cm.newComplexColumnVector freq);
|
c@10
|
54 done frames;
|
c@10
|
55 done [0..octaves-1];
|
c@10
|
56
|
c@13
|
57 // The cqblocks list is a list<list<matrix>>. Each top-level list
|
c@11
|
58 // corresponds to an octave, from highest to lowest, each having
|
c@11
|
59 // twice as many elements in its list as the next octave. The
|
c@11
|
60 // sub-lists are sampled in time with an effective spacing of
|
c@11
|
61 // fftSize * 2^(octave-1) audio frames, and the matrices are row
|
c@11
|
62 // vectors with atomsPerFrame * binsPerOctave complex elements.
|
c@13
|
63 //
|
c@13
|
64 // ***
|
c@13
|
65 //
|
c@13
|
66 // In a typical constant-Q structure, each (2^(octaves-1) *
|
c@13
|
67 // fftHop) input frames gives us an output structure conceptually
|
c@13
|
68 // like this:
|
c@10
|
69 //
|
c@10
|
70 // [][][][][][][][] <- fftHop frames per highest-octave output value
|
c@10
|
71 // [][][][][][][][] layered as many times as binsPerOctave (here 2)
|
c@10
|
72 // [--][--][--][--] <- fftHop*2 frames for the next lower octave
|
c@10
|
73 // [--][--][--][--] etc
|
c@10
|
74 // [------][------]
|
c@10
|
75 // [------][------]
|
c@10
|
76 // [--------------]
|
c@10
|
77 // [--------------]
|
c@10
|
78 //
|
c@13
|
79 // ***
|
c@13
|
80 //
|
c@13
|
81 // But the kernel we're using here has more than one temporally
|
c@13
|
82 // spaced atom; each individual cell is a row vector with
|
c@13
|
83 // atomsPerFrame * binsPerOctave elements, but that actually
|
c@13
|
84 // represents a rectangular matrix of result cells with width
|
c@13
|
85 // atomsPerFrame and height binsPerOctave. The columns of this
|
c@13
|
86 // matrix (the atoms) then need to be spaced by 2^(octave-1)
|
c@13
|
87 // relative to those from the highest octave.
|
c@10
|
88
|
c@15
|
89 // Reshape each row vector into the appropriate rectangular matrix
|
c@15
|
90 cqblocks = map do octlist:
|
c@14
|
91 map do rv:
|
c@15
|
92 cm.generate do row col:
|
c@15
|
93 cm.at rv ((row * kdata.atomsPerFrame) + col) 0
|
c@15
|
94 done {
|
c@15
|
95 rows = kdata.binsPerOctave,
|
c@15
|
96 columns = kdata.atomsPerFrame
|
c@15
|
97 }
|
c@14
|
98 done octlist
|
c@15
|
99 done cqblocks;
|
c@14
|
100
|
c@15
|
101 //println "lengths per oct: \(map length cqblocks)";
|
c@15
|
102
|
c@15
|
103 // Slice each octave's blocks into a list of columns with zeros
|
c@15
|
104 // interspersed. This doesn't look very elegant (?)
|
c@15
|
105
|
c@15
|
106 cqblocks = map2 do octlist octave: concat
|
c@15
|
107 (map do m: concat
|
c@15
|
108 (map do col:
|
c@15
|
109 // col :: map \(cplx.zeros kdata.binsPerOctave) [1..pow 2 octave - 1]
|
c@15
|
110 map \col [0..pow 2 octave - 1]
|
c@15
|
111 done (cm.asColumns m))
|
c@15
|
112 done octlist)
|
c@15
|
113 done cqblocks [0..octaves-1];
|
c@15
|
114
|
c@15
|
115 // Then pile up the slices into taller slices spanning all octaves
|
c@15
|
116
|
c@15
|
117 pileOf octaves acc =
|
c@15
|
118 case (head octaves) of
|
c@15
|
119 h::t:
|
c@15
|
120 (heads = map head octaves;
|
c@15
|
121 // tall = concat (map reverse heads); // are the bpos values low->high or high->low?
|
c@15
|
122 tall = concat heads;
|
c@15
|
123 pileOf (map tail octaves) (acc ++ [tall]));
|
c@15
|
124 _: acc;
|
c@15
|
125 esac;
|
c@15
|
126
|
c@15
|
127 pileOf cqblocks [];
|
c@15
|
128
|
c@15
|
129 //println "lengths per oct: \(map length cqblocks)";
|
c@15
|
130
|
c@15
|
131
|
c@15
|
132 //cqblocks;
|
c@10
|
133 );
|
c@10
|
134
|
c@10
|
135 testStream = manipulate.withDuration 96000 (syn.sinusoid 48000 500);
|
c@10
|
136
|
c@10
|
137 println "have test stream";
|
c@10
|
138
|
c@10
|
139 cqt testStream;
|
c@10
|
140
|
c@10
|
141
|
c@10
|
142
|