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

Plugging in headphones does not switch over from builtin speakers #5577

Open
danielmkarlsson opened this issue Sep 26, 2021 · 16 comments
Open
Assignees
Labels
bug Issues that relate to unexpected/unwanted behavior. Don't use for PRs. comp: scsynth os: macOS

Comments

@danielmkarlsson
Copy link
Contributor

Environment

  • SuperCollider version: 3.12.1
  • Operating system: MacOS 11.5.2
  • Other details: MacBook Air (M1, 2020)

Steps to reproduce

Boot SuperCollider.
Choose builtin speakers.
Play any sound.
Plug in headphones.

(
SynthDef(\default, {
		arg dur, attack=0.01, release=1.0,
		t_gate=1, out, freq=442, cutoff=5500,
		rq=1, pan=0.0, amp=0.5;
		var env = EnvGen.kr(Env.perc(attack, release), t_gate, timeScale: dur, doneAction: 2);
		var sig = Saw.ar(freq: freq, mul: env);
		sig = RLPF.ar(sig, cutoff.clip(20.0, 20000.0), rq.clip(0.0,1.0));
		sig = Pan2.ar(sig, pan);
		Out.ar(out, sig * amp);
	}).add;
Pbind().play;
)

Expected vs. actual behavior

Notice how the sound is still coming out of the builtin speakers and not out of the headphones. I expected the sound to switch over to the headphones when I plugged them in. I tried reproducing this using another program. I could not. I am convinced that this is an issue with SuperCollider.

@danielmkarlsson danielmkarlsson added the bug Issues that relate to unexpected/unwanted behavior. Don't use for PRs. label Sep 26, 2021
@dyfer
Copy link
Member

dyfer commented Sep 28, 2021

Yes, I have seen this too on M1 MBP under Big Sur.
I believe the way we handle switching to headphone output is by checking for the device name after the system output device changes (when plugging in the headphones) and if it matches "Built-in Output" we switch to that device. This name seems to have changed to "External Headphones" or something like that either in Catalina or on M1 MBP.
EDIT: I'm not sure if my original assessment was correct.

@danielmkarlsson
Copy link
Contributor Author

I'd now like to add another thing that I would like you to try:

Boot SuperCollider.
Choose headphones.
Play any sound.
Unplug headphones.
Notice how there is no sound coming out of built in speakers.
Plug headphones back in.
Notice how at this point there is no sound coming out of the headphones either.

Weird huh?

Try doing the same thing when playing sound in Quicktime Player to experience what I am sure everyone wants to have happen when plugging in or unplugging headphones.

(I am still running the same machine and SC version as when I originally posted)

@dyfer
Copy link
Member

dyfer commented May 25, 2022

Weird huh?

Not weird :) unfortunately we currently don't handle audio device switching on the M1 macs.
Unplugging and re-plugging headphones on these machines is causing the synthesis to freeze, in the same way as unplugging and re-plugging external audio interface does (and always did in SC).

Someone needs to take a look what change in the device switching layer to bring this functionality back for M1 macs.
I think it would be great if SC recovered after re-plugging the original audio device, but this is not currently implemented.

@scztt
Copy link
Contributor

scztt commented May 27, 2022

The "unplugging headphones" case is just a special case of the more general problem of switching audio devices while the server is running. On Mac, this ultimately comes down to running DriverSetup on a new device while the server is running (https://github.com/supercollider/supercollider/blob/develop/server/scsynth/SC_CoreAudio.cpp#L529). This returns two values - sampleRate and numSamples (e.g. number of samples per callback), which are used to set other things up in the server.

My GUESS is that adding a new server command to switch audio devices, and using that to basically call DriverSetup again would be relatively straightforward IF the resulting sampleRate and numSamples where the same. We are able to specify a hardware buffer size via mPreferredHardwareBufferFrameSize, so as long as this is honored by a new device we shouldn't have a problem with numSamples. This is a very worthwhile change overall - switching devices while running would be extremely useful for the server. Anything than more than this "trivial" (e.g. same sample rate etc) switch is probably not something to spend a lot of time on: audio stacks often just re-initialize everything when you switch devices - doing this on the SC server is just not something it was designed for sadly.

If anyone picks this up, I would recommend NOT approaching this from the perspective of "switching devices when you plug in headphones", but rather just implementing it as a "switch the audio device" server message, and let the client decide on the behavior (it would be easy enough to add a primitive to list audio devices in sclang, and then just let sclang handle the switching logic).

@dyfer
Copy link
Member

dyfer commented May 27, 2022

@scztt thanks for the comments. Just one note here - switching headphones to speakers does work on intel hardware (at least before Big Sur/Monterey, can't test these ATM), so some form of device switch is already implemented AFAIU. First, it's a matter of figuring out what changed lately and bringing back that functionality on M1.

As for device switching other than built-in/headphones (which is expected and should work), I also think it's worthwhile to have this implemented for other devices. BUT if anyone attempts this - it should not be an automatic switch in all cases. At the very least the user should be prompted what to do (IMO MainStage strikes a good balance between functionality and stage-ready reliability - it asks when the devices change whether to use the new one or keep using the old one).

@scztt
Copy link
Contributor

scztt commented Jun 2, 2022

@scztt thanks for the comments. Just one note here - switching headphones to speakers does work on intel hardware (at least before Big Sur/Monterey, can't test these ATM), so some form of device switch is already implemented AFAIU. First, it's a matter of figuring out what changed lately and bringing back that functionality on M1.

I believe it's actually a hardware-specific thing and not an OS thing - older macbooks have a single device that does the switch, newer macbooks have two devices where the OS handles the switch. Hard to find info, but here's one article: https://weblog.rogueamoeba.com/2018/11/09/apples-newest-macs-include-better-built-in-audio-devices/

Yeah, I agree that automatic switching could be very confusing / disruptive. But, from the perspective SC unplugging your headphones is identical to unplugging your USB audio interface. AFAIK the only differentiating factor we would have there is the name of the device? It's possible that there are flags deeper in CoreAudio that signify e.g. "you should fall back to this device if this one goes away", but it feels more straightforward to simply let SC specify the behavior openly and have the default be ONLY switching in the narrow case of "External Headphones" and "MacBook Pro Speakers".

@scztt
Copy link
Contributor

scztt commented Jun 2, 2022

It's disappointing that Apple didn't provide any automatic switching options for their aggregate devices, since already work very well as an abstraction over multiple audio devices....

@dyfer
Copy link
Member

dyfer commented Jun 2, 2022

I believe it's actually a hardware-specific thing and not an OS thing

Thanks for that clarification. Because the device name did change on older hardware, and because of comments like this one in the code I thought that we were actually handling device switching for built-in speakers/headphones... but it looks like you're right, we were most likely not doing that and the device did not actually change. I was trying to trace that switching behavior in the SC's CoreAudio backend and I couldn't follow it, maybe it's because it's not actually happening :)

@telephon
Copy link
Member

Here is a similar issue in Audacity, maybe this gives some clues as to where to move? audacity/audacity#2409

@telephon
Copy link
Member

Btw. I think this is a very important issue. There are quite a few situations in which supercollider has become very awkward to use, to say the least. E.g. you are working with speakers and need to plug in your headphones because you are disturbing someone. Currently we have to explicitly change the settings and restart the server ...

@danielmkarlsson
Copy link
Contributor Author

danielmkarlsson commented Feb 5, 2023

Hey y'all. Just popping in here to add a tiny bit of information that may be good for y'all to have.
I just noticed that VLC Version 3.0.16 running on the same machine with the same OS that I originally posted with is able to switch between headphones and internal speakers with no hickups of any kind. Perhaps I am naive about the possibility of finding clues by looking at how another open source software project solved this problem, but I thought, what the heck you know? Maybe it could do more good to share this information with y'all rather than me just sitting on it. Best of luck to everyone involved. Please know that I am cheering you on loads.

https://github.com/videolan/vlc

@danielmkarlsson
Copy link
Contributor Author

Hello again. It's me, the original poster. Just thought I'd bump this thread and provide this little bit of information:

Reaper has recently fixed this issue in Reaper, so don't feel bad for not having gotten around to fixing this in SC. Reaper is a commercial software and it took them until very recently (May 30th, 2023) to get this issue taken care of. I hope y'all are not too swamped with stuff that needs fixing and that eventually you'll get around to fixing this issue for SC. Again, I really think it would be good to take a look at how VLC solved this, link in my previous post, as they are also an open source application. Maybe it can be quick and painless? Maybe it can be a matter of copying and pasting a line or two. Who knows?

Please know that I love and admire you for the important work that you do with this software.

@telephon
Copy link
Member

In audacity this still seems unsolved. What a mess! audacity/audacity#2409

It may have to do with different hardware as well? #5577 (comment)

@iamjonny
Copy link

Hello again. It's me, the original poster. Just thought I'd bump this thread and provide this little bit of information:

Reaper has recently fixed this issue in Reaper, so don't feel bad for not having gotten around to fixing this in SC. Reaper is a commercial software and it took them until very recently (May 30th, 2023) to get this issue taken care of. I hope y'all are not too swamped with stuff that needs fixing and that eventually you'll get around to fixing this issue for SC. Again, I really think it would be good to take a look at how VLC solved this, link in my previous post, as they are also an open source application. Maybe it can be quick and painless? Maybe it can be a matter of copying and pasting a line or two. Who knows?

Please know that I love and admire you for the important work that you do with this software.

Hi, this has been on my radar since discovering SP and SC.
I took the liberty of reaching out to Mr Reaper after your useful insights^, and received this information, on how this was fixed there... I don't know if this also is for windows, but hope this helps!

If opening the default devices, need to add a listener for kAudioHardwarePropertyDefaultInputDevice etc and if those devices change, re-open the devices...

Searching for this listener, example found @ https://github.com/retrography/audioswitch/blob/master/device.c

@jonnyhotchkiss
Copy link

Hey y'all. Just popping in here to add a tiny bit of information that may be good for y'all to have. I just noticed that VLC Version 3.0.16 running on the same machine with the same OS that I originally posted with is able to switch between headphones and internal speakers with no hickups of any kind. Perhaps I am naive about the possibility of finding clues by looking at how another open source software project solved this problem, but I thought, what the heck you know? Maybe it could do more good to share this information with y'all rather than me just sitting on it. Best of luck to everyone involved. Please know that I am cheering you on loads.

https://github.com/videolan/vlc

Hi, I also noticed this is not an issue with beloved REAPER, so I asked one of the experts, and the masterful Mr. Frankel advised

If opening the default devices, need to add a listener for kAudioHardwarePropertyDefaultInputDevice etc
and if those devices change, re-open the devices...

looking in the codebase this is found, but appears to be commented out...

            //			err = AudioHardwareGetProperty(kAudioHardwarePropertyDefaultInputDevice, &count, (void *) &

Ax'ing the pseudo-expert, GPT proposed

#include "SC_CoreAudio.h"
#include <CoreAudio/AudioHardware.h>

// Function to re-open default devices when they change
void SC_CoreAudio::handleDefaultDeviceChange(AudioDeviceID device, UInt32 propertyID) {
    if (propertyID == kAudioHardwarePropertyDefaultInputDevice ||
        propertyID == kAudioHardwarePropertyDefaultOutputDevice) {
        // Re-open default devices
        openDefaultDevices();
    }
}

// Initialize function
void SC_CoreAudio::initialize() {
    // Initialize superclass
    SC_AudioIO::initialize();

    // Add listener for default device changes
    AudioObjectPropertyAddress propertyAddress;
    propertyAddress.mSelector = kAudioHardwarePropertyDefaultInputDevice;
    propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
    propertyAddress.mElement = kAudioObjectPropertyElementMaster;
    AudioObjectAddPropertyListener(kAudioObjectSystemObject, &propertyAddress, defaultDeviceChangeListener, this);
    
    propertyAddress.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
    AudioObjectAddPropertyListener(kAudioObjectSystemObject, &propertyAddress, defaultDeviceChangeListener, this);
}

// Function to open default devices
void SC_CoreAudio::openDefaultDevices() {
    // Close current devices if they are open
    if (isRunning()) {
        stop();
    }
    if (inputDevice) {
        inputDevice->close();
    }
    if (outputDevice) {
        outputDevice->close();
    }

    // Open default input and output devices
    inputDevice = new SC_CoreAudioInputDevice(this);
    outputDevice = new SC_CoreAudioOutputDevice(this);

    // Start audio I/O
    start();
}

// Callback function for default device changes
OSStatus SC_CoreAudio::defaultDeviceChangeListener(AudioObjectID inObjectID, UInt32 inNumberAddresses, const AudioObjectPropertyAddress inAddresses[], void *inClientData) {
    SC_CoreAudio *coreAudio = static_cast<SC_CoreAudio *>(inClientData);
    for (UInt32 i = 0; i < inNumberAddresses; ++i) {
        coreAudio->handleDefaultDeviceChange(inObjectID, inAddresses[i].mSelector);
    }
    return noErr;
}

Likely to require tweaks imho, but not sure if anyone here has chops to run with this...
Naive Optimist, using sonicpi, which uses SC, on windows

@telephon
Copy link
Member

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Issues that relate to unexpected/unwanted behavior. Don't use for PRs. comp: scsynth os: macOS
Projects
None yet
Development

No branches or pull requests

7 participants