Package: python-sonic
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).InstallationFirst you need Python 3 (https://www.python.org, ) - Python 3.5
should work, because it’s the development environmentThen Sonic Pi (https://sonic-pi.net) - That makes the soundModul python-osc (https://pypi.python.org/pypi/python-osc) -
Connection between Python and Sonic Pi ServerAnd this modul python-sonic - simply copy the sourceOr try$ pip install python-sonicThat should work.LimitationsYou have to startSonic Pifirst before you can use it with
python-sonicOnly the notes from C2 to C6ChangelogV
ersion0.2.0Some changes for Sonic Pi 2.11. Simpler multi-threading
with decorator@in_thread. Messaging withcueandsync.0.3.0OSC Communication0.3.1.Update, sort and duration of samples0.3.2.Restructured0.4.0Changes communication ports and recordingCommunicationThe APIpython-soniccommunications withSonic Piover UDP and two
ports. One port is an internalSonic Piport and could be changed.For olderSonic PiVersion you have to set the ports explicitlyfrompsonicimport*set_server_parameter('127.0.0.1',4557,4559)ExamplesMany of the examples are inspired from the help menu inSonic Pi.frompsonicimport*The first soundplay(70)#play MIDI note 70Some more notesplay(72)sleep(1)play(75)sleep(1)play(79)In more tratitional music notationplay(C5)sleep(0.5)play(D5)sleep(0.5)play(G5)Play sharp notes likeF#or dimished ones likeEbplay(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)#leftDifferent synthesizer soundsuse_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)Envelopeplay(60,attack=0.5,decay=1,sustain_level=0.4,sustain=2,release=0.5)sleep(4)Play some samplessample(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)#backsample(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 notesimportrandomforiinrange(5):play(random.randrange(50,100))sleep(0.5)foriinrange(3):play(random.choice([C5,E5,G5]))sleep(1)Sample slicingfrompsonicimport*number_of_pieces=8foriinrange(16):s=random.randrange(0,number_of_pieces)/number_of_pieces#sample starts at 0.0 and finishes at 1.0f=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 ifwhileTrue:ifone_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.importrandomfrompsonicimport*fromthreadingimportThreaddefbass_sound():c=chord(E3,MAJOR7)whileTrue:use_synth(PROPHET)play(random.choice(c),release=0.6)sleep(0.5)defsnare_sound():whileTrue:sample(ELEC_SNARE)sleep(1)bass_thread=Thread(target=bass_sound)snare_thread=Thread(target=snare_sound)bass_thread.start()snare_thread.start()whileTrue:pass---------------------------------------------------------------------------

KeyboardInterrupt                         Traceback (most recent call last)

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


KeyboardInterrupt:Every functionbass_soundandsnare_soundhave its own thread. Your
can hear them running.frompsonicimport*fromthreadingimportThread,Conditionfromrandomimportchoicedefrandom_riff(condition):use_synth(PROPHET)sc=scale(E3,MINOR)whileTrue:s=random.choice([0.125,0.25,0.5])withcondition:condition.wait()#Wait for messageforiinrange(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)defdrums(condition):whileTrue:withcondition:condition.notifyAll()#Message to threadsforiinrange(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 useCondition. One function sends a message withcondition.notifyAllthe other waits until the message comescondition.wait.More simple with decorator__@in_thread__frompsonicimport*fromrandomimportchoicetick=Message()@in_threaddefrandom_riff():use_synth(PROPHET)sc=scale(E3,MINOR)whileTrue:s=random.choice([0.125,0.25,0.5])tick.sync()foriinrange(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_threaddefdrums():whileTrue:tick.cue()foriinrange(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...frompsonicimport*tick=Message()@in_threaddefmetronom():whileTrue:tick.cue()sleep(1)@in_threaddefinstrument():whileTrue:tick.sync()sample(DRUM_HEAVY_KICK)metronom()instrument()whileTrue:passPlay a list of notesfrompsonicimport*play([64,67,71],amp=0.3)sleep(1)play([E4,G4,B4])sleep(1)Play chordsplay(chord(E4,MINOR))sleep(1)play(chord(E4,MAJOR))sleep(1)play(chord(E4,MINOR7))sleep(1)play(chord(E4,DOM7))sleep(1)Play arpeggiosplay_pattern(chord(E4,'m7'))play_pattern_timed(chord(E4,'m7'),0.25)play_pattern_timed(chord(E4,'dim'),[0.25,0.5])Play scalesplay_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 functionscalereturns a list with all notes of a scale. So you
can use list methodes or functions. For example to play arpeggios
descending or shuffeld.importrandomfrompsonicimport*s=scale(C3,MAJOR)s[48, 50, 52, 53, 55, 57, 59, 60]s.reverse()play_pattern_timed(s,0.125,release=0.1)random.shuffle(s)play_pattern_timed(s,0.125,release=0.1)Live LoopOne of the best in SONIC PI is theLive Loop. While a loop is playing
music you can change it and hear the change. Let’s try it in Python,
too.frompsonicimport*fromthreadingimportThreaddefmy_loop():play(60)sleep(1)deflooper():whileTrue: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 functionmy_loopund you can hear it.defmy_loop():use_synth(TB303)play(60,release=0.3)sleep(0.25)defmy_loop():use_synth(TB303)play(chord(E3,MINOR),release=0.3)sleep(0.5)defmy_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 –>
RestartNow with two live loops which are synch.frompsonicimport*fromthreadingimportThread,Conditionfromrandomimportchoicedefloop_foo():play(E4,release=0.5)sleep(0.5)defloop_bar():sample(DRUM_SNARE_SOFT)sleep(1)deflive_loop_1(condition):whileTrue:withcondition:condition.notifyAll()#Message to threadsloop_foo()deflive_loop_2(condition):whileTrue:withcondition:condition.wait()#Wait for messageloop_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'defloop_foo():play(A4,release=0.5)sleep(0.5)defloop_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.frompsonicimport*fromthreadingimportThread,Condition,Eventdefloop_foo():play(E4,release=0.5)sleep(0.5)defloop_bar():sample(DRUM_SNARE_SOFT)sleep(1)deflive_loop_1(condition,stop_event):whilenotstop_event.is_set():withcondition:condition.notifyAll()#Message to threadsloop_foo()deflive_loop_2(condition,stop_event):whilenotstop_event.is_set():withcondition:condition.wait()#Wait for messageloop_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 loopssc=Ring(scale(E3,MINOR_PENTATONIC))defloop_foo():play(next(sc),release=0.1)sleep(0.125)sc2=Ring(scale(E3,MINOR_PENTATONIC,num_octaves=2))defloop_bar():use_synth(DSAW)play(next(sc2),release=0.25)sleep(0.25)Now a simple structure with four live loopsimportrandomfrompsonicimport*fromthreadingimportThread,Condition,Eventdeflive_1():passdeflive_2():passdeflive_3():passdeflive_4():passdeflive_loop_1(condition,stop_event):whilenotstop_event.is_set():withcondition:condition.notifyAll()#Message to threadslive_1()deflive_loop_2(condition,stop_event):whilenotstop_event.is_set():withcondition:condition.wait()#Wait for messagelive_2()deflive_loop_3(condition,stop_event):whilenotstop_event.is_set():withcondition:condition.wait()#Wait for messagelive_3()deflive_loop_4(condition,stop_event):whilenotstop_event.is_set():withcondition:condition.wait()#Wait for messagelive_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 themdeflive_1():sample(BD_HAUS,amp=2)sleep(0.5)passdeflive_2():#sample(AMBI_CHOIR, rate=0.4)#sleep(1)passdeflive_3():use_synth(TB303)play(E2,release=4,cutoff=120,cutoff_attack=1)sleep(4)deflive_4():notes=scale(E3,MINOR_PENTATONIC,num_octaves=2)foriinrange(8):play(random.choice(notes),release=0.1,amp=1.5)sleep(0.125)And stop.stop_event.set()Creating Soundfrompsonicimport*synth(SINE,note=D4)synth(SQUARE,note=D4)synth(TRI,note=D4,amp=0.4)detune=0.7synth(SQUARE,note=E4)synth(SQUARE,note=E4+detune)detune=0.1# Amplitude shapingsynth(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 StepUsing FXNot implemented yetfrompsonicimport*withFx(SLICER):synth(PROPHET,note=E2,release=8,cutoff=80)synth(PROPHET,note=E2+4,release=8,cutoff=80)withFx(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.frompsonicimport*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.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.send_message('/trigger/prophet',70,100,8)stop()RecordingWith python-sonic you can record wave files.frompsonicimport*# start recordingstart_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)# stop recordingstop_recording<function psonic.psonic.stop_recording()># save filesave_recording('/Volumes/jupyter/python-sonic/test.wav')More Examplesfrompsonicimport*#Inspired by Steve Reich Clapping Musicclapping=[1,1,1,0,1,1,0,1,0,1,1,0]foriinrange(13):forjinrange(4):forkinrange(12):ifclapping[k]==1:sample(DRUM_SNARE_SOFT,pan=-0.5)ifclapping[(i+k)%12]==1:sample(DRUM_HEAVY_KICK,pan=0.5)sleep(0.25)Projects that use Python-SonicRaspberry Pi sonic-track.py a Sonic-pi Motion Track Demohttps://github.com/pageauc/sonic-trackSourcesJoe Armstrong: Connecting Erlang to the Sonic Pihttp://joearms.github.io/2015/01/05/Connecting-Erlang-to-Sonic-Pi.htmlJoe Armstrong: Controlling Sound with OSC Messageshttp://joearms.github.io/2016/01/29/Controlling-Sound-with-OSC-Messages.html