├── Classes ├── ReplaceBadValues.sc ├── Safety.sc └── plusReplaceBadZap.sc ├── HelpSource └── Classes │ ├── ReplaceBadValues.schelp │ └── Safety.schelp ├── README.md └── SafetyNet.quark /Classes/ReplaceBadValues.sc: -------------------------------------------------------------------------------- 1 | 2 | ReplaceBadValues { 3 | *ar { |in, sub = 0, id = 0, post = 2| 4 | var subIndex = CheckBadValues.ar(in, id, post) > 0; 5 | // prepare for Select 6 | sub = sub.asArray.collect { |sub1| 7 | if (sub1.rate != \audio) { sub = K2A.ar(sub) } { sub }; 8 | }; 9 | ^Select.ar(subIndex, [in, sub]); 10 | } 11 | *kr { |in, sub = 0, id = 0, post = 2| 12 | var subIndex = CheckBadValues.kr(in, id, post) > 0; 13 | ^Select.kr(subIndex, [in, sub]); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Classes/Safety.sc: -------------------------------------------------------------------------------- 1 | Safety { 2 | 3 | classvar defaultDefName = \safeClip; 6 | classvar <>useRootNode = true; 7 | classvar Filters 4 | related:: Classes/CheckBadValues, Classes/MasterFX 5 | 6 | DESCRIPTION:: 7 | This pseudo-UGen uses CheckBadValues to test a ugen signal for infinity, NaN (not a number), and denormals. If one of these is found, it replaces the bad signal with a substitute signal given. 8 | 9 | First examples: 10 | 11 | code:: 12 | // when live coding, use shortcut ugen.zap: 13 | 14 | x = { RLPF.ar(PinkNoise.ar(0.1), \freq.kr(500), 0.2).zap }.play; 15 | 16 | // in longhand, this does: 17 | ( 18 | x.free; 19 | x = { arg freq = 440; 20 | var sig = RLPF.ar(PinkNoise.ar(0.1), freq, 0.2); 21 | ReplaceBadValues.ar(sig); 22 | }.play; 23 | ) 24 | // test 25 | x.set(\freq, inf); // create bad freq value inf -> filter breaks ... 26 | x.set(\freq, 330); // and back to working 27 | x.set(\freq, -1.sqrt); // same with NotANumber nan value 28 | x.set(\freq, 550); // and back 29 | x.free; 30 | :: 31 | 32 | CLASSMETHODS:: 33 | 34 | METHOD:: ar, kr 35 | argument:: in 36 | the input signal to replace when bad 37 | argument:: sub 38 | the substitute signal to replace it with 39 | argument:: id 40 | an optional ID to post when input changes bad/ok state 41 | argument:: post 42 | set post mode, see CheckBadValues 43 | 44 | EXAMPLES:: 45 | 46 | code:: 47 | // test filter and replacement with kr signals 48 | ( 49 | x = { arg freq = 440; 50 | var safeFreq = ReplaceBadValues.kr(freq.lag(0.2), 666).poll; 51 | SinOsc.ar(safeFreq, 0, 0.1); 52 | }.play; 53 | ) 54 | // 55 | x.set(\freq, inf); // create bad freq value inf -> replaces with 666 56 | x.set(\freq, 330); // and goes back to input kr when that is fine again 57 | x.set(\freq, -1.sqrt); // create nan value -> replaces with 666 58 | x.set(\freq, 550); // and goes back to input kr when that is fine again 59 | x.free; 60 | 61 | // Test filter and replace with ar signals: 62 | ( 63 | x = { arg freq = 440, subVol = 0.2; 64 | var sig, subSig, safeSig, secondSig; 65 | // RLPF breaks when freq goes bad 66 | sig = RLPF.ar(PinkNoise.ar([1, 1] * 0.1), freq, 0.2); 67 | // substitute with silence (default) 68 | subSig = 0; 69 | // or substitute with a signal 70 | subSig = [Impulse.ar(5), Dust.ar(8)] * subVol; 71 | // Replace bad sig here 72 | safeSig = ReplaceBadValues.ar(sig, subSig); 73 | // create a second signal to test when mixing 74 | secondSig = Ringz.ar(Dust.ar(3), [440, 666], 1) * 0.03; 75 | 76 | // goes into an IIR filter - this never recovers from a bad number 77 | CombL.ar(safeSig + secondSig, 0.02, [0.02, 0.03], 1); 78 | }.play; 79 | ) 80 | 81 | x.set(\freq, -1.sqrt); // nan -> blows filter math -> replaces signal 82 | x.set(\freq, 666); // recovers fine, and CombL never sees bad signal 83 | x.set(\freq, 1/0); // blows filter math -> nan -> replaces signal 84 | x.set(\freq, 0.02); // very low frequency - weird output, but no nan 85 | x.set(\freq, 500); 86 | 87 | x.free; 88 | :: 89 | -------------------------------------------------------------------------------- /HelpSource/Classes/Safety.schelp: -------------------------------------------------------------------------------- 1 | TITLE:: Safety 2 | summary:: protect ears and equipment from risky sound signals 3 | categories:: Utilities 4 | related:: Classes/ReplaceBadValues, Classes/Server 5 | 6 | DESCRIPTION:: 7 | Safety protects users from risky sound signals in two respects: 8 | It replaces bad values before they leave the software and enter the sound device, and it keeps the signal within +-1 (or a user-set lower limit) given by clipping, limiting, or other methods. 9 | 10 | When the Safety quark is installed, it is on by default so that newbie users are safe; it can be turned off if desired. It puts itself at the tail of the rootnode; thus scope will be added after it, and master volume goes before it. 11 | 12 | In link::Classes/ReplaceBadValues:: see esp. the method ugen.zap for suppressing bad numbers individually per synth. 13 | 14 | First code examples: 15 | 16 | code:: 17 | s.reboot; // Safety informs by posting: 18 | // -> Safety('localhost') is running, using 'safeClip_2' or similar. 19 | 20 | // Safety installs a Safety object for every known server: 21 | Safety.all; 22 | // which can be accessed in three ways: 23 | Safety(s); 24 | Safety(\localhost); 25 | Safety.all[\localhost]; 26 | 27 | // Safety reinstates protection when stopping all sounds with Cmd-Period: 28 | CmdPeriod.run; 29 | // -> posts Safety('localhost') etc 30 | 31 | // Safety can be disabled if desired: 32 | Safety(s).disable; 33 | Safety(s).enabled; 34 | s.queryAllNodes; // gone 35 | CmdPeriod.run; // -> no post after cmd-. 36 | 37 | // enable again 38 | Safety(s).enable; // posts Safety... again 39 | Safety(s).enabled; 40 | CmdPeriod.run; // posts 41 | s.queryAllNodes; 42 | 43 | // Setting the number of channels: 44 | // by default, a Safety has no numChannels value: 45 | Safety(s).numChannels // -> nil 46 | // then, it uses it's server's options.numOutputBusChannels 47 | // which is 2 by default 48 | s.options.numOutputBusChannels; 49 | // same as the "2" in "safeClip_2" 50 | CmdPeriod.run; 51 | // When you set a Safety's numChannels, these will be used: 52 | s.options.numOutputBusChannels = 8; 53 | CmdPeriod.run; // now uses "safeClip_8" 54 | 55 | 56 | // Choosing protection mode by setting defName 57 | // Safety has 4 modes in its synthDefFuncs dictionary: 58 | // \safeClip uses .clip(1.0) clipping 59 | // \safeSoft uses .softclip distortion 60 | // \safeTanh uses .tanh distortion 61 | // \safeLimit uses a Limiter.ar 62 | // you can add your own functions here: 63 | Safety.synthDefFuncs; 64 | // and change the defName like this: 65 | Safety(s).defName = \safeLimit; 66 | 67 | /// Set the general Safety limit level 68 | // - sets limit in all Safety objects: 69 | Safety.setLimit(0.8); 70 | Safety.limit; 71 | Safety(s).limit; 72 | 73 | // set individual limit for a Safety: 74 | Safety(\localhost).setLimit(0.5); 75 | 76 | // test that the set limit kicks in 77 | s.volume = -1; 78 | // play sound at full level +-1 79 | { SinOsc.ar([220, 330]) }.play; 80 | s.plotTree; 81 | s.scope; 82 | 83 | // test setting general limit for all Safetys 84 | Safety.setLimit(0.2) 85 | // and individual limit for Safety(s) 86 | Safety(s).setLimit(0.8); 87 | 88 | :: 89 | 90 | CLASSMETHODS:: 91 | 92 | METHOD:: all 93 | dict for all Safety objects 94 | 95 | METHOD:: enable, disable 96 | enable and disable all Safety objects at once 97 | 98 | METHOD:: addServers 99 | detect all present servers, and make Safety objects for them. When creating server by hand, run this method to use Safety on them too. 100 | 101 | METHOD:: synthDefFuncs 102 | dict for all synthDef-generating functions Safety can use 103 | 104 | METHOD:: defaultDefName 105 | get and set the name the default synthdef to use 106 | 107 | METHOD:: addSynthDefFunc 108 | add a function to create a safety synthDef by name. 109 | 110 | METHOD:: synthDefFor 111 | make a synthDef from a named synthDefFunc for a given number of channels 112 | 113 | METHOD:: useRootNode 114 | get and set whether to add safety synth to tail of rootnode or not. 115 | true by default. if false, safety synth will run after server.defaultGroup. 116 | 117 | PRIVATE:: initSynthDefFuncs, synthDefFor 118 | 119 | METHOD:: new 120 | make a new Safety, needed only when creating a custom server. 121 | argument:: server 122 | the server for which to make the safety 123 | argument:: defName 124 | the name of the synthdef it should use 125 | argument:: enable 126 | flag whether to enable this safety when making it. 127 | argument:: numChannels 128 | sets number of output channels to protect by Safety. 129 | Only needed if different from s.options.numOutputBusChannels. 130 | 131 | private:: initClass 132 | 133 | INSTANCEMETHODS:: 134 | 135 | METHOD:: server 136 | the server of this safety 137 | 138 | METHOD:: defName 139 | get and set the name of the synthdef to use for this safety 140 | 141 | METHOD:: synth 142 | the synth running for this safety 143 | 144 | METHOD:: treeFunc 145 | the function in ServerTree used to send safety synth when booting or after cmd-period. 146 | 147 | METHOD:: numChannels 148 | the number of output channels of safety.server 149 | 150 | METHOD:: enabled 151 | flag whether this safety is enabled 152 | 153 | METHOD:: enable, disable 154 | enable and disable safety. 155 | 156 | METHOD:: asTarget 157 | When Safety.useRootNode is true, this returns the server's rootnode; 158 | when false, it returns the server. This is used as target to whose tail the safety's synth will be added. 159 | 160 | private:: printOn, storeArgs, init 161 | 162 | EXAMPLES:: 163 | 164 | code:: 165 | 166 | 167 | ( 168 | // example for adding a custom synthDefFunc: 169 | // this one posts when Limiter goes into action. 170 | // kindly contributed by Glen Fraser (@totalgee on GitHub) 171 | Safety.addSynthDefFunc(\safeLimitNotify, { |numChans, limit=1| 172 | { 173 | var limitCtl = \limit.kr(limit).abs; 174 | var mainOuts = In.ar(0, numChans); 175 | var safeOuts = ReplaceBadValues.ar(mainOuts); 176 | var resetTrig = Impulse.ar(0.5); 177 | var peak = Peak.ar(safeOuts * (1 - resetTrig), resetTrig); 178 | var maxPeak = peak.reduce(\max); 179 | var delayMaxPeak = Delay1.ar(maxPeak); 180 | var overTrig = delayMaxPeak > limitCtl; 181 | var limited = Limiter.ar(safeOuts, limitCtl); 182 | (delayMaxPeak / limitCtl).ampdb.poll((delayMaxPeak > maxPeak) * overTrig, "Audio peak exceeded limit by dB"); 183 | ReplaceOut.ar(0, limited); 184 | } 185 | }); 186 | ) 187 | 188 | // tell all Safety objects to use it by default: 189 | Safety.defaultDefName = \safeLimitNotify; 190 | 191 | 192 | // Tests - see where Safety goes in the node tree : 193 | s.boot; 194 | s.plotTree; // safety is there 195 | 196 | // add a source 197 | x = { RLPF.ar(Impulse.ar(300), \freq.kr(1000), 0.1, \amp.kr(0.1)) }.play; 198 | 199 | // scope goes after safety: 200 | s.scope; 201 | // correct order: vol, safety, scope 202 | s.volume.volume = -2; 203 | 204 | /////////// TEST BAD SIGNALS: 205 | x.set(\freq, -1.sqrt); // signal gets bad -> mutes and posts info 206 | x.set(\freq, 550); // recovers 207 | 208 | // try with second source 209 | y = { SinOsc.ar([220, 330], 0, \amp.kr(0.1)) }.play; 210 | x.set(\freq, -1.sqrt); // signal on ch 1 goes bad -> silent, ch2 remains 211 | x.set(\freq, 550); // recovers 212 | x.free; y.free; 213 | 214 | 215 | /// test switching modes of volume limiting: 216 | Safety(s).defName = \safeClip; 217 | // remake scope to add it after safety, 218 | // and set yZoom so you see the clip limit 219 | s.scope(zoom: 1).yZoom_(0.9); 220 | // make a sound with amp from 0 to 4 221 | y = { SinOsc.ar([220, 330], 0, LFSaw.kr(0.3, 0, 2, 2)) }.play; 222 | // test limiting: now with safeClip 223 | y.set(\amp, 2); // gets clipped 224 | y.set(\amp, 5); // gets clipped 225 | 226 | Safety(s).defName = \safeSoft; 227 | s.scope; 228 | Safety(s).defName = \safeLimit; 229 | s.scope; 230 | Safety(s).defName = \safeTanh; 231 | s.scope; 232 | y.free; 233 | 234 | // -------------- turn volume down on system output - gets softer 235 | 236 | 237 | x.set(\freq, -1.sqrt); // signal on ch 1 goes bad -> silent, ch2 remains 238 | x.set(\freq, 550); // recovers 239 | x.set(\amp, 5); // gets limited 240 | x.set(\amp, 0.5); // fine again 241 | Safety(s).setLimit(0.5); // set limit lower 242 | x.set(\amp, 5); // gets limited at 0.5; 243 | x.set(\amp, 0.5); // fine again 244 | 245 | // when multiple clients play on the same server, 246 | // it may be preferable that each client runs a separate Safety 247 | // on her own defaultGroup rather than on the shared RootNode: 248 | // set flag 249 | Safety.useRootNode = false; 250 | Safety(s).disable; // turn it off and on again, 251 | Safety(s).enable(true); 252 | // ... and now it run within the defaultGroup: 253 | s.plotTree; 254 | 255 | // adding a custom Safety synthDefFunc: 256 | // this one uses fold2, which distorts quite dramatically: 257 | 258 | Safety.addSynthDefFunc(\mySafeFold, { |numChans| 259 | { |limit=1| 260 | // read the hardware output channel busses: 261 | var mainOuts = In.ar(0, numChans); 262 | // filter them for bad values 263 | var safeOuts = ReplaceBadValues.ar(mainOuts); 264 | // apply whatever custom limiting method 265 | var limited = safeOuts.fold2(limit); 266 | // write the safe and limited back to the output channels busses: 267 | ReplaceOut.ar(0, limited); 268 | } 269 | }); 270 | 271 | Safety(s).defName_(\mySafeFold); 272 | // and back to harmless limiter 273 | Safety(s).defName_(\safeLimit); 274 | 275 | :: 276 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SafetyNet 2 | A SuperCollider quark that protects users from dangerous audio signals. 3 | 4 | ## How to install 5 | If you have git (highly recommended), use 6 | ``Quarks.install("SafetyNet")`` 7 | 8 | If you don't have git: 9 | - Download a ZIP from this page 10 | - Extract to a folder 11 | - put it where SuperCollider quarks want to be: 12 | ``Quarks.folder.postcs.openOS;`` 13 | - then install with this: 14 | ``Quarks.install("SafetyNet")`` 15 | -------------------------------------------------------------------------------- /SafetyNet.quark: -------------------------------------------------------------------------------- 1 | ( 2 | \name: "SafetyNet", 3 | \summary: "Protects users from dangerous audio signals.", 4 | \author: "Alberto de Campo", 5 | \since: "2016", 6 | \schelp: "Safety" 7 | ) 8 | --------------------------------------------------------------------------------