view 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
line wrap: on
line source

module cqt;

cqtkernel = load cqtkernel;
resample = load may.stream.resample;
manipulate = load may.stream.manipulate;
syn = load may.stream.syntheticstream;
cm = load may.matrix.complex;
mat = load may.matrix;
framer = load may.stream.framer;
cplx = load may.complex;
fft = load may.transform.fft;
vec = load may.vector;

{ pow, round, floor, ceil, log2, nextPowerOfTwo } = load may.mathmisc;

cqt str =
   (sampleRate = str.sampleRate;
    maxFreq = sampleRate/2;
    minFreq = 40;
    binsPerOctave = 24;

println "Here";

    octaves = ceil (log2 (maxFreq / minFreq));

println "Here: about to calculate stuff with \(octaves)";

    actualMinFreq = (maxFreq / (pow 2 octaves)) * (pow 2 (1/binsPerOctave));

    println "sampleRate = \(sampleRate), maxFreq = \(maxFreq), minFreq = \(minFreq), actualMinFreq = \(actualMinFreq), octaves = \(octaves), binsPerOctave = \(binsPerOctave)";

    kdata = cqtkernel.makeKernel { sampleRate, maxFreq, binsPerOctave };

    println "atomsPerFrame = \(kdata.atomsPerFrame)";

    streams = manipulate.duplicated octaves str;

    //!!! can't be right!
    kernel = cm.transposed (cm.conjugateTransposed kdata.kernel);

    println "have kernel";

    fftFunc = fft.forward kdata.fftSize;

    cqblocks =
        map do octave:
            frames = framer.monoFrames //!!! mono for now
                { framesize = kdata.fftSize, hop = kdata.fftHop }
                (resample.decimated (pow 2 octave) streams[octave]);
            map do frame:
                freq = fftFunc (cplx.complexArray frame (vec.zeros kdata.fftSize));
                cm.product kernel (cm.newComplexColumnVector freq);
            done frames;
        done [0..octaves-1];

    // The cqblocks list is a list<list<matrix>>. Each top-level list
    // corresponds to an octave, from highest to lowest, each having
    // twice as many elements in its list as the next octave. The
    // sub-lists are sampled in time with an effective spacing of
    // fftSize * 2^(octave-1) audio frames, and the matrices are row
    // vectors with atomsPerFrame * binsPerOctave complex elements.
    //
    // ***
    // 
    // In a typical constant-Q structure, each (2^(octaves-1) *
    // fftHop) input frames gives us an output structure conceptually
    // like this:
    //
    // [][][][][][][][]   <- fftHop frames per highest-octave output value
    // [][][][][][][][]      layered as many times as binsPerOctave (here 2)
    // [--][--][--][--]   <- fftHop*2 frames for the next lower octave
    // [--][--][--][--]      etc
    // [------][------]
    // [------][------]
    // [--------------]
    // [--------------]
    //
    // ***
    //
    // But the kernel we're using here has more than one temporally
    // spaced atom; each individual cell is a row vector with
    // atomsPerFrame * binsPerOctave elements, but that actually
    // represents a rectangular matrix of result cells with width
    // atomsPerFrame and height binsPerOctave. The columns of this
    // matrix (the atoms) then need to be spaced by 2^(octave-1)
    // relative to those from the highest octave.

    // Reshape each row vector into the appropriate rectangular matrix
    cqblocks = map do octlist:
        map do rv:
            cm.generate do row col:
                cm.at rv ((row * kdata.atomsPerFrame) + col) 0
            done {
                rows = kdata.binsPerOctave,
                columns = kdata.atomsPerFrame
            }
        done octlist
    done cqblocks;

//println "lengths per oct: \(map length cqblocks)";

    // Slice each octave's blocks into a list of columns with zeros
    // interspersed. This doesn't look very elegant (?)

    cqblocks = map2 do octlist octave: concat
           (map do m: concat
               (map do col: 
//                    col :: map \(cplx.zeros kdata.binsPerOctave) [1..pow 2 octave - 1]
                    map \col [0..pow 2 octave - 1]
                done (cm.asColumns m))
            done octlist)
        done cqblocks [0..octaves-1];
  
    // Then pile up the slices into taller slices spanning all octaves
      
    pileOf octaves acc =
        case (head octaves) of
        h::t:
           (heads = map head octaves;
//            tall = concat (map reverse heads); // are the bpos values low->high or high->low?
            tall = concat heads;
            pileOf (map tail octaves) (acc ++ [tall]));
         _: acc;
        esac;

    pileOf cqblocks [];

//println "lengths per oct: \(map length cqblocks)";


//cqblocks;
    );

testStream = manipulate.withDuration 96000 (syn.sinusoid 48000 500);

println "have test stream";

cqt testStream;