aboutsummaryrefslogtreecommitdiffstats
path: root/src/test/com/jogamp/openal
diff options
context:
space:
mode:
Diffstat (limited to 'src/test/com/jogamp/openal')
-rw-r--r--src/test/com/jogamp/openal/test/manual/Synth01AL.java234
-rw-r--r--src/test/com/jogamp/openal/test/manual/Synth02AL.java449
2 files changed, 683 insertions, 0 deletions
diff --git a/src/test/com/jogamp/openal/test/manual/Synth01AL.java b/src/test/com/jogamp/openal/test/manual/Synth01AL.java
new file mode 100644
index 0000000..64da9fa
--- /dev/null
+++ b/src/test/com/jogamp/openal/test/manual/Synth01AL.java
@@ -0,0 +1,234 @@
+/**
+ * Copyright 2023 JogAmp Community. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are
+ * permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this list of
+ * conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this list
+ * of conditions and the following disclaimer in the documentation and/or other materials
+ * provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * The views and conclusions contained in the software and documentation are those of the
+ * authors and should not be interpreted as representing official policies, either expressed
+ * or implied, of JogAmp Community.
+ */
+package com.jogamp.openal.test.manual;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.ShortBuffer;
+
+import com.jogamp.openal.AL;
+import com.jogamp.openal.ALC;
+import com.jogamp.openal.ALCcontext;
+import com.jogamp.openal.ALCdevice;
+import com.jogamp.openal.ALConstants;
+import com.jogamp.openal.ALFactory;
+import com.jogamp.openal.ALVersion;
+
+/**
+ * A continuous simple on-thread immutable sine wave synthesizer.
+ * <p>
+ * Implementation simply finds the best loop'able sample-count for a fixed frequency
+ * and plays it indefinitely.
+ * </p>
+ */
+public class Synth01AL {
+ /** The value PI, i.e. 180 degrees in radians. */
+ public static final float PI = 3.14159265358979323846f;
+
+ /** The value 2PI, i.e. 360 degrees in radians. */
+ public static final float TWO_PI = 2f * PI;
+
+ private static final float EPSILON = 1.1920929E-7f; // Float.MIN_VALUE == 1.4e-45f ; double EPSILON 2.220446049250313E-16d
+
+ private static final float SHORT_MAX = 32767.0f; // == Short.MAX_VALUE
+
+ public static void waitForKey(final String message) {
+ final BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in));
+ System.err.println("> Press enter to "+message);
+ try {
+ System.err.println(stdin.readLine());
+ } catch (final IOException e) { e.printStackTrace(); }
+ }
+
+ private ALC alc = null;
+ private ALCdevice device = null;
+ private ALCcontext context = null;
+ private AL al = null;
+
+ private final int[] buffers = { 0 };
+ private final int[] sources = { 0 };
+
+ private void alCheckError(final String given_label, final boolean throwEx) {
+ final int error = al.alGetError();
+ if(ALConstants.AL_NO_ERROR != error) {
+ final String msg = String.format("ERROR - 0x%X %s (%s)", error, al.alGetString(error), given_label);
+ System.err.println(msg);
+ if( throwEx ) {
+ throw new RuntimeException(msg);
+ }
+ }
+ }
+
+ public void init() {
+ alc = ALFactory.getALC();
+ device = alc.alcOpenDevice(null);
+ context = alc.alcCreateContext(device, null);
+ alc.alcMakeContextCurrent(context);
+ al = ALFactory.getAL(); // valid after makeContextCurrent(..)
+ System.out.println("ALVersion: "+new ALVersion(al).toString());
+ System.out.println("Output devices:");
+ {
+ final String[] outDevices = alc.alcGetDeviceSpecifiers();
+ if( null != outDevices ) {
+ for (final String name : outDevices) {
+ System.out.println(" "+name);
+ }
+ }
+ }
+ alCheckError("setup", true);
+
+ al.alGenBuffers(1, buffers, 0);
+ alCheckError("alGenBuffers", true);
+
+ // Set-up sound source
+ al.alGenSources(1, sources, 0);
+ alCheckError("alGenSources", true);
+ }
+
+ public void exit() {
+ // Stop the sources
+ al.alSourceStopv(1, sources, 0);
+ for (int ii = 0; ii < 1; ++ii) {
+ al.alSourcei(sources[ii], ALConstants.AL_BUFFER, 0);
+ }
+ alCheckError("sources: stop and disconnected", true);
+
+ // Clean-up
+ al.alDeleteSources(1, sources, 0);
+ al.alDeleteBuffers(1, buffers, 0);
+ alCheckError("sources/buffers: deleted", true);
+
+ if( null != context ) {
+ alc.alcMakeContextCurrent(null);
+ alc.alcDestroyContext(context);
+ context = null;
+ }
+ if( null != device ) {
+ alc.alcCloseDevice(device);
+ device = null;
+ }
+ }
+
+ public static int findBestWaveCount(final float freq, final int sampleRate, final int minWaves, final int maxWaves) {
+ final float period = 1.0f / freq; // [s]
+ final float sample_step = ( TWO_PI * freq ) / sampleRate;
+ int wave_count;
+ float s_diff = Float.MAX_VALUE;
+ float sc_diff = Float.MAX_VALUE;
+ int wc_best = -1;
+ for(wave_count = minWaves; wave_count < maxWaves && s_diff >= EPSILON; ++wave_count) {
+ final float d = wave_count * period; // duration [s] for 'i' full waves
+ final float sc_f = d * sampleRate; // sample count for 'i' full waves [n]
+ final int sc_i = (int)sc_f;
+ final float s1 = (float) Math.abs( Math.sin( sample_step * sc_i ) ); // last_step + 1 = next wave start == error to zero
+ if( s1 < s_diff ) {
+ s_diff = s1;
+ sc_diff = sc_f - sc_i; // sample_count delta float - int
+ wc_best = wave_count;
+ }
+ }
+ System.err.printf("%nBest: %d/[%d..%d], waves %d, sample_count diff %.12f, sample diff %.12f%n", wave_count, minWaves, maxWaves, wc_best, sc_diff, s_diff);
+ return wc_best;
+ }
+
+ private static final int SAMPLE_RATE = 44100; // [Hz]
+
+ public void loop(final float freq /* [Hz] */) {
+ final float period = 1.0f / freq; // [s]
+ final float sample_step = ( TWO_PI * freq ) / SAMPLE_RATE;
+
+ final int wave_count = findBestWaveCount(freq, SAMPLE_RATE, 10, 1000);
+ final float duration = wave_count * period; // [s], full waves
+ final int sample_count = (int)( duration * SAMPLE_RATE ); // [n]
+
+ System.err.printf("%nFreq %f Hz, period %f [ms], waves %d, duration %f [ms], sample[rate %d, step %f]%n", freq, 1000.0*period, wave_count, 1000.0*duration, SAMPLE_RATE, sample_step);
+
+ // allocate PCM audio buffer
+ final ShortBuffer samples = ShortBuffer.allocate(sample_count);
+
+ for(int i=0; i<sample_count; ++i) {
+ final float s = (float) Math.sin( sample_step * i );
+ samples.put( (short)( SHORT_MAX * s ) );
+ }
+ samples.rewind();
+ alCheckError("populating samples", true);
+
+ // upload buffer to OpenAL
+ al.alBufferData(buffers[0], ALConstants.AL_FORMAT_MONO16, samples, sample_count*2, SAMPLE_RATE);
+ alCheckError("alBufferData samples", true);
+
+ samples.clear();
+
+ // Play source / buffer
+ al.alSourcei(sources[0], ALConstants.AL_BUFFER, buffers[0]);
+ alCheckError("alSourcei source <-> buffer", true);
+
+ al.alSourcei(sources[0], ALConstants.AL_LOOPING, 1);
+ final int[] loopArray = new int[1];
+ al.alGetSourcei(sources[0], ALConstants.AL_LOOPING, loopArray, 0);
+ System.err.println("Looping 1: " + (loopArray[0] == ALConstants.AL_TRUE));
+
+ al.alSourceRewind(sources[0]);
+ al.alSourcePlay(sources[0]);
+ alCheckError("alSourcePlay", true);
+
+ // ---------------------
+
+ final int[] current_playing_state = { 0 };
+ al.alGetSourcei(sources[0], ALConstants.AL_SOURCE_STATE, current_playing_state, 0);
+ alCheckError("alGetSourcei AL_SOURCE_STATE", true);
+
+ if( ALConstants.AL_PLAYING == current_playing_state[0] ) {
+ waitForKey("Stop");
+ }
+ }
+
+ public static float atof(final String str, final float def) {
+ try {
+ return Float.parseFloat(str);
+ } catch (final Exception ex) {
+ ex.printStackTrace();
+ }
+ return def;
+ }
+
+ public static void main(final String[] args) {
+ float freq = 100.0f;
+ for(int i=0; i<args.length; i++) {
+ if(args[i].equals("-f")) {
+ i++;
+ freq = atof(args[i], freq);
+ }
+ }
+ final Synth01AL o = new Synth01AL();
+ o.init();
+ o.loop(freq);
+ o.exit();
+ }
+}
diff --git a/src/test/com/jogamp/openal/test/manual/Synth02AL.java b/src/test/com/jogamp/openal/test/manual/Synth02AL.java
new file mode 100644
index 0000000..068f168
--- /dev/null
+++ b/src/test/com/jogamp/openal/test/manual/Synth02AL.java
@@ -0,0 +1,449 @@
+/**
+ * Copyright 2023 JogAmp Community. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are
+ * permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this list of
+ * conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this list
+ * of conditions and the following disclaimer in the documentation and/or other materials
+ * provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * The views and conclusions contained in the software and documentation are those of the
+ * authors and should not be interpreted as representing official policies, either expressed
+ * or implied, of JogAmp Community.
+ */
+package com.jogamp.openal.test.manual;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.ByteBuffer;
+import java.util.concurrent.TimeUnit;
+
+import com.jogamp.common.av.AudioSink;
+import com.jogamp.common.av.AudioSinkFactory;
+import com.jogamp.common.os.Clock;
+import com.jogamp.common.util.InterruptSource;
+import com.jogamp.common.util.InterruptedRuntimeException;
+import com.jogamp.common.util.SourcedInterruptedException;
+
+/**
+ * A continuous simple off-thread mutable sine wave synthesizer.
+ * <p>
+ * Implementation utilizes an off-thread worker thread,
+ * allowing to change frequency and amplitude without disturbance.
+ * </p>
+ * <p>
+ * Latency is hardcoded as 1 - 3 times frameDuration, having a frameDuration of 16 ms.
+ * Averages around 48 ms.
+ * </p>
+ * <p>
+ * Latency needs improvement to have a highly responsive life-music synthesizer.
+ * </p>
+ */
+public final class Synth02AL {
+ private static final boolean DEBUG = false;
+
+ /** The value PI, i.e. 180 degrees in radians. */
+ private static final float PI = 3.14159265358979323846f;
+
+ /** The value 2PI, i.e. 360 degrees in radians. */
+ private static final float TWO_PI = 2f * PI;
+
+ private static final float EPSILON = 1.1920929E-7f; // Float.MIN_VALUE == 1.4e-45f ; double EPSILON 2.220446049250313E-16d
+
+ private static final float SHORT_MAX = 32767.0f; // == Short.MAX_VALUE
+
+ public static final int frameDuration = 16; // AudioSink.DefaultFrameDuration; // [ms]
+
+ public static final int audioQueueLimit = 3 * frameDuration;
+
+ public static final float MIDDLE_C = 261.625f;
+
+ private final Object stateLock = new Object();
+ private volatile float audioAmplitude = 1.0f;
+ private volatile float audioFreq = MIDDLE_C;
+ private volatile int lastAudioPTS = 0;
+ private SynthWorker streamWorker;
+
+ public Synth02AL() {
+ streamWorker = new SynthWorker();
+ }
+
+ public void setFreq(final float f) {
+ audioFreq = f;
+ }
+ public float getFreq() { return audioFreq; }
+
+ public void setAmplitude(final float a) {
+ audioAmplitude = Math.min(1.0f, Math.max(0.0f, a)); // clip [0..1]
+ }
+ public float getAmplitude() { return audioAmplitude; }
+
+ public void play() {
+ synchronized( stateLock ) {
+ if( null == streamWorker ) {
+ streamWorker = new SynthWorker();
+ }
+ streamWorker.doResume();
+ }
+ }
+
+ public void pause(final boolean flush) {
+ synchronized( stateLock ) {
+ if( null != streamWorker ) {
+ streamWorker.doPause(true);
+ }
+ }
+ }
+
+ public void stop() {
+ synchronized( stateLock ) {
+ if( null != streamWorker ) {
+ streamWorker.doStop();
+ streamWorker = null;
+ }
+ }
+ }
+
+ public boolean isPlaying() {
+ synchronized( stateLock ) {
+ if( null != streamWorker ) {
+ return streamWorker.isPlaying();
+ }
+ }
+ return false;
+ }
+
+ public boolean isRunning() {
+ synchronized( stateLock ) {
+ if( null != streamWorker ) {
+ return streamWorker.isRunning();
+ }
+ }
+ return false;
+ }
+
+ public int getGenPTS() { return lastAudioPTS; }
+
+ public int getPTS() { return null != streamWorker ? streamWorker.audioSink.getPTS() : 0; }
+
+ @Override
+ public final String toString() {
+ synchronized( stateLock ) {
+ final String as = null != streamWorker ? streamWorker.audioSink.toString() : "ALAudioSink[null]";
+ final int pts = getPTS();
+ final int lag = getGenPTS() - pts;
+ return getClass().getSimpleName()+"[f "+audioFreq+", a "+audioAmplitude+", state[running "+isRunning()+", playing "+isPlaying()+"], pts[gen "+getGenPTS()+", play "+pts+", lag "+lag+"], "+as+"]";
+ }
+ }
+
+ class SynthWorker extends InterruptSource.Thread {
+ private volatile boolean isRunning = false;
+ private volatile boolean isPlaying = false;
+ private volatile boolean isBlocked = false;
+
+ private volatile boolean shallPause = true;
+ private volatile boolean shallStop = false;
+
+ private final AudioSink audioSink;
+ private final AudioSink.AudioFormat audioFormat;
+ private ByteBuffer sampleBuffer = ByteBuffer.allocate(2*1000);
+ private float lastFreq;
+ private float nextSin;
+ private boolean upSin;
+ private int nextStep;
+
+ /**
+ * Starts this daemon thread,
+ * <p>
+ * This thread pauses after it's started!
+ * </p>
+ **/
+ SynthWorker() {
+ setDaemon(true);
+ synchronized(this) {
+ lastAudioPTS = 0;
+ audioSink = AudioSinkFactory.createDefault(Synth02AL.class.getClassLoader());
+ audioFormat = new AudioSink.AudioFormat(audioSink.getPreferredSampleRate(), 16, 1, true /* signed */,
+ true /* fixed point */, false /* planar */, true /* littleEndian */);
+ audioSink.init(audioFormat, frameDuration, audioQueueLimit, 0, audioQueueLimit);
+ lastFreq = 0;
+ nextSin = 0;
+ upSin = true;
+ nextStep = 0;
+ start();
+ try {
+ this.notifyAll(); // wake-up startup-block
+ while( !isRunning && !shallStop ) {
+ this.wait(); // wait until started
+ }
+ } catch (final InterruptedException e) {
+ throw new InterruptedRuntimeException(e);
+ }
+ }
+ }
+
+ private final int findNextStep(final boolean upSin, final float nextSin, final float freq, final int sampleRate, final int sampleCount) {
+ final float sample_step = ( TWO_PI * freq ) / sampleRate;
+
+ float s_diff = Float.MAX_VALUE;
+ float s_best = 0;
+ int i_best = -1;
+ float s0 = 0;
+ for(int i=0; i < sampleCount && s_diff >= EPSILON ; ++i) {
+ final float s1 = (float) Math.sin( sample_step * i );
+ final float s_d = Math.abs(nextSin - s1);
+ if( s_d < s_diff && ( ( upSin && s1 >= s0 ) || ( !upSin && s1 < s0 ) ) ) {
+ s_best = s1;
+ s_diff = s_d;
+ i_best = i;
+ }
+ s0 = s1;
+ }
+ if( DEBUG ) {
+ System.err.printf("%nBest: %d/[%d..%d]: s %f / %f (up %b), s_diff %f%n", i_best, 0, sampleCount, s_best, nextSin, upSin, s_diff);
+ }
+ return i_best;
+ }
+
+ private final void enqueueWave() {
+ // use local cache of volatiles, stable values
+ final float freq = audioFreq;
+ final float amp = audioAmplitude;
+
+ final float period = 1.0f / freq; // [s]
+ final float sample_step = ( TWO_PI * freq ) / audioFormat.sampleRate;
+
+ final float duration = frameDuration / 1000.0f; // [s]
+ final int sample_count = (int)( duration * audioFormat.sampleRate ); // [n]
+
+ final boolean overflow;
+ final boolean changedFreq;
+ if( Math.abs( freq - lastFreq ) >= EPSILON ) {
+ changedFreq = true;
+ overflow = false;
+ lastFreq = freq;
+ nextStep = findNextStep(upSin, nextSin, freq, audioFormat.sampleRate, sample_count);
+ } else {
+ changedFreq = false;
+ if( nextStep + sample_count >= Integer.MAX_VALUE/1000 ) {
+ nextStep = findNextStep(upSin, nextSin, freq, audioFormat.sampleRate, sample_count);
+ overflow = true;
+ } else {
+ overflow = false;
+ }
+ }
+
+ if( DEBUG ) {
+ if( changedFreq || overflow ) {
+ final float wave_count = duration / period;
+ System.err.printf("%nFreq %f Hz, period %f [ms], waves %.2f, duration %f [ms], sample[count %d, rate %d, step %f, next[up %b, sin %f, step %d]]%n", freq,
+ 1000.0*period, wave_count, 1000.0*duration, sample_count, audioFormat.sampleRate, sample_step, upSin, nextSin, nextStep);
+ // System.err.println(Synth02AL.this.toString());
+ }
+ }
+
+ if( sampleBuffer.capacity() < 2*sample_count ) {
+ if( DEBUG ) {
+ System.err.printf("SampleBuffer grow: %d -> %d%n", sampleBuffer.capacity(), 2*sample_count);
+ }
+ sampleBuffer = ByteBuffer.allocate(2*sample_count);
+ }
+
+ {
+ final int l = nextStep;
+ int i;
+ float s = 0;
+ for(i=l; i<l+sample_count; ++i) {
+ s = (float) Math.sin( sample_step * i );
+ final short s16 = (short)( SHORT_MAX * s * amp );
+ sampleBuffer.put( (byte) ( s16 & 0xff ) );
+ sampleBuffer.put( (byte) ( ( s16 >>> 8 ) & 0xff ) );
+ }
+ nextStep = i;
+ nextSin = (float) Math.sin( sample_step * nextStep );
+ upSin = nextSin >= s;
+ sampleBuffer.rewind();
+ }
+ audioSink.enqueueData(lastAudioPTS, sampleBuffer, sample_count*2);
+ sampleBuffer.clear();
+ lastAudioPTS += frameDuration;
+ }
+
+ public final synchronized void doPause(final boolean waitUntilDone) {
+ if( isPlaying ) {
+ shallPause = true;
+ if( java.lang.Thread.currentThread() != this ) {
+ if( isBlocked && isPlaying ) {
+ this.interrupt();
+ }
+ if( waitUntilDone ) {
+ try {
+ while( isPlaying && isRunning ) {
+ this.wait(); // wait until paused
+ }
+ } catch (final InterruptedException e) {
+ throw new InterruptedRuntimeException(e);
+ }
+ }
+ }
+ }
+ }
+ public final synchronized void doResume() {
+ if( isRunning && !isPlaying ) {
+ shallPause = false;
+ if( java.lang.Thread.currentThread() != this ) {
+ try {
+ this.notifyAll(); // wake-up pause-block
+ while( !isPlaying && !shallPause && isRunning ) {
+ this.wait(); // wait until resumed
+ }
+ } catch (final InterruptedException e) {
+ final InterruptedException e2 = SourcedInterruptedException.wrap(e);
+ doPause(false);
+ throw new InterruptedRuntimeException(e2);
+ }
+ }
+ }
+ }
+ public final synchronized void doStop() {
+ if( isRunning ) {
+ shallStop = true;
+ if( java.lang.Thread.currentThread() != this ) {
+ if( isBlocked && isRunning ) {
+ this.interrupt();
+ }
+ try {
+ this.notifyAll(); // wake-up pause-block (opt)
+ while( isRunning ) {
+ this.wait(); // wait until stopped
+ }
+ } catch (final InterruptedException e) {
+ throw new InterruptedRuntimeException(e);
+ }
+ }
+ }
+ audioSink.destroy();
+ }
+ public final boolean isRunning() { return isRunning; }
+ public final boolean isPlaying() { return isPlaying; }
+
+ @Override
+ public final void run() {
+ setName(getName()+"-SynthWorker_"+SynthWorkerInstanceId);
+ SynthWorkerInstanceId++;
+
+ synchronized ( this ) {
+ isRunning = true;
+ this.notifyAll(); // wake-up ctor()
+ }
+
+ while( !shallStop ) {
+ if( shallPause ) {
+ synchronized ( this ) {
+ while( shallPause && !shallStop ) {
+ audioSink.pause();
+ isPlaying = false;
+ this.notifyAll(); // wake-up doPause()
+ try {
+ this.wait(); // wait until resumed
+ } catch (final InterruptedException e) {
+ if( !shallPause ) {
+ e.printStackTrace();
+ }
+ }
+ }
+ audioSink.play();
+ isPlaying = true;
+ this.notifyAll(); // wake-up doResume()
+ }
+ }
+ if( !shallStop ) {
+ isBlocked = true;
+ enqueueWave();
+ isBlocked = false;
+ }
+ }
+ synchronized ( this ) {
+ isRunning = false;
+ isPlaying = false;
+ this.notifyAll(); // wake-up doStop()
+ }
+ }
+ }
+ static int SynthWorkerInstanceId = 0;
+
+ public static float atof(final String str, final float def) {
+ try {
+ return Float.parseFloat(str);
+ } catch (final Exception ex) {
+ ex.printStackTrace();
+ }
+ return def;
+ }
+
+ public static String enterValue(final String message) {
+ final BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in));
+ System.err.println(message);
+ try {
+ final String s = stdin.readLine();
+ System.err.println(s);
+ return s;
+ } catch (final IOException e) { e.printStackTrace(); }
+ return "";
+ }
+
+ public static void main(final String[] args) {
+ float freq = MIDDLE_C;
+ for(int i=0; i<args.length; i++) {
+ if(args[i].equals("-f")) {
+ i++;
+ freq = atof(args[i], freq);
+ }
+ }
+ final Synth02AL o = new Synth02AL();
+ o.setFreq(freq);
+ System.err.println("0: "+o);
+ o.play();
+ System.err.println("1: "+o);
+ {
+ final long t0 = Clock.currentNanos();
+ for(float f=100; f<10000; f+=30) {
+ o.setFreq(f);
+ try {
+ Thread.sleep(frameDuration);
+ } catch (final InterruptedException e) { }
+ }
+ final long t1 = Clock.currentNanos();
+ System.err.println("Loop "+TimeUnit.NANOSECONDS.toMillis(t1-t0)+" ms");
+ }
+ o.setFreq( MIDDLE_C );
+ System.err.println("c: "+o);
+ boolean quit = false;
+ while ( !quit ){
+ final String in = enterValue("Enter new frequency or just enter to exit");
+ if( 0 == in.length() ) {
+ quit = true;
+ } else {
+ freq = atof(in, freq);
+ o.setFreq(freq);
+ System.err.println("n: "+o);
+ }
+ }
+ o.stop();
+ }
+}