From 012bce65ff7be3965aaa9fd487ff72e890135498 Mon Sep 17 00:00:00 2001 From: python-ninja-hebi Date: Thu, 15 Oct 2020 14:55:28 +0200 Subject: [PATCH 01/17] Version 0.4.0 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) mode change 100644 => 100755 setup.py diff --git a/setup.py b/setup.py old mode 100644 new mode 100755 index a43b2f1..5381683 --- a/setup.py +++ b/setup.py @@ -24,7 +24,7 @@ setup( name='python-sonic', - version='0.3.2', + version='0.4.0', description='Programming Music with Sonic Pi or Supercollider', long_description=long_description, url='https://github.com/gkvoelkl/python-sonic', From f7328041951ffabc43ac815138f069b2c370d806 Mon Sep 17 00:00:00 2001 From: python-ninja-hebi Date: Thu, 15 Oct 2020 15:07:57 +0200 Subject: [PATCH 02/17] Version 0.4.0 --- psonic/notes.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 psonic/notes.py diff --git a/psonic/notes.py b/psonic/notes.py old mode 100644 new mode 100755 From 55d90dd2af48a2077844a3bc32191327134a0a6b Mon Sep 17 00:00:00 2001 From: python-ninja-hebi Date: Thu, 15 Oct 2020 15:08:36 +0200 Subject: [PATCH 03/17] Version 0.4.0 --- tests/test_psonic.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 tests/test_psonic.py diff --git a/tests/test_psonic.py b/tests/test_psonic.py old mode 100644 new mode 100755 From a0e1b3dc2f08d64aa5d9867293c242aab41836ba Mon Sep 17 00:00:00 2001 From: python-ninja-hebi Date: Thu, 15 Oct 2020 15:37:46 +0200 Subject: [PATCH 04/17] Version 0.4.0 --- psonic/psonic.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/psonic/psonic.py b/psonic/psonic.py index 94d6057..d38e856 100755 --- a/psonic/psonic.py +++ b/psonic/psonic.py @@ -167,8 +167,23 @@ def send_message(message, *parameter): synth_server.send_message(message, *parameter) +def start_recording(): + synth_server.start_recording() + + +def stop_recording(): + synth_server.stop_recording() + + +def save_recording(name): + synth_server.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 _debug(*args): if __debug: print(args) From d9b255e8c9ef6d3ba31d67840c4454b64896123a Mon Sep 17 00:00:00 2001 From: python-ninja-hebi Date: Thu, 15 Oct 2020 15:38:24 +0200 Subject: [PATCH 05/17] Version 0.4.0 --- psonic/synth_server.py | 50 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 44 insertions(+), 6 deletions(-) diff --git a/psonic/synth_server.py b/psonic/synth_server.py index e52b6f5..cf9353e 100755 --- a/psonic/synth_server.py +++ b/psonic/synth_server.py @@ -19,6 +19,15 @@ class SonicPiCommon: UDP_IP = "127.0.0.1" + def __init__(self): + self.udp_ip = self.UDP_IP + + def set_parameter(self, udp_ip=""): + if udp_ip == "": + self.udp_ip = self.UDP_IP + else: + self.udp_ip = udp_ip + def send(self, command): pass @@ -28,28 +37,57 @@ 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 ## Connection classes ## class SonicPi(SonicPiCommon): """Communiction to Sonic Pi""" - UDP_PORT = 4557 - UDP_PORT_OSC_MESSAGE = 4559 + #UDP_PORT = 4557 + UDP_PORT = 51235 + + #UDP_PORT_OSC_MESSAGE = 4559 + UDP_PORT_OSC_MESSAGE = 4560 GUI_ID = 'SONIC_PI_PYTHON' RUN_COMMAND = "/run-code" STOP_COMMAND = "/stop-all-jobs" def __init__(self): + super().__init__() + self.udp_port = self.UDP_PORT + self.udp_port_osc_message = self.UDP_PORT_OSC_MESSAGE + + self._init_client() + + def _init_client(self): self.client = udp_client.UDPClient( - self.UDP_IP, - self.UDP_PORT + self.udp_ip, + self.udp_port ) self.client_for_messages = udp_client.UDPClient( - self.UDP_IP, - self.UDP_PORT_OSC_MESSAGE + self.udp_ip, + self.udp_port_osc_message ) + def set_parameter(self, udp_ip = "", 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 + 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 self.send(command) From f761621682e5d15248419c577b78e2e328f9fe8b Mon Sep 17 00:00:00 2001 From: python-ninja-hebi Date: Thu, 15 Oct 2020 15:39:01 +0200 Subject: [PATCH 06/17] Version 0.4.0 --- python-sonic.ipynb | 400 +++++++++++++++++++++++++++------------------ 1 file changed, 245 insertions(+), 155 deletions(-) diff --git a/python-sonic.ipynb b/python-sonic.ipynb index 8aa4d2d..f5bd8d0 100755 --- a/python-sonic.ipynb +++ b/python-sonic.ipynb @@ -39,9 +39,7 @@ }, { "cell_type": "raw", - "metadata": { - "collapsed": true - }, + "metadata": {}, "source": [ "$ pip install python-sonic" ] @@ -84,7 +82,31 @@ "| 0.2.0 | Some changes for Sonic Pi 2.11. Simpler multi-threading with decorator *@in_thread*. Messaging with *cue* and *sync*. |\n", "| 0.3.0 | OSC Communication | \n", "| 0.3.1. | Update, sort and duration of samples |\n", - "| 0.3.2. | Restructured |" + "| 0.3.2. | Restructured |\n", + "| 0.4.0 | Changes communication ports and recording |" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Communication" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "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. \n", + "For older *Sonic Pi* Version you have to set the ports explicitly" + ] + }, + { + "cell_type": "raw", + "metadata": {}, + "source": [ + "from psonic import *\n", + "set_server_parameters('127.0.0.1',4557,4559)" ] }, { @@ -103,10 +125,8 @@ }, { "cell_type": "code", - "execution_count": 4, - "metadata": { - "collapsed": true - }, + "execution_count": 1, + "metadata": {}, "outputs": [], "source": [ "from psonic import *" @@ -121,10 +141,8 @@ }, { "cell_type": "code", - "execution_count": 5, - "metadata": { - "collapsed": true - }, + "execution_count": 2, + "metadata": {}, "outputs": [], "source": [ "play(70) #play MIDI note 70" @@ -139,10 +157,8 @@ }, { "cell_type": "code", - "execution_count": 10, - "metadata": { - "collapsed": true - }, + "execution_count": 3, + "metadata": {}, "outputs": [], "source": [ "play(72)\n", @@ -161,10 +177,8 @@ }, { "cell_type": "code", - "execution_count": 11, - "metadata": { - "collapsed": true - }, + "execution_count": 4, + "metadata": {}, "outputs": [], "source": [ "play(C5)\n", @@ -184,9 +198,7 @@ { "cell_type": "code", "execution_count": 5, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "play(Fs5)\n", @@ -204,9 +216,7 @@ { "cell_type": "code", "execution_count": 6, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "play(72,amp=2)\n", @@ -223,10 +233,8 @@ }, { "cell_type": "code", - "execution_count": 12, - "metadata": { - "collapsed": true - }, + "execution_count": 7, + "metadata": {}, "outputs": [], "source": [ "use_synth(SAW)\n", @@ -248,10 +256,8 @@ }, { "cell_type": "code", - "execution_count": 13, - "metadata": { - "collapsed": true - }, + "execution_count": 8, + "metadata": {}, "outputs": [], "source": [ "play (60, attack=0.5, decay=1, sustain_level=0.4, sustain=2, release=0.5) \n", @@ -268,9 +274,7 @@ { "cell_type": "code", "execution_count": 9, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "sample(AMBI_LUNAR_LAND, amp=0.5)" @@ -278,10 +282,8 @@ }, { "cell_type": "code", - "execution_count": 4, - "metadata": { - "collapsed": true - }, + "execution_count": 10, + "metadata": {}, "outputs": [], "source": [ "sample(LOOP_AMEN,pan=-1)\n", @@ -292,9 +294,7 @@ { "cell_type": "code", "execution_count": 11, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "sample(LOOP_AMEN,rate=0.5)" @@ -303,9 +303,7 @@ { "cell_type": "code", "execution_count": 12, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "sample(LOOP_AMEN,rate=1.5)" @@ -314,9 +312,7 @@ { "cell_type": "code", "execution_count": 13, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "sample(LOOP_AMEN,rate=-1)#back" @@ -325,9 +321,7 @@ { "cell_type": "code", "execution_count": 14, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "sample(DRUM_CYMBAL_OPEN,attack=0.01,sustain=0.3,release=0.1)" @@ -336,9 +330,7 @@ { "cell_type": "code", "execution_count": 15, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "sample(LOOP_AMEN,start=0.5,finish=0.8,rate=-0.2,attack=0.3,release=1)" @@ -354,9 +346,7 @@ { "cell_type": "code", "execution_count": 16, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "import random\n", @@ -369,9 +359,7 @@ { "cell_type": "code", "execution_count": 17, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "for i in range(3):\n", @@ -388,10 +376,8 @@ }, { "cell_type": "code", - "execution_count": 5, - "metadata": { - "collapsed": true - }, + "execution_count": 18, + "metadata": {}, "outputs": [], "source": [ "from psonic import *\n", @@ -449,11 +435,21 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], + "execution_count": 19, + "metadata": {}, + "outputs": [ + { + "ename": "KeyboardInterrupt", + "evalue": "", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mKeyboardInterrupt\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 22\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 23\u001b[0m \u001b[0;32mwhile\u001b[0m \u001b[0;32mTrue\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 24\u001b[0;31m \u001b[0;32mpass\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;31mKeyboardInterrupt\u001b[0m: " + ] + } + ], "source": [ "import random\n", "from psonic import *\n", @@ -494,10 +490,10 @@ "metadata": {}, "outputs": [ { - "name": "stdout", + "name": "stdin", "output_type": "stream", "text": [ - "Press Enter to continue...\n" + "Press Enter to continue... \n" ] }, { @@ -565,25 +561,15 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": null, "metadata": {}, "outputs": [ { - "name": "stdout", + "name": "stdin", "output_type": "stream", "text": [ - "Press Enter to continue...\n" + "Press Enter to continue... \n" ] - }, - { - "data": { - "text/plain": [ - "''" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" } ], "source": [ @@ -624,9 +610,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "from psonic import *\n", @@ -661,10 +645,8 @@ }, { "cell_type": "code", - "execution_count": 1, - "metadata": { - "collapsed": true - }, + "execution_count": 2, + "metadata": {}, "outputs": [], "source": [ "from psonic import *\n", @@ -685,9 +667,7 @@ { "cell_type": "code", "execution_count": 2, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "play(chord(E4, MINOR)) \n", @@ -710,9 +690,7 @@ { "cell_type": "code", "execution_count": 3, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "play_pattern( chord(E4, 'm7')) \n", @@ -729,10 +707,8 @@ }, { "cell_type": "code", - "execution_count": 5, - "metadata": { - "collapsed": true - }, + "execution_count": 4, + "metadata": {}, "outputs": [], "source": [ "play_pattern_timed(scale(C3, MAJOR), 0.125, release = 0.1) \n", @@ -749,7 +725,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 5, "metadata": {}, "outputs": [ { @@ -758,7 +734,7 @@ "[48, 50, 52, 53, 55, 57, 59, 60]" ] }, - "execution_count": 9, + "execution_count": 5, "metadata": {}, "output_type": "execute_result" } @@ -773,10 +749,8 @@ }, { "cell_type": "code", - "execution_count": 10, - "metadata": { - "collapsed": true - }, + "execution_count": 6, + "metadata": {}, "outputs": [], "source": [ "s.reverse()" @@ -784,10 +758,8 @@ }, { "cell_type": "code", - "execution_count": 11, - "metadata": { - "collapsed": true - }, + "execution_count": 7, + "metadata": {}, "outputs": [], "source": [ "\n", @@ -863,7 +835,10 @@ "cell_type": "code", "execution_count": 6, "metadata": { - "collapsed": true + "collapsed": true, + "jupyter": { + "outputs_hidden": true + } }, "outputs": [], "source": [ @@ -877,7 +852,10 @@ "cell_type": "code", "execution_count": 7, "metadata": { - "collapsed": true + "collapsed": true, + "jupyter": { + "outputs_hidden": true + } }, "outputs": [], "source": [ @@ -891,7 +869,10 @@ "cell_type": "code", "execution_count": 8, "metadata": { - "collapsed": true + "collapsed": true, + "jupyter": { + "outputs_hidden": true + } }, "outputs": [], "source": [ @@ -980,7 +961,10 @@ "cell_type": "code", "execution_count": 11, "metadata": { - "collapsed": true + "collapsed": true, + "jupyter": { + "outputs_hidden": true + } }, "outputs": [], "source": [ @@ -993,7 +977,10 @@ "cell_type": "code", "execution_count": 14, "metadata": { - "collapsed": true + "collapsed": true, + "jupyter": { + "outputs_hidden": true + } }, "outputs": [], "source": [ @@ -1076,7 +1063,10 @@ "cell_type": "code", "execution_count": 3, "metadata": { - "collapsed": true + "collapsed": true, + "jupyter": { + "outputs_hidden": true + } }, "outputs": [], "source": [ @@ -1085,9 +1075,7 @@ }, { "cell_type": "markdown", - "metadata": { - "collapsed": true - }, + "metadata": {}, "source": [ "More complex live loops" ] @@ -1096,7 +1084,10 @@ "cell_type": "code", "execution_count": 4, "metadata": { - "collapsed": true + "collapsed": true, + "jupyter": { + "outputs_hidden": true + } }, "outputs": [], "source": [ @@ -1211,7 +1202,10 @@ "cell_type": "code", "execution_count": 12, "metadata": { - "collapsed": true + "collapsed": true, + "jupyter": { + "outputs_hidden": true + } }, "outputs": [], "source": [ @@ -1225,7 +1219,10 @@ "cell_type": "code", "execution_count": 7, "metadata": { - "collapsed": true + "collapsed": true, + "jupyter": { + "outputs_hidden": true + } }, "outputs": [], "source": [ @@ -1239,7 +1236,10 @@ "cell_type": "code", "execution_count": 14, "metadata": { - "collapsed": true + "collapsed": true, + "jupyter": { + "outputs_hidden": true + } }, "outputs": [], "source": [ @@ -1253,7 +1253,10 @@ "cell_type": "code", "execution_count": 13, "metadata": { - "collapsed": true + "collapsed": true, + "jupyter": { + "outputs_hidden": true + } }, "outputs": [], "source": [ @@ -1275,7 +1278,10 @@ "cell_type": "code", "execution_count": 15, "metadata": { - "collapsed": true + "collapsed": true, + "jupyter": { + "outputs_hidden": true + } }, "outputs": [], "source": [ @@ -1294,6 +1300,9 @@ "execution_count": 4, "metadata": { "collapsed": true, + "jupyter": { + "outputs_hidden": true + }, "scrolled": true }, "outputs": [], @@ -1309,7 +1318,10 @@ "cell_type": "code", "execution_count": 5, "metadata": { - "collapsed": true + "collapsed": true, + "jupyter": { + "outputs_hidden": true + } }, "outputs": [], "source": [ @@ -1322,7 +1334,10 @@ "cell_type": "code", "execution_count": 12, "metadata": { - "collapsed": true + "collapsed": true, + "jupyter": { + "outputs_hidden": true + } }, "outputs": [], "source": [ @@ -1352,7 +1367,10 @@ "cell_type": "code", "execution_count": 2, "metadata": { - "collapsed": true + "collapsed": true, + "jupyter": { + "outputs_hidden": true + } }, "outputs": [], "source": [ @@ -1367,7 +1385,10 @@ "cell_type": "code", "execution_count": 3, "metadata": { - "collapsed": true + "collapsed": true, + "jupyter": { + "outputs_hidden": true + } }, "outputs": [], "source": [ @@ -1394,9 +1415,7 @@ { "cell_type": "code", "execution_count": 1, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "from psonic import *" @@ -1411,15 +1430,13 @@ }, { "cell_type": "code", - "execution_count": 3, - "metadata": { - "collapsed": true - }, + "execution_count": 2, + "metadata": {}, "outputs": [], "source": [ "run(\"\"\"live_loop :foo do\n", " use_real_time\n", - " a, b, c = sync \"/osc/trigger/prophet\"\n", + " a, b, c = sync \"/osc*/trigger/prophet\"\n", " synth :prophet, note: a, cutoff: b, sustain: c\n", "end \"\"\")" ] @@ -1433,10 +1450,8 @@ }, { "cell_type": "code", - "execution_count": 4, - "metadata": { - "collapsed": true - }, + "execution_count": 3, + "metadata": {}, "outputs": [], "source": [ "send_message('/trigger/prophet', 70, 100, 8)" @@ -1444,15 +1459,86 @@ }, { "cell_type": "code", - "execution_count": 5, - "metadata": { - "collapsed": true - }, + "execution_count": 4, + "metadata": {}, "outputs": [], "source": [ "stop()" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Recording" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "With python-sonic you can record wave files." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "from psonic import *" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "# start recording\n", + "start_recording()\n", + "\n", + "play(chord(E4, MINOR)) \n", + "sleep(1)\n", + "play(chord(E4, MAJOR))\n", + "sleep(1)\n", + "play(chord(E4, MINOR7))\n", + "sleep(1)\n", + "play(chord(E4, DOM7))\n", + "sleep(1)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# stop recording\n", + "stop_recording" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "# save file\n", + "save_recording('/Volumes/jupyter/python-sonic/test.wav')" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -1464,7 +1550,10 @@ "cell_type": "code", "execution_count": 1, "metadata": { - "collapsed": true + "collapsed": true, + "jupyter": { + "outputs_hidden": true + } }, "outputs": [], "source": [ @@ -1475,7 +1564,10 @@ "cell_type": "code", "execution_count": null, "metadata": { - "collapsed": true + "collapsed": true, + "jupyter": { + "outputs_hidden": true + } }, "outputs": [], "source": [ @@ -1507,9 +1599,7 @@ }, { "cell_type": "markdown", - "metadata": { - "collapsed": true - }, + "metadata": {}, "source": [ "## Sources" ] @@ -1553,9 +1643,9 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.4" + "version": "3.7.9" } }, "nbformat": 4, - "nbformat_minor": 1 + "nbformat_minor": 4 } From ad2563160a6edf6dce23fbfaff1070c87947a374 Mon Sep 17 00:00:00 2001 From: python-ninja-hebi Date: Thu, 15 Oct 2020 15:39:39 +0200 Subject: [PATCH 07/17] Version 0.4.0 --- README.rst | 150 ++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 101 insertions(+), 49 deletions(-) diff --git a/README.rst b/README.rst index a45ccd9..22403b1 100755 --- a/README.rst +++ b/README.rst @@ -1,4 +1,3 @@ - python-sonic - Programming Music with Python, Sonic Pi or Supercollider ======================================================================= @@ -14,8 +13,8 @@ If you like it, use it. If you have some suggestions, tell me Installation ------------ -- First you need Python 3 (https://www.python.org) - Python 3.5 - should work, because it's the development environment +- First you need Python 3 (https://www.python.org, ) - Python 3.5 + should work, because it’s the development environment - 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 @@ -32,22 +31,37 @@ Limitations - You have to start *Sonic Pi* first before you can use it with python-sonic +- Only the notes from C2 to C6 Changelog --------- -+------------+---------------------------------------------------------------+ -| Version | | -+============+===============================================================+ -| 0.2.0 | Some changes for Sonic Pi 2.11. Simpler multi-threading with | -| | decorator *@in\_thread*. Messaging with *cue* and *sync*. | -+------------+---------------------------------------------------------------+ -| 0.3.0 | OSC Communication | -+------------+---------------------------------------------------------------+ -| 0.3.1. | Update, sort and duration of samples | -+------------+---------------------------------------------------------------+ -| 0.3.2. | Restructured | -+------------+---------------------------------------------------------------+ ++--------+-------------------------------------------------------------+ +| V | | +| ersion | | ++========+=============================================================+ +| 0.2.0 | Some changes for Sonic Pi 2.11. Simpler multi-threading | +| | with decorator *@in_thread*. Messaging with *cue* and | +| | *sync*. | ++--------+-------------------------------------------------------------+ +| 0.3.0 | OSC Communication | ++--------+-------------------------------------------------------------+ +| 0.3.1. | Update, sort and duration of samples | ++--------+-------------------------------------------------------------+ +| 0.3.2. | Restructured | ++--------+-------------------------------------------------------------+ +| 0.4.0 | Changes communication ports and recording | ++--------+-------------------------------------------------------------+ + +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 + +from psonic import * +set_server_parameters('127.0.0.1',4557,4559) Examples -------- @@ -74,7 +88,7 @@ Some more notes sleep(1) play(79) -In more traditional music notation +In more tratitional music notation .. code:: ipython3 @@ -249,8 +263,25 @@ If you want to hear more than one sound at a time, use Threads. while True: pass -Every function *bass\_sound* and *snare\_sound* have its own thread. -Your can hear them running. + +:: + + + --------------------------------------------------------------------------- + + KeyboardInterrupt Traceback (most recent call last) + + in + 22 + 23 while True: + ---> 24 pass + + + KeyboardInterrupt: + + +Every function *bass_sound* and *snare_sound* have its own thread. Your +can hear them running. .. code:: ipython3 @@ -293,7 +324,7 @@ Your can hear them running. .. parsed-literal:: - Press Enter to continue... + Press Enter to continue... @@ -309,7 +340,7 @@ you can use *Condition*. One function sends a message with *condition.notifyAll* the other waits until the message comes *condition.wait*. -More simple with decorator \_\_@in\_thread\_\_ +More simple with decorator \_\_@in_thread_\_ .. code:: ipython3 @@ -349,15 +380,7 @@ More simple with decorator \_\_@in\_thread\_\_ .. parsed-literal:: - Press Enter to continue... - - - - -.. parsed-literal:: - - '' - + Press Enter to continue... .. code:: ipython3 @@ -408,19 +431,6 @@ Play chords play(chord(E4, DOM7)) sleep(1) -Play chords with inversions - -.. code:: ipython3 - - play(chord(E4, MAJOR)) - sleep(1) - play(chord(E4, MAJOR, inversion=1)) - sleep(1) - play(chord(E4, MAJOR, 2)) - sleep(1) - play(chord(E3, MAJOR)) - sleep(1) - Play arpeggios .. code:: ipython3 @@ -473,7 +483,7 @@ Live Loop ~~~~~~~~~ One of the best in SONIC PI is the *Live Loop*. While a loop is playing -music you can change it and hear the change. Let's try it in Python, +music you can change it and hear the change. Let’s try it in Python, too. .. code:: ipython3 @@ -509,7 +519,7 @@ too. -Now change the function *my\_loop* und you can hear it. +Now change the function *my_loop* und you can hear it. .. code:: ipython3 @@ -533,7 +543,7 @@ Now change the function *my\_loop* und you can hear it. play(random.choice(chord(E3, MINOR)), release= 0.2, cutoff=random.randrange(60, 130)) sleep(0.25) -To stop the sound you have to end the kernel. In IPython with Kernel --> +To stop the sound you have to end the kernel. In IPython with Kernel –> Restart Now with two live loops which are synch. @@ -728,7 +738,7 @@ Now a simple structure with four live loops live_thread_1 = Thread(name='producer', target=live_loop_1, args=(condition,stop_event)) live_thread_2 = Thread(name='consumer1', target=live_loop_2, args=(condition,stop_event)) live_thread_3 = Thread(name='consumer2', target=live_loop_3, args=(condition,stop_event)) - live_thread_4 = Thread(name='consumer3', target=live_loop_4, args=(condition,stop_event)) + live_thread_4 = Thread(name='consumer3', target=live_loop_3, args=(condition,stop_event)) live_thread_1.start() live_thread_2.start() @@ -763,8 +773,8 @@ After starting the loops you can change them .. code:: ipython3 def live_2(): - sample(AMBI_CHOIR, rate=0.4) - sleep(1) + #sample(AMBI_CHOIR, rate=0.4) + #sleep(1) pass .. code:: ipython3 @@ -850,7 +860,7 @@ You can write it in th GUI or send one with Python. run("""live_loop :foo do use_real_time - a, b, c = sync "/osc/trigger/prophet" + a, b, c = sync "/osc*/trigger/prophet" synth :prophet, note: a, cutoff: b, sustain: c end """) @@ -864,6 +874,48 @@ Now send a message to Sonic Pi. stop() +Recording +--------- + +With python-sonic you can record wave files. + +.. code:: ipython3 + + from psonic import * + +.. code:: ipython3 + + # start recording + start_recording() + + play(chord(E4, MINOR)) + sleep(1) + play(chord(E4, MAJOR)) + sleep(1) + play(chord(E4, MINOR7)) + sleep(1) + play(chord(E4, DOM7)) + sleep(1) + +.. code:: ipython3 + + # stop recording + stop_recording + + + + +.. parsed-literal:: + + + + + +.. code:: ipython3 + + # save file + save_recording('/Volumes/jupyter/python-sonic/test.wav') + More Examples ------------- From 1af7f5ce390172270b81c4d14d2b3d6ac9362e16 Mon Sep 17 00:00:00 2001 From: python-ninja-hebi Date: Sun, 25 Oct 2020 15:33:04 +0100 Subject: [PATCH 08/17] Version 0.4.1 --- setup.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 5381683..caf3b62 100755 --- a/setup.py +++ b/setup.py @@ -1,12 +1,14 @@ from setuptools import setup # To use a consistent encoding +# python setup.py sdist bdist_wheel +# twine upload dist/* from codecs import open from os import path here = path.abspath(path.dirname(__file__)) # Get the long description from the README file -with open(path.join(here, 'README.rst'), encoding='utf-8') as f: +with open(path.join(here, 'README.txt'), encoding='utf-8') as f: long_description = f.read() requirements = [ @@ -24,9 +26,11 @@ setup( name='python-sonic', - version='0.4.0', + version='0.4.1', description='Programming Music with Sonic Pi or Supercollider', long_description=long_description, +# long_description_content_type='text/x-rst', + long_description_content_type='text/plain', url='https://github.com/gkvoelkl/python-sonic', author='gkvoelkl', author_email='gkvoelkl@nelson-games.de', @@ -50,6 +54,7 @@ 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7' ], keywords= [ 'music', From d0620f93b656fb37e1ab83f454a2aff0703dd85f Mon Sep 17 00:00:00 2001 From: python-ninja-hebi Date: Sun, 25 Oct 2020 15:35:05 +0100 Subject: [PATCH 09/17] Version 0.4.1 --- psonic/synth_server.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/psonic/synth_server.py b/psonic/synth_server.py index cf9353e..353e97f 100755 --- a/psonic/synth_server.py +++ b/psonic/synth_server.py @@ -61,6 +61,9 @@ class SonicPi(SonicPiCommon): RUN_COMMAND = "/run-code" STOP_COMMAND = "/stop-all-jobs" + START_RECORDING_COMMAND = "/start-recording" + STOP_RECORDING_COMMAND = "/stop-recording" + SAVE_RECORDING_COMMAND = "/save-recording" def __init__(self): super().__init__() @@ -98,6 +101,16 @@ def synth(self, command): def run(self, command): self.send_command(SonicPi.RUN_COMMAND, command) + def start_recording(self): + self.send_command(SonicPi.START_RECORDING_COMMAND) + + def stop_recording(self): + self.send_command(SonicPi.START_RECORDING_COMMAND) + + def save_recording(self, name): + self.send_command(SonicPi.SAVE_RECORDING_COMMAND,name) + + def send(self,command): self.run(command) From b17ded608556c62f55b7e40910c4856215f51133 Mon Sep 17 00:00:00 2001 From: python-ninja-hebi Date: Tue, 24 Nov 2020 16:58:22 +0100 Subject: [PATCH 10/17] Version 0.4.1 --- README.rst | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 22403b1..edd4306 100755 --- a/README.rst +++ b/README.rst @@ -60,8 +60,10 @@ Communication 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 -from psonic import * -set_server_parameters('127.0.0.1',4557,4559) +.. code:: ipython3 + + from psonic import * + set_server_parameter('127.0.0.1',4557,4559) Examples -------- From 7a31edc065494dfdb4938a49e2cb3c496b130956 Mon Sep 17 00:00:00 2001 From: Mike Roberts <2947595+m-roberts@users.noreply.github.com> Date: Wed, 4 Aug 2021 14:58:32 +0100 Subject: [PATCH 11/17] Fix `setup.py` (#35) * Fix README * Revert pytest-runner change --- setup.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index caf3b62..6120bd6 100755 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ here = path.abspath(path.dirname(__file__)) # Get the long description from the README file -with open(path.join(here, 'README.txt'), encoding='utf-8') as f: +with open(path.join(here, 'README.rst'), encoding='utf-8') as f: long_description = f.read() requirements = [ @@ -29,8 +29,7 @@ version='0.4.1', description='Programming Music with Sonic Pi or Supercollider', long_description=long_description, -# long_description_content_type='text/x-rst', - long_description_content_type='text/plain', + long_description_content_type='text/x-rst', url='https://github.com/gkvoelkl/python-sonic', author='gkvoelkl', author_email='gkvoelkl@nelson-games.de', From 2cee64a3dc7aefe1b7668f1f147ac514ea8240d1 Mon Sep 17 00:00:00 2001 From: python-ninja-hebi Date: Sun, 15 Aug 2021 16:37:51 +0200 Subject: [PATCH 12/17] Version 0.4.2 --- PREADME.txt | 1069 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1069 insertions(+) create mode 100755 PREADME.txt diff --git a/PREADME.txt b/PREADME.txt new file mode 100755 index 0000000..ec9f21d --- /dev/null +++ b/PREADME.txt @@ -0,0 +1,1069 @@ +== python-sonic - Programming Music with Python, Sonic Pi or Supercollider + +Python-Sonic is a simple Python interface for Sonic Pi, which is a real +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. + +If you like it, use it. If you have some suggestions, tell me +(gkvoelkl@nelson-games.de). + +== Installation + +* First you need Python 3 (https://www.python.org, ) - Python 3.5 should +work, because it’s the development environment +* 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. + +== Limitations + +* You have to start _Sonic Pi_ first before you can use it with +python-sonic +* Only the notes from C2 to C6 + +== Changelog + +[width="100%",cols="14%,86%",options="header",] +|=== +|Version | +|0.2.0 |Some changes for Sonic Pi 2.11. Simpler multi-threading with +decorator _@in_thread_. Messaging with _cue_ and _sync_. + +|0.3.0 |OSC Communication + +|0.3.1. |Update, sort and duration of samples + +|0.3.2. |Restructured + +|0.4.0 |Changes communication ports and recording +|=== + +== 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 +from psonic import * +set_server_parameters('127.0.0.1',4557,4559) +== Examples + +Many of the examples are inspired from the help menu in _Sonic Pi_. + + ++*In[1]:*+ +[source, ipython3] +---- +from psonic import * +---- + +The first sound + + ++*In[2]:*+ +[source, ipython3] +---- +play(70) #play MIDI note 70 +---- + +Some more notes + + ++*In[3]:*+ +[source, ipython3] +---- +play(72) +sleep(1) +play(75) +sleep(1) +play(79) +---- + +In more tratitional music notation + + ++*In[4]:*+ +[source, ipython3] +---- +play(C5) +sleep(0.5) +play(D5) +sleep(0.5) +play(G5) +---- + +Play sharp notes like _F#_ or dimished ones like _Eb_ + + ++*In[5]:*+ +[source, ipython3] +---- +play(Fs5) +sleep(0.5) +play(Eb5) +---- + +Play louder (parameter amp) or from a different direction (parameter +pan) + + ++*In[6]:*+ +[source, ipython3] +---- +play(72,amp=2) +sleep(0.5) +play(74,pan=-1) #left +---- + +Different synthesizer sounds + + ++*In[7]:*+ +[source, ipython3] +---- +use_synth(SAW) +play(38) +sleep(0.25) +play(50) +sleep(0.5) +use_synth(PROPHET) +play(57) +sleep(0.25) +---- + +ADSR _(Attack, Decay, Sustain and Release)_ Envelope + + ++*In[8]:*+ +[source, ipython3] +---- +play (60, attack=0.5, decay=1, sustain_level=0.4, sustain=2, release=0.5) +sleep(4) +---- + +Play some samples + + ++*In[9]:*+ +[source, ipython3] +---- +sample(AMBI_LUNAR_LAND, amp=0.5) +---- + + ++*In[10]:*+ +[source, ipython3] +---- +sample(LOOP_AMEN,pan=-1) +sleep(0.877) +sample(LOOP_AMEN,pan=1) +---- + + ++*In[11]:*+ +[source, ipython3] +---- +sample(LOOP_AMEN,rate=0.5) +---- + + ++*In[12]:*+ +[source, ipython3] +---- +sample(LOOP_AMEN,rate=1.5) +---- + + ++*In[13]:*+ +[source, ipython3] +---- +sample(LOOP_AMEN,rate=-1)#back +---- + + ++*In[14]:*+ +[source, ipython3] +---- +sample(DRUM_CYMBAL_OPEN,attack=0.01,sustain=0.3,release=0.1) +---- + + ++*In[15]:*+ +[source, ipython3] +---- +sample(LOOP_AMEN,start=0.5,finish=0.8,rate=-0.2,attack=0.3,release=1) +---- + +Play some random notes + + ++*In[16]:*+ +[source, ipython3] +---- +import random + +for i in range(5): + play(random.randrange(50, 100)) + sleep(0.5) +---- + + ++*In[17]:*+ +[source, ipython3] +---- +for i in range(3): + play(random.choice([C5,E5,G5])) + sleep(1) +---- + +Sample slicing + + ++*In[18]:*+ +[source, ipython3] +---- +from psonic import * + +number_of_pieces = 8 + +for i in range(16): + s = random.randrange(0,number_of_pieces)/number_of_pieces #sample starts at 0.0 and finishes at 1.0 + f = s + (1.0/number_of_pieces) + sample(LOOP_AMEN,beat_stretch=2,start=s,finish=f) + sleep(2.0/number_of_pieces) +---- + +An infinite loop and if + + ++*In[18]:*+ +[source, ipython3] +---- +while True: + if one_in(2): + sample(DRUM_HEAVY_KICK) + sleep(0.5) + else: + sample(DRUM_CYMBAL_CLOSED) + sleep(0.25) +---- + + ++*Out[18]:*+ +---- + + --------------------------------------------------------------------------- + + KeyboardInterrupt Traceback (most recent call last) + + in () + 5 else: + 6 sample(DRUM_CYMBAL_CLOSED) + ----> 7 sleep(0.25) + + + /mnt/jupyter/python-sonic/psonic.py in sleep(duration) + 587 :return: + 588 """ + --> 589 time.sleep(duration) + 590 _debug('sleep', duration) + 591 + + + KeyboardInterrupt: + +---- + +If you want to hear more than one sound at a time, use Threads. + + ++*In[19]:*+ +[source, ipython3] +---- +import random +from psonic import * +from threading import Thread + +def bass_sound(): + c = chord(E3, MAJOR7) + while True: + use_synth(PROPHET) + play(random.choice(c), release=0.6) + sleep(0.5) + +def snare_sound(): + while True: + sample(ELEC_SNARE) + sleep(1) + +bass_thread = Thread(target=bass_sound) +snare_thread = Thread(target=snare_sound) + +bass_thread.start() +snare_thread.start() + +while True: + pass +---- + + ++*Out[19]:*+ +---- + + --------------------------------------------------------------------------- + + KeyboardInterrupt Traceback (most recent call last) + + in + 22 + 23 while True: + ---> 24 pass + + + KeyboardInterrupt: + +---- + +Every function _bass_sound_ and _snare_sound_ have its own thread. Your +can hear them running. + + ++*In[1]:*+ +[source, ipython3] +---- +from psonic import * +from threading import Thread, Condition +from random import choice + +def random_riff(condition): + use_synth(PROPHET) + sc = scale(E3, MINOR) + while True: + s = random.choice([0.125,0.25,0.5]) + with condition: + condition.wait() #Wait for message + for i in range(8): + r = random.choice([0.125, 0.25, 1, 2]) + n = random.choice(sc) + co = random.randint(30,100) + play(n, release = r, cutoff = co) + sleep(s) + +def drums(condition): + while True: + with condition: + condition.notifyAll() #Message to threads + for i in range(16): + r = random.randrange(1,10) + sample(DRUM_BASS_HARD, rate=r) + sleep(0.125) + +condition = Condition() +random_riff_thread = Thread(name='consumer1', target=random_riff, args=(condition,)) +drums_thread = Thread(name='producer', target=drums, args=(condition,)) + +random_riff_thread.start() +drums_thread.start() + +input("Press Enter to continue...") +---- + + ++*Out[1]:*+ +---- +Press Enter to continue... +''---- + +To synchronize the thread, so that they play a note at the same time, +you can use _Condition_. One function sends a message with +_condition.notifyAll_ the other waits until the message comes +_condition.wait_. + +More simple with decorator __@in_thread__ + + ++*In[ ]:*+ +[source, ipython3] +---- +from psonic import * +from random import choice + +tick = Message() + +@in_thread +def random_riff(): + use_synth(PROPHET) + sc = scale(E3, MINOR) + while True: + s = random.choice([0.125,0.25,0.5]) + tick.sync() + for i in range(8): + r = random.choice([0.125, 0.25, 1, 2]) + n = random.choice(sc) + co = random.randint(30,100) + play(n, release = r, cutoff = co) + sleep(s) + +@in_thread +def drums(): + while True: + tick.cue() + for i in range(16): + r = random.randrange(1,10) + sample(DRUM_BASS_HARD, rate=r) + sleep(0.125) + +random_riff() +drums() + +input("Press Enter to continue...") +---- + + ++*Out[ ]:*+ +---- +Press Enter to continue... +---- + + ++*In[ ]:*+ +[source, ipython3] +---- +from psonic import * + +tick = Message() + +@in_thread +def metronom(): + while True: + tick.cue() + sleep(1) + +@in_thread +def instrument(): + while True: + tick.sync() + sample(DRUM_HEAVY_KICK) + +metronom() +instrument() + +while True: + pass +---- + +Play a list of notes + + ++*In[2]:*+ +[source, ipython3] +---- +from psonic import * + +play ([64, 67, 71], amp = 0.3) +sleep(1) +play ([E4, G4, B4]) +sleep(1) +---- + +Play chords + + ++*In[2]:*+ +[source, ipython3] +---- +play(chord(E4, MINOR)) +sleep(1) +play(chord(E4, MAJOR)) +sleep(1) +play(chord(E4, MINOR7)) +sleep(1) +play(chord(E4, DOM7)) +sleep(1) +---- + +Play arpeggios + + ++*In[3]:*+ +[source, ipython3] +---- +play_pattern( chord(E4, 'm7')) +play_pattern_timed( chord(E4, 'm7'), 0.25) +play_pattern_timed(chord(E4, 'dim'), [0.25, 0.5]) +---- + +Play scales + + ++*In[4]:*+ +[source, ipython3] +---- +play_pattern_timed(scale(C3, MAJOR), 0.125, release = 0.1) +play_pattern_timed(scale(C3, MAJOR, num_octaves = 2), 0.125, release = 0.1) +play_pattern_timed(scale(C3, MAJOR_PENTATONIC, num_octaves = 2), 0.125, release = 0.1) +---- + +The function _scale_ returns a list with all notes of a scale. So you +can use list methodes or functions. For example to play arpeggios +descending or shuffeld. + + ++*In[5]:*+ +[source, ipython3] +---- +import random +from psonic import * + +s = scale(C3, MAJOR) +s +---- + + ++*Out[5]:*+ +----[48, 50, 52, 53, 55, 57, 59, 60]---- + + ++*In[6]:*+ +[source, ipython3] +---- +s.reverse() +---- + + ++*In[7]:*+ +[source, ipython3] +---- + +play_pattern_timed(s, 0.125, release = 0.1) +random.shuffle(s) +play_pattern_timed(s, 0.125, release = 0.1) +---- + +== Live Loop + +One of the best in SONIC PI is the _Live Loop_. While a loop is playing +music you can change it and hear the change. Let’s try it in Python, +too. + + ++*In[5]:*+ +[source, ipython3] +---- +from psonic import * +from threading import Thread + +def my_loop(): + play(60) + sleep(1) + +def looper(): + while True: + my_loop() + +looper_thread = Thread(name='looper', target=looper) + +looper_thread.start() + +input("Press Enter to continue...") +---- + + ++*Out[5]:*+ +---- +Press Enter to continue...Y +'Y'---- + +Now change the function _my_loop_ und you can hear it. + + ++*In[6]:*+ +[source, ipython3] +---- +def my_loop(): + use_synth(TB303) + play (60, release= 0.3) + sleep (0.25) +---- + + ++*In[7]:*+ +[source, ipython3] +---- +def my_loop(): + use_synth(TB303) + play (chord(E3, MINOR), release= 0.3) + sleep(0.5) +---- + + ++*In[8]:*+ +[source, ipython3] +---- +def my_loop(): + use_synth(TB303) + sample(DRUM_BASS_HARD, rate = random.uniform(0.5, 2)) + play(random.choice(chord(E3, MINOR)), release= 0.2, cutoff=random.randrange(60, 130)) + sleep(0.25) +---- + +To stop the sound you have to end the kernel. In IPython with Kernel –> +Restart + +Now with two live loops which are synch. + + ++*In[10]:*+ +[source, ipython3] +---- +from psonic import * +from threading import Thread, Condition +from random import choice + +def loop_foo(): + play (E4, release = 0.5) + sleep (0.5) + + +def loop_bar(): + sample (DRUM_SNARE_SOFT) + sleep (1) + + +def live_loop_1(condition): + while True: + with condition: + condition.notifyAll() #Message to threads + loop_foo() + +def live_loop_2(condition): + while True: + with condition: + condition.wait() #Wait for message + loop_bar() + +condition = Condition() +live_thread_1 = Thread(name='producer', target=live_loop_1, args=(condition,)) +live_thread_2 = Thread(name='consumer1', target=live_loop_2, args=(condition,)) + +live_thread_1.start() +live_thread_2.start() + +input("Press Enter to continue...") +---- + + ++*Out[10]:*+ +---- +Press Enter to continue...y +'y'---- + + ++*In[11]:*+ +[source, ipython3] +---- +def loop_foo(): + play (A4, release = 0.5) + sleep (0.5) +---- + + ++*In[14]:*+ +[source, ipython3] +---- +def loop_bar(): + sample (DRUM_HEAVY_KICK) + sleep (0.125) +---- + +If would be nice if we can stop the loop with a simple command. With +stop event it works. + + ++*In[1]:*+ +[source, ipython3] +---- +from psonic import * +from threading import Thread, Condition, Event + +def loop_foo(): + play (E4, release = 0.5) + sleep (0.5) + + +def loop_bar(): + sample (DRUM_SNARE_SOFT) + sleep (1) + + +def live_loop_1(condition,stop_event): + while not stop_event.is_set(): + with condition: + condition.notifyAll() #Message to threads + loop_foo() + +def live_loop_2(condition,stop_event): + while not stop_event.is_set(): + with condition: + condition.wait() #Wait for message + loop_bar() + + + +condition = Condition() +stop_event = Event() +live_thread_1 = Thread(name='producer', target=live_loop_1, args=(condition,stop_event)) +live_thread_2 = Thread(name='consumer1', target=live_loop_2, args=(condition,stop_event)) + + +live_thread_1.start() +live_thread_2.start() + +input("Press Enter to continue...") +---- + + ++*Out[1]:*+ +---- +Press Enter to continue...y +'y'---- + + ++*In[3]:*+ +[source, ipython3] +---- +stop_event.set() +---- + +More complex live loops + + ++*In[4]:*+ +[source, ipython3] +---- +sc = Ring(scale(E3, MINOR_PENTATONIC)) + +def loop_foo(): + play (next(sc), release= 0.1) + sleep (0.125) + +sc2 = Ring(scale(E3,MINOR_PENTATONIC,num_octaves=2)) + +def loop_bar(): + use_synth(DSAW) + play (next(sc2), release= 0.25) + sleep (0.25) +---- + +Now a simple structure with four live loops + + ++*In[11]:*+ +[source, ipython3] +---- +import random +from psonic import * +from threading import Thread, Condition, Event + +def live_1(): + pass + +def live_2(): + pass + +def live_3(): + pass + +def live_4(): + pass + +def live_loop_1(condition,stop_event): + while not stop_event.is_set(): + with condition: + condition.notifyAll() #Message to threads + live_1() + +def live_loop_2(condition,stop_event): + while not stop_event.is_set(): + with condition: + condition.wait() #Wait for message + live_2() + +def live_loop_3(condition,stop_event): + while not stop_event.is_set(): + with condition: + condition.wait() #Wait for message + live_3() + +def live_loop_4(condition,stop_event): + while not stop_event.is_set(): + with condition: + condition.wait() #Wait for message + live_4() + +condition = Condition() +stop_event = Event() +live_thread_1 = Thread(name='producer', target=live_loop_1, args=(condition,stop_event)) +live_thread_2 = Thread(name='consumer1', target=live_loop_2, args=(condition,stop_event)) +live_thread_3 = Thread(name='consumer2', target=live_loop_3, args=(condition,stop_event)) +live_thread_4 = Thread(name='consumer3', target=live_loop_3, args=(condition,stop_event)) + +live_thread_1.start() +live_thread_2.start() +live_thread_3.start() +live_thread_4.start() + +input("Press Enter to continue...") +---- + + ++*Out[11]:*+ +---- +Press Enter to continue...y +'y'---- + +After starting the loops you can change them + + ++*In[12]:*+ +[source, ipython3] +---- +def live_1(): + sample(BD_HAUS,amp=2) + sleep(0.5) + pass +---- + + ++*In[7]:*+ +[source, ipython3] +---- +def live_2(): + #sample(AMBI_CHOIR, rate=0.4) + #sleep(1) + pass +---- + + ++*In[14]:*+ +[source, ipython3] +---- +def live_3(): + use_synth(TB303) + play(E2, release=4,cutoff=120,cutoff_attack=1) + sleep(4) +---- + + ++*In[13]:*+ +[source, ipython3] +---- +def live_4(): + notes = scale(E3, MINOR_PENTATONIC, num_octaves=2) + for i in range(8): + play(random.choice(notes),release=0.1,amp=1.5) + sleep(0.125) +---- + +And stop. + + ++*In[15]:*+ +[source, ipython3] +---- +stop_event.set() +---- + +== Creating Sound + + ++*In[4]:*+ +[source, ipython3] +---- +from psonic import * + +synth(SINE, note=D4) +synth(SQUARE, note=D4) +synth(TRI, note=D4, amp=0.4) +---- + + ++*In[5]:*+ +[source, ipython3] +---- +detune = 0.7 +synth(SQUARE, note = E4) +synth(SQUARE, note = E4+detune) +---- + + ++*In[12]:*+ +[source, ipython3] +---- +detune=0.1 # Amplitude shaping +synth(SQUARE, note = E2, release = 2) +synth(SQUARE, note = E2+detune, amp = 2, release = 2) +synth(GNOISE, release = 2, amp = 1, cutoff = 60) +synth(GNOISE, release = 0.5, amp = 1, cutoff = 100) +synth(NOISE, release = 0.2, amp = 1, cutoff = 90) +---- + +== Next Step + +Using FX _Not implemented yet_ + + ++*In[2]:*+ +[source, ipython3] +---- +from psonic import * + +with Fx(SLICER): + synth(PROPHET,note=E2,release=8,cutoff=80) + synth(PROPHET,note=E2+4,release=8,cutoff=80) +---- + + ++*In[3]:*+ +[source, ipython3] +---- +with Fx(SLICER, phase=0.125, probability=0.6,prob_pos=1): + synth(TB303, note=E2, cutoff_attack=8, release=8) + synth(TB303, note=E3, cutoff_attack=4, release=8) + synth(TB303, note=E4, cutoff_attack=2, release=8) +---- + +== OSC Communication (Sonic Pi Ver. 3.x or better) + +In Sonic Pi version 3 or better you can work with messages. + + ++*In[1]:*+ +[source, ipython3] +---- +from psonic import * +---- + +First you need a programm in the Sonic Pi server that receives messages. +You can write it in th GUI or send one with Python. + + ++*In[2]:*+ +[source, ipython3] +---- +run("""live_loop :foo do + use_real_time + a, b, c = sync "/osc*/trigger/prophet" + synth :prophet, note: a, cutoff: b, sustain: c +end """) +---- + +Now send a message to Sonic Pi. + + ++*In[3]:*+ +[source, ipython3] +---- +send_message('/trigger/prophet', 70, 100, 8) +---- + + ++*In[4]:*+ +[source, ipython3] +---- +stop() +---- + +== Recording + +With python-sonic you can record wave files. + + ++*In[1]:*+ +[source, ipython3] +---- +from psonic import * +---- + + ++*In[5]:*+ +[source, ipython3] +---- +# start recording +start_recording() + +play(chord(E4, MINOR)) +sleep(1) +play(chord(E4, MAJOR)) +sleep(1) +play(chord(E4, MINOR7)) +sleep(1) +play(chord(E4, DOM7)) +sleep(1) +---- + + ++*In[6]:*+ +[source, ipython3] +---- +# stop recording +stop_recording +---- + + ++*Out[6]:*+ +-------- + + ++*In[7]:*+ +[source, ipython3] +---- +# save file +save_recording('/Volumes/jupyter/python-sonic/test.wav') +---- + +== More Examples + + ++*In[1]:*+ +[source, ipython3] +---- +from psonic import * +---- + + ++*In[ ]:*+ +[source, ipython3] +---- +#Inspired by Steve Reich Clapping Music + +clapping = [1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0] + +for i in range(13): + for j in range(4): + for k in range(12): + if clapping[k] ==1 : sample(DRUM_SNARE_SOFT,pan=-0.5) + if clapping[(i+k)%12] == 1: sample(DRUM_HEAVY_KICK,pan=0.5) + sleep (0.25) +---- + +== Projects that use Python-Sonic + +Raspberry Pi sonic-track.py a Sonic-pi Motion Track Demo +https://github.com/pageauc/sonic-track + +== Sources + +Joe Armstrong: Connecting Erlang to the Sonic Pi +http://joearms.github.io/2015/01/05/Connecting-Erlang-to-Sonic-Pi.html + +Joe Armstrong: Controlling Sound with OSC Messages +http://joearms.github.io/2016/01/29/Controlling-Sound-with-OSC-Messages.html + +.. From 5d0a66c5618673d74daf063aa4ce4d8caf856c40 Mon Sep 17 00:00:00 2001 From: python-ninja-hebi Date: Sun, 15 Aug 2021 16:38:48 +0200 Subject: [PATCH 13/17] Version 0.4.2 --- setup.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/setup.py b/setup.py index caf3b62..a6383c9 100755 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ here = path.abspath(path.dirname(__file__)) # Get the long description from the README file -with open(path.join(here, 'README.txt'), encoding='utf-8') as f: +with open(path.join(here, 'PREADME.txt'), encoding='utf-8') as f: long_description = f.read() requirements = [ @@ -26,10 +26,9 @@ setup( name='python-sonic', - version='0.4.1', + version='0.4.2', description='Programming Music with Sonic Pi or Supercollider', long_description=long_description, -# long_description_content_type='text/x-rst', long_description_content_type='text/plain', url='https://github.com/gkvoelkl/python-sonic', author='gkvoelkl', @@ -54,7 +53,9 @@ 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7' + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9' ], keywords= [ 'music', From 7c83394cdff625664bc3d1aa69e53ec1274c0a24 Mon Sep 17 00:00:00 2001 From: Mike Roberts <2947595+m-roberts@users.noreply.github.com> Date: Sun, 22 Aug 2021 14:48:57 +0100 Subject: [PATCH 14/17] Fix README for PyPI (#37) --- PREADME.txt | 1069 --------------------------------------------------- README.rst | 124 +++--- setup.py | 3 +- 3 files changed, 63 insertions(+), 1133 deletions(-) delete mode 100755 PREADME.txt diff --git a/PREADME.txt b/PREADME.txt deleted file mode 100755 index ec9f21d..0000000 --- a/PREADME.txt +++ /dev/null @@ -1,1069 +0,0 @@ -== python-sonic - Programming Music with Python, Sonic Pi or Supercollider - -Python-Sonic is a simple Python interface for Sonic Pi, which is a real -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. - -If you like it, use it. If you have some suggestions, tell me -(gkvoelkl@nelson-games.de). - -== Installation - -* First you need Python 3 (https://www.python.org, ) - Python 3.5 should -work, because it’s the development environment -* 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. - -== Limitations - -* You have to start _Sonic Pi_ first before you can use it with -python-sonic -* Only the notes from C2 to C6 - -== Changelog - -[width="100%",cols="14%,86%",options="header",] -|=== -|Version | -|0.2.0 |Some changes for Sonic Pi 2.11. Simpler multi-threading with -decorator _@in_thread_. Messaging with _cue_ and _sync_. - -|0.3.0 |OSC Communication - -|0.3.1. |Update, sort and duration of samples - -|0.3.2. |Restructured - -|0.4.0 |Changes communication ports and recording -|=== - -== 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 -from psonic import * -set_server_parameters('127.0.0.1',4557,4559) -== Examples - -Many of the examples are inspired from the help menu in _Sonic Pi_. - - -+*In[1]:*+ -[source, ipython3] ----- -from psonic import * ----- - -The first sound - - -+*In[2]:*+ -[source, ipython3] ----- -play(70) #play MIDI note 70 ----- - -Some more notes - - -+*In[3]:*+ -[source, ipython3] ----- -play(72) -sleep(1) -play(75) -sleep(1) -play(79) ----- - -In more tratitional music notation - - -+*In[4]:*+ -[source, ipython3] ----- -play(C5) -sleep(0.5) -play(D5) -sleep(0.5) -play(G5) ----- - -Play sharp notes like _F#_ or dimished ones like _Eb_ - - -+*In[5]:*+ -[source, ipython3] ----- -play(Fs5) -sleep(0.5) -play(Eb5) ----- - -Play louder (parameter amp) or from a different direction (parameter -pan) - - -+*In[6]:*+ -[source, ipython3] ----- -play(72,amp=2) -sleep(0.5) -play(74,pan=-1) #left ----- - -Different synthesizer sounds - - -+*In[7]:*+ -[source, ipython3] ----- -use_synth(SAW) -play(38) -sleep(0.25) -play(50) -sleep(0.5) -use_synth(PROPHET) -play(57) -sleep(0.25) ----- - -ADSR _(Attack, Decay, Sustain and Release)_ Envelope - - -+*In[8]:*+ -[source, ipython3] ----- -play (60, attack=0.5, decay=1, sustain_level=0.4, sustain=2, release=0.5) -sleep(4) ----- - -Play some samples - - -+*In[9]:*+ -[source, ipython3] ----- -sample(AMBI_LUNAR_LAND, amp=0.5) ----- - - -+*In[10]:*+ -[source, ipython3] ----- -sample(LOOP_AMEN,pan=-1) -sleep(0.877) -sample(LOOP_AMEN,pan=1) ----- - - -+*In[11]:*+ -[source, ipython3] ----- -sample(LOOP_AMEN,rate=0.5) ----- - - -+*In[12]:*+ -[source, ipython3] ----- -sample(LOOP_AMEN,rate=1.5) ----- - - -+*In[13]:*+ -[source, ipython3] ----- -sample(LOOP_AMEN,rate=-1)#back ----- - - -+*In[14]:*+ -[source, ipython3] ----- -sample(DRUM_CYMBAL_OPEN,attack=0.01,sustain=0.3,release=0.1) ----- - - -+*In[15]:*+ -[source, ipython3] ----- -sample(LOOP_AMEN,start=0.5,finish=0.8,rate=-0.2,attack=0.3,release=1) ----- - -Play some random notes - - -+*In[16]:*+ -[source, ipython3] ----- -import random - -for i in range(5): - play(random.randrange(50, 100)) - sleep(0.5) ----- - - -+*In[17]:*+ -[source, ipython3] ----- -for i in range(3): - play(random.choice([C5,E5,G5])) - sleep(1) ----- - -Sample slicing - - -+*In[18]:*+ -[source, ipython3] ----- -from psonic import * - -number_of_pieces = 8 - -for i in range(16): - s = random.randrange(0,number_of_pieces)/number_of_pieces #sample starts at 0.0 and finishes at 1.0 - f = s + (1.0/number_of_pieces) - sample(LOOP_AMEN,beat_stretch=2,start=s,finish=f) - sleep(2.0/number_of_pieces) ----- - -An infinite loop and if - - -+*In[18]:*+ -[source, ipython3] ----- -while True: - if one_in(2): - sample(DRUM_HEAVY_KICK) - sleep(0.5) - else: - sample(DRUM_CYMBAL_CLOSED) - sleep(0.25) ----- - - -+*Out[18]:*+ ----- - - --------------------------------------------------------------------------- - - KeyboardInterrupt Traceback (most recent call last) - - in () - 5 else: - 6 sample(DRUM_CYMBAL_CLOSED) - ----> 7 sleep(0.25) - - - /mnt/jupyter/python-sonic/psonic.py in sleep(duration) - 587 :return: - 588 """ - --> 589 time.sleep(duration) - 590 _debug('sleep', duration) - 591 - - - KeyboardInterrupt: - ----- - -If you want to hear more than one sound at a time, use Threads. - - -+*In[19]:*+ -[source, ipython3] ----- -import random -from psonic import * -from threading import Thread - -def bass_sound(): - c = chord(E3, MAJOR7) - while True: - use_synth(PROPHET) - play(random.choice(c), release=0.6) - sleep(0.5) - -def snare_sound(): - while True: - sample(ELEC_SNARE) - sleep(1) - -bass_thread = Thread(target=bass_sound) -snare_thread = Thread(target=snare_sound) - -bass_thread.start() -snare_thread.start() - -while True: - pass ----- - - -+*Out[19]:*+ ----- - - --------------------------------------------------------------------------- - - KeyboardInterrupt Traceback (most recent call last) - - in - 22 - 23 while True: - ---> 24 pass - - - KeyboardInterrupt: - ----- - -Every function _bass_sound_ and _snare_sound_ have its own thread. Your -can hear them running. - - -+*In[1]:*+ -[source, ipython3] ----- -from psonic import * -from threading import Thread, Condition -from random import choice - -def random_riff(condition): - use_synth(PROPHET) - sc = scale(E3, MINOR) - while True: - s = random.choice([0.125,0.25,0.5]) - with condition: - condition.wait() #Wait for message - for i in range(8): - r = random.choice([0.125, 0.25, 1, 2]) - n = random.choice(sc) - co = random.randint(30,100) - play(n, release = r, cutoff = co) - sleep(s) - -def drums(condition): - while True: - with condition: - condition.notifyAll() #Message to threads - for i in range(16): - r = random.randrange(1,10) - sample(DRUM_BASS_HARD, rate=r) - sleep(0.125) - -condition = Condition() -random_riff_thread = Thread(name='consumer1', target=random_riff, args=(condition,)) -drums_thread = Thread(name='producer', target=drums, args=(condition,)) - -random_riff_thread.start() -drums_thread.start() - -input("Press Enter to continue...") ----- - - -+*Out[1]:*+ ----- -Press Enter to continue... -''---- - -To synchronize the thread, so that they play a note at the same time, -you can use _Condition_. One function sends a message with -_condition.notifyAll_ the other waits until the message comes -_condition.wait_. - -More simple with decorator __@in_thread__ - - -+*In[ ]:*+ -[source, ipython3] ----- -from psonic import * -from random import choice - -tick = Message() - -@in_thread -def random_riff(): - use_synth(PROPHET) - sc = scale(E3, MINOR) - while True: - s = random.choice([0.125,0.25,0.5]) - tick.sync() - for i in range(8): - r = random.choice([0.125, 0.25, 1, 2]) - n = random.choice(sc) - co = random.randint(30,100) - play(n, release = r, cutoff = co) - sleep(s) - -@in_thread -def drums(): - while True: - tick.cue() - for i in range(16): - r = random.randrange(1,10) - sample(DRUM_BASS_HARD, rate=r) - sleep(0.125) - -random_riff() -drums() - -input("Press Enter to continue...") ----- - - -+*Out[ ]:*+ ----- -Press Enter to continue... ----- - - -+*In[ ]:*+ -[source, ipython3] ----- -from psonic import * - -tick = Message() - -@in_thread -def metronom(): - while True: - tick.cue() - sleep(1) - -@in_thread -def instrument(): - while True: - tick.sync() - sample(DRUM_HEAVY_KICK) - -metronom() -instrument() - -while True: - pass ----- - -Play a list of notes - - -+*In[2]:*+ -[source, ipython3] ----- -from psonic import * - -play ([64, 67, 71], amp = 0.3) -sleep(1) -play ([E4, G4, B4]) -sleep(1) ----- - -Play chords - - -+*In[2]:*+ -[source, ipython3] ----- -play(chord(E4, MINOR)) -sleep(1) -play(chord(E4, MAJOR)) -sleep(1) -play(chord(E4, MINOR7)) -sleep(1) -play(chord(E4, DOM7)) -sleep(1) ----- - -Play arpeggios - - -+*In[3]:*+ -[source, ipython3] ----- -play_pattern( chord(E4, 'm7')) -play_pattern_timed( chord(E4, 'm7'), 0.25) -play_pattern_timed(chord(E4, 'dim'), [0.25, 0.5]) ----- - -Play scales - - -+*In[4]:*+ -[source, ipython3] ----- -play_pattern_timed(scale(C3, MAJOR), 0.125, release = 0.1) -play_pattern_timed(scale(C3, MAJOR, num_octaves = 2), 0.125, release = 0.1) -play_pattern_timed(scale(C3, MAJOR_PENTATONIC, num_octaves = 2), 0.125, release = 0.1) ----- - -The function _scale_ returns a list with all notes of a scale. So you -can use list methodes or functions. For example to play arpeggios -descending or shuffeld. - - -+*In[5]:*+ -[source, ipython3] ----- -import random -from psonic import * - -s = scale(C3, MAJOR) -s ----- - - -+*Out[5]:*+ -----[48, 50, 52, 53, 55, 57, 59, 60]---- - - -+*In[6]:*+ -[source, ipython3] ----- -s.reverse() ----- - - -+*In[7]:*+ -[source, ipython3] ----- - -play_pattern_timed(s, 0.125, release = 0.1) -random.shuffle(s) -play_pattern_timed(s, 0.125, release = 0.1) ----- - -== Live Loop - -One of the best in SONIC PI is the _Live Loop_. While a loop is playing -music you can change it and hear the change. Let’s try it in Python, -too. - - -+*In[5]:*+ -[source, ipython3] ----- -from psonic import * -from threading import Thread - -def my_loop(): - play(60) - sleep(1) - -def looper(): - while True: - my_loop() - -looper_thread = Thread(name='looper', target=looper) - -looper_thread.start() - -input("Press Enter to continue...") ----- - - -+*Out[5]:*+ ----- -Press Enter to continue...Y -'Y'---- - -Now change the function _my_loop_ und you can hear it. - - -+*In[6]:*+ -[source, ipython3] ----- -def my_loop(): - use_synth(TB303) - play (60, release= 0.3) - sleep (0.25) ----- - - -+*In[7]:*+ -[source, ipython3] ----- -def my_loop(): - use_synth(TB303) - play (chord(E3, MINOR), release= 0.3) - sleep(0.5) ----- - - -+*In[8]:*+ -[source, ipython3] ----- -def my_loop(): - use_synth(TB303) - sample(DRUM_BASS_HARD, rate = random.uniform(0.5, 2)) - play(random.choice(chord(E3, MINOR)), release= 0.2, cutoff=random.randrange(60, 130)) - sleep(0.25) ----- - -To stop the sound you have to end the kernel. In IPython with Kernel –> -Restart - -Now with two live loops which are synch. - - -+*In[10]:*+ -[source, ipython3] ----- -from psonic import * -from threading import Thread, Condition -from random import choice - -def loop_foo(): - play (E4, release = 0.5) - sleep (0.5) - - -def loop_bar(): - sample (DRUM_SNARE_SOFT) - sleep (1) - - -def live_loop_1(condition): - while True: - with condition: - condition.notifyAll() #Message to threads - loop_foo() - -def live_loop_2(condition): - while True: - with condition: - condition.wait() #Wait for message - loop_bar() - -condition = Condition() -live_thread_1 = Thread(name='producer', target=live_loop_1, args=(condition,)) -live_thread_2 = Thread(name='consumer1', target=live_loop_2, args=(condition,)) - -live_thread_1.start() -live_thread_2.start() - -input("Press Enter to continue...") ----- - - -+*Out[10]:*+ ----- -Press Enter to continue...y -'y'---- - - -+*In[11]:*+ -[source, ipython3] ----- -def loop_foo(): - play (A4, release = 0.5) - sleep (0.5) ----- - - -+*In[14]:*+ -[source, ipython3] ----- -def loop_bar(): - sample (DRUM_HEAVY_KICK) - sleep (0.125) ----- - -If would be nice if we can stop the loop with a simple command. With -stop event it works. - - -+*In[1]:*+ -[source, ipython3] ----- -from psonic import * -from threading import Thread, Condition, Event - -def loop_foo(): - play (E4, release = 0.5) - sleep (0.5) - - -def loop_bar(): - sample (DRUM_SNARE_SOFT) - sleep (1) - - -def live_loop_1(condition,stop_event): - while not stop_event.is_set(): - with condition: - condition.notifyAll() #Message to threads - loop_foo() - -def live_loop_2(condition,stop_event): - while not stop_event.is_set(): - with condition: - condition.wait() #Wait for message - loop_bar() - - - -condition = Condition() -stop_event = Event() -live_thread_1 = Thread(name='producer', target=live_loop_1, args=(condition,stop_event)) -live_thread_2 = Thread(name='consumer1', target=live_loop_2, args=(condition,stop_event)) - - -live_thread_1.start() -live_thread_2.start() - -input("Press Enter to continue...") ----- - - -+*Out[1]:*+ ----- -Press Enter to continue...y -'y'---- - - -+*In[3]:*+ -[source, ipython3] ----- -stop_event.set() ----- - -More complex live loops - - -+*In[4]:*+ -[source, ipython3] ----- -sc = Ring(scale(E3, MINOR_PENTATONIC)) - -def loop_foo(): - play (next(sc), release= 0.1) - sleep (0.125) - -sc2 = Ring(scale(E3,MINOR_PENTATONIC,num_octaves=2)) - -def loop_bar(): - use_synth(DSAW) - play (next(sc2), release= 0.25) - sleep (0.25) ----- - -Now a simple structure with four live loops - - -+*In[11]:*+ -[source, ipython3] ----- -import random -from psonic import * -from threading import Thread, Condition, Event - -def live_1(): - pass - -def live_2(): - pass - -def live_3(): - pass - -def live_4(): - pass - -def live_loop_1(condition,stop_event): - while not stop_event.is_set(): - with condition: - condition.notifyAll() #Message to threads - live_1() - -def live_loop_2(condition,stop_event): - while not stop_event.is_set(): - with condition: - condition.wait() #Wait for message - live_2() - -def live_loop_3(condition,stop_event): - while not stop_event.is_set(): - with condition: - condition.wait() #Wait for message - live_3() - -def live_loop_4(condition,stop_event): - while not stop_event.is_set(): - with condition: - condition.wait() #Wait for message - live_4() - -condition = Condition() -stop_event = Event() -live_thread_1 = Thread(name='producer', target=live_loop_1, args=(condition,stop_event)) -live_thread_2 = Thread(name='consumer1', target=live_loop_2, args=(condition,stop_event)) -live_thread_3 = Thread(name='consumer2', target=live_loop_3, args=(condition,stop_event)) -live_thread_4 = Thread(name='consumer3', target=live_loop_3, args=(condition,stop_event)) - -live_thread_1.start() -live_thread_2.start() -live_thread_3.start() -live_thread_4.start() - -input("Press Enter to continue...") ----- - - -+*Out[11]:*+ ----- -Press Enter to continue...y -'y'---- - -After starting the loops you can change them - - -+*In[12]:*+ -[source, ipython3] ----- -def live_1(): - sample(BD_HAUS,amp=2) - sleep(0.5) - pass ----- - - -+*In[7]:*+ -[source, ipython3] ----- -def live_2(): - #sample(AMBI_CHOIR, rate=0.4) - #sleep(1) - pass ----- - - -+*In[14]:*+ -[source, ipython3] ----- -def live_3(): - use_synth(TB303) - play(E2, release=4,cutoff=120,cutoff_attack=1) - sleep(4) ----- - - -+*In[13]:*+ -[source, ipython3] ----- -def live_4(): - notes = scale(E3, MINOR_PENTATONIC, num_octaves=2) - for i in range(8): - play(random.choice(notes),release=0.1,amp=1.5) - sleep(0.125) ----- - -And stop. - - -+*In[15]:*+ -[source, ipython3] ----- -stop_event.set() ----- - -== Creating Sound - - -+*In[4]:*+ -[source, ipython3] ----- -from psonic import * - -synth(SINE, note=D4) -synth(SQUARE, note=D4) -synth(TRI, note=D4, amp=0.4) ----- - - -+*In[5]:*+ -[source, ipython3] ----- -detune = 0.7 -synth(SQUARE, note = E4) -synth(SQUARE, note = E4+detune) ----- - - -+*In[12]:*+ -[source, ipython3] ----- -detune=0.1 # Amplitude shaping -synth(SQUARE, note = E2, release = 2) -synth(SQUARE, note = E2+detune, amp = 2, release = 2) -synth(GNOISE, release = 2, amp = 1, cutoff = 60) -synth(GNOISE, release = 0.5, amp = 1, cutoff = 100) -synth(NOISE, release = 0.2, amp = 1, cutoff = 90) ----- - -== Next Step - -Using FX _Not implemented yet_ - - -+*In[2]:*+ -[source, ipython3] ----- -from psonic import * - -with Fx(SLICER): - synth(PROPHET,note=E2,release=8,cutoff=80) - synth(PROPHET,note=E2+4,release=8,cutoff=80) ----- - - -+*In[3]:*+ -[source, ipython3] ----- -with Fx(SLICER, phase=0.125, probability=0.6,prob_pos=1): - synth(TB303, note=E2, cutoff_attack=8, release=8) - synth(TB303, note=E3, cutoff_attack=4, release=8) - synth(TB303, note=E4, cutoff_attack=2, release=8) ----- - -== OSC Communication (Sonic Pi Ver. 3.x or better) - -In Sonic Pi version 3 or better you can work with messages. - - -+*In[1]:*+ -[source, ipython3] ----- -from psonic import * ----- - -First you need a programm in the Sonic Pi server that receives messages. -You can write it in th GUI or send one with Python. - - -+*In[2]:*+ -[source, ipython3] ----- -run("""live_loop :foo do - use_real_time - a, b, c = sync "/osc*/trigger/prophet" - synth :prophet, note: a, cutoff: b, sustain: c -end """) ----- - -Now send a message to Sonic Pi. - - -+*In[3]:*+ -[source, ipython3] ----- -send_message('/trigger/prophet', 70, 100, 8) ----- - - -+*In[4]:*+ -[source, ipython3] ----- -stop() ----- - -== Recording - -With python-sonic you can record wave files. - - -+*In[1]:*+ -[source, ipython3] ----- -from psonic import * ----- - - -+*In[5]:*+ -[source, ipython3] ----- -# start recording -start_recording() - -play(chord(E4, MINOR)) -sleep(1) -play(chord(E4, MAJOR)) -sleep(1) -play(chord(E4, MINOR7)) -sleep(1) -play(chord(E4, DOM7)) -sleep(1) ----- - - -+*In[6]:*+ -[source, ipython3] ----- -# stop recording -stop_recording ----- - - -+*Out[6]:*+ --------- - - -+*In[7]:*+ -[source, ipython3] ----- -# save file -save_recording('/Volumes/jupyter/python-sonic/test.wav') ----- - -== More Examples - - -+*In[1]:*+ -[source, ipython3] ----- -from psonic import * ----- - - -+*In[ ]:*+ -[source, ipython3] ----- -#Inspired by Steve Reich Clapping Music - -clapping = [1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0] - -for i in range(13): - for j in range(4): - for k in range(12): - if clapping[k] ==1 : sample(DRUM_SNARE_SOFT,pan=-0.5) - if clapping[(i+k)%12] == 1: sample(DRUM_HEAVY_KICK,pan=0.5) - sleep (0.25) ----- - -== Projects that use Python-Sonic - -Raspberry Pi sonic-track.py a Sonic-pi Motion Track Demo -https://github.com/pageauc/sonic-track - -== Sources - -Joe Armstrong: Connecting Erlang to the Sonic Pi -http://joearms.github.io/2015/01/05/Connecting-Erlang-to-Sonic-Pi.html - -Joe Armstrong: Controlling Sound with OSC Messages -http://joearms.github.io/2016/01/29/Controlling-Sound-with-OSC-Messages.html - -.. diff --git a/README.rst b/README.rst index edd4306..949f371 100755 --- a/README.rst +++ b/README.rst @@ -60,7 +60,7 @@ Communication 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 -.. code:: ipython3 +.. code-block:: python from psonic import * set_server_parameter('127.0.0.1',4557,4559) @@ -70,19 +70,19 @@ Examples Many of the examples are inspired from the help menu in *Sonic Pi*. -.. code:: ipython3 +.. code-block:: python from psonic import * The first sound -.. code:: ipython3 +.. code-block:: python play(70) #play MIDI note 70 Some more notes -.. code:: ipython3 +.. code-block:: python play(72) sleep(1) @@ -92,7 +92,7 @@ Some more notes In more tratitional music notation -.. code:: ipython3 +.. code-block:: python play(C5) sleep(0.5) @@ -102,7 +102,7 @@ In more tratitional music notation Play sharp notes like *F#* or dimished ones like *Eb* -.. code:: ipython3 +.. code-block:: python play(Fs5) sleep(0.5) @@ -111,7 +111,7 @@ Play sharp notes like *F#* or dimished ones like *Eb* Play louder (parameter amp) or from a different direction (parameter pan) -.. code:: ipython3 +.. code-block:: python play(72,amp=2) sleep(0.5) @@ -119,7 +119,7 @@ pan) Different synthesizer sounds -.. code:: ipython3 +.. code-block:: python use_synth(SAW) play(38) @@ -132,46 +132,46 @@ Different synthesizer sounds ADSR *(Attack, Decay, Sustain and Release)* Envelope -.. code:: ipython3 +.. code-block:: python play (60, attack=0.5, decay=1, sustain_level=0.4, sustain=2, release=0.5) sleep(4) Play some samples -.. code:: ipython3 +.. code-block:: python sample(AMBI_LUNAR_LAND, amp=0.5) -.. code:: ipython3 +.. code-block:: python sample(LOOP_AMEN,pan=-1) sleep(0.877) sample(LOOP_AMEN,pan=1) -.. code:: ipython3 +.. code-block:: python sample(LOOP_AMEN,rate=0.5) -.. code:: ipython3 +.. code-block:: python sample(LOOP_AMEN,rate=1.5) -.. code:: ipython3 +.. code-block:: python sample(LOOP_AMEN,rate=-1)#back -.. code:: ipython3 +.. code-block:: python sample(DRUM_CYMBAL_OPEN,attack=0.01,sustain=0.3,release=0.1) -.. code:: ipython3 +.. code-block:: python sample(LOOP_AMEN,start=0.5,finish=0.8,rate=-0.2,attack=0.3,release=1) Play some random notes -.. code:: ipython3 +.. code-block:: python import random @@ -179,7 +179,7 @@ Play some random notes play(random.randrange(50, 100)) sleep(0.5) -.. code:: ipython3 +.. code-block:: python for i in range(3): play(random.choice([C5,E5,G5])) @@ -187,7 +187,7 @@ Play some random notes Sample slicing -.. code:: ipython3 +.. code-block:: python from psonic import * @@ -201,7 +201,7 @@ Sample slicing An infinite loop and if -.. code:: ipython3 +.. code-block:: python while True: if one_in(2): @@ -238,7 +238,7 @@ An infinite loop and if If you want to hear more than one sound at a time, use Threads. -.. code:: ipython3 +.. code-block:: python import random from psonic import * @@ -285,7 +285,7 @@ If you want to hear more than one sound at a time, use Threads. Every function *bass_sound* and *snare_sound* have its own thread. Your can hear them running. -.. code:: ipython3 +.. code-block:: python from psonic import * from threading import Thread, Condition @@ -344,7 +344,7 @@ you can use *Condition*. One function sends a message with More simple with decorator \_\_@in_thread_\_ -.. code:: ipython3 +.. code-block:: python from psonic import * from random import choice @@ -385,7 +385,7 @@ More simple with decorator \_\_@in_thread_\_ Press Enter to continue... -.. code:: ipython3 +.. code-block:: python from psonic import * @@ -411,7 +411,7 @@ More simple with decorator \_\_@in_thread_\_ Play a list of notes -.. code:: ipython3 +.. code-block:: python from psonic import * @@ -422,7 +422,7 @@ Play a list of notes Play chords -.. code:: ipython3 +.. code-block:: python play(chord(E4, MINOR)) sleep(1) @@ -435,7 +435,7 @@ Play chords Play arpeggios -.. code:: ipython3 +.. code-block:: python play_pattern( chord(E4, 'm7')) play_pattern_timed( chord(E4, 'm7'), 0.25) @@ -443,7 +443,7 @@ Play arpeggios Play scales -.. code:: ipython3 +.. code-block:: python play_pattern_timed(scale(C3, MAJOR), 0.125, release = 0.1) play_pattern_timed(scale(C3, MAJOR, num_octaves = 2), 0.125, release = 0.1) @@ -453,7 +453,7 @@ The function *scale* returns a list with all notes of a scale. So you can use list methodes or functions. For example to play arpeggios descending or shuffeld. -.. code:: ipython3 +.. code-block:: python import random from psonic import * @@ -470,11 +470,11 @@ descending or shuffeld. -.. code:: ipython3 +.. code-block:: python s.reverse() -.. code:: ipython3 +.. code-block:: python play_pattern_timed(s, 0.125, release = 0.1) @@ -488,7 +488,7 @@ One of the best in SONIC PI is the *Live Loop*. While a loop is playing music you can change it and hear the change. Let’s try it in Python, too. -.. code:: ipython3 +.. code-block:: python from psonic import * from threading import Thread @@ -523,21 +523,21 @@ too. Now change the function *my_loop* und you can hear it. -.. code:: ipython3 +.. code-block:: python def my_loop(): use_synth(TB303) play (60, release= 0.3) sleep (0.25) -.. code:: ipython3 +.. code-block:: python def my_loop(): use_synth(TB303) play (chord(E3, MINOR), release= 0.3) sleep(0.5) -.. code:: ipython3 +.. code-block:: python def my_loop(): use_synth(TB303) @@ -550,7 +550,7 @@ Restart Now with two live loops which are synch. -.. code:: ipython3 +.. code-block:: python from psonic import * from threading import Thread, Condition @@ -601,13 +601,13 @@ Now with two live loops which are synch. -.. code:: ipython3 +.. code-block:: python def loop_foo(): play (A4, release = 0.5) sleep (0.5) -.. code:: ipython3 +.. code-block:: python def loop_bar(): sample (DRUM_HEAVY_KICK) @@ -616,7 +616,7 @@ Now with two live loops which are synch. If would be nice if we can stop the loop with a simple command. With stop event it works. -.. code:: ipython3 +.. code-block:: python from psonic import * from threading import Thread, Condition, Event @@ -670,13 +670,13 @@ stop event it works. -.. code:: ipython3 +.. code-block:: python stop_event.set() More complex live loops -.. code:: ipython3 +.. code-block:: python sc = Ring(scale(E3, MINOR_PENTATONIC)) @@ -693,7 +693,7 @@ More complex live loops Now a simple structure with four live loops -.. code:: ipython3 +.. code-block:: python import random from psonic import * @@ -765,28 +765,28 @@ Now a simple structure with four live loops After starting the loops you can change them -.. code:: ipython3 +.. code-block:: python def live_1(): sample(BD_HAUS,amp=2) sleep(0.5) pass -.. code:: ipython3 +.. code-block:: python def live_2(): #sample(AMBI_CHOIR, rate=0.4) #sleep(1) pass -.. code:: ipython3 +.. code-block:: python def live_3(): use_synth(TB303) play(E2, release=4,cutoff=120,cutoff_attack=1) sleep(4) -.. code:: ipython3 +.. code-block:: python def live_4(): notes = scale(E3, MINOR_PENTATONIC, num_octaves=2) @@ -796,14 +796,14 @@ After starting the loops you can change them And stop. -.. code:: ipython3 +.. code-block:: python stop_event.set() Creating Sound ~~~~~~~~~~~~~~ -.. code:: ipython3 +.. code-block:: python from psonic import * @@ -811,13 +811,13 @@ Creating Sound synth(SQUARE, note=D4) synth(TRI, note=D4, amp=0.4) -.. code:: ipython3 +.. code-block:: python detune = 0.7 synth(SQUARE, note = E4) synth(SQUARE, note = E4+detune) -.. code:: ipython3 +.. code-block:: python detune=0.1 # Amplitude shaping synth(SQUARE, note = E2, release = 2) @@ -831,7 +831,7 @@ Next Step Using FX *Not implemented yet* -.. code:: ipython3 +.. code-block:: python from psonic import * @@ -839,7 +839,7 @@ Using FX *Not implemented yet* synth(PROPHET,note=E2,release=8,cutoff=80) synth(PROPHET,note=E2+4,release=8,cutoff=80) -.. code:: ipython3 +.. code-block:: python with Fx(SLICER, phase=0.125, probability=0.6,prob_pos=1): synth(TB303, note=E2, cutoff_attack=8, release=8) @@ -851,14 +851,14 @@ OSC Communication (Sonic Pi Ver. 3.x or better) In Sonic Pi version 3 or better you can work with messages. -.. code:: ipython3 +.. code-block:: python from psonic import * First you need a programm in the Sonic Pi server that receives messages. You can write it in th GUI or send one with Python. -.. code:: ipython3 +.. code-block:: python run("""live_loop :foo do use_real_time @@ -868,11 +868,11 @@ You can write it in th GUI or send one with Python. Now send a message to Sonic Pi. -.. code:: ipython3 +.. code-block:: python send_message('/trigger/prophet', 70, 100, 8) -.. code:: ipython3 +.. code-block:: python stop() @@ -881,11 +881,11 @@ Recording With python-sonic you can record wave files. -.. code:: ipython3 +.. code-block:: python from psonic import * -.. code:: ipython3 +.. code-block:: python # start recording start_recording() @@ -899,7 +899,7 @@ With python-sonic you can record wave files. play(chord(E4, DOM7)) sleep(1) -.. code:: ipython3 +.. code-block:: python # stop recording stop_recording @@ -913,7 +913,7 @@ With python-sonic you can record wave files. -.. code:: ipython3 +.. code-block:: python # save file save_recording('/Volumes/jupyter/python-sonic/test.wav') @@ -921,11 +921,11 @@ With python-sonic you can record wave files. More Examples ------------- -.. code:: ipython3 +.. code-block:: python from psonic import * -.. code:: ipython3 +.. code-block:: python #Inspired by Steve Reich Clapping Music diff --git a/setup.py b/setup.py index a6383c9..505e1bd 100755 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ here = path.abspath(path.dirname(__file__)) # Get the long description from the README file -with open(path.join(here, 'PREADME.txt'), encoding='utf-8') as f: +with open(path.join(here, 'README.rst'), encoding='utf-8') as f: long_description = f.read() requirements = [ @@ -29,7 +29,6 @@ version='0.4.2', description='Programming Music with Sonic Pi or Supercollider', long_description=long_description, - long_description_content_type='text/plain', url='https://github.com/gkvoelkl/python-sonic', author='gkvoelkl', author_email='gkvoelkl@nelson-games.de', From 18165fa7a86fd63e0192628e514d9eb90bee42c8 Mon Sep 17 00:00:00 2001 From: python-ninja-hebi Date: Mon, 30 Aug 2021 19:01:35 +0200 Subject: [PATCH 15/17] Version 0.4.3 --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 505e1bd..60250e6 100755 --- a/setup.py +++ b/setup.py @@ -1,7 +1,7 @@ from setuptools import setup # To use a consistent encoding # python setup.py sdist bdist_wheel -# twine upload dist/* +# python -m twine upload dist/* from codecs import open from os import path @@ -26,7 +26,7 @@ setup( name='python-sonic', - version='0.4.2', + version='0.4.3', description='Programming Music with Sonic Pi or Supercollider', long_description=long_description, url='https://github.com/gkvoelkl/python-sonic', From 97dba79b9185e88152c8c21b6eccb97448bc1af7 Mon Sep 17 00:00:00 2001 From: Mickey Li Date: Sun, 19 Jan 2025 19:25:10 +0000 Subject: [PATCH 16/17] 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 17/17] 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