aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPhil Burk <[email protected]>2016-08-09 19:16:23 -0700
committerPhil Burk <[email protected]>2016-08-09 19:16:23 -0700
commite83baf9bf34cc41097e7f38f16dee0df2f80554a (patch)
treed8ed90f9edeea55903c6f5fd8ca9ecf8c6676791
parenteb36cf83f1cca79e76c4ad44fc78077c9428790b (diff)
Fix race condition when stopping and restarting the synthesizer.
https://github.com/philburk/jsyn/issues/30
-rw-r--r--src/com/jsyn/JSyn.java8
-rw-r--r--src/com/jsyn/devices/javasound/JavaSoundAudioDevice.java9
-rw-r--r--src/com/jsyn/engine/SynthesisEngine.java165
3 files changed, 95 insertions, 87 deletions
diff --git a/src/com/jsyn/JSyn.java b/src/com/jsyn/JSyn.java
index e42d141..7598305 100644
--- a/src/com/jsyn/JSyn.java
+++ b/src/com/jsyn/JSyn.java
@@ -56,10 +56,10 @@ public class JSyn {
// Update these for every release.
private final static int VERSION_MAJOR = 16;
private final static int VERSION_MINOR = 7;
- private final static int VERSION_REVISION = 5;
- public final static int BUILD_NUMBER = 459;
- private final static long BUILD_TIME = new GregorianCalendar(2015, GregorianCalendar.NOVEMBER,
- 2).getTime().getTime();
+ private final static int VERSION_REVISION = 6;
+ public final static int BUILD_NUMBER = 460;
+ private final static long BUILD_TIME = new GregorianCalendar(2016,
+ GregorianCalendar.AUGUST, 9).getTime().getTime();
public final static String VERSION = VERSION_MAJOR + "." + VERSION_MINOR + "."
+ VERSION_REVISION;
diff --git a/src/com/jsyn/devices/javasound/JavaSoundAudioDevice.java b/src/com/jsyn/devices/javasound/JavaSoundAudioDevice.java
index 56b3912..656dc1c 100644
--- a/src/com/jsyn/devices/javasound/JavaSoundAudioDevice.java
+++ b/src/com/jsyn/devices/javasound/JavaSoundAudioDevice.java
@@ -4,9 +4,9 @@
* 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.
@@ -34,7 +34,7 @@ import com.jsyn.devices.AudioDeviceOutputStream;
/**
* Use JavaSound to access the audio hardware.
- *
+ *
* @author Phil Burk (C) 2009 Mobileer Inc
*/
public class JavaSoundAudioDevice implements AudioDeviceManager {
@@ -229,9 +229,6 @@ public class JavaSoundAudioDevice implements AudioDeviceManager {
bytes[byteIndex++] = (byte) (sample >> 8); // big end
}
- // sleepUntilNonBlocking( byteIndex ); // This was just an attempt to get rid of clicks
- // on Apple.
-
line.write(bytes, 0, byteIndex);
}
diff --git a/src/com/jsyn/engine/SynthesisEngine.java b/src/com/jsyn/engine/SynthesisEngine.java
index 8413e29..e966b30 100644
--- a/src/com/jsyn/engine/SynthesisEngine.java
+++ b/src/com/jsyn/engine/SynthesisEngine.java
@@ -52,17 +52,17 @@ import com.softsynth.shared.time.TimeStamp;
* @author Phil Burk (C) 2009 Mobileer Inc
* @see Synthesizer
*/
-public class SynthesisEngine implements Runnable, Synthesizer {
+public class SynthesisEngine implements Synthesizer {
private final static int BLOCKS_PER_BUFFER = 8;
private final static int FRAMES_PER_BUFFER = Synthesizer.FRAMES_PER_BLOCK * BLOCKS_PER_BUFFER;
+ // I have measured JavaSound taking 1200 msec to close devices.
+ private static final int MAX_THREAD_STOP_TIME = 2000;
+
public static final int DEFAULT_FRAME_RATE = 44100;
private final AudioDeviceManager audioDeviceManager;
- private AudioDeviceOutputStream audioOutputStream;
- private AudioDeviceInputStream audioInputStream;
- private Thread audioThread;
+ private EngineThread engineThread;
private final ScheduledQueue<ScheduledCommand> commandQueue = new ScheduledQueue<ScheduledCommand>();
- private volatile boolean go;
private InterleavingBuffer inputBuffer;
private InterleavingBuffer outputBuffer;
@@ -85,6 +85,8 @@ public class SynthesisEngine implements Runnable, Synthesizer {
// private int numOutputChannels;
// private int numInputChannels;
private final CopyOnWriteArrayList<Runnable> audioTasks = new CopyOnWriteArrayList<Runnable>();
+ private double mOutputLatency;
+ private double mInputLatency;
/** A fraction corresponding to exactly -96 dB. */
public static final double DB96 = (1.0 / 63095.73444801943);
/** A fraction that is approximately -90.3 dB. Defined as 1 bit of an S16. */
@@ -235,20 +237,12 @@ public class SynthesisEngine implements Runnable, Synthesizer {
inverseNyquist = 2.0 / frameRate;
if (useRealTime) {
- if (numInputChannels > 0) {
- audioInputStream = audioDeviceManager.createInputStream(inputDeviceID, frameRate,
- numInputChannels);
- }
- if (numOutputChannels > 0) {
- audioOutputStream = audioDeviceManager.createOutputStream(outputDeviceID,
- frameRate, numOutputChannels);
- }
- audioThread = new Thread(this);
- logger.fine("Synth thread old priority = " + audioThread.getPriority());
- audioThread.setPriority(audioThread.getPriority() + 2);
- logger.fine("Synth thread new priority = " + audioThread.getPriority());
- go = true;
- audioThread.start();
+ engineThread = new EngineThread(inputDeviceID, numInputChannels,
+ outputDeviceID, numOutputChannels);
+ logger.fine("Synth thread old priority = " + engineThread.getPriority());
+ engineThread.setPriority(engineThread.getPriority() + 2);
+ logger.fine("Synth thread new priority = " + engineThread.getPriority());
+ engineThread.start();
}
started = true;
@@ -256,7 +250,8 @@ public class SynthesisEngine implements Runnable, Synthesizer {
@Override
public boolean isRunning() {
- return go;
+ Thread thread = engineThread;
+ return (thread != null) && thread.isAlive();
}
@Override
@@ -268,13 +263,11 @@ public class SynthesisEngine implements Runnable, Synthesizer {
if (useRealTime) {
// Stop audio synthesis and all units.
- go = false;
- if (audioThread != null) {
+ if (engineThread != null) {
try {
// Interrupt now, otherwise audio thread will wait for audio I/O.
- audioThread.interrupt();
-
- audioThread.join(1000);
+ engineThread.requestStop();
+ engineThread.join(MAX_THREAD_STOP_TIME);
} catch (InterruptedException e) {
e.printStackTrace();
}
@@ -286,58 +279,84 @@ public class SynthesisEngine implements Runnable, Synthesizer {
started = false;
}
- @Override
- public void run() {
- logger.fine("JSyn synthesis thread starting.");
- try {
- if (audioInputStream != null) {
- logger.finer("JSyn synthesis thread trying to start audio INPUT!");
- audioInputStream.start();
- String msg = String.format("Input Latency in = %5.1f msec",
- 1000 * audioInputStream.getLatency());
- logger.fine(msg);
+ private class EngineThread extends Thread
+ {
+ private AudioDeviceOutputStream audioOutputStream;
+ private AudioDeviceInputStream audioInputStream;
+ private volatile boolean go = true;
+
+ EngineThread(int inputDeviceID, int numInputChannels,
+ int outputDeviceID, int numOutputChannels) {
+ if (numInputChannels > 0) {
+ audioInputStream = audioDeviceManager.createInputStream(inputDeviceID, frameRate,
+ numInputChannels);
}
- if (audioOutputStream != null) {
- logger.finer("JSyn synthesis thread trying to start audio OUTPUT!");
- audioOutputStream.start();
- String msg = String.format("Output Latency = %5.1f msec",
- 1000 * audioOutputStream.getLatency());
- logger.fine(msg);
- // Buy some time while we fill the buffer.
- audioOutputStream.write(outputBuffer.interleavedBuffer);
+ if (numOutputChannels > 0) {
+ audioOutputStream = audioDeviceManager.createOutputStream(outputDeviceID,
+ frameRate, numOutputChannels);
}
- loadAnalyzer = new LoadAnalyzer();
- while (go) {
- if (audioInputStream != null) {
- audioInputStream.read(inputBuffer.interleavedBuffer);
- }
+ }
- loadAnalyzer.start();
- runAudioTasks();
- generateNextBuffer();
- loadAnalyzer.stop();
+ public void requestStop() {
+ go = false;
+ interrupt();
+ }
+ @Override
+ public void run() {
+ logger.fine("JSyn synthesis thread starting.");
+ try {
+ if (audioInputStream != null) {
+ logger.finer("JSyn synthesis thread trying to start audio INPUT!");
+ audioInputStream.start();
+ mInputLatency = audioInputStream.getLatency();
+ String msg = String.format("Input Latency in = %5.1f msec",
+ 1000 * mInputLatency);
+ logger.fine(msg);
+ }
if (audioOutputStream != null) {
- // This call will block when the output is full.
+ logger.finer("JSyn synthesis thread trying to start audio OUTPUT!");
+ audioOutputStream.start();
+ mOutputLatency = audioOutputStream.getLatency();
+ String msg = String.format("Output Latency = %5.1f msec",
+ 1000 * mOutputLatency);
+ logger.fine(msg);
+ // Buy some time while we fill the buffer.
audioOutputStream.write(outputBuffer.interleavedBuffer);
}
- }
+ loadAnalyzer = new LoadAnalyzer();
+ while (go) {
+ if (audioInputStream != null) {
+ audioInputStream.read(inputBuffer.interleavedBuffer);
+ }
+
+ loadAnalyzer.start();
+ runAudioTasks();
+ generateNextBuffer();
+ loadAnalyzer.stop();
+
+ if (audioOutputStream != null) {
+ // This call will block when the output is full.
+ audioOutputStream.write(outputBuffer.interleavedBuffer);
+ }
+ }
- } catch (Throwable e) {
- e.printStackTrace();
- go = false;
+ } catch (Throwable e) {
+ e.printStackTrace();
+ go = false;
- } finally {
- logger.info("JSyn synthesis thread in finally code.");
- // Stop audio system.
- if (audioInputStream != null) {
- audioInputStream.stop();
- }
- if (audioOutputStream != null) {
- audioOutputStream.stop();
+ } finally {
+ logger.info("JSyn synthesis thread in finally code.");
+ // Stop audio system.
+ if (audioInputStream != null) {
+ audioInputStream.stop();
+ }
+ if (audioOutputStream != null) {
+ audioOutputStream.stop();
+ }
}
+ logger.fine("JSyn synthesis thread exiting.");
}
- logger.fine("JSyn synthesis thread exiting.");
}
private void runAudioTasks() {
@@ -395,7 +414,7 @@ public class SynthesisEngine implements Runnable, Synthesizer {
@Override
public void scheduleCommand(TimeStamp timeStamp, ScheduledCommand command) {
- if ((Thread.currentThread() == audioThread) && (timeStamp.getTime() <= getCurrentTime())) {
+ if ((Thread.currentThread() == engineThread) && (timeStamp.getTime() <= getCurrentTime())) {
command.run();
} else {
logger.fine("scheduling " + command + ", at time " + timeStamp.getTime());
@@ -622,19 +641,11 @@ public class SynthesisEngine implements Runnable, Synthesizer {
}
public double getOutputLatency() {
- if (audioOutputStream != null) {
- return audioOutputStream.getLatency();
- } else {
- return 0;
- }
+ return mOutputLatency;
}
public double getInputLatency() {
- if (audioInputStream != null) {
- return audioInputStream.getLatency();
- } else {
- return 0;
- }
+ return mInputLatency;
}
@Override