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/main/java/com/jsyn/devices | |
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/main/java/com/jsyn/devices')
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"; + } +} |