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)
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 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_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 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()
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)