Chris@366: /* Chris@366: Constant-Q library Chris@366: Copyright (c) 2013-2014 Queen Mary, University of London Chris@366: Chris@366: Permission is hereby granted, free of charge, to any person Chris@366: obtaining a copy of this software and associated documentation Chris@366: files (the "Software"), to deal in the Software without Chris@366: restriction, including without limitation the rights to use, copy, Chris@366: modify, merge, publish, distribute, sublicense, and/or sell copies Chris@366: of the Software, and to permit persons to whom the Software is Chris@366: furnished to do so, subject to the following conditions: Chris@366: Chris@366: The above copyright notice and this permission notice shall be Chris@366: included in all copies or substantial portions of the Software. Chris@366: Chris@366: THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, Chris@366: EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF Chris@366: MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND Chris@366: NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY Chris@366: CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF Chris@366: CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION Chris@366: WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Chris@366: Chris@366: Except as contained in this notice, the names of the Centre for Chris@366: Digital Music; Queen Mary, University of London; and Chris Cannam Chris@366: shall not be used in advertising or otherwise to promote the sale, Chris@366: use or other dealings in this Software without prior written Chris@366: authorization. Chris@366: */ Chris@366: Chris@366: module test_frequency; Chris@366: Chris@366: mat = load may.matrix; Chris@366: vec = load may.vector; Chris@366: win = load may.signal.window; Chris@366: mm = load may.mathmisc; Chris@366: cm = load may.matrix.complex; Chris@366: syn = load may.stream.syntheticstream; Chris@366: plot = load may.plot; Chris@366: Chris@366: { compare } = load may.test; Chris@366: Chris@366: { cqt } = load cqt; Chris@366: Chris@366: // Test with a single windowed sinusoid, repeating at various frequencies Chris@366: Chris@366: sinTestStream sampleRate duration signalFreq = // duration is in samples Chris@366: (sin = syn.sinusoid sampleRate signalFreq; Chris@366: chunk = mat.getRow 0 (sin.read duration); Chris@366: syn.precalculatedMono sampleRate (win.windowed win.hann chunk)); Chris@366: Chris@366: // We want to make a CQ transform spanning more than one octave, but Chris@366: // not going all the way to fs/2 so we can test it also with Chris@366: // frequencies above and below its extents Chris@366: Chris@366: sampleRate = 100; Chris@366: Chris@366: // fs/2 = 50 so 10->40 gives us 2 octaves Chris@366: cqmin = 10; Chris@366: cqmax = 40; Chris@366: bpo = 4; // fairly arbitrary Chris@366: Chris@366: testFreqs = map (* 5) [ 0..10 ]; Chris@366: duration = sampleRate * 2; Chris@366: Chris@366: threshold = 0.08; Chris@366: Chris@366: streamBuilder = sinTestStream sampleRate duration; Chris@366: Chris@366: binForFreq f = Chris@366: mm.round (bpo * mm.log2 (f / cqmin)) - 1; Chris@366: Chris@366: report message matrix = Chris@366: (eprintln message; Chris@366: eprintln "matrix is:"; Chris@366: mat.eprint matrix); Chris@366: // chart = plot.plot [Grid matrix]; Chris@366: // sleep 100; Chris@366: // chart#dispose()); Chris@366: Chris@366: tests = mapIntoHash Chris@366: do f: "freq_\(f)" done Chris@366: do f: \( Chris@366: str = streamBuilder f; Chris@366: cq = cqt { maxFreq = cqmax, minFreq = cqmin, binsPerOctave = bpo } str; Chris@366: spec = cq.cqSpectrogram; Chris@366: rightSize = all id Chris@366: (map do s: Chris@366: compare (mat.size s) { Chris@366: rows = cq.kernel.binsPerOctave * cq.octaves, Chris@366: columns = cq.kernel.atomsPerFrame * mm.pow 2 (cq.octaves - 1) Chris@366: } Chris@366: done spec); Chris@366: m = mat.concatHorizontal spec; Chris@366: // println "binFrequencies = \(cq.kernel.binFrequencies)"; Chris@366: // println "binForFreq \(f) = \(binForFreq f)"; Chris@366: var colno = 0; Chris@366: success = all id Chris@366: (rightSize :: map do c: Chris@366: // The test passes for this column if: Chris@366: // Chris@366: // * the max bin is the expected one, or Chris@366: // Chris@366: // * the expected max is out of range entirely (but Chris@366: // we need to test _something_ in this case -- Chris@366: // what?), or Chris@366: // Chris@366: // * all bins are below a threshold, or Chris@366: // Chris@366: // * this is an odd column and the expected max is in Chris@366: // the lower octave Chris@366: // Chris@366: // We should also check that all values in the lower Chris@366: // octave are zero for odd columns. Chris@366: // Chris@366: expected = binForFreq f; Chris@366: good = Chris@366: (expected < 0 or expected >= vec.length c) or Chris@366: ((colno % 2 == 1) and expected < (vec.length c / 2)) or Chris@366: (vec.max c < threshold) or Chris@366: (vec.maxindex c == binForFreq f); Chris@366: if (not good) then Chris@366: report " * bad! maxindex \(vec.maxindex c) != expected \(binForFreq f) for freq \(f) in column \(colno) of \(mat.width m): column is \(vec.list c)" m; Chris@366: fi; Chris@366: colno := colno + 1; Chris@366: good; Chris@366: done (mat.asColumns m)); Chris@366: success; Chris@366: ) done Chris@366: testFreqs; Chris@366: Chris@366: tests is hash boolean>