diff options
author | RubbaBoy <[email protected]> | 2020-07-06 02:33:28 -0400 |
---|---|---|
committer | Phil Burk <[email protected]> | 2020-10-30 11:19:34 -0700 |
commit | 46888fae6eb7b1dd386f7af7d101ead99ae61981 (patch) | |
tree | 8969bbfd68d2fb5c0d8b86da49ec2eca230a72ab /src/test/java/com/jsyn/research | |
parent | c51e92e813dd481603de078f0778e1f75db2ab05 (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.java | 152 | ||||
-rw-r--r-- | src/test/java/com/jsyn/research/RecordVariousRamps.java | 193 |
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); + } +} |