Skip to content

Instantly share code, notes, and snippets.

@rbnpi
Created April 16, 2018 10:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rbnpi/a28f57765d6e2282e71e6062a0ecf221 to your computer and use it in GitHub Desktop.
Save rbnpi/a28f57765d6e2282e71e6062a0ecf221 to your computer and use it in GitHub Desktop.
Touch Sensitive Keyboard for Sonic Pi using Adafruit MPR121
#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
#!/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)
#!/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)
#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

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment