class Volume: """Server volume controls""" def __init__(self, server: "SCServer", min_: int = -90, max_: int = 6) -> None: self._server = server self._server.add_init_hook(self.send_synthdef) self._server.add_init_hook(self.update_synth) self.min = min_ self.max = max_ self._muted = False self._volume = 0.0 self._lag = 0.1 self._synth_name: Optional[str] = None self._synth: Optional[Synth] = None @property def muted(self): """True if muted.""" return self._muted @muted.setter def muted(self, muted: bool): if muted: self.mute() else: self.unmute() @property def volume(self): """Volume in dB.""" return self._volume @volume.setter def volume(self, volume): self._volume = clip(volume, self.min, self.max) self.update_synth() def mute(self) -> None: """Mute audio""" self._muted = True self.update_synth() def unmute(self) -> None: """Unmute audio""" self._muted = False self.update_synth() def update_synth(self) -> None: """Update volume Synth""" amp = 0.0 if self._muted else dbamp(self._volume) active = amp != 1.0 if active: if self._server.is_running: if self._synth is None: if self._synth_name is None: warnings.warn( "Cannot set volume. Volume SynthDef unknown. Is the default sclang running?" ) return controls = { "volumeAmp": amp, "volumeLag": self._lag, "bus": self._server.output_bus.idxs[0], } self._synth = Synth( self._synth_name, add_action=AddAction.AFTER, target=self._server.default_group, controls=controls, server=self._server, ) else: self._synth.set("volumeAmp", amp) else: if self._synth is not None: self._synth.release() self._synth = None def send_synthdef(self): """Send Volume SynthDef""" if self._server.is_running: num_channels = self._server.output_bus.num_channels synth_def = SynthDef( f"sc3nb_volumeAmpControl{num_channels}", r"""{ | volumeAmp = 1, volumeLag = 0.1, gate=1, bus | XOut.ar(bus, Linen.kr(gate, releaseTime: 0.05, doneAction:2), In.ar(bus, ^num_channels) * Lag.kr(volumeAmp, volumeLag) ); }""", ) try: self._server.lookup_receiver("sclang") except KeyError: _LOGGER.info( "Volume SynthDef cannot be send. No sclang receiver known." ) else: self._synth_name = synth_def.add(server=self._server) assert self._synth_name is not None, "Synth name is None"
class SynthTest(SCBaseTest): __test__ = True def setUp(self) -> None: with self.assertRaises(RuntimeError): SynthTest.sc.lang self.custom_nodeid = 42 self.synth_args = {"amp": 0.0, "num": 3} warnings.simplefilter("always", UserWarning) with self.assertWarnsRegex(UserWarning, "SynthDesc 's2' is unknown", msg="SynthDesc seems to be known"): self.synth = Synth("s2", controls=self.synth_args, nodeid=self.custom_nodeid) self.assertIsNone(self.synth._synth_desc) self.sc.server.sync() def tearDown(self) -> None: nodeid = self.synth.nodeid self.assertIn(nodeid, self.sc.server.nodes) self.synth.free() self.synth.wait() del self.synth # make sure that synth is deleted from registry t0 = time.time() while nodeid in self.sc.server.nodes: time.sleep(0.005) if time.time() - t0 > 0.2: self.fail("NodeID is still in server.nodes") self.assertNotIn(nodeid, self.sc.server.nodes) with self.assertRaises(KeyError): del self.sc.server.nodes[nodeid] def test_node_registry(self): copy1 = Synth(nodeid=self.synth.nodeid, new=False) copy2 = Synth(nodeid=self.custom_nodeid, new=False) self.assertIs(self.synth, copy1) self.assertIs(self.synth, copy2) self.assertIs(copy1, copy2) del copy1, copy2 def test_set_get(self): for name, value in {"amp": 0.0, "num": 1}.items(): self.synth.__setattr__(name, value) self.assertAlmostEqual(self.synth.__getattr__(name), value) with self.assertWarnsRegex(UserWarning, "Setting 'freq' as python attribute"): with self.assertRaisesRegex(AttributeError, "no attribute 'freq'"): self.synth.__getattribute__( "freq") # should not have a python attribute named freq self.synth.freq = 420 # should warn if setting attribute self.assertAlmostEqual(self.synth.get("freq"), 400) # default freq of s2 SynthDef with self.assertWarnsRegex(UserWarning, "recognized as Node Parameter now"): self.synth.set("freq", 100) self.assertAlmostEqual(self.synth.get("freq"), 100) self.synth.freq = 300 self.assertAlmostEqual(self.synth.get("freq"), 300) for name, value in self.synth_args.items(): self.assertAlmostEqual(self.synth.__getattr__(name), value) def test_new_warning(self): with self.assertLogs(level="WARNING") as log: self.synth.new() time.sleep(0.1) self.assertTrue("duplicate node ID" in log.output[-1]) def test_query(self): query_result = self.synth.query() self.assertIsInstance(query_result, SynthInfo) self.assertEqual(query_result.nodeid, self.custom_nodeid) self.assertEqual(query_result.group, SynthTest.sc.server.default_group.nodeid) self.assertEqual(query_result.prev_nodeid, -1) self.assertEqual(query_result.next_nodeid, -1)