ps

python-sonic

Programming Music with Python, Sonic Pi and Supercollider

Showing:

Popularity

Downloads/wk

0

GitHub Stars

228

Maintenance

Last Commit

22d ago

Contributors

9

Package

Dependencies

1

License

MIT

Categories

Readme

python-sonic - Programming Music with Python, Sonic Pi or Supercollider

Python-Sonic is a simple Python interface for Sonic Pi, which is a real great music software created by Sam Aaron (http://sonic-pi.net).

At the moment Python-Sonic works with Sonic Pi. It is planned, that it will work with Supercollider, too.

If you like it, use it. If you have some suggestions, tell me (gkvoelkl@nelson-games.de).

Installation

Or try

$ pip install python-sonic

That should work.

Limitations

  • You have to start Sonic Pi first before you can use it with python-sonic
  • Only the notes from C2 to C6

Changelog

+--------+-------------------------------------------------------------+ | V | | | ersion | | +========+=============================================================+ | 0.2.0 | Some changes for Sonic Pi 2.11. Simpler multi-threading | | | with decorator @in_thread. Messaging with cue and | | | sync. | +--------+-------------------------------------------------------------+ | 0.3.0 | OSC Communication | +--------+-------------------------------------------------------------+ | 0.3.1. | Update, sort and duration of samples | +--------+-------------------------------------------------------------+ | 0.3.2. | Restructured | +--------+-------------------------------------------------------------+ | 0.4.0 | Changes communication ports and recording | +--------+-------------------------------------------------------------+

Communication

| The API python-sonic communications with Sonic Pi over UDP and two ports. One port is an internal Sonic Pi port and could be changed. | For older Sonic Pi Version you have to set the ports explicitly

.. code-block:: python

from psonic import *
set_server_parameter('127.0.0.1',4557,4559)

Examples

Many of the examples are inspired from the help menu in Sonic Pi.

.. code-block:: python

from psonic import *

The first sound

.. code-block:: python

play(70) #play MIDI note 70

Some more notes

.. code-block:: python

play(72)
sleep(1)
play(75)
sleep(1)
play(79) 

In more tratitional music notation

.. code-block:: python

play(C5)
sleep(0.5)
play(D5)
sleep(0.5)
play(G5) 

Play sharp notes like F# or dimished ones like Eb

.. code-block:: python

play(Fs5)
sleep(0.5)
play(Eb5)

Play louder (parameter amp) or from a different direction (parameter pan)

.. code-block:: python

play(72,amp=2)
sleep(0.5)
play(74,pan=-1) #left

Different synthesizer sounds

.. code-block:: python

use_synth(SAW)
play(38)
sleep(0.25)
play(50)
sleep(0.5)
use_synth(PROPHET)
play(57)
sleep(0.25)

ADSR (Attack, Decay, Sustain and Release) Envelope

.. code-block:: python

play (60, attack=0.5, decay=1, sustain_level=0.4, sustain=2, release=0.5) 
sleep(4)

Play some samples

.. code-block:: python

sample(AMBI_LUNAR_LAND, amp=0.5)

.. code-block:: python

sample(LOOP_AMEN,pan=-1)
sleep(0.877)
sample(LOOP_AMEN,pan=1)

.. code-block:: python

sample(LOOP_AMEN,rate=0.5)

.. code-block:: python

sample(LOOP_AMEN,rate=1.5)

.. code-block:: python

sample(LOOP_AMEN,rate=-1)#back

.. code-block:: python

sample(DRUM_CYMBAL_OPEN,attack=0.01,sustain=0.3,release=0.1)

.. code-block:: python

sample(LOOP_AMEN,start=0.5,finish=0.8,rate=-0.2,attack=0.3,release=1)

Play some random notes

.. code-block:: python

import random

for i in range(5):
    play(random.randrange(50, 100))
    sleep(0.5)

.. code-block:: python

for i in range(3):
    play(random.choice([C5,E5,G5]))
    sleep(1)

Sample slicing

.. code-block:: python

from psonic import *

number_of_pieces = 8

for i in range(16):
    s = random.randrange(0,number_of_pieces)/number_of_pieces #sample starts at 0.0 and finishes at 1.0
    f = s + (1.0/number_of_pieces)
    sample(LOOP_AMEN,beat_stretch=2,start=s,finish=f)
    sleep(2.0/number_of_pieces)

An infinite loop and if

.. code-block:: python

while True:
  if one_in(2):
    sample(DRUM_HEAVY_KICK)
    sleep(0.5)
  else:
    sample(DRUM_CYMBAL_CLOSED)
    sleep(0.25)

::

---------------------------------------------------------------------------

KeyboardInterrupt                         Traceback (most recent call last)

<ipython-input-18-d8759ac2d27e> in <module>()
      5   else:
      6     sample(DRUM_CYMBAL_CLOSED)
----> 7     sleep(0.25)


/mnt/jupyter/python-sonic/psonic.py in sleep(duration)
    587     :return:
    588     """
--> 589     time.sleep(duration)
    590     _debug('sleep', duration)
    591 


KeyboardInterrupt: 

If you want to hear more than one sound at a time, use Threads.

.. code-block:: python

import random
from psonic import *
from threading import Thread

def bass_sound():
    c = chord(E3, MAJOR7)
    while True:
        use_synth(PROPHET)
        play(random.choice(c), release=0.6)
        sleep(0.5)

def snare_sound():
    while True:
        sample(ELEC_SNARE)
        sleep(1)

bass_thread = Thread(target=bass_sound)
snare_thread = Thread(target=snare_sound)

bass_thread.start()
snare_thread.start()

while True:
    pass

::

---------------------------------------------------------------------------

KeyboardInterrupt                         Traceback (most recent call last)

<ipython-input-19-5b8671a783d6> in <module>
     22 
     23 while True:
---> 24     pass


KeyboardInterrupt: 

Every function bass_sound and snare_sound have its own thread. Your can hear them running.

.. code-block:: python

from psonic import *
from threading import Thread, Condition
from random import choice

def random_riff(condition):
    use_synth(PROPHET)
    sc = scale(E3, MINOR)
    while True:
        s = random.choice([0.125,0.25,0.5])
        with condition:
            condition.wait() #Wait for message
        for i in range(8):
            r = random.choice([0.125, 0.25, 1, 2])
            n = random.choice(sc)
            co = random.randint(30,100)
            play(n, release = r, cutoff = co)
            sleep(s)

def drums(condition):
    while True:
        with condition:
            condition.notifyAll() #Message to threads
        for i in range(16):
            r = random.randrange(1,10)
            sample(DRUM_BASS_HARD, rate=r)
            sleep(0.125)

condition = Condition()
random_riff_thread = Thread(name='consumer1', target=random_riff, args=(condition,))
drums_thread = Thread(name='producer', target=drums, args=(condition,))

random_riff_thread.start()
drums_thread.start()

input("Press Enter to continue...")

.. parsed-literal::

Press Enter to continue... 

.. parsed-literal::

''

To synchronize the thread, so that they play a note at the same time, you can use Condition. One function sends a message with condition.notifyAll the other waits until the message comes condition.wait.

More simple with decorator __@inthread_

.. code-block:: python

from psonic import *
from random import choice

tick = Message()

@in_thread
def random_riff():
    use_synth(PROPHET)
    sc = scale(E3, MINOR)
    while True:
        s = random.choice([0.125,0.25,0.5])
        tick.sync()
        for i in range(8):
            r = random.choice([0.125, 0.25, 1, 2])
            n = random.choice(sc)
            co = random.randint(30,100)
            play(n, release = r, cutoff = co)
            sleep(s)
            
@in_thread
def drums():
    while True:
        tick.cue()
        for i in range(16):
            r = random.randrange(1,10)
            sample(DRUM_BASS_HARD, rate=r)
            sleep(0.125)

random_riff()
drums()

input("Press Enter to continue...")

.. parsed-literal::

Press Enter to continue... 

.. code-block:: python

from psonic import *

tick = Message()

@in_thread
def metronom():
    while True:
        tick.cue()
        sleep(1)
        
@in_thread
def instrument():
    while True:
        tick.sync()
        sample(DRUM_HEAVY_KICK)

metronom()
instrument()

while True:
    pass

Play a list of notes

.. code-block:: python

from psonic import *

play ([64, 67, 71], amp = 0.3) 
sleep(1)
play ([E4, G4, B4])
sleep(1)

Play chords

.. code-block:: python

play(chord(E4, MINOR)) 
sleep(1)
play(chord(E4, MAJOR))
sleep(1)
play(chord(E4, MINOR7))
sleep(1)
play(chord(E4, DOM7))
sleep(1)

Play arpeggios

.. code-block:: python

play_pattern( chord(E4, 'm7')) 
play_pattern_timed( chord(E4, 'm7'), 0.25) 
play_pattern_timed(chord(E4, 'dim'), [0.25, 0.5]) 

Play scales

.. code-block:: python

play_pattern_timed(scale(C3, MAJOR), 0.125, release = 0.1) 
play_pattern_timed(scale(C3, MAJOR, num_octaves = 2), 0.125, release = 0.1) 
play_pattern_timed(scale(C3, MAJOR_PENTATONIC, num_octaves = 2), 0.125, release = 0.1)

The function scale returns a list with all notes of a scale. So you can use list methodes or functions. For example to play arpeggios descending or shuffeld.

.. code-block:: python

import random
from psonic import *

s = scale(C3, MAJOR)
s

.. parsed-literal::

[48, 50, 52, 53, 55, 57, 59, 60]

.. code-block:: python

s.reverse()

.. code-block:: python

play_pattern_timed(s, 0.125, release = 0.1)
random.shuffle(s)
play_pattern_timed(s, 0.125, release = 0.1)

Live Loop


One of the best in SONIC PI is the *Live Loop*. While a loop is playing
music you can change it and hear the change. Let’s try it in Python,
too.

.. code-block:: python

    from psonic import *
    from threading import Thread
    
    def my_loop():
      play(60)
      sleep(1)
    
    def looper():
      while True:
        my_loop()
    
    looper_thread = Thread(name='looper', target=looper)
    
    looper_thread.start()
    
    input("Press Enter to continue...")


.. parsed-literal::

    Press Enter to continue...Y




.. parsed-literal::

    'Y'



Now change the function *my_loop* und you can hear it.

.. code-block:: python

    def my_loop():
      use_synth(TB303)
      play (60, release= 0.3)
      sleep (0.25)

.. code-block:: python

    def my_loop():
      use_synth(TB303)
      play (chord(E3, MINOR), release= 0.3)
      sleep(0.5)

.. code-block:: python

    def my_loop():
        use_synth(TB303)
        sample(DRUM_BASS_HARD, rate = random.uniform(0.5, 2))
        play(random.choice(chord(E3, MINOR)), release= 0.2, cutoff=random.randrange(60, 130))
        sleep(0.25)

To stop the sound you have to end the kernel. In IPython with Kernel –>
Restart

Now with two live loops which are synch.

.. code-block:: python

    from psonic import *
    from threading import Thread, Condition
    from random import choice
    
    def loop_foo():
      play (E4, release = 0.5)
      sleep (0.5)
    
    
    def loop_bar():
      sample (DRUM_SNARE_SOFT)
      sleep (1)
        
    
    def live_loop_1(condition):
        while True:
            with condition:
                condition.notifyAll() #Message to threads
            loop_foo()
                
    def live_loop_2(condition):
        while True:
            with condition:
                condition.wait() #Wait for message
            loop_bar()
    
    condition = Condition()
    live_thread_1 = Thread(name='producer', target=live_loop_1, args=(condition,))
    live_thread_2 = Thread(name='consumer1', target=live_loop_2, args=(condition,))
    
    live_thread_1.start()
    live_thread_2.start()
    
    input("Press Enter to continue...")


.. parsed-literal::

    Press Enter to continue...y




.. parsed-literal::

    'y'



.. code-block:: python

    def loop_foo():
      play (A4, release = 0.5)
      sleep (0.5)

.. code-block:: python

    def loop_bar():
      sample (DRUM_HEAVY_KICK)
      sleep (0.125)

If would be nice if we can stop the loop with a simple command. With
stop event it works.

.. code-block:: python

    from psonic import *
    from threading import Thread, Condition, Event
    
    def loop_foo():
      play (E4, release = 0.5)
      sleep (0.5)
    
    
    def loop_bar():
      sample (DRUM_SNARE_SOFT)
      sleep (1)
        
    
    def live_loop_1(condition,stop_event):
        while not stop_event.is_set():
            with condition:
                condition.notifyAll() #Message to threads
            loop_foo()
                
    def live_loop_2(condition,stop_event):
        while not stop_event.is_set():
            with condition:
                condition.wait() #Wait for message
            loop_bar()
    
    
    
    condition = Condition()
    stop_event = Event()
    live_thread_1 = Thread(name='producer', target=live_loop_1, args=(condition,stop_event))
    live_thread_2 = Thread(name='consumer1', target=live_loop_2, args=(condition,stop_event))
    
    
    live_thread_1.start()
    live_thread_2.start()
    
    input("Press Enter to continue...")


.. parsed-literal::

    Press Enter to continue...y




.. parsed-literal::

    'y'



.. code-block:: python

    stop_event.set()

More complex live loops

.. code-block:: python

    sc = Ring(scale(E3, MINOR_PENTATONIC))
    
    def loop_foo():
      play (next(sc), release= 0.1)
      sleep (0.125)
    
    sc2 = Ring(scale(E3,MINOR_PENTATONIC,num_octaves=2))
               
    def loop_bar():
      use_synth(DSAW)
      play (next(sc2), release= 0.25)
      sleep (0.25)

Now a simple structure with four live loops

.. code-block:: python

    import random
    from psonic import *
    from threading import Thread, Condition, Event
    
    def live_1():
        pass
    
    def live_2():
        pass
        
    def live_3():
        pass
    
    def live_4():
        pass
    
    def live_loop_1(condition,stop_event):
        while not stop_event.is_set():
            with condition:
                condition.notifyAll() #Message to threads
            live_1()
                
    def live_loop_2(condition,stop_event):
        while not stop_event.is_set():
            with condition:
                condition.wait() #Wait for message
            live_2()
    
    def live_loop_3(condition,stop_event):
        while not stop_event.is_set():
            with condition:
                condition.wait() #Wait for message
            live_3()
    
    def live_loop_4(condition,stop_event):
        while not stop_event.is_set():
            with condition:
                condition.wait() #Wait for message
            live_4()
            
    condition = Condition()
    stop_event = Event()
    live_thread_1 = Thread(name='producer', target=live_loop_1, args=(condition,stop_event))
    live_thread_2 = Thread(name='consumer1', target=live_loop_2, args=(condition,stop_event))
    live_thread_3 = Thread(name='consumer2', target=live_loop_3, args=(condition,stop_event))
    live_thread_4 = Thread(name='consumer3', target=live_loop_3, args=(condition,stop_event))
    
    live_thread_1.start()
    live_thread_2.start()
    live_thread_3.start()
    live_thread_4.start()
    
    input("Press Enter to continue...")


.. parsed-literal::

    Press Enter to continue...y




.. parsed-literal::

    'y'



After starting the loops you can change them

.. code-block:: python

    def live_1():
        sample(BD_HAUS,amp=2)
        sleep(0.5)
        pass

.. code-block:: python

    def live_2():
        #sample(AMBI_CHOIR, rate=0.4)
        #sleep(1)
        pass

.. code-block:: python

    def live_3():
        use_synth(TB303)
        play(E2, release=4,cutoff=120,cutoff_attack=1)
        sleep(4)

.. code-block:: python

    def live_4():
        notes = scale(E3, MINOR_PENTATONIC, num_octaves=2)
        for i in range(8):
            play(random.choice(notes),release=0.1,amp=1.5)
            sleep(0.125)

And stop.

.. code-block:: python

    stop_event.set()

Creating Sound

.. code-block:: python

from psonic import *

synth(SINE, note=D4)
synth(SQUARE, note=D4)
synth(TRI, note=D4, amp=0.4)

.. code-block:: python

detune = 0.7
synth(SQUARE, note = E4)
synth(SQUARE, note = E4+detune)

.. code-block:: python

detune=0.1 # Amplitude shaping
synth(SQUARE, note = E2, release = 2)
synth(SQUARE, note = E2+detune, amp =  2, release = 2)
synth(GNOISE, release = 2, amp = 1, cutoff = 60)
synth(GNOISE, release = 0.5, amp = 1, cutoff = 100)
synth(NOISE, release = 0.2, amp = 1, cutoff = 90)

Next Step


Using FX *Not implemented yet*

.. code-block:: python

    from psonic import *
    
    with Fx(SLICER):
        synth(PROPHET,note=E2,release=8,cutoff=80)
        synth(PROPHET,note=E2+4,release=8,cutoff=80)

.. code-block:: python

    with Fx(SLICER, phase=0.125, probability=0.6,prob_pos=1):
        synth(TB303, note=E2, cutoff_attack=8, release=8)
        synth(TB303, note=E3, cutoff_attack=4, release=8)
        synth(TB303, note=E4, cutoff_attack=2, release=8)

OSC Communication (Sonic Pi Ver. 3.x or better)
-----------------------------------------------

In Sonic Pi version 3 or better you can work with messages.

.. code-block:: python

    from psonic import *

First you need a programm in the Sonic Pi server that receives messages.
You can write it in th GUI or send one with Python.

.. code-block:: python

    run("""live_loop :foo do
      use_real_time
      a, b, c = sync "/osc*/trigger/prophet"
      synth :prophet, note: a, cutoff: b, sustain: c
    end """)

Now send a message to Sonic Pi.

.. code-block:: python

    send_message('/trigger/prophet', 70, 100, 8)

.. code-block:: python

    stop()

Recording
---------

With python-sonic you can record wave files.

.. code-block:: python

    from psonic import *

.. code-block:: python

    # start recording
    start_recording()
    
    play(chord(E4, MINOR)) 
    sleep(1)
    play(chord(E4, MAJOR))
    sleep(1)
    play(chord(E4, MINOR7))
    sleep(1)
    play(chord(E4, DOM7))
    sleep(1)

.. code-block:: python

    # stop recording
    stop_recording




.. parsed-literal::

    <function psonic.psonic.stop_recording()>



.. code-block:: python

    # save file
    save_recording('/Volumes/jupyter/python-sonic/test.wav')

More Examples
-------------

.. code-block:: python

    from psonic import *

.. code-block:: python

    #Inspired by Steve Reich Clapping Music
    
    clapping = [1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0]
    
    for i in range(13):
        for j in range(4):
            for k in range(12): 
              if clapping[k] ==1 : sample(DRUM_SNARE_SOFT,pan=-0.5)
              if clapping[(i+k)%12] == 1: sample(DRUM_HEAVY_KICK,pan=0.5)
              sleep (0.25)

Projects that use Python-Sonic
------------------------------

Raspberry Pi sonic-track.py a Sonic-pi Motion Track Demo
https://github.com/pageauc/sonic-track

Sources
-------

Joe Armstrong: Connecting Erlang to the Sonic Pi
http://joearms.github.io/2015/01/05/Connecting-Erlang-to-Sonic-Pi.html

Joe Armstrong: Controlling Sound with OSC Messages
http://joearms.github.io/2016/01/29/Controlling-Sound-with-OSC-Messages.html

..

Rate & Review

Great Documentation0
Easy to Use0
Performant0
Highly Customizable0
Bleeding Edge0
Responsive Maintainers0
Poor Documentation0
Hard to Use0
Slow0
Buggy0
Abandoned0
Unwelcoming Community0
100