c@65
|
1
|
c@65
|
2 module test_frequency;
|
c@65
|
3
|
c@65
|
4 mat = load may.matrix;
|
c@65
|
5 vec = load may.vector;
|
c@65
|
6 win = load may.signal.window;
|
c@65
|
7 mm = load may.mathmisc;
|
c@65
|
8 cm = load may.matrix.complex;
|
c@65
|
9 syn = load may.stream.syntheticstream;
|
c@67
|
10 plot = load may.plot;
|
c@65
|
11
|
c@65
|
12 { cqt } = load cqt;
|
c@65
|
13
|
c@65
|
14 // Test with a single windowed sinusoid, repeating at various frequencies
|
c@65
|
15
|
c@65
|
16 sinTestStream sampleRate duration signalFreq = // duration is in samples
|
c@65
|
17 (sin = syn.sinusoid sampleRate signalFreq;
|
c@65
|
18 chunk = mat.getRow 0 (sin.read duration);
|
c@65
|
19 syn.precalculatedMono sampleRate (win.windowed win.hann chunk));
|
c@65
|
20
|
c@65
|
21 // We want to make a CQ transform spanning more than one octave, but
|
c@65
|
22 // not going all the way to fs/2 so we can test it also with
|
c@65
|
23 // frequencies above and below its extents
|
c@65
|
24
|
c@65
|
25 sampleRate = 100;
|
c@65
|
26
|
c@65
|
27 // fs/2 = 50 so 10->40 gives us 2 octaves
|
c@65
|
28 cqmin = 10;
|
c@65
|
29 cqmax = 40;
|
c@65
|
30 bpo = 4; // fairly arbitrary
|
c@65
|
31
|
c@65
|
32 testFreqs = map (* 5) [ 0..10 ];
|
c@65
|
33 duration = sampleRate * 2;
|
c@65
|
34
|
c@68
|
35 threshold = 0.08;
|
c@67
|
36
|
c@65
|
37 streamBuilder = sinTestStream sampleRate duration;
|
c@65
|
38
|
c@65
|
39 binForFreq f =
|
c@65
|
40 mm.round (bpo * mm.log2 (f / cqmin)) - 1;
|
c@65
|
41
|
c@68
|
42 report message matrix =
|
c@68
|
43 (eprintln message;
|
c@68
|
44 eprintln "matrix is:";
|
c@68
|
45 mat.eprint matrix;
|
c@68
|
46 chart = plot.plot [Grid matrix];
|
c@68
|
47 sleep 100;
|
c@68
|
48 chart#dispose());
|
c@68
|
49
|
c@65
|
50 tests = mapIntoHash
|
c@65
|
51 do f: "freq_\(f)" done
|
c@65
|
52 do f: \(
|
c@65
|
53 str = streamBuilder f;
|
c@65
|
54 cq = cqt { maxFreq = cqmax, minFreq = cqmin, binsPerOctave = bpo } str;
|
c@65
|
55 m = mat.concatHorizontal (map cm.magnitudes cq.output);
|
c@65
|
56 // println "binFrequencies = \(cq.kernel.binFrequencies)";
|
c@65
|
57 // println "binForFreq \(f) = \(binForFreq f)";
|
c@67
|
58 var colno = 0;
|
c@65
|
59 success = all id
|
c@65
|
60 (map do c:
|
c@68
|
61 // The test passes for this column if:
|
c@68
|
62 //
|
c@68
|
63 // * the max bin is the expected one, or
|
c@68
|
64 //
|
c@68
|
65 // * the expected max is out of range entirely (but
|
c@68
|
66 // we need to test _something_ in this case --
|
c@68
|
67 // what?), or
|
c@68
|
68 //
|
c@68
|
69 // * all bins are below a threshold, or
|
c@68
|
70 //
|
c@68
|
71 // * this is an odd column and the expected max is in
|
c@68
|
72 // the lower octave
|
c@68
|
73 //
|
c@68
|
74 // We should also check that all values in the lower
|
c@68
|
75 // octave are zero for odd columns.
|
c@68
|
76 //
|
c@65
|
77 expected = binForFreq f;
|
c@65
|
78 good =
|
c@65
|
79 (expected < 0 or expected >= vec.length c) or
|
c@68
|
80 ((colno % 2 == 1) and expected < (vec.length c / 2)) or
|
c@67
|
81 (vec.max c < threshold) or
|
c@65
|
82 (vec.maxindex c == binForFreq f);
|
c@65
|
83 if (not good) then
|
c@68
|
84 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;
|
c@65
|
85 fi;
|
c@67
|
86 colno := colno + 1;
|
c@65
|
87 good;
|
c@65
|
88 done (mat.asColumns m));
|
c@65
|
89 success;
|
c@65
|
90 ) done
|
c@65
|
91 testFreqs;
|
c@65
|
92
|
c@65
|
93 tests is hash<string, () -> boolean>
|