From 97dba79b9185e88152c8c21b6eccb97448bc1af7 Mon Sep 17 00:00:00 2001 From: Mickey Li Date: Sun, 19 Jan 2025 19:25:10 +0000 Subject: [PATCH 1/2] Support Sonic-Pi v4+ (#43) * Updated to support sonic-pi >v4 * Minor README fixes * Minor README fixes * Removes minor debugging changes * Added line about example in README --------- Co-authored-by: mickey li --- README.rst | 53 ++++++++++++++++++++++++++++++++++++++---- psonic/psonic.py | 30 ++++++++++++++++++++++-- psonic/synth_server.py | 26 ++++++++++----------- psonic_example.py | 6 +++++ setup.py | 2 +- 5 files changed, 96 insertions(+), 21 deletions(-) create mode 100644 psonic_example.py diff --git a/README.rst b/README.rst index 949f371..b83cecd 100755 --- a/README.rst +++ b/README.rst @@ -7,6 +7,9 @@ great music software created by Sam Aaron (http://sonic-pi.net). At the moment Python-Sonic works with Sonic Pi. It is planned, that it will work with Supercollider, too. +This version supports Sonic Pi versions > 4 when OSC run-code security +was added. + If you like it, use it. If you have some suggestions, tell me (gkvoelkl@nelson-games.de). @@ -26,6 +29,10 @@ $ pip install python-sonic That should work. +For local development you might want to locally install using + +$ pip install -e . + Limitations ----------- @@ -52,18 +59,56 @@ Changelog +--------+-------------------------------------------------------------+ | 0.4.0 | Changes communication ports and recording | +--------+-------------------------------------------------------------+ +| 0.4.4 | Enables GUI Token | ++--------+-------------------------------------------------------------+ Communication ------------- -| The API *python-sonic* communications with *Sonic Pi* over UDP and two - ports. One port is an internal *Sonic Pi* port and could be changed. -| For older *Sonic Pi* Version you have to set the ports explicitly +The API *python-sonic* communications with *Sonic Pi* over UDP and two +ports. One port is an internal *Sonic Pi* GUI port and the second is the +external OSC cue port. + +For >v4 a Token is needed to communicate with Sonic Pi. This token is generated +randomly at runtime when Sonic-Pi is started. The Token is extracted from the sonic-pi log files. +Similarly, the GUI udp port is now randomised at start and must be read from the log file too. +In order to play notes (and not just send OSC cues), a connection must be made to the GUI udp port +using the Token as the first argument in the message. The Token is automatically used on send. .. code-block:: python from psonic import * - set_server_parameter('127.0.0.1',4557,4559) + set_server_parameter('127.0.0.1', -2005799440, 30129, 4560) + + +These values are found from the file `~/.sonic-pi/log/spider.log` +:: + Sonic Pi Spider Server booting... + The time is 2023-10-01 11:01:41 +0100 + Using primary protocol: udp + Detecting port numbers... + Ports: {:server_port=>30129, :gui_port=>30130, :scsynth_port=>30131, :scsynth_send_port=>30131, :osc_cues_port=>4560, :tau_port=>30132, :listen_to_tau_port=>30136} + Token: -2005799440 + Opening UDP Server to listen to GUI on port: 30129 + Spider - Pulling in modules... + Spider - Starting Runtime Server + ... + +This can be automated by using the function `set_server_parameter_from_log` + +.. code-block:: python + + from psonic import * + set_server_parameter_from_log('127.0.0.1') + set_server_parameter_from_log('127.0.0.1', "path-to-log-file") + +Note if the `set_server_parameter` functions are not used, a default connection is created which will not work. + +There is a simple example file `psonic_example.py` which you can run to check that things work. First open sonic-pi, then run the following: + +$ python psonic_example.py + +and a note should be played. Examples -------- diff --git a/psonic/psonic.py b/psonic/psonic.py index d38e856..5606525 100755 --- a/psonic/psonic.py +++ b/psonic/psonic.py @@ -1,3 +1,5 @@ +import os +import re import random from .samples import Sample from .synthesizers import SAW @@ -180,9 +182,33 @@ def save_recording(name): synth_server = SonicPi() -def set_server_parameter(udp_ip="", udp_port=-1, udp_port_osc_message=-1): - synth_server.set_parameter(udp_ip, udp_port, udp_port_osc_message) +def set_server_parameter(udp_ip, token, udp_port=-1, udp_port_osc_message=-1): + synth_server.set_parameter(udp_ip, token, udp_port, udp_port_osc_message) +def set_server_parameter_from_log(udp_ip, log_file=None): + if log_file is None: + # Automatically find the log file + home = os.path.expanduser("~") + log_file = os.path.join(home, ".sonic-pi", "log", "spider.log") + print(f"Reading Parameters From Log File: {log_file}") + + with open(log_file, 'r') as f: + text = f.read() + + # Use regular expressions to extract the values + token_match = re.search(r'Token: (-?\d+)', text) + server_port_match = re.search(r':server_port=>(\d+)', text) + osc_cues_port_match = re.search(r':osc_cues_port=>(\d+)', text) + + token = int(token_match.group(1)) if token_match else None + udp_port = int(server_port_match.group(1)) if server_port_match else None + udp_port_osc_message = int(osc_cues_port_match.group(1)) if osc_cues_port_match else None + + if token and udp_port and udp_port_osc_message: + print(f"Reading Parameters Succeeded. ip: {udp_ip}, token: \"{token}\", server_port: \"{udp_port}\", osc_port: {udp_port_osc_message}") + synth_server.set_parameter(udp_ip, token, udp_port, udp_port_osc_message) + else: + raise RuntimeError(f"Reading Parameters Failed. token: {token}, server_port: {udp_port}, osc_port: {udp_port_osc_message}") def _debug(*args): if __debug: print(args) diff --git a/psonic/synth_server.py b/psonic/synth_server.py index 353e97f..732a104 100755 --- a/psonic/synth_server.py +++ b/psonic/synth_server.py @@ -37,16 +37,11 @@ def sleep(self, duration): def sample(self, command): self.send(command) -## Ports could be find in home ./sonic-pi/log/server-output.log -# Version 3.2.0 -# Listen port: 51235 4557 -# Scsynth port: 51237 4556 -# Scsynth send port: 51237 4556 -# OSC cues port: 4560 4559 -# Erlang port: 51240 4560 -# OSC MIDI out port: 51238 4561 -# OSC MIDI in port: 51239 4562 -# Websocket port: 51241 +## Sonic Pi version 4.X: Ports can be found in +# Windows: C:/Users//.sonic-pi/log/spider.log +# Linux: ~/.sonic-pi/log/spider.log +# Both the server_port and Token are required +# Default OSC Cues Port is still 4560 ## Connection classes ## class SonicPi(SonicPiCommon): @@ -57,7 +52,6 @@ class SonicPi(SonicPiCommon): #UDP_PORT_OSC_MESSAGE = 4559 UDP_PORT_OSC_MESSAGE = 4560 - GUI_ID = 'SONIC_PI_PYTHON' RUN_COMMAND = "/run-code" STOP_COMMAND = "/stop-all-jobs" @@ -69,6 +63,7 @@ def __init__(self): super().__init__() self.udp_port = self.UDP_PORT self.udp_port_osc_message = self.UDP_PORT_OSC_MESSAGE + self.token=None self._init_client() @@ -82,17 +77,18 @@ def _init_client(self): self.udp_port_osc_message ) - def set_parameter(self, udp_ip = "", udp_port=-1, udp_port_osc_message=-1): + def set_parameter(self, udp_ip = "", token = "", udp_port=-1, udp_port_osc_message=-1): super().set_parameter(udp_ip) if udp_port == -1: udp_port = self.UDP_PORT self.udp_port = udp_port + self.token=token if udp_port_osc_message == -1: udp_port_osc_message = self.UDP_PORT_OSC_MESSAGE self.udp_port_osc_message = udp_port_osc_message self._init_client() def play(self, command): - command = 'use_synth :{0}\n'.format(_current_synth.name) + command + command = f'use_synth :{_current_synth.name}\n{command}' self.send(command) def synth(self, command): @@ -124,7 +120,9 @@ def test_connection(self): def send_command(self, address, argument=''): msg = osc_message_builder.OscMessageBuilder(address=address) - msg.add_arg('SONIC_PI_PYTHON') + if self.token is None: + raise RuntimeError("No token specified, please set token from file or manually before sending a command") + msg.add_arg(self.token) if argument != "": msg.add_arg(argument) msg = msg.build() diff --git a/psonic_example.py b/psonic_example.py new file mode 100644 index 0000000..c702942 --- /dev/null +++ b/psonic_example.py @@ -0,0 +1,6 @@ +from psonic import * + +set_server_parameter_from_log("127.0.0.1") +run("play 60") + + diff --git a/setup.py b/setup.py index 60250e6..597a890 100755 --- a/setup.py +++ b/setup.py @@ -26,7 +26,7 @@ setup( name='python-sonic', - version='0.4.3', + version='0.4.4', description='Programming Music with Sonic Pi or Supercollider', long_description=long_description, url='https://github.com/gkvoelkl/python-sonic', From 5a0e873c532a3847e59b15e1f7041f8511e462ac Mon Sep 17 00:00:00 2001 From: Tristram Oaten Date: Sun, 19 Jan 2025 19:25:36 +0000 Subject: [PATCH 2/2] Formatted install instructions (#28) The install instructions seemed confusing to me. I also did a few driveby spelling fixes. Thanks! --- README.rst | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/README.rst b/README.rst index b83cecd..1fa44ea 100755 --- a/README.rst +++ b/README.rst @@ -21,13 +21,7 @@ Installation - Then Sonic Pi (https://sonic-pi.net) - That makes the sound - Modul python-osc (https://pypi.python.org/pypi/python-osc) - Connection between Python and Sonic Pi Server -- And this modul python-sonic - simply copy the source - -Or try - -$ pip install python-sonic - -That should work. +- Add the module `python-sonic` - simply copy the source, or try `pip install python-sonic` That should work. For local development you might want to locally install using