Programming Music with Sonic Pi or Supercollider
Project description
Python-Sonic is a simple Python interface for Sonic Pi, which is a real great music software created by Sam Aaron (http://sonic-pi.net).
At the moment Python-Sonic works with Sonic Pi. It is planned, that it will work with Supercollider, too.
If you like it, use it. If you have some suggestions, tell me (gkvoelkl@nelson-games.de).
Installation
First you need Python 3 (https://www.python.org, ) - Python 3.5 should work, because it’s the development environment
Then Sonic Pi (https://sonic-pi.net) - That makes the sound
Modul python-osc (https://pypi.python.org/pypi/python-osc) - Connection between Python and Sonic Pi Server
And this modul python-sonic - simply copy the source
Or try
$ pip install python-sonic
That should work.
Limitations
You have to start Sonic Pi first before you can use it with python-sonic
Only the notes from C2 to C6
Changelog
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.
from psonic import *
The first sound
play(70) #play MIDI note 70
Some more notes
play(72)
sleep(1)
play(75)
sleep(1)
play(79)
In more tratitional music notation
play(C5)
sleep(0.5)
play(D5)
sleep(0.5)
play(G5)
Play sharp notes like F# or dimished ones like Eb
play(Fs5)
sleep(0.5)
play(Eb5)
Play louder (parameter amp) or from a different direction (parameter pan)
play(72,amp=2)
sleep(0.5)
play(74,pan=-1) #left
Different synthesizer sounds
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
play (60, attack=0.5, decay=1, sustain_level=0.4, sustain=2, release=0.5)
sleep(4)
Play some samples
sample(AMBI_LUNAR_LAND, amp=0.5)
sample(LOOP_AMEN,pan=-1)
sleep(0.877)
sample(LOOP_AMEN,pan=1)
sample(LOOP_AMEN,rate=0.5)
sample(LOOP_AMEN,rate=1.5)
sample(LOOP_AMEN,rate=-1)#back
sample(DRUM_CYMBAL_OPEN,attack=0.01,sustain=0.3,release=0.1)
sample(LOOP_AMEN,start=0.5,finish=0.8,rate=-0.2,attack=0.3,release=1)
Play some random notes
import random
for i in range(5):
play(random.randrange(50, 100))
sleep(0.5)
for i in range(3):
play(random.choice([C5,E5,G5]))
sleep(1)
Sample slicing
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
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.
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.
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...")
Press Enter to continue...
''
To synchronize the thread, so that they play a note at the same time, you can use Condition. One function sends a message with condition.notifyAll the other waits until the message comes condition.wait.
More simple with decorator __@in_thread__
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...")
Press Enter to continue...
''
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
from psonic import *
play ([64, 67, 71], amp = 0.3)
sleep(1)
play ([E4, G4, B4])
sleep(1)
Play chords
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
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
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.
import random
s = scale(C3, MAJOR)
s
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.
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...")
Press Enter to continue...Y
'Y'
Now change the function my_loop und you can hear it.
def my_loop():
use_synth(TB303)
play (60, release= 0.3)
sleep (0.25)
def my_loop():
use_synth(TB303)
play (chord(E3, MINOR), release= 0.3)
sleep(0.5)
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.
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...")
Press Enter to continue...y
'y'
def loop_foo():
play (A4, release = 0.5)
sleep (0.5)
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.
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...")
Press Enter to continue...y
'y'
stop_event.set()
More complex live loops
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
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...")
Press Enter to continue...y
'y'
After starting the loops you can change them
def live_1():
sample(BD_HAUS,amp=2)
sleep(0.5)
pass
def live_2():
#sample(AMBI_CHOIR, rate=0.4)
#sleep(1)
pass
def live_3():
use_synth(TB303)
play(E2, release=4,cutoff=120,cutoff_attack=1)
sleep(4)
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.
stop_event.set()
More Examples
from psonic import *
#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
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distributions
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file python_sonic-0.2.0-py3-none-any.whl.
File metadata
- Download URL: python_sonic-0.2.0-py3-none-any.whl
- Upload date:
- Size: 16.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7274a5cbc30ce05f255fbe0a07d262e56b898164ee1b554899b842df72c03e04
|
|
| MD5 |
37b30be0da8198538ec69d531d2d5109
|
|
| BLAKE2b-256 |
8e64f62de71f25b73a4cf4f7ecc32a25a1b1a5f57c6c0611eadc4f3757dd99d8
|