Source code for sc3nb.sc

"""Contains classes enabling the easy communication with SuperCollider
within jupyter notebooks
"""

import logging
from typing import Optional, Sequence

from IPython import get_ipython

import sc3nb.magics as magics
from sc3nb.process_handling import ALLOWED_PARENTS
from sc3nb.sc_objects.server import SCServer, ServerOptions
from sc3nb.sclang import SCLang

[docs]_LOGGER = logging.getLogger(__name__)
[docs]def startup( start_server: bool = True, scsynth_path: Optional[str] = None, start_sclang: bool = True, sclang_path: Optional[str] = None, magic: bool = True, scsynth_options: Optional[ServerOptions] = None, with_blip: bool = True, console_logging: bool = False, allowed_parents: Sequence[str] = ALLOWED_PARENTS, timeout: float = 10, ) -> "SC": """Inits SuperCollider (scsynth, sclang) and registers ipython magics Parameters ---------- start_server : bool, optional If True boot scsynth, by default True scsynth_path : Optional[str], optional Path of scscynth executable, by default None start_sclang : bool, optional If True start sclang, by default True sclang_path : Optional[str], optional Path of sclang executable, by default None magic : bool, optional If True register magics to ipython, by default True scsynth_options : Optional[ServerOptions], optional Options for the server, by default None with_blip : bool, optional make a sound when booted, by default True console_logging : bool, optional If True write scsynth/sclang output to console, by default False allowed_parents : Sequence[str], optional Names of parents that are allowed for other instances of sclang/scsynth processes, by default ALLOWED_PARENTS timeout : float, optional timeout in seconds for starting the executable, by default 10 Returns ------- SC SuperCollider Interface class. """ if magic: ipy = get_ipython() if ipy is not None: magics.load_ipython_extension(ipy) if SC.default is None: SC.default = SC( start_server=start_server, scsynth_path=scsynth_path, start_sclang=start_sclang, sclang_path=sclang_path, scsynth_options=scsynth_options, with_blip=with_blip, console_logging=console_logging, allowed_parents=allowed_parents, timeout=timeout, ) else: _LOGGER.warning("SC already started") if start_sclang: SC.get_default().start_sclang( sclang_path=sclang_path, allowed_parents=allowed_parents, timeout=timeout, ) if start_server: SC.get_default().start_server( scsynth_options=scsynth_options, scsynth_path=scsynth_path, allowed_parents=allowed_parents, timeout=timeout, ) return SC.default
[docs]class SC: """Create a SuperCollider Wrapper object. Parameters ---------- start_server : bool, optional If True boot scsynth, by default True. scsynth_path : Optional[str], optional Path of scscynth executable, by default None. start_sclang : bool, optional If True start sclang, by default True. sclang_path : Optional[str], optional Path of sclang executable, by default None. scsynth_options : Optional[ServerOptions], optional Options for the server, by default None. with_blip : bool, optional Make a sound when booted, by default True. console_logging : bool, optional If True write scsynth/sclang output to console, by default True. allowed_parents : Sequence[str], optional Names of parents that are allowed for other instances of sclang/scsynth processes, by default ALLOWED_PARENTS. timeout : float, optional timeout in seconds for starting the executables, by default 5 """
[docs] default: Optional["SC"] = None
"""Default SC instance. This will be used by all SuperCollider objects if no SC/server/lang is specified. """ @classmethod
[docs] def get_default(cls) -> "SC": """Get the default SC instance Returns ------- SC default SC instance Raises ------ RuntimeError If there is no default SC instance. """ if cls.default is not None: return cls.default else: raise RuntimeError( "You need to start a SuperCollider SC instance first"
" or provide an sclang/server directly." ) def __init__( self, *, start_server: bool = True, scsynth_path: Optional[str] = None, start_sclang: bool = True, sclang_path: Optional[str] = None, scsynth_options: Optional[ServerOptions] = None, with_blip: bool = True, console_logging: bool = True, allowed_parents: Sequence[str] = ALLOWED_PARENTS, timeout: float = 5, ): if SC.default is None: SC.default = self self._console_logging = console_logging self._server = None self._sclang = None try: if start_sclang: self.start_sclang( sclang_path=sclang_path, console_logging=self._console_logging, allowed_parents=allowed_parents, timeout=timeout, ) if start_server: self.start_server( scsynth_path=scsynth_path, scsynth_options=scsynth_options, console_logging=self._console_logging, with_blip=with_blip, allowed_parents=allowed_parents, timeout=timeout, ) except Exception: if SC.default is self: SC.default = None raise
[docs] def start_sclang( self, sclang_path: Optional[str] = None, console_logging: bool = True, allowed_parents: Sequence[str] = ALLOWED_PARENTS, timeout: float = 5, ): """Start this SuperCollider language Parameters ---------- sclang_path : Optional[str], optional Path of sclang executable, by default None console_logging : bool, optional If True write scsynth/sclang output to console, by default True allowed_parents : Sequence[str], optional Names of parents that are allowed for other instances of sclang/scsynth processes, by default ALLOWED_PARENTS timeout : float, optional timeout in seconds for starting the executable, by default 5 """ if self._sclang is None: self._sclang = SCLang() try: self._sclang.start( sclang_path=sclang_path, console_logging=console_logging, allowed_parents=allowed_parents, timeout=timeout, ) except Exception as excep: self._sclang = None raise RuntimeError(f"Starting sclang failed - {excep}") from excep else: self._try_to_connect() else: _LOGGER.warning("sclang already started") if SC.default is None: SC.default = self
[docs] def start_server( self, scsynth_options: Optional[ServerOptions] = None, scsynth_path: Optional[str] = None, console_logging: bool = True, with_blip: bool = True, allowed_parents: Sequence[str] = ALLOWED_PARENTS, timeout: float = 5, ): """Start this SuperCollider server Parameters ---------- scsynth_options : Optional[ServerOptions], optional Options for the server, by default None scsynth_path : Optional[str], optional Path of scscynth executable, by default None console_logging : bool, optional If True write scsynth/sclang output to console, by default True with_blip : bool, optional make a sound when booted, by default True allowed_parents : Sequence[str], optional Names of parents that are allowed for other instances of sclang/scsynth processes, by default ALLOWED_PARENTS timeout : float, optional timeout in seconds for starting the executable, by default 5 """ if self._server is None: self._server = SCServer(options=scsynth_options) try: self._server.boot( scsynth_path=scsynth_path, console_logging=console_logging, allowed_parents=allowed_parents, with_blip=with_blip, timeout=timeout, ) except Exception as excep: self._server = None raise RuntimeError(f"Starting scsynth failed - {excep}") from excep else: self._try_to_connect() else: _LOGGER.warning("scsynth already started") if SC.default is None: SC.default = self
[docs] def _try_to_connect(self): if self._sclang is not None and self._server is not None: try: self._sclang.connect_to_server(self._server) except Exception as excep: raise RuntimeError( f"connecting {self._sclang} to {self._server} failed" ) from excep
@property def server(self) -> SCServer: """This SuperCollider server object""" if self._server is not None: return self._server else: raise RuntimeError("You need to start the SuperCollider Server first") @property def lang(self) -> SCLang: """This SuperCollider language object""" if self._sclang is not None: return self._sclang else: raise RuntimeError( "You need to start the SuperCollider Language (sclang) first" ) @property def console_logging(self): """True if console logging enabled""" return self._console_logging @console_logging.setter def console_logging(self, value): if self._server is not None: self._server.process.console_logging = value if self._sclang is not None: self._sclang.process.console_logging = value
[docs] def __del__(self): self.exit()
[docs] def __repr__(self) -> str: server = self._server if self._server is not None else "SCServer not started" lang = self._sclang if self._sclang is not None else "SCLang not started" return f"<SC {server},\n {lang}>"
[docs] def exit(self) -> None: """Closes SuperCollider and shuts down server""" try: if self._server is not None: self._server.quit() if self._sclang is not None: self._sclang.kill() finally: self._server = None self._sclang = None if self is SC.default: SC.default = None