c@10: c@37: module cqt; c@10: c@10: cqtkernel = load cqtkernel; c@10: resample = load may.stream.resample; c@10: manipulate = load may.stream.manipulate; c@10: cm = load may.matrix.complex; c@10: framer = load may.stream.framer; c@10: cplx = load may.complex; c@10: fft = load may.transform.fft; c@10: vec = load may.vector; c@42: ch = load may.stream.channels; c@10: c@10: { pow, round, floor, ceil, log2, nextPowerOfTwo } = load may.mathmisc; c@10: c@37: cqt { maxFreq, minFreq, binsPerOctave } str = c@10: (sampleRate = str.sampleRate; c@10: octaves = ceil (log2 (maxFreq / minFreq)); c@10: actualMinFreq = (maxFreq / (pow 2 octaves)) * (pow 2 (1/binsPerOctave)); c@10: c@41: kdata = cqtkernel.makeKernel { sampleRate, maxFreq, binsPerOctave }; c@10: c@41: eprintln "sampleRate = \(sampleRate), maxFreq = \(maxFreq), minFreq = \(minFreq), actualMinFreq = \(actualMinFreq), octaves = \(octaves), binsPerOctave = \(binsPerOctave), fftSize = \(kdata.fftSize), hop = \(kdata.fftHop)"; c@10: c@16: eprintln "atomsPerFrame = \(kdata.atomsPerFrame)"; c@11: c@41: padding = (kdata.fftSize * (pow 2 (octaves-1))); c@40: c@40: eprintln "padding = \(padding)"; c@40: c@40: str = manipulate.paddedBy padding str; c@40: c@10: streams = manipulate.duplicated octaves str; c@10: c@10: //!!! can't be right! c@10: kernel = cm.transposed (cm.conjugateTransposed kdata.kernel); c@10: c@16: eprintln "have kernel"; c@10: c@10: fftFunc = fft.forward kdata.fftSize; c@10: c@10: cqblocks = c@10: map do octave: c@42: frames = map ch.mixedDown //!!! mono for now c@42: (framer.frames kdata.fftSize [ Hop kdata.fftHop, Padded false ] c@42: (resample.decimated (pow 2 octave) streams[octave])); c@10: map do frame: c@10: freq = fftFunc (cplx.complexArray frame (vec.zeros kdata.fftSize)); c@43: // eprintln "octave = \(octave), frame = \(vec.list frame)"; c@43: // eprintln "octave = \(octave), freq = \(freq)"; c@10: cm.product kernel (cm.newComplexColumnVector freq); c@10: done frames; c@10: done [0..octaves-1]; c@10: c@13: // The cqblocks list is a list>. Each top-level list c@11: // corresponds to an octave, from highest to lowest, each having c@11: // twice as many elements in its list as the next octave. The c@11: // sub-lists are sampled in time with an effective spacing of c@11: // fftSize * 2^(octave-1) audio frames, and the matrices are row c@11: // vectors with atomsPerFrame * binsPerOctave complex elements. c@13: // c@13: // *** c@13: // c@13: // In a typical constant-Q structure, each (2^(octaves-1) * c@13: // fftHop) input frames gives us an output structure conceptually c@13: // like this: c@10: // c@10: // [][][][][][][][] <- fftHop frames per highest-octave output value c@10: // [][][][][][][][] layered as many times as binsPerOctave (here 2) c@10: // [--][--][--][--] <- fftHop*2 frames for the next lower octave c@10: // [--][--][--][--] etc c@10: // [------][------] c@10: // [------][------] c@10: // [--------------] c@10: // [--------------] c@10: // c@13: // *** c@13: // c@13: // But the kernel we're using here has more than one temporally c@13: // spaced atom; each individual cell is a row vector with c@13: // atomsPerFrame * binsPerOctave elements, but that actually c@13: // represents a rectangular matrix of result cells with width c@13: // atomsPerFrame and height binsPerOctave. The columns of this c@13: // matrix (the atoms) then need to be spaced by 2^(octave-1) c@13: // relative to those from the highest octave. c@10: c@15: // Reshape each row vector into the appropriate rectangular matrix c@21: // and split into single-atom columns c@19: c@21: emptyHops = kdata.firstCentre / kdata.atomSpacing; c@21: maxDrop = emptyHops * (pow 2 (octaves-1)) - emptyHops; c@21: eprintln "maxDrop = \(maxDrop)"; c@21: c@21: cqblocks = map do octlist: c@21: concat c@21: (map do rv: c@21: cm.asColumns c@21: (cm.generate do row col: c@21: cm.at rv ((row * kdata.atomsPerFrame) + col) 0 c@21: done { c@21: rows = kdata.binsPerOctave, c@21: columns = kdata.atomsPerFrame c@21: }) c@21: done octlist) c@21: done cqblocks; c@21: c@21: cqblocks = array (map2 do octlist octave: c@21: d = emptyHops * (pow 2 (octaves-octave)) - emptyHops; c@22: c@41: // d = 0; //!!! c@22: c@21: eprintln "dropping \(d)"; c@21: drop d octlist; c@21: done cqblocks [1..octaves]); c@14: c@17: assembleBlock bits = c@19: (eprintln "assembleBlock: structure of bits is:"; c@19: eprintln (map length bits); c@19: c@19: rows = octaves * kdata.binsPerOctave; c@19: columns = (pow 2 (octaves - 1)) * kdata.atomsPerFrame; c@19: c@18: cm.generate do row col: c@19: c@19: // bits structure: [1,2,4,8,...] c@19: c@19: // each elt of bits is a list of the chunks that should c@19: // make up this block in that octave (lowest octave first) c@19: c@19: // each chunk has atomsPerFrame * binsPerOctave elts in it c@19: c@19: // row is disposed with 0 at the top, highest octave (in c@19: // both pitch and index into bits structure) c@19: c@18: oct = int (row / binsPerOctave); c@19: binNo = row % kdata.binsPerOctave; c@21: c@19: chunks = pow 2 oct; c@21: colsPerAtom = int (columns / (chunks * kdata.atomsPerFrame)); c@21: atomNo = int (col / colsPerAtom); c@21: atomOffset = col % colsPerAtom; c@18: c@40: if atomOffset == 0 and atomNo < length bits[oct] then c@21: bits[oct][atomNo][binNo]; c@20: else c@20: cplx.zero c@20: fi; c@19: c@19: done { rows, columns }; c@19: ); c@15: c@17: processOctaveLists octs = c@17: case octs[0] of c@17: block::rest: c@19: (toAssemble = array c@19: (map do oct: c@21: n = kdata.atomsPerFrame * pow 2 oct; c@17: if not empty? octs[oct] then c@19: forBlock = array (take n octs[oct]); c@17: octs[oct] := drop n octs[oct]; c@17: forBlock c@17: else c@19: array [] c@17: fi c@19: done (keys octs)); c@17: assembleBlock toAssemble :. \(processOctaveLists octs)); c@17: _: [] c@15: esac; c@15: c@19: eprintln "cqblocks has \(length cqblocks) entries"; c@15: c@17: octaveLists = [:]; c@19: c@19: cqblocks = array cqblocks; c@17: for [1..octaves] do oct: c@17: octaveLists[octaves - oct] := cqblocks[oct-1]; c@17: done; c@17: /* c@17: \() (map2 do octlist octave: c@17: println "oct \(octaves) - \(octave) = \(octaves - octave)"; c@17: octaveLists[octaves - octave] := octlist c@17: done cqblocks [1..octaves]); c@17: */ c@19: eprintln "octaveLists keys are: \(keys octaveLists)"; c@15: c@40: { c@40: kernel = kdata with { c@40: binFrequencies = array (concat c@40: (map do octave: c@40: map do freq: c@40: freq / (pow 2 octave); c@40: done (reverse (list kdata.binFrequencies)) c@40: done [0..octaves-1])) c@40: }, c@40: output = processOctaveLists octaveLists c@40: } c@10: ); c@10: c@37: { cqt } c@10: