annotate yeti/cqt.yeti @ 15:7f9a9af75d91

Towards shuffling the cq cells into the right arrangement for return
author Chris Cannam <c.cannam@qmul.ac.uk>
date Tue, 29 Oct 2013 16:55:55 +0000
parents 26d61c9f1f40
children 310435497b76
rev   line source
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