"""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