CSL  5.2
Test_Effects.cpp
Go to the documentation of this file.
1 //
2 // Test_Effects.cpp -- C main functions for the basic CSL effect and filter tests.
3 // See the copyright notice and acknowledgment of authors in the file COPYRIGHT
4 //
5 // This program simply reads the run_tests() function (at the bottom of this file)
6 // and executes a list of basic CSL tests
7 //
8 // Examples to add
9 // chords
10 // Microphone input
11 // JUCE MIDI
12 
13 #ifndef USE_JUCE
14 #define USE_TEST_MAIN // use the main() function in test_support.h
15 #include "Test_Support.cpp" // include all of CSL core and the test support functions
16 #else
17 #include "Test_Support.h"
18 #endif
19 
20 #include "RingBuffer.h" /// Utility circular buffer
21 #include "Clipper.h"
22 #include "FIR.h"
23 #include "InOut.h"
24 
25 using namespace csl; // this is the namespace, dummy!
26 
27 /////////////////////// Here are the actual unit tests ////////////////////
28 
29 /// Clip some sound
30 
31 void testClipper() {
32  Osc vox(110); // We need some sound, so . . . let's get a sine.
33 // ADSR vol(3.0, 0.3, 2.0, 0.2, 0.5); // The amplitude envelope.
34 // vox.setScale(vol); // We set the envelope to affect the sine.
35  Clipper clipIt(vox, -0.3, 0.3); // To use clipper, just specify a min and max value.
36  logMsg("playing clipped sin...");
37  runTest(clipIt);
38  logMsg("done.\n");
39 }
40 
41 /// Test the FIR filter BP 200-300 Hz on pink noise
42 
43 void testFIR() {
44  PinkNoise noise; // the sound source
45  double resp[2] = { 0, 1 }; // amplitudes in the 2 freq bands (i.e., hi-pass)
46  double freq[4] = { 0, 2000, 3000, 22050 }; // corner freqs of the pass, transition, and stop bands
47  double weight[2] = { 10, 20 }; // weights for error (ripple) in the 2 bands
48  FilterSpecification fs(64, 2, freq, resp, weight); // 64 taps (64-step IR), 2 bands
49  FIR vox(noise, fs); // create the filter
50  MulOp mul(vox, 10); // scale it back up
51  logMsg("playing FIR filtered noise...");
52  runTest(mul);
53  logMsg("FIR done.");
54 }
55 
56 /// Filter tests
57 
58 void testFilters() {
59  float dur = 2.f; // seconds to play each test for
60  // Butterworth high pass filter
61  WhiteNoise white1(0.1);
62  Butter butter(white1, BW_HIGH_PASS, 4000.0, 333.3); // corresponds to Q of 3
63  logMsg("playing Butterworth high-passed white noise...");
64 // butter.dump();
65  runTest(butter, dur);
66  logMsg("done.");
67 
68  // Butterworth band pass filter
69  WhiteNoise white2;
70  Butter butter1(white2, BW_BAND_PASS, 1000.f, 100.f);
71 // butter1.dump();
72  logMsg("playing Butterworth band-passed white noise...");
73 // butter1.dump();
74  runTest(butter1, dur);
75  logMsg("done.");
76 //
77  // Butterworth low pass filter
78  WhiteNoise white3;
79  Butter butter2(white3, BW_LOW_PASS, 250.f, 100.f);
80  logMsg("playing Butterworth low-passed white noise...");
81 // butter2.dump();
82  runTest(butter2, dur);
83  logMsg("done.");
84 //
85  // Butterworth band-stop filter
86  WhiteNoise white4(0.1);
87  Butter butter3(white4, BW_BAND_STOP, 3000.f, 2500.f);
88  logMsg("playing Butterworth band-rejected white noise...");
89 // butter3.dump();
90  runTest(butter3, dur);
91  logMsg("done.");
92 
93 // if (true) return;
94  // Notch filter
95  WhiteNoise white5(0.3);
96  Notch notch(white5, 500.f, 0.99995f);
97  logMsg("playing Notch filtered white noise...");
98 // notch.dump();
99  runTest(notch, dur);
100  logMsg("done.");
101 
102  // Formant filter
103  WhiteNoise white6(0.3);
104  Formant formant(white6, 100.f, 0.5f);
105  logMsg("playing Formant filtered white noise...");
106 // formant.dump();
107  runTest(formant, dur);
108  logMsg("done.");
109 
110  // Allpass filter
111  Square osc(400,0.2);
112  Allpass allpass(osc, 0.9995f);
113  logMsg("playing All-passed square wave...");
114  runTest(allpass, dur);
115  logMsg("done.");
116 
117  // a hand-built band pass filter
118  float bcoeffs[3] = {0.05f, 0.f, -0.0496f};
119  float acoeffs[2] = {-1.843f, 0.9f};
120  WhiteNoise white0;
121  Filter filter(white0, bcoeffs ,acoeffs, 3, 2);
122  logMsg("playing BP filtered white noise...");
123  runTest(filter, dur);
124  logMsg("done.");
125 
126  // Moog filter
127 // WhiteNoise white8;
128 // Moog moog(white8, 1000.f, 0.9f);
129 // logMsg("playing Moog filtered white noise...");
130 // runTest(moog, dur);
131 // logMsg("done.");
132 
133 }
134 
135 /// Test dynamic BP filter
136 
138  float dur = 6.0f; // seconds to play each test for
139  WhiteNoise white(1.0); // noise
140  RandEnvelope center(3, 1000, 2000, 600); // center/bw freq random walk
141  RandEnvelope bw(3, 100, 100, 40); // (frq, amp, offset)
142  Butter butter(white, BW_BAND_PASS, center, bw); // Butterworth BP filter
143  logMsg("playing dynamic Butterworth band-passed white noise...");
144 // butter.dump();
145 // center.trigger();
146 // bw.trigger();
147  runTest(butter, dur);
148  logMsg("done.");
149 }
150 
151 /// Test dynamic BP filter on a sound file
152 
154  float dur = 6.0f; // seconds to play each test for
155  RandEnvelope center(4, 1400, 1800, 600); // center/bw freq random walk
156  RandEnvelope bw(5, 60, 160, 40); // (frq, amp, offset, step)
157  SoundFile sfile(CGestalt::dataFolder() + "sns.aiff"); // open a speak'n'spell file
158  Butter butter(sfile, BW_BAND_PASS, center, bw); // Butterworth BP filter
159  logMsg("playing filtered snd file...");
160  sfile.trigger();
161  runTest(butter, dur);
162  logMsg("done.");
163 }
164 
165 /// Pan and mix many sines
166 
168  int num = 20; // # of layers
169  float scale = 3.0f / (float) num; // ampl scale
170  Mixer mix(2); // stereo mixer
171  for (int i = 0; i < num; i++) { // loop to add a panning, LFO-controlled osc to the mix
172  // (frq, amp, offset, step)
173  RandEnvelope * center = new RandEnvelope(3, 1800, fRandM(2000, 4000), 200);
174  RandEnvelope * bw = new RandEnvelope(3, 200, 400, 40);
175  WhiteNoise * white = new WhiteNoise(1.0); // noise
176  Butter * butter = new Butter(*white, BW_BAND_PASS, *center, *bw);
177  MulOp * mul = new MulOp(*butter, scale);
178  Osc * lfo = new Osc(fRandM(0.3, 0.6), 1, 0, fRandM(0, CSL_PI)); // panning LFO with rand phase
179  Panner * pan = new Panner(*mul, *lfo);
180  mix.addInput(*pan);
181  }
182  logMsg("playing mix of %d Butterworth band-passed white noise layers...", num);
183 // srand(getpid()); // seed the rand generator -- UNIX SPECIFIC CODE HERE
184  runTest(mix, 8.0f);
185  logMsg("done.\n");
186  mix.deleteInputs(); // clean up
187 }
188 
189 /// Play noise bursts into reverb
190 
191 void testReverb() {
192  ADSR mChiffEnv(1, 0.01, 0.01, 0.0, 1.5); // attack-chiff envelope
193  WhiteNoise mChiff; // attack-chiff noise source
194  Butter mChFilter(mChiff, BW_BAND_PASS, 2000.f, 500.f); // and filter
195  mChiffEnv.setScale(10); // scale chiff envelope
196  mChFilter.setScale(mChiffEnv); // apply chiff envelope
197  Freeverb mReverb(mChFilter); // stereo reverb
198  mReverb.setRoomSize(0.95); // longer reverb
199 
200  mChiffEnv.trigger();
201  logMsg("playing Reverb test\n");
202  theIO->setRoot(mReverb); // make some sound
203  for (unsigned i = 0; i < 4; i++) {
204  mChiffEnv.trigger();
205  sleepSec(4);
206  }
207  sleepSec(6);
208  theIO->clearRoot();
209  logMsg("done.\n");
210 }
211 
212 /// Play noise bursts into reverb
213 
215  ADSR mEnv(1, 0.005, 0.01, 0.0, 1.5); // attack-chiff envelope
216  WhiteNoise mChiff; // attack-chiff noise source
217  float ctrFrq = fRandB(3000, 1000); // filter center freq
218  Butter mFilter(mChiff, BW_BAND_PASS, ctrFrq, 500.0f); // BP filter
219  mEnv.setScale(10); // scale chiff envelope
220  mFilter.setScale(mEnv); // apply chiff envelope
221 
222  Panner mPanner(mFilter, 0.0); // stereo panner
223  Stereoverb mReverb(mPanner); // stereo reverb
224  mReverb.setRoomSize(0.988); // long reverb time
225 
226  theIO->setRoot(mReverb); // start sound output
227 #ifndef CSL_WINDOWS
228  srand(getpid()); // seed the rand generator -- UNIX SPECIFIC CODE HERE
229 #endif
230  float nap = 1.0f; // sleep time between noise bursts
231  logMsg("playing Stereoverb test\n");
232  for (unsigned i = 0; i < 10; i++) { // play a loop of notes
233  mPanner.setPosition(fRand1()); // select a random stereo position
234  mEnv.trigger(); // trigger the burst envelope
235  sleepSec(nap); // sleep a few seconds
236  nap *= 1.25f; // slow down
237  mFilter.setFrequency(fRandB(2000, 1000)); // pick a new frequency, 2k +- 1k
238  }
239  sleepSec(2); // sleep at the end to let it die out
240  theIO->clearRoot();
241  logMsg("done.\n");
242 }
243 
244 /// Play noise bursts into multi-tap delay line
245 
246 void testMultiTap() {
247  ADSR mChiffEnv(1, 0.01, 0.01, 0.0, 1.5); // attack-chiff envelope
248  WhiteNoise mChiff; // attack-chiff noise source
249  Butter mChFilter(mChiff, BW_BAND_PASS, 2000.f, 500.f); // and filter
250  mChiffEnv.setScale(10); // scale chiff envelope
251  mChFilter.setScale(mChiffEnv); // apply chiff envelope
252 
253  RingBuffer rbuf(mChFilter, 1, 22050); // mono, 1.2 sec.
254  rbuf.mTap.setOffset(2000);
255  RingBufferTap tap1(& rbuf, 4000);
256  RingBufferTap tap2(& rbuf, 8000);
257  RingBufferTap tap3(& rbuf, 12000);
258  RingBufferTap tap4(& rbuf, 16000);
259  RingBufferTap tap5(& rbuf, 20000);
260 
261  Mixer mix(2); // create a stereo mixer
262  mix.addInput(rbuf); // add the taps to the mixer
263  mix.addInput(tap1);
264  mix.addInput(tap2);
265  mix.addInput(tap3);
266  mix.addInput(tap4);
267  mix.addInput(tap5);
268 
269  mChiffEnv.trigger();
270  logMsg("");
271  logMsg("playing multi-tap delay test\n");
272  theIO->setRoot(mix); // make some sound
273  for (unsigned i = 0; i < 4; i++) {
274  mChiffEnv.trigger();
275  sleepSec(1);
276  }
277 // sleepSec(3);
278  theIO->clearRoot();
279  logMsg("done.\n");
280 }
281 
282 /// Test a block resizer by running a random gliss with a small block size
283 
285  float dur = 6.0f; // seconds to play each test for
286  Osc vox; // declare an oscillator
287  AR a_env(6, 1, 1); // dur, att, rel
288  RandEnvelope f_env(3, 80, 200, 40); // freq env = random walk
289  vox.setFrequency(f_env); // set the carrier's frequency
290  vox.setScale(a_env); // multiply index envelope by mod freq
291  a_env.trigger();
292  BlockResizer blocker(vox, 300); // small buffer, not a divisor of CSL's block size
293  logMsg("playing random gliss in a block up-sizer...");
294  runTest(blocker, dur); // run test
295  logMsg("done.");
296 }
297 
298 /// Test a block resizer by running a random gliss with a huge block size
299 
301  float dur = 6.0f; // seconds to play each test for
302  Osc vox; // declare an oscillator
303  AR a_env(6, 1, 1); // dur, att, rel
304  RandEnvelope f_env(3, 80, 200, 40); // freq env = random walk
305  vox.setFrequency(f_env); // set the carrier's frequency
306  vox.setScale(a_env); // multiply index envelope by mod freq
307  a_env.trigger();
308  BlockResizer blocker(vox, 1100); // large buffer, not a multiple of CSL's block size
309  logMsg("playing random gliss in a block down-sizer...");
310  runTest(blocker, dur); // run test
311  logMsg("done.");
312 }
313 
314 /// Test a Split/Join with a cross-over filter pair LPF to left, HPF to the right
315 
317  float dur = 6.0f; // seconds to play each test for
318  SumOfSines sos1(16, 0.5); // create a cmoplex sum-of-sines
319  sos1.createCache(); // make the cached wavetable
320  AR a_env(6, 1, 1); // dur, att, rel
321  RandEnvelope f_env(3, 180, 300, 40); // freq env = random walk
322  sos1.setFrequency(f_env); // set the carrier's frequency
323  sos1.setScale(a_env); // multiply index envelope by mod freq
324  a_env.trigger(); // start the envelope
325  Panner pan(sos1, 0); // stereo panner, center
326 
327  Splitter split(pan); // stereo-to-mono splitter
328  Butter lpf(split, BW_LOW_PASS, 500.0, 333.3); // lo-pass with Q of 3
329  Butter hpf(split, BW_HIGH_PASS, 500.0, 333.3); // hi-pass with Q of 3
330  Joiner join(lpf, hpf); // mono-to-stereo joiner
331 
332  logMsg("playing splitter/joiner/mixer-based crossover filter...");
333  runTest(join, dur); // run test
334  logMsg("done.");
335 }
336 
337 /// Test a Split/Join with a cross-over filter pair LPF to left, HPF to the right
338 /// mixer added in just for show
339 
341  float dur = 6.0f; // seconds to play each test for
342  SumOfSines sos1(16, 0.5); // create a cmoplex sum-of-sines
343  sos1.createCache(); // make the cached wavetable
344  AR a_env(6, 1, 1); // dur, att, rel
345  RandEnvelope f_env(3, 180, 300, 40); // freq env = random walk
346  sos1.setFrequency(f_env); // set the carrier's frequency
347  sos1.setScale(a_env); // multiply index envelope by mod freq
348  a_env.trigger(); // start the envelope
349  Panner pan(sos1, 0); // stereo panner, center
350  Mixer mix1(2); // stereo mixer, fixed
351  mix1.addInput(pan);
352 
353  Splitter split(mix1); // stereo-to-mono splitter
354  Butter lpf(split, BW_LOW_PASS, 500.0, 333.3); // lo-pass with Q of 3
355  Butter hpf(split, BW_HIGH_PASS, 500.0, 333.3); // hi-pass with Q of 3
356  Joiner join(lpf, hpf); // mono-to-stereo joiner
357  Mixer mix2(2); // stereo mixer, fixed
358  mix2.addInput(join);
359 
360  logMsg("playing splitter/joiner/mixer-based crossover filter...");
361  runTest(mix2, dur); // run test
362  logMsg("done.");
363 }
364 
365 /// Test a fan-out + mixer using UnitGenerator's built-in fan-out
366 
367 void testFanMix1() {
368  float dur = 6.0f; // seconds to play each test for
369  Osc vox; // declare an oscillator
370  AR a_env(6, 1, 1); // dur, att, rel
371  RandEnvelope f_env(3, 80, 200, 40); // freq env = random walk
372  vox.setFrequency(f_env); // set the carrier's frequency
373  a_env.setScale(0.5); // multiply index envelope by mod freq
374  vox.setScale(a_env); // multiply index envelope by mod freq
375  a_env.trigger(); // start the envelope
376  Panner pan(vox, fRand1()); // stereo panner
377 
378  Mixer mix(2); // stereo mixer, fixed
379  mix.addInput(pan); // add panner twice
380  mix.addInput(pan);
381 
382  logMsg("playing fan-out + mixer 1...");
383  runTest(mix, dur); // run test
384  logMsg("done.");
385 }
386 
387 /// Test a real fan-out + mixer
388 
389 void testFanMix2() {
390  float dur = 6.0f; // seconds to play each test for
391  Osc vox; // declare an oscillator
392  AR a_env(6, 1, 1); // dur, att, rel
393  RandEnvelope f_env(3, 80, 200, 40); // freq env = random walk
394  vox.setFrequency(f_env); // set the carrier's frequency
395  a_env.setScale(0.5); // multiply index envelope by mod freq
396  vox.setScale(a_env); // multiply index envelope by mod freq
397  a_env.trigger(); // start the envelope
398  Panner pan(vox, fRand1()); // stereo panner
399 
400  FanOut fan(pan, 2); // send the panner to 2 mixer ins
401  Mixer mix(2); // stereo mixer, fixed
402  mix.addInput(fan); // add fan-out twice
403  mix.addInput(fan);
404 
405  logMsg("playing fan-out + mixer 2...");
406  runTest(mix, dur); // run test
407  logMsg("done.");
408 }
409 
410 /// Function to create and answer a RandFreqEnv UGen patch
411 /// this is in test envelopes
412 
414 
415 /// Mix a few sources, adding/dropping graphs
416 
418  float duration = 4.0f; // seg dur
419  float pauset = 0.5f; // pause dur
420  float scale = 8.0f; // ampl scale
421  Mixer mix(2); // stereo mixer
422 
423  UGenVector insts;
424 
425  for (unsigned i = 0; i < 4; i++) { // loop to add 4 srcs to the mix
426  UnitGenerator * pan = createRandFreqEnvPatch(duration);
427  mix.addInput(*pan);
428  mix.scaleInput(*pan, scale);
429  insts.push_back(pan);
430  }
431  logMsg("playing mix of 4 sweep layers...");
432 // srand(getpid()); // seed the rand generator -- UNIX SPECIFIC CODE HERE
433  runTest(mix, duration);
434  sleepSec(pauset); // wait some
435 
436  UGenVector * ins = mix.getInputs();
437  for (unsigned i = 0; i < 2; i++) // remove 2 inputs
438  mix.removeInput(insts[i]);
439  logMsg("removed 2");
440  runTest(mix, duration);
441  sleepSec(pauset); // wait some
442 
443  for (unsigned i = 0; i < 4; i++) { // add 4 more
444  UnitGenerator * pan = createRandFreqEnvPatch(duration);
445  mix.addInput(*pan);
446  mix.scaleInput(*pan, scale);
447  }
448  logMsg("added 4");
449  runTest(mix, duration);
450  sleepSec(pauset); // wait some
451 
452  ins = mix.getInputs();
453  for (unsigned i = 2; i < 5; i++) // remove 3
454  mix.removeInput((*ins)[i]);
455  logMsg("removed 3");
456  runTest(mix, duration);
457  sleepSec(pauset); // wait some
458 
459  for (unsigned i = 0; i < 2; i++) { // add 2 more
460  mix.addInput(insts[i]);
461  mix.scaleInput(*insts[i], scale);
462  }
463  logMsg("added 2");
464  runTest(mix, duration);
465  sleepSec(pauset); // wait some
466 
467  ins = mix.getInputs();
468  for (unsigned i = 0; i < 6; i++) // remove all but one
469  mix.removeInput((*ins)[i]);
470  logMsg("removed all but one");
471  runTest(mix, duration);
472 
473  logMsg("done.\n");
474  mix.deleteInputs(); // clean up
475 }
476 
477 // Simple sample-average filter class - an example of a custom UnitGenerator definition
478 // This implements nextBuffer() with its own DSP routine: a scaled past-sample averager (lo-pass filter)
479 
480 class SAFliter : public Effect { // It's an Effect/UnitGenerator
481 public: // created with an input UGen &scale coeff
482  SAFliter(UnitGenerator & in, float coeff = 0.5f) : Effect(in), mCoeff(coeff), mStore(0) { };
483  ~SAFliter() { };
484  // nextBuffer() gets input and operates on it
485  void nextBuffer(Buffer & outputBuffer, unsigned outBufNum) throw (CException) {
486  unsigned numFrames = outputBuffer.mNumFrames; // get buffer length
487  csl::SampleBuffer out = outputBuffer.monoBuffer(outBufNum); // get ptr to output channel
488 
489  Effect::pullInput(numFrames); // get my input buffer
490  csl::SampleBuffer inPtr = mInputPtr; // get a pointer to the input samples
491 
492  float val;
493  for (unsigned i = 0; i < numFrames; i++) { // here's the filter loop
494  val = *inPtr++ * mCoeff; // get next input sample, scale
495  *out++ = val + mStore; // put current output in the buffer and increment
496  mStore = val; // store val
497  }
498  }
499 protected: // class' data members
500  float mCoeff, mStore; // scale coeff & past value
501 }; // end of class SAFliter
502 
503 // test it
504 
505 void testSAFilter() {
506  SoundFile sfile(CGestalt::dataFolder() + "sns.aiff"); // open a speak'n'spell file
507 
508  SAFliter averager(sfile); // s-a filter
509  logMsg("playing filtered snd file...");
510  runTest(averager, 5.0);
511  logMsg("done.");
512 }
513 
514 //////// RUN_TESTS Function ////////
515 
516 #ifndef USE_JUCE
517 
518 void runTests() {
519 // testClipper(); // Simple clipper tests
520 // testFIR(); // Noise filtering test
521 // testFilters();
522 // testDynamicFilters();
523 // testReverb();
524  testStereoverb();
525 // testMultiTap();
526 }
527 
528 #else
529 
530 // test list for Juce GUI
531 
532 testStruct effTestList[] = {
533  "Clipper", testClipper, "Demonstrate the signal clipper",
534  "FIR filter", testFIR, "Play an FIR band-pass filter",
535  "All filters", testFilters, "Test different filter types",
536  "Filtered snd file", testDynamicVoice, "Dynamic BPF on a voice track",
537  "Dynamic filter", testDynamicFilters, "Play a dynamic BP filter on noise",
538  "Many dynamic filters", testNDynamicFilters, "Many dynamic filtered-noise instruments",
539  "Reverb", testReverb, "Show mono reverb on impulses",
540  "Stereo-verb", testStereoverb, "Listen to the stereo reverb",
541  "Multi-tap delay", testMultiTap, "Play a multi-tap delay line",
542  "Split/Join filter", testSplitJoin1, "Play a splitter/joiner cross-over filter",
543  "Split/Join/Mix filter", testSplitJoin2, "Play a splitter/joiner/mixer cross-over filter",
544  "FanOut + Mixer 1", testFanMix1, "Play a sound through fan-out + mixer",
545  "FanOut + Mixer 2", testFanMix2, "Play a sound through fan-out + mixer",
546  "Dynamic Mixer", testDynamicMixer, "Mix adding/dropping sources",
547  "Block up-sizer", testBlockUpsizer, "Test the block resizer on up-sizing",
548  "Block down-sizer", testBlockDownsizer, "Test the block resizer on down-sizing",
549  "Sample-avg filter", testSAFilter, "Demo in-line sample-average-filter class",
550  NULL, NULL, NULL
551 };
552 
553 #endif