diff --git a/src/processing/sound/Engine.java b/src/processing/sound/Engine.java index 743b529..78c97d4 100644 --- a/src/processing/sound/Engine.java +++ b/src/processing/sound/Engine.java @@ -47,7 +47,13 @@ private static AudioDeviceManager createDefaultAudioDeviceManager() { private static AudioDeviceManager createAudioDeviceManager(boolean portAudio) { if (!portAudio) { - return Engine.createDefaultAudioDeviceManager(); + AudioDeviceManager a = Engine.createDefaultAudioDeviceManager(); + if (a.getDefaultOutputDeviceID() != -1) { + return a; + } + // if the default device manager lists no output devices, go straight for + // portaudio + Engine.printMessage("Didn't find any output devices with the default driver, trying PortAudio..."); } // hide JPortAudio init messages from console PrintStream originalStream = System.out; @@ -149,6 +155,7 @@ private Engine(AudioDeviceManager audioDeviceManager, int outputDevice) { // this method starts the synthesizer -- if the output fails, it might // create a new PortAudio synth on the fly and try to start that this.selectOutputDevice(outputDevice); + this.selectInputDevice(-1); } private void createSynth(AudioDeviceManager deviceManager) { @@ -187,6 +194,7 @@ protected boolean usePortAudio(boolean portAudio) { this.createSynth(Engine.createAudioDeviceManager(portAudio)); // if this was called by the user (from the MultiChannel class), its their // responsibilit to select output device and start the synth! + this.inputDevice = -1; } return this.isUsingPortAudio(); } @@ -214,6 +222,9 @@ private void stopSynth() { } private void startSynth() { + // it looks like some synth errors (such as Blocking API not implemented on + // Windows PortAudio) are unrecoverable, so it would actually be good to + // *always* purge the entire synth and not just stop/start it... this.stopSynth(); this.output = new ChannelOut[this.synth.getAudioDeviceManager().getMaxOutputChannels(this.outputDevice)]; @@ -233,7 +244,6 @@ private void startSynth() { // prevent IndexOutOfBoundsException on input-less devices int inputChannels = this.inputDevice >= 0 ? this.synth.getAudioDeviceManager().getMaxInputChannels(this.inputDevice) : 0; - // TODO suppress Mac Portaudio stdout this.synth.start(this.sampleRate, this.inputDevice, inputChannels, this.outputDevice, this.synth.getAudioDeviceManager().getMaxOutputChannels(this.outputDevice)); @@ -259,9 +269,29 @@ private boolean checkDeviceHasInputs(int deviceId) { } protected int selectInputDevice(int deviceId) { - if (this.isValidDeviceId(deviceId)) { + if (deviceId == -1) { + int defaultInputDevice = this.synth.getAudioDeviceManager().getDefaultInputDeviceID(); + if (defaultInputDevice == -1) { + Engine.printWarning("Did not find any sound devices with input channels, you won't be able to use the AudioIn class"); + } else { + // if the default device is a WDM-KS binding better not touch it, + // selecting it might ruin the synth object for good + if (!this.getDeviceName(defaultInputDevice).contains("WDM-KS")) { + // otherwise, give it a shot + try { + this.selectInputDevice(this.synth.getAudioDeviceManager().getDefaultInputDeviceID()); + } catch (RuntimeException e) { + Engine.printWarning("failed to initialise default input device '" + this.getDeviceName(deviceId) + "' (" + e.getMessage() + ")"); + this.inputDevice = -1; + this.startSynth(); + } + } + } + } else if (this.isValidDeviceId(deviceId)) { if (this.checkDeviceHasInputs(deviceId)) { + int oldInputDevice = this.inputDevice; this.inputDevice = deviceId; + // might throw a RuntimeException (see above) this.startSynth(); } else { Engine.printError("audio device #" + deviceId + " has no input channels"); @@ -289,6 +319,20 @@ private void probeDeviceOutputLine(int deviceId, int sampleRate) throws LineUnav line.close(); } + /** + * Go through the list of candidate device ids until the first one that works + * @throws RuntimeException if none of the output devices work + */ + protected int selectOutputDevice(int[] candidates) { + for (int i : candidates) { + try { + return this.selectOutputDevice(i); + } catch (RuntimeException e) { + } + } + throw new RuntimeException("failed to play to any of the output devices"); + } + /** * After calling this method, the synth is running. * @param deviceId device index, or -1 to select/find an appropriate stereo @@ -296,26 +340,17 @@ private void probeDeviceOutputLine(int deviceId, int sampleRate) throws LineUnav */ protected int selectOutputDevice(int deviceId) { if (deviceId == -1) { - // if the default device does not have a stereo output, loop through - for (int i : IntStream.concat( - IntStream.of(this.synth.getAudioDeviceManager().getDefaultOutputDeviceID()), - IntStream.range(0, this.synth.getAudioDeviceManager().getDeviceCount())).toArray()) { - try { - return this.selectOutputDevice(i); - } catch (RuntimeException e) { - // e.printStackTrace(); - } + // if the default device does not work, loop through + try { + return this.selectOutputDevice(IntStream.concat( + IntStream.of(this.synth.getAudioDeviceManager().getDefaultOutputDeviceID()), + IntStream.range(0, this.synth.getAudioDeviceManager().getDeviceCount())).toArray()); + } catch (RuntimeException e) { + // fatal + throw new RuntimeException("Could not find any supported audio devices with a stereo output"); } - throw new RuntimeException("Could not find any supported audio devices with a stereo output"); - // the available devices and just select the first one? - // if (this.outputDevice == -1) { - // return; - // } - // this.selectInputDevice(this.synth.getAudioDeviceManager().getDefaultInputDeviceID()); - // if (this.inputDevice == -1) { - // Engine.printWarning("could not find any sound devices with input channels, you won't be able to use the AudioIn class"); - // } } else if (!this.isValidDeviceId(deviceId) || this.outputDevice == deviceId) { + // prints an error or does nothing return this.outputDevice; } @@ -332,7 +367,11 @@ protected int selectOutputDevice(int deviceId) { try { // TODO does this also work as expected if the device is currently // listed as having 0 output channels? + // if (this.synth.getAudioDeviceManager().getMaxOutputChannels(deviceId) == 0) { + // Engine.printMessage(...); + // } else { this.probeDeviceOutputLine(deviceId, this.sampleRate); + // all is well, move along to the bottom... } catch (LineUnavailableException e) { // try portaudio access to the same device -- need get the name of the // old output device and re-select it on the new device manager @@ -343,7 +382,7 @@ protected int selectOutputDevice(int deviceId) { throw new RuntimeException(e); } // this might replace this.synth - Engine.printMessage("Output device '" + targetDeviceName + "' did not work with the default driver, automatically switched to PortAudio."); + Engine.printMessage("Output device '" + targetDeviceName + "' did not work with the default driver, switching to PortAudio..."); int newDeviceIdForOldDevice = this.synth.getAudioDeviceManager().getDefaultOutputDeviceID(); try { // TODO also loop through candidates @@ -355,23 +394,25 @@ protected int selectOutputDevice(int deviceId) { } } catch (RuntimeException ee) { // probably a generic device name like 'Primary Sound Device' - Engine.printMessage("Switched to new default output device '" + this.getDeviceName(newDeviceIdForOldDevice) + "'."); + Engine.printMessage("Switched to new default output device '" + this.getDeviceName(newDeviceIdForOldDevice) + "'"); } // recursive fun return this.selectOutputDevice(newDeviceIdForOldDevice); } } + + // finally made it to the 'normal' output device selection code if (this.checkDeviceHasOutputs(deviceId)) { this.outputDevice = deviceId; this.startSynth(); } else { - Engine.printError("audio device '" + this.getDeviceName(deviceId) + "' has no stereo output channel"); + Engine.printWarning("audio device '" + this.getDeviceName(deviceId) + "' has no stereo output channel"); } return this.outputDevice; } protected String getDeviceName(int deviceId) { - return this.synth.getAudioDeviceManager().getDeviceName(deviceId).trim(); + return this.isValidDeviceId(deviceId) ? this.synth.getAudioDeviceManager().getDeviceName(deviceId).trim() : ""; } protected int getDeviceIdByName(String deviceName) { diff --git a/src/processing/sound/MultiChannel.java b/src/processing/sound/MultiChannel.java index 01d7e42..9ad0f32 100644 --- a/src/processing/sound/MultiChannel.java +++ b/src/processing/sound/MultiChannel.java @@ -78,6 +78,7 @@ public static boolean usePortAudio() { Engine engine = Engine.getEngine(null, true); try { if (engine.usePortAudio(true)) { + // all good, go for default device engine.selectOutputDevice(-1); } return engine.isUsingPortAudio(); diff --git a/src/processing/sound/Sound.java b/src/processing/sound/Sound.java index 7e6c677..a97da74 100644 --- a/src/processing/sound/Sound.java +++ b/src/processing/sound/Sound.java @@ -261,10 +261,10 @@ public static void status() { Engine.printMessage("synthesis has been running for " + String.format("%.2f", e.getCurrentTime()) + " seconds, generated " + e.getFrameCount() + " frames (framerate " + e.getFrameRate() + ")"); Engine.println(" audio devices used by " + e.getAudioDeviceManager().getName() + ":"); if (Engine.getEngine().inputDevice != -1) { - Engine.println(" input from '" + e.getAudioDeviceManager().getDeviceName(Engine.getEngine().inputDevice) + "': " + e.getAudioDeviceManager().getMaxInputChannels(Engine.getEngine().inputDevice) + " channels, latency " + e.getInputLatency() + "ms"); + Engine.println(" input from '" + e.getAudioDeviceManager().getDeviceName(Engine.getEngine().inputDevice) + "': " + e.getAudioDeviceManager().getMaxInputChannels(Engine.getEngine().inputDevice) + " channels, latency " + Math.round(1000*e.getInputLatency()) + "ms"); } - Engine.println(" output on '" + e.getAudioDeviceManager().getDeviceName(Engine.getEngine().outputDevice) + "': " + e.getAudioDeviceManager().getMaxOutputChannels(Engine.getEngine().outputDevice) + " channels, latency " + e.getOutputLatency() + "ms"); - Engine.println("\n elements in synthesizer network: " + Engine.getEngine().addedUnits.size()); + Engine.println(" output on '" + e.getAudioDeviceManager().getDeviceName(Engine.getEngine().outputDevice) + "': " + e.getAudioDeviceManager().getMaxOutputChannels(Engine.getEngine().outputDevice) + " channels, latency " + Math.round(1000*e.getOutputLatency()) + "ms"); + Engine.println("\n nodes in synthesizer network: " + Engine.getEngine().addedUnits.size()); long nSamples = 0; for (FloatSample s : SoundFile.SAMPLECACHE.values()) { nSamples += s.getNumFrames() * s.getChannelsPerFrame();