aboutsummaryrefslogtreecommitdiffstats
path: root/tests/com/jsyn
diff options
context:
space:
mode:
Diffstat (limited to 'tests/com/jsyn')
-rw-r--r--tests/com/jsyn/SynthTestSuite.java74
-rw-r--r--tests/com/jsyn/benchmarks/BenchJSyn.java186
-rw-r--r--tests/com/jsyn/data/TestShortSample.java78
-rw-r--r--tests/com/jsyn/engine/TestAudioOutput.java86
-rw-r--r--tests/com/jsyn/engine/TestDevices.java69
-rw-r--r--tests/com/jsyn/engine/TestEngine.java180
-rw-r--r--tests/com/jsyn/engine/TestFifo.java219
-rw-r--r--tests/com/jsyn/engine/TestWaveFileReadWrite.java107
-rw-r--r--tests/com/jsyn/examples/AudioPassThrough.java71
-rw-r--r--tests/com/jsyn/examples/ChebyshevSong.java181
-rw-r--r--tests/com/jsyn/examples/CircuitTester.java109
-rw-r--r--tests/com/jsyn/examples/CustomCubeUnit.java48
-rw-r--r--tests/com/jsyn/examples/DualOscilloscope.java164
-rw-r--r--tests/com/jsyn/examples/EditEnvelope1.java148
-rw-r--r--tests/com/jsyn/examples/FFTPassthrough.java100
-rw-r--r--tests/com/jsyn/examples/GoogleWaveOscillator.java73
-rw-r--r--tests/com/jsyn/examples/HearDAHDSR.java125
-rw-r--r--tests/com/jsyn/examples/HearMoogFilter.java196
-rw-r--r--tests/com/jsyn/examples/HearSinePM.java129
-rw-r--r--tests/com/jsyn/examples/HearSpectralFilter.java206
-rw-r--r--tests/com/jsyn/examples/ListAudioDevices.java46
-rw-r--r--tests/com/jsyn/examples/LongEcho.java125
-rw-r--r--tests/com/jsyn/examples/MonoPassThrough.java66
-rw-r--r--tests/com/jsyn/examples/NotesToTone.java214
-rw-r--r--tests/com/jsyn/examples/PlayChords.java188
-rw-r--r--tests/com/jsyn/examples/PlayCustomUnit.java73
-rw-r--r--tests/com/jsyn/examples/PlayFunction.java91
-rw-r--r--tests/com/jsyn/examples/PlayGrains.java212
-rw-r--r--tests/com/jsyn/examples/PlayNotes.java103
-rw-r--r--tests/com/jsyn/examples/PlayPartials.java110
-rw-r--r--tests/com/jsyn/examples/PlaySample.java121
-rw-r--r--tests/com/jsyn/examples/PlaySampleCrossfade.java183
-rw-r--r--tests/com/jsyn/examples/PlaySegmentedEnvelope.java151
-rw-r--r--tests/com/jsyn/examples/PlaySegmentedEnvelopeCallback.java114
-rw-r--r--tests/com/jsyn/examples/PlaySequence.java85
-rw-r--r--tests/com/jsyn/examples/PlayTone.java80
-rw-r--r--tests/com/jsyn/examples/RecordSineSweep.java136
-rw-r--r--tests/com/jsyn/examples/SampleHoldNoteBlaster.java153
-rw-r--r--tests/com/jsyn/examples/SawFaders.java104
-rw-r--r--tests/com/jsyn/examples/SeeGoogleWave.java111
-rw-r--r--tests/com/jsyn/examples/SeeOscillators.java183
-rw-r--r--tests/com/jsyn/examples/ShowWaves.java121
-rw-r--r--tests/com/jsyn/examples/SwarmOfOscillators.java146
-rw-r--r--tests/com/jsyn/examples/UseMidiKeyboard.java198
-rw-r--r--tests/com/jsyn/examples/WindCircuit.java90
-rw-r--r--tests/com/jsyn/ports/TestQueuedDataPort.java492
-rw-r--r--tests/com/jsyn/ports/TestSequentialData.java50
-rw-r--r--tests/com/jsyn/ports/TestSet.java96
-rw-r--r--tests/com/jsyn/research/BenchMultiThreading.java143
-rw-r--r--tests/com/jsyn/research/RecordVariousRamps.java183
-rw-r--r--tests/com/jsyn/swing/TestRangeModels.java60
-rw-r--r--tests/com/jsyn/unitgen/CalibrateMoogFilter.java141
-rw-r--r--tests/com/jsyn/unitgen/EnablingGate.java51
-rw-r--r--tests/com/jsyn/unitgen/NonRealTimeTestCase.java48
-rw-r--r--tests/com/jsyn/unitgen/RecordMoogFilter.java153
-rw-r--r--tests/com/jsyn/unitgen/TestConnections.java113
-rw-r--r--tests/com/jsyn/unitgen/TestDelay.java73
-rw-r--r--tests/com/jsyn/unitgen/TestEnable.java78
-rw-r--r--tests/com/jsyn/unitgen/TestEnvelopeAttackDecay.java126
-rw-r--r--tests/com/jsyn/unitgen/TestEnvelopeDAHDSR.java339
-rw-r--r--tests/com/jsyn/unitgen/TestFunction.java78
-rw-r--r--tests/com/jsyn/unitgen/TestMath.java392
-rw-r--r--tests/com/jsyn/unitgen/TestRamps.java196
-rw-r--r--tests/com/jsyn/unitgen/TestUnitGate.java80
-rw-r--r--tests/com/jsyn/util/DebugSampleLoader.java138
-rw-r--r--tests/com/jsyn/util/TestFFT.java98
-rw-r--r--tests/com/jsyn/util/TestPseudoRandom.java82
-rw-r--r--tests/com/jsyn/util/TestVoiceAllocator.java111
68 files changed, 9074 insertions, 0 deletions
diff --git a/tests/com/jsyn/SynthTestSuite.java b/tests/com/jsyn/SynthTestSuite.java
new file mode 100644
index 0000000..25a89c8
--- /dev/null
+++ b/tests/com/jsyn/SynthTestSuite.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2010 Phil Burk, Mobileer Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.jsyn;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+import com.jsyn.data.TestShortSample;
+import com.jsyn.engine.TestDevices;
+import com.jsyn.engine.TestEngine;
+import com.jsyn.engine.TestFifo;
+import com.jsyn.engine.TestWaveFileReadWrite;
+import com.jsyn.ports.TestQueuedDataPort;
+import com.jsyn.ports.TestSet;
+import com.jsyn.unitgen.TestEnable;
+import com.jsyn.unitgen.TestEnvelopeAttackDecay;
+import com.jsyn.unitgen.TestEnvelopeDAHDSR;
+import com.jsyn.unitgen.TestFunction;
+import com.jsyn.unitgen.TestRamps;
+import com.jsyn.util.TestVoiceAllocator;
+
+/**
+ * Test new pure Java JSyn API.
+ *
+ * @author Phil Burk (C) 2010 Mobileer Inc
+ */
+public class SynthTestSuite {
+
+ public static Test suite() {
+
+ TestSuite suite = new TestSuite();
+
+ suite.addTestSuite(TestEngine.class);
+ suite.addTestSuite(TestSet.class);
+ // suite.addTestSuite( TestMath.class );
+ suite.addTestSuite(TestRamps.class);
+ suite.addTestSuite(TestEnvelopeAttackDecay.class);
+ suite.addTestSuite(TestEnvelopeDAHDSR.class);
+ suite.addTestSuite(TestShortSample.class);
+ suite.addTestSuite(TestDevices.class);
+ suite.addTestSuite(TestQueuedDataPort.class);
+ suite.addTestSuite(TestFifo.class);
+ suite.addTestSuite(TestEnable.class);
+ suite.addTestSuite(TestFunction.class);
+ // suite.addTestSuite( TestSampleLoader.class );
+ suite.addTestSuite(TestWaveFileReadWrite.class);
+ suite.addTestSuite(TestVoiceAllocator.class);
+ // suite.addTestSuite(TestWrapAroundBug.class);
+
+ return suite;
+ }
+
+ /**
+ * Runs the test suite using the textual runner.
+ */
+ public static void main(String[] args) {
+ junit.textui.TestRunner.run(suite());
+ System.exit(0);
+ }
+}
diff --git a/tests/com/jsyn/benchmarks/BenchJSyn.java b/tests/com/jsyn/benchmarks/BenchJSyn.java
new file mode 100644
index 0000000..51e09a4
--- /dev/null
+++ b/tests/com/jsyn/benchmarks/BenchJSyn.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright 2013 Phil Burk, Mobileer Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/**
+ *
+ */
+
+package com.jsyn.benchmarks;
+
+import com.jsyn.JSyn;
+import com.jsyn.Synthesizer;
+import com.jsyn.unitgen.PassThrough;
+import com.jsyn.unitgen.SawtoothOscillator;
+import com.jsyn.unitgen.SawtoothOscillatorBL;
+import com.jsyn.unitgen.SawtoothOscillatorDPW;
+import com.jsyn.unitgen.UnitOscillator;
+import com.softsynth.math.FourierMath;
+
+/**
+ * @author Phil Burk (C) 2013 Mobileer Inc
+ */
+public class BenchJSyn {
+ private Synthesizer synth;
+ private long startTime;
+ private long endTime;
+ private PassThrough pass;
+
+ public void run() {
+ try {
+ // Run multiple times to see if HotSpot compiler or cache makes a difference.
+ for (int i = 0; i < 4; i++) {
+ benchmark();
+ }
+ } catch (InstantiationException e) {
+ e.printStackTrace();
+ } catch (IllegalAccessException e) {
+ e.printStackTrace();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+
+ private void benchmark() throws InstantiationException, IllegalAccessException,
+ InterruptedException {
+ double realTime = 10.0;
+ int count = 40;
+
+ benchFFTDouble();
+ benchFFTFloat();
+ benchmarkOscillator(SawtoothOscillator.class, count, realTime);
+ benchmarkOscillator(SawtoothOscillatorDPW.class, count, realTime);
+ benchmarkOscillator(SawtoothOscillatorBL.class, count, realTime);
+
+ }
+
+ public void benchFFTDouble() {
+ int size = 2048;
+ int bin = 5;
+ int count = 20000;
+ double[] ar = new double[size];
+ double[] ai = new double[size];
+ double[] magnitudes = new double[size];
+
+ double amplitude = 1.0;
+ addSineWave(size, bin, ar, amplitude);
+ System.out.println("Bench double FFT");
+ startTiming();
+ for (int i = 0; i < count; i++) {
+ FourierMath.transform(1, size, ar, ai);
+ }
+
+ endTiming(FourierMath.class, count, size / (2.0 * 44100));
+ FourierMath.calculateMagnitudes(ar, ai, magnitudes);
+
+ assert (magnitudes[bin - 1] < 0.001);
+ assert (magnitudes[bin] > 0.5);
+ assert (magnitudes[bin + 1] < 0.001);
+
+ }
+
+ public void benchFFTFloat() {
+ int size = 2048;
+ int bin = 5;
+ int count = 20000;
+ float[] ar = new float[size];
+ float[] ai = new float[size];
+ float[] magnitudes = new float[size];
+
+ float amplitude = 1.0f;
+ addSineWave(size, bin, ar, amplitude);
+
+ System.out.println("Bench float FFT");
+ startTiming();
+ for (int i = 0; i < count; i++) {
+ FourierMath.transform(1, size, ar, ai);
+ }
+
+ endTiming(FourierMath.class, count, size / (2.0 * 44100));
+ FourierMath.calculateMagnitudes(ar, ai, magnitudes);
+
+ assert (magnitudes[bin - 1] < 0.001);
+ assert (magnitudes[bin] > 0.5);
+ assert (magnitudes[bin + 1] < 0.001);
+
+ }
+
+ private void addSineWave(int size, int bin, double[] ar, double amplitude) {
+ double phase = 0.0;
+ double phaseIncrement = 2.0 * Math.PI * bin / size;
+ for (int i = 0; i < size; i++) {
+ ar[i] += Math.sin(phase) * amplitude;
+ // System.out.println( i + " = " + ar[i] );
+ phase += phaseIncrement;
+ }
+ }
+
+ private void addSineWave(int size, int bin, float[] ar, float amplitude) {
+ float phase = 0.0f;
+ float phaseIncrement = (float) (2.0 * Math.PI * bin / size);
+ for (int i = 0; i < size; i++) {
+ ar[i] += (float) Math.sin(phase) * amplitude;
+ // System.out.println( i + " = " + ar[i] );
+ phase += phaseIncrement;
+ }
+ }
+
+ private void stopSynth() {
+ synth.stop();
+ }
+
+ private void startSynth() {
+ synth = JSyn.createSynthesizer(); // Mac
+ // synth = JSyn.createSynthesizer( new JSynAndroidAudioDevice() ); // Android
+ synth.setRealTime(false);
+ pass = new PassThrough();
+ synth.add(pass);
+ synth.start();
+ pass.start();
+ }
+
+ private void benchmarkOscillator(Class<?> clazz, int count, double realTime)
+ throws InstantiationException, IllegalAccessException, InterruptedException {
+ startSynth();
+ for (int i = 0; i < count; i++) {
+ UnitOscillator osc = (UnitOscillator) clazz.newInstance();
+ osc.output.connect(pass.input);
+ synth.add(osc);
+ }
+ startTiming();
+ synth.sleepFor(realTime);
+ endTiming(clazz, count, realTime);
+ stopSynth();
+ }
+
+ private void endTiming(Class<?> clazz, int count, double realTime) {
+ endTime = System.nanoTime();
+ double elapsedTime = (endTime - startTime) * 1E-9;
+ double percent = 100.0 * elapsedTime / (realTime * count);
+ System.out.printf("%32s took %5.3f/%d seconds to process %5.4f of audio = %6.3f%c.\n",
+ clazz.getSimpleName(), elapsedTime, count, realTime, percent, '%');
+ }
+
+ private void startTiming() {
+ startTime = System.nanoTime();
+ }
+
+ /**
+ * @param args
+ */
+ public static void main(String[] args) {
+ new BenchJSyn().run();
+ }
+
+}
diff --git a/tests/com/jsyn/data/TestShortSample.java b/tests/com/jsyn/data/TestShortSample.java
new file mode 100644
index 0000000..3837c19
--- /dev/null
+++ b/tests/com/jsyn/data/TestShortSample.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2009 Phil Burk, Mobileer Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.jsyn.data;
+
+import junit.framework.TestCase;
+
+/**
+ * @author Phil Burk, (C) 2009 Mobileer Inc
+ */
+public class TestShortSample extends TestCase {
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ public void testBytes() {
+ byte[] bar = {
+ 18, -3
+ };
+ short s = (short) ((bar[0] << 8) | (bar[1] & 0xFF));
+ assertEquals("A", 0x12FD, s);
+ }
+
+ public void testReadWrite() {
+ short[] data = {
+ 123, 456, -789, 111, 20000, -32768, 32767, 0, 9876
+ };
+ ShortSample sample = new ShortSample(data.length, 1);
+ assertEquals("Sample numFrames", data.length, sample.getNumFrames());
+
+ // Write and read entire sample.
+ sample.write(data);
+ short[] buffer = new short[data.length];
+ sample.read(buffer);
+
+ for (int i = 0; i < data.length; i++) {
+ assertEquals("read = write", data[i], buffer[i]);
+ }
+
+ // Write and read part of an array.
+ short[] partial = {
+ 333, 444, 555, 666, 777
+ };
+
+ sample.write(2, partial, 1, 3);
+ sample.read(1, buffer, 1, 5);
+
+ for (int i = 0; i < data.length; i++) {
+ if ((i >= 2) && (i <= 4)) {
+ assertEquals("partial", partial[i - 1], buffer[i]);
+ } else {
+ assertEquals("read = write", data[i], buffer[i]);
+ }
+ }
+
+ }
+
+}
diff --git a/tests/com/jsyn/engine/TestAudioOutput.java b/tests/com/jsyn/engine/TestAudioOutput.java
new file mode 100644
index 0000000..a95d426
--- /dev/null
+++ b/tests/com/jsyn/engine/TestAudioOutput.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2009 Phil Burk, Mobileer Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.jsyn.engine;
+
+import java.io.IOException;
+
+import junit.framework.TestCase;
+
+import com.jsyn.devices.AudioDeviceManager;
+import com.jsyn.devices.AudioDeviceOutputStream;
+import com.jsyn.devices.javasound.JavaSoundAudioDevice;
+
+/**
+ * @author Phil Burk, (C) 2009 Mobileer Inc
+ */
+public class TestAudioOutput extends TestCase {
+
+ SynthesisEngine synthesisEngine;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ public void testMonoSine() throws IOException {
+ System.out.println("Test mono output.");
+ final int FRAMES_PER_BUFFER = 128;
+ final int SAMPLES_PER_FRAME = 1;
+ double[] buffer = new double[FRAMES_PER_BUFFER * SAMPLES_PER_FRAME];
+ AudioDeviceManager audioDevice = new JavaSoundAudioDevice();
+ AudioDeviceOutputStream audioOutput = audioDevice.createOutputStream(
+ audioDevice.getDefaultOutputDeviceID(), 44100, SAMPLES_PER_FRAME);
+ for (int i = 0; i < FRAMES_PER_BUFFER; i++) {
+ double angle = (i * Math.PI * 2.0) / FRAMES_PER_BUFFER;
+ buffer[i] = Math.sin(angle);
+ }
+ audioOutput.start();
+ for (int i = 0; i < 1000; i++) {
+ audioOutput.write(buffer);
+ }
+ audioOutput.stop();
+
+ }
+
+ public void testStereoSine() throws IOException {
+ System.out.println("Test stereo output.");
+ final int FRAMES_PER_BUFFER = 128;
+ final int SAMPLES_PER_FRAME = 2;
+ double[] buffer = new double[FRAMES_PER_BUFFER * SAMPLES_PER_FRAME];
+ AudioDeviceManager audioDevice = new JavaSoundAudioDevice();
+ AudioDeviceOutputStream audioOutput = audioDevice.createOutputStream(
+ audioDevice.getDefaultOutputDeviceID(), 44100, SAMPLES_PER_FRAME);
+ int bi = 0;
+ for (int i = 0; i < FRAMES_PER_BUFFER; i++) {
+ double angle = (i * Math.PI * 2.0) / FRAMES_PER_BUFFER;
+ buffer[bi++] = Math.sin(angle);
+ buffer[bi++] = Math.sin(angle);
+ }
+ audioOutput.start();
+ for (int i = 0; i < 1000; i++) {
+ audioOutput.write(buffer);
+ }
+ audioOutput.stop();
+
+ }
+
+}
diff --git a/tests/com/jsyn/engine/TestDevices.java b/tests/com/jsyn/engine/TestDevices.java
new file mode 100644
index 0000000..56f2c1f
--- /dev/null
+++ b/tests/com/jsyn/engine/TestDevices.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2009 Phil Burk, Mobileer Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.jsyn.engine;
+
+import junit.framework.TestCase;
+
+import com.jsyn.JSyn;
+import com.jsyn.Synthesizer;
+import com.jsyn.devices.AudioDeviceFactory;
+import com.jsyn.devices.AudioDeviceManager;
+import com.jsyn.unitgen.LineIn;
+import com.jsyn.unitgen.LineOut;
+
+public class TestDevices extends TestCase {
+ // Test audio input and output simultaneously.
+ public void testPassThrough() {
+ Synthesizer synth;
+ LineIn lineIn;
+ LineOut lineOut;
+ // Create a context for the synthesizer.
+ synth = JSyn.createSynthesizer(AudioDeviceFactory.createAudioDeviceManager(true));
+ // Add an audio input.
+ synth.add(lineIn = new LineIn());
+ // Add an audio output.
+ synth.add(lineOut = new LineOut());
+ // Connect the input to the output.
+ lineIn.output.connect(0, lineOut.input, 0);
+ lineIn.output.connect(1, lineOut.input, 1);
+
+ // Both stereo.
+ int numInputChannels = 2;
+ int numOutputChannels = 2;
+ synth.start(44100, AudioDeviceManager.USE_DEFAULT_DEVICE, numInputChannels,
+ AudioDeviceManager.USE_DEFAULT_DEVICE, numOutputChannels);
+
+ // We only need to start the LineOut. It will pull data from the LineIn.
+ lineOut.start();
+ System.out.println("Audio passthrough started.");
+ // Sleep a while.
+ double sleepTime = 2.0;
+ try {
+ double time = synth.getCurrentTime();
+ // Sleep for a few seconds.
+ synth.sleepUntil(time + sleepTime);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ double synthTime = synth.getCurrentTime();
+ assertEquals("Time has advanced. " + synthTime, sleepTime, synthTime, 0.2);
+ // Stop everything.
+ synth.stop();
+ System.out.println("All done.");
+
+ }
+}
diff --git a/tests/com/jsyn/engine/TestEngine.java b/tests/com/jsyn/engine/TestEngine.java
new file mode 100644
index 0000000..b633bc1
--- /dev/null
+++ b/tests/com/jsyn/engine/TestEngine.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright 2009 Phil Burk, Mobileer Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.jsyn.engine;
+
+import junit.framework.TestCase;
+
+import com.jsyn.unitgen.LineOut;
+import com.jsyn.unitgen.PitchDetector;
+import com.jsyn.unitgen.SineOscillator;
+import com.jsyn.unitgen.ZeroCrossingCounter;
+
+public class TestEngine extends TestCase {
+
+ public void testInitialization() {
+ final int DEFAULT_FRAME_RATE = 44100;
+ SynthesisEngine synthesisEngine = new SynthesisEngine();
+ assertEquals("frameCount zero before starting", 0, synthesisEngine.getFrameCount());
+ assertEquals("default frameRate", DEFAULT_FRAME_RATE, synthesisEngine.getFrameRate());
+ assertEquals("default pullData", true, synthesisEngine.isPullDataEnabled());
+ }
+
+ public void checkPullData(boolean pullData) {
+ SynthesisEngine synthesisEngine = new SynthesisEngine();
+ assertEquals("default realTime", true, synthesisEngine.isRealTime());
+ synthesisEngine.setRealTime(false);
+
+ assertEquals("default pullData", true, synthesisEngine.isPullDataEnabled());
+ synthesisEngine.setPullDataEnabled(pullData);
+
+ SineOscillator sineOscillator = new SineOscillator();
+ synthesisEngine.add(sineOscillator);
+
+ LineOut lineOut = new LineOut();
+ synthesisEngine.add(lineOut);
+ sineOscillator.output.connect(0, lineOut.input, 0);
+
+ assertEquals("initial sine value", 0.0, sineOscillator.output.getValue());
+
+ synthesisEngine.start();
+ if (!pullData) {
+ sineOscillator.start();
+ }
+ // We always have to start the LineOut.
+ lineOut.start();
+ synthesisEngine.generateNextBuffer();
+ synthesisEngine.generateNextBuffer();
+
+ double value = sineOscillator.output.getValue();
+ assertTrue("sine value after generation = " + value, (value > 0.0));
+ }
+
+ public void testPullDataFalse() {
+ checkPullData(false);
+ }
+
+ public void testPullDataTrue() {
+ checkPullData(true);
+ }
+
+ public void testMixedAdding() {
+ boolean gotCaught = false;
+ SynthesisEngine synthesisEngine1 = new SynthesisEngine();
+ synthesisEngine1.setRealTime(false);
+ synthesisEngine1.setPullDataEnabled(true);
+ SynthesisEngine synthesisEngine2 = new SynthesisEngine();
+ synthesisEngine2.setRealTime(false);
+ synthesisEngine2.setPullDataEnabled(true);
+
+ // Create a sineOscillator but do not add it to the synth!
+ SineOscillator sineOscillator = new SineOscillator();
+ LineOut lineOut = new LineOut();
+
+ synthesisEngine1.add(lineOut);
+ synthesisEngine2.add(sineOscillator);
+ try {
+ sineOscillator.output.connect(0, lineOut.input, 0);
+ } catch (RuntimeException e) {
+ gotCaught = true;
+ assertTrue("informative MPE message", e.getMessage().contains("different synths"));
+ }
+
+ assertTrue("caught NPE caused by forgetting synth.add", gotCaught);
+ }
+
+ public void testNotAdding() {
+ SynthesisEngine synthesisEngine = new SynthesisEngine();
+ synthesisEngine.setRealTime(false);
+ synthesisEngine.setPullDataEnabled(true);
+
+ // Create a sineOscillator but do not add it to the synth!
+ SineOscillator sineOscillator = new SineOscillator();
+
+ LineOut lineOut = new LineOut();
+ sineOscillator.output.connect(0, lineOut.input, 0);
+ synthesisEngine.add(lineOut);
+
+ assertEquals("initial sine value", 0.0, sineOscillator.output.getValue());
+
+ synthesisEngine.start();
+ // We always have to start the LineOut.
+ lineOut.start();
+ boolean gotCaught = false;
+ try {
+ synthesisEngine.generateNextBuffer();
+ synthesisEngine.generateNextBuffer();
+ } catch (NullPointerException e) {
+ gotCaught = true;
+ assertTrue("informative MPE message", e.getMessage().contains("forgot to add"));
+ }
+
+ assertTrue("caught NPE caused by forgetting synth.add", gotCaught);
+ }
+
+ public void testMultipleStarts() throws InterruptedException {
+ SynthesisEngine synth = new SynthesisEngine();
+
+ // Create a sineOscillator but do not add it to the synth!
+ SineOscillator osc = new SineOscillator();
+ ZeroCrossingCounter counter = new ZeroCrossingCounter();
+ PitchDetector pitchDetector = new PitchDetector();
+ LineOut lineOut = new LineOut();
+ synth.add(osc);
+ synth.add(counter);
+ synth.add(lineOut);
+ synth.add(pitchDetector);
+ osc.output.connect(counter.input);
+ osc.output.connect(pitchDetector.input);
+ counter.output.connect(0, lineOut.input, 0);
+
+ assertEquals("initial count", 0, counter.getCount());
+
+ int[] rates = {
+ 32000, 48000, 44100, 22050
+ };
+ for (int rate : rates) {
+ synth.start(rate);
+ lineOut.start();
+ pitchDetector.start();
+
+ double time = synth.getCurrentTime();
+ double interval = 1.0;
+ time += interval;
+
+ long previousFrameCount = counter.getCount();
+ synth.sleepUntil(time);
+
+ double frequencyMeasured = pitchDetector.frequency.get();
+ double confidenceMeasured = pitchDetector.confidence.get();
+ double oscFreq = osc.frequency.get();
+ String msg = "freq at " + rate + " Hz";
+ System.out.println(msg);
+ assertEquals(msg, oscFreq, frequencyMeasured, oscFreq * 0.1);
+ assertEquals("pitch confidence", 0.9, confidenceMeasured, 0.1);
+
+ double expectedCount = interval * oscFreq;
+ double framesMeasured = counter.getCount() - previousFrameCount;
+ msg = "count at " + rate + " Hz";
+ System.out.println(msg);
+ assertEquals(msg, expectedCount, framesMeasured, expectedCount * 0.1);
+
+ synth.stop();
+ }
+
+ }
+
+}
diff --git a/tests/com/jsyn/engine/TestFifo.java b/tests/com/jsyn/engine/TestFifo.java
new file mode 100644
index 0000000..e504a0b
--- /dev/null
+++ b/tests/com/jsyn/engine/TestFifo.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright 2009 Phil Burk, Mobileer Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.jsyn.engine;
+
+import junit.framework.TestCase;
+
+import com.jsyn.io.AudioFifo;
+
+public class TestFifo extends TestCase {
+
+ public void testBasic() {
+ Thread watchdog = startWatchdog(600);
+
+ AudioFifo fifo = new AudioFifo();
+ fifo.setReadWaitEnabled(false);
+ fifo.allocate(8);
+ assertEquals("start empty", 0, fifo.available());
+
+ assertEquals("read back Nan when emopty", Double.NaN, fifo.read());
+
+ fifo.write(1.0);
+ assertEquals("added one value", 1, fifo.available());
+ assertEquals("read back same value", 1.0, fifo.read());
+ assertEquals("back to empty", 0, fifo.available());
+
+ for (int i = 0; i < fifo.size(); i++) {
+ assertEquals("adding data", i, fifo.available());
+ fifo.write(100.0 + i);
+ }
+ for (int i = 0; i < fifo.size(); i++) {
+ assertEquals("removing data", fifo.size() - i, fifo.available());
+ assertEquals("reading back data", 100.0 + i, fifo.read());
+ }
+ watchdog.interrupt();
+ }
+
+ /**
+ * Wrap around several times to test masking.
+ */
+ public void testWrapping() {
+
+ final int chunk = 5;
+ AudioFifo fifo = new AudioFifo();
+ fifo.allocate(8);
+ double value = 1000.0;
+ for (int i = 0; i < (fifo.size() * chunk); i++) {
+ value = checkFifoChunk(fifo, value, chunk);
+ }
+
+ }
+
+ private double checkFifoChunk(AudioFifo fifo, double value, int chunk) {
+ for (int i = 0; i < chunk; i++) {
+ assertEquals("adding data", i, fifo.available());
+ fifo.write(value + i);
+ }
+ for (int i = 0; i < chunk; i++) {
+ assertEquals("removing data", chunk - i, fifo.available());
+ assertEquals("reading back data", value + i, fifo.read());
+ }
+ return value + chunk;
+ }
+
+ public void testBadSize() {
+ boolean caught = false;
+ try {
+ AudioFifo fifo = new AudioFifo();
+ fifo.allocate(20); // not power of 2
+ assertTrue("should not get here", false);
+ } catch (IllegalArgumentException e) {
+ caught = true;
+ }
+ assertTrue("should have caught size exception", caught);
+ }
+
+ public void testSingleReadWait() {
+ final int chunk = 5;
+ final AudioFifo fifo = new AudioFifo();
+ fifo.allocate(8);
+
+ fifo.setWriteWaitEnabled(false);
+ fifo.setReadWaitEnabled(true);
+ final double value = 50.0;
+
+ // Schedule a delayed write in another thread.
+ new Thread() {
+ @Override
+ public void run() {
+ try {
+ sleep(200);
+ for (int i = 0; i < chunk; i++) {
+ fifo.write(value + i);
+ }
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ }.start();
+
+ Thread watchdog = startWatchdog(500);
+ for (int i = 0; i < chunk; i++) {
+ assertEquals("reading back data", value + i, fifo.read());
+ }
+ watchdog.interrupt();
+ }
+
+ private Thread startWatchdog(final int msec) {
+ Thread watchdog = new Thread() {
+ @Override
+ public void run() {
+ try {
+ sleep(msec);
+ assertTrue("test must still be waiting", false);
+ } catch (InterruptedException e) {
+ }
+ }
+ };
+ watchdog.start();
+ return watchdog;
+ }
+
+ public void testSingleWriteWait() {
+ final int chunk = 13;
+ final AudioFifo fifo = new AudioFifo();
+ fifo.allocate(8);
+
+ fifo.setWriteWaitEnabled(true);
+ fifo.setReadWaitEnabled(true);
+ final double value = 50.0;
+
+ // Schedule a delayed read in another thread.
+ Thread readThread = new Thread() {
+ @Override
+ public void run() {
+ try {
+ sleep(200);
+ for (int i = 0; i < chunk; i++) {
+ // System.out.println( "testSingleWriteWait: try to read" );
+ double got = fifo.read();
+ assertEquals("adding data", value + i, got);
+ // System.out.println( "testSingleWriteWait: read " + got );
+ }
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ };
+ readThread.start();
+
+ Thread watchdog = startWatchdog(500);
+ // Try to write more than will fit so we will hang.
+ for (int i = 0; i < chunk; i++) {
+ fifo.write(value + i);
+ }
+ watchdog.interrupt();
+
+ try {
+ readThread.join(200);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ assertEquals("readThread should be done.", false, readThread.isAlive());
+ }
+
+ public void testBlockReadWait() {
+ final int chunk = 50;
+ final AudioFifo fifo = new AudioFifo();
+ fifo.allocate(8);
+
+ fifo.setWriteWaitEnabled(false);
+ fifo.setReadWaitEnabled(true);
+ final double value = 300.0;
+ double[] readBuffer = new double[chunk];
+
+ // Schedule delayed writes in another thread.
+ new Thread() {
+ @Override
+ public void run() {
+ int numWritten = 0;
+ double[] writeBuffer = new double[4];
+ try {
+ while (numWritten < chunk) {
+ sleep(30);
+ for (int i = 0; i < writeBuffer.length; i++) {
+ writeBuffer[i] = value + numWritten;
+ numWritten += 1;
+ }
+
+ fifo.write(writeBuffer);
+ }
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ }.start();
+
+ Thread watchdog = startWatchdog(600);
+ fifo.read(readBuffer);
+ for (int i = 0; i < chunk; i++) {
+ assertEquals("reading back data", value + i, readBuffer[i]);
+ }
+ watchdog.interrupt();
+
+ }
+}
diff --git a/tests/com/jsyn/engine/TestWaveFileReadWrite.java b/tests/com/jsyn/engine/TestWaveFileReadWrite.java
new file mode 100644
index 0000000..ee406de
--- /dev/null
+++ b/tests/com/jsyn/engine/TestWaveFileReadWrite.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2009 Phil Burk, Mobileer Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.jsyn.engine;
+
+import java.io.File;
+import java.io.IOException;
+
+import javax.sound.sampled.UnsupportedAudioFileException;
+
+import junit.framework.TestCase;
+
+import com.jsyn.data.FloatSample;
+import com.jsyn.util.SampleLoader;
+import com.jsyn.util.WaveFileWriter;
+
+public class TestWaveFileReadWrite extends TestCase {
+
+ public void checkWriteReadWave(int numChannels, float[] data) throws IOException,
+ UnsupportedAudioFileException {
+ File temp = File.createTempFile("test_wave", ".wav");
+ temp.deleteOnExit();
+ System.out.println("Creating wave file " + temp);
+
+ WaveFileWriter writer = new WaveFileWriter(temp);
+ writer.setFrameRate(44100);
+ writer.setSamplesPerFrame(numChannels);
+ writer.setBitsPerSample(16);
+
+ for (int i = 0; i < data.length; i++) {
+ writer.write(data[i]);
+ }
+ writer.close();
+
+ // TODO Make sure blow up if writing after close.
+ // writer.write( 0.7 );
+
+ FloatSample sample = SampleLoader.loadFloatSample(temp);
+ assertEquals("stereo", numChannels, sample.getChannelsPerFrame());
+ assertEquals("frame rate", 44100.0, sample.getFrameRate());
+
+ for (int i = 0; i < data.length; i++) {
+ float v = data[i];
+ if (v > 1.0)
+ v = 1.0f;
+ else if (v < -1.0)
+ v = -1.0f;
+ assertEquals("sample data", v, sample.readDouble(i), 0.0001);
+ }
+
+ }
+
+ public void testRamp() throws IOException, UnsupportedAudioFileException {
+ float[] data = new float[200];
+ for (int i = 0; i < data.length; i++) {
+ data[i] = i / 1000.0f;
+ }
+
+ checkWriteReadWave(2, data);
+ }
+
+ public void testClippedSine() throws IOException, UnsupportedAudioFileException {
+ float[] data = new float[200];
+ for (int i = 0; i < data.length; i++) {
+ double phase = i * Math.PI * 2.0 / 100;
+ data[i] = (float) (1.3 * Math.sin(phase));
+ }
+
+ checkWriteReadWave(2, data);
+ }
+
+ public void testArguments() throws IOException {
+ File temp = File.createTempFile("test_wave", ".wav");
+ temp.deleteOnExit();
+ System.out.println("Creating wave file " + temp);
+
+ WaveFileWriter writer = new WaveFileWriter(temp);
+ writer.setBitsPerSample(16);
+ assertEquals("bitsPerSample", 16, writer.getBitsPerSample());
+ writer.setBitsPerSample(24);
+ assertEquals("bitsPerSample", 24, writer.getBitsPerSample());
+ boolean caughtIt = false;
+ try {
+ writer.setBitsPerSample(17);
+ assertTrue("tried setting illegal value", false);
+ } catch (IllegalArgumentException e) {
+ // e.printStackTrace();
+ caughtIt = true;
+ }
+ assertTrue("17 generated exception", caughtIt);
+ writer.close();
+ }
+
+}
diff --git a/tests/com/jsyn/examples/AudioPassThrough.java b/tests/com/jsyn/examples/AudioPassThrough.java
new file mode 100644
index 0000000..fb46992
--- /dev/null
+++ b/tests/com/jsyn/examples/AudioPassThrough.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2010 Phil Burk, Mobileer Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.jsyn.examples;
+
+import com.jsyn.JSyn;
+import com.jsyn.Synthesizer;
+import com.jsyn.devices.AudioDeviceManager;
+import com.jsyn.unitgen.LineIn;
+import com.jsyn.unitgen.LineOut;
+
+/**
+ * Pass audio input to audio output.
+ *
+ * @author Phil Burk (C) 2010 Mobileer Inc
+ */
+public class AudioPassThrough {
+ Synthesizer synth;
+ LineIn lineIn;
+ LineOut lineOut;
+
+ private void test() {
+ // Create a context for the synthesizer.
+ synth = JSyn.createSynthesizer();
+ // Add an audio input.
+ synth.add(lineIn = new LineIn());
+ // Add an audio output.
+ synth.add(lineOut = new LineOut());
+ // Connect the input to the output.
+ lineIn.output.connect(0, lineOut.input, 0);
+ lineIn.output.connect(1, lineOut.input, 1);
+
+ // Both stereo.
+ int numInputChannels = 2;
+ int numOutputChannels = 2;
+ synth.start(44100, AudioDeviceManager.USE_DEFAULT_DEVICE, numInputChannels,
+ AudioDeviceManager.USE_DEFAULT_DEVICE, numOutputChannels);
+
+ // We only need to start the LineOut. It will pull data from the LineIn.
+ lineOut.start();
+ System.out.println("Audio passthrough started.");
+ // Sleep a while.
+ try {
+ double time = synth.getCurrentTime();
+ // Sleep for a few seconds.
+ synth.sleepUntil(time + 8.0);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ // Stop everything.
+ synth.stop();
+ System.out.println("All done.");
+ }
+
+ public static void main(String[] args) {
+ new AudioPassThrough().test();
+ }
+}
diff --git a/tests/com/jsyn/examples/ChebyshevSong.java b/tests/com/jsyn/examples/ChebyshevSong.java
new file mode 100644
index 0000000..fffd4bb
--- /dev/null
+++ b/tests/com/jsyn/examples/ChebyshevSong.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright 2010 Phil Burk, Mobileer Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.jsyn.examples;
+
+import java.awt.BorderLayout;
+
+import javax.swing.JApplet;
+
+import com.jsyn.JSyn;
+import com.jsyn.Synthesizer;
+import com.jsyn.instruments.WaveShapingVoice;
+import com.jsyn.scope.AudioScope;
+import com.jsyn.swing.JAppletFrame;
+import com.jsyn.unitgen.Add;
+import com.jsyn.unitgen.LineOut;
+import com.jsyn.util.PseudoRandom;
+import com.jsyn.util.VoiceAllocator;
+import com.softsynth.math.AudioMath;
+import com.softsynth.shared.time.TimeStamp;
+
+/***************************************************************
+ * Play notes using a WaveShapingVoice. Allocate the notes using a VoiceAllocator.
+ *
+ * @author Phil Burk (C) 2010 Mobileer Inc
+ */
+public class ChebyshevSong extends JApplet implements Runnable {
+ private static final long serialVersionUID = -7459137388629333223L;
+ private Synthesizer synth;
+ private Add mixer;
+ private LineOut lineOut;
+ private AudioScope scope;
+ private volatile boolean go = false;
+ private PseudoRandom pseudo = new PseudoRandom();
+ private final static int MAX_VOICES = 8;
+ private final static int MAX_NOTES = 5;
+ private VoiceAllocator allocator;
+ private final static int scale[] = {
+ 0, 2, 4, 7, 9
+ }; // pentatonic scale
+
+ /* Can be run as either an application or as an applet. */
+ public static void main(String args[]) {
+ ChebyshevSong applet = new ChebyshevSong();
+ JAppletFrame frame = new JAppletFrame("ChebyshevSong", applet);
+ frame.setSize(640, 300);
+ frame.setVisible(true);
+ frame.test();
+ }
+
+ /*
+ * Setup synthesis.
+ */
+ @Override
+ public void start() {
+ setLayout(new BorderLayout());
+
+ synth = JSyn.createSynthesizer();
+
+ // Use a submix so we can show it on the scope.
+ synth.add(mixer = new Add());
+ synth.add(lineOut = new LineOut());
+
+ mixer.output.connect(0, lineOut.input, 0);
+ mixer.output.connect(0, lineOut.input, 1);
+
+ WaveShapingVoice[] voices = new WaveShapingVoice[MAX_VOICES];
+ for (int i = 0; i < MAX_VOICES; i++) {
+ WaveShapingVoice voice = new WaveShapingVoice();
+ synth.add(voice);
+ voice.usePreset(0);
+ voice.getOutput().connect(mixer.inputA);
+ voices[i] = voice;
+ }
+ allocator = new VoiceAllocator(voices);
+
+ // Start synthesizer using default stereo output at 44100 Hz.
+ synth.start();
+ lineOut.start();
+
+ // Use a scope to show the mixed output.
+ scope = new AudioScope(synth);
+ scope.addProbe(mixer.output);
+ scope.setTriggerMode(AudioScope.TriggerMode.NORMAL);
+ scope.getView().setControlsVisible(false);
+ add(BorderLayout.CENTER, scope.getView());
+ scope.start();
+
+ /* Synchronize Java display. */
+ getParent().validate();
+ getToolkit().sync();
+
+ // start thread that plays notes
+ Thread thread = new Thread(this);
+ go = true;
+ thread.start();
+
+ }
+
+ @Override
+ public void stop() {
+ // tell song thread to finish
+ go = false;
+ removeAll();
+ synth.stop();
+ }
+
+ double indexToFrequency(int index) {
+ int octave = index / scale.length;
+ int temp = index % scale.length;
+ int pitch = scale[temp] + (12 * octave);
+ return AudioMath.pitchToFrequency(pitch + 16);
+ }
+
+ private void noteOff(double time, int noteNumber) {
+ allocator.noteOff(noteNumber, new TimeStamp(time));
+ }
+
+ private void noteOn(double time, int noteNumber) {
+ double frequency = indexToFrequency(noteNumber);
+ double amplitude = 0.1;
+ TimeStamp timeStamp = new TimeStamp(time);
+ allocator.noteOn(noteNumber, frequency, amplitude, timeStamp);
+ allocator.setPort(noteNumber, "Range", 0.7, synth.createTimeStamp());
+ }
+
+ @Override
+ public void run() {
+ // always choose a new song based on time&date
+ int savedSeed = (int) System.currentTimeMillis();
+ // calculate tempo
+ double duration = 0.2;
+ // set time ahead of any system latency
+ double advanceTime = 0.5;
+ // time for next note to start
+ double nextTime = synth.getCurrentTime() + advanceTime;
+ // note is ON for half the duration
+ double onTime = duration / 2;
+ int beatIndex = 0;
+ try {
+ do {
+ // on every measure, maybe repeat previous pattern
+ if ((beatIndex & 7) == 0) {
+ if ((Math.random() < (1.0 / 2.0)))
+ pseudo.setSeed(savedSeed);
+ else if ((Math.random() < (1.0 / 2.0)))
+ savedSeed = pseudo.getSeed();
+ }
+
+ // Play a bunch of random notes in the scale.
+ int numNotes = pseudo.choose(MAX_NOTES);
+ for (int i = 0; i < numNotes; i++) {
+ int noteNumber = pseudo.choose(30);
+ noteOn(nextTime, noteNumber);
+ noteOff(nextTime + onTime, noteNumber);
+ }
+
+ nextTime += duration;
+ beatIndex += 1;
+
+ // wake up before we need to play note to cover system latency
+ synth.sleepUntil(nextTime - advanceTime);
+ } while (go);
+ } catch (InterruptedException e) {
+ System.err.println("Song exiting. " + e);
+ }
+ }
+}
diff --git a/tests/com/jsyn/examples/CircuitTester.java b/tests/com/jsyn/examples/CircuitTester.java
new file mode 100644
index 0000000..22db760
--- /dev/null
+++ b/tests/com/jsyn/examples/CircuitTester.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2012 Phil Burk, Mobileer Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.jsyn.examples;
+
+import java.awt.BorderLayout;
+
+import javax.swing.JApplet;
+
+import com.jsyn.JSyn;
+import com.jsyn.Synthesizer;
+import com.jsyn.scope.AudioScope;
+import com.jsyn.swing.JAppletFrame;
+import com.jsyn.swing.SoundTweaker;
+import com.jsyn.unitgen.LineOut;
+import com.jsyn.unitgen.UnitSource;
+
+/**
+ * Listen to a circuit while tweaking it knobs. Show output in a scope.
+ *
+ * @author Phil Burk (C) 2012 Mobileer Inc
+ */
+public class CircuitTester extends JApplet {
+ private static final long serialVersionUID = -2704222221111608377L;
+ private Synthesizer synth;
+ private LineOut lineOut;
+ private SoundTweaker tweaker;
+ private UnitSource unitSource;
+ private AudioScope scope;
+
+ @Override
+ public void init() {
+ setLayout(new BorderLayout());
+
+ synth = JSyn.createSynthesizer();
+ synth.add(lineOut = new LineOut());
+
+ unitSource = createUnitSource();
+ synth.add(unitSource.getUnitGenerator());
+
+ // Connect the source to both left and right speakers.
+ unitSource.getOutput().connect(0, lineOut.input, 0);
+ unitSource.getOutput().connect(0, lineOut.input, 1);
+
+ tweaker = new SoundTweaker(synth, unitSource.getUnitGenerator().getClass().getName(),
+ unitSource);
+ add(tweaker, BorderLayout.CENTER);
+
+ // Use a scope to see the output.
+ scope = new AudioScope(synth);
+ scope.addProbe(unitSource.getOutput());
+ scope.setTriggerMode(AudioScope.TriggerMode.NORMAL);
+ scope.getView().setControlsVisible(false);
+ add(BorderLayout.SOUTH, scope.getView());
+
+ validate();
+ }
+
+ /**
+ * Override this to test your own circuits.
+ *
+ * @return
+ */
+ public UnitSource createUnitSource() {
+ // return new SampleHoldNoteBlaster();
+ // return new com.syntona.exported.FMVoice();
+ // return new SubtractiveSynthVoice();
+ return new WindCircuit();
+ }
+
+ @Override
+ public void start() {
+ // Start synthesizer using default stereo output at 44100 Hz.
+ synth.start();
+ // Start the LineOut. It will pull data from the other units.
+ lineOut.start();
+
+ scope.start();
+ }
+
+ @Override
+ public void stop() {
+ scope.stop();
+ synth.stop();
+ }
+
+ /* Can be run as either an application or as an applet. */
+ public static void main(String args[]) {
+ CircuitTester applet = new CircuitTester();
+ JAppletFrame frame = new JAppletFrame("JSyn Circuit Tester", applet);
+ frame.setSize(600, 600);
+ frame.setVisible(true);
+ frame.test();
+ }
+
+}
diff --git a/tests/com/jsyn/examples/CustomCubeUnit.java b/tests/com/jsyn/examples/CustomCubeUnit.java
new file mode 100644
index 0000000..892c30c
--- /dev/null
+++ b/tests/com/jsyn/examples/CustomCubeUnit.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2010 Phil Burk, Mobileer Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.jsyn.examples;
+
+import com.jsyn.unitgen.UnitFilter;
+
+/**
+ * Custom unit generator that can be used with other JSyn units. Cube the input value and write it
+ * to output port.
+ *
+ * @author Phil Burk (C) 2010 Mobileer Inc
+ */
+public class CustomCubeUnit extends UnitFilter {
+
+ @Override
+ /** This is where the synthesis occurs.
+ * It is called in a high priority background thread so do not do
+ * anything crazy here like reading a file or doing network I/O.
+ * Just do fast arithmetic.
+ * <br>
+ * The start and limit allow us to do either block or single sample processing.
+ */
+ public void generate(int start, int limit) {
+ // Get signal arrays from ports.
+ double[] inputs = input.getValues();
+ double[] outputs = output.getValues();
+
+ for (int i = start; i < limit; i++) {
+ double x = inputs[i];
+ // Do the math.
+ outputs[i] = x * x * x;
+ }
+ }
+}
diff --git a/tests/com/jsyn/examples/DualOscilloscope.java b/tests/com/jsyn/examples/DualOscilloscope.java
new file mode 100644
index 0000000..d7798f4
--- /dev/null
+++ b/tests/com/jsyn/examples/DualOscilloscope.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright 2012 Phil Burk, Mobileer Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.jsyn.examples;
+
+import java.awt.BorderLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.ArrayList;
+
+import javax.swing.JApplet;
+import javax.swing.JComboBox;
+
+import com.jsyn.JSyn;
+import com.jsyn.Synthesizer;
+import com.jsyn.devices.AudioDeviceFactory;
+import com.jsyn.devices.AudioDeviceManager;
+import com.jsyn.scope.AudioScope;
+import com.jsyn.swing.JAppletFrame;
+import com.jsyn.unitgen.ChannelIn;
+import com.jsyn.unitgen.PassThrough;
+
+/**
+ * Two channel oscilloscope that demonstrates the use of audio input.
+ *
+ * @author Phil Burk (C) 2012 Mobileer Inc
+ */
+public class DualOscilloscope extends JApplet {
+ private static final long serialVersionUID = -2704222221111608377L;
+ private Synthesizer synth;
+ private ChannelIn channel1;
+ private ChannelIn channel2;
+ private PassThrough pass1;
+ private PassThrough pass2;
+ private AudioScope scope;
+ private AudioDeviceManager audioManager;
+ private int defaultInputId;
+ private ArrayList<String> deviceNames = new ArrayList<String>();
+ private ArrayList<Integer> deviceMaxInputs = new ArrayList<Integer>();
+ private ArrayList<Integer> deviceIds = new ArrayList<Integer>();
+ private int defaultSelection;
+ private JComboBox deviceComboBox;
+
+ @Override
+ public void init() {
+ audioManager = AudioDeviceFactory.createAudioDeviceManager(true);
+ synth = JSyn.createSynthesizer(audioManager);
+
+ int numDevices = audioManager.getDeviceCount();
+ defaultInputId = audioManager.getDefaultInputDeviceID();
+ for (int i = 0; i < numDevices; i++) {
+ int maxInputs = audioManager.getMaxInputChannels(i);
+ if (maxInputs > 0) {
+ String deviceName = audioManager.getDeviceName(i);
+ String itemName = maxInputs + ", " + deviceName + " (#" + i + ")";
+ if (i == defaultInputId) {
+ defaultSelection = deviceNames.size();
+ itemName += " (Default)";
+ }
+ deviceNames.add(itemName);
+ deviceMaxInputs.add(maxInputs);
+ deviceIds.add(i);
+ }
+ }
+
+ synth.add(channel1 = new ChannelIn());
+ channel1.setChannelIndex(0);
+ synth.add(channel2 = new ChannelIn());
+ channel2.setChannelIndex(1);
+
+ // Use PassThrough so we can easily disconnect input channels from the scope.
+ synth.add(pass1 = new PassThrough());
+ synth.add(pass2 = new PassThrough());
+
+ setupGUI();
+ }
+
+ private void setupGUI() {
+ setLayout(new BorderLayout());
+
+ deviceComboBox = new JComboBox(deviceNames.toArray(new String[0]));
+ deviceComboBox.setSelectedIndex(defaultSelection);
+ add(deviceComboBox, BorderLayout.NORTH);
+ deviceComboBox.addActionListener(new ActionListener() {
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ stopAudio();
+ int itemIndex = deviceComboBox.getSelectedIndex();
+ startAudio(itemIndex);
+ }
+ });
+
+ scope = new AudioScope(synth);
+
+ scope.addProbe(pass1.output);
+ scope.addProbe(pass2.output);
+
+ scope.setTriggerMode(AudioScope.TriggerMode.AUTO);
+ scope.getView().setControlsVisible(true);
+ add(scope.getView(), BorderLayout.CENTER);
+ validate();
+ }
+
+ protected void startAudio(int itemIndex) {
+ // Both stereo.
+ int numInputChannels = deviceMaxInputs.get(itemIndex);
+ if (numInputChannels > 2)
+ numInputChannels = 2;
+ int inputDeviceIndex = deviceIds.get(itemIndex);
+ synth.start(44100, inputDeviceIndex, numInputChannels,
+ AudioDeviceManager.USE_DEFAULT_DEVICE, 0);
+
+ channel1.output.connect(pass1.input);
+ // Only connect second channel if more than one input channel.
+ if (numInputChannels > 1) {
+ channel2.output.connect(pass2.input);
+ }
+
+ // We only need to start the LineOut. It will pull data from the
+ // channels.
+ scope.start();
+ }
+
+ @Override
+ public void start() {
+ startAudio(defaultSelection);
+ }
+
+ public void stopAudio() {
+ pass1.input.disconnectAll();
+ pass2.input.disconnectAll();
+ scope.stop();
+ synth.stop();
+ }
+
+ @Override
+ public void stop() {
+ stopAudio();
+ }
+
+ /* Can be run as either an application or as an applet. */
+ public static void main(String args[]) {
+ DualOscilloscope applet = new DualOscilloscope();
+ JAppletFrame frame = new JAppletFrame("Dual Oscilloscope", applet);
+ frame.setSize(640, 400);
+ frame.setVisible(true);
+ frame.test();
+ }
+
+}
diff --git a/tests/com/jsyn/examples/EditEnvelope1.java b/tests/com/jsyn/examples/EditEnvelope1.java
new file mode 100644
index 0000000..763037b
--- /dev/null
+++ b/tests/com/jsyn/examples/EditEnvelope1.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright 1997 Phil Burk, Mobileer Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/**
+ * Test Envelope using Java Audio Synthesizer
+ * Trigger attack or release portion.
+ *
+ * @author (C) 1997 Phil Burk
+ */
+
+package com.jsyn.examples;
+
+import java.awt.BorderLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.JApplet;
+import javax.swing.JButton;
+import javax.swing.JPanel;
+
+import com.jsyn.JSyn;
+import com.jsyn.Synthesizer;
+import com.jsyn.data.SegmentedEnvelope;
+import com.jsyn.swing.EnvelopeEditorPanel;
+import com.jsyn.swing.EnvelopePoints;
+import com.jsyn.swing.JAppletFrame;
+import com.jsyn.unitgen.LineOut;
+import com.jsyn.unitgen.SawtoothOscillatorBL;
+import com.jsyn.unitgen.UnitOscillator;
+import com.jsyn.unitgen.VariableRateDataReader;
+import com.jsyn.unitgen.VariableRateMonoReader;
+
+public class EditEnvelope1 extends JApplet {
+ private Synthesizer synth;
+ private UnitOscillator osc;
+ private LineOut lineOut;
+ private SegmentedEnvelope envelope;
+ private VariableRateDataReader envelopePlayer;
+
+ final int MAX_FRAMES = 16;
+ JButton hitme;
+ JButton attackButton;
+ JButton releaseButton;
+ private EnvelopeEditorPanel envEditor;
+ private EnvelopePoints points;
+
+ /* Can be run as either an application or as an applet. */
+ public static void main(String args[]) {
+ EditEnvelope1 applet = new EditEnvelope1();
+ JAppletFrame frame = new JAppletFrame("Test SynthEnvelope", applet);
+ frame.setSize(440, 200);
+ frame.setVisible(true);
+ frame.test();
+ }
+
+ /*
+ * Setup synthesis.
+ */
+ @Override
+ public void start() {
+ setLayout(new BorderLayout());
+
+ // Create a context for the synthesizer.
+ synth = JSyn.createSynthesizer();
+ // Add a tone generator.
+ synth.add(osc = new SawtoothOscillatorBL());
+ // Add an envelope player.
+ synth.add(envelopePlayer = new VariableRateMonoReader());
+
+ envelope = new SegmentedEnvelope(MAX_FRAMES);
+
+ // Add an output mixer.
+ synth.add(lineOut = new LineOut());
+ envelopePlayer.output.connect(osc.amplitude);
+ // Connect the oscillator to the output.
+ osc.output.connect(0, lineOut.input, 0);
+ osc.output.connect(0, lineOut.input, 1);
+
+ // Start synthesizer using default stereo output at 44100 Hz.
+ synth.start();
+
+ JPanel bottomPanel = new JPanel();
+ bottomPanel.add(hitme = new JButton("On"));
+ hitme.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ points.updateEnvelopeIfDirty(envelope);
+ envelopePlayer.dataQueue.queueOn(envelope);
+ }
+ });
+
+ bottomPanel.add(attackButton = new JButton("Off"));
+ attackButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ points.updateEnvelopeIfDirty(envelope);
+ envelopePlayer.dataQueue.queueOff(envelope);
+ }
+ });
+
+ bottomPanel.add(releaseButton = new JButton("Queue"));
+ releaseButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ points.updateEnvelopeIfDirty(envelope);
+ envelopePlayer.dataQueue.queue(envelope);
+ }
+ });
+
+ add(bottomPanel, BorderLayout.SOUTH);
+ lineOut.start();
+
+ // Create vector of points for editor.
+ points = new EnvelopePoints();
+ points.setName(osc.amplitude.getName());
+
+ // Setup initial envelope shape.
+ points.add(0.5, 1.0);
+ points.add(0.5, 0.2);
+ points.add(0.5, 0.8);
+ points.add(0.5, 0.0);
+ points.updateEnvelope(envelope);
+
+ // Add an envelope editor to the center of the panel.
+ add("Center", envEditor = new EnvelopeEditorPanel(points, MAX_FRAMES));
+
+ /* Synchronize Java display. */
+ getParent().validate();
+ getToolkit().sync();
+ }
+
+ @Override
+ public void stop() {
+ synth.stop();
+ }
+}
diff --git a/tests/com/jsyn/examples/FFTPassthrough.java b/tests/com/jsyn/examples/FFTPassthrough.java
new file mode 100644
index 0000000..1a1a7c5
--- /dev/null
+++ b/tests/com/jsyn/examples/FFTPassthrough.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2010 Phil Burk, Mobileer Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.jsyn.examples;
+
+import com.jsyn.JSyn;
+import com.jsyn.Synthesizer;
+import com.jsyn.unitgen.LineOut;
+import com.jsyn.unitgen.PassThrough;
+import com.jsyn.unitgen.SineOscillator;
+import com.jsyn.unitgen.SpectralFFT;
+import com.jsyn.unitgen.SpectralIFFT;
+import com.jsyn.unitgen.UnitOscillator;
+
+/**
+ * Play a sine sweep through an FFT/IFFT pair.
+ *
+ * @author Phil Burk (C) 2010 Mobileer Inc
+ */
+public class FFTPassthrough {
+ private Synthesizer synth;
+ private PassThrough center;
+ private UnitOscillator osc;
+ private UnitOscillator lfo;
+ private SpectralFFT fft;
+ private SpectralIFFT ifft1;
+ private LineOut lineOut;
+ private SpectralIFFT ifft2;
+
+ private void test() {
+ // Create a context for the synthesizer.
+ synth = JSyn.createSynthesizer();
+
+ // Start synthesizer using default stereo output at 44100 Hz.
+ synth.start();
+
+ // Add a tone generator.
+ synth.add(center = new PassThrough());
+ // synth.add( osc = new SawtoothOscillatorBL() );
+ synth.add(osc = new SineOscillator());
+ synth.add(lfo = new SineOscillator());
+ synth.add(fft = new SpectralFFT());
+ synth.add(ifft1 = new SpectralIFFT());
+ synth.add(ifft2 = new SpectralIFFT());
+ // Add a stereo audio output unit.
+ synth.add(lineOut = new LineOut());
+
+ // Connect the oscillator to both channels of the output.
+ center.output.connect(osc.frequency);
+ lfo.output.connect(osc.frequency);
+ osc.output.connect(fft.input);
+ fft.output.connect(ifft1.input);
+ fft.output.connect(ifft2.input);
+ ifft1.output.connect(0, lineOut.input, 0);
+ ifft2.output.connect(0, lineOut.input, 1);
+
+ // Set the frequency and amplitude for the modulated sine wave.
+ center.input.set(600.0);
+ lfo.frequency.set(0.2);
+ lfo.amplitude.set(400.0);
+ osc.amplitude.set(0.6);
+
+ // We only need to start the LineOut. It will pull data through the
+ // chain.
+ lineOut.start();
+
+ System.out.println("You should now be hearing a clean oscillator on the left channel,");
+ System.out.println("and the FFT->IFFT processed signal on the right channel.");
+
+ // Sleep while the sound is generated in the background.
+ try {
+ double time = synth.getCurrentTime();
+ // Sleep for a few seconds.
+ synth.sleepUntil(time + 20.0);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+
+ System.out.println("Stop playing. -------------------");
+ // Stop everything.
+ synth.stop();
+ }
+
+ public static void main(String[] args) {
+ new FFTPassthrough().test();
+ }
+}
diff --git a/tests/com/jsyn/examples/GoogleWaveOscillator.java b/tests/com/jsyn/examples/GoogleWaveOscillator.java
new file mode 100644
index 0000000..2f45cc4
--- /dev/null
+++ b/tests/com/jsyn/examples/GoogleWaveOscillator.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2010 Phil Burk, Mobileer Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.jsyn.examples;
+
+import com.jsyn.ports.UnitInputPort;
+import com.jsyn.unitgen.UnitOscillator;
+
+/**
+ * Custom unit generator to create the waveform shown on the Google home page on 2/22/12.
+ *
+ * @author Phil Burk (C) 2010 Mobileer Inc
+ */
+public class GoogleWaveOscillator extends UnitOscillator {
+ public UnitInputPort variance;
+ private double phaseIncrement = 0.1;
+ private double previousY;
+ private double randomAmplitude = 0.0;
+
+ public GoogleWaveOscillator() {
+ addPort(variance = new UnitInputPort("Variance", 0.0));
+ }
+
+ @Override
+ public void generate(int start, int limit) {
+ // Get signal arrays from ports.
+ double[] freqs = frequency.getValues();
+ double[] outputs = output.getValues();
+ double currentPhase = phase.getValue();
+ double y;
+
+ for (int i = start; i < limit; i++) {
+ if (currentPhase > 0.0) {
+ double p = currentPhase;
+ y = Math.sqrt(4.0 * (p * (1.0 - p)));
+ } else {
+ double p = -currentPhase;
+ y = -Math.sqrt(4.0 * (p * (1.0 - p)));
+ }
+
+ if ((previousY * y) <= 0.0) {
+ // Calculate randomly offset phaseIncrement.
+ double v = variance.getValues()[0];
+ double range = ((Math.random() - 0.5) * 4.0 * v);
+ double scale = Math.pow(2.0, range);
+ phaseIncrement = convertFrequencyToPhaseIncrement(freqs[i]) * scale;
+
+ // Calculate random amplitude.
+ scale = 1.0 + ((Math.random() - 0.5) * 1.5 * v);
+ randomAmplitude = amplitude.getValues()[0] * scale;
+ }
+
+ outputs[i] = y * randomAmplitude;
+ previousY = y;
+
+ currentPhase = incrementWrapPhase(currentPhase, phaseIncrement);
+ }
+ phase.setValue(currentPhase);
+ }
+}
diff --git a/tests/com/jsyn/examples/HearDAHDSR.java b/tests/com/jsyn/examples/HearDAHDSR.java
new file mode 100644
index 0000000..23e6fb5
--- /dev/null
+++ b/tests/com/jsyn/examples/HearDAHDSR.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2010 Phil Burk, Mobileer Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.jsyn.examples;
+
+import java.awt.GridLayout;
+
+import javax.swing.BorderFactory;
+import javax.swing.JApplet;
+
+import com.jsyn.JSyn;
+import com.jsyn.Synthesizer;
+import com.jsyn.ports.UnitInputPort;
+import com.jsyn.swing.DoubleBoundedRangeModel;
+import com.jsyn.swing.JAppletFrame;
+import com.jsyn.swing.PortModelFactory;
+import com.jsyn.swing.RotaryTextController;
+import com.jsyn.unitgen.EnvelopeDAHDSR;
+import com.jsyn.unitgen.LineOut;
+import com.jsyn.unitgen.SineOscillator;
+import com.jsyn.unitgen.SquareOscillator;
+import com.jsyn.unitgen.UnitOscillator;
+
+/**
+ * Play a tone using a JSyn oscillator. Modulate the amplitude using a DAHDSR envelope.
+ *
+ * @author Phil Burk (C) 2010 Mobileer Inc
+ */
+public class HearDAHDSR extends JApplet {
+ private static final long serialVersionUID = -2704222221111608377L;
+ private Synthesizer synth;
+ private UnitOscillator osc;
+ // Use a square wave to trigger the envelope.
+ private UnitOscillator gatingOsc;
+ private EnvelopeDAHDSR dahdsr;
+ private LineOut lineOut;
+
+ @Override
+ public void init() {
+ synth = JSyn.createSynthesizer();
+
+ // Add a tone generator.
+ synth.add(osc = new SineOscillator());
+ // Add a trigger.
+ synth.add(gatingOsc = new SquareOscillator());
+ // Use an envelope to control the amplitude.
+ synth.add(dahdsr = new EnvelopeDAHDSR());
+ // Add an output mixer.
+ synth.add(lineOut = new LineOut());
+
+ gatingOsc.output.connect(dahdsr.input);
+ dahdsr.output.connect(osc.amplitude);
+ dahdsr.attack.setup(0.001, 0.01, 2.0);
+ osc.output.connect(0, lineOut.input, 0);
+ osc.output.connect(0, lineOut.input, 1);
+
+ gatingOsc.frequency.setup(0.001, 0.5, 10.0);
+ gatingOsc.frequency.setName("Rate");
+
+ osc.frequency.setup(50.0, 440.0, 2000.0);
+ osc.frequency.setName("Freq");
+
+ // Arrange the knob in a row.
+ setLayout(new GridLayout(1, 0));
+
+ setupPortKnob(osc.frequency);
+ setupPortKnob(gatingOsc.frequency);
+ setupPortKnob(dahdsr.attack);
+ setupPortKnob(dahdsr.hold);
+ setupPortKnob(dahdsr.decay);
+ setupPortKnob(dahdsr.sustain);
+ setupPortKnob(dahdsr.release);
+
+ validate();
+ }
+
+ private void setupPortKnob(UnitInputPort port) {
+
+ DoubleBoundedRangeModel model = PortModelFactory.createExponentialModel(port);
+ System.out.println("Make knob for " + port.getName() + ", model.getDV = "
+ + model.getDoubleValue() + ", model.getV = " + model.getValue() + ", port.getV = "
+ + port.get());
+ RotaryTextController knob = new RotaryTextController(model, 10);
+ knob.setBorder(BorderFactory.createTitledBorder(port.getName()));
+ knob.setTitle(port.getName());
+ add(knob);
+ }
+
+ @Override
+ public void start() {
+ // Start synthesizer using default stereo output at 44100 Hz.
+ synth.start();
+ // We only need to start the LineOut. It will pull data from the
+ // oscillator.
+ lineOut.start();
+ }
+
+ @Override
+ public void stop() {
+ synth.stop();
+ }
+
+ /* Can be run as either an application or as an applet. */
+ public static void main(String args[]) {
+ HearDAHDSR applet = new HearDAHDSR();
+ JAppletFrame frame = new JAppletFrame("Hear DAHDSR Envelope", applet);
+ frame.setSize(640, 200);
+ frame.setVisible(true);
+ frame.test();
+ }
+
+}
diff --git a/tests/com/jsyn/examples/HearMoogFilter.java b/tests/com/jsyn/examples/HearMoogFilter.java
new file mode 100644
index 0000000..dfe3bec
--- /dev/null
+++ b/tests/com/jsyn/examples/HearMoogFilter.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright 2010 Phil Burk, Mobileer Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.jsyn.examples;
+
+import java.awt.BorderLayout;
+import java.awt.GridLayout;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+
+import javax.swing.BorderFactory;
+import javax.swing.BoxLayout;
+import javax.swing.ButtonGroup;
+import javax.swing.JApplet;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JRadioButton;
+
+import com.jsyn.JSyn;
+import com.jsyn.Synthesizer;
+import com.jsyn.ports.UnitInputPort;
+import com.jsyn.scope.AudioScope;
+import com.jsyn.scope.AudioScopeProbe;
+import com.jsyn.swing.DoubleBoundedRangeModel;
+import com.jsyn.swing.JAppletFrame;
+import com.jsyn.swing.PortModelFactory;
+import com.jsyn.swing.RotaryTextController;
+import com.jsyn.unitgen.FilterFourPoles;
+import com.jsyn.unitgen.FilterLowPass;
+import com.jsyn.unitgen.LineOut;
+import com.jsyn.unitgen.LinearRamp;
+import com.jsyn.unitgen.PassThrough;
+import com.jsyn.unitgen.SawtoothOscillatorBL;
+import com.jsyn.unitgen.UnitOscillator;
+
+/**
+ * Play a sawtooth through a 4-pole filter.
+ *
+ * @author Phil Burk (C) 2010 Mobileer Inc
+ */
+public class HearMoogFilter extends JApplet {
+ private Synthesizer synth;
+ private UnitOscillator oscillator;
+ private FilterFourPoles filterMoog;
+ private FilterLowPass filterBiquad;
+ private LinearRamp rampCutoff;
+ private PassThrough tieQ;
+ private PassThrough mixer;
+ private LineOut lineOut;
+
+ private AudioScope scope;
+ private AudioScopeProbe moogProbe;
+
+ @Override
+ public void init() {
+ synth = JSyn.createSynthesizer();
+ synth.add(oscillator = new SawtoothOscillatorBL());
+ synth.add(rampCutoff = new LinearRamp());
+ synth.add(tieQ = new PassThrough());
+ synth.add(filterMoog = new FilterFourPoles());
+ synth.add(filterBiquad = new FilterLowPass());
+ synth.add(mixer = new PassThrough());
+ synth.add(lineOut = new LineOut());
+
+ oscillator.output.connect(filterMoog.input);
+ oscillator.output.connect(filterBiquad.input);
+ rampCutoff.output.connect(filterMoog.frequency);
+ rampCutoff.output.connect(filterBiquad.frequency);
+ rampCutoff.time.set(0.050);
+ tieQ.output.connect(filterMoog.Q);
+ tieQ.output.connect(filterBiquad.Q);
+ filterMoog.output.connect(mixer.input);
+ mixer.output.connect(0, lineOut.input, 0);
+ mixer.output.connect(0, lineOut.input, 1);
+
+ filterBiquad.amplitude.set(0.1);
+ oscillator.frequency.setup(50.0, 130.0, 3000.0);
+ oscillator.amplitude.setup(0.0, 0.336, 1.0);
+ rampCutoff.input.setup(50.0, 400.0, 4000.0);
+ tieQ.input.setup(0.1, 0.7, 10.0);
+ setupGUI();
+ }
+
+ private void setupGUI() {
+ setLayout(new BorderLayout());
+
+ add(new JLabel("Sawtooth through a \"Moog\" style filter."), BorderLayout.NORTH);
+
+ JPanel rackPanel = new JPanel();
+ rackPanel.setLayout(new BoxLayout(rackPanel, BoxLayout.Y_AXIS));
+
+ JPanel buttonPanel = new JPanel();
+ ButtonGroup cbg = new ButtonGroup();
+ JRadioButton radioButton = new JRadioButton("Moog", true);
+ cbg.add(radioButton);
+ radioButton.addItemListener(new ItemListener() {
+ @Override
+ public void itemStateChanged(ItemEvent e) {
+ mixer.input.disconnectAll();
+ filterMoog.output.connect(mixer.input);
+ }
+ });
+ buttonPanel.add(radioButton);
+ radioButton = new JRadioButton("Biquad", false);
+ cbg.add(radioButton);
+ radioButton.addItemListener(new ItemListener() {
+ @Override
+ public void itemStateChanged(ItemEvent e) {
+ mixer.input.disconnectAll();
+ filterBiquad.output.connect(mixer.input);
+ }
+ });
+ buttonPanel.add(radioButton);
+
+ /*
+ * buttonPanel.add( new JLabel("Show:") ); cbg = new ButtonGroup(); radioButton = new
+ * JRadioButton( "Waveform", true ); cbg.add( radioButton ); radioButton.addItemListener(
+ * new ItemListener() { public void itemStateChanged( ItemEvent e ) { scope.setViewMode(
+ * AudioScope.ViewMode.WAVEFORM ); } } ); buttonPanel.add( radioButton ); radioButton = new
+ * JRadioButton( "Spectrum", true ); cbg.add( radioButton ); radioButton.addItemListener(
+ * new ItemListener() { public void itemStateChanged( ItemEvent e ) { scope.setViewMode(
+ * AudioScope.ViewMode.SPECTRUM ); } } ); buttonPanel.add( radioButton );
+ */
+
+ rackPanel.add(buttonPanel);
+
+ // Arrange the knobs in a row.
+ JPanel knobPanel = new JPanel();
+ knobPanel.setLayout(new GridLayout(1, 0));
+
+ knobPanel.add(setupPortKnob(oscillator.frequency, "OscFreq"));
+ knobPanel.add(setupPortKnob(oscillator.amplitude, "OscAmp"));
+
+ knobPanel.add(setupPortKnob(rampCutoff.input, "Cutoff"));
+ knobPanel.add(setupPortKnob(tieQ.input, "Q"));
+ rackPanel.add(knobPanel);
+ add(rackPanel, BorderLayout.SOUTH);
+
+ scope = new AudioScope(synth);
+ scope.addProbe(oscillator.output);
+ moogProbe = scope.addProbe(filterMoog.output);
+ scope.addProbe(filterBiquad.output);
+ scope.setTriggerMode(AudioScope.TriggerMode.NORMAL);
+ scope.getView().setControlsVisible(false);
+ add(scope.getView(), BorderLayout.CENTER);
+ scope.start();
+ validate();
+ }
+
+ private RotaryTextController setupPortKnob(UnitInputPort port, String label) {
+ DoubleBoundedRangeModel model = PortModelFactory.createExponentialModel(port);
+ RotaryTextController knob = new RotaryTextController(model, 10);
+ knob.setBorder(BorderFactory.createTitledBorder(label));
+ knob.setTitle(label);
+ return knob;
+ }
+
+ @Override
+ public void start() {
+ // Start synthesizer using default stereo output at 44100 Hz.
+ synth.start();
+ scope.start();
+ // We only need to start the LineOut. It will pull data from the
+ // oscillator.
+ lineOut.start();
+ }
+
+ @Override
+ public void stop() {
+ scope.stop();
+ synth.stop();
+ }
+
+ /* Can be run as either an application or as an applet. */
+ public static void main(String args[]) {
+ HearMoogFilter applet = new HearMoogFilter();
+ JAppletFrame frame = new JAppletFrame("Hear Moog Style Filter", applet);
+ frame.setSize(800, 600);
+ frame.setVisible(true);
+ frame.test();
+ }
+
+}
diff --git a/tests/com/jsyn/examples/HearSinePM.java b/tests/com/jsyn/examples/HearSinePM.java
new file mode 100644
index 0000000..2949605
--- /dev/null
+++ b/tests/com/jsyn/examples/HearSinePM.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright 2010 Phil Burk, Mobileer Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.jsyn.examples;
+
+import java.awt.BorderLayout;
+import java.awt.GridLayout;
+
+import javax.swing.BorderFactory;
+import javax.swing.JApplet;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+import com.jsyn.JSyn;
+import com.jsyn.Synthesizer;
+import com.jsyn.ports.UnitInputPort;
+import com.jsyn.scope.AudioScope;
+import com.jsyn.swing.DoubleBoundedRangeModel;
+import com.jsyn.swing.JAppletFrame;
+import com.jsyn.swing.PortModelFactory;
+import com.jsyn.swing.RotaryTextController;
+import com.jsyn.unitgen.LineOut;
+import com.jsyn.unitgen.SineOscillator;
+import com.jsyn.unitgen.SineOscillatorPhaseModulated;
+
+/**
+ * Play a tone using a phase modulated sinewave oscillator. Phase modulation (PM) is very similar to
+ * frequency modulation (FM) but is easier to control.
+ *
+ * @author Phil Burk (C) 2010 Mobileer Inc
+ */
+public class HearSinePM extends JApplet {
+ private static final long serialVersionUID = -2704222221111608377L;
+ private Synthesizer synth;
+ SineOscillatorPhaseModulated carrier;
+ SineOscillator modulator;
+ LineOut lineOut;
+ AudioScope scope;
+
+ @Override
+ public void init() {
+ synth = JSyn.createSynthesizer();
+ // Add a tone generator.
+ synth.add(modulator = new SineOscillator());
+ // Add a trigger.
+ synth.add(carrier = new SineOscillatorPhaseModulated());
+ // Add an output mixer.
+ synth.add(lineOut = new LineOut());
+
+ modulator.output.connect(carrier.modulation);
+ carrier.output.connect(0, lineOut.input, 0);
+ carrier.output.connect(0, lineOut.input, 1);
+ modulator.amplitude.setup(0.0, 1.0, 10.0);
+ carrier.amplitude.setup(0.0, 1.0, 1.0);
+ setupGUI();
+ }
+
+ private void setupGUI() {
+ setLayout(new BorderLayout());
+
+ add(new JLabel("Show Phase Modulation in an AudioScope"), BorderLayout.NORTH);
+
+ // Arrange the knob in a row.
+ JPanel knobPanel = new JPanel();
+ knobPanel.setLayout(new GridLayout(1, 0));
+
+ knobPanel.add(setupPortKnob(modulator.frequency, "MFreq"));
+ knobPanel.add(setupPortKnob(modulator.amplitude, "MAmp"));
+ knobPanel.add(setupPortKnob(carrier.frequency, "CFreq"));
+ knobPanel.add(setupPortKnob(carrier.amplitude, "CAmp"));
+ add(knobPanel, BorderLayout.SOUTH);
+
+ scope = new AudioScope(synth);
+ scope.addProbe(carrier.output);
+ scope.addProbe(modulator.output);
+ scope.setTriggerMode(AudioScope.TriggerMode.NORMAL);
+ scope.getView().setControlsVisible(true);
+ add(scope.getView(), BorderLayout.CENTER);
+ scope.start();
+ validate();
+ }
+
+ private RotaryTextController setupPortKnob(UnitInputPort port, String label) {
+ DoubleBoundedRangeModel model = PortModelFactory.createExponentialModel(port);
+ RotaryTextController knob = new RotaryTextController(model, 10);
+ knob.setBorder(BorderFactory.createTitledBorder(label));
+ knob.setTitle(label);
+ return knob;
+ }
+
+ @Override
+ public void start() {
+ // Start synthesizer using default stereo output at 44100 Hz.
+ synth.start();
+ scope.start();
+ // We only need to start the LineOut. It will pull data from the
+ // oscillator.
+ lineOut.start();
+ }
+
+ @Override
+ public void stop() {
+ scope.stop();
+ synth.stop();
+ }
+
+ /* Can be run as either an application or as an applet. */
+ public static void main(String args[]) {
+ HearSinePM applet = new HearSinePM();
+ JAppletFrame frame = new JAppletFrame("Hear Phase Modulation", applet);
+ frame.setSize(640, 400);
+ frame.setVisible(true);
+ frame.test();
+ }
+
+}
diff --git a/tests/com/jsyn/examples/HearSpectralFilter.java b/tests/com/jsyn/examples/HearSpectralFilter.java
new file mode 100644
index 0000000..93559b4
--- /dev/null
+++ b/tests/com/jsyn/examples/HearSpectralFilter.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright 2010 Phil Burk, Mobileer Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.jsyn.examples;
+
+import java.io.File;
+import java.io.IOException;
+
+import com.jsyn.JSyn;
+import com.jsyn.Synthesizer;
+import com.jsyn.data.Spectrum;
+import com.jsyn.unitgen.LineOut;
+import com.jsyn.unitgen.PassThrough;
+import com.jsyn.unitgen.SawtoothOscillatorBL;
+import com.jsyn.unitgen.SineOscillator;
+import com.jsyn.unitgen.SpectralFilter;
+import com.jsyn.unitgen.SpectralProcessor;
+import com.jsyn.unitgen.UnitOscillator;
+import com.jsyn.unitgen.WhiteNoise;
+import com.jsyn.util.WaveRecorder;
+
+/**
+ * Play a sine sweep through an FFT/IFFT pair.
+ *
+ * @author Phil Burk (C) 2010 Mobileer Inc
+ */
+public class HearSpectralFilter {
+ private Synthesizer synth;
+ private PassThrough center;
+ private UnitOscillator osc;
+ private UnitOscillator lfo;
+ private PassThrough mixer;
+ private SpectralFilter filter;
+ private LineOut lineOut;
+ private WaveRecorder recorder;
+ private final static boolean useRecorder = true;
+ private final static boolean useProcessor = true;
+ private final static int NUM_FFTS = 4;
+ private final static int SIZE_LOG_2 = 10;
+ private final static int SIZE = 1 << SIZE_LOG_2;
+ private SpectralProcessor[] processors;
+ private WhiteNoise noise;
+ private static int SAMPLE_RATE = 44100;
+
+ private static class CustomSpectralProcessor extends SpectralProcessor {
+ public CustomSpectralProcessor() {
+ super(SIZE);
+ }
+
+ @Override
+ public void processSpectrum(Spectrum inputSpectrum, Spectrum outputSpectrum) {
+ // pitchUpOctave( inputSpectrum, outputSpectrum );
+ lowPassFilter(inputSpectrum, outputSpectrum, 1500.0);
+ }
+
+ public void lowPassFilter(Spectrum inputSpectrum, Spectrum outputSpectrum, double frequency) {
+ inputSpectrum.copyTo(outputSpectrum);
+ double[] outReal = outputSpectrum.getReal();
+ double[] outImag = outputSpectrum.getImaginary();
+ // brickwall filter
+ int size = outReal.length;
+ int cutoff = (int) (frequency * size / SAMPLE_RATE);
+ int nyquist = size / 2;
+ for (int i = cutoff; i < nyquist; i++) {
+ // Bins above nyquist are mirror of ones below.
+ outReal[i] = outReal[size - i] = 0.0;
+ outImag[i] = outImag[size - i] = 0.0;
+ }
+ }
+
+ // TODO Figure out why this sounds bad.
+ public void pitchUpOctave(Spectrum inputSpectrum, Spectrum outputSpectrum) {
+ outputSpectrum.clear();
+ double[] inReal = inputSpectrum.getReal();
+ double[] inImag = inputSpectrum.getImaginary();
+ double[] outReal = outputSpectrum.getReal();
+ double[] outImag = outputSpectrum.getImaginary();
+ int size = inReal.length;
+ int nyquist = size / 2;
+ // Octave doubling by shifting the spectrum.
+ for (int i = nyquist - 2; i > 1; i--) {
+ int h = i / 2;
+ outReal[i] = inReal[h];
+ outImag[i] = inImag[h];
+ outReal[size - i] = inReal[size - h];
+ outImag[size - i] = inImag[size - h];
+ }
+ }
+ }
+
+ private void test() throws IOException {
+ // Create a context for the synthesizer.
+ synth = JSyn.createSynthesizer();
+ synth.setRealTime(true);
+
+ if (useRecorder) {
+ File waveFile = new File("temp_recording.wav");
+ // Default is stereo, 16 bits.
+ recorder = new WaveRecorder(synth, waveFile);
+ System.out.println("Writing to WAV file " + waveFile.getAbsolutePath());
+ }
+
+ if (useProcessor) {
+ processors = new SpectralProcessor[NUM_FFTS];
+ for (int i = 0; i < NUM_FFTS; i++) {
+ processors[i] = new CustomSpectralProcessor();
+ }
+ }
+
+ // Add a tone generator.
+ synth.add(center = new PassThrough());
+ synth.add(lfo = new SineOscillator());
+ synth.add(noise = new WhiteNoise());
+ synth.add(mixer = new PassThrough());
+
+ synth.add(osc = new SawtoothOscillatorBL());
+ // synth.add( osc = new SineOscillator() );
+
+ synth.add(filter = new SpectralFilter(NUM_FFTS, SIZE_LOG_2));
+ // Add a stereo audio output unit.
+ synth.add(lineOut = new LineOut());
+
+ center.output.connect(osc.frequency);
+ lfo.output.connect(osc.frequency);
+ osc.output.connect(mixer.input);
+ noise.output.connect(mixer.input);
+ mixer.output.connect(filter.input);
+ if (useProcessor) {
+ // Pass spectra through a custom processor.
+ for (int i = 0; i < NUM_FFTS; i++) {
+ filter.getSpectralOutput(i).connect(processors[i].input);
+ processors[i].output.connect(filter.getSpectralInput(i));
+ }
+ } else {
+ for (int i = 0; i < NUM_FFTS; i++) {
+ // Connect FFTs directly to IFFTs for passthrough.
+ filter.getSpectralOutput(i).connect(filter.getSpectralInput(i));
+ }
+
+ }
+ mixer.output.connect(0, lineOut.input, 0);
+ filter.output.connect(0, lineOut.input, 1);
+
+ // Set the frequency and amplitude for the modulated sine wave.
+ center.input.set(600.0);
+ lfo.frequency.set(0.2);
+ lfo.amplitude.set(400.0);
+ osc.amplitude.set(0.2);
+ noise.amplitude.set(0.2);
+
+ synth.start(SAMPLE_RATE);
+
+ if (useRecorder) {
+ mixer.output.connect(0, recorder.getInput(), 0);
+ filter.output.connect(0, recorder.getInput(), 1);
+ // When we start the recorder it will pull data from the oscillator
+ // and sweeper.
+ recorder.start();
+ }
+
+ lineOut.start();
+
+ System.out.println("You should now be hearing a clean oscillator on the left channel,");
+ System.out.println("and the FFT->IFFT processed signal on the right channel.");
+
+ // Sleep while the sound is generated in the background.
+ try {
+ double time = synth.getCurrentTime();
+ // Sleep for a few seconds.
+ synth.sleepUntil(time + 10.0);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+
+ if (recorder != null) {
+ recorder.stop();
+ recorder.close();
+ }
+
+ System.out.println("Stop playing. -------------------");
+ // Stop everything.
+ synth.stop();
+ }
+
+ public static void main(String[] args) {
+ try {
+ new HearSpectralFilter().test();
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/tests/com/jsyn/examples/ListAudioDevices.java b/tests/com/jsyn/examples/ListAudioDevices.java
new file mode 100644
index 0000000..6c5372d
--- /dev/null
+++ b/tests/com/jsyn/examples/ListAudioDevices.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2009 Phil Burk, Mobileer Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.jsyn.examples;
+
+import com.jsyn.devices.AudioDeviceFactory;
+import com.jsyn.devices.AudioDeviceManager;
+
+public class ListAudioDevices {
+
+ /**
+ * @param args
+ */
+ public static void main(String[] args) {
+ AudioDeviceManager audioManager = AudioDeviceFactory.createAudioDeviceManager();
+
+ int numDevices = audioManager.getDeviceCount();
+ for (int i = 0; i < numDevices; i++) {
+ String deviceName = audioManager.getDeviceName(i);
+ int maxInputs = audioManager.getMaxInputChannels(i);
+ int maxOutputs = audioManager.getMaxInputChannels(i);
+ boolean isDefaultInput = (i == audioManager.getDefaultInputDeviceID());
+ boolean isDefaultOutput = (i == audioManager.getDefaultInputDeviceID());
+ System.out.println("#" + i + " : " + deviceName);
+ System.out.println(" max inputs : " + maxInputs
+ + (isDefaultInput ? " (default)" : ""));
+ System.out.println(" max outputs: " + maxOutputs
+ + (isDefaultOutput ? " (default)" : ""));
+ }
+
+ }
+
+}
diff --git a/tests/com/jsyn/examples/LongEcho.java b/tests/com/jsyn/examples/LongEcho.java
new file mode 100644
index 0000000..41f21f2
--- /dev/null
+++ b/tests/com/jsyn/examples/LongEcho.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2010 Phil Burk, Mobileer Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.jsyn.examples;
+
+import java.io.File;
+import java.io.IOException;
+
+import com.jsyn.JSyn;
+import com.jsyn.Synthesizer;
+import com.jsyn.data.FloatSample;
+import com.jsyn.devices.AudioDeviceManager;
+import com.jsyn.unitgen.ChannelIn;
+import com.jsyn.unitgen.ChannelOut;
+import com.jsyn.unitgen.FixedRateMonoReader;
+import com.jsyn.unitgen.FixedRateMonoWriter;
+import com.jsyn.unitgen.Maximum;
+import com.jsyn.unitgen.Minimum;
+import com.jsyn.util.WaveFileWriter;
+
+/**
+ * Echo the input using a circular buffer in a sample.
+ *
+ * @author Phil Burk (C) 2010 Mobileer Inc
+ */
+public class LongEcho {
+ final static int DELAY_SECONDS = 4;
+ Synthesizer synth;
+ ChannelIn channelIn;
+ ChannelOut channelOut;
+ FloatSample sample;
+ FixedRateMonoReader reader;
+ FixedRateMonoWriter writer;
+ Minimum minner;
+ Maximum maxxer;
+
+ private void test() throws IOException {
+ // Create a context for the synthesizer.
+ synth = JSyn.createSynthesizer();
+ // Add a tone generator.
+ synth.add(channelIn = new ChannelIn());
+ // Add an output mixer.
+ synth.add(channelOut = new ChannelOut());
+
+ synth.add(minner = new Minimum());
+ synth.add(maxxer = new Maximum());
+ synth.add(reader = new FixedRateMonoReader());
+ synth.add(writer = new FixedRateMonoWriter());
+
+ sample = new FloatSample(44100 * DELAY_SECONDS, 1);
+
+ maxxer.inputB.set(-0.98); // clip
+ minner.inputB.set(0.98);
+
+ // Connect the input to the output.
+ channelIn.output.connect(minner.inputA);
+ minner.output.connect(maxxer.inputA);
+ maxxer.output.connect(writer.input);
+
+ reader.output.connect(channelOut.input);
+
+ // Both stereo.
+ int numInputChannels = 2;
+ int numOutputChannels = 2;
+ synth.start(44100, AudioDeviceManager.USE_DEFAULT_DEVICE, numInputChannels,
+ AudioDeviceManager.USE_DEFAULT_DEVICE, numOutputChannels);
+
+ writer.start();
+ channelOut.start();
+
+ // For a long echo, read cursor should be just in front of the write cursor.
+ reader.dataQueue.queue(sample, 1000, sample.getNumFrames() - 1000);
+ // Loop both forever.
+ reader.dataQueue.queueLoop(sample, 0, sample.getNumFrames());
+ writer.dataQueue.queueLoop(sample, 0, sample.getNumFrames());
+ System.out.println("Start talking. You should hear an echo after " + DELAY_SECONDS
+ + " seconds.");
+ // Sleep a while.
+ try {
+ double time = synth.getCurrentTime();
+ // Sleep for a while
+ synth.sleepUntil(time + 30.0);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+
+ saveEcho(new File("saved_echo.wav"));
+ // Stop everything.
+ synth.stop();
+ }
+
+ private void saveEcho(File file) throws IOException {
+ WaveFileWriter writer = new WaveFileWriter(file);
+ writer.setFrameRate(44100);
+ writer.setSamplesPerFrame(1);
+ writer.setBitsPerSample(16);
+ float[] buffer = new float[sample.getNumFrames()];
+ sample.read(buffer);
+ for (float v : buffer) {
+ writer.write(v);
+ }
+ writer.close();
+ }
+
+ public static void main(String[] args) {
+ try {
+ new LongEcho().test();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/tests/com/jsyn/examples/MonoPassThrough.java b/tests/com/jsyn/examples/MonoPassThrough.java
new file mode 100644
index 0000000..0e81abf
--- /dev/null
+++ b/tests/com/jsyn/examples/MonoPassThrough.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2010 Phil Burk, Mobileer Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.jsyn.examples;
+
+import com.jsyn.JSyn;
+import com.jsyn.Synthesizer;
+import com.jsyn.devices.AudioDeviceManager;
+import com.jsyn.unitgen.ChannelIn;
+import com.jsyn.unitgen.ChannelOut;
+
+/**
+ * Pass audio input to audio output.
+ *
+ * @author Phil Burk (C) 2010 Mobileer Inc
+ */
+public class MonoPassThrough {
+ Synthesizer synth;
+ ChannelIn channelIn;
+ ChannelOut channelOut;
+
+ private void test() {
+ // Create a context for the synthesizer.
+ synth = JSyn.createSynthesizer();
+ synth.add(channelIn = new ChannelIn());
+ synth.add(channelOut = new ChannelOut());
+ // Connect the input to the output.
+ channelIn.output.connect(channelOut.input);
+
+ // Both stereo.
+ int numInputChannels = 2;
+ int numOutputChannels = 2;
+ synth.start(48000, AudioDeviceManager.USE_DEFAULT_DEVICE, numInputChannels,
+ AudioDeviceManager.USE_DEFAULT_DEVICE, numOutputChannels);
+
+ // We only need to start the ChannelOut. It will pull data from the ChannelIn.
+ channelOut.start();
+ // Sleep a while.
+ try {
+ double time = synth.getCurrentTime();
+ // Sleep for a few seconds.
+ synth.sleepUntil(time + 4.0);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ // Stop everything.
+ synth.stop();
+ }
+
+ public static void main(String[] args) {
+ new MonoPassThrough().test();
+ }
+}
diff --git a/tests/com/jsyn/examples/NotesToTone.java b/tests/com/jsyn/examples/NotesToTone.java
new file mode 100644
index 0000000..9186087
--- /dev/null
+++ b/tests/com/jsyn/examples/NotesToTone.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright 2010 Phil Burk, Mobileer Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/**
+ * If you play notes fast enough they become a tone.
+ *
+ * Play a sine wave modulated by an envelope.
+ * Speed up the envelope until it is playing at audio rate.
+ * Slow down the oscillator until it becomes an LFO amp modulator.
+ * Use a LatchZeroCrossing to stop at the end of a sine wave cycle when we are finished.
+ *
+ * @author Phil Burk, (C) 2010 Mobileer Inc
+ */
+
+package com.jsyn.examples;
+
+import java.io.File;
+import java.io.IOException;
+
+import com.jsyn.JSyn;
+import com.jsyn.Synthesizer;
+import com.jsyn.data.SegmentedEnvelope;
+import com.jsyn.unitgen.ExponentialRamp;
+import com.jsyn.unitgen.LatchZeroCrossing;
+import com.jsyn.unitgen.LineOut;
+import com.jsyn.unitgen.SineOscillator;
+import com.jsyn.unitgen.UnitOscillator;
+import com.jsyn.unitgen.VariableRateDataReader;
+import com.jsyn.unitgen.VariableRateMonoReader;
+import com.jsyn.util.WaveRecorder;
+
+/**
+ * When notes speed up they can become a new tone. <br>
+ * Multiply an oscillator and an envelope. Speed up the envelope until it becomes a tone. Slow down
+ * the oscillator until it acts like an envelope. Write the resulting audio to a WAV file.
+ *
+ * @author Phil Burk (C) 2011 Mobileer Inc
+ */
+
+public class NotesToTone {
+ private final static double SONG_AMPLITUDE = 0.7;
+ private final static double INTRO_DURATION = 2.0;
+ private final static double OUTRO_DURATION = 2.0;
+ private final static double RAMP_DURATION = 20.0;
+ private final static double LOW_FREQUENCY = 1.0;
+ private final static double HIGH_FREQUENCY = 800.0;
+
+ private final static boolean useRecorder = true;
+ private WaveRecorder recorder;
+
+ private Synthesizer synth;
+ private ExponentialRamp envSweeper;
+ private ExponentialRamp oscSweeper;
+ private VariableRateDataReader envelopePlayer;
+ private UnitOscillator osc;
+ private LatchZeroCrossing latch;
+ private LineOut lineOut;
+ private SegmentedEnvelope envelope;
+
+ private void play() throws IOException {
+ synth = JSyn.createSynthesizer();
+ synth.setRealTime(true);
+
+ if (useRecorder) {
+ File waveFile = new File("notes_to_tone.wav");
+ // Default is stereo, 16 bits.
+ recorder = new WaveRecorder(synth, waveFile, 1);
+ System.out.println("Writing to WAV file " + waveFile.getAbsolutePath());
+ }
+
+ createUnits();
+
+ connectUnits();
+
+ setupEnvelope();
+
+ osc.amplitude.set(SONG_AMPLITUDE);
+
+ // Ramp the rate of the envelope up until it becomes an audible tone.
+ envSweeper.current.set(LOW_FREQUENCY);
+ envSweeper.input.set(LOW_FREQUENCY);
+ envSweeper.time.set(RAMP_DURATION);
+
+ // Ramp the rate of the oscillator down until it becomes an LFO.
+ oscSweeper.current.set(HIGH_FREQUENCY);
+ oscSweeper.input.set(HIGH_FREQUENCY);
+ oscSweeper.time.set(RAMP_DURATION);
+
+ // Start synthesizer using default stereo output at 44100 Hz.
+ synth.start();
+
+ // When we start the recorder it will pull data from the oscillator and
+ // sweeper.
+ if (recorder != null) {
+ recorder.start();
+ }
+
+ // We also need to start the LineOut if we want to hear it now.
+ lineOut.start();
+
+ // Get synthesizer time in seconds.
+ double timeNow = synth.getCurrentTime();
+
+ // Schedule start of ramps.
+ double songDuration = INTRO_DURATION + RAMP_DURATION + OUTRO_DURATION;
+ envSweeper.input.set(HIGH_FREQUENCY, timeNow + INTRO_DURATION);
+ oscSweeper.input.set(LOW_FREQUENCY, timeNow + INTRO_DURATION);
+
+ // Arm zero crossing latch
+ latch.gate.set(0.0, timeNow + songDuration);
+
+ // Sleep while the sound is being generated in the background thread.
+ try {
+ synth.sleepUntil(timeNow + songDuration + 2.0);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+
+ if (recorder != null) {
+ recorder.stop();
+ recorder.close();
+ }
+ // Stop everything.
+ synth.stop();
+ }
+
+ private void createUnits() {
+ // Add a tone generators.
+ synth.add(osc = new SineOscillator());
+ // Add a controller that will sweep the envelope rate up.
+ synth.add(envSweeper = new ExponentialRamp());
+ // Add a controller that will sweep the oscillator down.
+ synth.add(oscSweeper = new ExponentialRamp());
+
+ synth.add(latch = new LatchZeroCrossing());
+ // Add an output unit.
+ synth.add(lineOut = new LineOut());
+
+ // Add an envelope player.
+ synth.add(envelopePlayer = new VariableRateMonoReader());
+ }
+
+ private void connectUnits() {
+ oscSweeper.output.connect(osc.frequency);
+ osc.output.connect(latch.input);
+ // Latch when sine LFO crosses zero.
+ latch.output.connect(envelopePlayer.amplitude);
+
+ envSweeper.output.connect(envelopePlayer.rate);
+
+ // Connect the envelope player to the audio output.
+ envelopePlayer.output.connect(0, lineOut.input, 0);
+ // crossFade.output.connect( 0, lineOut.input, 1 );
+
+ if (recorder != null) {
+ envelopePlayer.output.connect(0, recorder.getInput(), 0);
+ // crossFade.output.connect( 0, recorder.getInput(), 1 );
+ }
+ }
+
+ private void setupEnvelope() {
+ // Setup envelope. The envelope has a total duration of 1.0 seconds.
+ // Values are (duration,target) pairs.
+ double[] pairs = new double[5 * 2 * 2];
+ int i = 0;
+ // duration, target for delay
+ pairs[i++] = 0.15;
+ pairs[i++] = 0.0;
+ // duration, target for attack
+ pairs[i++] = 0.05;
+ pairs[i++] = 1.0;
+ // duration, target for release
+ pairs[i++] = 0.1;
+ pairs[i++] = 0.6;
+ // duration, target for sustain
+ pairs[i++] = 0.1;
+ pairs[i++] = 0.6;
+ // duration, target for release
+ pairs[i++] = 0.1;
+ pairs[i++] = 0.0;
+ // Create mirror image of this envelope.
+ int halfLength = i;
+ while (i < pairs.length) {
+ pairs[i] = pairs[i - halfLength];
+ i++;
+ pairs[i] = pairs[i - halfLength] * -1.0;
+ i++;
+ }
+ envelope = new SegmentedEnvelope(pairs);
+
+ envelopePlayer.dataQueue.queueLoop(envelope, 0, envelope.getNumFrames());
+ }
+
+ public static void main(String[] args) {
+ try {
+ new NotesToTone().play();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+}
diff --git a/tests/com/jsyn/examples/PlayChords.java b/tests/com/jsyn/examples/PlayChords.java
new file mode 100644
index 0000000..0b1ae2e
--- /dev/null
+++ b/tests/com/jsyn/examples/PlayChords.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright 2009 Phil Burk, Mobileer Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.jsyn.examples;
+
+import com.jsyn.JSyn;
+import com.jsyn.Synthesizer;
+import com.jsyn.instruments.SubtractiveSynthVoice;
+import com.jsyn.unitgen.LineOut;
+import com.jsyn.unitgen.UnitVoice;
+import com.jsyn.util.VoiceAllocator;
+import com.softsynth.shared.time.TimeStamp;
+
+/**
+ * Play chords and melody using the VoiceAllocator.
+ *
+ * @author Phil Burk (C) 2009 Mobileer Inc
+ */
+public class PlayChords {
+ private static final int MAX_VOICES = 8;
+ private Synthesizer synth;
+ private VoiceAllocator allocator;
+ private LineOut lineOut;
+ /** Number of seconds to generate music in advance of presentation-time. */
+ private double advance = 0.2;
+ private double secondsPerBeat = 0.6;
+ // on time over note duration
+ private double dutyCycle = 0.8;
+ private double measure = secondsPerBeat * 4.0;
+ private UnitVoice[] voices;
+
+ private void test() {
+ synth = JSyn.createSynthesizer();
+
+ // Add an output.
+ synth.add(lineOut = new LineOut());
+
+ voices = new UnitVoice[MAX_VOICES];
+ for (int i = 0; i < MAX_VOICES; i++) {
+ SubtractiveSynthVoice voice = new SubtractiveSynthVoice();
+ synth.add(voice);
+ voice.getOutput().connect(0, lineOut.input, 0);
+ voice.getOutput().connect(0, lineOut.input, 1);
+ voices[i] = voice;
+ }
+ allocator = new VoiceAllocator(voices);
+
+ // Start synthesizer using default stereo output at 44100 Hz.
+ synth.start();
+ // We only need to start the LineOut. It will pull data from the
+ // voices.
+ lineOut.start();
+
+ // Get synthesizer time in seconds.
+ double timeNow = synth.getCurrentTime();
+
+ // Advance to a near future time so we have a clean start.
+ double time = timeNow + 1.0;
+
+ try {
+ int tonic = 60 - 12;
+ for (int i = 0; i < 4; i++) {
+ playMajorMeasure1(time, tonic);
+ time += measure;
+ catchUp(time);
+ playMajorMeasure1(time, tonic + 4);
+ time += measure;
+ catchUp(time);
+ playMajorMeasure1(time, tonic + 7);
+ time += measure;
+ catchUp(time);
+ playMinorMeasure1(time, tonic + 2); // minor chord
+ time += measure;
+ catchUp(time);
+ }
+ time += secondsPerBeat;
+ catchUp(time);
+
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+
+ // Stop everything.
+ synth.stop();
+ }
+
+ private void playMinorMeasure1(double time, int base) throws InterruptedException {
+ int p1 = base;
+ int p2 = base + 3;
+ int p3 = base + 7;
+ playChord1(time, p1, p2, p3);
+ playNoodle1(time, p1 + 24, p2 + 24, p3 + 24);
+ }
+
+ private void playMajorMeasure1(double time, int base) throws InterruptedException {
+ int p1 = base;
+ int p2 = base + 4;
+ int p3 = base + 7;
+ playChord1(time, p1, p2, p3);
+ playNoodle1(time, p1 + 24, p2 + 24, p3 + 24);
+ }
+
+ private void playNoodle1(double time, int p1, int p2, int p3) {
+ double secondsPerNote = secondsPerBeat * 0.5;
+ for (int i = 0; i < 8; i++) {
+ int p = pickFromThree(p1, p2, p3);
+ noteOn(time, p);
+ noteOff(time + dutyCycle * secondsPerNote, p);
+ time += secondsPerNote;
+ }
+ }
+
+ private int pickFromThree(int p1, int p2, int p3) {
+ int r = (int) (Math.random() * 3.0);
+ if (r < 1)
+ return p1;
+ else if (r < 2)
+ return p2;
+ else
+ return p3;
+ }
+
+ private void playChord1(double time, int p1, int p2, int p3) throws InterruptedException {
+ double dur = dutyCycle * secondsPerBeat;
+ playTriad(time, dur, p1, p2, p3);
+ time += secondsPerBeat;
+ playTriad(time, dur, p1, p2, p3);
+ time += secondsPerBeat;
+ playTriad(time, dur * 0.25, p1, p2, p3);
+ time += secondsPerBeat * 0.25;
+ playTriad(time, dur * 0.25, p1, p2, p3);
+ time += secondsPerBeat * 0.75;
+ playTriad(time, dur, p1, p2, p3);
+ time += secondsPerBeat;
+ }
+
+ private void playTriad(double time, double dur, int p1, int p2, int p3)
+ throws InterruptedException {
+ noteOn(time, p1);
+ noteOn(time, p2);
+ noteOn(time, p3);
+ double offTime = time + dur;
+ noteOff(offTime, p1);
+ noteOff(offTime, p2);
+ noteOff(offTime, p3);
+ }
+
+ private void catchUp(double time) throws InterruptedException {
+ synth.sleepUntil(time - advance);
+ }
+
+ private void noteOff(double time, int noteNumber) {
+ allocator.noteOff(noteNumber, new TimeStamp(time));
+ }
+
+ private void noteOn(double time, int noteNumber) {
+ double frequency = convertPitchToFrequency(noteNumber);
+ double amplitude = 0.2;
+ TimeStamp timeStamp = new TimeStamp(time);
+ allocator.noteOn(noteNumber, frequency, amplitude, timeStamp);
+ }
+
+ /**
+ * Calculate frequency in Hertz based on MIDI pitch. Middle C is 60.0. You can use fractional
+ * pitches so 60.5 would give you a pitch half way between C and C#.
+ */
+ double convertPitchToFrequency(double pitch) {
+ final double concertA = 440.0;
+ return concertA * Math.pow(2.0, ((pitch - 69) * (1.0 / 12.0)));
+ }
+
+ public static void main(String[] args) {
+ new PlayChords().test();
+ }
+}
diff --git a/tests/com/jsyn/examples/PlayCustomUnit.java b/tests/com/jsyn/examples/PlayCustomUnit.java
new file mode 100644
index 0000000..609c351
--- /dev/null
+++ b/tests/com/jsyn/examples/PlayCustomUnit.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2010 Phil Burk, Mobileer Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.jsyn.examples;
+
+import com.jsyn.JSyn;
+import com.jsyn.Synthesizer;
+import com.jsyn.unitgen.LineOut;
+import com.jsyn.unitgen.SineOscillator;
+import com.jsyn.unitgen.UnitOscillator;
+
+/**
+ * Play a tone using a JSyn oscillator and process it using a custom unit generator.
+ *
+ * @author Phil Burk (C) 2010 Mobileer Inc
+ */
+public class PlayCustomUnit {
+ private Synthesizer synth;
+ private UnitOscillator osc;
+ private CustomCubeUnit cuber;
+ private LineOut lineOut;
+
+ private void test() {
+ synth = JSyn.createSynthesizer();
+ // Add a tone generator.
+ synth.add(osc = new SineOscillator());
+ // Add a tone generator.
+ synth.add(cuber = new CustomCubeUnit());
+ // Add an output to the DAC.
+ synth.add(lineOut = new LineOut());
+ // Connect the oscillator to the cuber.
+ osc.output.connect(0, cuber.input, 0);
+ // Connect the cuber to the right output.
+ cuber.output.connect(0, lineOut.input, 1);
+ // Send the original to the left output for comparison.
+ osc.output.connect(0, lineOut.input, 0);
+
+ osc.frequency.set(240.0);
+
+ // Start synthesizer using default stereo output at 44100 Hz.
+ synth.start();
+ // We only need to start the LineOut.
+ // It will pull data from the cuber and the oscillator.
+ lineOut.start();
+ // Sleep while the sound is generated in the background.
+ try {
+ double time = synth.getCurrentTime();
+ // Sleep for a few seconds.
+ synth.sleepUntil(time + 10.0);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ // Stop everything.
+ synth.stop();
+ }
+
+ public static void main(String[] args) {
+ new PlayCustomUnit().test();
+ }
+}
diff --git a/tests/com/jsyn/examples/PlayFunction.java b/tests/com/jsyn/examples/PlayFunction.java
new file mode 100644
index 0000000..700152b
--- /dev/null
+++ b/tests/com/jsyn/examples/PlayFunction.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2010 Phil Burk, Mobileer Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.jsyn.examples;
+
+import com.jsyn.JSyn;
+import com.jsyn.Synthesizer;
+import com.jsyn.data.Function;
+import com.jsyn.unitgen.FunctionOscillator;
+import com.jsyn.unitgen.LineOut;
+
+/**
+ * Play a tone using a FunctionOscillator.
+ *
+ * @author Phil Burk (C) 2010 Mobileer Inc
+ */
+public class PlayFunction {
+ Synthesizer synth;
+ FunctionOscillator osc;
+ LineOut lineOut;
+
+ private void test() {
+ // Create a context for the synthesizer.
+ synth = JSyn.createSynthesizer();
+
+ // Start synthesizer using default stereo output at 44100 Hz.
+ synth.start();
+
+ // Add a FunctionOscillator
+ synth.add(osc = new FunctionOscillator());
+
+ // Define a function that gives the shape of the waveform.
+ Function func = new Function() {
+ @Override
+ public double evaluate(double input) {
+ // Input ranges from -1.0 to 1.0
+ double s = Math.sin(input * Math.PI * 2.0);
+ double cubed = s * s * s;
+ return cubed;
+ }
+ };
+ osc.function.set(func);
+
+ // Add a stereo audio output unit.
+ synth.add(lineOut = new LineOut());
+
+ // Connect the oscillator to both channels of the output.
+ osc.output.connect(0, lineOut.input, 0);
+ osc.output.connect(0, lineOut.input, 1);
+
+ // Set the frequency and amplitude for the sine wave.
+ osc.frequency.set(345.0);
+ osc.amplitude.set(0.6);
+
+ // We only need to start the LineOut. It will pull data from the
+ // oscillator.
+ lineOut.start();
+
+ System.out.println("You should now be hearing a sine wave. ---------");
+
+ // Sleep while the sound is generated in the background.
+ try {
+ double time = synth.getCurrentTime();
+ // Sleep for a few seconds.
+ synth.sleepUntil(time + 4.0);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+
+ System.out.println("Stop playing. -------------------");
+ // Stop everything.
+ synth.stop();
+ }
+
+ public static void main(String[] args) {
+ new PlayFunction().test();
+ }
+}
diff --git a/tests/com/jsyn/examples/PlayGrains.java b/tests/com/jsyn/examples/PlayGrains.java
new file mode 100644
index 0000000..6b2b11e
--- /dev/null
+++ b/tests/com/jsyn/examples/PlayGrains.java
@@ -0,0 +1,212 @@
+/*
+ * Copyright 2011 Phil Burk, Mobileer Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.jsyn.examples;
+
+import java.awt.BorderLayout;
+import java.awt.GridLayout;
+import java.io.File;
+import java.io.IOException;
+
+import javax.swing.BorderFactory;
+import javax.swing.JApplet;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+import com.jsyn.JSyn;
+import com.jsyn.Synthesizer;
+import com.jsyn.data.FloatSample;
+import com.jsyn.ports.UnitInputPort;
+import com.jsyn.scope.AudioScope;
+import com.jsyn.swing.DoubleBoundedRangeModel;
+import com.jsyn.swing.JAppletFrame;
+import com.jsyn.swing.PortModelFactory;
+import com.jsyn.swing.RotaryTextController;
+import com.jsyn.unitgen.ContinuousRamp;
+import com.jsyn.unitgen.GrainFarm;
+import com.jsyn.unitgen.LineOut;
+import com.jsyn.unitgen.SampleGrainFarm;
+import com.jsyn.util.SampleLoader;
+import com.jsyn.util.WaveRecorder;
+
+/**
+ * Play with Granular Synthesis tools.
+ *
+ * @author Phil Burk (C) 2011 Mobileer Inc
+ */
+public class PlayGrains extends JApplet {
+ private static final long serialVersionUID = -8315903842197137926L;
+ private Synthesizer synth;
+ private LineOut lineOut;
+ private AudioScope scope;
+ private GrainFarm grainFarm;
+ private ContinuousRamp ramp;
+ private static final int NUM_GRAINS = 32;
+ private FloatSample sample;
+ private WaveRecorder recorder;
+
+ private static final boolean useSample = true;
+ private final static boolean useRecorder = false;
+
+ // File sampleFile = new File( "samples/instructions.wav" );
+ File sampleFile = new File(
+ // "/Users/phil/Work/jsyn/guitar100/Guitar100_Ocean_1#02.aif" );
+ "/Users/phil/Music/samples/ChewyMonkeysWhistle.aiff");
+
+ /* Can be run as either an application or as an applet. */
+ public static void main(String args[]) {
+ PlayGrains applet = new PlayGrains();
+ JAppletFrame frame = new JAppletFrame("PlayGrains", applet);
+ frame.setSize(840, 500);
+ frame.setVisible(true);
+ frame.test();
+ }
+
+ private void setupGUI() {
+ setLayout(new BorderLayout());
+
+ add(BorderLayout.NORTH,
+ new JLabel("PlayGrains in an AudioScope - JSyn V" + synth.getVersion()));
+
+ scope = new AudioScope(synth);
+
+ // scope.addProbe( osc.output );
+ scope.addProbe(grainFarm.output);
+
+ scope.setTriggerMode(AudioScope.TriggerMode.NORMAL);
+ scope.getView().setControlsVisible(true);
+ add(BorderLayout.CENTER, scope.getView());
+ scope.start();
+
+ // Arrange the knob in a row.
+ JPanel knobPanel = new JPanel();
+ knobPanel.setLayout(new GridLayout(1, 0));
+
+ if (useSample) {
+ SampleGrainFarm sampleGrainFarm = (SampleGrainFarm) grainFarm;
+ knobPanel.add(setupLinearPortKnob(ramp.time, 0.001, 10.0, "Time"));
+ knobPanel.add(setupLinearPortKnob(ramp.input, -1.0, 1.0, "Position"));
+ knobPanel.add(setupLinearPortKnob(sampleGrainFarm.positionRange, 0.0, 0.5, "PosRange"));
+ }
+ knobPanel.add(setupPortKnob(grainFarm.density, 1.0, "Density"));
+ knobPanel.add(setupPortKnob(grainFarm.rate, 4.0, "Rate"));
+ knobPanel.add(setupPortKnob(grainFarm.rateRange, 3.0, "RateRange"));
+ knobPanel.add(setupPortKnob(grainFarm.duration, 0.1, "Duration"));
+ knobPanel.add(setupPortKnob(grainFarm.durationRange, 3.0, "DurRange"));
+ knobPanel.add(setupPortKnob(grainFarm.amplitude, 6.0, "Amplitude"));
+ knobPanel.add(setupPortKnob(grainFarm.amplitudeRange, 1.0, "AmpRange"));
+ add(knobPanel, BorderLayout.SOUTH);
+
+ validate();
+ }
+
+ private RotaryTextController setupLinearPortKnob(UnitInputPort port, double min, double max,
+ String label) {
+ port.setMinimum(min);
+ port.setMaximum(max);
+
+ DoubleBoundedRangeModel model = PortModelFactory.createLinearModel(port);
+ RotaryTextController knob = new RotaryTextController(model, 10);
+ knob.setBorder(BorderFactory.createTitledBorder(label));
+ knob.setTitle(label);
+ return knob;
+ }
+
+ private RotaryTextController setupPortKnob(UnitInputPort port, double max, String label) {
+ port.setMinimum(0.0);
+ port.setMaximum(max);
+
+ DoubleBoundedRangeModel model = PortModelFactory.createExponentialModel(port);
+ RotaryTextController knob = new RotaryTextController(model, 10);
+ knob.setBorder(BorderFactory.createTitledBorder(label));
+ knob.setTitle(label);
+ return knob;
+ }
+
+ @Override
+ public void start() {
+ synth = JSyn.createSynthesizer();
+
+ try {
+
+ if (useRecorder) {
+ File waveFile = new File("temp_recording.wav");
+ // Record mono 16 bits.
+ recorder = new WaveRecorder(synth, waveFile, 1);
+ System.out.println("Writing to WAV file " + waveFile.getAbsolutePath());
+ }
+
+ if (useSample) {
+ sample = SampleLoader.loadFloatSample(sampleFile);
+ SampleGrainFarm sampleGrainFarm = new SampleGrainFarm();
+ synth.add(ramp = new ContinuousRamp());
+ sampleGrainFarm.setSample(sample);
+ ramp.output.connect(sampleGrainFarm.position);
+ grainFarm = sampleGrainFarm;
+ } else {
+ GrainFarm sampleGrainFarm = new GrainFarm();
+ grainFarm = sampleGrainFarm;
+ }
+
+ synth.add(grainFarm);
+
+ grainFarm.allocate(NUM_GRAINS);
+
+ // Add an output so we can hear the grains.
+ synth.add(lineOut = new LineOut());
+
+ grainFarm.getOutput().connect(0, lineOut.input, 0);
+ grainFarm.getOutput().connect(0, lineOut.input, 1);
+
+ // Start synthesizer using default stereo output at 44100 Hz.
+ synth.start();
+ // Start lineOut so it can pull data from other units.
+ lineOut.start();
+
+ if (useRecorder) {
+ grainFarm.output.connect(0, recorder.getInput(), 0);
+ // When we start the recorder it will pull data from the
+ // oscillator
+ // and sweeper.
+ recorder.start();
+ }
+
+ setupGUI();
+ // We only need to start the LineOut. It will pull data from the
+ // oscillator.
+ lineOut.start();
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+
+ }
+
+ @Override
+ public void stop() {
+ try {
+ if (recorder != null) {
+ recorder.stop();
+ recorder.close();
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ scope.stop();
+ synth.stop();
+ }
+
+}
diff --git a/tests/com/jsyn/examples/PlayNotes.java b/tests/com/jsyn/examples/PlayNotes.java
new file mode 100644
index 0000000..65dc930
--- /dev/null
+++ b/tests/com/jsyn/examples/PlayNotes.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2009 Phil Burk, Mobileer Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.jsyn.examples;
+
+import com.jsyn.JSyn;
+import com.jsyn.Synthesizer;
+import com.jsyn.unitgen.LineOut;
+import com.jsyn.unitgen.SawtoothOscillator;
+import com.jsyn.unitgen.UnitGenerator;
+import com.jsyn.unitgen.UnitVoice;
+import com.softsynth.shared.time.TimeStamp;
+
+/**
+ * Play notes using timestamped noteOn and noteOff methods of the UnitVoice.
+ *
+ * @author Phil Burk (C) 2009 Mobileer Inc
+ */
+public class PlayNotes {
+ Synthesizer synth;
+ UnitGenerator ugen;
+ UnitVoice voice;
+ LineOut lineOut;
+
+ private void test() {
+ // Create a context for the synthesizer.
+ synth = JSyn.createSynthesizer();
+ // Set output latency to 123 msec because this is not an interactive app.
+ synth.getAudioDeviceManager().setSuggestedOutputLatency(0.123);
+
+ // Add a tone generator.
+ synth.add(ugen = new SawtoothOscillator());
+ // synth.add( ugen = new SineOscillator() );
+ // synth.add( ugen = new SubtractiveSynthVoice() );
+ voice = (UnitVoice) ugen;
+ // Add an output mixer.
+ synth.add(lineOut = new LineOut());
+
+ // Connect the oscillator to the left and right audio output.
+ voice.getOutput().connect(0, lineOut.input, 0);
+ voice.getOutput().connect(0, lineOut.input, 1);
+
+ // Start synthesizer using default stereo output at 44100 Hz.
+ synth.start();
+
+ // Get synthesizer time in seconds.
+ double timeNow = synth.getCurrentTime();
+
+ // Advance to a near future time so we have a clean start.
+ TimeStamp timeStamp = new TimeStamp(timeNow + 0.5);
+
+ // We only need to start the LineOut. It will pull data from the
+ // oscillator.
+ synth.startUnit(lineOut, timeStamp);
+
+ // Schedule a note on and off.
+ double freq = 200.0; // hertz
+ double duration = 1.4;
+ double onTime = 1.0;
+ voice.noteOn(freq, 0.5, timeStamp);
+ voice.noteOff(timeStamp.makeRelative(onTime));
+
+ // Schedule this to happen a bit later.
+ timeStamp = timeStamp.makeRelative(duration);
+ freq *= 1.5; // up a perfect fifth
+ voice.noteOn(freq, 0.5, timeStamp);
+ voice.noteOff(timeStamp.makeRelative(onTime));
+
+ timeStamp = timeStamp.makeRelative(duration);
+ freq *= 4.0 / 5.0; // down a major third
+ voice.noteOn(freq, 0.5, timeStamp);
+ voice.noteOff(timeStamp.makeRelative(onTime));
+
+ // Sleep while the song is being generated in the background thread.
+ try {
+ System.out.println("Sleep while synthesizing.");
+ synth.sleepUntil(timeStamp.getTime() + 2.0);
+ System.out.println("Woke up...");
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+
+ // Stop everything.
+ synth.stop();
+ }
+
+ public static void main(String[] args) {
+ new PlayNotes().test();
+ }
+}
diff --git a/tests/com/jsyn/examples/PlayPartials.java b/tests/com/jsyn/examples/PlayPartials.java
new file mode 100644
index 0000000..1d7d88e
--- /dev/null
+++ b/tests/com/jsyn/examples/PlayPartials.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2010 Phil Burk, Mobileer Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.jsyn.examples;
+
+import com.jsyn.JSyn;
+import com.jsyn.Synthesizer;
+import com.jsyn.unitgen.LineOut;
+import com.jsyn.unitgen.LinearRamp;
+import com.jsyn.unitgen.Multiply;
+import com.jsyn.unitgen.SineOscillator;
+import com.jsyn.unitgen.UnitOscillator;
+
+/**
+ * Play a enharmonic sine tones using JSyn oscillators.
+ *
+ * @author Phil Burk (C) 2010 Mobileer Inc
+ */
+public class PlayPartials {
+ private Synthesizer synth;
+ private UnitOscillator[] osc;
+ private Multiply[] multipliers;
+ private LinearRamp ramp;
+ private LineOut lineOut;
+ private double[] amps = {
+ 0.2, 0.1, 0.3, 0.4
+ };
+ private double[] ratios = {
+ 1.0, Math.sqrt(2.0), Math.E, Math.PI
+ };
+
+ private void test() {
+ // Create a context for the synthesizer.
+ synth = JSyn.createSynthesizer();
+
+ // Start synthesizer using default stereo output at 44100 Hz.
+ synth.start();
+
+ // Add a stereo audio output unit.
+ synth.add(lineOut = new LineOut());
+ synth.add(ramp = new LinearRamp());
+
+ // Add a tone generator.
+ osc = new SineOscillator[amps.length];
+ multipliers = new Multiply[ratios.length];
+
+ for (int i = 0; i < osc.length; i++) {
+ // Create unit generators and store them in arrays.
+ synth.add(osc[i] = new SineOscillator());
+ synth.add(multipliers[i] = new Multiply());
+
+ // Connect each oscillator to both channels of the output.
+ // They will be mixed automatically.
+ osc[i].output.connect(0, lineOut.input, 0);
+ osc[i].output.connect(0, lineOut.input, 1);
+
+ // Use a multiplier to scale the output of the ramp.
+ // output = inputA * inputB
+ ramp.output.connect(multipliers[i].inputA);
+ multipliers[i].output.connect(osc[i].frequency);
+ multipliers[i].inputB.set(ratios[i]);
+
+ osc[i].amplitude.set(amps[i]);
+ }
+
+ // start ramping up
+ ramp.current.set(100.0);
+ ramp.time.set(3.0);
+ ramp.input.set(700.0);
+
+ // We only need to start the LineOut. It will pull data from the
+ // oscillator.
+ lineOut.start();
+
+ System.out.println("You should now be hearing a sine wave. ---------");
+
+ // Sleep while the sound is generated in the background.
+ try {
+ // Sleep for a few seconds.
+ synth.sleepFor(4.0);
+ // ramp down
+ ramp.input.set(100.0);
+ synth.sleepFor(4.0);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+
+ System.out.println("Stop playing. -------------------");
+ // Stop everything.
+ synth.stop();
+ }
+
+ public static void main(String[] args) {
+ System.out.println("Java version = " + System.getProperty("java.version"));
+ new PlayPartials().test();
+ }
+}
diff --git a/tests/com/jsyn/examples/PlaySample.java b/tests/com/jsyn/examples/PlaySample.java
new file mode 100644
index 0000000..ac3d5ff
--- /dev/null
+++ b/tests/com/jsyn/examples/PlaySample.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright 2010 Phil Burk, Mobileer Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.jsyn.examples;
+
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import com.jsyn.JSyn;
+import com.jsyn.Synthesizer;
+import com.jsyn.data.FloatSample;
+import com.jsyn.unitgen.LineOut;
+import com.jsyn.unitgen.VariableRateDataReader;
+import com.jsyn.unitgen.VariableRateMonoReader;
+import com.jsyn.unitgen.VariableRateStereoReader;
+import com.jsyn.util.SampleLoader;
+
+/**
+ * Play a sample from a WAV file using JSyn.
+ *
+ * @author Phil Burk (C) 2010 Mobileer Inc
+ */
+public class PlaySample {
+ private Synthesizer synth;
+ private VariableRateDataReader samplePlayer;
+ private LineOut lineOut;
+
+ private void test() {
+
+ URL sampleFile;
+ try {
+ sampleFile = new URL("http://www.softsynth.com/samples/Clarinet.wav");
+ // sampleFile = new URL("http://www.softsynth.com/samples/NotHereNow22K.wav");
+ } catch (MalformedURLException e2) {
+ e2.printStackTrace();
+ return;
+ }
+
+ synth = JSyn.createSynthesizer();
+
+ FloatSample sample;
+ try {
+ // Add an output mixer.
+ synth.add(lineOut = new LineOut());
+
+ // Load the sample and display its properties.
+ SampleLoader.setJavaSoundPreferred(false);
+ sample = SampleLoader.loadFloatSample(sampleFile);
+ System.out.println("Sample has: channels = " + sample.getChannelsPerFrame());
+ System.out.println(" frames = " + sample.getNumFrames());
+ System.out.println(" rate = " + sample.getFrameRate());
+ System.out.println(" loopStart = " + sample.getSustainBegin());
+ System.out.println(" loopEnd = " + sample.getSustainEnd());
+
+ if (sample.getChannelsPerFrame() == 1) {
+ synth.add(samplePlayer = new VariableRateMonoReader());
+ samplePlayer.output.connect(0, lineOut.input, 0);
+ } else if (sample.getChannelsPerFrame() == 2) {
+ synth.add(samplePlayer = new VariableRateStereoReader());
+ samplePlayer.output.connect(0, lineOut.input, 0);
+ samplePlayer.output.connect(1, lineOut.input, 1);
+ } else {
+ throw new RuntimeException("Can only play mono or stereo samples.");
+ }
+
+ // Start synthesizer using default stereo output at 44100 Hz.
+ synth.start();
+
+ samplePlayer.rate.set(sample.getFrameRate());
+
+ // We only need to start the LineOut. It will pull data from the
+ // sample player.
+ lineOut.start();
+
+ // We can simply queue the entire file.
+ // Or if it has a loop we can play the loop for a while.
+ if (sample.getSustainBegin() < 0) {
+ System.out.println("queue the sample");
+ samplePlayer.dataQueue.queue(sample);
+ } else {
+ System.out.println("queueOn the sample");
+ samplePlayer.dataQueue.queueOn(sample);
+ synth.sleepFor(8.0);
+ System.out.println("queueOff the sample");
+ samplePlayer.dataQueue.queueOff(sample);
+ }
+
+ // Wait until the sample has finished playing.
+ do {
+ synth.sleepFor(1.0);
+ } while (samplePlayer.dataQueue.hasMore());
+
+ synth.sleepFor(0.5);
+
+ } catch (IOException e1) {
+ e1.printStackTrace();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ // Stop everything.
+ synth.stop();
+ }
+
+ public static void main(String[] args) {
+ new PlaySample().test();
+ }
+}
diff --git a/tests/com/jsyn/examples/PlaySampleCrossfade.java b/tests/com/jsyn/examples/PlaySampleCrossfade.java
new file mode 100644
index 0000000..b5ea5ca
--- /dev/null
+++ b/tests/com/jsyn/examples/PlaySampleCrossfade.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright 2010 Phil Burk, Mobileer Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.jsyn.examples;
+
+import java.awt.GridLayout;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import javax.swing.JApplet;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+
+import com.jsyn.JSyn;
+import com.jsyn.Synthesizer;
+import com.jsyn.data.FloatSample;
+import com.jsyn.devices.AudioDeviceFactory;
+import com.jsyn.ports.QueueDataCommand;
+import com.jsyn.swing.DoubleBoundedRangeModel;
+import com.jsyn.swing.DoubleBoundedRangeSlider;
+import com.jsyn.swing.JAppletFrame;
+import com.jsyn.swing.PortControllerFactory;
+import com.jsyn.unitgen.LineOut;
+import com.jsyn.unitgen.VariableRateDataReader;
+import com.jsyn.unitgen.VariableRateMonoReader;
+import com.jsyn.unitgen.VariableRateStereoReader;
+import com.jsyn.util.SampleLoader;
+
+/**
+ * Play a sample from a WAV file using JSyn. Use a crossfade to play a loop at an arbitrary
+ * position.
+ *
+ * @author Phil Burk (C) 2010 Mobileer Inc
+ */
+public class PlaySampleCrossfade extends JApplet {
+ private static final double LOOP_START_FRACTION = 0.2;
+ private Synthesizer synth;
+ private VariableRateDataReader samplePlayer;
+ private LineOut lineOut;
+ private FloatSample sample;
+ private DoubleBoundedRangeModel rangeModelSize;
+ private DoubleBoundedRangeModel rangeModelCrossfade;
+ private int loopStartFrame;
+
+ @Override
+ public void init() {
+
+ URL sampleFile;
+ try {
+ sampleFile = new URL("http://www.softsynth.com/samples/Clarinet.wav");
+ } catch (MalformedURLException e2) {
+ e2.printStackTrace();
+ return;
+ }
+
+ synth = JSyn.createSynthesizer(AudioDeviceFactory.createAudioDeviceManager(true));
+
+ try {
+ // Add an output mixer.
+ synth.add(lineOut = new LineOut());
+
+ // Load the sample and display its properties.
+ SampleLoader.setJavaSoundPreferred(false);
+ sample = SampleLoader.loadFloatSample(sampleFile);
+ System.out.println("Sample has: channels = " + sample.getChannelsPerFrame());
+ System.out.println(" frames = " + sample.getNumFrames());
+ System.out.println(" rate = " + sample.getFrameRate());
+ System.out.println(" loopStart = " + sample.getSustainBegin());
+ System.out.println(" loopEnd = " + sample.getSustainEnd());
+
+ if (sample.getChannelsPerFrame() == 1) {
+ synth.add(samplePlayer = new VariableRateMonoReader());
+ samplePlayer.output.connect(0, lineOut.input, 0);
+ } else if (sample.getChannelsPerFrame() == 2) {
+ synth.add(samplePlayer = new VariableRateStereoReader());
+ samplePlayer.output.connect(0, lineOut.input, 0);
+ samplePlayer.output.connect(1, lineOut.input, 1);
+ } else {
+ throw new RuntimeException("Can only play mono or stereo samples.");
+ }
+
+ samplePlayer.rate.set(sample.getFrameRate());
+
+ } catch (IOException e1) {
+ e1.printStackTrace();
+ }
+
+ // Start at arbitrary position near beginning of sample.
+ loopStartFrame = (int) (sample.getNumFrames() * LOOP_START_FRACTION);
+
+ // Arrange the faders in a stack.
+ setLayout(new GridLayout(0, 1));
+
+ samplePlayer.rate.setup(4000.0, sample.getFrameRate(), sample.getFrameRate() * 2.0);
+ add(PortControllerFactory.createExponentialPortSlider(samplePlayer.rate));
+
+ // Use fader to select arbitrary loop size.
+ rangeModelSize = new DoubleBoundedRangeModel("LoopSize", 10000, 0.01,
+ (1.0 - LOOP_START_FRACTION), 0.5);
+ rangeModelSize.addChangeListener(new ChangeListener() {
+ @Override
+ public void stateChanged(ChangeEvent e) {
+ queueNewLoop();
+ }
+
+ });
+ add(new DoubleBoundedRangeSlider(rangeModelSize, 3));
+
+ // Use fader to set the size of the crossfade region.
+ rangeModelCrossfade = new DoubleBoundedRangeModel("Crossfade", 1000, 0.0, 1000.0, 0.0);
+ rangeModelCrossfade.addChangeListener(new ChangeListener() {
+ @Override
+ public void stateChanged(ChangeEvent e) {
+ queueNewLoop();
+ }
+
+ });
+
+ add(new DoubleBoundedRangeSlider(rangeModelCrossfade, 3));
+
+ validate();
+ }
+
+ private void queueNewLoop() {
+ int loopSize = (int) (sample.getNumFrames() * rangeModelSize.getDoubleValue());
+ if ((loopStartFrame + loopSize) > sample.getNumFrames()) {
+ loopSize = sample.getNumFrames() - loopStartFrame;
+ }
+ int crossFadeSize = (int) (rangeModelCrossfade.getDoubleValue());
+
+ // For complex queuing operations, create a command and then customize it.
+ QueueDataCommand command = samplePlayer.dataQueue.createQueueDataCommand(sample,
+ loopStartFrame, loopSize);
+ command.setNumLoops(-1);
+ command.setSkipIfOthers(true);
+ command.setCrossFadeIn(crossFadeSize);
+
+ System.out.println("Queue: " + loopStartFrame + ", #" + loopSize + ", X=" + crossFadeSize);
+ synth.queueCommand(command);
+ }
+
+ @Override
+ public void start() {
+ // Start synthesizer using default stereo output at 44100 Hz.
+ synth.start();
+ // Start the LineOut. It will pull data from the oscillator.
+ lineOut.start();
+
+ // Queue attack portion of sample.
+ samplePlayer.dataQueue.queue(sample, 0, loopStartFrame);
+ queueNewLoop();
+ }
+
+ @Override
+ public void stop() {
+ synth.stop();
+ synth.stop();
+ }
+
+ /* Can be run as either an application or as an applet. */
+ public static void main(String args[]) {
+ PlaySampleCrossfade applet = new PlaySampleCrossfade();
+ JAppletFrame frame = new JAppletFrame("PlaySampleCrossfade", applet);
+ frame.setSize(440, 300);
+ frame.setVisible(true);
+ frame.test();
+ }
+
+}
diff --git a/tests/com/jsyn/examples/PlaySegmentedEnvelope.java b/tests/com/jsyn/examples/PlaySegmentedEnvelope.java
new file mode 100644
index 0000000..e7cc8f7
--- /dev/null
+++ b/tests/com/jsyn/examples/PlaySegmentedEnvelope.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright 2010 Phil Burk, Mobileer Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.jsyn.examples;
+
+import java.io.File;
+import java.io.IOException;
+
+import com.jsyn.JSyn;
+import com.jsyn.Synthesizer;
+import com.jsyn.data.SegmentedEnvelope;
+import com.jsyn.unitgen.LineOut;
+import com.jsyn.unitgen.SawtoothOscillatorBL;
+import com.jsyn.unitgen.UnitOscillator;
+import com.jsyn.unitgen.VariableRateDataReader;
+import com.jsyn.unitgen.VariableRateMonoReader;
+import com.jsyn.util.WaveRecorder;
+
+/**
+ * Modulate the amplitude of an oscillator using a segmented envelope.
+ *
+ * @author Phil Burk (C) 2010 Mobileer Inc
+ */
+public class PlaySegmentedEnvelope {
+ private Synthesizer synth;
+ private UnitOscillator osc;
+ private LineOut lineOut;
+ private SegmentedEnvelope envelope;
+ private VariableRateDataReader envelopePlayer;
+ private WaveRecorder recorder;
+ private final static boolean useRecorder = true;
+
+ private void test() throws IOException {
+ // Create a context for the synthesizer.
+ synth = JSyn.createSynthesizer();
+ // Add a tone generator.
+ synth.add(osc = new SawtoothOscillatorBL());
+ // Add an envelope player.
+ synth.add(envelopePlayer = new VariableRateMonoReader());
+
+ if (useRecorder) {
+ File waveFile = new File("temp_recording.wav");
+ // Default is stereo, 16 bits.
+ recorder = new WaveRecorder(synth, waveFile);
+ System.out.println("Writing to WAV file " + waveFile.getAbsolutePath());
+ }
+
+ // Create an envelope consisting of (duration,value) pairs.
+ double[] pairs = {
+ 0.1, 1.0, 0.2, 0.3, 0.6, 0.0
+ };
+ envelope = new SegmentedEnvelope(pairs);
+
+ // Add an output mixer.
+ synth.add(lineOut = new LineOut());
+ envelopePlayer.output.connect(osc.amplitude);
+ // Connect the oscillator to the output.
+ osc.output.connect(0, lineOut.input, 0);
+ osc.output.connect(0, lineOut.input, 1);
+
+ // Start synthesizer using default stereo output at 44100 Hz.
+ synth.start();
+
+ if (useRecorder) {
+ osc.output.connect(0, recorder.getInput(), 0);
+ envelopePlayer.output.connect(0, recorder.getInput(), 1);
+ // When we start the recorder it will pull data from the oscillator
+ // and sweeper.
+ recorder.start();
+ }
+
+ // We only need to start the LineOut. It will pull data from the other
+ // units.
+ lineOut.start();
+
+ try {
+ // ---------------------------------------------
+ // Queue the entire envelope to play once.
+ envelopePlayer.dataQueue.queue(envelope);
+ synth.sleepFor(2.0);
+
+ // ---------------------------------------------
+ // Queue the attack, then sustain for a while, then queue the
+ // release.
+ osc.frequency.set(750.0);
+ envelopePlayer.dataQueue.queue(envelope, 0, 2); // attack
+ synth.sleepFor(2.0);
+ envelopePlayer.dataQueue.queue(envelope, 2, 1); // release
+ synth.sleepFor(2.0);
+
+ // ---------------------------------------------
+ // Queue the attack, then sustain for a while, then queue the
+ // release. But this time use the sustain loop points.
+ osc.frequency.set(950.0);
+ // For noteOn, we want to play frames 0 and 1 then stop before 2.
+ envelope.setSustainBegin(2);
+ envelope.setSustainEnd(2);
+ envelopePlayer.dataQueue.queueOn(envelope); // attack
+ synth.sleepFor(2.0);
+ envelopePlayer.dataQueue.queueOff(envelope); // release
+ synth.sleepFor(2.0);
+
+ // ---------------------------------------------
+ // Queue the entire envelope to play 4 times (3 loops back).
+ osc.frequency.set(350.0);
+ envelopePlayer.dataQueue.queueLoop(envelope, 0, envelope.getNumFrames(), 3);
+ synth.sleepFor(5.0);
+
+ // ---------------------------------------------
+ // Queue the entire envelope as a repeating loop.
+ // It will loop until something else is queued.
+ osc.frequency.set(450.0);
+ envelopePlayer.dataQueue.queueLoop(envelope, 0, envelope.getNumFrames());
+ envelopePlayer.rate.set(3.0);
+ synth.sleepFor(5.0);
+ // Queue last frame to stop the looping.
+ envelopePlayer.dataQueue.queue(envelope, envelope.getNumFrames() - 1, 1);
+ synth.sleepFor(1.0);
+
+ if (recorder != null) {
+ recorder.stop();
+ recorder.close();
+ }
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ // Stop everything.
+ synth.stop();
+ }
+
+ public static void main(String[] args) {
+ try {
+ new PlaySegmentedEnvelope().test();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/tests/com/jsyn/examples/PlaySegmentedEnvelopeCallback.java b/tests/com/jsyn/examples/PlaySegmentedEnvelopeCallback.java
new file mode 100644
index 0000000..cf2441e
--- /dev/null
+++ b/tests/com/jsyn/examples/PlaySegmentedEnvelopeCallback.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2010 Phil Burk, Mobileer Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.jsyn.examples;
+
+import com.jsyn.JSyn;
+import com.jsyn.Synthesizer;
+import com.jsyn.data.SegmentedEnvelope;
+import com.jsyn.ports.QueueDataCommand;
+import com.jsyn.ports.QueueDataEvent;
+import com.jsyn.ports.UnitDataQueueCallback;
+import com.jsyn.unitgen.LineOut;
+import com.jsyn.unitgen.SawtoothOscillatorBL;
+import com.jsyn.unitgen.UnitOscillator;
+import com.jsyn.unitgen.VariableRateDataReader;
+import com.jsyn.unitgen.VariableRateMonoReader;
+
+/**
+ * Use a UnitDataQueueCallback to notify us of the envelope's progress. Modulate the amplitude of an
+ * oscillator using a segmented envelope.
+ *
+ * @author Phil Burk (C) 2010 Mobileer Inc
+ */
+public class PlaySegmentedEnvelopeCallback {
+ private Synthesizer synth;
+ private UnitOscillator osc;
+ private LineOut lineOut;
+ private SegmentedEnvelope envelope;
+ private VariableRateDataReader envelopePlayer;
+
+ private void test() {
+ // Create a context for the synthesizer.
+ synth = JSyn.createSynthesizer();
+ // Add a tone generator.
+ synth.add(osc = new SawtoothOscillatorBL());
+ // Add an envelope player.
+ synth.add(envelopePlayer = new VariableRateMonoReader());
+
+ // Create an envelope consisting of (duration,value) pairs.
+ double[] pairs = {
+ 0.1, 1.0, 0.2, 0.5, 0.6, 0.0
+ };
+ envelope = new SegmentedEnvelope(pairs);
+
+ // Add an output mixer.
+ synth.add(lineOut = new LineOut());
+ envelopePlayer.output.connect(osc.amplitude);
+ // Connect the oscillator to the output.
+ osc.output.connect(0, lineOut.input, 0);
+ osc.output.connect(0, lineOut.input, 1);
+
+ // Start synthesizer using default stereo output at 44100 Hz.
+ synth.start();
+ // We only need to start the LineOut. It will pull data from the other
+ // units.
+ lineOut.start();
+
+ try {
+ // Queue an envelope with callbacks.
+ QueueDataCommand command = envelopePlayer.dataQueue.createQueueDataCommand(envelope, 0,
+ envelope.getNumFrames());
+ // Create an object to be called when the queued data is done.
+ TestQueueCallback callback = new TestQueueCallback();
+ command.setCallback(callback);
+ command.setNumLoops(2);
+ envelopePlayer.rate.set(0.2);
+ synth.queueCommand(command);
+ synth.sleepFor(20.0);
+
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ // Stop everything.
+ synth.stop();
+ }
+
+ class TestQueueCallback implements UnitDataQueueCallback {
+ @Override
+ public void started(QueueDataEvent event) {
+ System.out.println("CALLBACK: Envelope started.");
+ }
+
+ @Override
+ public void looped(QueueDataEvent event) {
+ System.out.println("CALLBACK: Envelope looped.");
+ }
+
+ @Override
+ public void finished(QueueDataEvent event) {
+ System.out.println("CALLBACK: Envelope finished.");
+ // Queue the envelope again at a faster rate.
+ // (If this hangs we may have hit a deadlock.)
+ envelopePlayer.rate.set(2.0);
+ envelopePlayer.dataQueue.queue(envelope);
+ }
+ }
+
+ public static void main(String[] args) {
+ new PlaySegmentedEnvelopeCallback().test();
+ }
+}
diff --git a/tests/com/jsyn/examples/PlaySequence.java b/tests/com/jsyn/examples/PlaySequence.java
new file mode 100644
index 0000000..9d058b2
--- /dev/null
+++ b/tests/com/jsyn/examples/PlaySequence.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2009 Phil Burk, Mobileer Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.jsyn.examples;
+
+import com.jsyn.JSyn;
+import com.jsyn.Synthesizer;
+import com.jsyn.unitgen.LineOut;
+import com.jsyn.unitgen.SawtoothOscillatorBL;
+import com.jsyn.unitgen.UnitOscillator;
+
+/**
+ * Use time stamps to change the frequency of an oscillator at precise times.
+ *
+ * @author Phil Burk (C) 2009 Mobileer Inc
+ */
+public class PlaySequence {
+ Synthesizer synth;
+ UnitOscillator osc;
+ LineOut lineOut;
+
+ private void test() {
+ // Create a context for the synthesizer.
+ synth = JSyn.createSynthesizer();
+
+ // Add a tone generator.
+ synth.add(osc = new SawtoothOscillatorBL());
+ // Add an output mixer.
+ synth.add(lineOut = new LineOut());
+
+ // Connect the oscillator to the left and right audio output.
+ osc.output.connect(0, lineOut.input, 0);
+ osc.output.connect(0, lineOut.input, 1);
+
+ // Start synthesizer using default stereo output at 44100 Hz.
+ synth.start();
+ // We only need to start the LineOut. It will pull data from the
+ // oscillator.
+ lineOut.start();
+
+ // Get synthesizer time in seconds.
+ double timeNow = synth.getCurrentTime();
+
+ // Advance to a near future time so we have a clean start.
+ double time = timeNow + 0.5;
+ double freq = 400.0; // hertz
+ osc.frequency.set(freq, time);
+
+ // Schedule this to happen a bit later.
+ time += 0.5;
+ freq *= 1.5; // up a perfect fifth
+ osc.frequency.set(freq, time);
+
+ time += 0.5;
+ freq *= 4.0 / 5.0; // down a major third
+ osc.frequency.set(freq, time);
+
+ // Sleep while the sound is being generated in the background thread.
+ try {
+ synth.sleepUntil(time + 0.5);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+
+ // Stop everything.
+ synth.stop();
+ }
+
+ public static void main(String[] args) {
+ new PlaySequence().test();
+ }
+}
diff --git a/tests/com/jsyn/examples/PlayTone.java b/tests/com/jsyn/examples/PlayTone.java
new file mode 100644
index 0000000..172c98a
--- /dev/null
+++ b/tests/com/jsyn/examples/PlayTone.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2010 Phil Burk, Mobileer Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.jsyn.examples;
+
+import com.jsyn.JSyn;
+import com.jsyn.Synthesizer;
+import com.jsyn.unitgen.LineOut;
+import com.jsyn.unitgen.SineOscillator;
+import com.jsyn.unitgen.UnitOscillator;
+
+/**
+ * Play a tone using a JSyn oscillator.
+ *
+ * @author Phil Burk (C) 2010 Mobileer Inc
+ */
+public class PlayTone {
+ Synthesizer synth;
+ UnitOscillator osc;
+ LineOut lineOut;
+
+ private void test() {
+
+ // Create a context for the synthesizer.
+ synth = JSyn.createSynthesizer();
+
+ // Start synthesizer using default stereo output at 44100 Hz.
+ synth.start();
+
+ // Add a tone generator.
+ synth.add(osc = new SineOscillator());
+ // Add a stereo audio output unit.
+ synth.add(lineOut = new LineOut());
+
+ // Connect the oscillator to both channels of the output.
+ osc.output.connect(0, lineOut.input, 0);
+ osc.output.connect(0, lineOut.input, 1);
+
+ // Set the frequency and amplitude for the sine wave.
+ osc.frequency.set(345.0);
+ osc.amplitude.set(0.6);
+
+ // We only need to start the LineOut. It will pull data from the
+ // oscillator.
+ lineOut.start();
+
+ System.out.println("You should now be hearing a sine wave. ---------");
+
+ // Sleep while the sound is generated in the background.
+ try {
+ double time = synth.getCurrentTime();
+ System.out.println("time = " + time);
+ // Sleep for a few seconds.
+ synth.sleepUntil(time + 4.0);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+
+ System.out.println("Stop playing. -------------------");
+ // Stop everything.
+ synth.stop();
+ }
+
+ public static void main(String[] args) {
+ new PlayTone().test();
+ }
+}
diff --git a/tests/com/jsyn/examples/RecordSineSweep.java b/tests/com/jsyn/examples/RecordSineSweep.java
new file mode 100644
index 0000000..bb248e8
--- /dev/null
+++ b/tests/com/jsyn/examples/RecordSineSweep.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2010 Phil Burk, Mobileer Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/**
+ * Test recording to disk in non-real-time.
+ * Play several frequencies of a sine wave.
+ * Save data in a WAV file format.
+ *
+ * @author (C) 2010 Phil Burk
+ */
+
+package com.jsyn.examples;
+
+import java.io.File;
+import java.io.IOException;
+
+import com.jsyn.JSyn;
+import com.jsyn.Synthesizer;
+import com.jsyn.unitgen.ExponentialRamp;
+import com.jsyn.unitgen.LineOut;
+import com.jsyn.unitgen.SawtoothOscillatorBL;
+import com.jsyn.unitgen.SineOscillator;
+import com.jsyn.unitgen.UnitOscillator;
+import com.jsyn.util.WaveRecorder;
+
+public class RecordSineSweep {
+ final static double SONG_DURATION = 4.0;
+ private Synthesizer synth;
+ private UnitOscillator leftOsc;
+ private UnitOscillator rightOsc;
+ private ExponentialRamp sweeper;
+ private LineOut lineOut;
+ private WaveRecorder recorder;
+ private final static boolean useRecorder = true;
+
+ private void test() throws IOException {
+ // Create a context for the synthesizer.
+ synth = JSyn.createSynthesizer();
+ synth.setRealTime(false);
+
+ if (useRecorder) {
+ File waveFile = new File("temp_recording.wav");
+ // Default is stereo, 16 bits.
+ recorder = new WaveRecorder(synth, waveFile);
+ System.out.println("Writing to WAV file " + waveFile.getAbsolutePath());
+ }
+ // Add some tone generators.
+ synth.add(leftOsc = new SineOscillator());
+ synth.add(rightOsc = new SawtoothOscillatorBL());
+
+ // Add a controller that will sweep up.
+ synth.add(sweeper = new ExponentialRamp());
+ // Add an output unit.
+ synth.add(lineOut = new LineOut());
+
+ sweeper.current.set(50.0);
+ sweeper.input.set(1400.0);
+ sweeper.time.set(SONG_DURATION);
+ sweeper.output.connect(leftOsc.frequency);
+ sweeper.output.connect(rightOsc.frequency);
+
+ // Connect the oscillator to the left and right audio output.
+ leftOsc.output.connect(0, lineOut.input, 0);
+ rightOsc.output.connect(0, lineOut.input, 1);
+
+ // Start synthesizer using default stereo output at 44100 Hz.
+ synth.start();
+
+ if (useRecorder) {
+ leftOsc.output.connect(0, recorder.getInput(), 0);
+ rightOsc.output.connect(0, recorder.getInput(), 1);
+ // When we start the recorder it will pull data from the oscillator
+ // and sweeper.
+ recorder.start();
+ }
+
+ // We also need to start the LineOut if we want to hear it now.
+ lineOut.start();
+
+ // Get synthesizer time in seconds.
+ double timeNow = synth.getCurrentTime();
+
+ // Sleep while the sound is being generated in the background thread.
+ try {
+ synth.sleepUntil(timeNow + SONG_DURATION);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+
+ // Test stopping and restarting a recorder. This will cause a pop.
+ if (recorder != null) {
+ System.out.println("Stop and restart recorder.");
+ recorder.stop();
+ }
+ sweeper.input.set(100.0);
+ timeNow = synth.getCurrentTime();
+ if (recorder != null) {
+ recorder.start();
+ }
+
+ // Sleep while the sound is being generated in the background thread.
+ try {
+ synth.sleepUntil(timeNow + SONG_DURATION);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+
+ if (recorder != null) {
+ recorder.stop();
+ recorder.close();
+ }
+ // Stop everything.
+ synth.stop();
+ }
+
+ public static void main(String[] args) {
+ try {
+ new RecordSineSweep().test();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+}
diff --git a/tests/com/jsyn/examples/SampleHoldNoteBlaster.java b/tests/com/jsyn/examples/SampleHoldNoteBlaster.java
new file mode 100644
index 0000000..bc6b4d0
--- /dev/null
+++ b/tests/com/jsyn/examples/SampleHoldNoteBlaster.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2011 Phil Burk, Mobileer Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.jsyn.examples;
+
+import com.jsyn.ports.UnitInputPort;
+import com.jsyn.ports.UnitOutputPort;
+import com.jsyn.unitgen.Circuit;
+import com.jsyn.unitgen.EdgeDetector;
+import com.jsyn.unitgen.EnvelopeDAHDSR;
+import com.jsyn.unitgen.FilterLowPass;
+import com.jsyn.unitgen.Latch;
+import com.jsyn.unitgen.Multiply;
+import com.jsyn.unitgen.PassThrough;
+import com.jsyn.unitgen.PulseOscillator;
+import com.jsyn.unitgen.SawtoothOscillatorDPW;
+import com.jsyn.unitgen.SineOscillator;
+import com.jsyn.unitgen.UnitOscillator;
+import com.jsyn.unitgen.UnitSource;
+
+/**
+ * Classic osc-filter-envelope voice with a sample and hold.
+ *
+ * @author Phil Burk (C) 2011 Mobileer Inc
+ */
+public class SampleHoldNoteBlaster extends Circuit implements UnitSource {
+
+ public UnitInputPort frequency;
+ public UnitInputPort amplitude;
+ public UnitInputPort modRate;
+ public UnitInputPort modDepth;
+ private UnitInputPort cutoff;
+ private UnitInputPort resonance;
+ private UnitInputPort pulseRate;
+ private UnitInputPort sweepRate;
+ private UnitInputPort sweepDepth;
+ public UnitOutputPort output;
+
+ private static SampleHoldNoteBlaster soundMaker; // singleton
+
+ private UnitOscillator osc;
+ private UnitOscillator samplee; // for sample and hold
+ private PulseOscillator pulser;
+ private Latch latch;
+ private UnitOscillator lfo;
+ private FilterLowPass filter;
+ private PassThrough frequencyPin;
+ private Multiply modScaler;
+ private EnvelopeDAHDSR ampEnv;
+ private Multiply sweepScaler;
+ private EdgeDetector edgeDetector;
+ private UnitInputPort pulseWidth;
+ private UnitInputPort attack;
+ private UnitInputPort decay;
+ private UnitInputPort sustain;
+ private UnitInputPort release;
+
+ public static SampleHoldNoteBlaster getInstance() {
+ if (soundMaker == null) {
+ soundMaker = new SampleHoldNoteBlaster();
+ }
+ return soundMaker;
+ }
+
+ public SampleHoldNoteBlaster() {
+ add(frequencyPin = new PassThrough());
+ add(modScaler = new Multiply());
+ add(sweepScaler = new Multiply());
+ add(edgeDetector = new EdgeDetector());
+ add(latch = new Latch());
+ add(samplee = new SineOscillator());
+ add(pulser = new PulseOscillator());
+ add(lfo = new SineOscillator());
+ add(osc = new SawtoothOscillatorDPW());
+ add(filter = new FilterLowPass());
+ // Use an envelope to control the amplitude.
+ add(ampEnv = new EnvelopeDAHDSR());
+
+ samplee.output.connect(latch.input);
+ pulser.output.connect(edgeDetector.input);
+ edgeDetector.output.connect(latch.gate);
+ latch.output.connect(osc.frequency);
+
+ frequencyPin.output.connect(osc.frequency);
+
+ frequencyPin.output.connect(modScaler.inputA);
+ modScaler.output.connect(lfo.amplitude);
+
+ frequencyPin.output.connect(sweepScaler.inputA);
+ sweepScaler.output.connect(samplee.amplitude);
+
+ lfo.output.connect(osc.frequency);
+ osc.output.connect(filter.input);
+ filter.output.connect(ampEnv.amplitude);
+ pulser.output.connect(ampEnv.input);
+
+ // Setup ports ---------------
+ addPort(amplitude = osc.amplitude, "amplitude");
+ amplitude.set(0.6);
+
+ addPort(frequency = frequencyPin.input, "frequency");
+ frequency.setup(50.0, 800.0, 2000.0);
+
+ addPort(modRate = lfo.frequency, "modRate");
+ modRate.setup(0.0, 12, 20.0);
+
+ addPort(modDepth = modScaler.inputB, "modDepth");
+ modDepth.setup(0.0, 0.0, 0.5);
+
+ addPort(cutoff = filter.frequency, "cutoff");
+ cutoff.setup(20.0, 2000.0, 5000.0);
+ addPort(resonance = filter.Q, "Q");
+
+ addPort(sweepDepth = sweepScaler.inputB, "sweepDepth");
+ sweepDepth.setup(0.0, 0.6, 1.0);
+ addPort(sweepRate = samplee.frequency, "sweepRate");
+ sweepRate.setup(0.2, 5.9271, 20.0);
+
+ addPort(pulseRate = pulser.frequency, "pulseRate");
+ pulseRate.setup(0.2, 7.0, 20.0);
+ addPort(pulseWidth = pulser.width, "pulseWidth");
+ pulseWidth.setup(-0.9, 0.9, 0.9);
+
+ addPort(attack = ampEnv.attack, "attack");
+ attack.setup(0.001, 0.001, 2.0);
+ addPort(decay = ampEnv.decay, "decay");
+ decay.setup(0.001, 0.26, 2.0);
+ addPort(sustain = ampEnv.sustain, "sustain");
+ sustain.setup(0.000, 0.24, 1.0);
+ addPort(release = ampEnv.release, "release");
+ release.setup(0.001, 0.2, 2.0);
+
+ addPort(output = ampEnv.output);
+ }
+
+ @Override
+ public UnitOutputPort getOutput() {
+ return output;
+ }
+}
diff --git a/tests/com/jsyn/examples/SawFaders.java b/tests/com/jsyn/examples/SawFaders.java
new file mode 100644
index 0000000..eaa3d1b
--- /dev/null
+++ b/tests/com/jsyn/examples/SawFaders.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2010 Phil Burk, Mobileer Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.jsyn.examples;
+
+import java.awt.GridLayout;
+
+import javax.swing.JApplet;
+import javax.swing.JPanel;
+
+import com.jsyn.JSyn;
+import com.jsyn.Synthesizer;
+import com.jsyn.swing.ExponentialRangeModel;
+import com.jsyn.swing.JAppletFrame;
+import com.jsyn.swing.PortControllerFactory;
+import com.jsyn.swing.PortModelFactory;
+import com.jsyn.swing.RotaryTextController;
+import com.jsyn.unitgen.LineOut;
+import com.jsyn.unitgen.LinearRamp;
+import com.jsyn.unitgen.SawtoothOscillatorBL;
+import com.jsyn.unitgen.UnitOscillator;
+
+/**
+ * Play a sawtooth using a JSyn oscillator and some knobs.
+ *
+ * @author Phil Burk (C) 2010 Mobileer Inc
+ */
+public class SawFaders extends JApplet {
+ private static final long serialVersionUID = -2704222221111608377L;
+ private Synthesizer synth;
+ private UnitOscillator osc;
+ private LinearRamp lag;
+ private LineOut lineOut;
+
+ @Override
+ public void init() {
+ synth = JSyn.createSynthesizer();
+
+ // Add a tone generator. (band limited sawtooth)
+ synth.add(osc = new SawtoothOscillatorBL());
+ // Add a lag to smooth out amplitude changes and avoid pops.
+ synth.add(lag = new LinearRamp());
+ // Add an output mixer.
+ synth.add(lineOut = new LineOut());
+ // Connect the oscillator to both left and right output.
+ osc.output.connect(0, lineOut.input, 0);
+ osc.output.connect(0, lineOut.input, 1);
+
+ // Set the minimum, current and maximum values for the port.
+ lag.output.connect(osc.amplitude);
+ lag.input.setup(0.0, 0.5, 1.0);
+ lag.time.set(0.2);
+
+ // Arrange the faders in a stack.
+ setLayout(new GridLayout(0, 1));
+
+ ExponentialRangeModel amplitudeModel = PortModelFactory.createExponentialModel(lag.input);
+ RotaryTextController knob = new RotaryTextController(amplitudeModel, 5);
+ JPanel knobPanel = new JPanel();
+ knobPanel.add(knob);
+ add(knobPanel);
+
+ osc.frequency.setup(50.0, 300.0, 10000.0);
+ add(PortControllerFactory.createExponentialPortSlider(osc.frequency));
+ validate();
+ }
+
+ @Override
+ public void start() {
+ // Start synthesizer using default stereo output at 44100 Hz.
+ synth.start();
+ // We only need to start the LineOut. It will pull data from the
+ // oscillator.
+ lineOut.start();
+ }
+
+ @Override
+ public void stop() {
+ synth.stop();
+ }
+
+ /* Can be run as either an application or as an applet. */
+ public static void main(String args[]) {
+ SawFaders applet = new SawFaders();
+ JAppletFrame frame = new JAppletFrame("SawFaders", applet);
+ frame.setSize(440, 200);
+ frame.setVisible(true);
+ frame.test();
+ }
+
+}
diff --git a/tests/com/jsyn/examples/SeeGoogleWave.java b/tests/com/jsyn/examples/SeeGoogleWave.java
new file mode 100644
index 0000000..eb7a5ff
--- /dev/null
+++ b/tests/com/jsyn/examples/SeeGoogleWave.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2010 Phil Burk, Mobileer Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.jsyn.examples;
+
+import java.awt.BorderLayout;
+import java.awt.GridLayout;
+
+import javax.swing.JApplet;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+import com.jsyn.JSyn;
+import com.jsyn.Synthesizer;
+import com.jsyn.scope.AudioScope;
+import com.jsyn.swing.JAppletFrame;
+import com.jsyn.swing.PortControllerFactory;
+import com.jsyn.unitgen.LineOut;
+
+/**
+ * Generate the waveform shown on the Google home page on 2/22/12.
+ *
+ * @author Phil Burk (C) 2010 Mobileer Inc
+ */
+public class SeeGoogleWave extends JApplet {
+ private static final long serialVersionUID = -831590388347137926L;
+ private Synthesizer synth;
+ private GoogleWaveOscillator googleWaveUnit;
+ private LineOut lineOut;
+ private AudioScope scope;
+
+ /* Can be run as either an application or as an applet. */
+ public static void main(String args[]) {
+ SeeGoogleWave applet = new SeeGoogleWave();
+ JAppletFrame frame = new JAppletFrame("Google Wave", applet);
+ frame.setSize(640, 500);
+ frame.setVisible(true);
+ frame.test();
+ frame.validate();
+ }
+
+ private void setupGUI() {
+ setLayout(new BorderLayout());
+
+ add(BorderLayout.NORTH, new JLabel("GoogleWave - elliptical segments"));
+
+ scope = new AudioScope(synth);
+ scope.addProbe(googleWaveUnit.output);
+ scope.setTriggerMode(AudioScope.TriggerMode.NORMAL);
+ scope.getView().setShowControls(false);
+ scope.start();
+ add(BorderLayout.CENTER, scope.getView());
+
+ JPanel southPanel = new JPanel();
+ southPanel.setLayout(new GridLayout(0, 1));
+ add(BorderLayout.SOUTH, southPanel);
+
+ southPanel.add(PortControllerFactory.createExponentialPortSlider(googleWaveUnit.frequency));
+ southPanel.add(PortControllerFactory.createExponentialPortSlider(googleWaveUnit.variance));
+ southPanel.add(PortControllerFactory.createExponentialPortSlider(googleWaveUnit.amplitude));
+
+ validate();
+ }
+
+ @Override
+ public void start() {
+ synth = JSyn.createSynthesizer();
+ synth.add(googleWaveUnit = new GoogleWaveOscillator());
+ googleWaveUnit.amplitude.setup(0.02, 0.5, 1.0);
+ googleWaveUnit.variance.setup(0.0, 0.0, 1.0);
+ googleWaveUnit.frequency.setup(40.0, 200.0, 1000.0);
+
+ // Add an output so we can hear it.
+ synth.add(lineOut = new LineOut());
+
+ googleWaveUnit.output.connect(0, lineOut.input, 0);
+ googleWaveUnit.output.connect(0, lineOut.input, 1);
+
+ setupGUI();
+
+ // Start synthesizer using default stereo output at 44100 Hz.
+ synth.start();
+ // Start lineOut so it can pull data from other units.
+ lineOut.start();
+
+ // We only need to start the LineOut. It will pull data from the
+ // oscillator.
+ lineOut.start();
+
+ }
+
+ @Override
+ public void stop() {
+ scope.stop();
+ synth.stop();
+ }
+
+}
diff --git a/tests/com/jsyn/examples/SeeOscillators.java b/tests/com/jsyn/examples/SeeOscillators.java
new file mode 100644
index 0000000..b01e3a9
--- /dev/null
+++ b/tests/com/jsyn/examples/SeeOscillators.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright 2010 Phil Burk, Mobileer Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.jsyn.examples;
+
+import java.awt.BorderLayout;
+import java.awt.GridLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.ArrayList;
+
+import javax.swing.ButtonGroup;
+import javax.swing.JApplet;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JRadioButton;
+
+import com.jsyn.JSyn;
+import com.jsyn.Synthesizer;
+import com.jsyn.scope.AudioScope;
+import com.jsyn.swing.JAppletFrame;
+import com.jsyn.swing.PortControllerFactory;
+import com.jsyn.unitgen.ImpulseOscillator;
+import com.jsyn.unitgen.ImpulseOscillatorBL;
+import com.jsyn.unitgen.LineOut;
+import com.jsyn.unitgen.LinearRamp;
+import com.jsyn.unitgen.Multiply;
+import com.jsyn.unitgen.PulseOscillator;
+import com.jsyn.unitgen.PulseOscillatorBL;
+import com.jsyn.unitgen.RedNoise;
+import com.jsyn.unitgen.SawtoothOscillator;
+import com.jsyn.unitgen.SawtoothOscillatorBL;
+import com.jsyn.unitgen.SawtoothOscillatorDPW;
+import com.jsyn.unitgen.SineOscillator;
+import com.jsyn.unitgen.SquareOscillator;
+import com.jsyn.unitgen.SquareOscillatorBL;
+import com.jsyn.unitgen.TriangleOscillator;
+import com.jsyn.unitgen.UnitOscillator;
+
+/**
+ * Display each oscillators waveform using the AudioScope. This is a reimplementation of the
+ * TJ_SeeOsc Applet from the old API.
+ *
+ * @author Phil Burk (C) 2010 Mobileer Inc
+ */
+public class SeeOscillators extends JApplet {
+ private static final long serialVersionUID = -8315903842197137926L;
+ private Synthesizer synth;
+ private ArrayList<UnitOscillator> oscillators = new ArrayList<UnitOscillator>();
+ private LineOut lineOut;
+ private AudioScope scope;
+ private JPanel oscPanel;
+ private Multiply oscGain;
+ private ButtonGroup buttonGroup;
+ private LinearRamp freqRamp;
+
+ /* Can be run as either an application or as an applet. */
+ public static void main(String args[]) {
+ SeeOscillators applet = new SeeOscillators();
+ JAppletFrame frame = new JAppletFrame("ShowWaves", applet);
+ frame.setSize(640, 500);
+ frame.setVisible(true);
+ frame.test();
+ frame.validate();
+ }
+
+ private void setupGUI() {
+ setLayout(new BorderLayout());
+
+ add(BorderLayout.NORTH, new JLabel("Show Oscillators in an AudioScope"));
+
+ scope = new AudioScope(synth);
+ scope.addProbe(oscGain.output);
+ scope.setTriggerMode(AudioScope.TriggerMode.NORMAL);
+ // scope.getModel().getTriggerModel().getLevelModel().setDoubleValue( 0.0001 );
+ // Turn off the gain and trigger control GUI.
+ scope.getView().setShowControls(false);
+ scope.start();
+ add(BorderLayout.CENTER, scope.getView());
+
+ JPanel southPanel = new JPanel();
+ southPanel.setLayout(new GridLayout(0, 1));
+ add(BorderLayout.SOUTH, southPanel);
+
+ oscPanel = new JPanel();
+ oscPanel.setLayout(new GridLayout(2, 5));
+ southPanel.add(oscPanel);
+
+ southPanel.add(PortControllerFactory.createExponentialPortSlider(freqRamp.input));
+ southPanel.add(PortControllerFactory.createExponentialPortSlider(oscGain.inputB));
+
+ oscPanel.validate();
+ validate();
+ }
+
+ @Override
+ public void start() {
+ synth = JSyn.createSynthesizer();
+
+ // Use a multiplier for gain control and so we can hook up to the scope
+ // from a single unit.
+ synth.add(oscGain = new Multiply());
+ oscGain.inputB.setup(0.02, 0.5, 1.0);
+ oscGain.inputB.setName("Amplitude");
+
+ synth.add(freqRamp = new LinearRamp());
+ freqRamp.input.setup(50.0, 300.0, 20000.0);
+ freqRamp.input.setName("Frequency");
+ freqRamp.time.set(0.1);
+
+ // Add an output so we can hear the oscillators.
+ synth.add(lineOut = new LineOut());
+
+ oscGain.output.connect(lineOut.input);
+
+ setupGUI();
+
+ buttonGroup = new ButtonGroup();
+
+ addOscillator(new SineOscillator(), "Sine");
+ addOscillator(new TriangleOscillator(), "Triangle");
+ addOscillator(new SawtoothOscillator(), "Sawtooth");
+ addOscillator(new SawtoothOscillatorBL(), "SawBL");
+ addOscillator(new SawtoothOscillatorDPW(), "SawDPW");
+ addOscillator(new RedNoise(), "RedNoise");
+
+ addOscillator(new SquareOscillator(), "Square");
+ addOscillator(new SquareOscillatorBL(), "SquareBL");
+ addOscillator(new PulseOscillator(), "Pulse");
+ addOscillator(new PulseOscillatorBL(), "PulseBL");
+ addOscillator(new ImpulseOscillator(), "Impulse");
+ addOscillator(new ImpulseOscillatorBL(), "ImpulseBL");
+
+ // Start synthesizer using default stereo output at 44100 Hz.
+ synth.start();
+ // Start lineOut so it can pull data from other units.
+ lineOut.start();
+
+ // We only need to start the LineOut. It will pull data from the
+ // oscillator.
+ lineOut.start();
+
+ }
+
+ private void addOscillator(final UnitOscillator osc, String label) {
+ oscillators.add(osc);
+ synth.add(osc);
+ freqRamp.output.connect(osc.frequency);
+ osc.amplitude.set(1.0);
+ JRadioButton checkBox = new JRadioButton(label);
+ buttonGroup.add(checkBox);
+ checkBox.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent arg0) {
+ // Disconnect other oscillators.
+ oscGain.inputA.disconnectAll(0);
+ // Connect this one.
+ osc.output.connect(oscGain.inputA);
+ }
+ });
+ oscPanel.add(checkBox);
+ }
+
+ @Override
+ public void stop() {
+ scope.stop();
+ synth.stop();
+ }
+
+}
diff --git a/tests/com/jsyn/examples/ShowWaves.java b/tests/com/jsyn/examples/ShowWaves.java
new file mode 100644
index 0000000..b1dd215
--- /dev/null
+++ b/tests/com/jsyn/examples/ShowWaves.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright 2010 Phil Burk, Mobileer Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.jsyn.examples;
+
+import java.awt.BorderLayout;
+import java.util.ArrayList;
+
+import javax.swing.JApplet;
+import javax.swing.JLabel;
+
+import com.jsyn.JSyn;
+import com.jsyn.Synthesizer;
+import com.jsyn.scope.AudioScope;
+import com.jsyn.swing.JAppletFrame;
+import com.jsyn.unitgen.Add;
+import com.jsyn.unitgen.LineOut;
+import com.jsyn.unitgen.SawtoothOscillatorBL;
+import com.jsyn.unitgen.SineOscillator;
+import com.jsyn.unitgen.TriangleOscillator;
+import com.jsyn.unitgen.UnitOscillator;
+
+/**
+ * Display waveforms using the AudioScope. The frequency of the oscillators is modulated by an LFO.
+ *
+ * @author Phil Burk (C) 2010 Mobileer Inc
+ */
+public class ShowWaves extends JApplet {
+ private static final long serialVersionUID = -8315903842197137926L;
+ private Synthesizer synth;
+ private UnitOscillator lfo;
+ private Add adder;
+ private ArrayList<UnitOscillator> oscillators = new ArrayList<UnitOscillator>();
+ private LineOut lineOut;
+ private AudioScope scope;
+
+ /* Can be run as either an application or as an applet. */
+ public static void main(String args[]) {
+ ShowWaves applet = new ShowWaves();
+ JAppletFrame frame = new JAppletFrame("ShowWaves", applet);
+ frame.setSize(640, 300);
+ frame.setVisible(true);
+ frame.test();
+ }
+
+ private void setupGUI() {
+ setLayout(new BorderLayout());
+
+ add(BorderLayout.NORTH, new JLabel("ShowWaves in an AudioScope Mod001"));
+
+ scope = new AudioScope(synth);
+ for (UnitOscillator osc : oscillators) {
+ scope.addProbe(osc.output);
+ }
+ scope.setTriggerMode(AudioScope.TriggerMode.NORMAL);
+ scope.start();
+
+ // Turn on the gain and trigger control GUI.
+ scope.getView().setControlsVisible(true);
+ add(BorderLayout.CENTER, scope.getView());
+ validate();
+ }
+
+ @Override
+ public void start() {
+ synth = JSyn.createSynthesizer();
+
+ // Add an LFO.
+ synth.add(lfo = new SineOscillator());
+ synth.add(adder = new Add());
+
+ // Add an output so we can hear the oscillators.
+ synth.add(lineOut = new LineOut());
+
+ lfo.frequency.set(0.1);
+ lfo.amplitude.set(200.0);
+ adder.inputB.set(400.0);
+ lfo.output.connect(adder.inputA);
+
+ oscillators.add(new SawtoothOscillatorBL());
+ oscillators.add(new SineOscillator());
+ oscillators.add(new TriangleOscillator());
+ for (UnitOscillator osc : oscillators) {
+ synth.add(osc);
+ adder.output.connect(osc.frequency);
+ osc.output.connect(0, lineOut.input, 0);
+ osc.amplitude.set(0.2);
+ }
+
+ // Start synthesizer using default stereo output at 44100 Hz.
+ synth.start();
+ // Start lineOut so it can pull data from other units.
+ lineOut.start();
+ setupGUI();
+
+ // We only need to start the LineOut. It will pull data from the
+ // oscillator.
+ lineOut.start();
+
+ }
+
+ @Override
+ public void stop() {
+ scope.stop();
+ synth.stop();
+ }
+
+}
diff --git a/tests/com/jsyn/examples/SwarmOfOscillators.java b/tests/com/jsyn/examples/SwarmOfOscillators.java
new file mode 100644
index 0000000..9f7c19c
--- /dev/null
+++ b/tests/com/jsyn/examples/SwarmOfOscillators.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright 2009 Phil Burk, Mobileer Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.jsyn.examples;
+
+import com.jsyn.JSyn;
+import com.jsyn.Synthesizer;
+import com.jsyn.ports.UnitOutputPort;
+import com.jsyn.unitgen.Add;
+import com.jsyn.unitgen.AsymptoticRamp;
+import com.jsyn.unitgen.Circuit;
+import com.jsyn.unitgen.LineOut;
+import com.jsyn.unitgen.Pan;
+import com.jsyn.unitgen.SawtoothOscillatorDPW;
+import com.jsyn.unitgen.SineOscillator;
+import com.jsyn.unitgen.UnitOscillator;
+import com.jsyn.unitgen.UnitSource;
+
+/**
+ * Make a bunch of oscillators that swarm around a moving frequency.
+ *
+ * @author Phil Burk (C) 2009 Mobileer Inc
+ */
+public class SwarmOfOscillators {
+ private Synthesizer synth;
+ Follower[] followers;
+ SineOscillator lfo;
+ LineOut lineOut;
+ private Add tiePoint;
+ private static final int NUM_FOLLOWERS = 30;
+
+ class Follower extends Circuit implements UnitSource {
+ UnitOscillator osc;
+ AsymptoticRamp lag;
+ Pan panner;
+
+ Follower() {
+ // Add a tone generator.
+ add(osc = new SawtoothOscillatorDPW());
+ osc.amplitude.set(0.03);
+
+ // Use a lag to smoothly change frequency.
+ add(lag = new AsymptoticRamp());
+ double hlife = 0.01 + (Math.random() * 0.9);
+ lag.halfLife.set(hlife);
+
+ // Set left/right pan randomly between -1.0 and +1.0.
+ add(panner = new Pan());
+ panner.pan.set((Math.random() * 2.0) - 1.0);
+
+ // Track the frequency coming through the tiePoint.
+ tiePoint.output.connect(lag.input);
+ // Add the LFO offset.
+ lfo.output.connect(lag.input);
+
+ lag.output.connect(osc.frequency);
+
+ // Connect the oscillator to the left and right audio output.
+ osc.output.connect(panner.input);
+ }
+
+ @Override
+ public UnitOutputPort getOutput() {
+ return panner.output;
+ }
+ }
+
+ private void test() {
+ synth = JSyn.createSynthesizer();
+
+ // Add an output mixer.
+ synth.add(lineOut = new LineOut());
+
+ // Add a unit just to distribute the control frequency.
+ synth.add(tiePoint = new Add());
+ synth.add(lfo = new SineOscillator());
+ lfo.amplitude.set(40.0);
+ lfo.frequency.set(2.3);
+
+ followers = new Follower[NUM_FOLLOWERS];
+ for (int i = 0; i < followers.length; i++) {
+ Follower follower = new Follower();
+ synth.add(follower);
+
+ // Every follower can connect directly to the lineOut because all input ports are
+ // mixers.
+ follower.getOutput().connect(0, lineOut.input, 0);
+ follower.getOutput().connect(1, lineOut.input, 1);
+
+ followers[i] = follower;
+ }
+
+ // Start synthesizer using default stereo output at 44100 Hz.
+ synth.start();
+ // We only need to start the LineOut. It will pull data from the
+ // oscillator.
+ lineOut.start();
+
+ // Get synthesizer time in seconds.
+ double timeNow = synth.getCurrentTime();
+
+ // Advance to a near future time so we have a clean start.
+ double duration = 0.9;
+ double time = timeNow + duration;
+ double freq = 400.0; // hertz
+ tiePoint.inputA.set(freq, time);
+
+ // Randomly change the target frequency for the followers.
+ try {
+ for (int i = 0; i < 20; i++) {
+ // Schedule this to happen a bit later.
+ time += duration;
+ freq = 200.0 + (Math.random() * 500.0);
+ tiePoint.inputA.set(freq, time);
+
+ // Sleep while the sound is being generated in the background
+ // thread.
+ synth.sleepUntil(time - 0.2);
+ }
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+
+ System.out.format("CPU usage = %4.2f%c\n", synth.getUsage() * 100, '%');
+
+ // Stop everything.
+ synth.stop();
+ }
+
+ public static void main(String[] args) {
+ new SwarmOfOscillators().test();
+ }
+}
diff --git a/tests/com/jsyn/examples/UseMidiKeyboard.java b/tests/com/jsyn/examples/UseMidiKeyboard.java
new file mode 100644
index 0000000..196f13c
--- /dev/null
+++ b/tests/com/jsyn/examples/UseMidiKeyboard.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright 2010 Phil Burk, Mobileer Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.jsyn.examples;
+
+import java.io.IOException;
+
+import javax.sound.midi.MidiDevice;
+import javax.sound.midi.MidiMessage;
+import javax.sound.midi.MidiUnavailableException;
+import javax.sound.midi.Receiver;
+
+import com.jsyn.JSyn;
+import com.jsyn.Synthesizer;
+import com.jsyn.devices.javasound.MidiDeviceTools;
+import com.jsyn.instruments.SubtractiveSynthVoice;
+import com.jsyn.midi.MessageParser;
+import com.jsyn.midi.MidiConstants;
+import com.jsyn.unitgen.LineOut;
+import com.jsyn.unitgen.PowerOfTwo;
+import com.jsyn.unitgen.SineOscillator;
+import com.jsyn.unitgen.UnitOscillator;
+import com.jsyn.util.VoiceAllocator;
+import com.softsynth.shared.time.TimeStamp;
+
+/**
+ * Connect a USB MIDI Keyboard to the internal MIDI Synthesizer using JavaSound.
+ *
+ * @author Phil Burk (C) 2010 Mobileer Inc
+ */
+public class UseMidiKeyboard {
+ private static final int MAX_VOICES = 8;
+ private Synthesizer synth;
+ private VoiceAllocator allocator;
+ private LineOut lineOut;
+ private double vibratoRate = 5.0;
+ private double vibratoDepth = 0.0;
+
+ private UnitOscillator lfo;
+ private PowerOfTwo powerOfTwo;
+ private MessageParser messageParser;
+ private SubtractiveSynthVoice[] voices;
+
+ public static void main(String[] args) {
+ UseMidiKeyboard app = new UseMidiKeyboard();
+ try {
+ app.test();
+ } catch (MidiUnavailableException e) {
+ e.printStackTrace();
+ } catch (IOException e) {
+ e.printStackTrace();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+
+ // Write a Receiver to get the messages from a Transmitter.
+ class CustomReceiver implements Receiver {
+ @Override
+ public void close() {
+ System.out.print("Closed.");
+ }
+
+ @Override
+ public void send(MidiMessage message, long timeStamp) {
+ byte[] bytes = message.getMessage();
+ messageParser.parse(bytes);
+ }
+ }
+
+ public int test() throws MidiUnavailableException, IOException, InterruptedException {
+ setupSynth();
+
+ messageParser = new MyParser();
+
+ int result = 2;
+ MidiDevice keyboard = MidiDeviceTools.findKeyboard();
+ Receiver receiver = new CustomReceiver();
+ // Just use default synthesizer.
+ if (keyboard != null) {
+ // If you forget to open them you will hear no sound.
+ keyboard.open();
+ // Put the receiver in the transmitter.
+ // This gives fairly low latency playing.
+ keyboard.getTransmitter().setReceiver(receiver);
+ System.out.println("Play MIDI keyboard: " + keyboard.getDeviceInfo().getDescription());
+ result = 0;
+ } else {
+ System.out.println("Could not find a keyboard.");
+ }
+ return result;
+ }
+
+ class MyParser extends MessageParser {
+ @Override
+ public void controlChange(int channel, int index, int value) {
+ // Mod Wheel
+ if (index == 1) {
+ vibratoDepth = 0.1 * value / 128.0;
+ // System.out.println( "vibratoDepth = " + vibratoDepth );
+ lfo.amplitude.set(vibratoDepth);
+ }
+ // 102 is the index of the first knob on my Axiom 25
+ else if (index == 102) {
+ final double bump = 0.95;
+ if (value < 64) {
+ vibratoRate *= bump;
+ } else {
+ vibratoRate *= 1.0 / bump;
+ }
+ System.out.println("vibratoRate = " + vibratoRate);
+ lfo.frequency.set(vibratoRate);
+ }
+
+ }
+
+ @Override
+ public void noteOff(int channel, int noteNumber, int velocity) {
+ allocator.noteOff(noteNumber, synth.createTimeStamp());
+ }
+
+ @Override
+ public void noteOn(int channel, int noteNumber, int velocity) {
+ double frequency = convertPitchToFrequency(noteNumber);
+ double amplitude = velocity / (4 * 128.0);
+ TimeStamp timeStamp = synth.createTimeStamp();
+ allocator.noteOn(noteNumber, frequency, amplitude, timeStamp);
+ }
+
+ @Override
+ public void pitchBend(int channel, int bend) {
+ double fraction = (bend - MidiConstants.PITCH_BEND_CENTER)
+ / ((double) MidiConstants.PITCH_BEND_CENTER);
+ System.out.println("bend = " + bend + ", fraction = " + fraction);
+ }
+ }
+
+ /**
+ * Calculate frequency in Hertz based on MIDI pitch. Middle C is 60.0. You can use fractional
+ * pitches so 60.5 would give you a pitch half way between C and C#.
+ */
+ double convertPitchToFrequency(double pitch) {
+ final double concertA = 440.0;
+ return concertA * Math.pow(2.0, ((pitch - 69) * (1.0 / 12.0)));
+ }
+
+ private void setupSynth() {
+ synth = JSyn.createSynthesizer();
+
+ // Add an output.
+ synth.add(lineOut = new LineOut());
+
+ synth.add(powerOfTwo = new PowerOfTwo());
+ synth.add(lfo = new SineOscillator());
+ // Sums pitch modulation.
+ lfo.output.connect(powerOfTwo.input);
+ lfo.amplitude.set(vibratoDepth);
+ lfo.frequency.set(vibratoRate);
+
+ voices = new SubtractiveSynthVoice[MAX_VOICES];
+ for (int i = 0; i < MAX_VOICES; i++) {
+ SubtractiveSynthVoice voice = new SubtractiveSynthVoice();
+ synth.add(voice);
+ powerOfTwo.output.connect(voice.pitchModulation);
+ voice.getOutput().connect(0, lineOut.input, 0);
+ voice.getOutput().connect(0, lineOut.input, 1);
+ voices[i] = voice;
+ }
+ allocator = new VoiceAllocator(voices);
+
+ // Start synthesizer using default stereo output at 44100 Hz.
+ synth.start();
+ // We only need to start the LineOut. It will pull data from the
+ // oscillator.
+ lineOut.start();
+
+ // Get synthesizer time in seconds.
+ double timeNow = synth.getCurrentTime();
+
+ // Advance to a near future time so we have a clean start.
+ double time = timeNow + 0.5;
+
+ }
+
+}
diff --git a/tests/com/jsyn/examples/WindCircuit.java b/tests/com/jsyn/examples/WindCircuit.java
new file mode 100644
index 0000000..1e3623e
--- /dev/null
+++ b/tests/com/jsyn/examples/WindCircuit.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 1997 Phil Burk, Mobileer Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.jsyn.examples;
+
+import com.jsyn.ports.UnitInputPort;
+import com.jsyn.ports.UnitOutputPort;
+import com.jsyn.unitgen.Circuit;
+import com.jsyn.unitgen.FilterStateVariable;
+import com.jsyn.unitgen.MultiplyAdd;
+import com.jsyn.unitgen.RedNoise;
+import com.jsyn.unitgen.UnitSource;
+import com.jsyn.unitgen.WhiteNoise;
+
+/**
+ * Wind Sound Create a wind-like sound by feeding white noise "shshshshsh" through a randomly
+ * varying state filter to make a "whooowhoosh" sound. The cuttoff frequency of the low pass filter
+ * is controlled by a RedNoise unit which creates a slowly varying random control signal.
+ *
+ * @author (C) 1997 Phil Burk, SoftSynth.com
+ */
+
+public class WindCircuit extends Circuit implements UnitSource {
+ /* Declare units that will be part of the circuit. */
+ WhiteNoise myNoise;
+ FilterStateVariable myFilter;
+ RedNoise myLFO;
+ MultiplyAdd myScalar;
+
+ /* Declare ports. */
+ public UnitInputPort noiseAmp;
+ public UnitInputPort modRate;
+ public UnitInputPort modDepth;
+ public UnitInputPort cutoff;
+ public UnitInputPort resonance;
+ public UnitInputPort amplitude;
+ public UnitOutputPort output;
+
+ public WindCircuit() {
+ /*
+ * Create various unit generators and add them to circuit.
+ */
+ add(myNoise = new WhiteNoise());
+ add(myFilter = new FilterStateVariable());
+ add(myLFO = new RedNoise());
+ add(myScalar = new MultiplyAdd());
+
+ /* Make ports on internal units appear as ports on circuit. */
+ /* Optionally give some circuit ports more meaningful names. */
+ addPort(noiseAmp = myNoise.amplitude, "NoiseAmp");
+ addPort(modRate = myLFO.frequency, "ModRate");
+ addPort(modDepth = myScalar.inputB, "ModDepth");
+ addPort(cutoff = myScalar.inputC, "Cutoff");
+ addPort(resonance = myFilter.resonance);
+ addPort(amplitude = myFilter.amplitude);
+ addPort(output = myFilter.output);
+
+ /* Connect SynthUnits to make control signal path. */
+ myLFO.output.connect(myScalar.inputA);
+ myScalar.output.connect(myFilter.frequency);
+ /* Connect SynthUnits to make audio signal path. */
+ myNoise.output.connect(myFilter.input);
+
+ /* Set ports to useful values and ranges. */
+ noiseAmp.setup(0.0, 0.3, 0.4);
+ modRate.setup(0.0, 1.0, 10.0);
+ modDepth.setup(0.0, 300.0, 1000.0);
+ cutoff.setup(0.0, 600.0, 1000.0);
+ resonance.setup(0.0, 0.066, 0.2);
+ amplitude.setup(0.0, 0.9, 0.999);
+ }
+
+ @Override
+ public UnitOutputPort getOutput() {
+ return output;
+ }
+}
diff --git a/tests/com/jsyn/ports/TestQueuedDataPort.java b/tests/com/jsyn/ports/TestQueuedDataPort.java
new file mode 100644
index 0000000..8c4714b
--- /dev/null
+++ b/tests/com/jsyn/ports/TestQueuedDataPort.java
@@ -0,0 +1,492 @@
+/*
+ * Copyright 2009 Phil Burk, Mobileer Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.jsyn.ports;
+
+import junit.framework.TestCase;
+
+import com.jsyn.JSyn;
+import com.jsyn.Synthesizer;
+import com.jsyn.data.FloatSample;
+import com.jsyn.data.SequentialData;
+import com.jsyn.data.ShortSample;
+import com.jsyn.unitgen.FixedRateMonoReader;
+
+/**
+ * Test sample and envelope queuing and looping.
+ *
+ * @author Phil Burk, (C) 2009 Mobileer Inc
+ */
+public class TestQueuedDataPort extends TestCase {
+ Synthesizer synth;
+ float[] floatData = {
+ 0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f
+ };
+ FloatSample floatSample;
+ FixedRateMonoReader reader;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ synth = JSyn.createSynthesizer();
+ synth.setRealTime(false);
+ synth.start();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ synth.stop();
+ super.tearDown();
+ }
+
+ private void queueDirect(UnitDataQueuePort port, SequentialData data, int startFrame,
+ int numFrames) {
+ queueDirect(port, data, startFrame, numFrames, 0);
+ }
+
+ private void queueDirect(UnitDataQueuePort port, SequentialData data, int startFrame,
+ int numFrames, int numLoops) {
+ QueueDataCommand command = port.createQueueDataCommand(data, startFrame, numFrames);
+ command.setNumLoops(numLoops);
+ port.addQueuedBlock(command);
+ }
+
+ public void testQueueSingleShort() {
+ short[] data = {
+ 234, -9876, 4567
+ };
+ ShortSample sample = new ShortSample(data.length, 1);
+ sample.write(data);
+
+ UnitDataQueuePort dataQueue = new UnitDataQueuePort("test");
+ assertEquals("start empty", false, dataQueue.hasMore());
+
+ queueDirect(dataQueue, sample, 0, data.length);
+ checkQueuedData(data, dataQueue, 0, data.length);
+
+ assertEquals("end empty", false, dataQueue.hasMore());
+ }
+
+ public void testQueueSingleFloat() {
+ float[] data = {
+ 0.4f, 1.9f, 22.7f
+ };
+ FloatSample sample = new FloatSample(data.length, 1);
+ sample.write(data);
+
+ UnitDataQueuePort dataQueue = new UnitDataQueuePort("test");
+ assertEquals("start empty", false, dataQueue.hasMore());
+
+ queueDirect(dataQueue, sample, 0, data.length);
+ checkQueuedData(data, dataQueue, 0, data.length);
+
+ assertEquals("end empty", false, dataQueue.hasMore());
+ }
+
+ public void testQueueMultiple() {
+ short[] data = {
+ 234, 17777, -9876, 4567, -14287
+ };
+ ShortSample sample = new ShortSample(data.length, 1);
+ sample.write(data);
+
+ UnitDataQueuePort dataQueue = new UnitDataQueuePort("test");
+ assertEquals("start empty", false, dataQueue.hasMore());
+
+ queueDirect(dataQueue, sample, 1, 3);
+ queueDirect(dataQueue, sample, 0, 5);
+ queueDirect(dataQueue, sample, 2, 2);
+
+ checkQueuedData(data, dataQueue, 1, 3);
+ checkQueuedData(data, dataQueue, 0, 5);
+ checkQueuedData(data, dataQueue, 2, 2);
+
+ assertEquals("end empty", false, dataQueue.hasMore());
+ }
+
+ public void testQueueNoLoops() throws InterruptedException {
+ System.out.println("testQueueNoLoops() ================");
+ UnitDataQueuePort dataQueue = setupFloatSample();
+
+ dataQueue.queueOn(floatSample, synth.createTimeStamp());
+ // Advance synth so that the queue command propagates to the engine.
+ synth.sleepUntil(synth.getCurrentTime() + 0.01);
+
+ // play entire sample
+ checkQueuedData(floatData, dataQueue, 0, floatData.length);
+
+ assertEquals("end empty", false, dataQueue.hasMore());
+ }
+
+ public void testQueueLoopForever() throws InterruptedException {
+ System.out.println("testQueueLoopForever() ================");
+
+ UnitDataQueuePort dataQueue = setupFloatSample();
+
+ dataQueue.queue(floatSample, 0, 3);
+ dataQueue.queueLoop(floatSample, 3, 4);
+
+ // Advance synth so that the queue commands propagate to the engine.
+ synth.sleepUntil(synth.getCurrentTime() + 0.01);
+
+ checkQueuedData(floatData, dataQueue, 0, 3);
+ checkQueuedData(floatData, dataQueue, 3, 4);
+ checkQueuedData(floatData, dataQueue, 3, 4);
+ checkQueuedData(floatData, dataQueue, 3, 4);
+ checkQueuedData(floatData, dataQueue, 3, 1);
+
+ // queue final release
+ dataQueue.queue(floatSample, 3, 5);
+ synth.sleepUntil(synth.getCurrentTime() + 0.01);
+ // current loop will finish
+ checkQueuedData(floatData, dataQueue, 4, 3);
+ // release portion will play
+ checkQueuedData(floatData, dataQueue, 3, 5);
+
+ assertEquals("end empty", false, dataQueue.hasMore());
+ }
+
+ public void testQueueLoopAtLeastOnce() throws InterruptedException {
+ System.out.println("testQueueLoopAtLeastOnce() ================");
+
+ UnitDataQueuePort dataQueue = setupFloatSample();
+
+ dataQueue.queue(floatSample, 0, 3);
+ dataQueue.queueLoop(floatSample, 3, 2); // this should play at least once
+ dataQueue.queue(floatSample, 5, 2);
+
+ // Advance synth so that the queue commands propagate to the engine.
+ synth.sleepUntil(synth.getCurrentTime() + 0.01);
+
+ checkQueuedData(floatData, dataQueue, 0, 3);
+ checkQueuedData(floatData, dataQueue, 3, 2);
+ checkQueuedData(floatData, dataQueue, 5, 2);
+
+ assertEquals("end empty", false, dataQueue.hasMore());
+ }
+
+ public void testQueueNumLoops() throws InterruptedException {
+ System.out.println("testQueueNumLoops() ================");
+ UnitDataQueuePort dataQueue = setupFloatSample();
+
+ dataQueue.queue(floatSample, 0, 2);
+
+ int numLoopsA = 5;
+ dataQueue.queueLoop(floatSample, 2, 3, numLoopsA);
+
+ dataQueue.queue(floatSample, 4, 2);
+
+ int numLoopsB = 3;
+ dataQueue.queueLoop(floatSample, 3, 4, numLoopsB);
+
+ dataQueue.queue(floatSample, 5, 2);
+
+ // Advance synth so that the queue commands propagate to the engine.
+ synth.sleepUntil(synth.getCurrentTime() + 0.01);
+
+ checkQueuedData(floatData, dataQueue, 0, 2);
+ for (int i = 0; i < (numLoopsA + 1); i++) {
+ System.out.println("loop A #" + i);
+ checkQueuedData(floatData, dataQueue, 2, 3);
+ }
+ checkQueuedData(floatData, dataQueue, 4, 2);
+ for (int i = 0; i < (numLoopsB + 1); i++) {
+ System.out.println("loop B #" + i);
+ checkQueuedData(floatData, dataQueue, 3, 4);
+ }
+
+ checkQueuedData(floatData, dataQueue, 5, 2);
+
+ assertEquals("end empty", false, dataQueue.hasMore());
+ }
+
+ private UnitDataQueuePort setupFloatSample() {
+ floatSample = new FloatSample(floatData.length, 1);
+ floatSample.write(floatData);
+
+ synth.add(reader = new FixedRateMonoReader());
+ UnitDataQueuePort dataQueue = reader.dataQueue;
+ assertEquals("start empty", false, dataQueue.hasMore());
+ return dataQueue;
+ }
+
+ public void testQueueSustainLoop() throws InterruptedException {
+ System.out.println("testQueueSustainLoop() ================");
+
+ UnitDataQueuePort dataQueue = setupFloatSample();
+
+ // set up sustain loops ===========================
+ floatSample.setSustainBegin(2);
+ floatSample.setSustainEnd(4);
+ floatSample.setReleaseBegin(-1);
+ floatSample.setReleaseEnd(-1);
+
+ dataQueue.queueOn(floatSample, synth.createTimeStamp());
+ // Advance synth so that the queue command propagates to the engine.
+ synth.sleepUntil(synth.getCurrentTime() + 0.01);
+
+ checkQueuedData(floatData, dataQueue, 0, 2);
+ checkQueuedData(floatData, dataQueue, 2, 2);
+ checkQueuedData(floatData, dataQueue, 2, 2);
+ checkQueuedData(floatData, dataQueue, 2, 1); // looping
+
+ dataQueue.queueOff(floatSample, true); // queue off in middle of loop
+ synth.sleepUntil(synth.getCurrentTime() + 0.01);
+
+ checkQueuedData(floatData, dataQueue, 3, 5); // release
+ assertEquals("end empty", false, dataQueue.hasMore());
+ }
+
+ public void testQueueReleaseLoop() throws InterruptedException {
+ System.out.println("testQueueReleaseLoop() ================");
+ UnitDataQueuePort dataQueue = setupFloatSample();
+
+ // set up sustain loops ===========================
+ floatSample.setSustainBegin(-1);
+ floatSample.setSustainEnd(-1);
+ floatSample.setReleaseBegin(4);
+ floatSample.setReleaseEnd(6);
+
+ dataQueue.queueOn(floatSample, synth.createTimeStamp());
+ // Advance synth so that the queue command propagates to the engine.
+ synth.sleepUntil(synth.getCurrentTime() + 0.01);
+
+ checkQueuedData(floatData, dataQueue, 0, 4);
+ checkQueuedData(floatData, dataQueue, 4, 2);
+ checkQueuedData(floatData, dataQueue, 4, 2);
+ checkQueuedData(floatData, dataQueue, 4, 2); // looping in release cuz no
+ // sustain loop
+
+ dataQueue.queueOff(floatSample, true); // queue off in middle of loop
+ synth.sleepUntil(synth.getCurrentTime() + 0.01);
+
+ checkQueuedData(floatData, dataQueue, 4, 2);
+ checkQueuedData(floatData, dataQueue, 4, 2); // still looping
+ assertEquals("end full", true, dataQueue.hasMore());
+ }
+
+ public void testQueueSustainReleaseLoops() throws InterruptedException {
+ System.out.println("testQueueSustainReleaseLoops() ================");
+ UnitDataQueuePort dataQueue = setupFloatSample();
+
+ // set up sustain loops ===========================
+ floatSample.setSustainBegin(2);
+ floatSample.setSustainEnd(4);
+ floatSample.setReleaseBegin(5);
+ floatSample.setReleaseEnd(7);
+
+ dataQueue.queueOn(floatSample, synth.createTimeStamp());
+ // Advance synth so that the queue command propagates to the engine.
+ synth.sleepUntil(synth.getCurrentTime() + 0.01);
+
+ checkQueuedData(floatData, dataQueue, 0, 4);
+ checkQueuedData(floatData, dataQueue, 2, 2);
+ checkQueuedData(floatData, dataQueue, 2, 1); // middle of sustain loop
+
+ dataQueue.queueOff(floatSample, true); // queue off in middle of loop
+ synth.sleepUntil(synth.getCurrentTime() + 0.01);
+
+ checkQueuedData(floatData, dataQueue, 3, 2);
+ checkQueuedData(floatData, dataQueue, 5, 2); // release loop
+ checkQueuedData(floatData, dataQueue, 5, 2); // release loop
+ assertEquals("end full", true, dataQueue.hasMore());
+ }
+
+ private void checkQueuedData(short[] data, UnitDataQueuePort dataQueue, int offset,
+ int numFrames) {
+ for (int i = 0; i < numFrames; i++) {
+ assertEquals("got data", true, dataQueue.hasMore());
+ double value = dataQueue.readNextMonoDouble(synth.getFramePeriod());
+ assertEquals("data matches", data[i + offset] / 32768.0, value, 0.0001);
+ }
+ }
+
+ private void checkQueuedData(float[] data, UnitDataQueuePort dataQueue, int offset,
+ int numFrames) {
+ for (int i = 0; i < numFrames; i++) {
+ assertEquals("got data", true, dataQueue.hasMore());
+ double value = dataQueue.readNextMonoDouble(synth.getFramePeriod());
+ assertEquals("data matches", data[i + offset], value, 0.0001);
+ }
+ }
+
+ class TestQueueCallback implements UnitDataQueueCallback {
+ boolean gotStarted = false;
+ boolean gotLooped = false;
+ boolean gotFinished = false;
+ QueueDataEvent lastEvent;
+
+ @Override
+ public void started(QueueDataEvent event) {
+ System.out.println("Callback started.");
+ gotStarted = true;
+ lastEvent = event;
+ }
+
+ @Override
+ public void looped(QueueDataEvent event) {
+ System.out.println("Callback looped.");
+ gotLooped = true;
+ lastEvent = event;
+ }
+
+ @Override
+ public void finished(QueueDataEvent event) {
+ System.out.println("Callback finished.");
+ gotFinished = true;
+ lastEvent = event;
+ }
+ }
+
+ public void testQueueCallback() {
+ float[] data = {
+ 0.2f, -8.9f, 2.7f
+ };
+ FloatSample sample = new FloatSample(data.length, 1);
+ sample.write(data);
+
+ UnitDataQueuePort dataQueue = new UnitDataQueuePort("test");
+ assertEquals("start empty", false, dataQueue.hasMore());
+
+ // Create an object to be called when the queued data is done.
+ TestQueueCallback callback = new TestQueueCallback();
+
+ QueueDataCommand command = dataQueue.createQueueDataCommand(sample, 0, data.length);
+ command.setCallback(callback);
+ command.setNumLoops(2);
+ dataQueue.addQueuedBlock(command);
+
+ // Check to see if flags get set true by callback.
+ dataQueue.firePendingCallbacks();
+ assertEquals("not started yet", false, callback.gotStarted);
+ assertEquals("not looped yet", false, callback.gotLooped);
+ assertEquals("not finished yet", false, callback.gotFinished);
+
+ checkQueuedData(data, dataQueue, 0, 1);
+ dataQueue.firePendingCallbacks();
+ assertEquals("should be started now", true, callback.gotStarted);
+ assertEquals("not looped yet", false, callback.gotLooped);
+ assertEquals("not finished yet", false, callback.gotFinished);
+ assertEquals("check source of event", dataQueue, callback.lastEvent.getSource());
+ assertEquals("check sample", sample, callback.lastEvent.getSequentialData());
+ assertEquals("check loopCount", 2, callback.lastEvent.getLoopsLeft());
+
+ checkQueuedData(data, dataQueue, 1, data.length - 1);
+ dataQueue.firePendingCallbacks();
+ assertEquals("should be looped now", true, callback.gotLooped);
+ assertEquals("check loopCount", 1, callback.lastEvent.getLoopsLeft());
+ assertEquals("not finished yet", false, callback.gotFinished);
+
+ checkQueuedData(data, dataQueue, 0, data.length);
+ dataQueue.firePendingCallbacks();
+ assertEquals("check loopCount", 0, callback.lastEvent.getLoopsLeft());
+
+ checkQueuedData(data, dataQueue, 0, data.length);
+ dataQueue.firePendingCallbacks();
+ assertEquals("should be finished now", true, callback.gotFinished);
+
+ assertEquals("end empty", false, dataQueue.hasMore());
+ }
+
+ public void testImmediate() {
+ float[] data = {
+ 0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f, 10.0f, 11.0f
+ };
+ FloatSample sample = new FloatSample(data.length, 1);
+ sample.write(data);
+
+ UnitDataQueuePort dataQueue = new UnitDataQueuePort("test");
+ dataQueue.queue(sample);
+
+ // Only play some of the data then interrupt it with an immediate block.
+ checkQueuedData(data, dataQueue, 0, 3);
+
+ QueueDataCommand command = dataQueue.createQueueDataCommand(sample, 7, 3);
+ command.setImmediate(true);
+ command.run(); // execute "immediate" operation and add to block list
+
+ // Should already be in new data.
+ checkQueuedData(data, dataQueue, 7, 3);
+ }
+
+ public void testCrossFade() {
+ float[] data1 = {
+ 0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f
+ };
+ float[] data2 = {
+ 20.0f, 19.0f, 18.0f, 17.0f, 16.0f, 15.0f, 14.0f, 13.0f, 12.0f, 11.0f
+ };
+ FloatSample sample1 = new FloatSample(data1);
+ FloatSample sample2 = new FloatSample(data2);
+
+ UnitDataQueuePort dataQueue = new UnitDataQueuePort("test");
+ dataQueue.queue(sample1, 0, 4);
+
+ QueueDataCommand command = dataQueue.createQueueDataCommand(sample2, 1, 8);
+ command.setCrossFadeIn(3);
+ command.run(); // execute "immediate" operation and add to block list
+
+ // Only play some of the data then crossfade to another sample.
+ checkQueuedData(data1, dataQueue, 0, 4);
+
+ for (int i = 0; i < 3; i++) {
+ double factor = i / 3.0;
+ double value = ((1.0 - factor) * data1[i + 4]) + (factor * data2[i + 1]);
+ System.out.println("i = " + i + ", factor = " + factor + ", value = " + value);
+
+ double actual = dataQueue.readNextMonoDouble(synth.getFramePeriod());
+ assertEquals("crossfade " + i, value, actual, 0.00001);
+ }
+
+ // Should already be in new data.
+ checkQueuedData(data2, dataQueue, 4, 5);
+ }
+
+ public void testImmediateCrossFade() {
+ float[] data1 = {
+ 0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f
+ };
+ float[] data2 = {
+ 20.0f, 19.0f, 18.0f, 17.0f, 16.0f, 15.0f, 14.0f, 13.0f, 12.0f, 11.0f
+ };
+ FloatSample sample1 = new FloatSample(data1);
+ FloatSample sample2 = new FloatSample(data2);
+
+ UnitDataQueuePort dataQueue = new UnitDataQueuePort("test");
+ dataQueue.queue(sample1, 0, 4);
+
+ // Only play some of the data then crossfade to another sample.
+ int beforeInterrupt = 2;
+ checkQueuedData(data1, dataQueue, 0, beforeInterrupt);
+
+ QueueDataCommand command = dataQueue.createQueueDataCommand(sample2, 1, 8);
+ command.setImmediate(true);
+ command.setCrossFadeIn(3);
+ command.run(); // execute "immediate" operation and add to block list
+
+ for (int i = 0; i < 3; i++) {
+ double factor = i / 3.0;
+ double value = ((1.0 - factor) * data1[i + beforeInterrupt]) + (factor * data2[i + 1]);
+ System.out.println("i = " + i + ", factor = " + factor + ", value = " + value);
+
+ double actual = dataQueue.readNextMonoDouble(synth.getFramePeriod());
+ assertEquals("crossfade " + i, value, actual, 0.00001);
+ }
+
+ // Should already be in new data.
+ checkQueuedData(data2, dataQueue, 4, 5);
+ }
+}
diff --git a/tests/com/jsyn/ports/TestSequentialData.java b/tests/com/jsyn/ports/TestSequentialData.java
new file mode 100644
index 0000000..1328c78
--- /dev/null
+++ b/tests/com/jsyn/ports/TestSequentialData.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2009 Phil Burk, Mobileer Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.jsyn.ports;
+
+import junit.framework.TestCase;
+
+import com.jsyn.data.FloatSample;
+
+public class TestSequentialData extends TestCase {
+
+ float[] data1 = {
+ 0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f
+ };
+ FloatSample sample1;
+ float[] data2 = {
+ 20.0f, 19.0f, 18.0f, 17.0f, 16.0f, 15.0f, 14.0f, 13.0f, 12.0f, 11.0f
+ };
+ FloatSample sample2;
+
+ public void testCrossfade() {
+ sample1 = new FloatSample(data1);
+ sample2 = new FloatSample(data2);
+ SequentialDataCrossfade xfade = new SequentialDataCrossfade();
+ xfade.setup(sample1, 4, 3, sample2, 1, 6);
+
+ for (int i = 0; i < 3; i++) {
+ double factor = i / 3.0;
+ double value = ((1.0 - factor) * data1[i + 4]) + (factor * data2[i + 1]);
+ System.out.println("i = " + i + ", factor = " + factor + ", value = " + value);
+ assertEquals("crossfade " + i, value, xfade.readDouble(i), 0.00001);
+ }
+ for (int i = 3; i < 6; i++) {
+ assertEquals("crossfade " + i, sample2.readDouble(i + 1), xfade.readDouble(i), 0.00001);
+ }
+ }
+}
diff --git a/tests/com/jsyn/ports/TestSet.java b/tests/com/jsyn/ports/TestSet.java
new file mode 100644
index 0000000..8d1f3ea
--- /dev/null
+++ b/tests/com/jsyn/ports/TestSet.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2009 Phil Burk, Mobileer Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.jsyn.ports;
+
+import junit.framework.TestCase;
+
+import com.jsyn.engine.SynthesisEngine;
+import com.jsyn.unitgen.Minimum;
+
+public class TestSet extends TestCase {
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ /** Internal value setting. */
+ public void testSetValue() {
+ int numParts = 4;
+ UnitInputPort port = new UnitInputPort(numParts, "Tester");
+ port.setValueInternal(0, 100.0);
+ port.setValueInternal(2, 120.0);
+ port.setValueInternal(1, 110.0);
+ port.setValueInternal(3, 130.0);
+ assertEquals("check port value", 100.0, port.getValue(0));
+ assertEquals("check port value", 120.0, port.getValue(2));
+ assertEquals("check port value", 110.0, port.getValue(1));
+ assertEquals("check port value", 130.0, port.getValue(3));
+ }
+
+ public void testSet() throws InterruptedException {
+ SynthesisEngine synthesisEngine = new SynthesisEngine();
+ synthesisEngine.setRealTime(false);
+ synthesisEngine.start();
+ synthesisEngine.sleepUntil(0.01);
+ Minimum min;
+ synthesisEngine.add(min = new Minimum());
+
+ double x = 33.99;
+ double y = 8.31;
+ min.inputA.set(x);
+ min.inputB.set(y);
+ synthesisEngine.sleepFor(0.01);
+ assertEquals("min set A", x, min.inputA.getValue());
+ assertEquals("min set B", y, min.inputB.getValue());
+ min.start();
+ synthesisEngine.sleepFor(0.01);
+
+ assertEquals("min output", y, min.output.getValue());
+ synthesisEngine.stop();
+ }
+
+ /** if we use a port index out of range we want to know now and not blow up the engine. */
+ public void testSetBadPort() throws InterruptedException {
+ SynthesisEngine synthesisEngine = new SynthesisEngine();
+ synthesisEngine.setRealTime(false);
+ synthesisEngine.start();
+ Minimum min;
+ synthesisEngine.add(min = new Minimum());
+
+ min.start();
+ Exception caught = null;
+ try {
+ min.inputA.set(1, 23.45);
+ } catch (Exception e) {
+ caught = e;
+ }
+ assertTrue("Catch port out of range, caught " + caught,
+ (caught instanceof ArrayIndexOutOfBoundsException));
+
+ // Don't blow up here.
+ synthesisEngine.sleepUntil(0.01);
+
+ synthesisEngine.stop();
+ }
+
+}
diff --git a/tests/com/jsyn/research/BenchMultiThreading.java b/tests/com/jsyn/research/BenchMultiThreading.java
new file mode 100644
index 0000000..79b20bb
--- /dev/null
+++ b/tests/com/jsyn/research/BenchMultiThreading.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2009 Phil Burk, Mobileer Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.jsyn.research;
+
+import java.util.ArrayList;
+
+import junit.framework.TestCase;
+
+public class BenchMultiThreading extends TestCase {
+ private static final int FRAMES_PER_BLOCK = 64;
+ int numThreads = 4;
+ int numLoops = 100000;
+ private ArrayList<CustomThread> threadList;
+
+ class CustomThread extends Thread {
+ long frameCount = 0;
+ long desiredFrame = 0;
+ Object semaphore = new Object();
+ Object goSemaphore = new Object();
+ volatile boolean go = true;
+ long startNano;
+ long stopNano;
+ long maxElapsed;
+
+ @Override
+ public void run() {
+ try {
+ startNano = System.nanoTime();
+ while (go) {
+ // Watch for long delays.
+ stopNano = System.nanoTime();
+ long elapsed = stopNano - startNano;
+ startNano = System.nanoTime();
+ if (elapsed > maxElapsed) {
+ maxElapsed = elapsed;
+ }
+
+ synchronized (semaphore) {
+ // Audio synthesis would occur here.
+ frameCount += 1;
+ // System.out.println( this + " generating frame " +
+ // frameCount );
+ semaphore.notify();
+ }
+ synchronized (goSemaphore) {
+ while (desiredFrame <= frameCount) {
+ goSemaphore.wait();
+ }
+ }
+ long stopNano = System.nanoTime();
+ }
+ } catch (InterruptedException e) {
+ System.out.println("CustomThread interrupted. ");
+ }
+ System.out.println("Finishing " + this);
+ }
+
+ public void abort() {
+ go = false;
+ interrupt();
+ }
+
+ public void waitForFrame(long targetFrame) throws InterruptedException {
+ synchronized (semaphore) {
+ while (frameCount < targetFrame) {
+ semaphore.wait();
+ }
+ }
+ }
+
+ public void generateFrame(long desiredFrame) {
+ synchronized (goSemaphore) {
+ this.desiredFrame = desiredFrame;
+ goSemaphore.notify();
+ }
+ }
+
+ }
+
+ public void testMultiThreads() {
+ threadList = new ArrayList<CustomThread>();
+ for (int i = 0; i < numThreads; i++) {
+ CustomThread thread = new CustomThread();
+ threadList.add(thread);
+ thread.start();
+ }
+
+ long frameCount = 0;
+ long startTime = System.currentTimeMillis();
+ try {
+ for (int i = 0; i < numLoops; i++) {
+ frameCount += 1;
+ waitForThreads(frameCount);
+ // System.out.println("got frame " + frameCount );
+ }
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ long stopTime = System.currentTimeMillis();
+ long elapsedTime = stopTime - startTime;
+ double elapsedSeconds = 0.001 * elapsedTime;
+ double blocksPerSecond = numLoops / elapsedSeconds;
+ System.out.format("blocksPerSecond = %10.3f\n", blocksPerSecond);
+ double framesPerSecond = blocksPerSecond * FRAMES_PER_BLOCK;
+ System.out.format("audio framesPerSecond = %10.3f at %d frames per block\n",
+ framesPerSecond, FRAMES_PER_BLOCK);
+
+ for (CustomThread thread : threadList) {
+ System.out.format("max elapsed time is %d nanos or %f msec\n", thread.maxElapsed,
+ (thread.maxElapsed / 1000000.0));
+ }
+ for (CustomThread thread : threadList) {
+ assertEquals("BlockCount must match ", frameCount, thread.frameCount);
+ thread.abort();
+ }
+
+ }
+
+ private void waitForThreads(long frameCount) throws InterruptedException {
+ for (CustomThread thread : threadList) {
+ // Ask threads to wake up and generate up to this frame.
+ thread.generateFrame(frameCount);
+ }
+ for (CustomThread thread : threadList) {
+ // Wait for all the threads to catch up.
+ thread.waitForFrame(frameCount);
+ }
+ }
+}
diff --git a/tests/com/jsyn/research/RecordVariousRamps.java b/tests/com/jsyn/research/RecordVariousRamps.java
new file mode 100644
index 0000000..c90ea9a
--- /dev/null
+++ b/tests/com/jsyn/research/RecordVariousRamps.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright 2014 Phil Burk, Mobileer Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/**
+ * Generate steps, linear ramps and smooth ramps.
+ *
+ * @author (C) 2014 Phil Burk
+ */
+
+package com.jsyn.research;
+
+import java.io.File;
+import java.io.IOException;
+
+import com.jsyn.JSyn;
+import com.jsyn.Synthesizer;
+import com.jsyn.unitgen.ContinuousRamp;
+import com.jsyn.unitgen.LineOut;
+import com.jsyn.unitgen.LinearRamp;
+import com.jsyn.unitgen.Multiply;
+import com.jsyn.unitgen.PassThrough;
+import com.jsyn.unitgen.PowerOfTwo;
+import com.jsyn.unitgen.SawtoothOscillatorBL;
+import com.jsyn.unitgen.UnitFilter;
+import com.jsyn.unitgen.UnitOscillator;
+import com.jsyn.util.WaveRecorder;
+
+public class RecordVariousRamps {
+ private Synthesizer synth;
+ private UnitOscillator osc;
+ private Multiply multiplier;
+ private UnitFilter ramp;
+ private LinearRamp linearRamp;
+ private ContinuousRamp continuousRamp;
+ private LineOut lineOut;
+ private WaveRecorder recorder;
+ private PowerOfTwo powerOfTwo;
+ private static final int MODE_STEP = 0;
+ private static final int MODE_LINEAR = 1;
+ private static final int MODE_SMOOTH = 2;
+ private static final String[] modeNames = {
+ "step", "linear", "smooth"
+ };
+
+ private RampEvent[] rampData = {
+ new RampEvent(1.0, 1.5, 2.0), new RampEvent(-0.9, 0.5, 1.0),
+ new RampEvent(0.9, 0.5, 0.8), new RampEvent(-0.3, 0.5, 0.8),
+ new RampEvent(0.9, 0.5, 0.3), new RampEvent(-0.5, 0.5, 0.3),
+ new RampEvent(0.8, 2.0, 1.0),
+ };
+
+ private static class RampEvent {
+ double target;
+ double eventDuration;
+ double rampDuration;
+
+ RampEvent(double target, double eventDuration, double rampDuration) {
+ this.target = target;
+ this.eventDuration = eventDuration;
+ this.rampDuration = rampDuration;
+ }
+ }
+
+ private void test(int mode) throws IOException {
+ // Create a context for the synthesizer.
+ synth = JSyn.createSynthesizer();
+ synth.setRealTime(false);
+
+ File waveFile = new File("ramp_pitch_" + modeNames[mode] + ".wav");
+ // Mono 16 bits.
+ recorder = new WaveRecorder(synth, waveFile, 1, 16);
+ System.out.println("Writing to 16-bit WAV file " + waveFile.getAbsolutePath());
+
+ // Add some tone generators.
+ synth.add(osc = new SawtoothOscillatorBL());
+
+ // Add a controller that will sweep up.
+ synth.add(multiplier = new Multiply());
+ synth.add(powerOfTwo = new PowerOfTwo());
+ // Add an output unit.
+ synth.add(lineOut = new LineOut());
+ multiplier.inputB.set(660.0);
+
+ switch (mode) {
+ case MODE_STEP:
+ synth.add(ramp = new PassThrough());
+ break;
+ case MODE_LINEAR:
+ synth.add(ramp = linearRamp = new LinearRamp());
+ linearRamp.current.set(-1.0);
+ linearRamp.time.set(10.0);
+ break;
+ case MODE_SMOOTH:
+ synth.add(ramp = continuousRamp = new ContinuousRamp());
+ continuousRamp.current.set(-1.0);
+ continuousRamp.time.set(10.0);
+ break;
+ }
+
+ ramp.getInput().set(-1.0);
+ ramp.getOutput().connect(powerOfTwo.input);
+
+ powerOfTwo.output.connect(multiplier.inputA);
+ multiplier.output.connect(osc.frequency);
+
+ // Connect the oscillator to the left and right audio output.
+ osc.output.connect(0, lineOut.input, 0);
+ osc.output.connect(0, lineOut.input, 1);
+
+ // Start synthesizer using default stereo output at 44100 Hz.
+ synth.start();
+
+ osc.output.connect(0, recorder.getInput(), 0);
+ // When we start the recorder it will pull data from the oscillator
+ // and sweeper.
+ recorder.start();
+
+ // We also need to start the LineOut if we want to hear it now.
+ lineOut.start();
+
+ // Get synthesizer time in seconds.
+ double nextEventTime = synth.getCurrentTime() + 1.0;
+ try {
+ synth.sleepUntil(nextEventTime);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+
+ for (RampEvent rampEvent : rampData) {
+
+ switch (mode) {
+ case MODE_STEP:
+ break;
+ case MODE_LINEAR:
+ linearRamp.time.set(rampEvent.rampDuration);
+ break;
+ case MODE_SMOOTH:
+ continuousRamp.time.set(rampEvent.rampDuration);
+ break;
+ }
+ ramp.getInput().set(rampEvent.target);
+
+ nextEventTime += rampEvent.eventDuration;
+ System.out.println("target = " + rampEvent.target + ", rampDur = "
+ + rampEvent.rampDuration + ", eventDur = " + rampEvent.eventDuration);
+ try {
+ synth.sleepUntil(nextEventTime);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+
+ if (recorder != null) {
+ recorder.stop();
+ recorder.close();
+ }
+ // Stop everything.
+ synth.stop();
+ }
+
+ public static void main(String[] args) {
+ try {
+ new RecordVariousRamps().test(MODE_STEP);
+ new RecordVariousRamps().test(MODE_LINEAR);
+ new RecordVariousRamps().test(MODE_SMOOTH);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+}
diff --git a/tests/com/jsyn/swing/TestRangeModels.java b/tests/com/jsyn/swing/TestRangeModels.java
new file mode 100644
index 0000000..8bcd021
--- /dev/null
+++ b/tests/com/jsyn/swing/TestRangeModels.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2009 Phil Burk, Mobileer Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.jsyn.swing;
+
+import junit.framework.TestCase;
+
+public class TestRangeModels extends TestCase {
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ public void checkDoubleRange(double dmin, double dmax, double dval) {
+ int resolution = 1000;
+ DoubleBoundedRangeModel model = new DoubleBoundedRangeModel("test", resolution, dmin, dmax,
+ dval);
+ assertEquals("setup min", dmin, model.getDoubleMinimum(), 0.0001);
+ assertEquals("setup max", dmax, model.getDoubleMaximum(), 0.0001);
+ assertEquals("setup value", dval, model.getDoubleValue(), 0.0001);
+
+ model.setDoubleValue(dmin);
+ assertEquals("min double value", dmin, model.getDoubleValue(), 0.0001);
+ assertEquals("min value", 0, model.getValue());
+
+ double dmid = (dmax + dmin) / 2.0;
+ model.setDoubleValue(dmid);
+ assertEquals("middle double value", dmid, model.getDoubleValue(), 0.0001);
+ assertEquals("middle value", resolution / 2, model.getValue());
+
+ model.setDoubleValue(dmax);
+ assertEquals("max double value", dmax, model.getDoubleValue(), 0.0001);
+ assertEquals("max value", resolution, model.getValue());
+
+ }
+
+ public void testDoubleRange() {
+ checkDoubleRange(10.0, 20.0, 12.0);
+ checkDoubleRange(-1.0, 1.0, 0.5);
+ }
+}
diff --git a/tests/com/jsyn/unitgen/CalibrateMoogFilter.java b/tests/com/jsyn/unitgen/CalibrateMoogFilter.java
new file mode 100644
index 0000000..a830fcc
--- /dev/null
+++ b/tests/com/jsyn/unitgen/CalibrateMoogFilter.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright 2010 Phil Burk, Mobileer Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.jsyn.unitgen;
+
+import javax.swing.JApplet;
+
+import com.jsyn.JSyn;
+import com.jsyn.Synthesizer;
+
+/**
+ * Play a sawtooth through a 4-pole filter.
+ *
+ * @author Phil Burk (C) 2010 Mobileer Inc
+ */
+public class CalibrateMoogFilter extends JApplet {
+ private Synthesizer synth;
+ private UnitOscillator oscillator;
+ private SineOscillator reference;
+ ZeroCrossingCounter zeroCounter;
+ PitchDetector pitchDetector;
+ ZeroCrossingCounter sineZeroCounter;
+ PitchDetector sinePitchDetector;
+ private FilterFourPoles filterMoog;
+ private LineOut lineOut;
+
+ @Override
+ public void init() {
+ synth = JSyn.createSynthesizer();
+ synth.setRealTime(false);
+ synth.add(oscillator = new SawtoothOscillatorBL());
+ synth.add(reference = new SineOscillator());
+ synth.add(filterMoog = new FilterFourPoles());
+ synth.add(pitchDetector = new PitchDetector());
+ synth.add(sinePitchDetector = new PitchDetector());
+ synth.add(zeroCounter = new ZeroCrossingCounter());
+ synth.add(sineZeroCounter = new ZeroCrossingCounter());
+ synth.add(lineOut = new LineOut());
+
+ oscillator.output.connect(filterMoog.input);
+ filterMoog.output.connect(zeroCounter.input);
+ zeroCounter.output.connect(pitchDetector.input);
+ reference.output.connect(0, lineOut.input, 0);
+ filterMoog.output.connect(0, lineOut.input, 1);
+
+ reference.output.connect(sineZeroCounter.input);
+ sineZeroCounter.output.connect(sinePitchDetector.input);
+
+ oscillator.frequency.set(130.0);
+ oscillator.amplitude.set(0.001);
+ filterMoog.frequency.set(440.0);
+ filterMoog.Q.set(4.1);
+ }
+
+ @Override
+ public void start() {
+ // Start synthesizer using default stereo output at 44100 Hz.
+ synth.start();
+ pitchDetector.start();
+ sinePitchDetector.start();
+ lineOut.start();
+ }
+
+ @Override
+ public void stop() {
+ pitchDetector.stop();
+ sinePitchDetector.stop();
+ lineOut.stop();
+ synth.stop();
+ }
+
+ public void test() {
+ init();
+ start();
+ try {
+ calibrate();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ stop();
+ }
+
+ private void calibrate() throws InterruptedException {
+ synth.sleepFor(2.0);
+ double freq = 100.0;
+ System.out
+ .printf("freq, moogFreq, ratio, moogConf, sineFreq, sineConf, moogZRate, sineZRate\n");
+ long startingFrameCount = synth.getFrameCount();
+ long startingMoogZeroCount = zeroCounter.getCount();
+ long startingSineZeroCount = sineZeroCounter.getCount();
+ for (int i = 0; i < 50; i++) {
+ reference.frequency.set(freq);
+ filterMoog.frequency.set(freq);
+ synth.sleepFor(2.0);
+
+ long endingFrameCount = synth.getFrameCount();
+ long elapsedFrames = endingFrameCount - startingFrameCount;
+ startingFrameCount = endingFrameCount;
+
+ long endingMoogZeroCount = zeroCounter.getCount();
+ long elapsedMoogZeros = endingMoogZeroCount - startingMoogZeroCount;
+ startingMoogZeroCount = endingMoogZeroCount;
+
+ long endingSineZeroCount = sineZeroCounter.getCount();
+ long elapsedSineZeros = endingSineZeroCount - startingSineZeroCount;
+ startingSineZeroCount = endingSineZeroCount;
+
+ double moogZeroRate = elapsedMoogZeros * (double) synth.getFrameRate() / elapsedFrames;
+ double sineZeroRate = elapsedSineZeros * (double) synth.getFrameRate() / elapsedFrames;
+
+ double moogMeasuredFreq = pitchDetector.frequency.get();
+ double moogConfidence = pitchDetector.confidence.get();
+ double sineMeasuredFreq = sinePitchDetector.frequency.get();
+ double sineConfidence = sinePitchDetector.confidence.get();
+ double ratio = freq / moogMeasuredFreq;
+ System.out.printf("%7.2f, %8.5f, %7.5f, %4.2f, %8.5f, %4.2f, %8.4f, %8.4f\n", freq,
+ moogMeasuredFreq, ratio, moogConfidence, sineMeasuredFreq, sineConfidence,
+ moogZeroRate, sineZeroRate);
+
+ freq *= 1.1;
+ }
+ }
+
+ public static void main(String args[]) {
+ new CalibrateMoogFilter().test();
+ }
+
+}
diff --git a/tests/com/jsyn/unitgen/EnablingGate.java b/tests/com/jsyn/unitgen/EnablingGate.java
new file mode 100644
index 0000000..daf36be
--- /dev/null
+++ b/tests/com/jsyn/unitgen/EnablingGate.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2010 Phil Burk, Mobileer Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.jsyn.unitgen;
+
+import com.jsyn.ports.UnitInputPort;
+
+/**
+ * This can be used to block the execution of upstream units. It can be placed at the output of a
+ * circuit and driven with an amplitude envelope.
+ *
+ * @author Phil Burk (C) 2010 Mobileer Inc
+ */
+public class EnablingGate extends UnitFilter {
+ public UnitInputPort gate;
+
+ /* Define Unit Ports used by connect() and set(). */
+ public EnablingGate() {
+ super();
+ addPort(gate = new UnitInputPort("Gate"));
+ }
+
+ @Override
+ public void generate(int start, int limit) {
+ double[] aValues = input.getValues();
+ double[] bValues = gate.getValues();
+ double[] outputs = output.getValues();
+ for (int i = start; i < limit; i++) {
+ outputs[i] = aValues[i] * bValues[i];
+ }
+ // If we end up at zero then disable pulling of data.
+ // We do this at the end so that envelope can get started.
+ if (outputs[limit - 1] <= 0.0) {
+ setEnabled(false);
+ }
+ }
+
+}
diff --git a/tests/com/jsyn/unitgen/NonRealTimeTestCase.java b/tests/com/jsyn/unitgen/NonRealTimeTestCase.java
new file mode 100644
index 0000000..5d332a9
--- /dev/null
+++ b/tests/com/jsyn/unitgen/NonRealTimeTestCase.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2009 Phil Burk, Mobileer Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.jsyn.unitgen;
+
+import junit.framework.TestCase;
+
+import com.jsyn.engine.SynthesisEngine;
+
+public abstract class NonRealTimeTestCase extends TestCase {
+
+ protected SynthesisEngine synthesisEngine;
+
+ public NonRealTimeTestCase() {
+ super();
+ }
+
+ public NonRealTimeTestCase(String name) {
+ super(name);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ synthesisEngine = new SynthesisEngine();
+ synthesisEngine.setRealTime(false);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ synthesisEngine.stop();
+ }
+
+}
diff --git a/tests/com/jsyn/unitgen/RecordMoogFilter.java b/tests/com/jsyn/unitgen/RecordMoogFilter.java
new file mode 100644
index 0000000..6af11fd
--- /dev/null
+++ b/tests/com/jsyn/unitgen/RecordMoogFilter.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2010 Phil Burk, Mobileer Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.jsyn.unitgen;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+import javax.swing.JApplet;
+
+import com.jsyn.JSyn;
+import com.jsyn.Synthesizer;
+import com.jsyn.util.WaveRecorder;
+
+/**
+ * Measure actual frequency as a function of input frequency and Q.
+ *
+ * @author Phil Burk (C) 2010 Mobileer Inc
+ */
+public class RecordMoogFilter extends JApplet {
+ private final static boolean SWEEP_Q = false;
+ private final static boolean SWEEP_FREQUENCY = true;
+ private final static int NUM_STEPS = 11;
+
+ private final static double MIN_Q = 0.0;
+ private final static double DEFAULT_Q = 9.0;
+ private final static double MAX_Q = 10.0;
+
+ private final static double MIN_FREQUENCY = 100.0;
+ private final static double DEFAULT_FREQUENCY = 500.0;
+ private final static double MAX_FREQUENCY = 4000.0;
+
+ private Synthesizer synth;
+ private WhiteNoise source;
+ private SineOscillator reference;
+ private FilterFourPoles filterMoog;
+ private LineOut lineOut;
+ private WaveRecorder recorder;
+
+ @Override
+ public void init() {
+ synth = JSyn.createSynthesizer();
+ synth.setRealTime(false);
+ synth.add(source = new WhiteNoise());
+ synth.add(filterMoog = new FilterFourPoles());
+ synth.add(reference = new SineOscillator());
+ synth.add(lineOut = new LineOut());
+
+ source.output.connect(filterMoog.input);
+ reference.output.connect(0, lineOut.input, 0);
+ filterMoog.output.connect(0, lineOut.input, 1);
+
+ reference.amplitude.set(0.5);
+ source.amplitude.set(0.5);
+ filterMoog.frequency.set(DEFAULT_FREQUENCY);
+ filterMoog.Q.set(DEFAULT_Q);
+
+ File waveFile = new File("temp_recording.wav");
+ // Default is stereo, 16 bits.
+ try {
+ recorder = new WaveRecorder(synth, waveFile);
+ } catch (FileNotFoundException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ System.out.println("Writing to WAV file " + waveFile.getAbsolutePath());
+ }
+
+ @Override
+ public void start() {
+ // Start synthesizer using default stereo output at 44100 Hz.
+ synth.start();
+ lineOut.start();
+
+ reference.output.connect(0, recorder.getInput(), 0);
+ filterMoog.output.connect(0, recorder.getInput(), 1);
+ recorder.start();
+ }
+
+ @Override
+ public void stop() {
+ if (recorder != null) {
+ recorder.stop();
+ try {
+ recorder.close();
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+ lineOut.stop();
+ synth.stop();
+ }
+
+ public void test() {
+ init();
+ start();
+ try {
+ calibrate();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ stop();
+ }
+
+ private void calibrate() throws InterruptedException {
+ synth.sleepFor(0.2);
+ double freq = SWEEP_FREQUENCY ? MIN_FREQUENCY : DEFAULT_FREQUENCY;
+ double q = SWEEP_Q ? MIN_Q : DEFAULT_Q;
+ double stepQ = (MAX_Q - MIN_Q) / (NUM_STEPS - 1);
+ double scaleFrequency = Math.pow((MAX_FREQUENCY / MIN_FREQUENCY), (1.0 / (NUM_STEPS - 1)));
+ System.out.printf("freq, q, measured\n");
+ for (int i = 0; i < NUM_STEPS; i++) {
+ double refAmp = reference.amplitude.get();
+ reference.amplitude.set(0.0);
+ synth.sleepFor(0.1);
+ reference.amplitude.set(refAmp);
+
+ System.out.printf("%8.2f, %6.3f, \n", freq, q);
+ filterMoog.frequency.set(freq);
+ reference.frequency.set(freq);
+ filterMoog.Q.set(q);
+
+ synth.sleepFor(2.0);
+
+ if (SWEEP_FREQUENCY) {
+ freq *= scaleFrequency;
+ }
+ if (SWEEP_Q) {
+ q += stepQ;
+ }
+ }
+ }
+
+ public static void main(String args[]) {
+ new RecordMoogFilter().test();
+ }
+
+}
diff --git a/tests/com/jsyn/unitgen/TestConnections.java b/tests/com/jsyn/unitgen/TestConnections.java
new file mode 100644
index 0000000..d15a257
--- /dev/null
+++ b/tests/com/jsyn/unitgen/TestConnections.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2009 Phil Burk, Mobileer Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.jsyn.unitgen;
+
+import junit.framework.TestCase;
+
+import com.jsyn.JSyn;
+import com.jsyn.Synthesizer;
+
+public class TestConnections extends TestCase {
+ Add add1;
+ Add add2;
+ Add add3;
+
+ Synthesizer synth;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ synth = JSyn.createSynthesizer();
+
+ synth.add(add1 = new Add());
+ synth.add(add2 = new Add());
+ synth.add(add3 = new Add());
+
+ add1.start();
+ add2.start();
+ add3.start();
+
+ add1.inputA.set(0.1);
+ add1.inputB.set(0.2);
+
+ add2.inputA.set(0.4);
+ add2.inputB.set(0.8);
+
+ add3.inputA.set(1.6);
+ add3.inputB.set(3.2);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ public void testSet() throws InterruptedException {
+ synth.sleepFor(0.01);
+ assertEquals("set inputs of adder", 0.3, add1.output.getValue(), 0.0001);
+ }
+
+ public void testConnect() throws InterruptedException {
+ synth.sleepFor(0.01);
+ assertEquals("set inputs of adder", 0.3, add1.output.getValue(), 0.0001);
+ assertEquals("set inputs of adder", 1.2, add2.output.getValue(), 0.0001);
+
+ // Test different ways of connecting.
+ add1.output.connect(add2.inputB);
+ checkConnection();
+
+ add1.output.connect(0, add2.inputB, 0);
+ checkConnection();
+
+ add1.output.connect(add2.inputB.getConnectablePart(0));
+ checkConnection();
+
+ add1.output.getConnectablePart(0).connect(add2.inputB);
+ checkConnection();
+
+ add1.output.getConnectablePart(0).connect(add2.inputB.getConnectablePart(0));
+ checkConnection();
+
+ add2.inputB.connect(add1.output);
+ checkConnection();
+
+ add2.inputB.connect(0, add1.output, 0);
+ checkConnection();
+
+ add2.inputB.connect(add1.output.getConnectablePart(0));
+ checkConnection();
+
+ add2.inputB.getConnectablePart(0).connect(add1.output);
+ checkConnection();
+
+ add2.inputB.getConnectablePart(0).connect(add1.output.getConnectablePart(0));
+ checkConnection();
+ }
+
+ private void checkConnection() throws InterruptedException {
+ synth.sleepFor(0.01);
+ assertEquals("connection should not change output", 0.3, add1.output.getValue(), 0.0001);
+ assertEquals("replace set value with output", 0.7, add2.output.getValue(), 0.0001);
+
+ // Revert to set value after disconnection.
+ add1.output.disconnectAll();
+ synth.sleepFor(0.01);
+ assertEquals("still the same", 0.3, add1.output.getValue(), 0.0001);
+ assertEquals("should revert to original set() value", 1.2, add2.output.getValue(), 0.0001);
+ }
+
+}
diff --git a/tests/com/jsyn/unitgen/TestDelay.java b/tests/com/jsyn/unitgen/TestDelay.java
new file mode 100644
index 0000000..12af1cd
--- /dev/null
+++ b/tests/com/jsyn/unitgen/TestDelay.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2009 Phil Burk, Mobileer Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.jsyn.unitgen;
+
+import com.jsyn.util.AudioStreamReader;
+
+public class TestDelay extends NonRealTimeTestCase {
+ public void testFloor() {
+ double x = -7.3;
+ int n = (int) Math.floor(x);
+ assertEquals("int", -8, n);
+ }
+
+ public void checkInterpolatingDelay(int maxFrames, double delayFrames)
+ throws InterruptedException {
+ synthesisEngine.start();
+
+ System.out.printf("test delayFrames = %7.5f\n", delayFrames);
+ InterpolatingDelay delay = new InterpolatingDelay();
+ synthesisEngine.add(delay);
+ delay.allocate(maxFrames);
+ delay.delay.set(delayFrames / 44100.0);
+ SawtoothOscillator osc = new SawtoothOscillator();
+ synthesisEngine.add(osc);
+ osc.frequency.set(synthesisEngine.getFrameRate() / 4.0);
+ osc.amplitude.set(1.0);
+ osc.output.connect(delay.input);
+
+ int samplesPerFrame = 1;
+ AudioStreamReader reader = new AudioStreamReader(synthesisEngine, samplesPerFrame);
+ delay.output.connect(reader.getInput());
+
+ delay.start();
+ for (int i = 0; i < (3 * maxFrames); i++) {
+ if (reader.available() == 0) {
+ synthesisEngine.sleepFor(0.01);
+ }
+ double actual = reader.read();
+ double expected = 1 + i - delayFrames;
+ if (expected < 0.0) {
+ expected = 0.0;
+ }
+ // System.out.printf( "[%d] expected = %7.3f, delayed = %7.3f\n", i, expected, actual );
+ // assertEquals("delayed output", expected, actual, 0.00001);
+ }
+ }
+
+ public void testSmall() throws InterruptedException {
+ checkInterpolatingDelay(40, 7.0);
+ }
+
+ public void testEven() throws InterruptedException {
+ checkInterpolatingDelay(44100, 13671.0);
+ }
+
+ public void testInterpolatingDelay() throws InterruptedException {
+ checkInterpolatingDelay(44100, 13671.4);
+ }
+}
diff --git a/tests/com/jsyn/unitgen/TestEnable.java b/tests/com/jsyn/unitgen/TestEnable.java
new file mode 100644
index 0000000..37a4a2b
--- /dev/null
+++ b/tests/com/jsyn/unitgen/TestEnable.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2009 Phil Burk, Mobileer Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.jsyn.unitgen;
+
+import junit.framework.TestCase;
+
+import com.jsyn.engine.SynthesisEngine;
+
+public class TestEnable extends TestCase {
+ SynthesisEngine synthesisEngine;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ synthesisEngine = new SynthesisEngine();
+ synthesisEngine.setRealTime(false);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ synthesisEngine.stop();
+ }
+
+ public void testEnablingGate() throws InterruptedException {
+ LinearRamp ramp = new LinearRamp();
+ synthesisEngine.add(ramp);
+ EnablingGate enabler = new EnablingGate();
+ synthesisEngine.add(enabler);
+ Add adder = new Add();
+ synthesisEngine.add(adder);
+
+ ramp.output.connect(enabler.input);
+ enabler.output.connect(adder.inputA);
+
+ // set up so ramp should equal time
+ ramp.current.set(0.0);
+ ramp.input.set(1.0);
+ ramp.time.set(1.0);
+ enabler.gate.set(1.0);
+
+ synthesisEngine.start();
+ double startTime = synthesisEngine.getCurrentTime();
+ // pull from final adder
+ adder.start();
+ synthesisEngine.sleepUntil(startTime + 0.1);
+ double tolerance = 0.002;
+ assertEquals("ramp going up", 0.1, ramp.output.getValue(), tolerance);
+ assertEquals("enabler going up", 0.1, enabler.output.getValue(), tolerance);
+ assertEquals("adder going up", 0.1, adder.output.getValue(), tolerance);
+ synthesisEngine.sleepUntil(startTime + 0.2);
+ assertEquals("start enabled", 0.2, adder.output.getValue(), tolerance);
+
+ // disable everything upstream
+ enabler.gate.set(0.0);
+
+ synthesisEngine.sleepUntil(startTime + 0.3);
+ assertEquals("should not be pulled", 0.2, ramp.output.getValue(), tolerance);
+ assertEquals("should be disabled", false, enabler.isEnabled());
+ assertEquals("should be zero", 0.0, enabler.output.getValue(), tolerance);
+ assertEquals("zero", 0.0, adder.output.getValue(), tolerance);
+
+ }
+}
diff --git a/tests/com/jsyn/unitgen/TestEnvelopeAttackDecay.java b/tests/com/jsyn/unitgen/TestEnvelopeAttackDecay.java
new file mode 100644
index 0000000..50ecb15
--- /dev/null
+++ b/tests/com/jsyn/unitgen/TestEnvelopeAttackDecay.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2009 Phil Burk, Mobileer Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.jsyn.unitgen;
+
+import com.jsyn.engine.SynthesisEngine;
+
+public class TestEnvelopeAttackDecay extends TestUnitGate {
+ double attackTime;
+ double decayTime;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ synthesisEngine = new SynthesisEngine();
+ synthesisEngine.setRealTime(false);
+ attackTime = 0.2;
+ decayTime = 0.4;
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ synthesisEngine.stop();
+ }
+
+ public void testOnOff() throws InterruptedException {
+ EnvelopeAttackDecay envelope = new EnvelopeAttackDecay();
+ synthesisEngine.add(envelope);
+
+ envelope.attack.set(0.1);
+ envelope.decay.set(0.2);
+
+ synthesisEngine.start();
+ envelope.start();
+ time = synthesisEngine.getCurrentTime();
+ synthesisEngine.sleepUntil(time + 0.1);
+ assertEquals("still idling", 0.0, envelope.output.getValue());
+
+ // Trigger the envelope using on/off
+ envelope.input.on();
+ time = synthesisEngine.getCurrentTime();
+ // Check end of attack cycle.
+ synthesisEngine.sleepUntil(time + 0.1);
+ assertTrue("at peak", (envelope.output.getValue() > 0.8));
+ envelope.input.off();
+ // Check end of decay cycle.
+ synthesisEngine.sleepUntil(time + 0.3);
+ assertTrue("at peak", (envelope.output.getValue() < 0.1));
+
+ synthesisEngine.sleepFor(0.1);
+
+ // Trigger the envelope using trigger()
+ envelope.input.trigger();
+ time = synthesisEngine.getCurrentTime();
+ // Check end of attack cycle.
+ synthesisEngine.sleepUntil(time + 0.1);
+ assertTrue("at peak", (envelope.output.getValue() > 0.8));
+ // Check end of decay cycle.
+ synthesisEngine.sleepUntil(time + 0.3);
+ assertTrue("at peak", (envelope.output.getValue() < 0.1));
+
+ }
+
+ public void testRetrigger() throws InterruptedException {
+ EnvelopeAttackDecay envelope = new EnvelopeAttackDecay();
+ synthesisEngine.add(envelope);
+
+ envelope.attack.set(0.1);
+ envelope.decay.set(0.2);
+
+ synthesisEngine.start();
+ envelope.start();
+ time = synthesisEngine.getCurrentTime();
+ synthesisEngine.sleepUntil(time + 0.1);
+ assertEquals("still idling", 0.0, envelope.output.getValue());
+
+ // Trigger the envelope using trigger()
+ envelope.input.trigger();
+ // Check end of attack cycle.
+ synthesisEngine.sleepFor(0.1);
+ assertEquals("at peak", 1.0, envelope.output.getValue(), 0.1);
+
+ // Decay half way.
+ synthesisEngine.sleepFor(0.1);
+ assertTrue("at peak", (envelope.output.getValue() < 0.7));
+
+ // Retrigger while decaying
+ envelope.input.trigger();
+ // Will get to top faster.
+ synthesisEngine.sleepFor(0.1);
+ assertEquals("at peak", 1.0, envelope.output.getValue(), 0.1);
+
+ // Check end of decay cycle.
+ synthesisEngine.sleepFor(0.2);
+ assertTrue("at peak", (envelope.output.getValue() < 0.1));
+
+ }
+
+ public void testAutoDisable() throws InterruptedException {
+
+ LinearRamp ramp = new LinearRamp();
+ synthesisEngine.add(ramp);
+ EnvelopeAttackDecay envelope = new EnvelopeAttackDecay();
+ envelope.attack.set(0.1);
+ envelope.decay.set(0.1);
+ synthesisEngine.add(envelope);
+ ramp.output.connect(envelope.amplitude);
+
+ checkAutoDisable(ramp, envelope);
+
+ }
+}
diff --git a/tests/com/jsyn/unitgen/TestEnvelopeDAHDSR.java b/tests/com/jsyn/unitgen/TestEnvelopeDAHDSR.java
new file mode 100644
index 0000000..8c781ac
--- /dev/null
+++ b/tests/com/jsyn/unitgen/TestEnvelopeDAHDSR.java
@@ -0,0 +1,339 @@
+/*
+ * Copyright 2009 Phil Burk, Mobileer Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.jsyn.unitgen;
+
+import com.jsyn.engine.SynthesisEngine;
+
+public class TestEnvelopeDAHDSR extends TestUnitGate {
+ double delayTime;
+ double attackTime;
+ double holdTime;
+ double decayTime;
+ double sustainLevel;
+ double releaseTime;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ synthesisEngine = new SynthesisEngine();
+ synthesisEngine.setRealTime(false);
+ delayTime = 0.1;
+ attackTime = 0.2;
+ holdTime = 0.3;
+ decayTime = 0.4;
+ sustainLevel = 0.5;
+ releaseTime = 0.6;
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ synthesisEngine.stop();
+ }
+
+ public void testStages() throws InterruptedException {
+ EnvelopeDAHDSR ramp = checkToSustain();
+
+ // Change sustain level to simulate tremolo sustain.
+ sustainLevel = 0.7;
+ ramp.sustain.set(sustainLevel);
+ time += 0.01;
+ synthesisEngine.sleepUntil(time);
+ assertEquals("sustain moving delaying", sustainLevel, ramp.output.getValue(), 0.01);
+
+ // Gate off to let envelope release.
+ ramp.input.set(0.0);
+ synthesisEngine.sleepUntil(time + (releaseTime * 0.1));
+ double releaseValue = ramp.output.getValue();
+ assertEquals("partway down release", sustainLevel * 0.36, releaseValue, 0.01);
+ }
+
+ private EnvelopeDAHDSR checkToSustain() throws InterruptedException {
+ EnvelopeDAHDSR ramp = new EnvelopeDAHDSR();
+ synthesisEngine.add(ramp);
+
+ ramp.delay.set(delayTime);
+ ramp.attack.set(attackTime);
+ ramp.hold.set(holdTime);
+ ramp.decay.set(decayTime);
+ ramp.sustain.set(sustainLevel);
+ ramp.release.set(releaseTime);
+
+ synthesisEngine.start();
+ ramp.start();
+ time = synthesisEngine.getCurrentTime();
+ synthesisEngine.sleepUntil(time + (2.0 * delayTime));
+ assertEquals("still idling", 0.0, ramp.output.getValue());
+
+ // Trigger the envelope.
+ ramp.input.set(1.0);
+ time = synthesisEngine.getCurrentTime();
+ // Check end of delay cycle.
+ synthesisEngine.sleepUntil(time + (delayTime * 0.9));
+ assertEquals("still delaying", 0.0, ramp.output.getValue(), 0.01);
+ // Half way up attack ramp.
+ synthesisEngine.sleepUntil(time + delayTime + (attackTime * 0.5));
+ assertEquals("half attack", 0.5, ramp.output.getValue(), 0.01);
+ // Holding after attack.
+ synthesisEngine.sleepUntil(time + delayTime + attackTime + (holdTime * 0.1));
+ assertEquals("holding", 1.0, ramp.output.getValue(), 0.01);
+ synthesisEngine.sleepUntil(time + delayTime + attackTime + (holdTime * 0.9));
+ assertEquals("still holding", 1.0, ramp.output.getValue(), 0.01);
+ synthesisEngine.sleepUntil(time + delayTime + attackTime + holdTime + decayTime);
+ time = synthesisEngine.getCurrentTime();
+ assertEquals("at sustain", sustainLevel, ramp.output.getValue(), 0.01);
+ return ramp;
+ }
+
+ public void testRetrigger() throws InterruptedException {
+ EnvelopeDAHDSR ramp = checkToSustain();
+
+ // Gate off to let envelope release.
+ ramp.input.set(0.0);
+ synthesisEngine.sleepUntil(time + (releaseTime * 0.1));
+ double releaseValue = ramp.output.getValue();
+ assertEquals("partway down release", sustainLevel * 0.36, releaseValue, 0.01);
+
+ // Retrigger during release phase.
+ time = synthesisEngine.getCurrentTime();
+ ramp.input.set(1.0);
+ // Check end of delay cycle.
+ synthesisEngine.sleepUntil(time + (delayTime * 0.9));
+ assertEquals("still delaying", releaseValue, ramp.output.getValue(), 0.01);
+ // Half way up attack ramp from where it started.
+ synthesisEngine.sleepUntil(time + delayTime + (attackTime * 0.5));
+ assertEquals("half attack", releaseValue + 0.5, ramp.output.getValue(), 0.01);
+
+ }
+
+ // I noticed a hang while playing with knobs.
+ public void testHang() throws InterruptedException {
+
+ delayTime = 0.0;
+ attackTime = 0.0;
+ holdTime = 0.0;
+ decayTime = 0.0;
+ sustainLevel = 0.3;
+ releaseTime = 3.0;
+
+ EnvelopeDAHDSR ramp = new EnvelopeDAHDSR();
+ synthesisEngine.add(ramp);
+
+ ramp.delay.set(delayTime);
+ ramp.attack.set(attackTime);
+ ramp.hold.set(holdTime);
+ ramp.decay.set(decayTime);
+ ramp.sustain.set(sustainLevel);
+ ramp.release.set(releaseTime);
+
+ synthesisEngine.start();
+ ramp.start();
+ // Trigger the envelope.
+ ramp.input.set(1.0);
+ time = synthesisEngine.getCurrentTime();
+ synthesisEngine.sleepUntil(time + 0.01);
+ assertEquals("should jump to sustain level", sustainLevel, ramp.output.getValue());
+
+ // Gate off to let envelope release.
+ ramp.input.set(0.0);
+ synthesisEngine.sleepUntil(time + 1.0);
+ double releaseValue = ramp.output.getValue();
+ assertTrue("partway down release", sustainLevel > releaseValue);
+
+ holdTime = 0.5;
+ ramp.hold.set(holdTime);
+ decayTime = 0.5;
+ ramp.decay.set(decayTime);
+
+ // Retrigger during release phase and try to catch it at top of hold
+ time = synthesisEngine.getCurrentTime();
+ ramp.input.set(1.0);
+ // Check end of delay cycle.
+ synthesisEngine.sleepUntil(time + (holdTime * 0.1));
+ assertEquals("should jump to hold", 1.0, ramp.output.getValue(), 0.01);
+ }
+
+ public void testNegative() throws InterruptedException {
+ delayTime = -0.1;
+ attackTime = -0.2;
+ holdTime = -0.3;
+ decayTime = -0.4;
+ sustainLevel = 0.3;
+ releaseTime = -0.5;
+
+ EnvelopeDAHDSR ramp = new EnvelopeDAHDSR();
+ synthesisEngine.add(ramp);
+
+ ramp.delay.set(delayTime);
+ ramp.attack.set(attackTime);
+ ramp.hold.set(holdTime);
+ ramp.decay.set(decayTime);
+ ramp.sustain.set(sustainLevel);
+ ramp.release.set(releaseTime);
+
+ synthesisEngine.start();
+ ramp.start();
+ // Trigger the envelope.
+ ramp.input.set(1.0);
+ time = synthesisEngine.getCurrentTime();
+ time += 0.1;
+ synthesisEngine.sleepUntil(time + 0.01);
+ assertEquals("should jump to sustain level", sustainLevel, ramp.output.getValue());
+
+ ramp.sustain.set(sustainLevel = -0.4);
+ time += 0.1;
+ synthesisEngine.sleepUntil(time);
+ assertEquals("sustain should clip at zero", sustainLevel, ramp.output.getValue());
+
+ ramp.sustain.set(sustainLevel = 0.4);
+ time += 0.1;
+ synthesisEngine.sleepUntil(time);
+ assertEquals("sustain should come back", sustainLevel, ramp.output.getValue());
+
+ // Gate off to let envelope release.
+ ramp.input.set(0.0);
+ time += 0.1;
+ synthesisEngine.sleepUntil(time);
+ double releaseValue = ramp.output.getValue();
+ assertEquals("release quickly", 0.0, releaseValue);
+ }
+
+ public void testOnOff() throws InterruptedException {
+ EnvelopeDAHDSR ramp = new EnvelopeDAHDSR();
+ synthesisEngine.add(ramp);
+
+ ramp.delay.set(0.0);
+ ramp.attack.set(0.1);
+ ramp.hold.set(0.0);
+ ramp.decay.set(0.0);
+ ramp.sustain.set(0.9);
+ ramp.release.set(0.1);
+
+ synthesisEngine.start();
+ ramp.start();
+ time = synthesisEngine.getCurrentTime();
+ synthesisEngine.sleepUntil(time + 0.2);
+ assertEquals("still idling", 0.0, ramp.output.getValue());
+
+ // Trigger the envelope.
+ ramp.input.on();
+ time = synthesisEngine.getCurrentTime();
+ // Check end of delay cycle.
+ synthesisEngine.sleepUntil(time + 0.2);
+ assertEquals("at sustain", 0.9, ramp.output.getValue(), 0.01);
+
+ // Release the envelope.
+ ramp.input.off();
+ time = synthesisEngine.getCurrentTime();
+ // Check end of delay cycle.
+ synthesisEngine.sleepUntil(time + 0.2);
+ assertEquals("after release", 0.0, ramp.output.getValue(), 0.01);
+ }
+
+ public void testAutoDisable() throws InterruptedException {
+
+ LinearRamp ramp = new LinearRamp();
+ synthesisEngine.add(ramp);
+ EnvelopeDAHDSR envelope = new EnvelopeDAHDSR();
+ synthesisEngine.add(envelope);
+ envelope.attack.set(0.1);
+ envelope.decay.set(0.1);
+ envelope.release.set(0.1);
+ envelope.sustain.set(0.1);
+ ramp.output.connect(envelope.amplitude);
+
+ checkAutoDisable(ramp, envelope);
+
+ }
+
+ class GatedRampCircuit extends Circuit {
+ LinearRamp ramp;
+ EnvelopeDAHDSR envelope;
+
+ GatedRampCircuit() {
+ add(ramp = new LinearRamp());
+ add(envelope = new EnvelopeDAHDSR());
+ envelope.attack.set(0.1);
+ envelope.decay.set(0.1);
+ envelope.release.set(0.1);
+ envelope.sustain.set(0.1);
+
+ envelope.setupAutoDisable(this);
+ ramp.output.connect(envelope.amplitude);
+ }
+ }
+
+ public void testAutoDisableCircuit() throws InterruptedException {
+ GatedRampCircuit circuit = new GatedRampCircuit();
+ synthesisEngine.add(circuit);
+ checkAutoDisable(circuit.ramp, circuit.envelope);
+ }
+
+ public void checkReleaseTiming(double releaseTime, double tolerance)
+ throws InterruptedException {
+ delayTime = 0.0;
+ attackTime = 0.2;
+ holdTime = 0.0;
+ decayTime = 10.0;
+ sustainLevel = 1.0;
+
+ EnvelopeDAHDSR ramp = new EnvelopeDAHDSR();
+ synthesisEngine.add(ramp);
+
+ ramp.delay.set(delayTime);
+ ramp.attack.set(attackTime);
+ ramp.hold.set(holdTime);
+ ramp.decay.set(decayTime);
+ ramp.sustain.set(sustainLevel);
+ ramp.release.set(releaseTime);
+
+ synthesisEngine.start();
+ ramp.start();
+ // Trigger the envelope.
+ ramp.input.set(1.0);
+ time = synthesisEngine.getCurrentTime();
+ time += attackTime * 2;
+ synthesisEngine.sleepUntil(time);
+ assertEquals("should be at to sustain level", sustainLevel, ramp.output.getValue());
+
+ // Start envelope release.
+ ramp.input.set(0.0);
+ final double db90 = 20.0 * Math.log(1.0 / 32768.0) / Math.log(10.0);
+ System.out.println("JSyns DB90 is actually " + db90);
+ int numSteps = 10;
+ for (int i = 0; i < 10; i++) {
+ time += releaseTime / numSteps;
+ synthesisEngine.sleepUntil(time);
+ double expectedDB = db90 * (i + 1) / numSteps;
+ double expectedAmplitude = sustainLevel * Math.pow(10.0, expectedDB / 20.0);
+ double releaseValue = ramp.output.getValue();
+ assertEquals("release " + i + " at", expectedAmplitude, releaseValue, tolerance);
+ }
+ time += releaseTime / numSteps;
+ synthesisEngine.sleepUntil(time);
+ double releaseValue = ramp.output.getValue();
+ assertEquals("env after release time should go to zero", 0.0, releaseValue, 0.0001);
+ }
+
+ public void testReleaseTiming() throws InterruptedException {
+ checkReleaseTiming(0.1, 0.004);
+ checkReleaseTiming(1.0, 0.002);
+ checkReleaseTiming(2.5, 0.001);
+ checkReleaseTiming(10.0, 0.001);
+ }
+
+}
diff --git a/tests/com/jsyn/unitgen/TestFunction.java b/tests/com/jsyn/unitgen/TestFunction.java
new file mode 100644
index 0000000..a8bfac0
--- /dev/null
+++ b/tests/com/jsyn/unitgen/TestFunction.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2009 Phil Burk, Mobileer Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.jsyn.unitgen;
+
+import junit.framework.TestCase;
+
+import com.jsyn.JSyn;
+import com.jsyn.Synthesizer;
+import com.jsyn.data.DoubleTable;
+import com.jsyn.data.Function;
+
+/**
+ * @author Phil Burk, (C) 2009 Mobileer Inc
+ */
+public class TestFunction extends TestCase {
+ Synthesizer synth;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ synth = JSyn.createSynthesizer();
+ synth.setRealTime(false);
+ synth.start();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ synth.stop();
+ }
+
+ public void testDoubleTable() {
+ double[] data = {
+ 2.0, 0.0, 3.0
+ };
+ DoubleTable table = new DoubleTable(data);
+ assertEquals("DoubleTable below", 2.0, table.evaluate(-1.4));
+ assertEquals("DoubleTable edge", 2.0, table.evaluate(-1.0));
+ assertEquals("DoubleTable mid", 1.0, table.evaluate(-0.5));
+ assertEquals("DoubleTable zero", 0.0, table.evaluate(0.0));
+ assertEquals("DoubleTable mid", 0.75, table.evaluate(0.25));
+ assertEquals("DoubleTable above", 3.0, table.evaluate(1.3));
+
+ }
+
+ public void testFunctionEvaluator() throws InterruptedException {
+ FunctionEvaluator shaper = new FunctionEvaluator();
+ synth.add(shaper);
+ shaper.start();
+
+ Function cuber = new Function() {
+ @Override
+ public double evaluate(double x) {
+ return x * x * x;
+ }
+ };
+ shaper.function.set(cuber);
+
+ shaper.input.set(0.5);
+ synth.sleepFor(0.001);
+
+ assertEquals("Cuber", (0.5 * 0.5 * 0.5), shaper.output.getValue());
+ }
+
+}
diff --git a/tests/com/jsyn/unitgen/TestMath.java b/tests/com/jsyn/unitgen/TestMath.java
new file mode 100644
index 0000000..0fde9b5
--- /dev/null
+++ b/tests/com/jsyn/unitgen/TestMath.java
@@ -0,0 +1,392 @@
+/*
+ * Copyright 2009 Phil Burk, Mobileer Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.jsyn.unitgen;
+
+import junit.framework.TestCase;
+
+import com.jsyn.engine.SynthesisEngine;
+
+/**
+ * @author Phil Burk, (C) 2009 Mobileer Inc
+ */
+public class TestMath extends TestCase {
+ SynthesisEngine synthesisEngine;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ synthesisEngine = new SynthesisEngine();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ public void testAdd() {
+ Add add = new Add();
+ add.setSynthesisEngine(synthesisEngine);
+
+ double x = 33.99;
+ double y = 8.31;
+ add.inputA.setValueInternal(x);
+ add.inputB.setValueInternal(y);
+
+ add.generate();
+
+ assertEquals("Add", x + y, add.output.getValue(), 0.001);
+ }
+
+ public void testPartialAdd() {
+ Add add = new Add();
+ add.setSynthesisEngine(synthesisEngine);
+
+ double x = 2.5;
+ double y = 9.7;
+ add.inputA.setValueInternal(x);
+ add.inputB.setValueInternal(y);
+
+ // Only generate a few values in the middle.
+ // This is to test low latency feedback loops.
+ // Only generate values for 2,3,4
+ add.generate(2, 5);
+
+ assertEquals("Add partial", 0.0, add.output.getValues()[0], 0.001);
+ assertEquals("Add partial", 0.0, add.output.getValues()[1], 0.001);
+ assertEquals("Add partial", x + y, add.output.getValues()[2], 0.001);
+ assertEquals("Add partial", x + y, add.output.getValues()[3], 0.001);
+ assertEquals("Add partial", x + y, add.output.getValues()[4], 0.001);
+ assertEquals("Add partial", 0.0, add.output.getValues()[5], 0.001);
+ assertEquals("Add partial", 0.0, add.output.getValues()[6], 0.001);
+ assertEquals("Add partial", 0.0, add.output.getValues()[7], 0.001);
+
+ }
+
+ /**
+ * Unit test for Subtract.java - added by Lisa Tolentino 06/17/2009
+ */
+ public void testSubtract() {
+ Subtract sub = new Subtract();
+ sub.setSynthesisEngine(synthesisEngine);
+
+ double x = 33.99;
+ double y = 8.31;
+ sub.inputA.setValueInternal(x);
+ sub.inputB.setValueInternal(y);
+
+ sub.generate();
+
+ assertEquals("Subtract", x - y, sub.output.getValue(), 0.001);
+ }
+
+ public void testPartialSubtract() {
+ Subtract sub = new Subtract();
+ sub.setSynthesisEngine(synthesisEngine);
+
+ double x = 2.5;
+ double y = 9.7;
+ sub.inputA.setValueInternal(x);
+ sub.inputB.setValueInternal(y);
+
+ // Only generate a few values in the middle.
+ // This is to test low latency feedback loops.
+ // Only generate values for 2,3,4
+ sub.generate(2, 5);
+
+ assertEquals("Subtract partial", 0.0, sub.output.getValues()[0], 0.001);
+ assertEquals("Subtract partial", 0.0, sub.output.getValues()[1], 0.001);
+ assertEquals("Subtract partial", x - y, sub.output.getValues()[2], 0.001);
+ assertEquals("Subtract partial", x - y, sub.output.getValues()[3], 0.001);
+ assertEquals("Subtract partial", x - y, sub.output.getValues()[4], 0.001);
+ assertEquals("Subtract partial", 0.0, sub.output.getValues()[5], 0.001);
+ assertEquals("Subtract partial", 0.0, sub.output.getValues()[6], 0.001);
+ assertEquals("Subtract partial", 0.0, sub.output.getValues()[7], 0.001);
+
+ }
+
+ /**
+ * Unit test for Multiply.java - added by Lisa Tolentino 06/19/2009
+ */
+ public void testMultiply() {
+ Multiply mult = new Multiply();
+ mult.setSynthesisEngine(synthesisEngine);
+
+ double x = 33.99;
+ double y = 8.31;
+ mult.inputA.setValueInternal(x);
+ mult.inputB.setValueInternal(y);
+
+ mult.generate();
+
+ assertEquals("Multiply", x * y, mult.output.getValue(), 0.001);
+ }
+
+ public void testPartialMultiply() {
+ Multiply mult = new Multiply();
+ mult.setSynthesisEngine(synthesisEngine);
+
+ double x = 2.5;
+ double y = 9.7;
+ mult.inputA.setValueInternal(x);
+ mult.inputB.setValueInternal(y);
+
+ // Only generate a few values in the middle.
+ // This is to test low latency feedback loops.
+ // Only generate values for 2,3,4
+ mult.generate(2, 5);
+
+ assertEquals("Multiply partial", 0.0, mult.output.getValues()[0], 0.001);
+ assertEquals("Multiply partial", 0.0, mult.output.getValues()[1], 0.001);
+ assertEquals("Multiply partial", x * y, mult.output.getValues()[2], 0.001);
+ assertEquals("Multiply partial", x * y, mult.output.getValues()[3], 0.001);
+ assertEquals("Multiply partial", x * y, mult.output.getValues()[4], 0.001);
+ assertEquals("Multiply partial", 0.0, mult.output.getValues()[5], 0.001);
+ assertEquals("Multiply partial", 0.0, mult.output.getValues()[6], 0.001);
+ assertEquals("Multiply partial", 0.0, mult.output.getValues()[7], 0.001);
+
+ }
+
+ /**
+ * Unit test for Divide.java - added by Lisa Tolentino 06/19/2009
+ */
+ public void testDivide() {
+ Divide divide = new Divide();
+ divide.setSynthesisEngine(synthesisEngine);
+
+ double x = 33.99;
+ double y = 8.31;
+ divide.inputA.setValueInternal(x);
+ divide.inputB.setValueInternal(y);
+
+ divide.generate();
+
+ assertEquals("Divide", x / y, divide.output.getValue(), 0.001);
+ }
+
+ public void testPartialDivide() {
+ Divide divide = new Divide();
+ divide.setSynthesisEngine(synthesisEngine);
+
+ double x = 2.5;
+ double y = 9.7;
+ divide.inputA.setValueInternal(x);
+ divide.inputB.setValueInternal(y);
+
+ // Only generate a few values in the middle.
+ // This is to test low latency feedback loops.
+ // Only generate values for 2,3,4
+ divide.generate(2, 5);
+
+ assertEquals("Divide partial", 0.0, divide.output.getValues()[0], 0.001);
+ assertEquals("Divide partial", 0.0, divide.output.getValues()[1], 0.001);
+ assertEquals("Divide partial", x / y, divide.output.getValues()[2], 0.001);
+ assertEquals("Divide partial", x / y, divide.output.getValues()[3], 0.001);
+ assertEquals("Divide partial", x / y, divide.output.getValues()[4], 0.001);
+ assertEquals("Divide partial", 0.0, divide.output.getValues()[5], 0.001);
+ assertEquals("Divide partial", 0.0, divide.output.getValues()[6], 0.001);
+ assertEquals("Divide partial", 0.0, divide.output.getValues()[7], 0.001);
+
+ }
+
+ /**
+ * Unit test for MultiplyAdd.java - added by Lisa Tolentino 06/19/2009
+ */
+ public void testMultiplyAdd() {
+ MultiplyAdd multAdd = new MultiplyAdd();
+ multAdd.setSynthesisEngine(synthesisEngine);
+
+ double x = 33.99;
+ double y = 8.31;
+ double z = 2.28;
+ multAdd.inputA.setValueInternal(x);
+ multAdd.inputB.setValueInternal(y);
+ multAdd.inputC.setValueInternal(z);
+
+ multAdd.generate();
+
+ assertEquals("MultiplyAdd", (x * y) + z, multAdd.output.getValue(), 0.001);
+ }
+
+ public void testPartialMultiplyAdd() {
+ MultiplyAdd multAdd = new MultiplyAdd();
+ multAdd.setSynthesisEngine(synthesisEngine);
+
+ double x = 33.99;
+ double y = 8.31;
+ double z = 2.28;
+ multAdd.inputA.setValueInternal(x);
+ multAdd.inputB.setValueInternal(y);
+ multAdd.inputC.setValueInternal(z);
+
+ // Only generate a few values in the middle.
+ // This is to test low latency feedback loops.
+ // Only generate values for 2,3,4
+ multAdd.generate(2, 5);
+
+ assertEquals("MultiplyAdd partial", 0.0, multAdd.output.getValues()[0], 0.001);
+ assertEquals("MultiplyAdd partial", 0.0, multAdd.output.getValues()[1], 0.001);
+ assertEquals("MultiplyAdd partial", (x * y) + z, multAdd.output.getValues()[2], 0.001);
+ assertEquals("MultiplyAdd partial", (x * y) + z, multAdd.output.getValues()[3], 0.001);
+ assertEquals("MultiplyAdd partial", (x * y) + z, multAdd.output.getValues()[4], 0.001);
+ assertEquals("MultiplyAdd partial", 0.0, multAdd.output.getValues()[5], 0.001);
+ assertEquals("MultiplyAdd partial", 0.0, multAdd.output.getValues()[6], 0.001);
+ assertEquals("MultiplyAdd partial", 0.0, multAdd.output.getValues()[7], 0.001);
+
+ }
+
+ /**
+ * Unit test for Compare.java - added by Lisa Tolentino 06/19/2009
+ */
+ public void testCompare() {
+ UnitBinaryOperator compare = new Compare();
+ compare.setSynthesisEngine(synthesisEngine);
+
+ double x = 33.99;
+ double y = 8.31;
+ compare.inputA.setValueInternal(x);
+ compare.inputB.setValueInternal(y);
+
+ compare.generate();
+
+ assertEquals("Compare", (x > y ? 1 : 0), compare.output.getValue(), 0.001);
+ }
+
+ public void testPartialCompare() {
+ UnitBinaryOperator compare = new Compare();
+ compare.setSynthesisEngine(synthesisEngine);
+
+ double x = 33.99;
+ double y = 8.31;
+ compare.inputA.setValueInternal(x);
+ compare.inputB.setValueInternal(y);
+
+ // Only generate a few values in the middle.
+ // This is to test low latency feedback loops.
+ // Only generate values for 2,3,4
+ compare.generate(2, 5);
+
+ assertEquals("Compare partial", 0.0, compare.output.getValues()[0], 0.001);
+ assertEquals("Compare partial", 0.0, compare.output.getValues()[1], 0.001);
+ assertEquals("Compare partial", (x > y ? 1 : 0), compare.output.getValues()[2], 0.001);
+ assertEquals("Compare partial", (x > y ? 1 : 0), compare.output.getValues()[3], 0.001);
+ assertEquals("Compare partial", (x > y ? 1 : 0), compare.output.getValues()[4], 0.001);
+ assertEquals("Compare partial", 0.0, compare.output.getValues()[5], 0.001);
+ assertEquals("Compare partial", 0.0, compare.output.getValues()[6], 0.001);
+ assertEquals("Compare partial", 0.0, compare.output.getValues()[7], 0.001);
+
+ }
+
+ /**
+ * Unit test for Maximum.java - added by Lisa Tolentino 06/20/2009
+ */
+ public void testMaximum() {
+ Maximum max = new Maximum();
+ max.setSynthesisEngine(synthesisEngine);
+
+ double x = 33.99;
+ double y = 8.31;
+ max.inputA.setValueInternal(x);
+ max.inputB.setValueInternal(y);
+
+ max.generate();
+
+ assertEquals("Maximum", (x > y ? x : y), max.output.getValue(), 0.001);
+ }
+
+ public void testPartialMaximum() {
+ Maximum max = new Maximum();
+ max.setSynthesisEngine(synthesisEngine);
+
+ double x = 33.99;
+ double y = 8.31;
+ max.inputA.setValueInternal(x);
+ max.inputB.setValueInternal(y);
+
+ // Only generate a few values in the middle.
+ // This is to test low latency feedback loops.
+ // Only generate values for 2,3,4
+ max.generate(2, 5);
+
+ assertEquals("Maximum partial", 0.0, max.output.getValues()[0], 0.001);
+ assertEquals("Maximum partial", 0.0, max.output.getValues()[1], 0.001);
+ assertEquals("Maximum partial", (x > y ? x : y), max.output.getValues()[2], 0.001);
+ assertEquals("Maximum partial", (x > y ? x : y), max.output.getValues()[3], 0.001);
+ assertEquals("Maximum partial", (x > y ? x : y), max.output.getValues()[4], 0.001);
+ assertEquals("Maximum partial", 0.0, max.output.getValues()[5], 0.001);
+ assertEquals("Maximum partial", 0.0, max.output.getValues()[6], 0.001);
+ assertEquals("Maximum partial", 0.0, max.output.getValues()[7], 0.001);
+
+ }
+
+ /**
+ * Unit test for Minimum.java - added by Lisa Tolentino 06/20/2009
+ */
+ public void testMinimum() {
+ Minimum min = new Minimum();
+ min.setSynthesisEngine(synthesisEngine);
+
+ double x = 33.99;
+ double y = 8.31;
+ min.inputA.setValueInternal(x);
+ min.inputB.setValueInternal(y);
+
+ min.generate();
+
+ assertEquals("Minimum", (x < y ? x : y), min.output.getValue(), 0.001);
+ }
+
+ public void testPartialMinimum() {
+ Minimum min = new Minimum();
+ min.setSynthesisEngine(synthesisEngine);
+
+ double x = 33.99;
+ double y = 8.31;
+ min.inputA.setValueInternal(x);
+ min.inputB.setValueInternal(y);
+
+ // Only generate a few values in the middle.
+ // This is to test low latency feedback loops.
+ // Only generate values for 2,3,4
+ min.generate(2, 5);
+
+ assertEquals("Maximum partial", 0.0, min.output.getValues()[0], 0.001);
+ assertEquals("Maximum partial", 0.0, min.output.getValues()[1], 0.001);
+ assertEquals("Maximum partial", (x < y ? x : y), min.output.getValues()[2], 0.001);
+ assertEquals("Maximum partial", (x < y ? x : y), min.output.getValues()[3], 0.001);
+ assertEquals("Maximum partial", (x < y ? x : y), min.output.getValues()[4], 0.001);
+ assertEquals("Maximum partial", 0.0, min.output.getValues()[5], 0.001);
+ assertEquals("Maximum partial", 0.0, min.output.getValues()[6], 0.001);
+ assertEquals("Maximum partial", 0.0, min.output.getValues()[7], 0.001);
+
+ }
+
+ public void testPowerOfTwo() {
+ PowerOfTwo powerOfTwo = new PowerOfTwo();
+ powerOfTwo.setSynthesisEngine(synthesisEngine);
+ final double smallValue = -1.5308084989341915E-17;
+ double values[] = {
+ 0.0, 1.3, 4.5, -0.5, -1.0, -2.8, smallValue, -smallValue, 1.0 - smallValue,
+ 1.0 + smallValue
+ };
+ for (double in : values) {
+ powerOfTwo.input.setValueInternal(in);
+ powerOfTwo.generate();
+ assertEquals("PowerOfTwo", Math.pow(2.0, in), powerOfTwo.output.getValue(), 0.001);
+ }
+ }
+
+}
diff --git a/tests/com/jsyn/unitgen/TestRamps.java b/tests/com/jsyn/unitgen/TestRamps.java
new file mode 100644
index 0000000..83ebacf
--- /dev/null
+++ b/tests/com/jsyn/unitgen/TestRamps.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright 2009 Phil Burk, Mobileer Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.jsyn.unitgen;
+
+public class TestRamps extends NonRealTimeTestCase {
+
+ public void viewContinuousRamp(double duration, double startValue, double targetValue)
+ throws InterruptedException {
+ ContinuousRamp ramp = new ContinuousRamp();
+ synthesisEngine.add(ramp);
+
+ ramp.current.set(startValue);
+ ramp.input.set(startValue);
+ ramp.time.set(duration);
+
+ synthesisEngine.setRealTime(false);
+ synthesisEngine.start();
+ ramp.start();
+ synthesisEngine.sleepUntil(synthesisEngine.getCurrentTime() + 0.01);
+ ramp.input.set(targetValue);
+
+ double time = synthesisEngine.getCurrentTime();
+ int numLoops = 20;
+ double increment = duration / numLoops;
+ for (int i = 0; i < (numLoops + 1); i++) {
+ double value = ramp.output.getValue();
+ System.out.printf("i = %d, t = %9.5f, value = %8.4f\n", i, time, value);
+ time += increment;
+ synthesisEngine.sleepUntil(time);
+ }
+
+ synthesisEngine.stop();
+ }
+
+ public void checkContinuousRamp(double duration, double startValue, double targetValue)
+ throws InterruptedException {
+ ContinuousRamp ramp = new ContinuousRamp();
+ synthesisEngine.add(ramp);
+
+ ramp.current.set(startValue);
+ ramp.input.set(startValue);
+ ramp.time.set(duration);
+
+ synthesisEngine.setRealTime(false);
+ synthesisEngine.start();
+ ramp.start();
+ synthesisEngine.sleepUntil(synthesisEngine.getCurrentTime() + 0.01);
+ assertEquals("start flat", ramp.input.getValue(), ramp.output.getValue());
+
+ ramp.input.set(targetValue);
+ double startTime = synthesisEngine.getCurrentTime();
+ synthesisEngine.sleepUntil(startTime + (duration / 2));
+ assertEquals("ramping up", (targetValue + startValue) / 2.0, ramp.output.getValue(), 0.01);
+ synthesisEngine.sleepUntil(startTime + duration);
+ assertEquals("ramping up", targetValue, ramp.output.getValue(), 0.01);
+ synthesisEngine.sleepUntil(startTime + duration + 0.1);
+ assertEquals("flat again", targetValue, ramp.output.getValue());
+
+ synthesisEngine.stop();
+ }
+
+ public void testContinuousRamp() throws InterruptedException {
+ viewContinuousRamp(4.0, 0.0, 1.0);
+ }
+
+ public void testExponentialRamp() throws InterruptedException {
+ ExponentialRamp ramp = new ExponentialRamp();
+ synthesisEngine.add(ramp);
+
+ double duration = 0.3;
+ ramp.current.set(1.0);
+ ramp.input.set(1.0);
+ ramp.time.set(duration);
+
+ synthesisEngine.start();
+ ramp.start();
+ synthesisEngine.sleepUntil(synthesisEngine.getCurrentTime() + 0.01);
+ assertEquals("start flat", ramp.input.getValue(), ramp.output.getValue());
+
+ ramp.input.set(8.0);
+ double startTime = synthesisEngine.getCurrentTime();
+ synthesisEngine.sleepUntil(startTime + 0.1);
+ assertEquals("ramping up", 2.0, ramp.output.getValue(), 0.01);
+ synthesisEngine.sleepUntil(startTime + 0.2);
+ assertEquals("ramping up", 4.0, ramp.output.getValue(), 0.01);
+ synthesisEngine.sleepUntil(startTime + 0.3);
+ assertEquals("ramping up", 8.0, ramp.output.getValue(), 0.01);
+ synthesisEngine.sleepUntil(startTime + 0.4);
+ assertEquals("flat again", 8.0, ramp.output.getValue());
+ }
+
+ public void testLinearRamp() throws InterruptedException {
+ LinearRamp ramp = new LinearRamp();
+ synthesisEngine.add(ramp);
+
+ double duration = 0.4;
+ ramp.current.set(0.0);
+ ramp.input.set(0.0);
+ ramp.time.set(duration);
+
+ synthesisEngine.start();
+ ramp.start();
+ synthesisEngine.sleepUntil(synthesisEngine.getCurrentTime() + 0.01);
+ assertEquals("start flat", ramp.input.getValue(), ramp.output.getValue());
+
+ ramp.input.set(8.0);
+ double startTime = synthesisEngine.getCurrentTime();
+ synthesisEngine.sleepUntil(startTime + 0.1);
+ assertEquals("ramping up", 2.0, ramp.output.getValue(), 0.01);
+ synthesisEngine.sleepUntil(startTime + 0.2);
+ assertEquals("ramping up", 4.0, ramp.output.getValue(), 0.01);
+ synthesisEngine.sleepUntil(startTime + 0.3);
+ assertEquals("ramping up", 6.0, ramp.output.getValue(), 0.01);
+ synthesisEngine.sleepUntil(startTime + 0.4);
+ assertEquals("flat again", 8.0, ramp.output.getValue());
+ }
+
+ public void testExponentialRampConnected() throws InterruptedException {
+ ExponentialRamp ramp = new ExponentialRamp();
+ PassThrough pass = new PassThrough();
+ synthesisEngine.add(ramp);
+ synthesisEngine.add(pass);
+
+ double duration = 0.3;
+ ramp.current.set(1.0);
+ pass.input.set(1.0);
+ ramp.time.set(duration);
+
+ // Send value through a connected unit.
+ pass.input.set(1.0);
+ pass.output.connect(ramp.input);
+
+ synthesisEngine.start();
+ ramp.start();
+ synthesisEngine.sleepUntil(synthesisEngine.getCurrentTime() + 0.01);
+ assertEquals("start flat", ramp.input.getValue(), ramp.output.getValue());
+
+ pass.input.set(8.0);
+ double startTime = synthesisEngine.getCurrentTime();
+ synthesisEngine.sleepUntil(startTime + 0.1);
+ assertEquals("ramping up", 2.0, ramp.output.getValue(), 0.01);
+ synthesisEngine.sleepUntil(startTime + 0.2);
+ assertEquals("ramping up", 4.0, ramp.output.getValue(), 0.01);
+ synthesisEngine.sleepUntil(startTime + 0.3);
+ assertEquals("ramping up", 8.0, ramp.output.getValue(), 0.01);
+ synthesisEngine.sleepUntil(startTime + 0.4);
+ assertEquals("flat again", 8.0, ramp.output.getValue());
+ }
+
+ public void testLinearRampConnected() throws InterruptedException {
+ LinearRamp ramp = new LinearRamp();
+ PassThrough pass = new PassThrough();
+ synthesisEngine.add(ramp);
+ synthesisEngine.add(pass);
+
+ double duration = 0.4;
+ ramp.current.set(0.0);
+ pass.input.set(0.0);
+ ramp.time.set(duration);
+
+ // Send value through a connected unit.
+ pass.input.set(0.0);
+ pass.output.connect(ramp.input);
+
+ synthesisEngine.start();
+ ramp.start();
+ synthesisEngine.sleepUntil(synthesisEngine.getCurrentTime() + 0.01);
+ assertEquals("start flat", ramp.input.getValue(), ramp.output.getValue());
+
+ pass.input.set(8.0);
+ double startTime = synthesisEngine.getCurrentTime();
+ synthesisEngine.sleepUntil(startTime + 0.1);
+ assertEquals("ramping up", 2.0, ramp.output.getValue(), 0.01);
+ synthesisEngine.sleepUntil(startTime + 0.2);
+ assertEquals("ramping up", 4.0, ramp.output.getValue(), 0.01);
+ synthesisEngine.sleepUntil(startTime + 0.3);
+ assertEquals("ramping up", 6.0, ramp.output.getValue(), 0.01);
+ synthesisEngine.sleepUntil(startTime + 0.4);
+ assertEquals("flat again", 8.0, ramp.output.getValue());
+ }
+
+}
diff --git a/tests/com/jsyn/unitgen/TestUnitGate.java b/tests/com/jsyn/unitgen/TestUnitGate.java
new file mode 100644
index 0000000..14129aa
--- /dev/null
+++ b/tests/com/jsyn/unitgen/TestUnitGate.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2009 Phil Burk, Mobileer Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.jsyn.unitgen;
+
+import junit.framework.TestCase;
+
+import com.jsyn.engine.SynthesisEngine;
+
+public class TestUnitGate extends TestCase {
+
+ protected SynthesisEngine synthesisEngine;
+ protected double time;
+
+ public void checkAutoDisable(LinearRamp ramp, UnitGate envelope) throws InterruptedException {
+ double tolerance = 0.01;
+ Add adder = new Add();
+ synthesisEngine.add(adder);
+
+ envelope.output.connect(adder.inputA);
+ if (ramp.getCircuit() != null) {
+ ramp.output.connect(adder.inputB);
+ }
+
+ envelope.input.setAutoDisableEnabled(true);
+ envelope.setEnabled(false);
+
+ // set up so ramp value should equal time
+ ramp.current.set(0.0);
+ ramp.input.set(1.0);
+ ramp.time.set(1.0);
+
+ synthesisEngine.start();
+ // pull from final adder
+ adder.start();
+
+ time = synthesisEngine.getCurrentTime();
+ time += 0.1;
+ synthesisEngine.sleepUntil(time);
+ assertEquals("still idling", 0.0, envelope.output.getValue());
+ assertEquals("ramp frozen at beginning", 0.0, ramp.output.getValue(), tolerance);
+
+ // run multiple times to make sure we can retrigger the envelope.
+ for (int i = 0; i < 3; i++) {
+ double level = ramp.output.getValue();
+ // Trigger the envelope using trigger()
+ envelope.input.on();
+ time += 0.1;
+ level += 0.1;
+ synthesisEngine.sleepUntil(time);
+ assertEquals("ramp going up " + i, level, ramp.output.getValue(), tolerance);
+ assertTrue("enabled at peak", envelope.isEnabled());
+
+ envelope.input.off();
+ time += 0.1;
+ level += 0.1;
+ synthesisEngine.sleepUntil(time);
+ assertEquals("ramp going up more " + i, level, ramp.output.getValue(), tolerance);
+ assertEquals("at bottom", 0.0, envelope.output.getValue(), 0.1);
+
+ time += 0.2;
+ synthesisEngine.sleepUntil(time);
+ assertEquals("ramp frozen " + i, level, ramp.output.getValue(), tolerance);
+ }
+ }
+
+}
diff --git a/tests/com/jsyn/util/DebugSampleLoader.java b/tests/com/jsyn/util/DebugSampleLoader.java
new file mode 100644
index 0000000..23945b5
--- /dev/null
+++ b/tests/com/jsyn/util/DebugSampleLoader.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2010 Phil Burk, Mobileer Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.jsyn.util;
+
+import java.io.File;
+import java.io.IOException;
+
+import com.jsyn.JSyn;
+import com.jsyn.Synthesizer;
+import com.jsyn.data.FloatSample;
+import com.jsyn.unitgen.LineOut;
+import com.jsyn.unitgen.VariableRateDataReader;
+import com.jsyn.unitgen.VariableRateMonoReader;
+import com.jsyn.unitgen.VariableRateStereoReader;
+
+/**
+ * Play a sample from a WAV file using JSyn.
+ *
+ * @author Phil Burk (C) 2010 Mobileer Inc
+ */
+public class DebugSampleLoader {
+ private Synthesizer synth;
+ private VariableRateDataReader samplePlayer;
+ private LineOut lineOut;
+
+ private void test() throws IOException {
+ // File sampleFile = new File("samples/cello_markers.wav");
+ // File sampleFile = new File("samples/Piano_A440_PT.aif");
+ File sampleFile = new File("samples/sine_400_loop_i16.wav");
+ // File sampleFile = new File("samples/TwoDiffPitchedSines_F32_PT.wav");
+ // File sampleFile = new File("samples/sine_400_u8.aif");
+ // File sampleFile = new File("samples/sine_400_s8.aif");
+ // File sampleFile = new File("samples/sine_400_ulaw.aif");
+ // File sampleFile = new File("samples/sine_400_ulaw.wav");
+
+ // File sampleFile = new File("samples/aaClarinet.wav");
+ // File sampleFile = new File("samples/sine_400_mono.wav");
+ // File sampleFile = new File("samples/sine_200_300_i16.wav");
+ // File sampleFile = new File("samples/sine_200_300_i24.wav");
+ // File sampleFile = new File("samples/M1F1-int16-AFsp.wav");
+ // File sampleFile = new File("samples/M1F1-int24-AFsp.wav");
+ // File sampleFile = new File("samples/M1F1-float32-AFsp.wav");
+ // File sampleFile = new File("samples/M1F1-int16WE-AFsp.wav");
+ // File sampleFile = new File("samples/M1F1-int24WE-AFsp.wav");
+ // File sampleFile = new File("samples/M1F1-float32WE-AFsp.wav");
+ // File sampleFile = new File("samples/sine_200_300_i16.aif");
+ // File sampleFile = new File("samples/sine_200_300_f32.wavex");
+ // File sampleFile = new File("samples/Sine32bit.aif");
+ // File sampleFile = new File("samples/Sine32bit.wav");
+ // File sampleFile = new File("samples/smartCue.wav");
+
+ // URL sampleFile = new URL("http://www.softsynth.com/samples/Clarinet.wav");
+
+ synth = JSyn.createSynthesizer();
+
+ FloatSample sample;
+ try {
+ // Add an output mixer.
+ synth.add(lineOut = new LineOut());
+
+ // Load the sample and display its properties.
+ SampleLoader.setJavaSoundPreferred(false);
+ sample = SampleLoader.loadFloatSample(sampleFile);
+ System.out.println("Sample has: channels = " + sample.getChannelsPerFrame());
+ System.out.println(" frames = " + sample.getNumFrames());
+ System.out.println(" rate = " + sample.getFrameRate());
+ System.out.println(" loopStart = " + sample.getSustainBegin());
+ System.out.println(" loopEnd = " + sample.getSustainEnd());
+
+ if (sample.getChannelsPerFrame() == 1) {
+ synth.add(samplePlayer = new VariableRateMonoReader());
+ samplePlayer.output.connect(0, lineOut.input, 0);
+ } else if (sample.getChannelsPerFrame() == 2) {
+ synth.add(samplePlayer = new VariableRateStereoReader());
+ samplePlayer.output.connect(0, lineOut.input, 0);
+ samplePlayer.output.connect(1, lineOut.input, 1);
+ } else {
+ throw new RuntimeException("Can only play mono or stereo samples.");
+ }
+
+ // Start synthesizer using default stereo output at 44100 Hz.
+ synth.start();
+
+ samplePlayer.rate.set(sample.getFrameRate());
+
+ // We only need to start the LineOut. It will pull data from the
+ // sample player.
+ lineOut.start();
+
+ // We can simply queue the entire file.
+ // Or if it has a loop we can play the loop for a while.
+ if (sample.getSustainBegin() < 0) {
+ System.out.println("queue the sample");
+ samplePlayer.dataQueue.queue(sample);
+ } else {
+ System.out.println("queueOn the sample");
+ samplePlayer.dataQueue.queueOn(sample);
+ synth.sleepFor(8.0);
+ System.out.println("queueOff the sample");
+ samplePlayer.dataQueue.queueOff(sample);
+ }
+
+ // Wait until the sample has finished playing.
+ do {
+ synth.sleepFor(1.0);
+ } while (samplePlayer.dataQueue.hasMore());
+
+ } catch (IOException e1) {
+ e1.printStackTrace();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ // Stop everything.
+ synth.stop();
+ }
+
+ public static void main(String[] args) {
+ try {
+ new DebugSampleLoader().test();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/tests/com/jsyn/util/TestFFT.java b/tests/com/jsyn/util/TestFFT.java
new file mode 100644
index 0000000..f7fcce6
--- /dev/null
+++ b/tests/com/jsyn/util/TestFFT.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2009 Phil Burk, Mobileer Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.jsyn.util;
+
+import junit.framework.TestCase;
+
+import com.softsynth.math.FourierMath;
+
+public class TestFFT extends TestCase {
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ public void checkSingleSine(int size, int bin) {
+ double[] ar = new double[size];
+ double[] ai = new double[size];
+ double[] magnitudes = new double[size];
+
+ double amplitude = 1.0;
+ addSineWave(size, bin, ar, amplitude);
+
+ FourierMath.transform(1, size, ar, ai);
+ FourierMath.calculateMagnitudes(ar, ai, magnitudes);
+
+ assertTrue(magnitudes[bin - 1] < 0.001);
+ assertTrue(magnitudes[bin] > 0.5);
+ assertTrue(magnitudes[bin + 1] < 0.001);
+
+ }
+
+ private void addSineWave(int size, int bin, double[] ar, double amplitude) {
+ double phase = 0.0;
+ double phaseIncrement = 2.0 * Math.PI * bin / size;
+ for (int i = 0; i < size; i++) {
+ ar[i] += Math.sin(phase) * amplitude;
+ // System.out.println( i + " = " + ar[i] );
+ phase += phaseIncrement;
+ }
+ }
+
+ public void testSingles() {
+ checkSingleSine(32, 1);
+ checkSingleSine(32, 2);
+ checkSingleSine(64, 5);
+ checkSingleSine(256, 3);
+ }
+
+ public void checkInverseFFT(int size, int bin) {
+ double[] ar1 = new double[size];
+ double[] ai1 = new double[size];
+ double[] ar2 = new double[size];
+ double[] ai2 = new double[size];
+
+ double amplitude = 1.0;
+ addSineWave(size, bin, ar1, amplitude);
+
+ // Save a copy of the source.
+ System.arraycopy(ar1, 0, ar2, 0, size);
+ System.arraycopy(ai1, 0, ai2, 0, size);
+
+ FourierMath.transform(1, size, ar1, ai1); // FFT
+
+ FourierMath.transform(-1, size, ar1, ai1); // IFFT
+
+ for (int i = 0; i < size; i++) {
+ assertEquals(ar2[i], ar1[i], 0.00001);
+ assertEquals(ai2[i], ai1[i], 0.00001);
+ }
+ }
+
+ public void testInverse() {
+ checkInverseFFT(32, 1);
+ checkInverseFFT(32, 2);
+ checkInverseFFT(128, 17);
+ checkInverseFFT(512, 23);
+ }
+}
diff --git a/tests/com/jsyn/util/TestPseudoRandom.java b/tests/com/jsyn/util/TestPseudoRandom.java
new file mode 100644
index 0000000..0ef2fa3
--- /dev/null
+++ b/tests/com/jsyn/util/TestPseudoRandom.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2009 Phil Burk, Mobileer Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.jsyn.util;
+
+import junit.framework.TestCase;
+
+public class TestPseudoRandom extends TestCase {
+ PseudoRandom pseudoRandom;
+ private int[] bins;
+ private final static int BIN_SHIFTER = 8;
+ private final static int BIN_COUNT = 1 << BIN_SHIFTER;
+ private final static int BIN_MASK = BIN_COUNT - 1;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ pseudoRandom = new PseudoRandom();
+ bins = new int[BIN_COUNT];
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ public void testMath() {
+ long seed = 3964771111L;
+ int positiveInt = (int) (seed & 0x7FFFFFFF);
+ assertTrue("masked random positive, " + positiveInt, (positiveInt >= 0));
+ double rand = positiveInt * (1.0 / (1L << 31));
+ assertTrue("not too low, " + rand, (rand >= 0.0));
+ assertTrue("not too high, " + rand, (rand < 1.0));
+ }
+
+ public void testIntegerDistribution() {
+ int scaler = 10;
+ for (int i = 0; i < (bins.length * scaler); i++) {
+ int rand = pseudoRandom.nextRandomInteger();
+ int positiveInt = rand & 0x7FFFFFFF;
+ assertTrue("masked random " + positiveInt, (positiveInt >= 0));
+ int index = (rand >> (32 - BIN_SHIFTER)) & BIN_MASK;
+ bins[index] += 1;
+ }
+ checkDistribution(scaler);
+ }
+
+ public void test01Distribution() {
+ int scaler = 10;
+ for (int i = 0; i < (bins.length * scaler); i++) {
+ double rand = pseudoRandom.random();
+ assertTrue("not too low, #" + i + " = " + rand, (rand >= 0.0));
+ assertTrue("not too high, #" + i + " = " + rand, (rand < 1.0));
+ int index = (int) (rand * BIN_COUNT);
+ bins[index] += 1;
+ }
+ checkDistribution(scaler);
+ }
+
+ private void checkDistribution(int scaler) {
+ // Generate running average that should stay near scaler
+ double average = scaler;
+ double coefficient = 0.9;
+ for (int i = 0; i < (bins.length); i++) {
+ average = (average * coefficient) + (bins[i] * (1.0 - coefficient));
+ assertEquals("average at " + i, scaler, average, 0.2 * scaler);
+ }
+ }
+}
diff --git a/tests/com/jsyn/util/TestVoiceAllocator.java b/tests/com/jsyn/util/TestVoiceAllocator.java
new file mode 100644
index 0000000..fd5ba0b
--- /dev/null
+++ b/tests/com/jsyn/util/TestVoiceAllocator.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2009 Phil Burk, Mobileer Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.jsyn.util;
+
+import junit.framework.TestCase;
+
+import com.jsyn.instruments.SubtractiveSynthVoice;
+import com.jsyn.unitgen.UnitVoice;
+
+public class TestVoiceAllocator extends TestCase {
+ VoiceAllocator allocator;
+ int max = 4;
+ private UnitVoice[] voices;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ voices = new UnitVoice[max];
+ for (int i = 0; i < max; i++) {
+ voices[i] = new SubtractiveSynthVoice();
+ }
+
+ allocator = new VoiceAllocator(voices);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ public void testAllocation() {
+ assertEquals("get max", max, allocator.getVoiceCount());
+
+ int tag1 = 61;
+ int tag2 = 62;
+ int tag3 = 63;
+ int tag4 = 64;
+ int tag5 = 65;
+ int tag6 = 66;
+ UnitVoice voice1 = allocator.allocate(tag1);
+ assertTrue("voice should be non-null", (voice1 != null));
+
+ UnitVoice voice2 = allocator.allocate(tag2);
+ assertTrue("voice should be non-null", (voice2 != null));
+ assertTrue("new voice ", (voice2 != voice1));
+
+ UnitVoice voice = allocator.allocate(tag1);
+ assertTrue("should be voice1 again ", (voice == voice1));
+
+ voice = allocator.allocate(tag2);
+ assertTrue("should be voice2 again ", (voice == voice2));
+
+ UnitVoice voice3 = allocator.allocate(tag3);
+ @SuppressWarnings("unused")
+ UnitVoice voice4 = allocator.allocate(tag4);
+
+ UnitVoice voice5 = allocator.allocate(tag5);
+ assertTrue("ran out so get voice1 as oldest", (voice5 == voice1));
+
+ voice = allocator.allocate(tag2);
+ assertTrue("should be voice2 again ", (voice == voice2));
+
+ // Now voice 3 should be the oldest cuz voice 2 was touched.
+ UnitVoice voice6 = allocator.allocate(tag6);
+ assertTrue("ran out so get voice3 as oldest", (voice6 == voice3));
+ }
+
+ public void testOff() {
+ int tag1 = 61;
+ int tag2 = 62;
+ int tag3 = 63;
+ int tag4 = 64;
+ int tag5 = 65;
+ int tag6 = 66;
+ UnitVoice voice1 = allocator.allocate(tag1);
+ UnitVoice voice2 = allocator.allocate(tag2);
+ UnitVoice voice3 = allocator.allocate(tag3);
+ UnitVoice voice4 = allocator.allocate(tag4);
+
+ assertTrue("voice 3 should start on", allocator.isOn(tag3));
+ allocator.off(tag3);
+ assertEquals("voice 3 should now be off", false, allocator.isOn(tag3));
+
+ allocator.off(tag2);
+
+ UnitVoice voice5 = allocator.allocate(tag5);
+ assertTrue("should get voice3 cuz off first", (voice5 == voice3));
+ UnitVoice voice6 = allocator.allocate(tag6);
+ assertTrue("should get voice2 cuz off second", (voice6 == voice2));
+ voice3 = allocator.allocate(tag3);
+ assertTrue("should get voice1 cuz on first", (voice3 == voice1));
+
+ voice1 = allocator.allocate(tag1);
+ assertTrue("should get voice4 cuz next up", (voice1 == voice4));
+ }
+}