aboutsummaryrefslogtreecommitdiffstats
path: root/src/main/java/com/jsyn/devices
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/main/java/com/jsyn/devices
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/main/java/com/jsyn/devices')
-rw-r--r--src/main/java/com/jsyn/devices/AudioDeviceFactory.java93
-rw-r--r--src/main/java/com/jsyn/devices/AudioDeviceInputStream.java31
-rw-r--r--src/main/java/com/jsyn/devices/AudioDeviceManager.java120
-rw-r--r--src/main/java/com/jsyn/devices/AudioDeviceOutputStream.java30
-rw-r--r--src/main/java/com/jsyn/devices/javasound/JavaSoundAudioDevice.java432
-rw-r--r--src/main/java/com/jsyn/devices/javasound/MidiDeviceTools.java86
-rw-r--r--src/main/java/com/jsyn/devices/jportaudio/JPortAudioDevice.java264
7 files changed, 1056 insertions, 0 deletions
diff --git a/src/main/java/com/jsyn/devices/AudioDeviceFactory.java b/src/main/java/com/jsyn/devices/AudioDeviceFactory.java
new file mode 100644
index 0000000..612c81d
--- /dev/null
+++ b/src/main/java/com/jsyn/devices/AudioDeviceFactory.java
@@ -0,0 +1,93 @@
+/*
+ * 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.devices;
+
+import com.jsyn.util.JavaTools;
+
+/**
+ * Create a device appropriate for the platform.
+ *
+ * @author Phil Burk (C) 2010 Mobileer Inc
+ */
+public class AudioDeviceFactory {
+ private static AudioDeviceManager instance;
+
+ /**
+ * Use a custom device interface. Overrides the selection of a default device manager.
+ *
+ * @param instance
+ */
+ public static void setInstance(AudioDeviceManager instance) {
+ AudioDeviceFactory.instance = instance;
+ }
+
+ /**
+ * Try to load JPortAudio or JavaSound devices.
+ *
+ * @return A device supported on this platform.
+ */
+ public static AudioDeviceManager createAudioDeviceManager() {
+ return createAudioDeviceManager(false);
+ }
+
+ /**
+ * Try to load JPortAudio or JavaSound devices.
+ *
+ * @param preferJavaSound if true then try to create a JavaSound manager before other types.
+ * @return A device supported on this platform.
+ */
+ public static AudioDeviceManager createAudioDeviceManager(boolean preferJavaSound) {
+ if (preferJavaSound) {
+ tryJavaSound();
+ tryJPortAudio();
+ } else {
+ tryJPortAudio();
+ tryJavaSound();
+ }
+ return instance;
+ }
+
+ private static void tryJavaSound() {
+ if (instance == null) {
+ try {
+ @SuppressWarnings("unchecked")
+ Class<AudioDeviceManager> clazz = JavaTools.loadClass(
+ "com.jsyn.devices.javasound.JavaSoundAudioDevice", false);
+ if (clazz != null) {
+ instance = clazz.newInstance();
+ }
+ } catch (Throwable e) {
+ System.err.println("Could not load JavaSound device. " + e);
+ }
+ }
+ }
+
+ private static void tryJPortAudio() {
+ if (instance == null) {
+ try {
+ if (JavaTools.loadClass("com.portaudio.PortAudio", false) != null) {
+ instance = (AudioDeviceManager) JavaTools.loadClass(
+ "com.jsyn.devices.jportaudio.JPortAudioDevice").newInstance();
+ }
+
+ } catch (Throwable e) {
+ System.err.println("Could not load JPortAudio device. " + e);
+ }
+ }
+ }
+
+}
diff --git a/src/main/java/com/jsyn/devices/AudioDeviceInputStream.java b/src/main/java/com/jsyn/devices/AudioDeviceInputStream.java
new file mode 100644
index 0000000..a3d1854
--- /dev/null
+++ b/src/main/java/com/jsyn/devices/AudioDeviceInputStream.java
@@ -0,0 +1,31 @@
+/*
+ * 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.devices;
+
+import com.jsyn.io.AudioInputStream;
+
+public interface AudioDeviceInputStream extends AudioInputStream {
+ /** Start the input device. */
+ public void start();
+
+ public void stop();
+
+ /**
+ * @return Estimated latency in seconds.
+ */
+ public double getLatency();
+}
diff --git a/src/main/java/com/jsyn/devices/AudioDeviceManager.java b/src/main/java/com/jsyn/devices/AudioDeviceManager.java
new file mode 100644
index 0000000..ac8d47c
--- /dev/null
+++ b/src/main/java/com/jsyn/devices/AudioDeviceManager.java
@@ -0,0 +1,120 @@
+/*
+ * 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.devices;
+
+/**
+ * Interface for an audio system. This may be implemented using JavaSound, or a native device
+ * wrapper.
+ *
+ * @author Phil Burk (C) 2010 Mobileer Inc
+ */
+public interface AudioDeviceManager {
+ /**
+ * Pass this value to the start method to request the default device ID.
+ */
+ public final static int USE_DEFAULT_DEVICE = -1;
+
+ /**
+ * @return The number of devices available.
+ */
+ public int getDeviceCount();
+
+ /**
+ * Get the name of an audio device.
+ *
+ * @param deviceID An index between 0 to deviceCount-1.
+ * @return A name that can be shown to the user.
+ */
+ public String getDeviceName(int deviceID);
+
+ /**
+ * @return A name of the device manager that can be shown to the user.
+ */
+ public String getName();
+
+ /**
+ * The user can generally select a default device using a control panel that is part of the
+ * operating system.
+ *
+ * @return The ID for the input device that the user has selected as the default.
+ */
+ public int getDefaultInputDeviceID();
+
+ /**
+ * The user can generally select a default device using a control panel that is part of the
+ * operating system.
+ *
+ * @return The ID for the output device that the user has selected as the default.
+ */
+ public int getDefaultOutputDeviceID();
+
+ /**
+ * @param deviceID
+ * @return The maximum number of channels that the device will support.
+ */
+ public int getMaxInputChannels(int deviceID);
+
+ /**
+ * @param deviceID An index between 0 to numDevices-1.
+ * @return The maximum number of channels that the device will support.
+ */
+ public int getMaxOutputChannels(int deviceID);
+
+ /**
+ * This the lowest latency that the device can support reliably. It should be used for
+ * applications that require low latency such as live processing of guitar signals.
+ *
+ * @param deviceID An index between 0 to numDevices-1.
+ * @return Latency in seconds.
+ */
+ public double getDefaultLowInputLatency(int deviceID);
+
+ /**
+ * This the highest latency that the device can support. High latency is recommended for
+ * applications that are not time critical, such as recording.
+ *
+ * @param deviceID An index between 0 to numDevices-1.
+ * @return Latency in seconds.
+ */
+ public double getDefaultHighInputLatency(int deviceID);
+
+ public double getDefaultLowOutputLatency(int deviceID);
+
+ public double getDefaultHighOutputLatency(int deviceID);
+
+ /**
+ * Set latency in seconds for the audio device. If set to zero then the DefaultLowLatency value
+ * for the device will be used. This is just a suggestion that will be used when the
+ * AudioDeviceInputStream is started.
+ **/
+ public int setSuggestedInputLatency(double latency);
+
+ public int setSuggestedOutputLatency(double latency);
+
+ /**
+ * Create a stream that can be used internally by JSyn for outputting audio data. Applications
+ * should not call this directly.
+ */
+ AudioDeviceOutputStream createOutputStream(int deviceID, int frameRate, int numOutputChannels);
+
+ /**
+ * Create a stream that can be used internally by JSyn for acquiring audio input data.
+ * Applications should not call this directly.
+ */
+ AudioDeviceInputStream createInputStream(int deviceID, int frameRate, int numInputChannels);
+
+}
diff --git a/src/main/java/com/jsyn/devices/AudioDeviceOutputStream.java b/src/main/java/com/jsyn/devices/AudioDeviceOutputStream.java
new file mode 100644
index 0000000..5c17efb
--- /dev/null
+++ b/src/main/java/com/jsyn/devices/AudioDeviceOutputStream.java
@@ -0,0 +1,30 @@
+/*
+ * 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.devices;
+
+import com.jsyn.io.AudioOutputStream;
+
+public interface AudioDeviceOutputStream extends AudioOutputStream {
+ public void start();
+
+ public void stop();
+
+ /**
+ * @return Estimated latency in seconds.
+ */
+ public double getLatency();
+}
diff --git a/src/main/java/com/jsyn/devices/javasound/JavaSoundAudioDevice.java b/src/main/java/com/jsyn/devices/javasound/JavaSoundAudioDevice.java
new file mode 100644
index 0000000..75c4a8a
--- /dev/null
+++ b/src/main/java/com/jsyn/devices/javasound/JavaSoundAudioDevice.java
@@ -0,0 +1,432 @@
+/*
+ * 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.devices.javasound;
+
+import java.util.ArrayList;
+
+import javax.sound.sampled.AudioFormat;
+import javax.sound.sampled.AudioSystem;
+import javax.sound.sampled.DataLine;
+import javax.sound.sampled.Line;
+import javax.sound.sampled.LineUnavailableException;
+import javax.sound.sampled.Mixer;
+import javax.sound.sampled.SourceDataLine;
+import javax.sound.sampled.TargetDataLine;
+
+import com.jsyn.devices.AudioDeviceInputStream;
+import com.jsyn.devices.AudioDeviceManager;
+import com.jsyn.devices.AudioDeviceOutputStream;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Use JavaSound to access the audio hardware.
+ *
+ * @author Phil Burk (C) 2009 Mobileer Inc
+ */
+public class JavaSoundAudioDevice implements AudioDeviceManager {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(JavaSoundAudioDevice.class);
+
+ private static final int BYTES_PER_SAMPLE = 2;
+ private static final boolean USE_BIG_ENDIAN = false;
+
+ ArrayList<DeviceInfo> deviceRecords;
+ private double suggestedOutputLatency = 0.040;
+ private double suggestedInputLatency = 0.100;
+ private int defaultInputDeviceID = -1;
+ private int defaultOutputDeviceID = -1;
+
+ public JavaSoundAudioDevice() {
+ String osName = System.getProperty("os.name");
+ if (osName.contains("Windows")) {
+ suggestedOutputLatency = 0.08;
+ LOGGER.info("JSyn: default output latency set to "
+ + ((int) (suggestedOutputLatency * 1000)) + " msec for " + osName);
+ }
+ deviceRecords = new ArrayList<DeviceInfo>();
+ sniffAvailableMixers();
+ dumpAvailableMixers();
+ }
+
+ private void dumpAvailableMixers() {
+ for (DeviceInfo deviceInfo : deviceRecords) {
+ LOGGER.debug("" + deviceInfo);
+ }
+ }
+
+ /**
+ * Build device info and determine default devices.
+ */
+ private void sniffAvailableMixers() {
+ Mixer.Info[] mixers = AudioSystem.getMixerInfo();
+ for (int i = 0; i < mixers.length; i++) {
+ DeviceInfo deviceInfo = new DeviceInfo();
+
+ deviceInfo.name = mixers[i].getName();
+ Mixer mixer = AudioSystem.getMixer(mixers[i]);
+
+ Line.Info[] lines = mixer.getTargetLineInfo();
+ deviceInfo.maxInputs = scanMaxChannels(lines);
+ // Remember first device that supports input.
+ if ((defaultInputDeviceID < 0) && (deviceInfo.maxInputs > 0)) {
+ defaultInputDeviceID = i;
+ }
+
+ lines = mixer.getSourceLineInfo();
+ deviceInfo.maxOutputs = scanMaxChannels(lines);
+ // Remember first device that supports output.
+ if ((defaultOutputDeviceID < 0) && (deviceInfo.maxOutputs > 0)) {
+ defaultOutputDeviceID = i;
+ }
+
+ deviceRecords.add(deviceInfo);
+ }
+ }
+
+ private int scanMaxChannels(Line.Info[] lines) {
+ int maxChannels = 0;
+ for (Line.Info line : lines) {
+ if (line instanceof DataLine.Info) {
+ int numChannels = scanMaxChannels(((DataLine.Info) line));
+ if (numChannels > maxChannels) {
+ maxChannels = numChannels;
+ }
+ }
+ }
+ return maxChannels;
+ }
+
+ private int scanMaxChannels(DataLine.Info info) {
+ int maxChannels = 0;
+ for (AudioFormat format : info.getFormats()) {
+ int numChannels = format.getChannels();
+ if (numChannels > maxChannels) {
+ maxChannels = numChannels;
+ }
+ }
+ return maxChannels;
+ }
+
+ static class DeviceInfo {
+ String name;
+ int maxInputs;
+ int maxOutputs;
+
+ @Override
+ public String toString() {
+ return "AudioDevice: " + name + ", max in = " + maxInputs + ", max out = " + maxOutputs;
+ }
+ }
+
+ private static class JavaSoundStream {
+ AudioFormat format;
+ byte[] bytes;
+ int frameRate;
+ int deviceID;
+ int samplesPerFrame;
+
+ public JavaSoundStream(int deviceID, int frameRate, int samplesPerFrame) {
+ this.deviceID = deviceID;
+ this.frameRate = frameRate;
+ this.samplesPerFrame = samplesPerFrame;
+ format = new AudioFormat(frameRate, 16, samplesPerFrame, true, USE_BIG_ENDIAN);
+ }
+
+ Line getDataLine(DataLine.Info info) throws LineUnavailableException {
+ Line dataLine;
+ if (deviceID >= 0) {
+ Mixer.Info[] mixers = AudioSystem.getMixerInfo();
+ Mixer mixer = AudioSystem.getMixer(mixers[deviceID]);
+ dataLine = mixer.getLine(info);
+ } else {
+ dataLine = AudioSystem.getLine(info);
+ }
+ return dataLine;
+ }
+
+ int calculateBufferSize(double suggestedOutputLatency) {
+ int numFrames = (int) (suggestedOutputLatency * frameRate);
+ return numFrames * samplesPerFrame * BYTES_PER_SAMPLE;
+ }
+
+ }
+
+ private class JavaSoundOutputStream extends JavaSoundStream implements AudioDeviceOutputStream {
+ SourceDataLine line;
+
+ public JavaSoundOutputStream(int deviceID, int frameRate, int samplesPerFrame) {
+ super(deviceID, frameRate, samplesPerFrame);
+ }
+
+ @Override
+ public void start() {
+ DataLine.Info info = new DataLine.Info(SourceDataLine.class, format);
+ if (!AudioSystem.isLineSupported(info)) {
+ // Handle the error.
+ LOGGER.error("JavaSoundOutputStream - not supported." + format);
+ } else {
+ try {
+ line = (SourceDataLine) getDataLine(info);
+ int bufferSize = calculateBufferSize(suggestedOutputLatency);
+ line.open(format, bufferSize);
+ LOGGER.debug("Output buffer size = " + bufferSize + " bytes.");
+ line.start();
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ line = null;
+ }
+ }
+ }
+
+ /** Grossly inefficient. Call the array version instead. */
+ @Override
+ public void write(double value) {
+ double[] buffer = new double[1];
+ buffer[0] = value;
+ write(buffer, 0, 1);
+ }
+
+ @Override
+ public void write(double[] buffer) {
+ write(buffer, 0, buffer.length);
+ }
+
+ @Override
+ public void write(double[] buffer, int start, int count) {
+ // Allocate byte buffer if needed.
+ if ((bytes == null) || ((bytes.length * 2) < count)) {
+ bytes = new byte[count * 2];
+ }
+
+ // Convert float samples to LittleEndian bytes.
+ int byteIndex = 0;
+ for (int i = 0; i < count; i++) {
+ // Offset before casting so that we can avoid using floor().
+ // Also round by adding 0.5 so that very small signals go to zero.
+ double temp = (32767.0 * buffer[i + start]) + 32768.5;
+ int sample = ((int) temp) - 32768;
+ if (sample > Short.MAX_VALUE) {
+ sample = Short.MAX_VALUE;
+ } else if (sample < Short.MIN_VALUE) {
+ sample = Short.MIN_VALUE;
+ }
+ bytes[byteIndex++] = (byte) sample; // little end
+ bytes[byteIndex++] = (byte) (sample >> 8); // big end
+ }
+
+ line.write(bytes, 0, byteIndex);
+ }
+
+ @Override
+ public void stop() {
+ if (line != null) {
+ line.stop();
+ line.flush();
+ line.close();
+ line = null;
+ } else {
+ new RuntimeException("AudioOutput stop attempted when no line created.")
+ .printStackTrace();
+ }
+ }
+
+ @Override
+ public double getLatency() {
+ if (line == null) {
+ return 0.0;
+ }
+ int numBytes = line.getBufferSize();
+ int numFrames = numBytes / (BYTES_PER_SAMPLE * samplesPerFrame);
+ return ((double) numFrames) / frameRate;
+ }
+
+ @Override
+ public void close() {
+ }
+
+ }
+
+ private class JavaSoundInputStream extends JavaSoundStream implements AudioDeviceInputStream {
+ TargetDataLine line;
+
+ public JavaSoundInputStream(int deviceID, int frameRate, int samplesPerFrame) {
+ super(deviceID, frameRate, samplesPerFrame);
+ }
+
+ @Override
+ public void start() {
+ DataLine.Info info = new DataLine.Info(TargetDataLine.class, format);
+ if (!AudioSystem.isLineSupported(info)) {
+ // Handle the error.
+ LOGGER.error("JavaSoundInputStream - not supported." + format);
+ } else {
+ try {
+ line = (TargetDataLine) getDataLine(info);
+ int bufferSize = calculateBufferSize(suggestedInputLatency);
+ line.open(format, bufferSize);
+ LOGGER.debug("Input buffer size = " + bufferSize + " bytes.");
+ line.start();
+ } catch (Exception e) {
+ e.printStackTrace();
+ line = null;
+ }
+ }
+ }
+
+ @Override
+ public double read() {
+ double[] buffer = new double[1];
+ read(buffer, 0, 1);
+ return buffer[0];
+ }
+
+ @Override
+ public int read(double[] buffer) {
+ return read(buffer, 0, buffer.length);
+ }
+
+ @Override
+ public int read(double[] buffer, int start, int count) {
+ // Allocate byte buffer if needed.
+ if ((bytes == null) || ((bytes.length * 2) < count)) {
+ bytes = new byte[count * 2];
+ }
+ int bytesRead = line.read(bytes, 0, bytes.length);
+
+ // Convert BigEndian bytes to float samples
+ int bi = 0;
+ for (int i = 0; i < count; i++) {
+ int sample = bytes[bi++] & 0x00FF; // little end
+ sample = sample + (bytes[bi++] << 8); // big end
+ buffer[i + start] = sample * (1.0 / 32767.0);
+ }
+ return bytesRead / 4;
+ }
+
+ @Override
+ public void stop() {
+ if (line != null) {
+ line.drain();
+ line.close();
+ } else {
+ new RuntimeException("AudioInput stop attempted when no line created.")
+ .printStackTrace();
+ }
+ }
+
+ @Override
+ public double getLatency() {
+ if (line == null) {
+ return 0.0;
+ }
+ int numBytes = line.getBufferSize();
+ int numFrames = numBytes / (BYTES_PER_SAMPLE * samplesPerFrame);
+ return ((double) numFrames) / frameRate;
+ }
+
+ @Override
+ public int available() {
+ return line.available() / BYTES_PER_SAMPLE;
+ }
+
+ @Override
+ public void close() {
+ }
+
+ }
+
+ @Override
+ public AudioDeviceOutputStream createOutputStream(int deviceID, int frameRate,
+ int samplesPerFrame) {
+ return new JavaSoundOutputStream(deviceID, frameRate, samplesPerFrame);
+ }
+
+ @Override
+ public AudioDeviceInputStream createInputStream(int deviceID, int frameRate, int samplesPerFrame) {
+ return new JavaSoundInputStream(deviceID, frameRate, samplesPerFrame);
+ }
+
+ @Override
+ public double getDefaultHighInputLatency(int deviceID) {
+ return 0.300;
+ }
+
+ @Override
+ public double getDefaultHighOutputLatency(int deviceID) {
+ return 0.300;
+ }
+
+ @Override
+ public int getDefaultInputDeviceID() {
+ return defaultInputDeviceID;
+ }
+
+ @Override
+ public int getDefaultOutputDeviceID() {
+ return defaultOutputDeviceID;
+ }
+
+ @Override
+ public double getDefaultLowInputLatency(int deviceID) {
+ return 0.100;
+ }
+
+ @Override
+ public double getDefaultLowOutputLatency(int deviceID) {
+ return 0.100;
+ }
+
+ @Override
+ public int getDeviceCount() {
+ return deviceRecords.size();
+ }
+
+ @Override
+ public String getDeviceName(int deviceID) {
+ return deviceRecords.get(deviceID).name;
+ }
+
+ @Override
+ public int getMaxInputChannels(int deviceID) {
+ return deviceRecords.get(deviceID).maxInputs;
+ }
+
+ @Override
+ public int getMaxOutputChannels(int deviceID) {
+ return deviceRecords.get(deviceID).maxOutputs;
+ }
+
+ @Override
+ public int setSuggestedOutputLatency(double latency) {
+ suggestedOutputLatency = latency;
+ return 0;
+ }
+
+ @Override
+ public int setSuggestedInputLatency(double latency) {
+ suggestedInputLatency = latency;
+ return 0;
+ }
+
+ @Override
+ public String getName() {
+ return "JavaSound";
+ }
+
+}
diff --git a/src/main/java/com/jsyn/devices/javasound/MidiDeviceTools.java b/src/main/java/com/jsyn/devices/javasound/MidiDeviceTools.java
new file mode 100644
index 0000000..9cff095
--- /dev/null
+++ b/src/main/java/com/jsyn/devices/javasound/MidiDeviceTools.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.devices.javasound;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.sound.midi.MidiDevice;
+import javax.sound.midi.MidiSystem;
+import javax.sound.midi.MidiUnavailableException;
+import javax.sound.midi.Sequencer;
+import javax.sound.midi.Synthesizer;
+
+public class MidiDeviceTools {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(MidiDeviceTools.class);
+
+ /** Print the available MIDI Devices. */
+ public static void listDevices() {
+ // Ask the MidiSystem what is available.
+ MidiDevice.Info[] infos = MidiSystem.getMidiDeviceInfo();
+ // Print info about each device.
+ for (MidiDevice.Info info : infos) {
+ LOGGER.debug("MIDI Info: " + info.getDescription() + ", " + info.getName() + ", "
+ + info.getVendor() + ", " + info.getVersion());
+ // Get the device for more information.
+ try {
+ MidiDevice device = MidiSystem.getMidiDevice(info);
+ LOGGER.debug(" Device: " + ", #recv = " + device.getMaxReceivers()
+ + ", #xmit = " + device.getMaxTransmitters() + ", open = "
+ + device.isOpen() + ", " + device);
+ } catch (MidiUnavailableException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ /** Find a MIDI transmitter that contains text in the name. */
+ public static MidiDevice findKeyboard(String text) {
+ MidiDevice keyboard = null;
+ // Ask the MidiSystem what is available.
+ MidiDevice.Info[] infos = MidiSystem.getMidiDeviceInfo();
+ // Print info about each device.
+ for (MidiDevice.Info info : infos) {
+ try {
+ MidiDevice device = MidiSystem.getMidiDevice(info);
+ // Hardware devices are not Synthesizers or Sequencers.
+ if (!(device instanceof Synthesizer) && !(device instanceof Sequencer)) {
+ // Is this a transmitter?
+ // Might be -1 if unlimited.
+ if (device.getMaxTransmitters() != 0) {
+ if ((text == null)
+ || (info.getDescription().toLowerCase()
+ .contains(text.toLowerCase()))) {
+ keyboard = device;
+ LOGGER.debug("Chose: " + info.getDescription());
+ break;
+ }
+ }
+ }
+ } catch (MidiUnavailableException e) {
+ e.printStackTrace();
+ }
+ }
+ return keyboard;
+ }
+
+ public static MidiDevice findKeyboard() {
+ return findKeyboard(null);
+ }
+
+}
diff --git a/src/main/java/com/jsyn/devices/jportaudio/JPortAudioDevice.java b/src/main/java/com/jsyn/devices/jportaudio/JPortAudioDevice.java
new file mode 100644
index 0000000..15ab9ed
--- /dev/null
+++ b/src/main/java/com/jsyn/devices/jportaudio/JPortAudioDevice.java
@@ -0,0 +1,264 @@
+/*
+ * 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.devices.jportaudio;
+
+import com.jsyn.devices.AudioDeviceInputStream;
+import com.jsyn.devices.AudioDeviceManager;
+import com.jsyn.devices.AudioDeviceOutputStream;
+import com.portaudio.BlockingStream;
+import com.portaudio.DeviceInfo;
+import com.portaudio.HostApiInfo;
+import com.portaudio.PortAudio;
+import com.portaudio.StreamParameters;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class JPortAudioDevice implements AudioDeviceManager {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(JPortAudioDevice.class);
+
+ private double suggestedOutputLatency = 0.030;
+ private double suggestedInputLatency = 0.050;
+ private static final int FRAMES_PER_BUFFER = 128;
+
+ // static Logger logger = Logger.getLogger( JPortAudioDevice.class.getName() );
+
+ public JPortAudioDevice() {
+ PortAudio.initialize();
+ }
+
+ @Override
+ public int getDeviceCount() {
+ return PortAudio.getDeviceCount();
+ }
+
+ @Override
+ public String getDeviceName(int deviceID) {
+ DeviceInfo deviceInfo = PortAudio.getDeviceInfo(deviceID);
+ HostApiInfo hostInfo = PortAudio.getHostApiInfo(deviceInfo.hostApi);
+ return deviceInfo.name + " - " + hostInfo.name;
+ }
+
+ @Override
+ public int getDefaultInputDeviceID() {
+ return PortAudio.getDefaultInputDevice();
+ }
+
+ @Override
+ public int getDefaultOutputDeviceID() {
+ return PortAudio.getDefaultOutputDevice();
+ }
+
+ @Override
+ public int getMaxInputChannels(int deviceID) {
+ if (deviceID < 0) {
+ deviceID = PortAudio.getDefaultInputDevice();
+ }
+ return PortAudio.getDeviceInfo(deviceID).maxInputChannels;
+ }
+
+ @Override
+ public int getMaxOutputChannels(int deviceID) {
+ if (deviceID < 0) {
+ deviceID = PortAudio.getDefaultOutputDevice();
+ }
+ return PortAudio.getDeviceInfo(deviceID).maxOutputChannels;
+ }
+
+ @Override
+ public double getDefaultLowInputLatency(int deviceID) {
+ if (deviceID < 0) {
+ deviceID = PortAudio.getDefaultInputDevice();
+ }
+ return PortAudio.getDeviceInfo(deviceID).defaultLowInputLatency;
+ }
+
+ @Override
+ public double getDefaultHighInputLatency(int deviceID) {
+ if (deviceID < 0) {
+ deviceID = PortAudio.getDefaultInputDevice();
+ }
+ return PortAudio.getDeviceInfo(deviceID).defaultHighInputLatency;
+ }
+
+ @Override
+ public double getDefaultLowOutputLatency(int deviceID) {
+ if (deviceID < 0) {
+ deviceID = PortAudio.getDefaultOutputDevice();
+ }
+ return PortAudio.getDeviceInfo(deviceID).defaultLowOutputLatency;
+ }
+
+ @Override
+ public double getDefaultHighOutputLatency(int deviceID) {
+ if (deviceID < 0) {
+ deviceID = PortAudio.getDefaultOutputDevice();
+ }
+ return PortAudio.getDeviceInfo(deviceID).defaultHighOutputLatency;
+ }
+
+ @Override
+ public int setSuggestedOutputLatency(double latency) {
+ suggestedOutputLatency = latency;
+ return 0;
+ }
+
+ @Override
+ public int setSuggestedInputLatency(double latency) {
+ suggestedInputLatency = latency;
+ return 0;
+ }
+
+ @Override
+ public AudioDeviceOutputStream createOutputStream(int deviceID, int frameRate,
+ int samplesPerFrame) {
+ return new JPAOutputStream(deviceID, frameRate, samplesPerFrame);
+ }
+
+ @Override
+ public AudioDeviceInputStream createInputStream(int deviceID, int frameRate, int samplesPerFrame) {
+ return new JPAInputStream(deviceID, frameRate, samplesPerFrame);
+ }
+
+ private static class JPAStream {
+ BlockingStream blockingStream;
+ float[] floatBuffer = null;
+ int samplesPerFrame;
+
+ public void close() {
+ blockingStream.close();
+ }
+
+ public void start() {
+ blockingStream.start();
+ }
+
+ public void stop() {
+ blockingStream.stop();
+ }
+
+ }
+
+ private class JPAOutputStream extends JPAStream implements AudioDeviceOutputStream {
+
+ private JPAOutputStream(int deviceID, int frameRate, int samplesPerFrame) {
+ this.samplesPerFrame = samplesPerFrame;
+ StreamParameters streamParameters = new StreamParameters();
+ streamParameters.channelCount = samplesPerFrame;
+ if (deviceID < 0) {
+ deviceID = PortAudio.getDefaultOutputDevice();
+ }
+ streamParameters.device = deviceID;
+ streamParameters.suggestedLatency = suggestedOutputLatency;
+ int flags = 0;
+ LOGGER.debug("Audio output on " + getDeviceName(deviceID));
+ blockingStream = PortAudio.openStream(null, streamParameters, frameRate,
+ FRAMES_PER_BUFFER, flags);
+ }
+
+ /** Grossly inefficient. Call the array version instead. */
+ @Override
+ public void write(double value) {
+ double[] buffer = new double[1];
+ buffer[0] = value;
+ write(buffer, 0, 1);
+ }
+
+ @Override
+ public void write(double[] buffer) {
+ write(buffer, 0, buffer.length);
+ }
+
+ @Override
+ public void write(double[] buffer, int start, int count) {
+ // Allocate float buffer if needed.
+ if ((floatBuffer == null) || (floatBuffer.length < count)) {
+ floatBuffer = new float[count];
+ }
+ for (int i = 0; i < count; i++) {
+
+ floatBuffer[i] = (float) buffer[i + start];
+ }
+ blockingStream.write(floatBuffer, count / samplesPerFrame);
+ }
+
+ @Override
+ public double getLatency() {
+ return blockingStream.getInfo().outputLatency;
+ }
+ }
+
+ private class JPAInputStream extends JPAStream implements AudioDeviceInputStream {
+ private JPAInputStream(int deviceID, int frameRate, int samplesPerFrame) {
+ this.samplesPerFrame = samplesPerFrame;
+ StreamParameters streamParameters = new StreamParameters();
+ streamParameters.channelCount = samplesPerFrame;
+ if (deviceID < 0) {
+ deviceID = PortAudio.getDefaultInputDevice();
+ }
+ streamParameters.device = deviceID;
+ streamParameters.suggestedLatency = suggestedInputLatency;
+ int flags = 0;
+ LOGGER.debug("Audio input from " + getDeviceName(deviceID));
+ blockingStream = PortAudio.openStream(streamParameters, null, frameRate,
+ FRAMES_PER_BUFFER, flags);
+ }
+
+ @Override
+ public double read() {
+ double[] buffer = new double[1];
+ read(buffer, 0, 1);
+ return buffer[0];
+ }
+
+ @Override
+ public int read(double[] buffer) {
+ return read(buffer, 0, buffer.length);
+ }
+
+ @Override
+ public int read(double[] buffer, int start, int count) {
+ // Allocate float buffer if needed.
+ if ((floatBuffer == null) || (floatBuffer.length < count)) {
+ floatBuffer = new float[count];
+ }
+ blockingStream.read(floatBuffer, count / samplesPerFrame);
+
+ for (int i = 0; i < count; i++) {
+
+ buffer[i + start] = floatBuffer[i];
+ }
+ return count;
+ }
+
+ @Override
+ public double getLatency() {
+ return blockingStream.getInfo().inputLatency;
+ }
+
+ @Override
+ public int available() {
+ return blockingStream.getReadAvailable() * samplesPerFrame;
+ }
+
+ }
+
+ @Override
+ public String getName() {
+ return "JPortAudio";
+ }
+}