├── .gitignore ├── LICENSE ├── README.rst ├── psonic ├── __init__.py ├── api.py ├── effects.py ├── internals │ ├── __init__.py │ ├── chords.py │ └── scales.py ├── notes.py ├── psonic.py ├── samples │ ├── __init__.py │ ├── ambient.py │ ├── bass.py │ ├── drums.py │ ├── electric.py │ ├── guitars.py │ ├── loops │ │ └── __init__.py │ ├── misc.py │ ├── percussions.py │ ├── tabla.py │ └── vinyl.py ├── scales.py ├── synth_server.py └── synthesizers.py ├── psonic_example.py ├── python-sonic.ipynb ├── setup.py └── tests ├── __init__.py ├── conftest.py └── test_psonic.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .pytest_cache/ 25 | .ipynb_checkpoints/ 26 | .installed.cfg 27 | *.egg 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .coverage 43 | .coverage.* 44 | .cache 45 | nosetests.xml 46 | coverage.xml 47 | *,cover 48 | .hypothesis/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | 57 | # Sphinx documentation 58 | docs/_build/ 59 | 60 | # PyBuilder 61 | target/ 62 | 63 | # pyenv python configuration file 64 | .python-version 65 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 G. Völkl 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | python-sonic - Programming Music with Python, Sonic Pi or Supercollider 2 | ======================================================================= 3 | 4 | Python-Sonic is a simple Python interface for Sonic Pi, which is a real 5 | great music software created by Sam Aaron (http://sonic-pi.net). 6 | 7 | At the moment Python-Sonic works with Sonic Pi. It is planned, that it 8 | will work with Supercollider, too. 9 | 10 | This version supports Sonic Pi versions > 4 when OSC run-code security 11 | was added. 12 | 13 | If you like it, use it. If you have some suggestions, tell me 14 | (gkvoelkl@nelson-games.de). 15 | 16 | Installation 17 | ------------ 18 | 19 | - First you need Python 3 (https://www.python.org, ) - Python 3.5 20 | should work, because it’s the development environment 21 | - Then Sonic Pi (https://sonic-pi.net) - That makes the sound 22 | - Modul python-osc (https://pypi.python.org/pypi/python-osc) - 23 | Connection between Python and Sonic Pi Server 24 | - Add the module `python-sonic` - simply copy the source, or try `pip install python-sonic` That should work. 25 | 26 | For local development you might want to locally install using 27 | 28 | $ pip install -e . 29 | 30 | Limitations 31 | ----------- 32 | 33 | - You have to start *Sonic Pi* first before you can use it with 34 | python-sonic 35 | - Only the notes from C2 to C6 36 | 37 | Changelog 38 | --------- 39 | 40 | +--------+-------------------------------------------------------------+ 41 | | V | | 42 | | ersion | | 43 | +========+=============================================================+ 44 | | 0.2.0 | Some changes for Sonic Pi 2.11. Simpler multi-threading | 45 | | | with decorator *@in_thread*. Messaging with *cue* and | 46 | | | *sync*. | 47 | +--------+-------------------------------------------------------------+ 48 | | 0.3.0 | OSC Communication | 49 | +--------+-------------------------------------------------------------+ 50 | | 0.3.1. | Update, sort and duration of samples | 51 | +--------+-------------------------------------------------------------+ 52 | | 0.3.2. | Restructured | 53 | +--------+-------------------------------------------------------------+ 54 | | 0.4.0 | Changes communication ports and recording | 55 | +--------+-------------------------------------------------------------+ 56 | | 0.4.4 | Enables GUI Token | 57 | +--------+-------------------------------------------------------------+ 58 | 59 | Communication 60 | ------------- 61 | 62 | The API *python-sonic* communications with *Sonic Pi* over UDP and two 63 | ports. One port is an internal *Sonic Pi* GUI port and the second is the 64 | external OSC cue port. 65 | 66 | For >v4 a Token is needed to communicate with Sonic Pi. This token is generated 67 | randomly at runtime when Sonic-Pi is started. The Token is extracted from the sonic-pi log files. 68 | Similarly, the GUI udp port is now randomised at start and must be read from the log file too. 69 | In order to play notes (and not just send OSC cues), a connection must be made to the GUI udp port 70 | using the Token as the first argument in the message. The Token is automatically used on send. 71 | 72 | .. code-block:: python 73 | 74 | from psonic import * 75 | set_server_parameter('127.0.0.1', -2005799440, 30129, 4560) 76 | 77 | 78 | These values are found from the file `~/.sonic-pi/log/spider.log` 79 | :: 80 | Sonic Pi Spider Server booting... 81 | The time is 2023-10-01 11:01:41 +0100 82 | Using primary protocol: udp 83 | Detecting port numbers... 84 | 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} 85 | Token: -2005799440 86 | Opening UDP Server to listen to GUI on port: 30129 87 | Spider - Pulling in modules... 88 | Spider - Starting Runtime Server 89 | ... 90 | 91 | This can be automated by using the function `set_server_parameter_from_log` 92 | 93 | .. code-block:: python 94 | 95 | from psonic import * 96 | set_server_parameter_from_log('127.0.0.1') 97 | set_server_parameter_from_log('127.0.0.1', "path-to-log-file") 98 | 99 | Note if the `set_server_parameter` functions are not used, a default connection is created which will not work. 100 | 101 | 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: 102 | 103 | $ python psonic_example.py 104 | 105 | and a note should be played. 106 | 107 | Examples 108 | -------- 109 | 110 | Many of the examples are inspired from the help menu in *Sonic Pi*. 111 | 112 | .. code-block:: python 113 | 114 | from psonic import * 115 | 116 | The first sound 117 | 118 | .. code-block:: python 119 | 120 | play(70) #play MIDI note 70 121 | 122 | Some more notes 123 | 124 | .. code-block:: python 125 | 126 | play(72) 127 | sleep(1) 128 | play(75) 129 | sleep(1) 130 | play(79) 131 | 132 | In more tratitional music notation 133 | 134 | .. code-block:: python 135 | 136 | play(C5) 137 | sleep(0.5) 138 | play(D5) 139 | sleep(0.5) 140 | play(G5) 141 | 142 | Play sharp notes like *F#* or dimished ones like *Eb* 143 | 144 | .. code-block:: python 145 | 146 | play(Fs5) 147 | sleep(0.5) 148 | play(Eb5) 149 | 150 | Play louder (parameter amp) or from a different direction (parameter 151 | pan) 152 | 153 | .. code-block:: python 154 | 155 | play(72,amp=2) 156 | sleep(0.5) 157 | play(74,pan=-1) #left 158 | 159 | Different synthesizer sounds 160 | 161 | .. code-block:: python 162 | 163 | use_synth(SAW) 164 | play(38) 165 | sleep(0.25) 166 | play(50) 167 | sleep(0.5) 168 | use_synth(PROPHET) 169 | play(57) 170 | sleep(0.25) 171 | 172 | ADSR *(Attack, Decay, Sustain and Release)* Envelope 173 | 174 | .. code-block:: python 175 | 176 | play (60, attack=0.5, decay=1, sustain_level=0.4, sustain=2, release=0.5) 177 | sleep(4) 178 | 179 | Play some samples 180 | 181 | .. code-block:: python 182 | 183 | sample(AMBI_LUNAR_LAND, amp=0.5) 184 | 185 | .. code-block:: python 186 | 187 | sample(LOOP_AMEN,pan=-1) 188 | sleep(0.877) 189 | sample(LOOP_AMEN,pan=1) 190 | 191 | .. code-block:: python 192 | 193 | sample(LOOP_AMEN,rate=0.5) 194 | 195 | .. code-block:: python 196 | 197 | sample(LOOP_AMEN,rate=1.5) 198 | 199 | .. code-block:: python 200 | 201 | sample(LOOP_AMEN,rate=-1)#back 202 | 203 | .. code-block:: python 204 | 205 | sample(DRUM_CYMBAL_OPEN,attack=0.01,sustain=0.3,release=0.1) 206 | 207 | .. code-block:: python 208 | 209 | sample(LOOP_AMEN,start=0.5,finish=0.8,rate=-0.2,attack=0.3,release=1) 210 | 211 | Play some random notes 212 | 213 | .. code-block:: python 214 | 215 | import random 216 | 217 | for i in range(5): 218 | play(random.randrange(50, 100)) 219 | sleep(0.5) 220 | 221 | .. code-block:: python 222 | 223 | for i in range(3): 224 | play(random.choice([C5,E5,G5])) 225 | sleep(1) 226 | 227 | Sample slicing 228 | 229 | .. code-block:: python 230 | 231 | from psonic import * 232 | 233 | number_of_pieces = 8 234 | 235 | for i in range(16): 236 | s = random.randrange(0,number_of_pieces)/number_of_pieces #sample starts at 0.0 and finishes at 1.0 237 | f = s + (1.0/number_of_pieces) 238 | sample(LOOP_AMEN,beat_stretch=2,start=s,finish=f) 239 | sleep(2.0/number_of_pieces) 240 | 241 | An infinite loop and if 242 | 243 | .. code-block:: python 244 | 245 | while True: 246 | if one_in(2): 247 | sample(DRUM_HEAVY_KICK) 248 | sleep(0.5) 249 | else: 250 | sample(DRUM_CYMBAL_CLOSED) 251 | sleep(0.25) 252 | 253 | 254 | :: 255 | 256 | 257 | --------------------------------------------------------------------------- 258 | 259 | KeyboardInterrupt Traceback (most recent call last) 260 | 261 | in () 262 | 5 else: 263 | 6 sample(DRUM_CYMBAL_CLOSED) 264 | ----> 7 sleep(0.25) 265 | 266 | 267 | /mnt/jupyter/python-sonic/psonic.py in sleep(duration) 268 | 587 :return: 269 | 588 """ 270 | --> 589 time.sleep(duration) 271 | 590 _debug('sleep', duration) 272 | 591 273 | 274 | 275 | KeyboardInterrupt: 276 | 277 | 278 | If you want to hear more than one sound at a time, use Threads. 279 | 280 | .. code-block:: python 281 | 282 | import random 283 | from psonic import * 284 | from threading import Thread 285 | 286 | def bass_sound(): 287 | c = chord(E3, MAJOR7) 288 | while True: 289 | use_synth(PROPHET) 290 | play(random.choice(c), release=0.6) 291 | sleep(0.5) 292 | 293 | def snare_sound(): 294 | while True: 295 | sample(ELEC_SNARE) 296 | sleep(1) 297 | 298 | bass_thread = Thread(target=bass_sound) 299 | snare_thread = Thread(target=snare_sound) 300 | 301 | bass_thread.start() 302 | snare_thread.start() 303 | 304 | while True: 305 | pass 306 | 307 | 308 | :: 309 | 310 | 311 | --------------------------------------------------------------------------- 312 | 313 | KeyboardInterrupt Traceback (most recent call last) 314 | 315 | in 316 | 22 317 | 23 while True: 318 | ---> 24 pass 319 | 320 | 321 | KeyboardInterrupt: 322 | 323 | 324 | Every function *bass_sound* and *snare_sound* have its own thread. Your 325 | can hear them running. 326 | 327 | .. code-block:: python 328 | 329 | from psonic import * 330 | from threading import Thread, Condition 331 | from random import choice 332 | 333 | def random_riff(condition): 334 | use_synth(PROPHET) 335 | sc = scale(E3, MINOR) 336 | while True: 337 | s = random.choice([0.125,0.25,0.5]) 338 | with condition: 339 | condition.wait() #Wait for message 340 | for i in range(8): 341 | r = random.choice([0.125, 0.25, 1, 2]) 342 | n = random.choice(sc) 343 | co = random.randint(30,100) 344 | play(n, release = r, cutoff = co) 345 | sleep(s) 346 | 347 | def drums(condition): 348 | while True: 349 | with condition: 350 | condition.notifyAll() #Message to threads 351 | for i in range(16): 352 | r = random.randrange(1,10) 353 | sample(DRUM_BASS_HARD, rate=r) 354 | sleep(0.125) 355 | 356 | condition = Condition() 357 | random_riff_thread = Thread(name='consumer1', target=random_riff, args=(condition,)) 358 | drums_thread = Thread(name='producer', target=drums, args=(condition,)) 359 | 360 | random_riff_thread.start() 361 | drums_thread.start() 362 | 363 | input("Press Enter to continue...") 364 | 365 | 366 | .. parsed-literal:: 367 | 368 | Press Enter to continue... 369 | 370 | 371 | 372 | 373 | .. parsed-literal:: 374 | 375 | '' 376 | 377 | 378 | 379 | To synchronize the thread, so that they play a note at the same time, 380 | you can use *Condition*. One function sends a message with 381 | *condition.notifyAll* the other waits until the message comes 382 | *condition.wait*. 383 | 384 | More simple with decorator \_\_@in_thread_\_ 385 | 386 | .. code-block:: python 387 | 388 | from psonic import * 389 | from random import choice 390 | 391 | tick = Message() 392 | 393 | @in_thread 394 | def random_riff(): 395 | use_synth(PROPHET) 396 | sc = scale(E3, MINOR) 397 | while True: 398 | s = random.choice([0.125,0.25,0.5]) 399 | tick.sync() 400 | for i in range(8): 401 | r = random.choice([0.125, 0.25, 1, 2]) 402 | n = random.choice(sc) 403 | co = random.randint(30,100) 404 | play(n, release = r, cutoff = co) 405 | sleep(s) 406 | 407 | @in_thread 408 | def drums(): 409 | while True: 410 | tick.cue() 411 | for i in range(16): 412 | r = random.randrange(1,10) 413 | sample(DRUM_BASS_HARD, rate=r) 414 | sleep(0.125) 415 | 416 | random_riff() 417 | drums() 418 | 419 | input("Press Enter to continue...") 420 | 421 | 422 | .. parsed-literal:: 423 | 424 | Press Enter to continue... 425 | 426 | 427 | .. code-block:: python 428 | 429 | from psonic import * 430 | 431 | tick = Message() 432 | 433 | @in_thread 434 | def metronom(): 435 | while True: 436 | tick.cue() 437 | sleep(1) 438 | 439 | @in_thread 440 | def instrument(): 441 | while True: 442 | tick.sync() 443 | sample(DRUM_HEAVY_KICK) 444 | 445 | metronom() 446 | instrument() 447 | 448 | while True: 449 | pass 450 | 451 | Play a list of notes 452 | 453 | .. code-block:: python 454 | 455 | from psonic import * 456 | 457 | play ([64, 67, 71], amp = 0.3) 458 | sleep(1) 459 | play ([E4, G4, B4]) 460 | sleep(1) 461 | 462 | Play chords 463 | 464 | .. code-block:: python 465 | 466 | play(chord(E4, MINOR)) 467 | sleep(1) 468 | play(chord(E4, MAJOR)) 469 | sleep(1) 470 | play(chord(E4, MINOR7)) 471 | sleep(1) 472 | play(chord(E4, DOM7)) 473 | sleep(1) 474 | 475 | Play arpeggios 476 | 477 | .. code-block:: python 478 | 479 | play_pattern( chord(E4, 'm7')) 480 | play_pattern_timed( chord(E4, 'm7'), 0.25) 481 | play_pattern_timed(chord(E4, 'dim'), [0.25, 0.5]) 482 | 483 | Play scales 484 | 485 | .. code-block:: python 486 | 487 | play_pattern_timed(scale(C3, MAJOR), 0.125, release = 0.1) 488 | play_pattern_timed(scale(C3, MAJOR, num_octaves = 2), 0.125, release = 0.1) 489 | play_pattern_timed(scale(C3, MAJOR_PENTATONIC, num_octaves = 2), 0.125, release = 0.1) 490 | 491 | The function *scale* returns a list with all notes of a scale. So you 492 | can use list methodes or functions. For example to play arpeggios 493 | descending or shuffeld. 494 | 495 | .. code-block:: python 496 | 497 | import random 498 | from psonic import * 499 | 500 | s = scale(C3, MAJOR) 501 | s 502 | 503 | 504 | 505 | 506 | .. parsed-literal:: 507 | 508 | [48, 50, 52, 53, 55, 57, 59, 60] 509 | 510 | 511 | 512 | .. code-block:: python 513 | 514 | s.reverse() 515 | 516 | .. code-block:: python 517 | 518 | 519 | play_pattern_timed(s, 0.125, release = 0.1) 520 | random.shuffle(s) 521 | play_pattern_timed(s, 0.125, release = 0.1) 522 | 523 | Live Loop 524 | ~~~~~~~~~ 525 | 526 | One of the best in SONIC PI is the *Live Loop*. While a loop is playing 527 | music you can change it and hear the change. Let’s try it in Python, 528 | too. 529 | 530 | .. code-block:: python 531 | 532 | from psonic import * 533 | from threading import Thread 534 | 535 | def my_loop(): 536 | play(60) 537 | sleep(1) 538 | 539 | def looper(): 540 | while True: 541 | my_loop() 542 | 543 | looper_thread = Thread(name='looper', target=looper) 544 | 545 | looper_thread.start() 546 | 547 | input("Press Enter to continue...") 548 | 549 | 550 | .. parsed-literal:: 551 | 552 | Press Enter to continue...Y 553 | 554 | 555 | 556 | 557 | .. parsed-literal:: 558 | 559 | 'Y' 560 | 561 | 562 | 563 | Now change the function *my_loop* und you can hear it. 564 | 565 | .. code-block:: python 566 | 567 | def my_loop(): 568 | use_synth(TB303) 569 | play (60, release= 0.3) 570 | sleep (0.25) 571 | 572 | .. code-block:: python 573 | 574 | def my_loop(): 575 | use_synth(TB303) 576 | play (chord(E3, MINOR), release= 0.3) 577 | sleep(0.5) 578 | 579 | .. code-block:: python 580 | 581 | def my_loop(): 582 | use_synth(TB303) 583 | sample(DRUM_BASS_HARD, rate = random.uniform(0.5, 2)) 584 | play(random.choice(chord(E3, MINOR)), release= 0.2, cutoff=random.randrange(60, 130)) 585 | sleep(0.25) 586 | 587 | To stop the sound you have to end the kernel. In IPython with Kernel –> 588 | Restart 589 | 590 | Now with two live loops which are synch. 591 | 592 | .. code-block:: python 593 | 594 | from psonic import * 595 | from threading import Thread, Condition 596 | from random import choice 597 | 598 | def loop_foo(): 599 | play (E4, release = 0.5) 600 | sleep (0.5) 601 | 602 | 603 | def loop_bar(): 604 | sample (DRUM_SNARE_SOFT) 605 | sleep (1) 606 | 607 | 608 | def live_loop_1(condition): 609 | while True: 610 | with condition: 611 | condition.notifyAll() #Message to threads 612 | loop_foo() 613 | 614 | def live_loop_2(condition): 615 | while True: 616 | with condition: 617 | condition.wait() #Wait for message 618 | loop_bar() 619 | 620 | condition = Condition() 621 | live_thread_1 = Thread(name='producer', target=live_loop_1, args=(condition,)) 622 | live_thread_2 = Thread(name='consumer1', target=live_loop_2, args=(condition,)) 623 | 624 | live_thread_1.start() 625 | live_thread_2.start() 626 | 627 | input("Press Enter to continue...") 628 | 629 | 630 | .. parsed-literal:: 631 | 632 | Press Enter to continue...y 633 | 634 | 635 | 636 | 637 | .. parsed-literal:: 638 | 639 | 'y' 640 | 641 | 642 | 643 | .. code-block:: python 644 | 645 | def loop_foo(): 646 | play (A4, release = 0.5) 647 | sleep (0.5) 648 | 649 | .. code-block:: python 650 | 651 | def loop_bar(): 652 | sample (DRUM_HEAVY_KICK) 653 | sleep (0.125) 654 | 655 | If would be nice if we can stop the loop with a simple command. With 656 | stop event it works. 657 | 658 | .. code-block:: python 659 | 660 | from psonic import * 661 | from threading import Thread, Condition, Event 662 | 663 | def loop_foo(): 664 | play (E4, release = 0.5) 665 | sleep (0.5) 666 | 667 | 668 | def loop_bar(): 669 | sample (DRUM_SNARE_SOFT) 670 | sleep (1) 671 | 672 | 673 | def live_loop_1(condition,stop_event): 674 | while not stop_event.is_set(): 675 | with condition: 676 | condition.notifyAll() #Message to threads 677 | loop_foo() 678 | 679 | def live_loop_2(condition,stop_event): 680 | while not stop_event.is_set(): 681 | with condition: 682 | condition.wait() #Wait for message 683 | loop_bar() 684 | 685 | 686 | 687 | condition = Condition() 688 | stop_event = Event() 689 | live_thread_1 = Thread(name='producer', target=live_loop_1, args=(condition,stop_event)) 690 | live_thread_2 = Thread(name='consumer1', target=live_loop_2, args=(condition,stop_event)) 691 | 692 | 693 | live_thread_1.start() 694 | live_thread_2.start() 695 | 696 | input("Press Enter to continue...") 697 | 698 | 699 | .. parsed-literal:: 700 | 701 | Press Enter to continue...y 702 | 703 | 704 | 705 | 706 | .. parsed-literal:: 707 | 708 | 'y' 709 | 710 | 711 | 712 | .. code-block:: python 713 | 714 | stop_event.set() 715 | 716 | More complex live loops 717 | 718 | .. code-block:: python 719 | 720 | sc = Ring(scale(E3, MINOR_PENTATONIC)) 721 | 722 | def loop_foo(): 723 | play (next(sc), release= 0.1) 724 | sleep (0.125) 725 | 726 | sc2 = Ring(scale(E3,MINOR_PENTATONIC,num_octaves=2)) 727 | 728 | def loop_bar(): 729 | use_synth(DSAW) 730 | play (next(sc2), release= 0.25) 731 | sleep (0.25) 732 | 733 | Now a simple structure with four live loops 734 | 735 | .. code-block:: python 736 | 737 | import random 738 | from psonic import * 739 | from threading import Thread, Condition, Event 740 | 741 | def live_1(): 742 | pass 743 | 744 | def live_2(): 745 | pass 746 | 747 | def live_3(): 748 | pass 749 | 750 | def live_4(): 751 | pass 752 | 753 | def live_loop_1(condition,stop_event): 754 | while not stop_event.is_set(): 755 | with condition: 756 | condition.notifyAll() #Message to threads 757 | live_1() 758 | 759 | def live_loop_2(condition,stop_event): 760 | while not stop_event.is_set(): 761 | with condition: 762 | condition.wait() #Wait for message 763 | live_2() 764 | 765 | def live_loop_3(condition,stop_event): 766 | while not stop_event.is_set(): 767 | with condition: 768 | condition.wait() #Wait for message 769 | live_3() 770 | 771 | def live_loop_4(condition,stop_event): 772 | while not stop_event.is_set(): 773 | with condition: 774 | condition.wait() #Wait for message 775 | live_4() 776 | 777 | condition = Condition() 778 | stop_event = Event() 779 | live_thread_1 = Thread(name='producer', target=live_loop_1, args=(condition,stop_event)) 780 | live_thread_2 = Thread(name='consumer1', target=live_loop_2, args=(condition,stop_event)) 781 | live_thread_3 = Thread(name='consumer2', target=live_loop_3, args=(condition,stop_event)) 782 | live_thread_4 = Thread(name='consumer3', target=live_loop_3, args=(condition,stop_event)) 783 | 784 | live_thread_1.start() 785 | live_thread_2.start() 786 | live_thread_3.start() 787 | live_thread_4.start() 788 | 789 | input("Press Enter to continue...") 790 | 791 | 792 | .. parsed-literal:: 793 | 794 | Press Enter to continue...y 795 | 796 | 797 | 798 | 799 | .. parsed-literal:: 800 | 801 | 'y' 802 | 803 | 804 | 805 | After starting the loops you can change them 806 | 807 | .. code-block:: python 808 | 809 | def live_1(): 810 | sample(BD_HAUS,amp=2) 811 | sleep(0.5) 812 | pass 813 | 814 | .. code-block:: python 815 | 816 | def live_2(): 817 | #sample(AMBI_CHOIR, rate=0.4) 818 | #sleep(1) 819 | pass 820 | 821 | .. code-block:: python 822 | 823 | def live_3(): 824 | use_synth(TB303) 825 | play(E2, release=4,cutoff=120,cutoff_attack=1) 826 | sleep(4) 827 | 828 | .. code-block:: python 829 | 830 | def live_4(): 831 | notes = scale(E3, MINOR_PENTATONIC, num_octaves=2) 832 | for i in range(8): 833 | play(random.choice(notes),release=0.1,amp=1.5) 834 | sleep(0.125) 835 | 836 | And stop. 837 | 838 | .. code-block:: python 839 | 840 | stop_event.set() 841 | 842 | Creating Sound 843 | ~~~~~~~~~~~~~~ 844 | 845 | .. code-block:: python 846 | 847 | from psonic import * 848 | 849 | synth(SINE, note=D4) 850 | synth(SQUARE, note=D4) 851 | synth(TRI, note=D4, amp=0.4) 852 | 853 | .. code-block:: python 854 | 855 | detune = 0.7 856 | synth(SQUARE, note = E4) 857 | synth(SQUARE, note = E4+detune) 858 | 859 | .. code-block:: python 860 | 861 | detune=0.1 # Amplitude shaping 862 | synth(SQUARE, note = E2, release = 2) 863 | synth(SQUARE, note = E2+detune, amp = 2, release = 2) 864 | synth(GNOISE, release = 2, amp = 1, cutoff = 60) 865 | synth(GNOISE, release = 0.5, amp = 1, cutoff = 100) 866 | synth(NOISE, release = 0.2, amp = 1, cutoff = 90) 867 | 868 | Next Step 869 | ~~~~~~~~~ 870 | 871 | Using FX *Not implemented yet* 872 | 873 | .. code-block:: python 874 | 875 | from psonic import * 876 | 877 | with Fx(SLICER): 878 | synth(PROPHET,note=E2,release=8,cutoff=80) 879 | synth(PROPHET,note=E2+4,release=8,cutoff=80) 880 | 881 | .. code-block:: python 882 | 883 | with Fx(SLICER, phase=0.125, probability=0.6,prob_pos=1): 884 | synth(TB303, note=E2, cutoff_attack=8, release=8) 885 | synth(TB303, note=E3, cutoff_attack=4, release=8) 886 | synth(TB303, note=E4, cutoff_attack=2, release=8) 887 | 888 | OSC Communication (Sonic Pi Ver. 3.x or better) 889 | ----------------------------------------------- 890 | 891 | In Sonic Pi version 3 or better you can work with messages. 892 | 893 | .. code-block:: python 894 | 895 | from psonic import * 896 | 897 | First you need a programm in the Sonic Pi server that receives messages. 898 | You can write it in th GUI or send one with Python. 899 | 900 | .. code-block:: python 901 | 902 | run("""live_loop :foo do 903 | use_real_time 904 | a, b, c = sync "/osc*/trigger/prophet" 905 | synth :prophet, note: a, cutoff: b, sustain: c 906 | end """) 907 | 908 | Now send a message to Sonic Pi. 909 | 910 | .. code-block:: python 911 | 912 | send_message('/trigger/prophet', 70, 100, 8) 913 | 914 | .. code-block:: python 915 | 916 | stop() 917 | 918 | Recording 919 | --------- 920 | 921 | With python-sonic you can record wave files. 922 | 923 | .. code-block:: python 924 | 925 | from psonic import * 926 | 927 | .. code-block:: python 928 | 929 | # start recording 930 | start_recording() 931 | 932 | play(chord(E4, MINOR)) 933 | sleep(1) 934 | play(chord(E4, MAJOR)) 935 | sleep(1) 936 | play(chord(E4, MINOR7)) 937 | sleep(1) 938 | play(chord(E4, DOM7)) 939 | sleep(1) 940 | 941 | .. code-block:: python 942 | 943 | # stop recording 944 | stop_recording 945 | 946 | 947 | 948 | 949 | .. parsed-literal:: 950 | 951 | 952 | 953 | 954 | 955 | .. code-block:: python 956 | 957 | # save file 958 | save_recording('/Volumes/jupyter/python-sonic/test.wav') 959 | 960 | More Examples 961 | ------------- 962 | 963 | .. code-block:: python 964 | 965 | from psonic import * 966 | 967 | .. code-block:: python 968 | 969 | #Inspired by Steve Reich Clapping Music 970 | 971 | clapping = [1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0] 972 | 973 | for i in range(13): 974 | for j in range(4): 975 | for k in range(12): 976 | if clapping[k] ==1 : sample(DRUM_SNARE_SOFT,pan=-0.5) 977 | if clapping[(i+k)%12] == 1: sample(DRUM_HEAVY_KICK,pan=0.5) 978 | sleep (0.25) 979 | 980 | Projects that use Python-Sonic 981 | ------------------------------ 982 | 983 | Raspberry Pi sonic-track.py a Sonic-pi Motion Track Demo 984 | https://github.com/pageauc/sonic-track 985 | 986 | Sources 987 | ------- 988 | 989 | Joe Armstrong: Connecting Erlang to the Sonic Pi 990 | http://joearms.github.io/2015/01/05/Connecting-Erlang-to-Sonic-Pi.html 991 | 992 | Joe Armstrong: Controlling Sound with OSC Messages 993 | http://joearms.github.io/2016/01/29/Controlling-Sound-with-OSC-Messages.html 994 | 995 | .. 996 | -------------------------------------------------------------------------------- /psonic/__init__.py: -------------------------------------------------------------------------------- 1 | from .api import * 2 | -------------------------------------------------------------------------------- /psonic/api.py: -------------------------------------------------------------------------------- 1 | import random 2 | import threading 3 | from .synth_server import ( 4 | SonicPi, 5 | SonicPiNew, 6 | use_synth, 7 | ) 8 | from .psonic import * 9 | from .notes import * 10 | from .scales import * 11 | from .synthesizers import * 12 | from .effects import * 13 | from .samples import * 14 | from .samples.loops import * 15 | from .samples.ambient import * 16 | from .samples.bass import * 17 | from .samples.drums import * 18 | from .samples.electric import * 19 | from .samples.guitars import * 20 | from .samples.misc import * 21 | from .samples.percussions import * 22 | from .samples.tabla import * 23 | from .samples.vinyl import * 24 | from .internals.chords import * 25 | from .internals.scales import * 26 | 27 | 28 | def in_thread(func): 29 | """Thread decorator""" 30 | 31 | def wrapper(): 32 | _thread = threading.Thread(target=func) 33 | _thread.start() 34 | 35 | return wrapper 36 | 37 | class Fx: 38 | """FX Effects""" 39 | 40 | def __init__(self, mode, phase=0.24, probability=0, prob_pos=0): 41 | self.mode = mode 42 | self.phase = phase 43 | self.probability = probability 44 | self.prob_pos = prob_pos 45 | 46 | def __enter__(self): 47 | pass 48 | 49 | def __exit__(self, exc_type, exc_val, exc_tb): 50 | pass 51 | 52 | class Ring: 53 | """ring buffer""" 54 | 55 | def __init__(self, data): 56 | self.data = data 57 | self.index = -1 58 | 59 | def __iter__(self): # return Iterator 60 | return self 61 | 62 | def __next__(self): # return Iterator next element 63 | self.index += 1 64 | if self.index == len(self.data): 65 | self.index = 0 66 | return self.data[self.index] 67 | 68 | def choose(self): # random choose 69 | return random.choice(self.data) 70 | 71 | # Not used anywhere 72 | class Message: 73 | """For sending messages between threads""" 74 | 75 | def __init__(self): 76 | self._condition = threading.Condition() 77 | 78 | def cue(self): 79 | with self._condition: 80 | self._condition.notifyAll() # Message to threads 81 | 82 | def sync(self): 83 | with self._condition: 84 | self._condition.wait() # Wait for message 85 | -------------------------------------------------------------------------------- /psonic/effects.py: -------------------------------------------------------------------------------- 1 | """Effects""" 2 | 3 | class FxName: 4 | """FX name""" 5 | 6 | def __init__(self, name): 7 | self.name = name 8 | 9 | 10 | BITCRUSHER = FxName('bitcrusher') 11 | COMPRESSOR = FxName('compressor') 12 | ECHO = FxName('echo') 13 | FLANGER = FxName('flanger') 14 | KRUSH = FxName('krush') 15 | LPF = FxName('lpf') 16 | PAN = FxName('pan') 17 | PANSLICER = FxName('panslicer') 18 | REVERB = FxName('reverb') 19 | SLICER = FxName('slicer') 20 | WOBBLE = FxName('wobble') 21 | -------------------------------------------------------------------------------- /psonic/internals/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gkvoelkl/python-sonic/5a0e873c532a3847e59b15e1f7041f8511e462ac/psonic/internals/__init__.py -------------------------------------------------------------------------------- /psonic/internals/chords.py: -------------------------------------------------------------------------------- 1 | 2 | # Not used anywhere 3 | class ChordQuality: 4 | """Chord Quality""" 5 | 6 | def __init__(self, name, inter): 7 | self.name = name 8 | self.inter = inter 9 | 10 | _CHORD_QUALITY = { 11 | 'major': [0, 4, 7], 12 | 'minor': [0, 3, 7], 13 | 'major7': [0, 4, 7, 11], 14 | 'dom7': [0, 4, 7, 10], 15 | 'minor7': [0, 3, 7, 10], 16 | 'aug': [0, 4, 8], 17 | 'dim': [0, 3, 6], 18 | 'dim7': [0, 3, 6, 9], 19 | '1': [0], 20 | "5": [0, 7], 21 | "+5": [0, 4, 8], 22 | "m+5": [0, 3, 8], 23 | "sus2": [0, 2, 7], 24 | "sus4": [0, 5, 7], 25 | "6": [0, 4, 7, 9], 26 | "m6": [0, 3, 7, 9], 27 | "7sus2": [0, 2, 7, 10], 28 | "7sus4": [0, 5, 7, 10], 29 | "7-5": [0, 4, 6, 10], 30 | "m7-5": [0, 3, 6, 10], 31 | "7+5": [0, 4, 8, 10], 32 | "m7+5": [0, 3, 8, 10], 33 | "9": [0, 4, 7, 10, 14], 34 | "m9": [0, 3, 7, 10, 14], 35 | "m7+9": [0, 3, 7, 10, 14], 36 | "maj9": [0, 4, 7, 11, 14], 37 | "9sus4": [0, 5, 7, 10, 14], 38 | "6*9": [0, 4, 7, 9, 14], 39 | "m6*9": [0, 3, 9, 7, 14], 40 | "7-9": [0, 4, 7, 10, 13], 41 | "m7-9": [0, 3, 7, 10, 13], 42 | "7-10": [0, 4, 7, 10, 15], 43 | "9+5": [0, 10, 13], 44 | "m9+5": [0, 10, 14], 45 | "7+5-9": [0, 4, 8, 10, 13], 46 | "m7+5-9": [0, 3, 8, 10, 13], 47 | "11": [0, 4, 7, 10, 14, 17], 48 | "m11": [0, 3, 7, 10, 14, 17], 49 | "maj11": [0, 4, 7, 11, 14, 17], 50 | "11+": [0, 4, 7, 10, 14, 18], 51 | "m11+": [0, 3, 7, 10, 14, 18], 52 | "13": [0, 4, 7, 10, 14, 17, 21], 53 | "m13": [0, 3, 7, 10, 14, 17, 21], 54 | "M": [0, 4, 7], 55 | "m": [0, 3, 7], 56 | "7": [0, 4, 7, 10], 57 | "M7": [0, 4, 7, 11], 58 | "m7": [0, 3, 7], 59 | "augmented": [0, 4, 8], 60 | "a": [0, 4, 8], 61 | "diminished": [0, 3, 6], 62 | "i": [0, 3, 6], 63 | "diminished7": [0, 3, 6, 9], 64 | "i7": [0, 3, 6, 9] 65 | } 66 | ## Chord Quality (from sonic pi) ## 67 | MAJOR7 = "major7" 68 | DOM7 = "dom7" 69 | MINOR7 = "minor7" 70 | AUG = "aug" 71 | DIM = "dim" 72 | DIM7 = "dim7" 73 | 74 | 75 | -------------------------------------------------------------------------------- /psonic/internals/scales.py: -------------------------------------------------------------------------------- 1 | _ionian_sequence = [2, 2, 1, 2, 2, 2, 1] 2 | _hex_sequence = [2, 2, 1, 2, 2, 3] 3 | _pentatonic_sequence = [3, 2, 2, 3, 2] 4 | 5 | _SCALE_MODE = { 6 | 'diatonic': _ionian_sequence, 7 | 'ionian': _ionian_sequence, 8 | 'major': _ionian_sequence, 9 | 'dorian': _ionian_sequence[1:] + _ionian_sequence[:1], # rotate 1 10 | 'phrygian': _ionian_sequence[2:] + _ionian_sequence[:2], # rotate(2) 11 | 'lydian': _ionian_sequence[3:] + _ionian_sequence[:3], # rotate(3) 12 | 'mixolydian': _ionian_sequence[4:] + _ionian_sequence[:4], # rotate(4) 13 | 'aeolian': _ionian_sequence[5:] + _ionian_sequence[:5], # rotate(5) 14 | 'minor': _ionian_sequence[5:] + _ionian_sequence[:5], # rotate(5) 15 | 'locrian': _ionian_sequence[6:] + _ionian_sequence[:6], # rotate(6) 16 | 'hex_major6': _hex_sequence, 17 | 'hex_dorian': _hex_sequence[1:] + _hex_sequence[:1], # rotate(1) 18 | 'hex_phrygian': _hex_sequence[2:] + _hex_sequence[:2], # rotate(2) 19 | 'hex_major7': _hex_sequence[3:] + _hex_sequence[:3], # rotate(3) 20 | 'hex_sus': _hex_sequence[4:] + _hex_sequence[:4], # rotate(4) 21 | 'hex_aeolian': _hex_sequence[5:] + _hex_sequence[:5], # rotate(5) 22 | 'minor_pentatonic': _pentatonic_sequence, 23 | 'yu': _pentatonic_sequence, 24 | 'major_pentatonic': _pentatonic_sequence[1:] + _pentatonic_sequence[:1], # rotate(1) 25 | 'gong': _pentatonic_sequence[1:] + _pentatonic_sequence[:1], # rotate(1) 26 | 'egyptian': _pentatonic_sequence[2:] + _pentatonic_sequence[:2], # rotate(2) 27 | 'shang': _pentatonic_sequence[2:] + _pentatonic_sequence[:2], # rotate(2) 28 | 'jiao': _pentatonic_sequence[3:] + _pentatonic_sequence[:3], # rotate(3) 29 | 'zhi': _pentatonic_sequence[4:] + _pentatonic_sequence[:4], # rotate(4) 30 | 'ritusen': _pentatonic_sequence[4:] + _pentatonic_sequence[:4], # rotate(4) 31 | 'whole_tone': [2, 2, 2, 2, 2, 2], 32 | 'whole': [2, 2, 2, 2, 2, 2], 33 | 'chromatic': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], 34 | 'harmonic_minor': [2, 1, 2, 2, 1, 3, 1], 35 | 'melodic_minor_asc': [2, 1, 2, 2, 2, 2, 1], 36 | 'hungarian_minor': [2, 1, 3, 1, 1, 3, 1], 37 | 'octatonic': [2, 1, 2, 1, 2, 1, 2, 1], 38 | 'messiaen1': [2, 2, 2, 2, 2, 2], 39 | 'messiaen2': [1, 2, 1, 2, 1, 2, 1, 2], 40 | 'messiaen3': [2, 1, 1, 2, 1, 1, 2, 1, 1], 41 | 'messiaen4': [1, 1, 3, 1, 1, 1, 3, 1], 42 | 'messiaen5': [1, 4, 1, 1, 4, 1], 43 | 'messiaen6': [2, 2, 1, 1, 2, 2, 1, 1], 44 | 'messiaen7': [1, 1, 1, 2, 1, 1, 1, 1, 2, 1], 45 | 'super_locrian': [1, 2, 1, 2, 2, 2, 2], 46 | 'hirajoshi': [2, 1, 4, 1, 4], 47 | 'kumoi': [2, 1, 4, 2, 3], 48 | 'neapolitan_major': [1, 2, 2, 2, 2, 2, 1], 49 | 'bartok': [2, 2, 1, 2, 1, 2, 2], 50 | 'bhairav': [1, 3, 1, 2, 1, 3, 1], 51 | 'locrian_major': [2, 2, 1, 1, 2, 2, 2], 52 | 'ahirbhairav': [1, 3, 1, 2, 2, 1, 2], 53 | 'enigmatic': [1, 3, 2, 2, 2, 1, 1], 54 | 'neapolitan_minor': [1, 2, 2, 2, 1, 3, 1], 55 | 'pelog': [1, 2, 4, 1, 4], 56 | 'augmented2': [1, 3, 1, 3, 1, 3], 57 | 'scriabin': [1, 3, 3, 2, 3], 58 | 'harmonic_major': [2, 2, 1, 2, 1, 3, 1], 59 | 'melodic_minor_desc': [2, 1, 2, 2, 1, 2, 2], 60 | 'romanian_minor': [2, 1, 3, 1, 2, 1, 2], 61 | 'hindu': [2, 2, 1, 2, 1, 2, 2], 62 | 'iwato': [1, 4, 1, 4, 2], 63 | 'melodic_minor': [2, 1, 2, 2, 2, 2, 1], 64 | 'diminished2': [2, 1, 2, 1, 2, 1, 2, 1], 65 | 'marva': [1, 3, 2, 1, 2, 2, 1], 66 | 'melodic_major': [2, 2, 1, 2, 1, 2, 2], 67 | 'indian': [4, 1, 2, 3, 2], 68 | 'spanish': [1, 3, 1, 2, 1, 2, 2], 69 | 'prometheus': [2, 2, 2, 5, 1], 70 | 'diminished': [1, 2, 1, 2, 1, 2, 1, 2], 71 | 'todi': [1, 2, 3, 1, 1, 3, 1], 72 | 'leading_whole': [2, 2, 2, 2, 2, 1, 1], 73 | 'augmented': [3, 1, 3, 1, 3, 1], 74 | 'purvi': [1, 3, 2, 1, 1, 3, 1], 75 | 'chinese': [4, 2, 1, 4, 1], 76 | 'lydian_minor': [2, 2, 2, 1, 1, 2, 2], 77 | 'i': _ionian_sequence, 78 | 'ii': _ionian_sequence[1:] + _ionian_sequence[:1], # rotate(1) 79 | 'iii': _ionian_sequence[2:] + _ionian_sequence[:2], # rotate(2) 80 | 'iv': _ionian_sequence[3:] + _ionian_sequence[:3], # rotate(3) 81 | 'v': _ionian_sequence[4:] + _ionian_sequence[:4], # rotate(4) 82 | 'vi': _ionian_sequence[5:] + _ionian_sequence[:5], # rotate(5) 83 | 'vii': _ionian_sequence[6:] + _ionian_sequence[:6], # rotate(6), 84 | 'viii': _ionian_sequence[7:] + _ionian_sequence[:7], # rotate(7) 85 | } 86 | 87 | 88 | -------------------------------------------------------------------------------- /psonic/notes.py: -------------------------------------------------------------------------------- 1 | """Notes""" 2 | 3 | root_notes = { 4 | "C0": 12, 5 | "Cs0": 13, 6 | "Db0": 13, 7 | "D0": 14, 8 | "Ds0": 15, 9 | "Eb0": 15, 10 | "E0": 16, 11 | "F0": 17, 12 | "Fs0": 18, 13 | "Gb0": 18, 14 | "G0": 19, 15 | "Gs0": 20, 16 | "Ab0": 20, 17 | "A0": 21, 18 | "As0": 22, 19 | "Bb0": 22, 20 | "B0": 23, 21 | } 22 | 23 | notes = {} 24 | for octave in range(9): 25 | for key in root_notes: 26 | notes[key.replace("0", str(octave))] = root_notes[key] + (12 * octave) 27 | 28 | globals().update(notes) 29 | 30 | R = 0 31 | -------------------------------------------------------------------------------- /psonic/psonic.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | import random 4 | from .samples import Sample 5 | from .synthesizers import SAW 6 | from .notes import C5 7 | from .internals.chords import _CHORD_QUALITY 8 | from .internals.scales import _SCALE_MODE 9 | from .synth_server import ( 10 | SonicPi, 11 | use_synth, 12 | ) 13 | 14 | __debug = False 15 | 16 | def synth(name, note=None, attack=None, decay=None, 17 | sustain_level=None, sustain=None, release=None, 18 | cutoff=None, cutoff_attack=None, amp=None, pan=None): 19 | 20 | arguments = locals() 21 | arguments.pop('name') 22 | parameters = ['{0}: {1}'.format(k, v) for k, v in arguments.items() if v is not None] 23 | parameter = '' 24 | if len(parameters) > 0: parameter = ',' + ','.join(parameters) 25 | 26 | command = 'synth :{0}{1}'.format(name.name, parameter) 27 | 28 | _debug('synth command={}'.format(command)) 29 | synth_server.synth(command) 30 | 31 | def play(note, attack=None, decay=None, 32 | sustain_level=None, sustain=None, release=None, 33 | cutoff=None, cutoff_attack=None, amp=None, pan=None): 34 | 35 | arguments = locals() 36 | arguments.pop('note') 37 | parameters = ['{0}: {1}'.format(k, v) for k, v in arguments.items() if v is not None] 38 | parameter = '' 39 | if len(parameters) > 0: parameter = ',' + ','.join(parameters) 40 | 41 | command = 'play {0}{1}'.format(note, parameter) 42 | 43 | _debug('play command={}'.format(command)) 44 | synth_server.play(command) 45 | 46 | def play_pattern_timed(notes, times, release=None): 47 | """play notes 48 | :param notes: 49 | :param times: 50 | :return: 51 | """ 52 | if not type(notes) is list: notes = [notes] 53 | if not type(times) is list: times = [times] 54 | 55 | for t in times: 56 | for i in notes: 57 | play(i, release=release) 58 | sleep(t) 59 | 60 | def play_pattern(notes): 61 | """:param notes: 62 | :return: 63 | """ 64 | play_pattern_timed(notes, 1) 65 | 66 | def sample(sample, rate=None, attack=None, sustain=None, 67 | release=None, beat_stretch=None, start=None, 68 | finish=None, amp=None, pan=None): 69 | 70 | arguments = locals() 71 | arguments.pop('sample') 72 | parameters = ['{0}: {1}'.format(k, v) for k, v in arguments.items() if v is not None] 73 | parameter = '' 74 | command = '' 75 | 76 | if len(parameters) > 0: parameter = ',' + ','.join(parameters) 77 | if type(sample) == Sample: 78 | command = 'sample :{0}{1}'.format(sample.name, parameter) 79 | else: 80 | command = 'sample "{0}"{1}'.format(sample, parameter) 81 | _debug('sample command={}'.format(command)) 82 | synth_server.sample(command) 83 | 84 | def sleep(duration): 85 | """the same as time.sleep 86 | :param duration: 87 | :return: 88 | """ 89 | synth_server.sleep(duration) 90 | _debug('sleep', duration) 91 | 92 | def sample_duration(sample): 93 | """Returns the duration of the sample (in seconds) 94 | :param sample: 95 | :return: number 96 | """ 97 | return sample.duration 98 | 99 | def one_in(max): 100 | """random function returns True in one of max cases 101 | :param max: 102 | :return: boolean 103 | """ 104 | return random.randint(1, max) == 1 105 | 106 | def invert_pitches(pitches, inversion): 107 | """Inverts a list of pitches, wrapping the top pitches an octave below the root 108 | :param pitches: list 109 | :param inversion: int 110 | :return: list 111 | """ 112 | 113 | for i in range(1, (inversion % (len(pitches)))+1): 114 | pitches[-i] = pitches[-i] - 12 115 | 116 | pitches.sort() 117 | 118 | return pitches 119 | 120 | def chord(root_note, chord_quality, inversion=None): 121 | """Generates a list of notes of a chord 122 | :param root_note: 123 | :param chord_quality: 124 | :param inversion: 125 | :return: list 126 | """ 127 | result = [] 128 | n = root_note 129 | 130 | half_tone_steps = _CHORD_QUALITY[chord_quality] 131 | 132 | for i in half_tone_steps: 133 | q = n + i 134 | result.append(q) 135 | 136 | if inversion: 137 | result = invert_pitches(result, inversion) 138 | 139 | return result 140 | 141 | def scale(root_note, scale_mode, num_octaves=1): 142 | """Genarates a liste of notes of scale 143 | :param root_note: 144 | :param scale_mode: 145 | :param num_octaves: 146 | :return: list 147 | """ 148 | result = [] 149 | n = root_note 150 | 151 | half_tone_steps = _SCALE_MODE[scale_mode] 152 | 153 | for o in range(num_octaves): 154 | n = root_note + o * 12 155 | result.append(n) 156 | for i in half_tone_steps: 157 | n = n + i 158 | result.append(n) 159 | 160 | return result 161 | 162 | def run(command): 163 | synth_server.run(command) 164 | 165 | def stop(): 166 | synth_server.stop() 167 | 168 | def send_message(message, *parameter): 169 | synth_server.send_message(message, *parameter) 170 | 171 | 172 | def start_recording(): 173 | synth_server.start_recording() 174 | 175 | 176 | def stop_recording(): 177 | synth_server.stop_recording() 178 | 179 | 180 | def save_recording(name): 181 | synth_server.save_recording(name) 182 | 183 | synth_server = SonicPi() 184 | 185 | def set_server_parameter(udp_ip, token, udp_port=-1, udp_port_osc_message=-1): 186 | synth_server.set_parameter(udp_ip, token, udp_port, udp_port_osc_message) 187 | 188 | def set_server_parameter_from_log(udp_ip, log_file=None): 189 | if log_file is None: 190 | # Automatically find the log file 191 | home = os.path.expanduser("~") 192 | log_file = os.path.join(home, ".sonic-pi", "log", "spider.log") 193 | print(f"Reading Parameters From Log File: {log_file}") 194 | 195 | with open(log_file, 'r') as f: 196 | text = f.read() 197 | 198 | # Use regular expressions to extract the values 199 | token_match = re.search(r'Token: (-?\d+)', text) 200 | server_port_match = re.search(r':server_port=>(\d+)', text) 201 | osc_cues_port_match = re.search(r':osc_cues_port=>(\d+)', text) 202 | 203 | token = int(token_match.group(1)) if token_match else None 204 | udp_port = int(server_port_match.group(1)) if server_port_match else None 205 | udp_port_osc_message = int(osc_cues_port_match.group(1)) if osc_cues_port_match else None 206 | 207 | if token and udp_port and udp_port_osc_message: 208 | print(f"Reading Parameters Succeeded. ip: {udp_ip}, token: \"{token}\", server_port: \"{udp_port}\", osc_port: {udp_port_osc_message}") 209 | synth_server.set_parameter(udp_ip, token, udp_port, udp_port_osc_message) 210 | else: 211 | raise RuntimeError(f"Reading Parameters Failed. token: {token}, server_port: {udp_port}, osc_port: {udp_port_osc_message}") 212 | 213 | def _debug(*args): 214 | if __debug: print(args) 215 | 216 | 217 | if __name__ == '__main__': 218 | use_synth(SAW) 219 | play(C5, amp=2, pan=-1) 220 | -------------------------------------------------------------------------------- /psonic/samples/__init__.py: -------------------------------------------------------------------------------- 1 | class Sample(object): 2 | """Sample""" 3 | 4 | def __init__(self, name, duration): 5 | self.name = name 6 | self.duration = duration 7 | -------------------------------------------------------------------------------- /psonic/samples/ambient.py: -------------------------------------------------------------------------------- 1 | """Ambient sounds""" 2 | 3 | from . import Sample 4 | 5 | 6 | AMBI_CHOIR = Sample('ambi_choir', 1.5715419501133787) 7 | AMBI_DARK_WOOSH = Sample('ambi_dark_woosh', 3.7021315192743764) 8 | AMBI_DRONE = Sample('ambi_drone', 4.40843537414966) 9 | AMBI_GLASS_HUM = Sample('ambi_glass_hum', 10.0) 10 | AMBI_GLASS_RUB = Sample('ambi_glass_rub', 3.1493650793650794) 11 | AMBI_HAUNTED_HUM = Sample('ambi_haunted_hum', 9.78156462585034) 12 | AMBI_LUNAR_LAND = Sample('ambi_lunar_land', 7.394240362811791) 13 | AMBI_PIANO = Sample('ambi_piano', 2.811746031746032) 14 | AMBI_SOFT_BUZZ = Sample('ambi_soft_buzz', 0.7821541950113379) 15 | AMBI_SWOOSH = Sample('ambi_swoosh', 1.8484580498866212) 16 | -------------------------------------------------------------------------------- /psonic/samples/bass.py: -------------------------------------------------------------------------------- 1 | """Bass sounds""" 2 | 3 | from . import Sample 4 | 5 | 6 | BASS_DNB_F = Sample('bass_dnb_f', 0.8705442176870748) 7 | BASS_DROP_C = Sample('bass_drop_c', 2.3589115646258505) 8 | BASS_HARD_C = Sample('bass_hard_c', 1.5) 9 | BASS_HIT_C = Sample('bass_hit_c', 0.6092517006802721) 10 | BASS_THICK_C = Sample('bass_thick_c', 3.9680725623582767) 11 | BASS_VOXY_C = Sample('bass_voxy_c', 6.23469387755102) 12 | BASS_VOXY_HIT_C = Sample('bass_voxy_hit_c', 0.457437641723356) 13 | BASS_WOODSY_C = Sample('bass_woodsy_c', 3.252267573696145) 14 | -------------------------------------------------------------------------------- /psonic/samples/drums.py: -------------------------------------------------------------------------------- 1 | """Drum sounds""" 2 | 3 | from . import Sample 4 | 5 | 6 | ## Drum Sounds 7 | DRUM_BASS_HARD = Sample('drum_bass_hard', 0.6951927437641723) 8 | DRUM_BASS_SOFT = Sample('drum_bass_soft', 0.5624489795918367) 9 | DRUM_COWBELL = Sample('drum_cowbell', 0.34988662131519277) 10 | DRUM_CYMBAL_CLOSED = Sample('drum_cymbal_closed', 0.2069387755102041) 11 | DRUM_CYMBAL_HARD = Sample('drum_cymbal_hard', 1.6388208616780044) 12 | DRUM_CYMBAL_OPEN = Sample('drum_cymbal_open', 1.8043764172335601) 13 | DRUM_CYMBAL_PEDAL = Sample('drum_cymbal_pedal', 0.2721995464852608) 14 | DRUM_CYMBAL_SOFT = Sample('drum_cymbal_soft', 0.9497278911564626) 15 | DRUM_HEAVY_KICK = Sample('drum_heavy_kick', 0.2701360544217687) 16 | DRUM_ROLL = Sample('drum_roll', 6.241678004535148) 17 | DRUM_SNARE_HARD = Sample('drum_snare_hard', 0.44492063492063494) 18 | DRUM_SNARE_SOFT = Sample('drum_snare_soft', 0.3147845804988662) 19 | DRUM_SPLASH_HARD = Sample('drum_splash_hard', 2.5961904761904764) 20 | DRUM_SPLASH_SOFT = Sample('drum_splash_soft', 1.857981859410431) 21 | DRUM_TOM_HI_HARD = Sample('drum_tom_hi_hard', 0.7376643990929705) 22 | DRUM_TOM_HI_SOFT = Sample('drum_tom_hi_soft', 0.6290249433106576) 23 | DRUM_TOM_LO_HARD = Sample('drum_tom_lo_hard', 1.0002267573696144) 24 | DRUM_TOM_LO_SOFT = Sample('drum_tom_lo_soft', 0.8634013605442177) 25 | DRUM_TOM_MID_HARD = Sample('drum_tom_mid_hard', 0.7342176870748299) 26 | DRUM_TOM_MID_SOFT = Sample('drum_tom_mid_soft', 0.6721315192743764) 27 | 28 | ## Snare Drums Sounds 29 | SN_DOLF = Sample('sn_dolf', 0.37759637188208617) 30 | SN_DUB = Sample('sn_dub', 0.2781179138321995) 31 | SN_ZOME = Sample('sn_zome', 0.4787528344671202) 32 | 33 | ## Bass Drums Sounds 34 | BD_808 = Sample('bd_808', 0.5597505668934241) 35 | BD_ADA = Sample('bd_ada', 0.10179138321995465) 36 | BD_BOOM = Sample('bd_boom', 1.7142857142857142) 37 | BD_FAT = Sample('bd_fat', 0.23219954648526078) 38 | BD_GAS = Sample('bd_gas', 0.4471428571428571) 39 | BD_HAUS = Sample('bd_haus', 0.21993197278911564) 40 | BD_KLUB = Sample('bd_klub', 0.368843537414966) 41 | BD_PURE = Sample('bd_pure', 0.43324263038548755) 42 | BD_SONE = Sample('bd_sone', 0.4089115646258503) 43 | BD_TEK = Sample('bd_tek', 0.24024943310657595) 44 | BD_ZOME = Sample('bd_zome', 0.45972789115646256) 45 | BD_ZUM = Sample('bd_zum', 0.13158730158730159) 46 | -------------------------------------------------------------------------------- /psonic/samples/electric.py: -------------------------------------------------------------------------------- 1 | """Electric Sounds""" 2 | 3 | from . import Sample 4 | 5 | 6 | ELEC_BEEP = Sample('elec_beep', 0.10578231292517007) 7 | ELEC_BELL = Sample('elec_bell', 0.27825396825396825) 8 | ELEC_BLIP = Sample('elec_blip', 0.15816326530612246) 9 | ELEC_BLIP2 = Sample('elec_blip2', 0.1840816326530612) 10 | ELEC_BLUP = Sample('elec_blup', 0.6632199546485261) 11 | ELEC_BONG = Sample('elec_bong', 0.3464852607709751) 12 | ELEC_CHIME = Sample('elec_chime', 2.2775510204081635) 13 | ELEC_CYMBAL = Sample('elec_cymbal', 0.563219954648526) 14 | ELEC_FILT_SNARE = Sample('elec_filt_snare', 1.5158503401360544) 15 | ELEC_FLIP = Sample('elec_flip', 0.07476190476190477) 16 | ELEC_FUZZ_TOM = Sample('elec_fuzz_tom', 0.37179138321995464) 17 | ELEC_HI_SNARE = Sample('elec_hi_snare', 0.19736961451247165) 18 | ELEC_HOLLOW_KICK = Sample('elec_hollow_kick', 0.15564625850340136) 19 | ELEC_LO_SNARE = Sample('elec_lo_snare', 0.6607936507936508) 20 | ELEC_MID_SNARE = Sample('elec_mid_snare', 0.7256235827664399) 21 | ELEC_PING = Sample('elec_ping', 0.21226757369614513) 22 | ELEC_PLIP = Sample('elec_plip', 0.19882086167800453) 23 | ELEC_POP = Sample('elec_pop', 0.08680272108843537) 24 | ELEC_SNARE = Sample('elec_snare', 0.3893197278911565) 25 | ELEC_SOFT_KICK = Sample('elec_soft_kick', 0.1364172335600907) 26 | ELEC_TICK = Sample('elec_tick', 0.01943310657596372) 27 | ELEC_TRIANGLE = Sample('elec_triangle', 0.22294784580498866) 28 | ELEC_TWANG = Sample('elec_twang', 0.6243083900226757) 29 | ELEC_TWIP = Sample('elec_twip', 0.10140589569160997) 30 | ELEC_WOOD = Sample('elec_wood', 0.47811791383219954) 31 | -------------------------------------------------------------------------------- /psonic/samples/guitars.py: -------------------------------------------------------------------------------- 1 | """Sounds featuring guitars""" 2 | 3 | from . import Sample 4 | 5 | 6 | GUIT_E_FIFTHS = Sample('guit_e_fifths', 5.971791383219955) 7 | GUIT_E_SLIDE = Sample('guit_e_slide', 4.325192743764172) 8 | GUIT_EM9 = Sample('guit_em9', 9.972063492063493) 9 | GUIT_HARMONICS = Sample('guit_harmonics', 3.5322675736961453) 10 | -------------------------------------------------------------------------------- /psonic/samples/loops/__init__.py: -------------------------------------------------------------------------------- 1 | """Sounds for Looping""" 2 | 3 | from psonic.samples import Sample 4 | 5 | 6 | LOOP_AMEN = Sample('loop_amen', 1.753310657596372) 7 | LOOP_AMEN_FULL = Sample('loop_amen_full', 6.857142857142857) 8 | LOOP_BREAKBEAT = Sample('loop_breakbeat', 1.9047619047619047) 9 | LOOP_COMPUS = Sample('loop_compus', 6.486485260770975) 10 | LOOP_GARZUL = Sample('loop_garzul', 8.0) 11 | LOOP_INDUSTRIAL = Sample('loop_industrial', 0.8837414965986394) 12 | LOOP_MIKA = Sample('loop_mika', 8.0) 13 | LOOP_SAFARI = Sample('loop_safari', 8.005079365079364) 14 | LOOP_TABLA = Sample('loop_tabla', 10.673990929705216) 15 | -------------------------------------------------------------------------------- /psonic/samples/misc.py: -------------------------------------------------------------------------------- 1 | """Miscellaneous Sounds""" 2 | 3 | from . import Sample 4 | 5 | 6 | MISC_BURP = Sample('misc_burp', 0.7932879818594104) 7 | MISC_CINEBOOM = Sample('misc_cineboom', 7.922675736961451) 8 | MISC_CROW = Sample('misc_crow', 0.48063492063492064) 9 | -------------------------------------------------------------------------------- /psonic/samples/percussions.py: -------------------------------------------------------------------------------- 1 | """Percurssive Sounds""" 2 | 3 | from . import Sample 4 | 5 | 6 | PERC_BELL = Sample('perc_bell', 6.719206349206349) 7 | PERC_SNAP = Sample('perc_snap', 0.30795918367346936) 8 | PERC_SNAP2 = Sample('perc_snap2', 0.17414965986394557) 9 | PERC_SWASH = Sample('perc_swash', 0.3195011337868481) 10 | PERC_TILL = Sample('perc_till', 2.665736961451247) 11 | -------------------------------------------------------------------------------- /psonic/samples/tabla.py: -------------------------------------------------------------------------------- 1 | """Tabla""" 2 | 3 | from . import Sample 4 | 5 | 6 | TABLA_DHEC = Sample('tabla_dhec', 0.3250793650793651) 7 | TABLA_GHE1 = Sample('tabla_ghe1', 0.5912244897959184) 8 | TABLA_GHE2 = Sample('tabla_ghe2', 2.6607256235827665) 9 | TABLA_GHE3 = Sample('tabla_ghe3', 2.3908163265306124) 10 | TABLA_GHE4 = Sample('tabla_ghe4', 0.7960997732426304) 11 | TABLA_GHE5 = Sample('tabla_ghe5', 3.560045351473923) 12 | TABLA_GHE6 = Sample('tabla_ghe6', 3.6011337868480724) 13 | TABLA_GHE7 = Sample('tabla_ghe7', 2.1512698412698414) 14 | TABLA_GHE8 = Sample('tabla_ghe8', 0.5817913832199546) 15 | TABLA_KE1 = Sample('tabla_ke1', 0.04342403628117914) 16 | TABLA_KE2 = Sample('tabla_ke2', 0.04403628117913832) 17 | TABLA_KE3 = Sample('tabla_ke3', 0.07571428571428572) 18 | TABLA_NA = Sample('tabla_na', 0.7198185941043084) 19 | TABLA_NA_O = Sample('tabla_na_o', 1.4889795918367348) 20 | TABLA_NA_S = Sample('tabla_na_s', 0.23582766439909297) 21 | TABLA_RE = Sample('tabla_re', 0.2815419501133787) 22 | TABLA_TAS1 = Sample('tabla_tas1', 1.1116553287981858) 23 | TABLA_TAS2 = Sample('tabla_tas2', 1.4338321995464853) 24 | TABLA_TAS3 = Sample('tabla_tas3', 1.2364625850340136) 25 | TABLA_TE1 = Sample('tabla_te1', 0.17777777777777778) 26 | TABLA_TE2 = Sample('tabla_te2', 0.33233560090702946) 27 | TABLA_TE_M = Sample('tabla_te_m', 0.28879818594104306) 28 | TABLA_TE_NE = Sample('tabla_te_ne', 0.15310657596371882) 29 | TABLA_TUN1 = Sample('tabla_tun1', 2.3394104308390022) 30 | TABLA_TUN2 = Sample('tabla_tun2', 2.693514739229025) 31 | TABLA_TUN3 = Sample('tabla_tun3', 2.0956009070294783) 32 | -------------------------------------------------------------------------------- /psonic/samples/vinyl.py: -------------------------------------------------------------------------------- 1 | """Vinyl""" 2 | from . import Sample 3 | 4 | 5 | VINYL_BACKSPIN = Sample('vinyl_backspin', 1.06124716553288) 6 | VINYL_HISS = Sample('vinyl_hiss', 8.0) 7 | VINYL_REWIND = Sample('vinyl_rewind', 2.6804761904761905) 8 | VINYL_SCRATCH = Sample('vinyl_scratch', 0.27383219954648524) 9 | -------------------------------------------------------------------------------- /psonic/scales.py: -------------------------------------------------------------------------------- 1 | """Scale Mode (from sonic pi)""" 2 | 3 | DIATONIC = 'diatonic' 4 | IONIAN = 'ionian' 5 | MAJOR = 'major' 6 | DORIAN = 'dorian' 7 | PHRYGIAN = 'phrygian' 8 | LYDIAN = 'lydian' 9 | MIXOLYDIAN = 'mixolydian' 10 | AEOLIAN = 'aeolian' 11 | MINOR = 'minor' 12 | LOCRIAN = 'locrian' 13 | HEX_MAJOR6 = 'hex_major6' 14 | HEX_DORIAN = 'hex_dorian' 15 | HEX_PHRYGIAN = 'hex_phrygian' 16 | HEX_MAJOR7 = 'hex_major7' 17 | HEX_SUS = 'hex_sus' 18 | HEX_AEOLIAN = 'hex_aeolian' 19 | MINOR_PENTATONIC = 'minor_pentatonic' 20 | YU = 'yu' 21 | MAJOR_PENTATONIC = 'major_pentatonic' 22 | GONG = 'gong' 23 | EGYPTIAN = 'egyptian' 24 | SHANG = 'shang' 25 | JIAO = 'jiao' 26 | ZHI = 'zhi' 27 | RITUSEN = 'ritusen' 28 | WHOLE_TONE = 'whole_tone' 29 | WHOLE = 'whole' 30 | CHROMATIC = 'chromatic' 31 | HARMONIC_MINOR = 'harmonic_minor' 32 | MELODIC_MINOR_ASC = 'melodic_minor_asc' 33 | HUNGARIAN_MINOR = 'hungarian_minor' 34 | OCTATONIC = 'octatonic' 35 | MESSIAEN1 = 'messiaen1' 36 | MESSIAEN2 = 'messiaen2' 37 | MESSIAEN3 = 'messiaen3' 38 | MESSIAEN4 = 'messiaen4' 39 | MESSIAEN5 = 'messiaen5' 40 | MESSIAEN6 = 'messiaen6' 41 | MESSIAEN7 = 'messiaen7' 42 | SUPER_LOCRIAN = 'super_locrian' 43 | HIRAJOSHI = 'hirajoshi' 44 | KUMOI = 'kumoi' 45 | NEAPLOLITAN_MAJOR = 'neapolitan_major' 46 | BARTOK = 'bartok' 47 | BHAIRAV = 'bhairav' 48 | LOCRIAN_MAJOR = 'locrian_major' 49 | AHIRBHAIRAV = 'ahirbhairav' 50 | ENIGMATIC = 'enigmatic' 51 | NEAPLOLITAN_MINOR = 'neapolitan_minor' 52 | PELOG = 'pelog' 53 | AUGMENTED2 = 'augmented2' 54 | SCRIABIN = 'scriabin' 55 | HARMONIC_MAJOR = 'harmonic_major' 56 | MELODIC_MINOR_DESC = 'melodic_minor_desc' 57 | ROMANIAN_MINOR = 'romanian_minor' 58 | HINDU = 'hindu' 59 | IWATO = 'iwato' 60 | MELODIC_MINOR = 'melodic_minor' 61 | DIMISHED2 = 'diminished2' 62 | MARVA = 'marva' 63 | MELODIC_MAJOR = 'melodic_major' 64 | INDIAN = 'indian' 65 | SPANISH = 'spanish' 66 | PROMETHEUS = 'prometheus' 67 | DIMISHED = 'diminished' 68 | TODI = 'todi' 69 | LEADING_WHOLE = 'leading_whole' 70 | AUGMENTED = 'augmented' 71 | PRUVI = 'purvi' 72 | CHINESE = 'chinese' 73 | LYDIAN_MINOR = 'lydian_minor' 74 | I = 'i' 75 | II = 'ii' 76 | III = 'iii' 77 | IV = 'iv' 78 | V = 'v' 79 | VI = 'vi' 80 | VII = 'vii' 81 | VIII = 'viii' 82 | -------------------------------------------------------------------------------- /psonic/synth_server.py: -------------------------------------------------------------------------------- 1 | """SonicPi synth server""" 2 | 3 | import time 4 | from pythonosc import osc_message_builder # osc support 5 | from pythonosc import udp_client 6 | from .synthesizers import BEEP 7 | 8 | ## Module attributes ## 9 | _current_synth = BEEP 10 | 11 | ## Module methodes ## 12 | def use_synth(synth): 13 | global _current_synth 14 | _current_synth = synth 15 | 16 | 17 | ## Compound classes ## 18 | class SonicPiCommon: 19 | 20 | UDP_IP = "127.0.0.1" 21 | 22 | def __init__(self): 23 | self.udp_ip = self.UDP_IP 24 | 25 | def set_parameter(self, udp_ip=""): 26 | if udp_ip == "": 27 | self.udp_ip = self.UDP_IP 28 | else: 29 | self.udp_ip = udp_ip 30 | 31 | def send(self, command): 32 | pass 33 | 34 | def sleep(self, duration): 35 | time.sleep(duration) 36 | 37 | def sample(self, command): 38 | self.send(command) 39 | 40 | ## Sonic Pi version 4.X: Ports can be found in 41 | # Windows: C:/Users//.sonic-pi/log/spider.log 42 | # Linux: ~/.sonic-pi/log/spider.log 43 | # Both the server_port and Token are required 44 | # Default OSC Cues Port is still 4560 45 | 46 | ## Connection classes ## 47 | class SonicPi(SonicPiCommon): 48 | """Communiction to Sonic Pi""" 49 | 50 | #UDP_PORT = 4557 51 | UDP_PORT = 51235 52 | 53 | #UDP_PORT_OSC_MESSAGE = 4559 54 | UDP_PORT_OSC_MESSAGE = 4560 55 | 56 | RUN_COMMAND = "/run-code" 57 | STOP_COMMAND = "/stop-all-jobs" 58 | START_RECORDING_COMMAND = "/start-recording" 59 | STOP_RECORDING_COMMAND = "/stop-recording" 60 | SAVE_RECORDING_COMMAND = "/save-recording" 61 | 62 | def __init__(self): 63 | super().__init__() 64 | self.udp_port = self.UDP_PORT 65 | self.udp_port_osc_message = self.UDP_PORT_OSC_MESSAGE 66 | self.token=None 67 | 68 | self._init_client() 69 | 70 | def _init_client(self): 71 | self.client = udp_client.UDPClient( 72 | self.udp_ip, 73 | self.udp_port 74 | ) 75 | self.client_for_messages = udp_client.UDPClient( 76 | self.udp_ip, 77 | self.udp_port_osc_message 78 | ) 79 | 80 | def set_parameter(self, udp_ip = "", token = "", udp_port=-1, udp_port_osc_message=-1): 81 | super().set_parameter(udp_ip) 82 | if udp_port == -1: udp_port = self.UDP_PORT 83 | self.udp_port = udp_port 84 | self.token=token 85 | if udp_port_osc_message == -1: udp_port_osc_message = self.UDP_PORT_OSC_MESSAGE 86 | self.udp_port_osc_message = udp_port_osc_message 87 | 88 | self._init_client() 89 | 90 | def play(self, command): 91 | command = f'use_synth :{_current_synth.name}\n{command}' 92 | self.send(command) 93 | 94 | def synth(self, command): 95 | self.send(command) 96 | 97 | def run(self, command): 98 | self.send_command(SonicPi.RUN_COMMAND, command) 99 | 100 | def start_recording(self): 101 | self.send_command(SonicPi.START_RECORDING_COMMAND) 102 | 103 | def stop_recording(self): 104 | self.send_command(SonicPi.START_RECORDING_COMMAND) 105 | 106 | def save_recording(self, name): 107 | self.send_command(SonicPi.SAVE_RECORDING_COMMAND,name) 108 | 109 | 110 | def send(self,command): 111 | self.run(command) 112 | 113 | def stop(self): 114 | self.send_command(SonicPi.STOP_COMMAND) 115 | 116 | def test_connection(self): 117 | # OSC::Server.new(PORT) 118 | # abort("ERROR: Sonic Pi is not listening on #{PORT} - is it running?") 119 | pass 120 | 121 | def send_command(self, address, argument=''): 122 | msg = osc_message_builder.OscMessageBuilder(address=address) 123 | if self.token is None: 124 | raise RuntimeError("No token specified, please set token from file or manually before sending a command") 125 | msg.add_arg(self.token) 126 | if argument != "": 127 | msg.add_arg(argument) 128 | msg = msg.build() 129 | self.client.send(msg) 130 | 131 | def send_message(self, message, *parameters): 132 | msg = osc_message_builder.OscMessageBuilder(message) 133 | for p in parameters: 134 | msg.add_arg(p) 135 | msg = msg.build() 136 | self.client_for_messages.send(msg) 137 | 138 | class SonicPiNew(SonicPiCommon): 139 | """Communiction to Sonic Pi""" 140 | 141 | UDP_PORT = 4559 142 | 143 | def __init__(self): 144 | self.client = udp_client.UDPClient( 145 | self.UDP_IP, 146 | self.UDP_PORT 147 | ) 148 | self.commandServer = SonicPi() 149 | # x= 'live_loop :py do\n nv=sync "/SENDOSC"\n puts nv\n eval(nv[0])\nend' 150 | # self.commandServer.run(x) 151 | 152 | def set_OSC_receiver(self, source): 153 | self.commandServer.run(source) 154 | 155 | def send(self, address, *message): 156 | msg = osc_message_builder.OscMessageBuilder(address) 157 | for m in message: 158 | msg.add_arg(m) 159 | msg = msg.build() 160 | self.client.send(msg) 161 | 162 | def play(self, command): 163 | self.send(command) 164 | -------------------------------------------------------------------------------- /psonic/synthesizers.py: -------------------------------------------------------------------------------- 1 | """Synthesizer""" 2 | 3 | class Synth: 4 | """Synthesizer""" 5 | 6 | def __init__(self, name): 7 | self.name = name 8 | 9 | 10 | DULL_BELL = Synth('dull_bell') 11 | PRETTY_BELL = Synth('pretty_bell') 12 | SINE = Synth('sine') 13 | SQUARE = Synth('square') 14 | PULSE = Synth('pulse') 15 | SUBPULSE = Synth('subpulse') 16 | DTRI = Synth('dtri') 17 | DPULSE = Synth('dpulse') 18 | FM = Synth('fm') 19 | MOD_FM = Synth('mod_fm') 20 | MOD_SAW = Synth('mod_saw') 21 | MOD_DSAW = Synth('mod_dsaw') 22 | MOD_SINE = Synth('mod_sine') 23 | MOD_TRI = Synth('mod_tri') 24 | MOD_PULSE = Synth('mod_pulse') 25 | SUPERSAW = Synth('supersaw') 26 | HOOVER = Synth('hoover') 27 | SYNTH_VIOLIN = Synth('synth_violin') 28 | PLUCK = Synth('pluck') 29 | PIANO = Synth('piano') 30 | GROWL = Synth('growl') 31 | DARK_AMBIENCE = Synth('dark_ambience') 32 | DARK_SEA_HORN = Synth('dark_sea_horn') 33 | HOLLOW = Synth('hollow') 34 | ZAWA = Synth('zawa') 35 | NOISE = Synth('noise') 36 | GNOISE = Synth('gnoise') 37 | BNOISE = Synth('bnoise') 38 | CNOISE = Synth('cnoise') 39 | DSAW = Synth('dsaw') 40 | TB303 = Synth('tb303') 41 | BLADE = Synth('blade') 42 | PROPHET = Synth('prophet') 43 | SAW = Synth('saw') 44 | BEEP = Synth('beep') 45 | TRI = Synth('tri') 46 | CHIPLEAD = Synth('chiplead') # Sonic Pi 2.10 47 | CHIPBASS = Synth('chipbass') 48 | CHIPNOISE = Synth('chipnoise') 49 | TECHSAWS = Synth('tech_saws') # Sonic Pi 2.11 50 | SOUND_IN = Synth('sound_in') 51 | SOUND_IN_STEREO = Synth('sound_in_stereo') 52 | -------------------------------------------------------------------------------- /psonic_example.py: -------------------------------------------------------------------------------- 1 | from psonic import * 2 | 3 | set_server_parameter_from_log("127.0.0.1") 4 | run("play 60") 5 | 6 | 7 | -------------------------------------------------------------------------------- /python-sonic.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# python-sonic - Programming Music with Python, Sonic Pi or Supercollider" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "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). \n", 15 | "\n", 16 | "At the moment Python-Sonic works with Sonic Pi. It is planned, that it will work with Supercollider, too.\n", 17 | "\n", 18 | "If you like it, use it. If you have some suggestions, tell me (gkvoelkl@nelson-games.de)." 19 | ] 20 | }, 21 | { 22 | "cell_type": "markdown", 23 | "metadata": {}, 24 | "source": [ 25 | "## Installation" 26 | ] 27 | }, 28 | { 29 | "cell_type": "markdown", 30 | "metadata": {}, 31 | "source": [ 32 | "* First you need Python 3 (https://www.python.org, ) - Python 3.5 should work, because it's the development environment\n", 33 | "* Then Sonic Pi (https://sonic-pi.net) - That makes the sound\n", 34 | "* Modul python-osc (https://pypi.python.org/pypi/python-osc) - Connection between Python and Sonic Pi Server\n", 35 | "* And this modul python-sonic - simply copy the source\n", 36 | "\n", 37 | "Or try" 38 | ] 39 | }, 40 | { 41 | "cell_type": "raw", 42 | "metadata": {}, 43 | "source": [ 44 | "$ pip install python-sonic" 45 | ] 46 | }, 47 | { 48 | "cell_type": "markdown", 49 | "metadata": {}, 50 | "source": [ 51 | "That should work." 52 | ] 53 | }, 54 | { 55 | "cell_type": "markdown", 56 | "metadata": {}, 57 | "source": [ 58 | "## Limitations" 59 | ] 60 | }, 61 | { 62 | "cell_type": "markdown", 63 | "metadata": {}, 64 | "source": [ 65 | "* You have to start _Sonic Pi_ first before you can use it with python-sonic\n", 66 | "* Only the notes from C2 to C6" 67 | ] 68 | }, 69 | { 70 | "cell_type": "markdown", 71 | "metadata": {}, 72 | "source": [ 73 | "## Changelog" 74 | ] 75 | }, 76 | { 77 | "cell_type": "markdown", 78 | "metadata": {}, 79 | "source": [ 80 | "|Version | |\n", 81 | "|--------------|------------------------------------------------------------------------------------------|\n", 82 | "| 0.2.0 | Some changes for Sonic Pi 2.11. Simpler multi-threading with decorator *@in_thread*. Messaging with *cue* and *sync*. |\n", 83 | "| 0.3.0 | OSC Communication | \n", 84 | "| 0.3.1. | Update, sort and duration of samples |\n", 85 | "| 0.3.2. | Restructured |\n", 86 | "| 0.4.0 | Changes communication ports and recording |" 87 | ] 88 | }, 89 | { 90 | "cell_type": "markdown", 91 | "metadata": {}, 92 | "source": [ 93 | "## Communication" 94 | ] 95 | }, 96 | { 97 | "cell_type": "markdown", 98 | "metadata": {}, 99 | "source": [ 100 | "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", 101 | "For older *Sonic Pi* Version you have to set the ports explicitly" 102 | ] 103 | }, 104 | { 105 | "cell_type": "raw", 106 | "metadata": {}, 107 | "source": [ 108 | "from psonic import *\n", 109 | "set_server_parameters('127.0.0.1',4557,4559)" 110 | ] 111 | }, 112 | { 113 | "cell_type": "markdown", 114 | "metadata": {}, 115 | "source": [ 116 | "## Examples" 117 | ] 118 | }, 119 | { 120 | "cell_type": "markdown", 121 | "metadata": {}, 122 | "source": [ 123 | "Many of the examples are inspired from the help menu in *Sonic Pi*." 124 | ] 125 | }, 126 | { 127 | "cell_type": "code", 128 | "execution_count": 1, 129 | "metadata": {}, 130 | "outputs": [], 131 | "source": [ 132 | "from psonic import *" 133 | ] 134 | }, 135 | { 136 | "cell_type": "markdown", 137 | "metadata": {}, 138 | "source": [ 139 | "The first sound" 140 | ] 141 | }, 142 | { 143 | "cell_type": "code", 144 | "execution_count": 2, 145 | "metadata": {}, 146 | "outputs": [], 147 | "source": [ 148 | "play(70) #play MIDI note 70" 149 | ] 150 | }, 151 | { 152 | "cell_type": "markdown", 153 | "metadata": {}, 154 | "source": [ 155 | "Some more notes" 156 | ] 157 | }, 158 | { 159 | "cell_type": "code", 160 | "execution_count": 3, 161 | "metadata": {}, 162 | "outputs": [], 163 | "source": [ 164 | "play(72)\n", 165 | "sleep(1)\n", 166 | "play(75)\n", 167 | "sleep(1)\n", 168 | "play(79) " 169 | ] 170 | }, 171 | { 172 | "cell_type": "markdown", 173 | "metadata": {}, 174 | "source": [ 175 | "In more tratitional music notation" 176 | ] 177 | }, 178 | { 179 | "cell_type": "code", 180 | "execution_count": 4, 181 | "metadata": {}, 182 | "outputs": [], 183 | "source": [ 184 | "play(C5)\n", 185 | "sleep(0.5)\n", 186 | "play(D5)\n", 187 | "sleep(0.5)\n", 188 | "play(G5) " 189 | ] 190 | }, 191 | { 192 | "cell_type": "markdown", 193 | "metadata": {}, 194 | "source": [ 195 | "Play sharp notes like *F#* or dimished ones like *Eb*" 196 | ] 197 | }, 198 | { 199 | "cell_type": "code", 200 | "execution_count": 5, 201 | "metadata": {}, 202 | "outputs": [], 203 | "source": [ 204 | "play(Fs5)\n", 205 | "sleep(0.5)\n", 206 | "play(Eb5)" 207 | ] 208 | }, 209 | { 210 | "cell_type": "markdown", 211 | "metadata": {}, 212 | "source": [ 213 | "Play louder (parameter amp) or from a different direction (parameter pan)" 214 | ] 215 | }, 216 | { 217 | "cell_type": "code", 218 | "execution_count": 6, 219 | "metadata": {}, 220 | "outputs": [], 221 | "source": [ 222 | "play(72,amp=2)\n", 223 | "sleep(0.5)\n", 224 | "play(74,pan=-1) #left" 225 | ] 226 | }, 227 | { 228 | "cell_type": "markdown", 229 | "metadata": {}, 230 | "source": [ 231 | "Different synthesizer sounds" 232 | ] 233 | }, 234 | { 235 | "cell_type": "code", 236 | "execution_count": 7, 237 | "metadata": {}, 238 | "outputs": [], 239 | "source": [ 240 | "use_synth(SAW)\n", 241 | "play(38)\n", 242 | "sleep(0.25)\n", 243 | "play(50)\n", 244 | "sleep(0.5)\n", 245 | "use_synth(PROPHET)\n", 246 | "play(57)\n", 247 | "sleep(0.25)" 248 | ] 249 | }, 250 | { 251 | "cell_type": "markdown", 252 | "metadata": {}, 253 | "source": [ 254 | "ADSR *(Attack, Decay, Sustain and Release)* Envelope" 255 | ] 256 | }, 257 | { 258 | "cell_type": "code", 259 | "execution_count": 8, 260 | "metadata": {}, 261 | "outputs": [], 262 | "source": [ 263 | "play (60, attack=0.5, decay=1, sustain_level=0.4, sustain=2, release=0.5) \n", 264 | "sleep(4)" 265 | ] 266 | }, 267 | { 268 | "cell_type": "markdown", 269 | "metadata": {}, 270 | "source": [ 271 | "Play some samples" 272 | ] 273 | }, 274 | { 275 | "cell_type": "code", 276 | "execution_count": 9, 277 | "metadata": {}, 278 | "outputs": [], 279 | "source": [ 280 | "sample(AMBI_LUNAR_LAND, amp=0.5)" 281 | ] 282 | }, 283 | { 284 | "cell_type": "code", 285 | "execution_count": 10, 286 | "metadata": {}, 287 | "outputs": [], 288 | "source": [ 289 | "sample(LOOP_AMEN,pan=-1)\n", 290 | "sleep(0.877)\n", 291 | "sample(LOOP_AMEN,pan=1)" 292 | ] 293 | }, 294 | { 295 | "cell_type": "code", 296 | "execution_count": 11, 297 | "metadata": {}, 298 | "outputs": [], 299 | "source": [ 300 | "sample(LOOP_AMEN,rate=0.5)" 301 | ] 302 | }, 303 | { 304 | "cell_type": "code", 305 | "execution_count": 12, 306 | "metadata": {}, 307 | "outputs": [], 308 | "source": [ 309 | "sample(LOOP_AMEN,rate=1.5)" 310 | ] 311 | }, 312 | { 313 | "cell_type": "code", 314 | "execution_count": 13, 315 | "metadata": {}, 316 | "outputs": [], 317 | "source": [ 318 | "sample(LOOP_AMEN,rate=-1)#back" 319 | ] 320 | }, 321 | { 322 | "cell_type": "code", 323 | "execution_count": 14, 324 | "metadata": {}, 325 | "outputs": [], 326 | "source": [ 327 | "sample(DRUM_CYMBAL_OPEN,attack=0.01,sustain=0.3,release=0.1)" 328 | ] 329 | }, 330 | { 331 | "cell_type": "code", 332 | "execution_count": 15, 333 | "metadata": {}, 334 | "outputs": [], 335 | "source": [ 336 | "sample(LOOP_AMEN,start=0.5,finish=0.8,rate=-0.2,attack=0.3,release=1)" 337 | ] 338 | }, 339 | { 340 | "cell_type": "markdown", 341 | "metadata": {}, 342 | "source": [ 343 | "Play some random notes" 344 | ] 345 | }, 346 | { 347 | "cell_type": "code", 348 | "execution_count": 16, 349 | "metadata": {}, 350 | "outputs": [], 351 | "source": [ 352 | "import random\n", 353 | "\n", 354 | "for i in range(5):\n", 355 | " play(random.randrange(50, 100))\n", 356 | " sleep(0.5)" 357 | ] 358 | }, 359 | { 360 | "cell_type": "code", 361 | "execution_count": 17, 362 | "metadata": {}, 363 | "outputs": [], 364 | "source": [ 365 | "for i in range(3):\n", 366 | " play(random.choice([C5,E5,G5]))\n", 367 | " sleep(1)" 368 | ] 369 | }, 370 | { 371 | "cell_type": "markdown", 372 | "metadata": {}, 373 | "source": [ 374 | "Sample slicing" 375 | ] 376 | }, 377 | { 378 | "cell_type": "code", 379 | "execution_count": 18, 380 | "metadata": {}, 381 | "outputs": [], 382 | "source": [ 383 | "from psonic import *\n", 384 | "\n", 385 | "number_of_pieces = 8\n", 386 | "\n", 387 | "for i in range(16):\n", 388 | " s = random.randrange(0,number_of_pieces)/number_of_pieces #sample starts at 0.0 and finishes at 1.0\n", 389 | " f = s + (1.0/number_of_pieces)\n", 390 | " sample(LOOP_AMEN,beat_stretch=2,start=s,finish=f)\n", 391 | " sleep(2.0/number_of_pieces)" 392 | ] 393 | }, 394 | { 395 | "cell_type": "markdown", 396 | "metadata": {}, 397 | "source": [ 398 | "An infinite loop and if" 399 | ] 400 | }, 401 | { 402 | "cell_type": "code", 403 | "execution_count": 18, 404 | "metadata": {}, 405 | "outputs": [ 406 | { 407 | "ename": "KeyboardInterrupt", 408 | "evalue": "", 409 | "output_type": "error", 410 | "traceback": [ 411 | "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", 412 | "\u001b[1;31mKeyboardInterrupt\u001b[0m Traceback (most recent call last)", 413 | "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m()\u001b[0m\n\u001b[0;32m 5\u001b[0m \u001b[1;32melse\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 6\u001b[0m \u001b[0msample\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mDRUM_CYMBAL_CLOSED\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 7\u001b[1;33m \u001b[0msleep\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;36m0.25\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", 414 | "\u001b[1;32m/mnt/jupyter/python-sonic/psonic.py\u001b[0m in \u001b[0;36msleep\u001b[1;34m(duration)\u001b[0m\n\u001b[0;32m 587\u001b[0m \u001b[1;33m:\u001b[0m\u001b[1;32mreturn\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 588\u001b[0m \"\"\"\n\u001b[1;32m--> 589\u001b[1;33m \u001b[0mtime\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0msleep\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mduration\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 590\u001b[0m \u001b[0m_debug\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m'sleep'\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mduration\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 591\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n", 415 | "\u001b[1;31mKeyboardInterrupt\u001b[0m: " 416 | ] 417 | } 418 | ], 419 | "source": [ 420 | "while True:\n", 421 | " if one_in(2):\n", 422 | " sample(DRUM_HEAVY_KICK)\n", 423 | " sleep(0.5)\n", 424 | " else:\n", 425 | " sample(DRUM_CYMBAL_CLOSED)\n", 426 | " sleep(0.25)" 427 | ] 428 | }, 429 | { 430 | "cell_type": "markdown", 431 | "metadata": {}, 432 | "source": [ 433 | "If you want to hear more than one sound at a time, use Threads." 434 | ] 435 | }, 436 | { 437 | "cell_type": "code", 438 | "execution_count": 19, 439 | "metadata": {}, 440 | "outputs": [ 441 | { 442 | "ename": "KeyboardInterrupt", 443 | "evalue": "", 444 | "output_type": "error", 445 | "traceback": [ 446 | "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", 447 | "\u001b[0;31mKeyboardInterrupt\u001b[0m Traceback (most recent call last)", 448 | "\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", 449 | "\u001b[0;31mKeyboardInterrupt\u001b[0m: " 450 | ] 451 | } 452 | ], 453 | "source": [ 454 | "import random\n", 455 | "from psonic import *\n", 456 | "from threading import Thread\n", 457 | "\n", 458 | "def bass_sound():\n", 459 | " c = chord(E3, MAJOR7)\n", 460 | " while True:\n", 461 | " use_synth(PROPHET)\n", 462 | " play(random.choice(c), release=0.6)\n", 463 | " sleep(0.5)\n", 464 | "\n", 465 | "def snare_sound():\n", 466 | " while True:\n", 467 | " sample(ELEC_SNARE)\n", 468 | " sleep(1)\n", 469 | "\n", 470 | "bass_thread = Thread(target=bass_sound)\n", 471 | "snare_thread = Thread(target=snare_sound)\n", 472 | "\n", 473 | "bass_thread.start()\n", 474 | "snare_thread.start()\n", 475 | "\n", 476 | "while True:\n", 477 | " pass" 478 | ] 479 | }, 480 | { 481 | "cell_type": "markdown", 482 | "metadata": {}, 483 | "source": [ 484 | "Every function *bass_sound* and *snare_sound* have its own thread. Your can hear them running." 485 | ] 486 | }, 487 | { 488 | "cell_type": "code", 489 | "execution_count": 1, 490 | "metadata": {}, 491 | "outputs": [ 492 | { 493 | "name": "stdin", 494 | "output_type": "stream", 495 | "text": [ 496 | "Press Enter to continue... \n" 497 | ] 498 | }, 499 | { 500 | "data": { 501 | "text/plain": [ 502 | "''" 503 | ] 504 | }, 505 | "execution_count": 1, 506 | "metadata": {}, 507 | "output_type": "execute_result" 508 | } 509 | ], 510 | "source": [ 511 | "from psonic import *\n", 512 | "from threading import Thread, Condition\n", 513 | "from random import choice\n", 514 | "\n", 515 | "def random_riff(condition):\n", 516 | " use_synth(PROPHET)\n", 517 | " sc = scale(E3, MINOR)\n", 518 | " while True:\n", 519 | " s = random.choice([0.125,0.25,0.5])\n", 520 | " with condition:\n", 521 | " condition.wait() #Wait for message\n", 522 | " for i in range(8):\n", 523 | " r = random.choice([0.125, 0.25, 1, 2])\n", 524 | " n = random.choice(sc)\n", 525 | " co = random.randint(30,100)\n", 526 | " play(n, release = r, cutoff = co)\n", 527 | " sleep(s)\n", 528 | "\n", 529 | "def drums(condition):\n", 530 | " while True:\n", 531 | " with condition:\n", 532 | " condition.notifyAll() #Message to threads\n", 533 | " for i in range(16):\n", 534 | " r = random.randrange(1,10)\n", 535 | " sample(DRUM_BASS_HARD, rate=r)\n", 536 | " sleep(0.125)\n", 537 | "\n", 538 | "condition = Condition()\n", 539 | "random_riff_thread = Thread(name='consumer1', target=random_riff, args=(condition,))\n", 540 | "drums_thread = Thread(name='producer', target=drums, args=(condition,))\n", 541 | "\n", 542 | "random_riff_thread.start()\n", 543 | "drums_thread.start()\n", 544 | "\n", 545 | "input(\"Press Enter to continue...\")" 546 | ] 547 | }, 548 | { 549 | "cell_type": "markdown", 550 | "metadata": {}, 551 | "source": [ 552 | "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*." 553 | ] 554 | }, 555 | { 556 | "cell_type": "markdown", 557 | "metadata": {}, 558 | "source": [ 559 | "More simple with decorator __@in_thread__" 560 | ] 561 | }, 562 | { 563 | "cell_type": "code", 564 | "execution_count": null, 565 | "metadata": {}, 566 | "outputs": [ 567 | { 568 | "name": "stdin", 569 | "output_type": "stream", 570 | "text": [ 571 | "Press Enter to continue... \n" 572 | ] 573 | } 574 | ], 575 | "source": [ 576 | "from psonic import *\n", 577 | "from random import choice\n", 578 | "\n", 579 | "tick = Message()\n", 580 | "\n", 581 | "@in_thread\n", 582 | "def random_riff():\n", 583 | " use_synth(PROPHET)\n", 584 | " sc = scale(E3, MINOR)\n", 585 | " while True:\n", 586 | " s = random.choice([0.125,0.25,0.5])\n", 587 | " tick.sync()\n", 588 | " for i in range(8):\n", 589 | " r = random.choice([0.125, 0.25, 1, 2])\n", 590 | " n = random.choice(sc)\n", 591 | " co = random.randint(30,100)\n", 592 | " play(n, release = r, cutoff = co)\n", 593 | " sleep(s)\n", 594 | " \n", 595 | "@in_thread\n", 596 | "def drums():\n", 597 | " while True:\n", 598 | " tick.cue()\n", 599 | " for i in range(16):\n", 600 | " r = random.randrange(1,10)\n", 601 | " sample(DRUM_BASS_HARD, rate=r)\n", 602 | " sleep(0.125)\n", 603 | "\n", 604 | "random_riff()\n", 605 | "drums()\n", 606 | "\n", 607 | "input(\"Press Enter to continue...\")" 608 | ] 609 | }, 610 | { 611 | "cell_type": "code", 612 | "execution_count": null, 613 | "metadata": {}, 614 | "outputs": [], 615 | "source": [ 616 | "from psonic import *\n", 617 | "\n", 618 | "tick = Message()\n", 619 | "\n", 620 | "@in_thread\n", 621 | "def metronom():\n", 622 | " while True:\n", 623 | " tick.cue()\n", 624 | " sleep(1)\n", 625 | " \n", 626 | "@in_thread\n", 627 | "def instrument():\n", 628 | " while True:\n", 629 | " tick.sync()\n", 630 | " sample(DRUM_HEAVY_KICK)\n", 631 | "\n", 632 | "metronom()\n", 633 | "instrument()\n", 634 | "\n", 635 | "while True:\n", 636 | " pass" 637 | ] 638 | }, 639 | { 640 | "cell_type": "markdown", 641 | "metadata": {}, 642 | "source": [ 643 | "Play a list of notes" 644 | ] 645 | }, 646 | { 647 | "cell_type": "code", 648 | "execution_count": 2, 649 | "metadata": {}, 650 | "outputs": [], 651 | "source": [ 652 | "from psonic import *\n", 653 | "\n", 654 | "play ([64, 67, 71], amp = 0.3) \n", 655 | "sleep(1)\n", 656 | "play ([E4, G4, B4])\n", 657 | "sleep(1)" 658 | ] 659 | }, 660 | { 661 | "cell_type": "markdown", 662 | "metadata": {}, 663 | "source": [ 664 | "Play chords" 665 | ] 666 | }, 667 | { 668 | "cell_type": "code", 669 | "execution_count": 2, 670 | "metadata": {}, 671 | "outputs": [], 672 | "source": [ 673 | "play(chord(E4, MINOR)) \n", 674 | "sleep(1)\n", 675 | "play(chord(E4, MAJOR))\n", 676 | "sleep(1)\n", 677 | "play(chord(E4, MINOR7))\n", 678 | "sleep(1)\n", 679 | "play(chord(E4, DOM7))\n", 680 | "sleep(1)" 681 | ] 682 | }, 683 | { 684 | "cell_type": "markdown", 685 | "metadata": {}, 686 | "source": [ 687 | "Play arpeggios" 688 | ] 689 | }, 690 | { 691 | "cell_type": "code", 692 | "execution_count": 3, 693 | "metadata": {}, 694 | "outputs": [], 695 | "source": [ 696 | "play_pattern( chord(E4, 'm7')) \n", 697 | "play_pattern_timed( chord(E4, 'm7'), 0.25) \n", 698 | "play_pattern_timed(chord(E4, 'dim'), [0.25, 0.5]) " 699 | ] 700 | }, 701 | { 702 | "cell_type": "markdown", 703 | "metadata": {}, 704 | "source": [ 705 | "Play scales" 706 | ] 707 | }, 708 | { 709 | "cell_type": "code", 710 | "execution_count": 4, 711 | "metadata": {}, 712 | "outputs": [], 713 | "source": [ 714 | "play_pattern_timed(scale(C3, MAJOR), 0.125, release = 0.1) \n", 715 | "play_pattern_timed(scale(C3, MAJOR, num_octaves = 2), 0.125, release = 0.1) \n", 716 | "play_pattern_timed(scale(C3, MAJOR_PENTATONIC, num_octaves = 2), 0.125, release = 0.1)" 717 | ] 718 | }, 719 | { 720 | "cell_type": "markdown", 721 | "metadata": {}, 722 | "source": [ 723 | "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." 724 | ] 725 | }, 726 | { 727 | "cell_type": "code", 728 | "execution_count": 5, 729 | "metadata": {}, 730 | "outputs": [ 731 | { 732 | "data": { 733 | "text/plain": [ 734 | "[48, 50, 52, 53, 55, 57, 59, 60]" 735 | ] 736 | }, 737 | "execution_count": 5, 738 | "metadata": {}, 739 | "output_type": "execute_result" 740 | } 741 | ], 742 | "source": [ 743 | "import random\n", 744 | "from psonic import *\n", 745 | "\n", 746 | "s = scale(C3, MAJOR)\n", 747 | "s" 748 | ] 749 | }, 750 | { 751 | "cell_type": "code", 752 | "execution_count": 6, 753 | "metadata": {}, 754 | "outputs": [], 755 | "source": [ 756 | "s.reverse()" 757 | ] 758 | }, 759 | { 760 | "cell_type": "code", 761 | "execution_count": 7, 762 | "metadata": {}, 763 | "outputs": [], 764 | "source": [ 765 | "\n", 766 | "play_pattern_timed(s, 0.125, release = 0.1)\n", 767 | "random.shuffle(s)\n", 768 | "play_pattern_timed(s, 0.125, release = 0.1)" 769 | ] 770 | }, 771 | { 772 | "cell_type": "markdown", 773 | "metadata": {}, 774 | "source": [ 775 | "### Live Loop" 776 | ] 777 | }, 778 | { 779 | "cell_type": "markdown", 780 | "metadata": {}, 781 | "source": [ 782 | "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." 783 | ] 784 | }, 785 | { 786 | "cell_type": "code", 787 | "execution_count": 5, 788 | "metadata": {}, 789 | "outputs": [ 790 | { 791 | "name": "stdout", 792 | "output_type": "stream", 793 | "text": [ 794 | "Press Enter to continue...Y\n" 795 | ] 796 | }, 797 | { 798 | "data": { 799 | "text/plain": [ 800 | "'Y'" 801 | ] 802 | }, 803 | "execution_count": 5, 804 | "metadata": {}, 805 | "output_type": "execute_result" 806 | } 807 | ], 808 | "source": [ 809 | "from psonic import *\n", 810 | "from threading import Thread\n", 811 | "\n", 812 | "def my_loop():\n", 813 | " play(60)\n", 814 | " sleep(1)\n", 815 | "\n", 816 | "def looper():\n", 817 | " while True:\n", 818 | " my_loop()\n", 819 | "\n", 820 | "looper_thread = Thread(name='looper', target=looper)\n", 821 | "\n", 822 | "looper_thread.start()\n", 823 | "\n", 824 | "input(\"Press Enter to continue...\")" 825 | ] 826 | }, 827 | { 828 | "cell_type": "markdown", 829 | "metadata": {}, 830 | "source": [ 831 | "Now change the function *my_loop* und you can hear it." 832 | ] 833 | }, 834 | { 835 | "cell_type": "code", 836 | "execution_count": 6, 837 | "metadata": { 838 | "collapsed": true, 839 | "jupyter": { 840 | "outputs_hidden": true 841 | } 842 | }, 843 | "outputs": [], 844 | "source": [ 845 | "def my_loop():\n", 846 | " use_synth(TB303)\n", 847 | " play (60, release= 0.3)\n", 848 | " sleep (0.25)" 849 | ] 850 | }, 851 | { 852 | "cell_type": "code", 853 | "execution_count": 7, 854 | "metadata": { 855 | "collapsed": true, 856 | "jupyter": { 857 | "outputs_hidden": true 858 | } 859 | }, 860 | "outputs": [], 861 | "source": [ 862 | "def my_loop():\n", 863 | " use_synth(TB303)\n", 864 | " play (chord(E3, MINOR), release= 0.3)\n", 865 | " sleep(0.5)" 866 | ] 867 | }, 868 | { 869 | "cell_type": "code", 870 | "execution_count": 8, 871 | "metadata": { 872 | "collapsed": true, 873 | "jupyter": { 874 | "outputs_hidden": true 875 | } 876 | }, 877 | "outputs": [], 878 | "source": [ 879 | "def my_loop():\n", 880 | " use_synth(TB303)\n", 881 | " sample(DRUM_BASS_HARD, rate = random.uniform(0.5, 2))\n", 882 | " play(random.choice(chord(E3, MINOR)), release= 0.2, cutoff=random.randrange(60, 130))\n", 883 | " sleep(0.25)" 884 | ] 885 | }, 886 | { 887 | "cell_type": "markdown", 888 | "metadata": {}, 889 | "source": [ 890 | "To stop the sound you have to end the kernel. In IPython with Kernel --> Restart" 891 | ] 892 | }, 893 | { 894 | "cell_type": "markdown", 895 | "metadata": {}, 896 | "source": [ 897 | "Now with two live loops which are synch." 898 | ] 899 | }, 900 | { 901 | "cell_type": "code", 902 | "execution_count": 10, 903 | "metadata": {}, 904 | "outputs": [ 905 | { 906 | "name": "stdout", 907 | "output_type": "stream", 908 | "text": [ 909 | "Press Enter to continue...y\n" 910 | ] 911 | }, 912 | { 913 | "data": { 914 | "text/plain": [ 915 | "'y'" 916 | ] 917 | }, 918 | "execution_count": 10, 919 | "metadata": {}, 920 | "output_type": "execute_result" 921 | } 922 | ], 923 | "source": [ 924 | "from psonic import *\n", 925 | "from threading import Thread, Condition\n", 926 | "from random import choice\n", 927 | "\n", 928 | "def loop_foo():\n", 929 | " play (E4, release = 0.5)\n", 930 | " sleep (0.5)\n", 931 | "\n", 932 | "\n", 933 | "def loop_bar():\n", 934 | " sample (DRUM_SNARE_SOFT)\n", 935 | " sleep (1)\n", 936 | " \n", 937 | "\n", 938 | "def live_loop_1(condition):\n", 939 | " while True:\n", 940 | " with condition:\n", 941 | " condition.notifyAll() #Message to threads\n", 942 | " loop_foo()\n", 943 | " \n", 944 | "def live_loop_2(condition):\n", 945 | " while True:\n", 946 | " with condition:\n", 947 | " condition.wait() #Wait for message\n", 948 | " loop_bar()\n", 949 | "\n", 950 | "condition = Condition()\n", 951 | "live_thread_1 = Thread(name='producer', target=live_loop_1, args=(condition,))\n", 952 | "live_thread_2 = Thread(name='consumer1', target=live_loop_2, args=(condition,))\n", 953 | "\n", 954 | "live_thread_1.start()\n", 955 | "live_thread_2.start()\n", 956 | "\n", 957 | "input(\"Press Enter to continue...\")" 958 | ] 959 | }, 960 | { 961 | "cell_type": "code", 962 | "execution_count": 11, 963 | "metadata": { 964 | "collapsed": true, 965 | "jupyter": { 966 | "outputs_hidden": true 967 | } 968 | }, 969 | "outputs": [], 970 | "source": [ 971 | "def loop_foo():\n", 972 | " play (A4, release = 0.5)\n", 973 | " sleep (0.5)" 974 | ] 975 | }, 976 | { 977 | "cell_type": "code", 978 | "execution_count": 14, 979 | "metadata": { 980 | "collapsed": true, 981 | "jupyter": { 982 | "outputs_hidden": true 983 | } 984 | }, 985 | "outputs": [], 986 | "source": [ 987 | "def loop_bar():\n", 988 | " sample (DRUM_HEAVY_KICK)\n", 989 | " sleep (0.125)" 990 | ] 991 | }, 992 | { 993 | "cell_type": "markdown", 994 | "metadata": {}, 995 | "source": [ 996 | "If would be nice if we can stop the loop with a simple command. With stop event it works." 997 | ] 998 | }, 999 | { 1000 | "cell_type": "code", 1001 | "execution_count": 1, 1002 | "metadata": {}, 1003 | "outputs": [ 1004 | { 1005 | "name": "stdout", 1006 | "output_type": "stream", 1007 | "text": [ 1008 | "Press Enter to continue...y\n" 1009 | ] 1010 | }, 1011 | { 1012 | "data": { 1013 | "text/plain": [ 1014 | "'y'" 1015 | ] 1016 | }, 1017 | "execution_count": 1, 1018 | "metadata": {}, 1019 | "output_type": "execute_result" 1020 | } 1021 | ], 1022 | "source": [ 1023 | "from psonic import *\n", 1024 | "from threading import Thread, Condition, Event\n", 1025 | "\n", 1026 | "def loop_foo():\n", 1027 | " play (E4, release = 0.5)\n", 1028 | " sleep (0.5)\n", 1029 | "\n", 1030 | "\n", 1031 | "def loop_bar():\n", 1032 | " sample (DRUM_SNARE_SOFT)\n", 1033 | " sleep (1)\n", 1034 | " \n", 1035 | "\n", 1036 | "def live_loop_1(condition,stop_event):\n", 1037 | " while not stop_event.is_set():\n", 1038 | " with condition:\n", 1039 | " condition.notifyAll() #Message to threads\n", 1040 | " loop_foo()\n", 1041 | " \n", 1042 | "def live_loop_2(condition,stop_event):\n", 1043 | " while not stop_event.is_set():\n", 1044 | " with condition:\n", 1045 | " condition.wait() #Wait for message\n", 1046 | " loop_bar()\n", 1047 | "\n", 1048 | "\n", 1049 | "\n", 1050 | "condition = Condition()\n", 1051 | "stop_event = Event()\n", 1052 | "live_thread_1 = Thread(name='producer', target=live_loop_1, args=(condition,stop_event))\n", 1053 | "live_thread_2 = Thread(name='consumer1', target=live_loop_2, args=(condition,stop_event))\n", 1054 | "\n", 1055 | "\n", 1056 | "live_thread_1.start()\n", 1057 | "live_thread_2.start()\n", 1058 | "\n", 1059 | "input(\"Press Enter to continue...\")" 1060 | ] 1061 | }, 1062 | { 1063 | "cell_type": "code", 1064 | "execution_count": 3, 1065 | "metadata": { 1066 | "collapsed": true, 1067 | "jupyter": { 1068 | "outputs_hidden": true 1069 | } 1070 | }, 1071 | "outputs": [], 1072 | "source": [ 1073 | "stop_event.set()" 1074 | ] 1075 | }, 1076 | { 1077 | "cell_type": "markdown", 1078 | "metadata": {}, 1079 | "source": [ 1080 | "More complex live loops" 1081 | ] 1082 | }, 1083 | { 1084 | "cell_type": "code", 1085 | "execution_count": 4, 1086 | "metadata": { 1087 | "collapsed": true, 1088 | "jupyter": { 1089 | "outputs_hidden": true 1090 | } 1091 | }, 1092 | "outputs": [], 1093 | "source": [ 1094 | "sc = Ring(scale(E3, MINOR_PENTATONIC))\n", 1095 | "\n", 1096 | "def loop_foo():\n", 1097 | " play (next(sc), release= 0.1)\n", 1098 | " sleep (0.125)\n", 1099 | "\n", 1100 | "sc2 = Ring(scale(E3,MINOR_PENTATONIC,num_octaves=2))\n", 1101 | " \n", 1102 | "def loop_bar():\n", 1103 | " use_synth(DSAW)\n", 1104 | " play (next(sc2), release= 0.25)\n", 1105 | " sleep (0.25)" 1106 | ] 1107 | }, 1108 | { 1109 | "cell_type": "markdown", 1110 | "metadata": {}, 1111 | "source": [ 1112 | "Now a simple structure with four live loops" 1113 | ] 1114 | }, 1115 | { 1116 | "cell_type": "code", 1117 | "execution_count": 11, 1118 | "metadata": {}, 1119 | "outputs": [ 1120 | { 1121 | "name": "stdout", 1122 | "output_type": "stream", 1123 | "text": [ 1124 | "Press Enter to continue...y\n" 1125 | ] 1126 | }, 1127 | { 1128 | "data": { 1129 | "text/plain": [ 1130 | "'y'" 1131 | ] 1132 | }, 1133 | "execution_count": 11, 1134 | "metadata": {}, 1135 | "output_type": "execute_result" 1136 | } 1137 | ], 1138 | "source": [ 1139 | "import random\n", 1140 | "from psonic import *\n", 1141 | "from threading import Thread, Condition, Event\n", 1142 | "\n", 1143 | "def live_1():\n", 1144 | " pass\n", 1145 | "\n", 1146 | "def live_2():\n", 1147 | " pass\n", 1148 | " \n", 1149 | "def live_3():\n", 1150 | " pass\n", 1151 | "\n", 1152 | "def live_4():\n", 1153 | " pass\n", 1154 | "\n", 1155 | "def live_loop_1(condition,stop_event):\n", 1156 | " while not stop_event.is_set():\n", 1157 | " with condition:\n", 1158 | " condition.notifyAll() #Message to threads\n", 1159 | " live_1()\n", 1160 | " \n", 1161 | "def live_loop_2(condition,stop_event):\n", 1162 | " while not stop_event.is_set():\n", 1163 | " with condition:\n", 1164 | " condition.wait() #Wait for message\n", 1165 | " live_2()\n", 1166 | "\n", 1167 | "def live_loop_3(condition,stop_event):\n", 1168 | " while not stop_event.is_set():\n", 1169 | " with condition:\n", 1170 | " condition.wait() #Wait for message\n", 1171 | " live_3()\n", 1172 | "\n", 1173 | "def live_loop_4(condition,stop_event):\n", 1174 | " while not stop_event.is_set():\n", 1175 | " with condition:\n", 1176 | " condition.wait() #Wait for message\n", 1177 | " live_4()\n", 1178 | " \n", 1179 | "condition = Condition()\n", 1180 | "stop_event = Event()\n", 1181 | "live_thread_1 = Thread(name='producer', target=live_loop_1, args=(condition,stop_event))\n", 1182 | "live_thread_2 = Thread(name='consumer1', target=live_loop_2, args=(condition,stop_event))\n", 1183 | "live_thread_3 = Thread(name='consumer2', target=live_loop_3, args=(condition,stop_event))\n", 1184 | "live_thread_4 = Thread(name='consumer3', target=live_loop_3, args=(condition,stop_event))\n", 1185 | "\n", 1186 | "live_thread_1.start()\n", 1187 | "live_thread_2.start()\n", 1188 | "live_thread_3.start()\n", 1189 | "live_thread_4.start()\n", 1190 | "\n", 1191 | "input(\"Press Enter to continue...\")" 1192 | ] 1193 | }, 1194 | { 1195 | "cell_type": "markdown", 1196 | "metadata": {}, 1197 | "source": [ 1198 | "After starting the loops you can change them" 1199 | ] 1200 | }, 1201 | { 1202 | "cell_type": "code", 1203 | "execution_count": 12, 1204 | "metadata": { 1205 | "collapsed": true, 1206 | "jupyter": { 1207 | "outputs_hidden": true 1208 | } 1209 | }, 1210 | "outputs": [], 1211 | "source": [ 1212 | "def live_1():\n", 1213 | " sample(BD_HAUS,amp=2)\n", 1214 | " sleep(0.5)\n", 1215 | " pass" 1216 | ] 1217 | }, 1218 | { 1219 | "cell_type": "code", 1220 | "execution_count": 7, 1221 | "metadata": { 1222 | "collapsed": true, 1223 | "jupyter": { 1224 | "outputs_hidden": true 1225 | } 1226 | }, 1227 | "outputs": [], 1228 | "source": [ 1229 | "def live_2():\n", 1230 | " #sample(AMBI_CHOIR, rate=0.4)\n", 1231 | " #sleep(1)\n", 1232 | " pass" 1233 | ] 1234 | }, 1235 | { 1236 | "cell_type": "code", 1237 | "execution_count": 14, 1238 | "metadata": { 1239 | "collapsed": true, 1240 | "jupyter": { 1241 | "outputs_hidden": true 1242 | } 1243 | }, 1244 | "outputs": [], 1245 | "source": [ 1246 | "def live_3():\n", 1247 | " use_synth(TB303)\n", 1248 | " play(E2, release=4,cutoff=120,cutoff_attack=1)\n", 1249 | " sleep(4)" 1250 | ] 1251 | }, 1252 | { 1253 | "cell_type": "code", 1254 | "execution_count": 13, 1255 | "metadata": { 1256 | "collapsed": true, 1257 | "jupyter": { 1258 | "outputs_hidden": true 1259 | } 1260 | }, 1261 | "outputs": [], 1262 | "source": [ 1263 | "def live_4():\n", 1264 | " notes = scale(E3, MINOR_PENTATONIC, num_octaves=2)\n", 1265 | " for i in range(8):\n", 1266 | " play(random.choice(notes),release=0.1,amp=1.5)\n", 1267 | " sleep(0.125)" 1268 | ] 1269 | }, 1270 | { 1271 | "cell_type": "markdown", 1272 | "metadata": {}, 1273 | "source": [ 1274 | "And stop." 1275 | ] 1276 | }, 1277 | { 1278 | "cell_type": "code", 1279 | "execution_count": 15, 1280 | "metadata": { 1281 | "collapsed": true, 1282 | "jupyter": { 1283 | "outputs_hidden": true 1284 | } 1285 | }, 1286 | "outputs": [], 1287 | "source": [ 1288 | "stop_event.set()" 1289 | ] 1290 | }, 1291 | { 1292 | "cell_type": "markdown", 1293 | "metadata": {}, 1294 | "source": [ 1295 | "### Creating Sound" 1296 | ] 1297 | }, 1298 | { 1299 | "cell_type": "code", 1300 | "execution_count": 4, 1301 | "metadata": { 1302 | "collapsed": true, 1303 | "jupyter": { 1304 | "outputs_hidden": true 1305 | }, 1306 | "scrolled": true 1307 | }, 1308 | "outputs": [], 1309 | "source": [ 1310 | "from psonic import *\n", 1311 | "\n", 1312 | "synth(SINE, note=D4)\n", 1313 | "synth(SQUARE, note=D4)\n", 1314 | "synth(TRI, note=D4, amp=0.4)" 1315 | ] 1316 | }, 1317 | { 1318 | "cell_type": "code", 1319 | "execution_count": 5, 1320 | "metadata": { 1321 | "collapsed": true, 1322 | "jupyter": { 1323 | "outputs_hidden": true 1324 | } 1325 | }, 1326 | "outputs": [], 1327 | "source": [ 1328 | "detune = 0.7\n", 1329 | "synth(SQUARE, note = E4)\n", 1330 | "synth(SQUARE, note = E4+detune)" 1331 | ] 1332 | }, 1333 | { 1334 | "cell_type": "code", 1335 | "execution_count": 12, 1336 | "metadata": { 1337 | "collapsed": true, 1338 | "jupyter": { 1339 | "outputs_hidden": true 1340 | } 1341 | }, 1342 | "outputs": [], 1343 | "source": [ 1344 | "detune=0.1 # Amplitude shaping\n", 1345 | "synth(SQUARE, note = E2, release = 2)\n", 1346 | "synth(SQUARE, note = E2+detune, amp = 2, release = 2)\n", 1347 | "synth(GNOISE, release = 2, amp = 1, cutoff = 60)\n", 1348 | "synth(GNOISE, release = 0.5, amp = 1, cutoff = 100)\n", 1349 | "synth(NOISE, release = 0.2, amp = 1, cutoff = 90)" 1350 | ] 1351 | }, 1352 | { 1353 | "cell_type": "markdown", 1354 | "metadata": {}, 1355 | "source": [ 1356 | "### Next Step" 1357 | ] 1358 | }, 1359 | { 1360 | "cell_type": "markdown", 1361 | "metadata": {}, 1362 | "source": [ 1363 | "Using FX *Not implemented yet*" 1364 | ] 1365 | }, 1366 | { 1367 | "cell_type": "code", 1368 | "execution_count": 2, 1369 | "metadata": { 1370 | "collapsed": true, 1371 | "jupyter": { 1372 | "outputs_hidden": true 1373 | } 1374 | }, 1375 | "outputs": [], 1376 | "source": [ 1377 | "from psonic import *\n", 1378 | "\n", 1379 | "with Fx(SLICER):\n", 1380 | " synth(PROPHET,note=E2,release=8,cutoff=80)\n", 1381 | " synth(PROPHET,note=E2+4,release=8,cutoff=80)" 1382 | ] 1383 | }, 1384 | { 1385 | "cell_type": "code", 1386 | "execution_count": 3, 1387 | "metadata": { 1388 | "collapsed": true, 1389 | "jupyter": { 1390 | "outputs_hidden": true 1391 | } 1392 | }, 1393 | "outputs": [], 1394 | "source": [ 1395 | "with Fx(SLICER, phase=0.125, probability=0.6,prob_pos=1):\n", 1396 | " synth(TB303, note=E2, cutoff_attack=8, release=8)\n", 1397 | " synth(TB303, note=E3, cutoff_attack=4, release=8)\n", 1398 | " synth(TB303, note=E4, cutoff_attack=2, release=8)" 1399 | ] 1400 | }, 1401 | { 1402 | "cell_type": "markdown", 1403 | "metadata": {}, 1404 | "source": [ 1405 | "## OSC Communication (Sonic Pi Ver. 3.x or better)" 1406 | ] 1407 | }, 1408 | { 1409 | "cell_type": "markdown", 1410 | "metadata": {}, 1411 | "source": [ 1412 | "In Sonic Pi version 3 or better you can work with messages." 1413 | ] 1414 | }, 1415 | { 1416 | "cell_type": "code", 1417 | "execution_count": 1, 1418 | "metadata": {}, 1419 | "outputs": [], 1420 | "source": [ 1421 | "from psonic import *" 1422 | ] 1423 | }, 1424 | { 1425 | "cell_type": "markdown", 1426 | "metadata": {}, 1427 | "source": [ 1428 | "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." 1429 | ] 1430 | }, 1431 | { 1432 | "cell_type": "code", 1433 | "execution_count": 2, 1434 | "metadata": {}, 1435 | "outputs": [], 1436 | "source": [ 1437 | "run(\"\"\"live_loop :foo do\n", 1438 | " use_real_time\n", 1439 | " a, b, c = sync \"/osc*/trigger/prophet\"\n", 1440 | " synth :prophet, note: a, cutoff: b, sustain: c\n", 1441 | "end \"\"\")" 1442 | ] 1443 | }, 1444 | { 1445 | "cell_type": "markdown", 1446 | "metadata": {}, 1447 | "source": [ 1448 | "Now send a message to Sonic Pi." 1449 | ] 1450 | }, 1451 | { 1452 | "cell_type": "code", 1453 | "execution_count": 3, 1454 | "metadata": {}, 1455 | "outputs": [], 1456 | "source": [ 1457 | "send_message('/trigger/prophet', 70, 100, 8)" 1458 | ] 1459 | }, 1460 | { 1461 | "cell_type": "code", 1462 | "execution_count": 4, 1463 | "metadata": {}, 1464 | "outputs": [], 1465 | "source": [ 1466 | "stop()" 1467 | ] 1468 | }, 1469 | { 1470 | "cell_type": "markdown", 1471 | "metadata": {}, 1472 | "source": [ 1473 | "## Recording" 1474 | ] 1475 | }, 1476 | { 1477 | "cell_type": "markdown", 1478 | "metadata": {}, 1479 | "source": [ 1480 | "With python-sonic you can record wave files." 1481 | ] 1482 | }, 1483 | { 1484 | "cell_type": "code", 1485 | "execution_count": 1, 1486 | "metadata": {}, 1487 | "outputs": [], 1488 | "source": [ 1489 | "from psonic import *" 1490 | ] 1491 | }, 1492 | { 1493 | "cell_type": "code", 1494 | "execution_count": 5, 1495 | "metadata": {}, 1496 | "outputs": [], 1497 | "source": [ 1498 | "# start recording\n", 1499 | "start_recording()\n", 1500 | "\n", 1501 | "play(chord(E4, MINOR)) \n", 1502 | "sleep(1)\n", 1503 | "play(chord(E4, MAJOR))\n", 1504 | "sleep(1)\n", 1505 | "play(chord(E4, MINOR7))\n", 1506 | "sleep(1)\n", 1507 | "play(chord(E4, DOM7))\n", 1508 | "sleep(1)" 1509 | ] 1510 | }, 1511 | { 1512 | "cell_type": "code", 1513 | "execution_count": 6, 1514 | "metadata": {}, 1515 | "outputs": [ 1516 | { 1517 | "data": { 1518 | "text/plain": [ 1519 | "" 1520 | ] 1521 | }, 1522 | "execution_count": 6, 1523 | "metadata": {}, 1524 | "output_type": "execute_result" 1525 | } 1526 | ], 1527 | "source": [ 1528 | "# stop recording\n", 1529 | "stop_recording" 1530 | ] 1531 | }, 1532 | { 1533 | "cell_type": "code", 1534 | "execution_count": 7, 1535 | "metadata": {}, 1536 | "outputs": [], 1537 | "source": [ 1538 | "# save file\n", 1539 | "save_recording('/Volumes/jupyter/python-sonic/test.wav')" 1540 | ] 1541 | }, 1542 | { 1543 | "cell_type": "markdown", 1544 | "metadata": {}, 1545 | "source": [ 1546 | "## More Examples" 1547 | ] 1548 | }, 1549 | { 1550 | "cell_type": "code", 1551 | "execution_count": 1, 1552 | "metadata": { 1553 | "collapsed": true, 1554 | "jupyter": { 1555 | "outputs_hidden": true 1556 | } 1557 | }, 1558 | "outputs": [], 1559 | "source": [ 1560 | "from psonic import *" 1561 | ] 1562 | }, 1563 | { 1564 | "cell_type": "code", 1565 | "execution_count": null, 1566 | "metadata": { 1567 | "collapsed": true, 1568 | "jupyter": { 1569 | "outputs_hidden": true 1570 | } 1571 | }, 1572 | "outputs": [], 1573 | "source": [ 1574 | "#Inspired by Steve Reich Clapping Music\n", 1575 | "\n", 1576 | "clapping = [1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0]\n", 1577 | "\n", 1578 | "for i in range(13):\n", 1579 | " for j in range(4):\n", 1580 | " for k in range(12): \n", 1581 | " if clapping[k] ==1 : sample(DRUM_SNARE_SOFT,pan=-0.5)\n", 1582 | " if clapping[(i+k)%12] == 1: sample(DRUM_HEAVY_KICK,pan=0.5)\n", 1583 | " sleep (0.25)" 1584 | ] 1585 | }, 1586 | { 1587 | "cell_type": "markdown", 1588 | "metadata": {}, 1589 | "source": [ 1590 | "## Projects that use Python-Sonic" 1591 | ] 1592 | }, 1593 | { 1594 | "cell_type": "markdown", 1595 | "metadata": {}, 1596 | "source": [ 1597 | "Raspberry Pi sonic-track.py a Sonic-pi Motion Track Demo https://github.com/pageauc/sonic-track" 1598 | ] 1599 | }, 1600 | { 1601 | "cell_type": "markdown", 1602 | "metadata": {}, 1603 | "source": [ 1604 | "## Sources" 1605 | ] 1606 | }, 1607 | { 1608 | "cell_type": "markdown", 1609 | "metadata": {}, 1610 | "source": [ 1611 | "Joe Armstrong: Connecting Erlang to the Sonic Pi http://joearms.github.io/2015/01/05/Connecting-Erlang-to-Sonic-Pi.html" 1612 | ] 1613 | }, 1614 | { 1615 | "cell_type": "markdown", 1616 | "metadata": {}, 1617 | "source": [ 1618 | "Joe Armstrong: Controlling Sound with OSC Messages http://joearms.github.io/2016/01/29/Controlling-Sound-with-OSC-Messages.html" 1619 | ] 1620 | }, 1621 | { 1622 | "cell_type": "markdown", 1623 | "metadata": {}, 1624 | "source": [ 1625 | ".." 1626 | ] 1627 | } 1628 | ], 1629 | "metadata": { 1630 | "anaconda-cloud": {}, 1631 | "kernelspec": { 1632 | "display_name": "Python 3", 1633 | "language": "python", 1634 | "name": "python3" 1635 | }, 1636 | "language_info": { 1637 | "codemirror_mode": { 1638 | "name": "ipython", 1639 | "version": 3 1640 | }, 1641 | "file_extension": ".py", 1642 | "mimetype": "text/x-python", 1643 | "name": "python", 1644 | "nbconvert_exporter": "python", 1645 | "pygments_lexer": "ipython3", 1646 | "version": "3.7.9" 1647 | } 1648 | }, 1649 | "nbformat": 4, 1650 | "nbformat_minor": 4 1651 | } 1652 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | # To use a consistent encoding 3 | # python setup.py sdist bdist_wheel 4 | # python -m twine upload dist/* 5 | from codecs import open 6 | from os import path 7 | 8 | here = path.abspath(path.dirname(__file__)) 9 | 10 | # Get the long description from the README file 11 | with open(path.join(here, 'README.rst'), encoding='utf-8') as f: 12 | long_description = f.read() 13 | 14 | requirements = [ 15 | 'python-osc', 16 | ] 17 | 18 | setup_requirements = [ 19 | 'pytest-runner', 20 | ] 21 | 22 | test_requirements = [ 23 | 'pytest', 24 | ] 25 | 26 | 27 | setup( 28 | name='python-sonic', 29 | version='0.4.4', 30 | description='Programming Music with Sonic Pi or Supercollider', 31 | long_description=long_description, 32 | url='https://github.com/gkvoelkl/python-sonic', 33 | author='gkvoelkl', 34 | author_email='gkvoelkl@nelson-games.de', 35 | packages=[ 36 | 'psonic', 37 | 'psonic.samples', 38 | 'psonic.samples.loops', 39 | 'psonic.internals', 40 | ], 41 | license='MIT', 42 | zip_safe=False, 43 | include_package_data=True, 44 | install_requires=requirements, 45 | classifiers=[ 46 | 'Development Status :: 4 - Beta', 47 | 'Intended Audience :: Developers', 48 | 'Topic :: Multimedia :: Sound/Audio', 49 | 'License :: OSI Approved :: MIT License', 50 | 'Programming Language :: Python :: 3', 51 | 'Programming Language :: Python :: 3.3', 52 | 'Programming Language :: Python :: 3.4', 53 | 'Programming Language :: Python :: 3.5', 54 | 'Programming Language :: Python :: 3.6', 55 | 'Programming Language :: Python :: 3.7', 56 | 'Programming Language :: Python :: 3.8', 57 | 'Programming Language :: Python :: 3.9' 58 | ], 59 | keywords= [ 60 | 'music', 61 | 'sonic pi', 62 | 'raspberry pi', 63 | 'audio', 64 | 'music composition', 65 | 'scsynth', 66 | 'supercollider', 67 | 'synthesis' 68 | ], 69 | test_suite='tests', 70 | tests_require=test_requirements, 71 | setup_requires=setup_requirements, 72 | ) 73 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gkvoelkl/python-sonic/5a0e873c532a3847e59b15e1f7041f8511e462ac/tests/__init__.py -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gkvoelkl/python-sonic/5a0e873c532a3847e59b15e1f7041f8511e462ac/tests/conftest.py -------------------------------------------------------------------------------- /tests/test_psonic.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from psonic import * 3 | 4 | def test_originl_behaviour(): 5 | notes_to_play = [72, G5, Fs5, Eb5] 6 | (play(note) for note in notes_to_play) 7 | sleep(0.5) 8 | play(72, amp=2) 9 | play(74, pan=-1) 10 | use_synth(SAW) 11 | use_synth(PROPHET) 12 | play( 13 | 60, attack=0.5, decay=1, sustain_level=0.4, 14 | sustain=2, release=0.5 15 | ) 16 | sample( 17 | LOOP_AMEN, start=0.5, finish=0.8, 18 | rate=-0.2, attack=0.3, release=1 19 | ) 20 | sample( 21 | DRUM_CYMBAL_OPEN, attack=0.01, 22 | sustain=0.3, release=0.1 23 | ) 24 | play([64, 67, 71], amp = 0.3) 25 | play(chord(E4, MINOR7)) 26 | play_pattern_timed( 27 | scale( 28 | C3, MAJOR_PENTATONIC, num_octaves = 2 29 | ), 30 | 0.125, release = 0.1, 31 | ) 32 | synth(TRI, note=D4, amp=0.4) 33 | detune = 0.7 34 | synth(SQUARE, note=E4+detune) 35 | synth(GNOISE, release=0.5, amp=1, cutoff=100) 36 | sc = Ring(scale(E3, MINOR_PENTATONIC)) 37 | play(next(sc), release=0.1) 38 | with Fx(SLICER): 39 | synth(PROPHET, note=E2, release=8, cutoff=80) 40 | synth(PROPHET, note=E2+4, release=8, cutoff=80) 41 | run("""live_loop :foo do 42 | use_real_time 43 | a, b, c = sync "/osc/trigger/prophet" 44 | synth :prophet, note: a, cutoff: b, sustain: c 45 | end """) 46 | send_message('/trigger/prophet', 70, 100, 8) 47 | stop() 48 | 49 | 50 | @pytest.mark.parametrize("root,quality,inversion,result", ( 51 | (C4, MAJOR, None, [C4, E4, G4]), 52 | (C4, MAJOR, 0, [C4, E4, G4]), 53 | (C4, MAJOR, 1, [G3, C4, E4]), 54 | (C4, MAJOR, 2, [E3, G3, C4]), 55 | (C4, MAJOR, 3, [C4, E4, G4]), 56 | (C4, MAJOR, 4, [G3, C4, E4]), 57 | (C4, MAJOR7, 0, [C4, E4, G4, B4]), 58 | (C4, MAJOR7, 1, [B3, C4, E4, G4]), 59 | (C4, MAJOR7, 2, [G3, B3, C4, E4]), 60 | (C4, MAJOR7, 3, [E3, G3, B3, C4]), 61 | (C4, MAJOR7, 4, [C4, E4, G4, B4]), 62 | (C4, MAJOR7, 5, [B3, C4, E4, G4]), 63 | )) 64 | def test_chord_inversions(root, quality, inversion, result): 65 | assert chord(root, quality, inversion) == result 66 | 67 | 68 | def test_imports(): 69 | from psonic import( 70 | SonicPi, 71 | SonicPiNew, 72 | ChordQuality, 73 | Message, 74 | Ring, 75 | Fx, 76 | Synth, 77 | Sample, 78 | ) 79 | --------------------------------------------------------------------------------