class SynthTestWithSClang(SCBaseTest): __test__ = True start_sclang = True def setUp(self) -> None: self.assertIsNotNone(SynthTestWithSClang.sc.lang) self.custom_nodeid = 42 self.all_synth_args = { "freq": 400, "amp": 0.3, "num": 4, "pan": 0, "lg": 0.1 } self.synth = Synth("s2", nodeid=self.custom_nodeid) def tearDown(self) -> None: self.synth.free() time.sleep(0.1) def test_synth_desc(self): self.assertIsNotNone(self.synth.synth_desc) for name, synth_arg in self.synth.synth_desc.items(): self.assertEqual(name, synth_arg.name) self.assertAlmostEqual(self.synth.get(name), synth_arg.default) def test_getattr(self): for name, value in self.all_synth_args.items(): self.assertAlmostEqual(self.synth.__getattr__(name), value)
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 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_wait(self): duration = 0.15 tol = 0.05 def check(synth): self.assertEqual(synth.is_playing, None) self.assertEqual(synth.started, True) self.assertEqual(synth.freed, False) self.assertEqual(synth.group, self.sc.server.default_group.nodeid) t_wait_for_notification = time.time() while not synth.is_playing: if time.time() - t_wait_for_notification > 0.15: self.fail("Waiting for /n_go notification took too long.") self.assertEqual(synth.is_playing, True) self.assertEqual(synth.started, True) self.assertEqual(synth.freed, False) synth.wait(timeout=1) time_played = time.time() - t0 self.assertEqual(synth.is_playing, False) self.assertEqual(synth.started, False) self.assertEqual(synth.freed, True) self.assertEqual(synth.group, None) self.assertLessEqual(time_played, duration + tol) self.assertGreaterEqual(time_played, duration - tol) t0 = time.time() with self.assertWarnsRegex( UserWarning, "SynthDesc 's1' is unknown", msg="SynthDesc seems to be known" ): s1_synth = Synth("s1", controls={"dur": duration, "amp": 0.0}) check(s1_synth) t0 = time.time() s1_synth.new() check(s1_synth)
def setUp(self) -> None: self.assertIsNotNone(SynthTestWithSClang.sc.lang) self.custom_nodeid = 42 self.all_synth_args = { "freq": 400, "amp": 0.3, "num": 4, "pan": 0, "lg": 0.1 } self.synth = Synth("s2", nodeid=self.custom_nodeid)
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()
class VolumeTest(SCBaseTest): __test__ = True start_sclang = True def setUp(self) -> None: self.assertIsNotNone(VolumeTest.sc.lang) VolumeTest.sc.server.unmute() self.assertFalse(VolumeTest.sc.server.muted) vol_synth = VolumeTest.sc.server._volume._synth if vol_synth is not None: vol_synth.wait(timeout=1) del vol_synth self.assertIsNone(VolumeTest.sc.server._volume._synth) self.custom_nodeid = 42 self.all_synth_args = { "freq": 400, "amp": 0.3, "num": 4, "pan": 0, "lg": 0.1 } self.synth = Synth("s2", nodeid=self.custom_nodeid) def tearDown(self) -> None: self.synth.free() time.sleep(0.1) def test_synth_desc(self): num_channels = VolumeTest.sc.server.options.num_output_buses self.assertIsNotNone( SynthDef.get_description(f"sc3nb_volumeAmpControl{num_channels}")) def test_set_volume(self): volume = -10 VolumeTest.sc.server.volume = volume self.assertEqual(volume, VolumeTest.sc.server.volume) vol_synth = VolumeTest.sc.server._volume._synth self.assertIn(vol_synth, VolumeTest.sc.server.query_tree().children) self.assertAlmostEqual(dbamp(volume), vol_synth.get("volumeAmp")) VolumeTest.sc.server.muted = True VolumeTest.sc.server.volume = 0 self.assertAlmostEqual(0, vol_synth.get("volumeAmp")) VolumeTest.sc.server.muted = False vol_synth.wait(timeout=0.2) self.assertNotIn(vol_synth, VolumeTest.sc.server.query_tree().children) del vol_synth self.assertFalse(VolumeTest.sc.server.muted) self.assertIsNone(VolumeTest.sc.server._volume._synth)
def setUp(self) -> None: self.assertIsNotNone(VolumeTest.sc.lang) VolumeTest.sc.server.unmute() self.assertFalse(VolumeTest.sc.server.muted) vol_synth = VolumeTest.sc.server._volume._synth if vol_synth is not None: vol_synth.wait(timeout=1) del vol_synth self.assertIsNone(VolumeTest.sc.server._volume._synth) self.custom_nodeid = 42 self.all_synth_args = { "freq": 400, "amp": 0.3, "num": 4, "pan": 0, "lg": 0.1 } self.synth = Synth("s2", nodeid=self.custom_nodeid)
def start( self, timetag: float = 0, duration: Optional[float] = None, node: Union[Node, int] = 0, bus: int = 0, ): """Start the recording. Parameters ---------- timetag : float, by default 0 (immediately) Time (or time offset when <1e6) to start duration : float, optional Length of the recording, by default until stopped. node : Union[Node, int], optional Node that should be recorded, by default 0 bus : int, by default 0 Bus that should be recorded Raises ------ RuntimeError When trying to start a recording unprepared. """ if self._state != RecorderState.PREPARED: raise RuntimeError( f"Recorder state must be PREPARED but is {self._state}") args = dict(bus=bus, duration=duration or -1, bufnum=self._record_buffer.bufnum) with self._server.bundler(timetag=timetag): self._record_synth = Synth( self._synth_name, controls=args, server=self._server, target=node, add_action=AddAction.TO_TAIL, ) self._state = RecorderState.RECORDING
def test_fast_wait(self): duration = 0.15 tol = 0.05 def check(synth): self.assertEqual(synth.is_playing, False) self.assertEqual(synth.started, False) self.assertEqual(synth.freed, True) time_played = time.time() - t0 self.assertLessEqual(time_played, duration + tol) self.assertGreaterEqual(time_played, duration - tol) t0 = time.time() with self.assertWarnsRegex( UserWarning, "SynthDesc 's1' is unknown", msg="SynthDesc seems to be known" ): s1_synth = Synth("s1", controls={"dur": duration, "amp": 0.0}) s1_synth.wait(timeout=1) check(s1_synth) t0 = time.time() s1_synth.new() s1_synth.wait(timeout=1) check(s1_synth)
def test_setattr(self): with self.assertWarnsRegex(UserWarning, "SynthDesc 's2' is unknown", msg="SynthDesc seems to be known"): synth1 = Synth("s2", controls={"amp": 0.0}, target=self.group) with self.assertWarnsRegex(UserWarning, "SynthDesc 's2' is unknown", msg="SynthDesc seems to be known"): synth2 = Synth("s2", controls={"amp": 0.0}, target=self.group) GroupTest.sc.server.sync() cmd_args = {"pan": -1.0, "num": 1} for name, value in cmd_args.items(): self.group.set(name, value) GroupTest.sc.server.sync() self.assertEqual(synth1.group, self.group.nodeid) self.assertEqual(synth2.group, self.group.nodeid) for name, value in cmd_args.items(): self.assertAlmostEqual(synth1.get(name), value) self.assertAlmostEqual(synth2.get(name), value)
class Recorder: """Allows to record audio easily.""" # TODO rec_header, rec_format with Literal type (py3.8) from Buffer def __init__( self, path: str = "record.wav", nr_channels: int = 2, rec_header: str = "wav", rec_format: str = "int16", bufsize: int = 65536, server: Optional[SCServer] = None, ): """Create and prepare a recorder. Parameters ---------- path : str, optional path of recording file, by default "record.wav" nr_channels : int, optional Number of channels, by default 2 rec_header : str, optional File format, by default "wav" rec_format : str, optional Recording resolution, by default "int16" bufsize : int, optional size of buffer, by default 65536 server : SCServer, optional server used for recording, by default use the SC default server """ self._state = RecorderState.UNPREPARED self._server = server or SC.get_default().server self._record_buffer = Buffer(server=self._server) self._record_synth: Optional[Synth] = None self.prepare(path, nr_channels, rec_header, rec_format, bufsize) def prepare( self, path: str = "record.wav", nr_channels: int = 2, rec_header: str = "wav", rec_format: str = "int16", bufsize: int = 65536, ): """Pepare the recorder. Parameters ---------- path : str, optional path of recording file, by default "record.wav" nr_channels : int, optional Number of channels, by default 2 rec_header : str, optional File format, by default "wav" rec_format : str, optional Recording resolution, by default "int16" bufsize : int, optional size of buffer, by default 65536 Raises ------ RuntimeError When Recorder does not needs to be prepared. """ if self._state != RecorderState.UNPREPARED: raise RuntimeError( f"Recorder state must be UNPREPARED but is {self._state}") # prepare buffer self._record_buffer.alloc(bufsize, channels=nr_channels) self._record_buffer.write( path=path, header=rec_header, sample=rec_format, num_frames=0, starting_frame=0, leave_open=True, ) self._rec_id = self._record_buffer.bufnum # TODO we could prepare the synthDef beforehand and just use the right one here. # This would allow Recordings without sclang self._synth_def = SynthDef( f"sc3nb_recording_{self._rec_id}", r"""{ |bus, bufnum, duration| var tick = Impulse.kr(1); var timer = PulseCount.kr(tick) - 1; Line.kr(0, 0, duration, doneAction: if(duration <= 0, 0, 2)); SendReply.kr(tick, '/recordingDuration', timer, ^rec_id); DiskOut.ar(bufnum, In.ar(bus, ^nr_channels)) }""", ) self._synth_name = self._synth_def.add(pyvars={ "rec_id": self._rec_id, "nr_channels": nr_channels }) self._state = RecorderState.PREPARED def start( self, timetag: float = 0, duration: Optional[float] = None, node: Union[Node, int] = 0, bus: int = 0, ): """Start the recording. Parameters ---------- timetag : float, by default 0 (immediately) Time (or time offset when <1e6) to start duration : float, optional Length of the recording, by default until stopped. node : Union[Node, int], optional Node that should be recorded, by default 0 bus : int, by default 0 Bus that should be recorded Raises ------ RuntimeError When trying to start a recording unprepared. """ if self._state != RecorderState.PREPARED: raise RuntimeError( f"Recorder state must be PREPARED but is {self._state}") args = dict(bus=bus, duration=duration or -1, bufnum=self._record_buffer.bufnum) with self._server.bundler(timetag=timetag): self._record_synth = Synth( self._synth_name, controls=args, server=self._server, target=node, add_action=AddAction.TO_TAIL, ) self._state = RecorderState.RECORDING def pause(self, timetag: float = 0): """Pause the recording. Parameters ---------- timetag : float, by default 0 (immediately) Time (or time offset when <1e6) to pause Raises ------ RuntimeError When trying to pause if not recording. """ if self._state != RecorderState.RECORDING or self._record_synth is None: raise RuntimeError( f"Recorder state must be RECORDING but is {self._state}") with self._server.bundler(timetag=timetag): self._record_synth.run(False) self._state = RecorderState.PAUSED def resume(self, timetag: float = 0): """Resume the recording Parameters ---------- timetag : float, by default 0 (immediately) Time (or time offset when <1e6) to resume Raises ------ RuntimeError When trying to resume if not paused. """ if self._state != RecorderState.PAUSED or self._record_synth is None: raise RuntimeError( f"Recorder state must be PAUSED but is {self._state}") with self._server.bundler(timetag=timetag): self._record_synth.run(True) self._state = RecorderState.RECORDING def stop(self, timetag: float = 0): """Stop the recording. Parameters ---------- timetag : float, by default 0 (immediately) Time (or time offset when <1e6) to stop Raises ------ RuntimeError When trying to stop if not started. """ if (self._state not in [RecorderState.RECORDING, RecorderState.PAUSED] or self._record_synth is None): raise RuntimeError( f"Recorder state must be RECORDING or PAUSED but is {self._state}" ) with self._server.bundler(timetag=timetag): self._record_synth.free() self._record_synth = None self._record_buffer.close() self._state = RecorderState.UNPREPARED def __repr__(self) -> str: return f"<Recorder [{self._state.value}]>" def __del__(self): try: self.stop() except RuntimeError: pass self._record_buffer.free()
def test_reuse_nodeid(self): with self.assertWarnsRegex( UserWarning, "SynthDesc 's2' is unknown", msg="SynthDesc seems to be known" ): synth1 = Synth("s2", {"amp": 0.0}) nodeid = synth1.nodeid synth1.free() synth1.wait(timeout=1) group = Group(nodeid=nodeid) with self.assertRaisesRegex(RuntimeError, "Tried to get "): Synth("s2", nodeid=nodeid) group.free() group.wait(timeout=1) synth2 = Synth("s2", nodeid=nodeid) synth2.free() synth2.wait(timeout=1)
def test_too_many_arguments(self): with self.assertRaises(TypeError): Synth("s2", {"amp": 0.0}, "this is too much!") with self.assertRaises(TypeError): Group(101, "this is too much!")
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)
def test_duplicate(self): self.assertNotIn("/s_new", self.sc.server.fails) with self.assertWarnsRegex( UserWarning, "SynthDesc 's2' is unknown", msg="SynthDesc seems to be known" ): synth1 = Synth("s2", controls={"amp": 0.0}) wait_t0 = time.time() synth1.new(controls={"amp": 0.0}) while not "/s_new" in self.sc.server.fails: self.assertLessEqual(time.time() - wait_t0, 0.5) self.assertEqual(self.sc.server.fails["/s_new"].get(), "duplicate node ID") synth1.free() synth1.wait(timeout=1) synth1.new({"amp": 0.0}) synth1.free() synth1.wait(timeout=1) with self.assertRaises(Empty): self.sc.server.fails["/s_new"].get(timeout=0.5)
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"