diff options
Diffstat (limited to 'tests/com/jsyn')
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)); + } +} |