Skip to content

Commit 3e87433

Browse files
(Gsoc'21)💚Added tests to fft.js
1 parent f690db4 commit 3e87433

File tree

2 files changed

+231
-46
lines changed

2 files changed

+231
-46
lines changed

src/fft.js

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -171,13 +171,12 @@ class FFT {
171171
*
172172
*/
173173
waveform() {
174-
var bins, mode;
174+
var mode;
175175
var normalArray = new Array();
176176

177177
for (var i = 0; i < arguments.length; i++) {
178178
if (typeof arguments[i] === 'number') {
179-
bins = arguments[i];
180-
this.analyser.fftSize = bins * 2;
179+
this.bins = arguments[i];
181180
}
182181
if (typeof arguments[i] === 'string') {
183182
mode = arguments[i];
@@ -275,7 +274,6 @@ class FFT {
275274
for (var i = 0; i < arguments.length; i++) {
276275
if (typeof arguments[i] === 'number') {
277276
this.bins = arguments[i];
278-
this.analyser.fftSize = this.bins * 2;
279277
}
280278
if (typeof arguments[i] === 'string') {
281279
mode = arguments[i];

test/tests/p5.FFT.js

Lines changed: 229 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,245 @@
11
const expect = chai.expect;
22

33
describe('p5.FFT', function () {
4-
let fft;
4+
it('can be created and disposed', function () {
5+
let fft = new p5.FFT();
56

6-
beforeEach(function () {
7-
fft = new p5.FFT();
8-
});
7+
expect(fft.input).to.have.property('context');
8+
expect(fft.input).to.have.property('frequencyBinCount');
9+
expect(fft.analyser).to.have.property('context');
10+
expect(fft.analyser).to.have.property('frequencyBinCount');
11+
expect(fft.bins).to.equal(1024);
12+
expect(fft.freqDomain).to.be.a('Uint8Array').to.have.length(1024);
13+
expect(fft.timeDomain).to.be.a('Uint8Array').to.have.length(1024);
914

10-
afterEach(function () {
11-
fft.dispose();
12-
});
15+
expect(fft.bass).to.deep.equal([20, 140]);
16+
expect(fft.lowMid).to.deep.equal([140, 400]);
17+
expect(fft.mid).to.deep.equal([400, 2600]);
18+
expect(fft.highMid).to.deep.equal([2600, 5200]);
19+
expect(fft.treble).to.deep.equal([5200, 14000]);
1320

14-
it('has default bins of 1024', function () {
15-
expect(fft.bins).to.equal(1024);
16-
});
21+
expect(p5.soundOut.soundArray).to.include(fft);
1722

18-
it('has default smoothing of 0.8', function () {
19-
expect(fft.smooth()).to.equal(0.8);
20-
expect(fft.smoothing).to.equal(0.8);
23+
fft.dispose();
24+
expect(p5.soundOut.soundArray).to.not.include(fft);
25+
expect(fft).to.not.have.property('analyser');
2126
});
2227

23-
it('accepts smoothing and bins as args', function () {
24-
fft.dispose();
25-
fft = new p5.FFT(0, 128);
26-
expect(fft.smoothing).to.equal(0);
28+
it('can be created with smoothing and bins value', function () {
29+
let fft = new p5.FFT(0.4, 128);
30+
expect(fft.smoothing).to.equal(0.4);
2731
expect(fft.bins).to.equal(128);
2832
});
33+
it('can get and set smoothing and bins value', function () {
34+
let fft = new p5.FFT();
35+
expect(fft.analyser.smoothingTimeConstant).to.equal(0.8);
36+
expect(fft.analyser.fftSize).to.equal(2048);
37+
fft.smoothing = 0.23;
38+
fft.bins = 64;
39+
expect(fft.analyser.smoothingTimeConstant).to.equal(0.23);
40+
expect(fft.smoothing).to.equal(0.23);
41+
expect(fft.analyser.fftSize).to.equal(128);
42+
expect(fft.bins).to.equal(64);
43+
});
44+
it('can handle out of range smoothing and bins values', function () {
45+
let fft = new p5.FFT();
46+
//smoothing
47+
expect(() => fft.smooth(-1)).to.throw();
48+
expect(() => fft.smooth(1.1)).to.throw();
49+
fft.smoothing = 0.54;
50+
expect(() => fft.smooth('some bad param')).to.throw();
51+
expect(fft.smoothing).to.equal(0.54);
2952

30-
it('can set smoothing to zero', function () {
31-
fft.smooth(0);
32-
expect(fft.smoothing).to.equal(0);
33-
expect(fft.smooth()).to.equal(0);
34-
fft.smooth(0.9);
35-
expect(fft.smoothing).to.equal(0.9);
36-
expect(fft.smooth()).to.equal(0.9);
53+
//bins
54+
expect(() => (fft.bins = 8)).to.throw();
55+
expect(() => (fft.bins = 13)).to.throw();
56+
expect(() => (fft.bins = -1)).to.throw();
57+
fft.bins = 512;
58+
expect(() => (fft.bins = 'some bad param')).to.throw();
59+
expect(fft.bins).to.equal(512);
3760
});
3861

39-
it('handles smoothing values out of range', function () {
40-
expect(fft.smooth()).to.equal(0.8);
41-
try {
42-
fft.smooth(-1);
43-
expect.fail();
44-
} catch (e) {
45-
expect(e).to.be.an.instanceof(Error);
46-
}
47-
expect(fft.smoothing).to.equal(0.8);
48-
expect(fft.smooth()).to.equal(0.8);
49-
try {
50-
fft.smooth('some bad param');
51-
expect.fail();
52-
} catch (e) {
53-
expect(e).to.be.an.instanceof(Error);
54-
}
55-
expect(fft.smoothing).to.equal(0.8);
56-
expect(fft.smooth()).to.equal(0.8);
62+
describe('methods', function () {
63+
it('can set input source of fft', function () {
64+
let osc = new p5.SinOsc();
65+
let fft = new p5.FFT();
66+
fft.setInput(); //to p5sound.fftMeter
67+
fft.setInput(osc); //to osc.output
68+
fft.setInput(osc.output);
69+
});
70+
71+
it('can get the waveform', function () {
72+
let fft = new p5.FFT();
73+
let source = new p5.AudioIn();
74+
fft.setInput(source);
75+
//no mode
76+
let waveform = fft.waveform();
77+
expect(waveform).to.be.a('array').to.have.length(1024);
78+
expect(fft.timeDomain).to.be.a('Uint8Array').to.have.length(1024); //timeToInt
79+
});
80+
it('can get waveform with parameters', function () {
81+
let fft = new p5.FFT();
82+
let source = new p5.AudioIn();
83+
fft.setInput(source);
84+
85+
let waveform = fft.waveform(256);
86+
expect(fft.bins).to.equal(256);
87+
expect(waveform).to.be.a('array').to.have.length(1024);
88+
expect(fft.timeDomain).to.be.a('Uint8Array').to.have.length(1024);
89+
90+
waveform = fft.waveform(64, 'float32');
91+
expect(fft.bins).to.equal(64);
92+
expect(waveform).to.be.a('Float32Array').to.have.length(64);
93+
expect(fft.timeDomain).to.be.a('Float32Array').to.have.length(64); // timeToFloat
94+
95+
// parameters position doesn't matter if there is a number and a string
96+
waveform = fft.waveform(undefined, 'float32', 128);
97+
expect(fft.bins).to.equal(128);
98+
expect(waveform).to.be.a('Float32Array').to.have.length(64);
99+
expect(fft.timeDomain).to.be.a('Float32Array').to.have.length(64);
100+
});
101+
102+
it('can analyze a fft', function () {
103+
let fft = new p5.FFT();
104+
//no mode
105+
let spectrum = fft.analyze();
106+
expect(fft.freqDomain).to.be.a('Uint8Array').to.have.length(1024);
107+
expect(spectrum).to.be.a('array').to.have.length(1024);
108+
});
109+
it('can get analyze with parameters', function () {
110+
let fft = new p5.FFT();
111+
let source = new p5.AudioIn();
112+
fft.setInput(source);
113+
114+
let spectrum = fft.analyze(256);
115+
expect(fft.bins).to.equal(256);
116+
expect(spectrum).to.be.a('array').to.have.length(1024);
117+
118+
spectrum = fft.analyze(64, 'DB');
119+
expect(fft.bins).to.equal(64);
120+
expect(spectrum).to.be.a('Float32Array').to.have.length(64); //freqToFloat
121+
expect(fft.freqDomain).to.be.a('Float32Array').to.have.length(64);
122+
123+
// parameters position doesn't matter if there is a number and a string
124+
spectrum = fft.analyze(undefined, 'dB', 128);
125+
expect(fft.bins).to.equal(128);
126+
expect(spectrum).to.be.a('Float32Array').to.have.length(64);
127+
expect(fft.freqDomain).to.be.a('Float32Array').to.have.length(64);
128+
});
129+
130+
it('can get energy at a frequency', function () {
131+
let fft = new p5.FFT();
132+
let energy = fft.getEnergy('bass');
133+
expect(energy).to.equal(0); // as fft.freDomain is all zeros
134+
});
135+
it('can get energy from playing a file', function (done) {
136+
let recorder = new p5.SoundRecorder();
137+
let fft = new p5.FFT();
138+
let inputSoundFile = new p5.SoundFile();
139+
inputSoundFile.setBuffer([[1], [1]]);
140+
const recordingDuration =
141+
recorder.bufferSize / p5.soundOut.audiocontext.sampleRate;
142+
143+
const outputSoundFile = new p5.SoundFile();
144+
inputSoundFile.loop();
145+
fft.setInput(inputSoundFile);
146+
recorder.record(outputSoundFile, recordingDuration, function () {
147+
fft.analyze();
148+
expect(fft.getEnergy('highMid')).to.not.equal(0); //string param
149+
expect(fft.getEnergy(1000)).to.not.equal(0); //one param
150+
expect(fft.getEnergy(100, 20000)).to.not.equal(0); // two params
151+
expect(fft.getEnergy(1000, 200)).to.not.equal(0);
152+
outputSoundFile.dispose();
153+
recorder.dispose();
154+
inputSoundFile.dispose();
155+
done();
156+
});
157+
});
158+
it('getEnergy can throw error for invalid inputs', function () {
159+
let fft = new p5.FFT();
160+
expect(() => {
161+
fft.getEnergy();
162+
}).to.throw;
163+
expect(() => {
164+
fft.getEnergy('bad param', 100);
165+
}).to.throw;
166+
});
167+
168+
it('can get centroid', function (done) {
169+
let recorder = new p5.SoundRecorder();
170+
let fft = new p5.FFT();
171+
let inputSoundFile = new p5.SoundFile();
172+
inputSoundFile.setBuffer([[1], [1]]);
173+
const recordingDuration =
174+
recorder.bufferSize / p5.soundOut.audiocontext.sampleRate;
175+
176+
const outputSoundFile = new p5.SoundFile();
177+
expect(fft.getCentroid()).to.be.a('number').to.equal(0);
178+
inputSoundFile.loop();
179+
fft.setInput(inputSoundFile);
180+
recorder.record(outputSoundFile, recordingDuration, function () {
181+
fft.analyze();
182+
expect(fft.getCentroid()).to.be.a('number').to.not.equal(0);
183+
outputSoundFile.dispose();
184+
recorder.dispose();
185+
inputSoundFile.dispose();
186+
done();
187+
});
188+
});
189+
190+
it('can get and set smoothing value using smooth function', function () {
191+
let fft = new p5.FFT();
192+
expect(fft.smooth()).to.equal(0.8);
193+
expect(fft.smooth(0.63)).to.equal(0.63);
194+
});
195+
196+
it('can get octave bands', function () {
197+
let fft = new p5.FFT();
198+
let sqrt2 = Math.pow(2, 1 / 2);
199+
let i;
200+
let octaveBands = fft.getOctaveBands(1);
201+
expect(octaveBands[0].ctr).to.equal(15.625);
202+
expect(octaveBands[0].lo).to.be.approximately(15.625 / sqrt2, 0.01);
203+
expect(octaveBands[0].hi).to.be.approximately(15.625 * sqrt2, 0.01);
204+
for (i = 1; i < octaveBands.length; i++) {
205+
let prev_ctr = octaveBands[i - 1].ctr;
206+
let prev_hi = octaveBands[i - 1].hi;
207+
expect(octaveBands[i].ctr).to.equal(prev_ctr * 2);
208+
expect(octaveBands[i].lo).to.equal(prev_hi);
209+
expect(octaveBands[i].hi).to.equal(prev_ctr * 2 * sqrt2);
210+
}
211+
expect(octaveBands[i - 1].lo).to.be.lessThan(
212+
p5.soundOut.audiocontext.sampleRate / 2
213+
); //last band lo to be less than sampleRate
214+
expect(octaveBands[i - 1].hi).to.be.greaterThan(
215+
p5.soundOut.audiocontext.sampleRate / 2
216+
);
217+
});
218+
219+
it('can get linear and log averages', function (done) {
220+
let recorder = new p5.SoundRecorder();
221+
let fft = new p5.FFT();
222+
let inputSoundFile = new p5.SoundFile();
223+
inputSoundFile.setBuffer([[1], [1]]);
224+
const recordingDuration =
225+
recorder.bufferSize / p5.soundOut.audiocontext.sampleRate;
226+
227+
const outputSoundFile = new p5.SoundFile();
228+
inputSoundFile.loop();
229+
fft.setInput(inputSoundFile);
230+
recorder.record(outputSoundFile, recordingDuration, function () {
231+
fft.analyze();
232+
expect(fft.linAverages().length).to.equal(16);
233+
expect(fft.linAverages(8).length).to.equal(8);
234+
let octaveBands = fft.getOctaveBands();
235+
expect(fft.logAverages(octaveBands).length).to.equal(
236+
octaveBands.length
237+
);
238+
outputSoundFile.dispose();
239+
recorder.dispose();
240+
inputSoundFile.dispose();
241+
done();
242+
});
243+
});
57244
});
58245
});

0 commit comments

Comments
 (0)