aboutsummaryrefslogtreecommitdiffstats
path: root/src/test/java/com/jsyn/research
diff options
context:
space:
mode:
authorRubbaBoy <[email protected]>2020-07-06 02:33:28 -0400
committerPhil Burk <[email protected]>2020-10-30 11:19:34 -0700
commit46888fae6eb7b1dd386f7af7d101ead99ae61981 (patch)
tree8969bbfd68d2fb5c0d8b86da49ec2eca230a72ab /src/test/java/com/jsyn/research
parentc51e92e813dd481603de078f0778e1f75db2ab05 (diff)
Restructured project, added gradle, JUnit, logger, and more
Added Gradle (and removed ant), modernized testing via the JUnit framework, moved standalone examples from the tests directory to a separate module, removed sparsely used Java logger and replaced it with SLF4J. More work could be done, however this is a great start to greatly improving the health of the codebase.
Diffstat (limited to 'src/test/java/com/jsyn/research')
-rw-r--r--src/test/java/com/jsyn/research/BenchMultiThreading.java152
-rw-r--r--src/test/java/com/jsyn/research/RecordVariousRamps.java193
2 files changed, 345 insertions, 0 deletions
diff --git a/src/test/java/com/jsyn/research/BenchMultiThreading.java b/src/test/java/com/jsyn/research/BenchMultiThreading.java
new file mode 100644
index 0000000..24624c5
--- /dev/null
+++ b/src/test/java/com/jsyn/research/BenchMultiThreading.java
@@ -0,0 +1,152 @@
+/*
+ * 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 org.junit.jupiter.api.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+// TODO: Use thread pools, or maybe JMH?
+public class BenchMultiThreading {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(BenchMultiThreading.class);
+
+ private static final int FRAMES_PER_BLOCK = 64;
+ int numThreads = 4;
+ int numLoops = 100000;
+ private ArrayList<CustomThread> threadList;
+
+ static 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;
+ // LOGGER.debug( this + " generating frame " +
+ // frameCount );
+ semaphore.notify();
+ }
+ synchronized (goSemaphore) {
+ while (desiredFrame <= frameCount) {
+ goSemaphore.wait();
+ }
+ }
+ long stopNano = System.nanoTime();
+ }
+ } catch (InterruptedException e) {
+ LOGGER.debug("CustomThread interrupted. ");
+ }
+ LOGGER.debug("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();
+ }
+ }
+
+ }
+
+ @Test
+ public void testMultiThreads() {
+ threadList = new ArrayList<>();
+ 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);
+ // LOGGER.debug("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(frameCount, thread.frameCount, "BlockCount must match ");
+ 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/src/test/java/com/jsyn/research/RecordVariousRamps.java b/src/test/java/com/jsyn/research/RecordVariousRamps.java
new file mode 100644
index 0000000..7abb2b1
--- /dev/null
+++ b/src/test/java/com/jsyn/research/RecordVariousRamps.java
@@ -0,0 +1,193 @@
+/*
+ * 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;
+import org.junit.jupiter.api.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class RecordVariousRamps {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(RecordVariousRamps.class);
+
+ 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 static final 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);
+ LOGGER.debug("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;
+ LOGGER.debug("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();
+ }
+
+ @Test
+ private void stepMode() throws IOException {
+ test(MODE_STEP);
+ }
+
+ @Test
+ public void linearMode() throws IOException {
+ test(MODE_LINEAR);
+ }
+
+ @Test
+ public void smoothMode() throws IOException {
+ test(MODE_SMOOTH);
+ }
+}