Last active
October 20, 2024 17:50
-
-
Save rbnpi/ebedd26d61180230db891ccbd9d74971 to your computer and use it in GitHub Desktop.
A polyphonic gated synth for Sonic Pi3 with midi keyboard input. Runs on Raspberry Pi3 or more powerful computer with Sonic Pi 3. Accompanying article and video SECOND VERSION ADDED: see comments
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#polyphonic midi input program with sustained notes | |
#experimental program by Robin Newman, November 2017 | |
#pitchbend can be applied to notes at any time while they are sounding | |
use_debug false | |
set :synth,:tb303 #initial value | |
set :pb,0 #pitchbend initial value | |
kill_list=[] #list to contain notes to be killed | |
on_notes=[] #list of notes currently playing | |
ns=[] #array to store note playing references | |
nv=[0]*128 #array to store state of note for a particlar pitch 1=on, 0=off | |
128.times do |i| | |
ns[i]=("n"+i.to_s).to_sym #set up array of symbols :n0 ...:n127 | |
end | |
#puts ns #for testing | |
define :sv do |sym| #extract numeric value associated with symbol eg :n64 => 64 | |
return sym.to_s[1..-1].to_i | |
end | |
#puts sv(ns[64]) #for testing | |
live_loop :choose_synth do | |
b= sync "/midi/*/*/*/control_change" #use wild cards to works with any controller | |
if b[0]==10 #adjust control number to suit your controller | |
sc=(b[1].to_f/127*3 ).to_i | |
set :synth,[:tri,:saw,:tb303,:fm][sc] #can change synth list if you wish | |
puts "Synth #{get(:synth)} selected" | |
end | |
end | |
live_loop :pb do #get current pitchbend value adjusted in range -12 to +12 (octave) | |
b = sync "/midi/*/*/*/pitch_bend" #change to match your controller | |
set :pb,(b[0]-8192).to_f/8192*12 | |
end | |
with_fx :reverb,room: 0.8,mix: 0.6 do #add some reverb | |
live_loop :midi_note_on do #this loop starts 100 second notes for specified pitches and stores reference | |
use_real_time | |
note, on = sync "/midi/*/*/*/note_on" | |
if on >0 | |
if nv[note]==0 #check if new start for the note | |
puts "setting note #{note} on" | |
vn=on.to_f/127 | |
nv[note]=1 #mark note as started for this pitch | |
use_synth get(:synth) | |
x = play note+get(:pb),attack: 0.01, sustain: 100,amp: vn #start playing note | |
set ns[note],x #store reference to note in ns array | |
on_notes.push [note,vn] #add note to list of notes playing | |
end | |
else | |
if nv[note]==1 #check if this pitch is on | |
nv[note]=0 #set this pitch off | |
kill_list.push note #add note to list of notes to kill | |
end | |
end | |
end | |
live_loop :processnote,auto_cue: false,delay: 0.4 do # this applies pitchbend if any to note as it plays | |
#delayed start helps reduce timing errors | |
use_real_time | |
if on_notes.length > 0 #check if any notes on | |
k=on_notes.pop #get next note from "on" list | |
puts "processing note #{k[0]}" | |
in_thread do #start a thread to apply pitchbend to the note every 0.05 seconds | |
v=get(ns[k[0]]) #retrieve control value for the note | |
while nv[k[0]]==1 #while the note is still merked as on | |
control v,note: k[0]+get(:pb),note_slide: 0.05,amp: k[1] | |
sleep 0.05 | |
end | |
#belt and braces kill here as well as in notekill liveloop: catches any that miss | |
control v,amp: 0,amp_slide: 0.02 #fade note out in 0.02 seconds | |
sleep 0.02 | |
puts "backup kill note #{k[0]}" | |
kill v #kill the note referred to in ns array | |
end | |
end | |
sleep 0.08 #so that the loop sleeps if no notes on | |
end | |
live_loop :notekill,auto_cue: false,delay: 0.3 do # this loop kills released notes | |
#delayed start helps reduce timing errors | |
use_real_time | |
while kill_list.length > 0 #check if there are notes to be killed | |
k=kill_list.pop #get next note to kill | |
puts "killing note #{k}" | |
v=get(ns[k]) #retrieve reference to the note | |
control v,amp: 0,amp_slide: 0.02 #fade note out in 0.02 seconds | |
sleep 0.02 | |
kill v #kill the note referred to in ns array | |
end | |
sleep 0.08 #so that the loop sleeps if no notes to be killed | |
end | |
end #reverb |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#polyphonic midi input program with sustained notes | |
#experimental program by Robin Newman, November 2017 | |
#pitchbend can be applied to note AS IT STARTS | |
#This version for controllers with separate note_on and note_off midi signals | |
#rather than using note_on with velocity 0 for midi_off signal | |
set :pb,0 #pitchbend value | |
plist=[] #list to contains references to notes to be killed | |
ns=[] #array to store note playing references | |
nv=[0]*128 #array to store state of note for a particlar pitch 1=on, 0 = 0ff | |
128.times do |i| | |
ns[i]=("n"+i.to_s).to_sym #set up array of symbols :n0 ...:n127 | |
end | |
#puts ns #for testing | |
define :sv do |sym| #extract numeric value associated with symbol eg :n64 => 64 | |
return sym.to_s[1..-1].to_i | |
end | |
#puts sv(ns[64]) #for testing | |
live_loop :pb do #get current pitchbend value adjusted in range -12 to +12 (octave) | |
b = sync "/midi/*/*/*/pitch_bend" | |
set :pb,(b[0]-8192).to_f/8192*12 | |
puts get(:pb) | |
end | |
define :geton do |address| | |
v= get_event(address).to_s.split(",")[6]#[address.length+1..-2].to_i | |
return v.include?"note_on" | |
end | |
define :parse_sync_address do |address| | |
v= get_event(address).to_s.split(",")[6]#[address.length+1..-2].to_i | |
if v != nil | |
return v[3..-2].split("/") | |
else | |
return ["error"] | |
end | |
end | |
live_loop :midi_piano_on do #this loop starts 5 second notes for spcified pitches and stores reference | |
use_real_time | |
note, vol = sync "/midi/*/*/*/note_*" | |
res= parse_sync_address "/midi/*/*/*/*" | |
puts res[4] | |
if res[4]=="note_on" | |
puts note,nv[note] | |
if nv[note]==0 #check if new start for the note | |
nv[note]=1 #mark note as started for this pitch | |
use_synth :tri | |
#max duration of note set to 5 on next line. Can increase if you wish. | |
x = play note+get(:pb), amp: vol/127.0,sustain: 50 #play note | |
set ns[note],x #store reference in ns array | |
end | |
else | |
if nv[note]==1 #check if this pitch is on | |
nv[note]=0 #set this pitch off | |
plist << get(ns[note]) | |
end | |
end | |
end | |
live_loop :notekill,auto_cue: false,delay: 0.25 do | |
use_real_time | |
if plist.length > 0 #check if notes to be killed | |
k=plist.pop | |
control k,amp: 0,amp_slide: 0.02 #fade note out in 0.02 seconds | |
sleep 0.02 | |
kill k #kill the note referred to in ns array | |
end | |
sleep 0.01 | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
And here the version with the live pitch bend and reverb