Metadata-Version: 2.0
Name: python-sonic
Version: 0.2.0
Summary: Programming Music with Sonic Pi or Supercollider
Home-page: https://github.com/gkvoelkl/python-sonic
Author: gkvoelkl
Author-email: gkvoelkl@nelson-games.de
License: MIT
Keywords: music,sonic pi,raspberry pi,audio,music composition,scsynth,supercollider,synthesis
Platform: UNKNOWN
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Topic :: Multimedia :: Sound/Audio
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.3
Classifier: Programming Language :: Python :: 3.4
Classifier: Programming Language :: Python :: 3.5
Requires-Dist: python-osc


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

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

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

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

Installation
------------

-  First you need Python 3 (https://www.python.org, ) - Python 3.5
   should work, because it's the development environment
-  Then Sonic Pi (https://sonic-pi.net) - That makes the sound
-  Modul python-osc (https://pypi.python.org/pypi/python-osc) -
   Connection between Python and Sonic Pi Server
-  And this modul python-sonic - simply copy the source

Or try

.. code:: python

    $ 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
---------

+------------+---------------------------------------------------------------+
| Version    |                                                               |
+============+===============================================================+
| 0.2.0      | Some changes for Sonic Pi 2.11. Simpler multi-threading with  |
|            | decorator *@in\_thread*. Messaging with *cue* and *sync*.     |
+------------+---------------------------------------------------------------+

Examples
--------

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

.. code:: python

    from psonic import *

The first sound

.. code:: python

    play(70) #play MIDI note 70

Some more notes

.. code:: python

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

In more tratitional music notation

.. code:: python

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

Play sharp notes like *F#* or dimished ones like *Eb*

.. code:: python

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

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

.. code:: python

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

Different synthesizer sounds

.. code:: 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:: python

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

Play some samples

.. code:: python

    sample(AMBI_LUNAR_LAND, amp=0.5)

.. code:: python

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

.. code:: python

    sample(LOOP_AMEN,rate=0.5)

.. code:: python

    sample(LOOP_AMEN,rate=1.5)

.. code:: python

    sample(LOOP_AMEN,rate=-1)#back

.. code:: python

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

.. code:: python

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

Play some random notes

.. code:: python

    import random

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

.. code:: python

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

Sample slicing

.. code:: 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:: 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:: 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

Every function *bass\_sound* and *snare\_sound* have its own thread.
Your can hear them running.

.. code:: 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 \_\_@in\_thread\_\_

.. code:: 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...




.. parsed-literal::

    ''



.. code:: 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:: python

    from psonic import *

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

Play chords

.. code:: 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:: 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:: 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:: python

    import random

    s = scale(C3, MAJOR)
    s

.. code:: python

    play_pattern_timed(s.reverse(), 0.125, release = 0.1)
    play_pattern_timed(random.shuffle(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:: 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:: python

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

.. code:: python

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

.. code:: 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:: 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:: python

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

.. code:: 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:: 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:: python

    stop_event.set()

More complex live loops

.. code:: 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:: 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:: python

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

.. code:: python

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

.. code:: python

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

.. code:: 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:: python

    stop_event.set()

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

.. code:: python

    from psonic import *

.. code:: 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)

More Informations
-----------------

Sonic Pi
~~~~~~~~

..

OSC
~~~

..

MIDI
~~~~

..

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

..


