sc-kernel 0.3.2__py3-none-any.whl → 0.5.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
sc_kernel/__init__.py CHANGED
@@ -1 +1 @@
1
- __version__ = '0.3.2'
1
+ __version__ = "0.4.0"
sc_kernel/kernel.py CHANGED
@@ -1,49 +1,128 @@
1
1
  import json
2
2
  import platform
3
3
  import sys
4
- import time
4
+ from functools import partial
5
5
  from subprocess import check_output
6
6
  import re
7
- from typing import Optional, List
7
+ from typing import Dict, Optional
8
8
  import os
9
9
 
10
10
  import pexpect
11
11
  from IPython.lib.display import Audio
12
+ from IPython.display import Image
12
13
  from metakernel import ProcessMetaKernel, REPLWrapper
13
14
 
14
15
  from . import __version__
15
16
 
17
+ # idea from
18
+ # https://github.com/supercollider/qpm/blob/d3f72894e289744f01f3c098ab0a474d5190315e/qpm/sclang_process.py#L62
19
+
20
+ PLOT_HELPER = """
21
+ var plot = {|w, fileName, waitTime|
22
+ var image;
23
+ var filePath;
24
+ (waitTime?1.0).wait;
25
+ fileName = fileName ? "sc_%.png".format({rrand(0, 9)}.dup(10).join(""));
26
+ filePath = cwd +/+ fileName;
27
+ if(w.isKindOf(Plotter), {
28
+ w = w.parent;
29
+ });
30
+ if(w.isNil, {"Can not create image of closed window".throw;});
31
+ image = Image.fromWindow(w).write(filePath);
32
+ "-----PLOTTED_IMAGE_%-----".format(filePath).postln;
33
+ image;
34
+ };
35
+ """
36
+
37
+ RECORD_HELPER = """
38
+ var record = {|duration, fileName|
39
+ var filePath;
40
+ var recorder;
41
+ fileName = fileName ? "sc_%.flac".format({rrand(0, 9)}.dup(10).join(""));
42
+ filePath = cwd +/+ fileName;
43
+ Server.default.recorder.recHeaderFormat = "flac";
44
+ Server.default.recorder.recSampleFormat = "int16";
45
+ recorder = Server.default.record(path: filePath, duration: duration);
46
+ (duration+1).wait;
47
+ "-----RECORDED_AUDIO_%-----".format(filePath).postln;
48
+ recorder;
49
+ };
50
+ """
51
+
52
+ EXEC_WRAPPER = partial(
53
+ """
54
+ var cwd = "{cwd}";
55
+ {plot_helper}
56
+ {record_helper}
57
+
58
+ {{
59
+ var result;
60
+ "**** JUPYTER ****".postln;
61
+ result = {{ try {{
62
+ {code}
63
+ }} {{|error| "ERROR %".format(error).postln}} }}.value();
64
+ postf("-> %%\n", result);
65
+ "**** /JUPYTER ****".postln;
66
+ }}.fork(AppClock);
67
+ """.format, # noqa
68
+ cwd=os.getcwd(),
69
+ plot_helper="" if os.environ.get("NO_QT") else PLOT_HELPER,
70
+ record_helper=RECORD_HELPER,
71
+ )
72
+
73
+ SEARCH_CLASSES = partial(
74
+ """
75
+ var getClasses = {{|t|
76
+ var res = [];
77
+ if(t.size>2, {{
78
+ Class.allClasses.do({{ |class|
79
+ var name = class.name.asString;
80
+ if (name.beginsWith(t)) {{
81
+ res = res.add(name);
82
+ }};
83
+ }});
84
+ }});
85
+ res.cs;
86
+ }};
87
+ getClasses.("{search_term}");
88
+ """.format # noqa
89
+ )
90
+
91
+
92
+ class BgColors:
93
+ # from https://stackoverflow.com/a/287944/3475778
94
+ FAIL = "\033[91m"
95
+ END = "\x1b[0m"
96
+
16
97
 
17
98
  def get_kernel_json():
18
- """Get the kernel json for the kernel.
19
- """
99
+ """Get the kernel json for the kernel."""
20
100
  here = os.path.dirname(__file__)
21
- with open(os.path.join(here, 'kernel.json')) as fid:
101
+ with open(os.path.join(here, "kernel.json")) as fid:
22
102
  data = json.load(fid)
23
- data['argv'][0] = sys.executable
103
+ data["argv"][0] = sys.executable
24
104
  return data
25
105
 
26
106
 
27
107
  class SCKernel(ProcessMetaKernel):
28
- app_name = 'supercollider_kernel'
29
- name = 'SuperCollider Kernel'
30
- implementation = 'sc_kernel'
108
+ app_name = "supercollider_kernel"
109
+ name = "SuperCollider Kernel"
110
+ implementation = "sc_kernel"
31
111
  implementation_version = __version__
32
- language = 'supercollider'
112
+ language = "supercollider"
33
113
  language_info = {
34
- 'mimetype': 'text/x-sclang',
35
- 'name': 'smalltalk', # although supercollider is included in pygments its not working here
36
- 'file_extension': '.scd',
37
- 'pygments_lexer': 'supercollider',
114
+ "mimetype": "text/x-sclang",
115
+ "name": "smalltalk", # although supercollider is included in pygments its not working here
116
+ "file_extension": ".scd",
117
+ "pygments_lexer": "supercollider",
38
118
  }
39
119
  kernel_json = get_kernel_json()
40
120
 
41
- METHOD_DUMP_REGEX = re.compile(r'(\w*)\s*\(')
42
- HTML_HELP_FILE_PATH_REGEX = re.compile(r'-> file:\/\/(.*\.html)')
121
+ HTML_HELP_FILE_PATH_REGEX = re.compile(r"-> file:\/\/(.*\.html)")
43
122
  SCHELP_HELP_FILE_PATH_REGEX = re.compile(r"<a href='file:\/\/(.*\.schelp)'>")
44
- SC_VERSION_REGEX = re.compile(r'sclang (\d+(\.\d+)+)')
45
- METHOD_EXTRACTOR_REGEX = re.compile(r'([A-Z]\w*)\.(.*)')
46
- RECORD_MAGIC_REGEX = re.compile(r'%%\s?record \"?([A-z \.]*)\"?')
123
+ SC_VERSION_REGEX = re.compile(r"sclang (\d+(\.\d+)+)")
124
+ PLOT_REGEX = re.compile(r"-{5}PLOTTED_IMAGE_(?P<ImagePath>[^%].*)-{5}")
125
+ RECORDING_REGEX = re.compile(r"-{5}RECORDED_AUDIO_(?P<AudioPath>[^%].*)-{5}")
47
126
 
48
127
  @property
49
128
  def language_version(self):
@@ -51,118 +130,97 @@ class SCKernel(ProcessMetaKernel):
51
130
 
52
131
  @property
53
132
  def banner(self):
54
- return check_output([self._sclang_path, '-v']).decode('utf-8')
133
+ return check_output([self._sclang_path, "-v"]).decode("utf-8")
55
134
 
56
135
  def __init__(self, *args, **kwargs):
57
136
  super().__init__(*args, **kwargs)
58
137
  self._sclang_path = self._get_sclang_path()
59
138
  self.__sclang: Optional[REPLWrapper] = None
60
- self.__sc_classes: Optional[List[str]] = None
61
139
  self.wrapper = self._sclang
62
- self.recording_paths = set()
63
140
 
64
141
  @staticmethod
65
142
  def _get_sclang_path() -> str:
66
- sclang_path = os.environ.get('SCLANG_PATH')
143
+ sclang_path = os.environ.get("SCLANG_PATH")
67
144
  if not sclang_path:
68
145
  p = platform.system()
69
- if p == 'Linux':
70
- sclang_path = 'sclang'
71
- if p == 'Darwin':
72
- sclang_path = '/Applications/SuperCollider.app/Contents/MacOS/sclang'
73
- if p == 'Windows':
146
+ if p == "Linux":
147
+ sclang_path = "sclang"
148
+ if p == "Darwin":
149
+ sclang_path = "/Applications/SuperCollider.app/Contents/MacOS/sclang"
150
+ if p == "Windows":
74
151
  raise NotImplementedError
75
152
  return sclang_path # type: ignore
76
153
 
77
154
  @property
78
- def _sclang(self) -> 'ScREPLWrapper':
155
+ def _sclang(self) -> "ScREPLWrapper":
79
156
  if self.__sclang:
80
157
  return self.__sclang # type: ignore
81
- self.__sclang = ScREPLWrapper(f'{self._sclang_path} -i jupyter')
158
+ self.__sclang = ScREPLWrapper(f"{self._sclang_path} -i jupyter")
82
159
  return self.__sclang
83
160
 
84
161
  def do_execute_direct(self, code: str, silent=False):
85
- if code == '.':
86
- code = 'CmdPeriod.run;'
87
- for file_recording in self.RECORD_MAGIC_REGEX.findall(code):
88
- # check https://en.wikipedia.org/wiki/HTML5_audio#Supported_audio_coding_formats
89
- # for available formats in a browser and
90
- # http://doc.sccode.org/Classes/SoundFile.html#-headerFormat
91
- # for available SC formats
92
- _, file_ext = os.path.splitext(file_recording)
93
- if file_ext.lower() not in ['.flac', '.wav']:
94
- self.log.error('Only FLAC and WAV is supported for browser playback!')
95
- file_path = os.path.join(os.getcwd(), file_recording)
96
- self.log.info(f'Start recording to {file_path}')
97
- recording_code = f"""
98
- s.recorder.recHeaderFormat = "{file_ext[1:]}";
99
- s.recorder.recSampleFormat = "int24";
100
- s.record("{file_path}");
101
- """
102
- code = recording_code + code
103
- # remove magic from code execution
104
- code = self.RECORD_MAGIC_REGEX.sub('', code)
105
-
162
+ if code == ".":
163
+ code = "CmdPeriod.run;"
106
164
  return super().do_execute_direct(
107
165
  code=code,
108
166
  silent=silent,
109
167
  )
110
168
 
111
169
  def _check_for_recordings(self, message):
112
- """
113
- As Jupyter Notebook struggles with audio files that are still written to we can only display
114
- audio files once they are fully written.
115
- As SuperCollider is only displaying the file name at the end
116
-
117
- :param message:
118
- :return:
119
- """
120
- # we also want to check the output that has been echoed before the matching of the regex
121
- # because the SClang recorder works prepares async and we will not capture its output
122
- # without preparing the record otherwise, check the docs
123
- # https://doc.sccode.org/Classes/Recorder.html
124
- message = message + self._sclang.before_output
125
- recording_paths: List[str] = self._sclang.RECORD_MATCHER_REGEX.findall(message)
126
- self.recording_paths.update(recording_paths)
127
- for recording_path in recording_paths:
128
- self.log.info(f'Found new recording: {recording_path}')
129
-
130
- finished_recordings: List[str] = self._sclang.RECORDING_STOPPED_REGEX.findall(message)
131
-
132
- displayed_recordings: List[str] = []
133
- for finished_recording in finished_recordings:
134
- for recording_path in [f for f in self.recording_paths if finished_recording in f]:
135
- self.log.info(f'Found finished recording: {recording_path}')
136
- time.sleep(1.0) # wait for finished writing, just in case
137
- self.Display(Audio(filename=recording_path))
138
- displayed_recordings.append(recording_path)
139
- self.recording_paths.difference_update(displayed_recordings)
170
+ audio_path: str
171
+ for audio_path in self.RECORDING_REGEX.findall(message):
172
+ try:
173
+ self.Display(Audio(audio_path))
174
+ except ValueError as e:
175
+ message += f"Error displaying {audio_path}: {e}\n"
176
+ return self.RECORDING_REGEX.sub("", message)
177
+
178
+ def _check_for_plot(self, message: str) -> str:
179
+ image_path: str
180
+ for image_path in self.PLOT_REGEX.findall(message):
181
+ try:
182
+ self.Display(Image(filename=image_path))
183
+ except (ValueError, FileNotFoundError) as e:
184
+ message += f"Error displaying {image_path}: {e}\n"
185
+
186
+ return self.PLOT_REGEX.sub("", message)
140
187
 
141
188
  def Write(self, message):
142
- self._check_for_recordings(message)
189
+ message = self._check_for_recordings(message)
190
+ message = self._check_for_plot(message)
143
191
  super().Write(message)
144
192
 
145
- @property
146
- def _sc_classes(self) -> List[str]:
147
- if self.__sc_classes:
148
- return self.__sc_classes
149
- self.__sc_classes = self._sclang.run_command('Class.allClasses.do({|c| c.postln; nil;})\n').split('\n')[:-1]
150
- return self.__sc_classes # type: ignore
151
-
152
- def get_completions(self, info):
153
- code: str = info['obj'] # returns everything in the line before the cursor
154
- if '.' not in code:
155
- # only return classes if no dot is present
156
- return [c for c in self._sc_classes if c.startswith(code)]
157
- if code.count('.') == 1:
158
- # @todo too hacky :/
159
- for sc_class, sc_method in self.METHOD_EXTRACTOR_REGEX.findall(code):
160
- output = self._sclang.run_command(f'{sc_class}.dumpAllMethods;', timeout=10)
161
- return [f'{sc_class}.{m}' for m in self.METHOD_DUMP_REGEX.findall(output) if m.startswith(sc_method)]
162
-
163
- def get_kernel_help_on(self, info, level=0, none_on_fail=False):
164
- code = info['obj'].split('.')[0]
165
- output = self._sclang.run_command(f'{code}.helpFilePath;')
193
+ @staticmethod
194
+ def extract_class_name(obj: str) -> str:
195
+ reverse_class_name = ""
196
+ # not used but maybe later
197
+ reverse_method_name = None # noqa
198
+
199
+ # chars which delimit a new class
200
+ TERMINAL_CHARS = [" ", "(", ")"]
201
+
202
+ for o in obj[::-1]:
203
+ if o in TERMINAL_CHARS:
204
+ break
205
+ if o == ".":
206
+ # everything after a . is a method - reset our counter
207
+ reverse_method_name = reverse_class_name # noqa
208
+ reverse_class_name = ""
209
+ continue
210
+ reverse_class_name += o
211
+ return reverse_class_name[::-1]
212
+
213
+ def get_completions(self, info: Dict):
214
+ class_name = self.extract_class_name(info["obj"])
215
+ sc_string = self._sclang.run_command(
216
+ SEARCH_CLASSES(search_term=class_name), timeout=5
217
+ )
218
+ # need to remove -> from interpreter
219
+ return json.loads(sc_string[2:])
220
+
221
+ def get_kernel_help_on(self, info: Dict, level=0, none_on_fail=False):
222
+ code = self.extract_class_name(info["obj"])
223
+ output = self._sclang.run_command(f'SCDoc.findHelpFile("{code}");')
166
224
  help_file_paths = self.HTML_HELP_FILE_PATH_REGEX.findall(output)
167
225
  if help_file_paths:
168
226
  if os.path.isfile(help_file_paths[0]):
@@ -178,24 +236,22 @@ class SCKernel(ProcessMetaKernel):
178
236
 
179
237
  class ScREPLWrapper(REPLWrapper):
180
238
  def __init__(self, cmd_or_spawn, *args, **kwargs):
181
- cmd = pexpect.spawn(
182
- cmd_or_spawn,
183
- encoding='utf-8'
184
- )
239
+ cmd = pexpect.spawn(cmd_or_spawn, encoding="utf-8")
185
240
  try:
186
241
  # we wait for the Welcome message and throw everything before it away as well
187
- cmd.expect('Welcome to SuperCollider.*', timeout=15)
242
+ cmd.expect("Welcome to SuperCollider.*", timeout=15)
188
243
  except pexpect.TIMEOUT as e:
189
- print(f'Could not start sclang successfully: {e}')
244
+ print(f"Could not start sclang successfully: {e}")
190
245
  raise e
191
246
 
192
247
  super().__init__(
193
248
  cmd,
194
- prompt_regex='',
195
- prompt_change_cmd='',
249
+ prompt_regex="",
250
+ prompt_change_cmd="",
196
251
  new_prompt_regex="",
197
252
  prompt_emit_cmd="",
198
- *args, **kwargs
253
+ *args,
254
+ **kwargs,
199
255
  )
200
256
  self.child: pexpect.spawn
201
257
  # increase buffer size so streaming of large data goes much faster
@@ -205,9 +261,13 @@ class ScREPLWrapper(REPLWrapper):
205
261
 
206
262
  BEGIN_TOKEN = "**** JUPYTER ****"
207
263
  END_TOKEN = "**** /JUPYTER ****"
208
- COMMAND_REGEX = re.compile(r'\*{4} JUPYTER \*{4}(.*)\*{4} /JUPYTER \*{4}', re.DOTALL)
209
- RECORD_MATCHER_REGEX = re.compile(r"Recording channels \[[\d ,]*\] \.\.\. \npath: \'(.*)'", re.MULTILINE)
210
- RECORDING_STOPPED_REGEX = re.compile(r'Recording Stopped: \((.*)\)')
264
+ COMMAND_REGEX = re.compile(
265
+ r"\*{4} JUPYTER \*{4}(?P<Content>.*)\*{4} /JUPYTER \*{4}", re.DOTALL
266
+ )
267
+ ERROR_REGEX = re.compile(
268
+ r"(\*{4} JUPYTER \*{4})?(?P<Content>.*ERROR: .*\-{35})", re.DOTALL
269
+ )
270
+ THROW_REGEX = re.compile(r"(?P<Content>(ERROR: .*)?CALL STACK:)", re.DOTALL)
211
271
 
212
272
  def run_command(self, command, timeout=30, *args, **kwargs):
213
273
  """
@@ -223,26 +283,27 @@ class ScREPLWrapper(REPLWrapper):
223
283
  :param kwargs:
224
284
  :return: output of command as a string
225
285
  """
226
- # idea from
227
- # https://github.com/supercollider/qpm/blob/d3f72894e289744f01f3c098ab0a474d5190315e/qpm/sclang_process.py#L62
228
-
229
- exec_command = '{ var result; "%s".postln; result = {%s}.value(); postf("-> %%\n", result); "%s".postln;}.fork(AppClock);' % ( # noqa
230
- self.BEGIN_TOKEN, command, self.END_TOKEN) # noqa
231
-
232
286
  # 0x1b is the escape key which tells sclang to evaluate any command b/c
233
287
  # we can not use \n as we can have multiple lines in our command
234
- self.child.sendline(f'{exec_command}{chr(0x1b)}')
288
+ command = EXEC_WRAPPER(code=command)
289
+ # print(command)
290
+ self.child.sendline(f"{command}{chr(0x1b)}")
235
291
 
236
- self.child.expect(self.COMMAND_REGEX, timeout=timeout)
292
+ self.child.expect(
293
+ [self.COMMAND_REGEX, self.ERROR_REGEX, self.THROW_REGEX], timeout=timeout
294
+ )
237
295
 
238
296
  # although \r\n is DOS style it is for some reason used by UNIX, see
239
297
  # https://pexpect.readthedocs.io/en/stable/overview.html#find-the-end-of-line-cr-lf-conventions
240
298
  # we also remove \r\n at start and end of each command with the slicing [2:-2]
241
- output = self._clean_output(self.child.match.groups()[0])
299
+ output = self._clean_output(
300
+ self.child.match.groupdict().get("Content", "ERROR")
301
+ )
242
302
  # output = self.child.match.groups()[0][2:-2].replace('\r\n', '\n')
243
- if 'ERROR: ' in output:
244
- output += self.child.readline().replace('\r\n', '\n')
245
-
303
+ if "ERROR: " in output:
304
+ output += self.child.readline().replace("\r\n", "\n")
305
+ # add red color for fail
306
+ output = f"{BgColors.FAIL} {output} {BgColors.END}"
246
307
  return output
247
308
 
248
309
  @staticmethod
@@ -253,11 +314,11 @@ class ScREPLWrapper(REPLWrapper):
253
314
  :param output:
254
315
  :return:
255
316
  """
256
- if output.startswith('\r\n'):
317
+ if output.startswith("\r\n"):
257
318
  output = output[2:]
258
- if output.endswith('\r\n'):
319
+ if output.endswith("\r\n"):
259
320
  output = output[:-2]
260
- return output.replace('\r\n', '\n')
321
+ return output.replace("\r\n", "\n")
261
322
 
262
323
  @property
263
324
  def before_output(self) -> str:
@@ -0,0 +1,218 @@
1
+ Metadata-Version: 2.4
2
+ Name: sc_kernel
3
+ Version: 0.5.0
4
+ Summary: SuperCollider kernel for Jupyter
5
+ Author: Dennis Scheiba
6
+ License: BSD
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: Operating System :: OS Independent
9
+ Classifier: Framework :: IPython
10
+ Classifier: License :: OSI Approved :: BSD License
11
+ Classifier: Topic :: System :: Shells
12
+ Classifier: Topic :: Multimedia :: Sound/Audio :: Sound Synthesis
13
+ Requires-Python: >=3.10
14
+ Description-Content-Type: text/markdown
15
+ License-File: LICENSE
16
+ Requires-Dist: metakernel<0.31,>=0.23.0
17
+ Requires-Dist: ipython<10.0,>=4.0
18
+ Requires-Dist: pygments<3.0
19
+ Requires-Dist: jupyterlab<5.0,>=3.0
20
+ Requires-Dist: jupyter_server<3.0,>2.0
21
+ Provides-Extra: dev
22
+ Requires-Dist: coverage==5.2.1; extra == "dev"
23
+ Requires-Dist: flake8<=6.0.0; extra == "dev"
24
+ Requires-Dist: unittest-xml-reporting==3.0.4; extra == "dev"
25
+ Requires-Dist: mypy<=1.0.0; extra == "dev"
26
+ Requires-Dist: pre-commit==2.17.0; extra == "dev"
27
+ Requires-Dist: black<=23.1.0; extra == "dev"
28
+ Dynamic: license-file
29
+
30
+ # Supercollider Jupyter Kernel
31
+
32
+ This kernel allows running [SuperCollider](https://supercollider.github.io/) Code in a [Jupyter](https://jupyter.org/) environment.
33
+
34
+ ![Demo Notebook](demo.jpg)
35
+
36
+ ## Installation
37
+
38
+ Please make sure one has installed [SuperCollider](https://supercollider.github.io/) and
39
+ [Python 3 with pip](https://realpython.com/installing-python).
40
+
41
+ * To install the kernel for Jupyter execute
42
+
43
+ ```shell
44
+ pip3 install --upgrade sc-kernel
45
+ ```
46
+
47
+ This will also install [Jupyter Lab](https://jupyter.org/) if it is not already installed on the system.
48
+
49
+ * Start a new Jupyter Lab instance by executing `jupyter lab` in a console.
50
+
51
+ * Click on the SuperCollider icon
52
+
53
+ If one has not installed SuperCollider in the default location, one has to set a environment variable
54
+ called `SCLANG_PATH` which points to the sclang executable.
55
+
56
+ To uninstall the kernel execute
57
+
58
+ ```shell
59
+ jupyter kernelspec uninstall sc_kernel
60
+ ```
61
+
62
+ ### As a Docker container
63
+
64
+ It is also possible to run sc-kernel in a Docker container, although a sound output is not possible in this case.
65
+ Assuming you have cloned the repository and opened a terminal in its directory.
66
+
67
+ ```shell
68
+ # build container - takes some time b/c we build supercollider
69
+ docker build -t sc_kernel .
70
+ # run container
71
+ # -v mounts the current directory to the container
72
+ # -p passes the container port to our host
73
+ docker run -v ${PWD}:/home/sc_kernel -p 8888:8888 sc_kernel
74
+ ```
75
+
76
+ ## Usage
77
+
78
+ Contrary to ScIDE each document will run in its own interpreter and not in a shared one.
79
+ This is the default behavior of Jupyter but maybe this will be changed at a later point.
80
+
81
+ Currently it is only possible to use the default config - if you encounter missing classes
82
+ it is probably caused that they are not available in the default config.
83
+
84
+ ### Stop sound
85
+
86
+ Currently the `Cmd + .` command is not binded. Instead create a new cell with a single dot
87
+
88
+ ```supercollider
89
+ .
90
+ ```
91
+
92
+ and execute this cell. This will transform the command to `CommandPeriod.run;` which is what is actually called on the `Cmd + .` press in the IDE.
93
+
94
+ ### Recording
95
+
96
+ `sc_kernel` provides an easy way to record audio to the local directory and store it embedded in the notebook
97
+ so one can transfer the notebook into a website which has the audio files included.
98
+
99
+ The audio is stored in FLAC with 16 bit resolution.
100
+
101
+ The provided function `record` takes 2 arguments:
102
+
103
+ * Duration in seconds
104
+ * Filename which will be used for the recording, using the path of the notebook as base path.
105
+
106
+ Assuming one has started the server, simply execute
107
+
108
+ ```supercollider
109
+ Ndef(\sine, {
110
+ var sig = SinOsc.ar(LFDNoise0.kr(1.0!2).exprange(100, 400));
111
+ sig = sig * \amp.kr(0.2);
112
+ sig;
113
+ }).play;
114
+
115
+ record.(4.0);
116
+ ```
117
+
118
+ ![Recording](recording.png)
119
+
120
+ ### Plotting
121
+
122
+ `sc_kernel` also provides a way to embed images of SuperCollider windows into the Jupyter document.
123
+ First create a window that you want to embed into the document
124
+
125
+ ```supercollider
126
+ w = {SinOsc.ar(2.0)}.plot(1.0);
127
+ ```
128
+
129
+ After the plotting is finished by the server we can now simply save an image of the window
130
+ to a file and also embed the image into the document via a SuperCollider helper method which is available.
131
+
132
+ ```supercollider
133
+ plot.(w);
134
+ ```
135
+
136
+ ![Plotting magic](plotting.png)
137
+
138
+ The image will be saved relative the directory where `jupyter lab` was executed.
139
+ The optional second argument can be the filename.
140
+
141
+ > Note that `{}.plot` does not return a `Window` but a `Plotter`, but `sc_kernel`
142
+ > accesses the window of a `Plotter` automatically.
143
+ >
144
+ > For plotting e.g. the server meter you need to pass the proper window, so
145
+ >
146
+ > ```supercollider
147
+ > a = s.meter;
148
+ > // a is a ServerMeter
149
+ >
150
+ > // new cell
151
+ > plot.(a.window, "meter.png");
152
+ > ```
153
+
154
+ ### Autocomplete
155
+
156
+ Simply push `Tab` to see available autocompletions.
157
+ This is currently limited to scan for available classes.
158
+
159
+ ### Documentation
160
+
161
+ To display the documentation of a Class, simply prepend a `?` to it and execute it, e.g.
162
+
163
+ ```supercollider
164
+ ?SinOsc
165
+ ```
166
+
167
+ You can also hit `shift <tab>` iff the cursor is behind a class to trigger the inline documentation.
168
+
169
+ ![Inline documentation](inline_docs.png)
170
+
171
+ ### Real Time Collaboration
172
+
173
+ Jupyter Lab allows for real time collaboration in which multiple users can write in the same document from different computers by visiting the Jupyter server via their browser.
174
+ Each user can write and execute sclang statements on your local sclang interpreter and the cursors of each user is shown to everyone.
175
+
176
+ This allows for interactive, shared sessions which can be an interesting live coding sessions.
177
+
178
+ > Be aware that this can be a security threat as it allows for other people from within the network to execute arbitrary sclang commands on your computer
179
+
180
+ To start such a session you can spin Jupyter Lab via
181
+
182
+ ```shell
183
+ jupyter lab --ip 0.0.0.0 --collaborative --NotebookApp.token='sclang'
184
+ ```
185
+
186
+ where the `NotebookApp.token` is the necessary password to login - set it to `''` if no password is wanted.
187
+
188
+ Check out the [documentation on Jupyter Lab](https://jupyterlab.readthedocs.io/en/stable/user/rtc.html) about *Real Time Collaboration*.
189
+
190
+ ## Development
191
+
192
+ Any PR is welcome! Please state the changes in an Issue.
193
+ To contribute, please
194
+
195
+ * Fork the repository and clone it to a local directory
196
+
197
+ * Create a virtual environment and install the dev dependencies
198
+ in it with
199
+
200
+ ```shell
201
+ pip3 install -e ".[dev]"
202
+ ```
203
+
204
+ * If one wants to add the kernel to an existing Jupyter installation one can execute
205
+
206
+ ```shell
207
+ jupyter kernelspec install sc_kernel
208
+ ```
209
+
210
+ and run `jupyter lab` from within the cloned directory as
211
+ we need to have access to `sc_kernel`.
212
+
213
+ * Run `./run_tests.sh` and make a PR :)
214
+ Use `black sc_kernel test` to format the source code.
215
+
216
+ ## Maintainers
217
+
218
+ * [Dennis Scheiba](https://dennis-scheiba.com)
@@ -0,0 +1,14 @@
1
+ sc_kernel/__init__.py,sha256=42STGor_9nKYXumfeV5tiyD_M8VdcddX7CEexmibPBk,22
2
+ sc_kernel/__main__.py,sha256=_ZNuw-RZoWvEFNAWCOMRRIZ11fKNY3WeHAGBrcWaTpU,126
3
+ sc_kernel/kernel.json,sha256=ck5y4eT6sjMINUGTYZBOaDhsoO2Bj8vK0EkYIoSEV4c,106
4
+ sc_kernel/kernel.py,sha256=_j0Wbx12LMqgPJweQZVAJZMhHxOJnDUnQp7K28Vw5K0,11074
5
+ sc_kernel/images/logo-32x32.png,sha256=JsjTZpe6UkvbEqPr8x7Op8shzIuAGnVS3sFFjM3JotE,3949
6
+ sc_kernel/images/logo-64x64.png,sha256=yjFvhw6uio4bx8KOW5RDGGASOyo6JkyE5CaSdh3wbt8,12511
7
+ sc_kernel-0.5.0.data/data/share/jupyter/kernels/sc_kernel/kernel.json,sha256=ck5y4eT6sjMINUGTYZBOaDhsoO2Bj8vK0EkYIoSEV4c,106
8
+ sc_kernel-0.5.0.data/data/share/jupyter/kernels/sc_kernel/logo-32x32.png,sha256=JsjTZpe6UkvbEqPr8x7Op8shzIuAGnVS3sFFjM3JotE,3949
9
+ sc_kernel-0.5.0.data/data/share/jupyter/kernels/sc_kernel/logo-64x64.png,sha256=yjFvhw6uio4bx8KOW5RDGGASOyo6JkyE5CaSdh3wbt8,12511
10
+ sc_kernel-0.5.0.dist-info/licenses/LICENSE,sha256=SFO9qjqxmDQwmS2w2NkXljrvheZBqzgzg6LANm-UA5I,1459
11
+ sc_kernel-0.5.0.dist-info/METADATA,sha256=UsNjKm-kUapEBsfhyjDEEZVZTuKG3cAkSLIueABpW0o,6773
12
+ sc_kernel-0.5.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
13
+ sc_kernel-0.5.0.dist-info/top_level.txt,sha256=REBbQoy_WwYSyTAnZjqKTiwea6PdRv3CVK9My8LKj0g,10
14
+ sc_kernel-0.5.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.35.1)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,139 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: sc-kernel
3
- Version: 0.3.2
4
- Summary: UNKNOWN
5
- Home-page: https://github.com/capital-G/sc_kernel
6
- Author: Dennis Scheiba
7
- License: BSD
8
- Platform: UNKNOWN
9
- Classifier: Framework :: IPython
10
- Classifier: License :: OSI Approved :: BSD License
11
- Classifier: Programming Language :: Python :: 3.6
12
- Classifier: Programming Language :: Python :: 3.7
13
- Classifier: Programming Language :: Python :: 3.8
14
- Classifier: Topic :: System :: Shells
15
- Requires-Python: >=3.6
16
- Description-Content-Type: text/markdown
17
- Requires-Dist: metakernel (>=0.23.0)
18
- Requires-Dist: ipython (>=4.0)
19
- Requires-Dist: pygments (>=2.1)
20
- Requires-Dist: jupyterlab (>=2.0)
21
- Provides-Extra: dev
22
- Requires-Dist: coverage (==5.2.1) ; extra == 'dev'
23
- Requires-Dist: flake8 (==3.8.3) ; extra == 'dev'
24
- Requires-Dist: unittest-xml-reporting (==3.0.4) ; extra == 'dev'
25
- Requires-Dist: mypy (==0.770) ; extra == 'dev'
26
-
27
- # Supercollider Jupyter Kernel
28
-
29
- This kernel allows running [SuperCollider](https://supercollider.github.io/) Code in a [Jupyter](https://jupyter.org/) environment.
30
-
31
- ![Demo Notebook](demo.jpg)
32
-
33
- ## Installation
34
-
35
- Please make sure one has installed [SuperCollider](https://supercollider.github.io/) and
36
- [Python 3 with pip](https://realpython.com/installing-python).
37
-
38
- * To install the kernel for Jupyter execute
39
-
40
- ```shell
41
- pip3 install sc-kernel
42
- ```
43
-
44
- This will also install [Jupyter Lab](https://jupyter.org/) if it is not already installed on the system.
45
-
46
- * Start a new Jupyter Lab instance by executing `jupyter lab` in a console.
47
-
48
- * Click on the SuperCollider icon
49
-
50
- If one has not installed SuperCollider in the default location, one has to set a environment variable
51
- called `SCLANG_PATH` which points to the sclang executable.
52
-
53
- To uninstall the kernel execute
54
-
55
- ```shell
56
- jupyter kernelspec uninstall sc_kernel
57
- ```
58
-
59
- ## Usage
60
-
61
- ### Stop sound
62
-
63
- Currently the `Cmd + .` command is not binded. Instead create a new cell with a single dot
64
-
65
- ```
66
- .
67
- ```
68
-
69
- and execute this cell. This will transform the command to `CommandPeriod.run;` which is what is actually called on the `Cmd + .` press in the IDE.
70
-
71
-
72
- ### Recording
73
-
74
- `sc_kernel` provides an easy way to record audio to the local directory and store it in the notebook
75
- so one can later share the notebook with the sound examples embedded.
76
-
77
- Assuming one has started the server, simply execute
78
-
79
- ```supercollider
80
- %% record "my_file.flac"
81
-
82
- {SinOsc.ar(SinOsc.ar(200)*200)*0.2!2}.play;
83
- ```
84
-
85
- to start the recording.
86
-
87
- To stop the recording, simply stop the Server recording via
88
-
89
- ```supercollider
90
- s.stopRecording;
91
- ```
92
-
93
- If one has chosen FLAC or WAV as file format, one will see a playback menu for the file within the notebook.
94
-
95
- ![Recording magic](recording.png)
96
-
97
- If an relative path is provided as filename it will be put relative to the folder where `jupyter lab` was executed.
98
- If an absolute path is given the output will be directed to the absolute path.
99
-
100
- Keep in mind that **good browser support only exists for FLAC** and with WAV the seeking does not work.
101
- The standard recording format of supercollider AIFF is not supported by browsers.
102
-
103
- ### Autocomplete
104
-
105
- Simply push `Tab` to see available autocompletions.
106
-
107
- ### Documentation
108
-
109
- To display the documentation of a Class, simply prepend a `?` to it and execute it, e.g.
110
-
111
- ```supercollider
112
- ?SinOsc
113
- ```
114
-
115
- ## Development
116
-
117
- Any PR is welcome! Please state the changes in an Issue.
118
- To contribute, please
119
-
120
- * Fork the repository and clone it to a local directory
121
-
122
- * If one wants to add the kernel to an existing Jupyter installation one can execute
123
-
124
- ```shell
125
- jupyter kernelspec install sc_kernel
126
- ```
127
-
128
- and run `jupyter lab` from within the cloned directory as
129
- we need to have access to `sc_kernel`.
130
-
131
- * Run `./run_tests.sh` and make a PR :)
132
- The tests often run into a pexpect timeout for some reason.
133
- They should pass if they are run alone.
134
-
135
- ## Maintainers
136
-
137
- * [Dennis Scheiba](https://dennis-scheiba.com)
138
-
139
-
@@ -1,14 +0,0 @@
1
- sc_kernel/__init__.py,sha256=bqu4G7Is-8Were-hMi-SthEalL7QW-PAbKWGZ9pVB6U,22
2
- sc_kernel/__main__.py,sha256=_ZNuw-RZoWvEFNAWCOMRRIZ11fKNY3WeHAGBrcWaTpU,126
3
- sc_kernel/kernel.json,sha256=ck5y4eT6sjMINUGTYZBOaDhsoO2Bj8vK0EkYIoSEV4c,106
4
- sc_kernel/kernel.py,sha256=UZte2a7FRM-RZu4zmP_iW49v3je8p2g9jIzWrIDE0sU,10922
5
- sc_kernel/images/logo-32x32.png,sha256=JsjTZpe6UkvbEqPr8x7Op8shzIuAGnVS3sFFjM3JotE,3949
6
- sc_kernel/images/logo-64x64.png,sha256=yjFvhw6uio4bx8KOW5RDGGASOyo6JkyE5CaSdh3wbt8,12511
7
- sc_kernel-0.3.2.data/data/share/jupyter/kernels/sc_kernel/kernel.json,sha256=ck5y4eT6sjMINUGTYZBOaDhsoO2Bj8vK0EkYIoSEV4c,106
8
- sc_kernel-0.3.2.data/data/share/jupyter/kernels/sc_kernel/logo-32x32.png,sha256=JsjTZpe6UkvbEqPr8x7Op8shzIuAGnVS3sFFjM3JotE,3949
9
- sc_kernel-0.3.2.data/data/share/jupyter/kernels/sc_kernel/logo-64x64.png,sha256=yjFvhw6uio4bx8KOW5RDGGASOyo6JkyE5CaSdh3wbt8,12511
10
- sc_kernel-0.3.2.dist-info/LICENSE,sha256=SFO9qjqxmDQwmS2w2NkXljrvheZBqzgzg6LANm-UA5I,1459
11
- sc_kernel-0.3.2.dist-info/METADATA,sha256=T1jqGi-2UTRnsAq8b-rBxWxzAxg_Yf6iiHS590pb90o,3878
12
- sc_kernel-0.3.2.dist-info/WHEEL,sha256=EVRjI69F5qVjm_YgqcTXPnTAv3BfSUr0WVAHuSP3Xoo,92
13
- sc_kernel-0.3.2.dist-info/top_level.txt,sha256=REBbQoy_WwYSyTAnZjqKTiwea6PdRv3CVK9My8LKj0g,10
14
- sc_kernel-0.3.2.dist-info/RECORD,,