Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for 24 bit output devices on Windows #85

Closed
7 of 10 tasks
kevinstadler opened this issue Aug 26, 2023 · 2 comments
Closed
7 of 10 tasks

Add support for 24 bit output devices on Windows #85

kevinstadler opened this issue Aug 26, 2023 · 2 comments
Labels
enhancement New feature or request
Milestone

Comments

@kevinstadler
Copy link
Collaborator

kevinstadler commented Aug 26, 2023

Many audio interfaces currently throw a javax.sound.sampled.LineUnavailableException: line with format PCM_SIGNED 44100.0 Hz, 16 bit, stereo, 4 bytes/frame, little-endian not supported on Windows because 16 bit resolution is hard-coded in JSyn's JavaSoundAudioDevice (or don't list any audio devices to begin with, such as the Motu Ultralite mk5).

This might be fixable by bundling the JPortAudio bindings for Windows and making use of the JPortAudioDevice instead.

Work-in-progress

It turns out that getting (and) keeping the engine and synth in a valid state between device manager/synth switches is actually quite tricky. It goes something like this:

Assuming we are on JavaSound:

After this first (silent) startup, there is a running SynthesisEngine with volume and output nodes.

  • if there is more than one device with outputs, should the selected device info be printed to the console?

Whenever the user selects a different input or output device, do the following:

  • if we're still on JavaSound: try to switch to PortAudio and, if successful, find the input and output devices with the same or similar names (this might need several attempts, again MME...), printing a note about changed device id's/names ultimately selected. This should also happen if Sound.outputDevice() was the first call to the library!

    • changing device manager requires completely purging the existing SynthesisEngine and all of its associated nodes. Check the Engine's playingUnits tracker for anything already instantiated and warn the user about it!
    • note that when switching audio device managers they need to be instantiated directly, JSyn's AudioDeviceManagerFactory only ever returns whichever device manager it instantiated first
  • if we're already on PortAudio: restart the synth on whatever devices given (as long as they have appropriate channels)

    • If there is a problem with opening a line (often with MME devices), either let the AudioDeviceManager print the error to the console, or probe and throw an exception to stop the sketch?
  • an open question is who/where should execute System.loadLibrary() for the appropriate native libraries. When the JPortAudioDevice is first instantiated it runs this static import block, not sure how it would raise a failure https://github.com/philburk/portaudio-java/blob/2ec5cc47d6f8abe85ddb09c34e69342bfe72c60b/src/main/java/com/portaudio/PortAudio.java#L101-L121 either way System.out would need to be diverted as this is happening, to suppress JPortAudio output in the console, like so:

    static {
    PrintStream originalStream = System.out;
    System.setOut(new PrintStream(new OutputStream(){
    public void write(int b) { }
    }));
    try {
    System.loadLibrary("portaudio_x64");
    } catch (UnsatisfiedLinkError e) {
    // System.loadLibrary("jportaudio_0_1_0");
    }
    System.setOut(originalStream);
    }

  • including (completely optional) PortAudio support on Mac might be desirable, e.g. when using some Bluetooth devices with strange line constraints (such as the Sony WH-CH510) with JavaSound the entire system audio seems to be forced into a low fidelity 16 bit mode, with the Processing audio output reminiscent of what is described here: https://www.reddit.com/r/processing/comments/qtgb1q/audio_quality_is_slow_buzzy_and_distorted/

    • OSX first refuses to load the jnilib, instructions on giving permission in the system preferences would need to be added to the console (added in 843cacf)

Two outstanding glitches/corner cases:

  • when JPortAudio finds no output devices, it currently displays a rather uninformative "-1, possibly no available default device" exception
  • still need to test whether the following code in selectOutputDevice() also automatically switches to PortAudio as expected when called with a device id that (erroneously) shows 0 output channels on JavaSound:
    // 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);
@kevinstadler kevinstadler changed the title Look into javax.sound.sampled.LineUnavailableException: line with format PCM_SIGNED 44100.0 Hz, 16 bit, stereo, 4 bytes/frame, little-endian not supported Add support for 24 bit output devices on Windows Sep 6, 2023
@kevinstadler kevinstadler added this to the 2.4 milestone Sep 6, 2023
@kevinstadler kevinstadler added the enhancement New feature or request label Sep 6, 2023
@kevinstadler
Copy link
Collaborator Author

kevinstadler commented Sep 8, 2023

When the PortAudio dlls and jportaudio.jar are bundled with the library, JSyn will automatically use the JPortAudioDevice as long as

System.loadLibrary("portaudio_x64");

is called before instantiating the AudioDeviceManager.

Since the PortAudio support is still considered experimental, it should probably not become the default mode for Windows, but only be loaded when:

  • manually requested by the user, or
  • for convenience, automatically when the library realises that it is necessary. How exactly that should be detected is a bit tricky:
    • some 24 bit output devices do show up under JavaSound but with a wrong number of output channels (e.g. the Presonus c26 shows 2 instead of 4), when starting a Synthesizer pointing to the device it throws a LineUnavailableException, which is a clear indicator that PortAudio should be loaded, after which it shows the correct number of channels. (It might be necessary to create a new Synthesizer for the JPortAudioDevice to take come into action.)
    • some devices (e.g. Motu UltraLight Mk5) show up with 0 input and output channels, which is by itself an indicator that JavaSound support on this device is limited -- then again, just because there is some device listed that might be better supported under PortAudio does not mean that the user will actually want to use it..

kevinstadler added a commit that referenced this issue Sep 8, 2023
Known issue: when the library/ folder contains a `windows-amd64`
subdirectory (with the bundled dlls), Processing4 prints a (bogus)
"<Libraryname> does not run on this architecture" message to the
Processing console when using the library on any other architecture
@kevinstadler
Copy link
Collaborator Author

cfb0a1b also prepared the option of including PortAudio libraries for Mac, since JavaSound can sometimes negatively affect the sound output quality when using Bluetooth devices.

Closing this, with many thanks to @trackme518 for testing and support!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

1 participant