These files accompany an article at rbnrpi.wordpress.com The two python scripts are identical, except that the first comments out support for the PS3 wireless controlleer The two Sonic Pi scripts can be used to receive OSC messages from the touch keybaord and will play sounds on Sonic Pi. There is a video of the project in operation.
-
-
Save rbnpi/a28f57765d6e2282e71e6062a0ecf221 to your computer and use it in GitHub Desktop.
Touch Sensitive Keyboard for Sonic Pi using Adafruit MPR121
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
#Sonic Pi Loop Controller by Robin Newman | |
#VERSION 2 ADDS CONTROL OF BACKGROUND PULSE AND SCREEN | |
#THIS VERSION TESTED ON Pi3 B and Pi3 B+ | |
#in particular uses some new :loop samples only in version 3.1 | |
#These can be copied to the samples folder on version 3.0.1 if required | |
#or you can specify different ones. | |
#This version uses 11 push buttons on TouchOSC for input and 1 LED on TouchOSC for output | |
#Can be modified to use any suitable OSC source that can give on/off signals when a button is pressed | |
use_real_time #use_sched_ahead_time 0.2 #if problems | |
use_debug false | |
use_osc_logging false | |
use_osc "localhost",4559 | |
#prime ps3 vol and cutoff | |
osc "/rud",0 | |
osc "/rlr",0 | |
use_bpm 120 | |
path="~/Desktop/samples/" | |
#turn background sound off | |
set :bg,0 | |
use_debug false | |
use_osc_logging false | |
#input on and off live_loops to detect inputs | |
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 | |
define :toggleBG do | |
b=get(:bg) | |
if b==1 | |
set :bg,0 | |
else | |
set :bg,1 | |
end | |
end | |
live_loop :pon do | |
b = sync "/osc/key*" | |
if b[0]==1 | |
r=parse_sync_address "/osc/key*" | |
ns= r[1][3..-1] | |
set ("c"+ns).to_sym,1 | |
doCommandSelect(ns .to_i) | |
end | |
end | |
live_loop :poff do | |
b = sync "/osc/key*" | |
if b[0]==0 | |
r=parse_sync_address "/osc/key*" | |
ns= r[1][3..-1] | |
set ("c"+ns).to_sym,0 | |
end | |
end | |
define :doCommandSelect do |n| | |
puts n | |
case n | |
when 0 | |
toggleBG | |
when 1 | |
doLoop 1,0.5,:loop_amen,4 #parameters: channel,vol,samplename,beatstrech value | |
when 2 | |
doLoop 2,0.5,:loop_garzul,16 | |
when 3 | |
doLoop 3,0.8,:loop_compus,16 | |
when 4 | |
doLoop 4,0.9,:loop_mehackit1,4 | |
when 5 | |
doLongNote 5,0.5,:fm,:c3,4 #parameters channel, vol,synth,note,repeat duration***** | |
#*** can add an optiona beat_stretch, but probably not required | |
when 6 | |
doLoopSequence 6,0.3,:tb303 #parameters channel, vol,synth | |
when 7 | |
doLoop 7,0.7,:loop_mika, 16 | |
when 8 | |
doLoop 8,0.7, :loop_weirdo, 4 | |
when 9 | |
doLoop 9,0.9,:loop_safari,16 #doSingleSample | |
when 10 | |
doLoop 10,0.7, :loop_mehackit2,4 | |
when 11 | |
doOneShot 11,4,path+"testsample.flac" #parameters channel,vol,sample | |
#as a singleShot plays once so only sync the start | |
else | |
puts "nothing" | |
end | |
end | |
#general function to set up stoppable live_loop | |
define :doLoop do |n,vol,sampleName,bs,| | |
set ("kill"+n.to_s).to_sym,false | |
ln=("name"+n.to_s).to_sym | |
in_thread do | |
loop do | |
if get( ("c"+n.to_s).to_sym)==0 | |
s= get( ("s"+n.to_s).to_sym) | |
kill s | |
set ("kill"+n.to_s).to_sym, true | |
stop | |
end | |
sleep 0.2 | |
end | |
end | |
live_loop ln, sync: :metro do | |
s=sample sampleName,beat_stretch: bs,amp: vol | |
set ("s"+n.to_s).to_sym,s | |
k=(bs/0.25).to_i | |
k.times do | |
sleep bs.to_f/k | |
control s, amp: vol*((get "/osc/rud")[0]+1),amp_slide: 0.05 | |
stop if get( ("kill"++n.to_s).to_sym) | |
end | |
end | |
end | |
#general function to start stoppable single shot sample | |
define :doOneShot do |n,vol,sampleName,bs=0| | |
sync :metro | |
if bs >0 | |
s=sample sampleName,beat_stretch: bs,amp: vol | |
else | |
s=sample sampleName,amp: vol | |
end | |
in_thread do | |
loop do | |
if get( ("c"++n.to_s).to_sym)==0 | |
kill s | |
stop | |
end | |
sleep 0.2 | |
end | |
end | |
end | |
#general function to set up long repeating note | |
define :doLongNote do |n,vol,synth,note,dur| | |
set ("kill"+n.to_s).to_sym,false | |
ln=("name"+n.to_s).to_sym | |
in_thread do | |
loop do | |
if get( ("c"+n.to_s).to_sym)==0 | |
s= get( ("s"+n.to_s).to_sym) | |
control s,amp: 0,amp_slide: 0.05 | |
sleep 0.05 | |
kill s | |
set ("kill"+n.to_s).to_sym, true | |
stop | |
end | |
sleep 0.2 | |
end | |
end | |
live_loop ln, sync: :metro do | |
use_synth synth | |
s=play note,sustain: dur-1,release: 1,cutoff: 100,amp: vol | |
set ("s"+n.to_s).to_sym,s | |
k=(dur/0.25).to_i | |
k.times do | |
sleep dur.to_f/k | |
stop if get( ("kill"++n.to_s).to_sym) | |
end | |
end | |
end | |
#function to setup sequence of notes (stoppable) | |
define :doLoopSequence do |n,vol,synth| | |
set ("kill"+n.to_s).to_sym,false | |
ln=("name"+n.to_s).to_sym | |
in_thread do | |
loop do | |
if get( ("c"+n.to_s).to_sym)==0 | |
set ("kill"+n.to_s).to_sym, true | |
stop | |
end | |
sleep 0.1 | |
end | |
end | |
live_loop ln, sync: :metro do | |
use_synth synth | |
co=play scale(:c3,:minor_pentatonic,num_octaves: 2).choose,release: 0.20,amp: vol*((get "/osc/rud")[0]+1),cutoff: 80#rrand_i(60,120) | |
control co,cutoff: (get "/osc/rlr")[0]*40+80,cutoff_slide: 0.05,amp: vol*((get "/osc/rud")[0]+1),amp_slide: 0.05 | |
2.times do #do this to get a quicker response time | |
sleep 0.25 / 2 | |
stop if get(("kill"+n.to_s).to_sym) | |
end | |
end | |
end | |
live_loop :metro do #metronome to sync stuff together | |
sleep 1 | |
end | |
#Optional repeating pulse | |
with_fx :gverb,room: 15,mix: 0.8,amp: 0.5 do | |
live_loop :alwaysplaying,sync: :metro do #runs continuously playing | |
use_synth :fm | |
if get(:bg)==1 | |
play :c3,release: 0.9,amp: 1,pan: -0.8 | |
play :c4,release: 1.8,amp: 0.2,pan: -0.8 | |
end | |
sleep 2 | |
if get(:bg)==1 | |
play :c3,release: 1.8,amp: 1,pan: 0.8 | |
end | |
sleep 2 | |
end | |
end #fx | |
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
#!/usr/bin/env python3 | |
#driver for 12 pad touch keys and ps3 wireless controller by Robin Newman | |
#April 2018 | |
#uses library for Adafruit MPR121 board | |
# Copyright (c) 2014 Adafruit Industries | |
# Author: Tony DiCola | |
# | |
# Permission is hereby granted, free of charge, to any person obtaining a copy | |
# of this software and associated documentation files (the "Software"), to deal | |
# in the Software without restriction, including without limitation the rights | |
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
# copies of the Software, and to permit persons to whom the Software is | |
# furnished to do so, subject to the following conditions: | |
# | |
# The above copyright notice and this permission notice shall be included in | |
# all copies or substantial portions of the Software. | |
# | |
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
# THE SOFTWARE. | |
# NB Install Adafruit Library and python-osc library before use | |
import sys | |
import time | |
import pygame | |
import Adafruit_MPR121.MPR121 as MPR121 | |
from pythonosc import osc_message_builder #add library for OSC support | |
from pythonosc import udp_client | |
import argparse | |
pygame.display.init() | |
##### uncomment next three lines to add joystick support | |
#pygame.joystick.init() #add support for PS3 controller | |
#ps3=pygame.joystick.Joystick(0) | |
#ps3.init() | |
def control(spip): #This is called once __main__ has set up the Sonic Pi ip address | |
gate = 0.1 #dead area used for PS3 joysticks | |
sender=udp_client.SimpleUDPClient(spip,4559) #set up OSC link to Sonic Pi | |
print('Adafruit MPR121 Capacitive Touch keypads') | |
# Create MPR121 instance. | |
cap = MPR121.MPR121() | |
# Initialize communication with MPR121 using default I2C bus of device, and | |
# default I2C address (0x5A). On BeagleBone Black will default to I2C bus 0. | |
if not cap.begin(): | |
print('Error initializing MPR121. Check your wiring!') | |
sys.exit(1) | |
# Alternatively, specify a custom I2C address such as 0x5B (ADDR tied to 3.3V), | |
# 0x5C (ADDR tied to SDA), or 0x5D (ADDR tied to SCL). | |
#cap.begin(address=0x5B) | |
# Also you can specify an optional I2C bus with the bus keyword parameter. | |
#cap.begin(busnum=1) | |
pygame.init() | |
# Main loop to print a message every time a pin is touched. | |
print('Press Ctrl-C to quit.') | |
last_touched = cap.touched() | |
while True: | |
try: | |
##### uncomment next section to add joystick support ############# | |
#pygame.event.pump() | |
#rud=ps3.get_axis(3) | |
#rlr=ps3.get_axis(2) | |
#if abs(rud) > gate: | |
#sender.send_message('/rud',-rud) | |
#print("/rud, "+str(-rud)) | |
#if abs(rlr) > gate: | |
#sender.send_message('/rlr',rlr) | |
#print("/rlr, "+str(-rlr)) | |
###### end of section to uncomment for joystick support ########### | |
current_touched = cap.touched() | |
# Check each pin's last and current state to see if it was pressed or released. | |
for i in range(12): | |
# Each pin is represented by a bit in the touched value. A value of 1 | |
# means the pin is being touched, and 0 means it is not being touched. | |
pin_bit = 1 << i | |
# First check if transitioned from not touched to touched. | |
if current_touched & pin_bit and not last_touched & pin_bit: | |
print('{0} touched!'.format(i)) | |
print("/key"+str(i)+',1') | |
sender.send_message('/key'+str(i) ,1) | |
if not current_touched & pin_bit and last_touched & pin_bit: | |
print('{0} released!'.format(i)) | |
print('/key'+str(i)+',0') | |
sender.send_message('/key'+str(i) ,0) | |
# Update last state and wait a short period before repeating. | |
last_touched = current_touched | |
time.sleep(0.1) | |
except KeyboardInterrupt: | |
print("\nExiting") | |
sys.exit() | |
if __name__=="__main__": | |
parser = argparse.ArgumentParser() #setup retrieving arg (if any) for --sp | |
parser.add_argument("--sp", | |
default="127.0.0.1", help="The ip on which Sonic Pi listens") | |
args = parser.parse_args() | |
spip=args.sp | |
print("Sonic Pi on ip",spip) | |
control(spip) |
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
#!/usr/bin/env python3 | |
#driver for 12 pad touch keys and ps3 wireless controller by Robin Newman | |
#April 2018 | |
#uses library for Adafruit MPR121 board | |
# Copyright (c) 2014 Adafruit Industries | |
# Author: Tony DiCola | |
# | |
# Permission is hereby granted, free of charge, to any person obtaining a copy | |
# of this software and associated documentation files (the "Software"), to deal | |
# in the Software without restriction, including without limitation the rights | |
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
# copies of the Software, and to permit persons to whom the Software is | |
# furnished to do so, subject to the following conditions: | |
# | |
# The above copyright notice and this permission notice shall be included in | |
# all copies or substantial portions of the Software. | |
# | |
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
# THE SOFTWARE. | |
# NB Install Adafruit Library and python-osc library before use | |
import sys | |
import time | |
import pygame | |
import Adafruit_MPR121.MPR121 as MPR121 | |
from pythonosc import osc_message_builder #add library for OSC support | |
from pythonosc import udp_client | |
import argparse | |
pygame.display.init() | |
pygame.joystick.init() #add support for PS3 controller | |
ps3=pygame.joystick.Joystick(0) | |
ps3.init() | |
def control(spip): #This is called once __main__ has set up the Sonic Pi ip address | |
gate = 0.1 #dead area used for PS3 joysticks | |
sender=udp_client.SimpleUDPClient(spip,4559) #set up OSC link to Sonic Pi | |
print('Adafruit MPR121 Capacitive Touch keypads') | |
# Create MPR121 instance. | |
cap = MPR121.MPR121() | |
# Initialize communication with MPR121 using default I2C bus of device, and | |
# default I2C address (0x5A). On BeagleBone Black will default to I2C bus 0. | |
if not cap.begin(): | |
print('Error initializing MPR121. Check your wiring!') | |
sys.exit(1) | |
# Alternatively, specify a custom I2C address such as 0x5B (ADDR tied to 3.3V), | |
# 0x5C (ADDR tied to SDA), or 0x5D (ADDR tied to SCL). | |
#cap.begin(address=0x5B) | |
# Also you can specify an optional I2C bus with the bus keyword parameter. | |
#cap.begin(busnum=1) | |
pygame.init() | |
# Main loop to print a message every time a pin is touched. | |
print('Press Ctrl-C to quit.') | |
last_touched = cap.touched() | |
while True: | |
try: | |
pygame.event.pump() | |
rud=ps3.get_axis(3) | |
rlr=ps3.get_axis(2) | |
if abs(rud) > gate: | |
sender.send_message('/rud',-rud) | |
print("/rud, "+str(-rud)) | |
if abs(rlr) > gate: | |
sender.send_message('/rlr',rlr) | |
print("/rlr, "+str(-rlr)) | |
current_touched = cap.touched() | |
# Check each pin's last and current state to see if it was pressed or released. | |
for i in range(12): | |
# Each pin is represented by a bit in the touched value. A value of 1 | |
# means the pin is being touched, and 0 means it is not being touched. | |
pin_bit = 1 << i | |
# First check if transitioned from not touched to touched. | |
if current_touched & pin_bit and not last_touched & pin_bit: | |
print('{0} touched!'.format(i)) | |
print("/key"+str(i)+',1') | |
sender.send_message('/key'+str(i) ,1) | |
if not current_touched & pin_bit and last_touched & pin_bit: | |
print('{0} released!'.format(i)) | |
print('/key'+str(i)+',0') | |
sender.send_message('/key'+str(i) ,0) | |
# Update last state and wait a short period before repeating. | |
last_touched = current_touched | |
time.sleep(0.1) | |
except KeyboardInterrupt: | |
print("\nExiting") | |
sys.exit() | |
if __name__=="__main__": | |
parser = argparse.ArgumentParser() #setup retrieving arg (if any) for --sp | |
parser.add_argument("--sp", | |
default="127.0.0.1", help="The ip on which Sonic Pi listens") | |
args = parser.parse_args() | |
spip=args.sp | |
print("Sonic Pi on ip",spip) | |
control(spip) |
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
#Test program to receive OSC messages from touch keyboard | |
#written by Robin Newman, April 2018 | |
#keys 0 to 7 olay a scale | |
#keys 8 and 9 shift up and down by an octave | |
#keys 10 and 11 choose sdiffernt synths | |
#the followng function detects value of wild card in osc address | |
define:parse_sync_address do |address| | |
v= get_event(address).to_s.split(",")[6] | |
if v != nil | |
return v[3..-2].split("/") | |
else | |
return ["error"] | |
end | |
end | |
use_synth :pluck #set initial synth | |
set :base,60 #set initial octave base note | |
live_loop :getkey do | |
use_real_time | |
b = sync "/osc/key*" #detect osc messages from ANY key using wild card * | |
if b[0] == 1 #if the key has been pressed | |
res = parse_sync_address "/osc/key*" #decode wild card value | |
puts "Decoded address information is",res | |
key = res[1][3..-1].to_i #extract key from res list and convert to integer | |
puts "key pressed was",key | |
if key<8 #key <8 used to play a note | |
scaleOffset=[0,2,4,5,7,9,11,12] | |
play note get(:base) + scaleOffset[key] | |
elsif key==8 #key 8 moves down an octave | |
base=get(:base) | |
base=[24,base-12].max #limits minimum value of base to 36 | |
set :base,base | |
elsif key==9 #key 9 moves up an octave | |
base=get(:base) | |
base=[84,base+12].min #limits max value of base to 84 | |
set :base,base | |
elsif key==10 #key 10 chooses :pluck synth | |
use_synth :pluck | |
elsif key==11 #key 11 chooses :piano synth | |
use_synth :piano | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment