view yeti/cqt.yeti @ 50:e64ea86fe781

Remove debug out
author Chris Cannam <c.cannam@qmul.ac.uk>
date Thu, 28 Nov 2013 11:00:33 +0000
parents 3d6397e43671
children a219bab90abe
line wrap: on
line source

module cqt;

cqtkernel = load cqtkernel;
resample = load may.stream.resample;
manipulate = load may.stream.manipulate;
cm = load may.matrix.complex;
framer = load may.stream.framer;
cplx = load may.complex;
fft = load may.transform.fft;
vec = load may.vector;
ch = load may.stream.channels;

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

cqt { maxFreq, minFreq, binsPerOctave } str =
   (sampleRate = str.sampleRate;
    octaves = ceil (log2 (maxFreq / minFreq));
    actualMinFreq = (maxFreq / (pow 2 octaves)) * (pow 2 (1/binsPerOctave));

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

    eprintln "sampleRate = \(sampleRate), maxFreq = \(maxFreq), minFreq = \(minFreq), actualMinFreq = \(actualMinFreq), octaves = \(octaves), binsPerOctave = \(binsPerOctave), fftSize = \(kdata.fftSize), hop = \(kdata.fftHop)";

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

    padding = (kdata.fftSize * (pow 2 (octaves-1)));

eprintln "padding = \(padding)";

    str = manipulate.paddedBy padding str;

    streams = manipulate.duplicated octaves str;

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

    eprintln "have kernel";

    fftFunc = fft.forward kdata.fftSize;

    cqblocks =
        map do octave:
            frames = map ch.mixedDown //!!! mono for now
               (framer.frames kdata.fftSize [ Hop kdata.fftHop, Padded false ]
                   (resample.decimated (pow 2 octave) streams[octave]));
            map do frame:
                freq = fftFunc (cplx.complexArray frame (vec.zeros kdata.fftSize));
// eprintln "octave = \(octave), frame = \(vec.list frame)";
// eprintln "octave = \(octave), freq = \(freq)";
                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
    // and split into single-atom columns

    emptyHops = kdata.firstCentre / kdata.atomSpacing; //!!! int? round?
    maxDrop = emptyHops * (pow 2 (octaves-1)) - emptyHops;
    eprintln "maxDrop = \(maxDrop)";

    cqblocks =
        map do octlist:
            concatMap do rv:
                cm.asColumns
                   (cm.generate do row col:
                        cm.at rv ((row * kdata.atomsPerFrame) + col) 0
                    done {
                        rows = kdata.binsPerOctave,
                        columns = kdata.atomsPerFrame
                    })
            done octlist
        done cqblocks;

    cqblocks = array (map2 do octlist octave:
        d = emptyHops * (pow 2 (octaves-octave)) - emptyHops;

//        d = 0; //!!!

        eprintln "dropping \(d)";
        drop d octlist;
    done cqblocks [1..octaves]);

    assembleBlock bits =
       (eprintln "assembleBlock: structure of bits is:";
        eprintln (map length bits);

        rows = octaves * kdata.binsPerOctave;
        columns = (pow 2 (octaves - 1)) * kdata.atomsPerFrame;

        cm.generate do row col:

            // bits structure: [1,2,4,8,...]

            // each elt of bits is a list of the chunks that should
            // make up this block in that octave (lowest octave first)

            // each chunk has atomsPerFrame * binsPerOctave elts in it

            // row is disposed with 0 at the top, highest octave (in
            // both pitch and index into bits structure)

            oct = int (row / binsPerOctave);
            binNo = row % kdata.binsPerOctave;

            chunks = pow 2 oct;
            colsPerAtom = int (columns / (chunks * kdata.atomsPerFrame));
            atomNo = int (col / colsPerAtom);
            atomOffset = col % colsPerAtom;

            if atomOffset == 0 and atomNo < length bits[oct] then
                bits[oct][atomNo][binNo];
            else
                cplx.zero
            fi;

        done { rows, columns };
        );

    processOctaveLists octs =
        case octs[0] of
        block::rest:
           (toAssemble = array 
               (map do oct:
                    n = kdata.atomsPerFrame * pow 2 oct;
                    if not empty? octs[oct] then
                        forBlock = array (take n octs[oct]);
                        octs[oct] := drop n octs[oct];
                        forBlock
                    else
                        array []
                    fi
                done (keys octs));
            assembleBlock toAssemble :. \(processOctaveLists octs));
         _: []
        esac;

eprintln "cqblocks has \(length cqblocks) entries";

    octaveLists = [:];

    cqblocks = array cqblocks;
    for [1..octaves] do oct:
        octaveLists[octaves - oct] := cqblocks[oct-1];
    done;
/*
    \() (map2 do octlist octave:
println "oct \(octaves) - \(octave) = \(octaves - octave)";
             octaveLists[octaves - octave] := octlist 
         done cqblocks [1..octaves]);
*/
eprintln "octaveLists keys are: \(keys octaveLists)";

    {
        kernel = kdata with {
            binFrequencies = array
               (concatMap do octave:
                    map do freq:
                        freq / (pow 2 octave);
                    done (reverse (list kdata.binFrequencies))
                done [0..octaves-1])
        },
        output = processOctaveLists octaveLists
    }
    );

{ cqt }