-
Notifications
You must be signed in to change notification settings - Fork 761
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
scsynth: macOS: clip values on hw out busses #5110
Conversation
65a2238
to
6213268
Compare
Thanks @elgiano !
IMO it should be macOS only, as other backends seem to clip by themselves (AFAIU). I'd also like to see a way to disable clipping completely, as in omitting I'd also prefer to have less headroom - full scale signal with high RMS will still be much louder than other sounds and I don't think we need 6dB ( |
I don't know how to disable clip completely. I mean, I could put an "if", but that would be one more "if" that wasn't there before anyway. Isn't -s inf a fair enough bypass switch? Comparing to inf will never clip |
Yeah, I can't say that I know how to do it either. One ugly, but likely well-performing way would be to put |
Idea: one way could be to create a subclass of SC_CoreAudioDriver, say SC_CoreAudioClippingDriver, override only the Run method, and select which class to use in here: supercollider/server/scsynth/SC_CoreAudio.cpp Line 473 in f806ace
|
Yeah, that might be a good way to go about it... |
I've written a POC of the subclass implementation. I'm not sure if it's worth it, I have a feeling we should avoid having a virtual Run function. I'm not an expert at this level of precision, so any help would be fundamental. Virtuality help avoiding code duplication... but is the vtable bad for Run's performance? Do anyone know any other way around this? |
I don't recall if we tested other OSes rigorously for this. SC+JACK in Linux won't overload because JACK has no volume control -- but I bet you could replicate the problem by running SC --> another app that scales down volume --> hardware. If I have time, I might also try it with the experimental PulseAudio backend: PA does have a volume control. Did anyone check Windows? The test would be: Turn system volume down, and try signals with amp > 1. If it gets louder out of the speakers at +10, +20 etc dB, then we knew the system isn't clipping internally.
I proposed the small headroom -- I wouldn't mind reducing it to 2 dB. |
On Linux it's managed by alsa, right ?(one level deeper than jack). I don't know how it's done internally, but I do know that a blown-up filter sounds quiet when system volume is quiet.
Great! I usually tested this with something like what was reported to be harmful on the forum: { LPF.ar(WhiteNoise.ar, -200) }.play // this is super loud, it spikes around 1e14 |
Using the PulseAudio backend from #5081, I can reproduce the same problem that macOS users have reported. The TL;DR conclusion, then, is that it isn't safe to implement this only for macOS -- we should do it for all platforms. The issue, as I understand it, is:
It's not at all far-fetched to imagine a user generating full-scale signals in SC, and using the system volume control to bring that down to a listenable level. User expectation at this point is, "I set my volume control to -40 dB so I shouldn't get anything louder than that in my earphones." If SC accidentally generates a +700 dB signal (which I've done before), then the volume control makes that +660 dB and the hardware clips at full-scale -- but the user turned down the volume because full-scale is too loud = ear damage. The defining feature of the issue, then, is that system volume control can pass through a signal louder than the set level. To test, I ran this on scsynth built for PulseAudio: (
a = {
var trig = Impulse.kr(2);
var amp = Demand.kr(trig, 0, Dseq([Dseries(-18, 6, 8)], inf));
SinOsc.ar(440, 0, Lag.kr(amp, 0.1).dbamp).dup
}.play;
) -18, -12, -6, 0 -- no clipping Then I went to the PulseAudio volume panel. When the scsynth volume level is set in pavucontrol to 0 dB, as expected, I hear four clean tones and four distorted tones. If I turn pavucontrol-scsynth volume down to -20 dB, then I hear seven clean tones and one distorted tone -- and the output level goes above -20 dB. If the volume control is clipping as we want (to protect hearing), then I would hear four clean and four distorted tones, just quieter. That's definitely not the result I got. Therefore, the same problem can occur in Linux. |
Moreover, if the intention is ear safety, the end-stage clipper needs to be based on the server's volume setting, not full scale. Same scenario: user's speakers/headphones are loud. Only now they discover, "if I write I.e. you can imagine this case purely within SC, irrespective of OS. That makes implementation trickier... or carefully document the limitations of SC's internal volume control (which is tricky to message in a way that doesn't look dumb). |
I think it's a valid point about volume setting on the server... but you
could see it both ways - master volume to prevent everything from clipping,
or actual master volume that limits output. I think the former is more
common.
Regardless, this would be a consideration for the sclang-side limiter to
address, independent from this PR, IMO.
…On Thu, Jul 30, 2020 at 10:27 PM H. James Harkins ***@***.***> wrote:
Moreover, if the intention is ear safety, the end-stage clipper needs to
be based on the server's volume setting, not full scale.
Same scenario: user's speakers/headphones are loud. Only now they
discover, "if I write s.volume = -40 in my startup file, then SC is at a
good level and I don't have to think about it"... until getting a super
loud signal, which SC's volume control reduces slightly and SC's end stage
clipping brings to... full scale again, *bang* and "why didn't that
volume control protect my hearing?"
I.e. you can imagine this case purely within SC, irrespective of OS.
That makes implementation trickier... or carefully document the
limitations of SC's internal volume control (which is tricky to message in
a way that doesn't look dumb).
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
<#5110 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAMD7OO5BRZWT3BNWN7HDSDR6JI5JANCNFSM4PNZ7XBQ>
.
|
So, maybe we shouldn't deal with this at the backed level, and focus on solving the issues with a language-side implementation. After having tried the server path, I'm much more willing to abandon it. Server pros:
Server cons:
Client pros:
Client cons:
|
I'd add one more Server-side pro / Client-side con: server-side implementation will also provide basic safety for other clients. |
0dc0c9f
to
8250de8
Compare
I pushed a template-based implementation:
The cost is that now we have two audioIOProc funcs and two SC_CoreAudioDriver:Run funcs (from templates, no code duplication), which I don't think is a cost at all, or very minimal. Feedback would be welcome and very important :) This doesn't deal with s.volume (which I would leave to the sclang-side stage limiter implementation) or other platforms than macOS. Since so far we don't have that many backends, would it be reasonable to double-check all of them and add this feature only to the ones that need it, and then evaluate every future backend addition (e.g. pulse)? Thanks to @brianlheim for the precious tips on how to use templates for this! |
8250de8
to
311d66e
Compare
include/server/SC_WorldOptions.h
Outdated
@@ -49,6 +49,7 @@ struct WorldOptions { | |||
|
|||
bool mRealTime = true; | |||
bool mMemoryLocking = false; | |||
float mSafetyClipThreshold = 2.0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
note -- look for convo w @Spacechild1 about ABI/API changes
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks @brianlheim for catching that! WorldOptions
is part of the public libscsynth API and changing a public struct causes API/ABI breakage.
Note that the libscsynth API/ABI has already been broken in past. For example, 695aea5 changed function signatures, and 22c9dea turned WorldOptions
from a old-style C++ struct to a C++11 struct. The first commit causes a linker error if header and binary don't have the same version. The second commit has no consequences expect possible compilation errors for pre-C++11 projects.
However, changing the layout of a public struct can cause runtime crashes, e.g. when the host links against a new libsynth binary, but is compiled against an older header. This means it is now required that the SC_WorldOptions.h
header and the libscsynth binary have the same SC version (which means you can't update the libscsynth binary without recompiling the host app against the same SuperCollider SDK version - and vice versa!) This is just something to be aware of.
I see two solutions:
a) make it clear that header and binary must have the same version
b) add a simple versioning mechanism similar to the SC plugin API, so that World_New
fails on a version mismatch.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks @Spacechild1 !!
i don't know if many people use this part of the API at all. just thinking back, i have never seen or heard any concerns over the shared-libscsynth build breaking -- except for the Arch Linux distribution, but David Runge said he's not aware of anyone making use of it either. i've also never seen complaints about crashes or other breakage when those API changes did happen.
i wouldn't want to spend time on adding a feature that nobody will make use of, so for now i think we could just document in the header that the API may change across minor versions, so it should be compiled against headers from the same minor version used to link. that's at least a small stability guarantee. what do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i don't know if many people use this part of the API at all
I'm sure there are a few people who use libscsynth, but they probably all use a binary + header with the same version. So in practice, ABI breakage is not such a big deal.
i've also never seen complaints about crashes or other breakage when those API changes did happen.
The difference is that previous breakages would lead to a compiler resp. linker error, which are easy to spot/fix. Changing the struct can cause runtime crashes, which are harder to debug.
so for now i think we could just document in the header that the API may change across minor versions, so it should be compiled against headers from the same minor version used to link. that's at least a small stability guarantee. what do you think?
That would be option a), and I think that's probably sufficient for now :-)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@elgiano can you please add this note?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Something like this?
// PLEASE NOTE:
// This API might change across minor versions.
// Always make sure, when using libscsynth, that the binary and this header come from the same minor version.
Thanks @dyfer and @brianlheim. I put a short summary here since navigating reviews is starting to become difficult for me:
|
724a9a0
to
9689923
Compare
no worries, it is quite disordered. just one thing, it might help to collapse review comments with "mark as resolved" once you resolve each one, then it's clearer what still remains to be done.
i think it's good. you should definitely also note that "this API" applies specifically to things in
oh yes right i forgot about that! will you make the supernova implementation in another PR? it may be better for this change because there are already a lot of PR comments to sift through. i guess it will be done to the portaudio backend since there is no supernova coreaudio backend yet. |
9689923
to
a9a8ab5
Compare
This I usually leave to the one who made the first comment, it feels a bit rude for me to decide when it is resolved :)
I've updated the message and added it to all three files in include/server
I'll open a new PR as soon as we are done with this one |
just a note for supernova: we use portaudio on macOS so this is the backend for which this needs to be implemented. It would be good to test whether windows APIs have the same problem (namely MME as this is the default) and then decide if we apply it only for mac or mac and windows. EDIT: I clearly missed Brian's comment earlier about this, sorry for the noise! |
i'm used to the opposite workflow but i don't mind doing it that way, thanks for explaining! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks!
ah, looks like the linting job is failing, can you please format this @elgiano ? https://travis-ci.org/github/supercollider/supercollider/jobs/745436435
a9a8ab5
to
eacbf3f
Compare
Sorry, I didn't expect to have linting problems with comments, but ofc it makes sense! it's clang-formatted now :) |
@timblechmann would it be possible for you to get in touch about implementing this for supernova? I'd really benefit from a quick chat about it! EDIT: you can see my current draft here: elgiano@aba9f87 |
eacbf3f
to
76c524c
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks! gonna let someone else approve/merge as this is a slightly complex feature.
fwiw @elgiano your branch looks good to me aside from the capitalization missing here elgiano@aba9f87#diff-95e496ab2ce1c36e8c20a690148fa7def521da35f7d1fac6e3cd2917241609e3R290 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm happy to merge it as is, but if you feel like making these changes I think it would be even better. Thanks!
76c524c
to
7969bf4
Compare
@brianlheim
Thanks for looking at this Brian, and catching that typo! I haven't tested it yet (haven't built it once actually) because at this moment my access to a mac computer is somehow limited. But I will get to do it soon! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this looks good, thank you!
IMO can be merged once CI passes (or maybe earlier? since last change did not touch cpp/sc code)
Thanks everyone, I'm very happy this got merged 👍 |
@elgiano are you still planning to make the supernova PR for this functionality? IMO we should have sn implementation on our radar for 3.12 |
I had a branch going on for supernova, couldn't really get it to work though, and then I ended up not having a Mac anymore.... however I might get one from my job soon and then I would look again into it! |
@elgiano - how is your progress on this looking? More than happy to take it over if you aren't able to keep on it. |
@joshpar I still don't have a working Mac right now, so I can't do anything about it. |
Purpose and Motivation
Related to #4894.
Proposes a fix for CoreAudio handling of loud volumes. Since CoreAudio only applies a relative attenuation, setting a low system volume doesn't prevent output values > +- 1 from sounding absolutely loud, possibly leading to hear damage.
This PR attempts to fix it by:
-s safetyClipThreshold
Although clipping affects inter-app routing (as it happens through the same out busses), it doesn't affect recording.
Note:
s.options.safetyClipThreshold = inf
is valid (thx c++!) and makes clipping ineffectiveNeeds discussion: cross-platform
This is initially macos only. Any consideration about making it general? I wouldn't like to have it on linux for instance, where it is not needed, but maybe it would be good to have the same behavior for all platforms?
Types of changes
( doesn't change any interface, but introduces hard clipping on macos scsynth by default and adds a cli switch )
To-do list