Last active
April 22, 2016 13:43
-
-
Save RBilsland/aa2bd2a9498cb6cb9187 to your computer and use it in GitHub Desktop.
A Ruby class (and example of use) for Sonic Pi. The Instrument Bank class allows multiple samples of an instrument playing different notes to be loaded and then by changing the rate of playback to allow a wide range of notes to be produced.
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
#Set a couple of system settings | |
set_sched_ahead_time! 1 | |
use_debug false | |
#Class for playing a single note | |
class SingleNote | |
def initialize(sonic_pi, sample, rate, distance, options_hash) | |
@sonic_pi = sonic_pi | |
@sample = sample | |
@rate = rate | |
@distance = distance | |
@attack = options_hash[:attack] | |
@sustain = options_hash[:sustain] | |
@release = options_hash[:release] | |
@amp = options_hash[:amp] | |
@pan = options_hash[:pan] | |
end | |
def sample | |
@sample | |
end | |
def distance | |
@distance | |
end | |
def update(sample, rate, distance) | |
@sample = sample | |
@rate = rate | |
@distance = distance | |
end | |
def options(options_hash) | |
@attack = options_hash[:attack] | |
@sustain = options_hash[:sustain] | |
@release = options_hash[:release] | |
@amp = options_hash[:amp] | |
@pan = options_hash[:pan] | |
end | |
def play(duration) | |
if @sample.is_a?(String) | |
@sonic_pi.sample @sonic_pi.current_sample_pack + '/' + @sample, rate: @rate, attack: (duration * @attack), sustain: | |
(duration * @sustain), release: (duration * @release), amp: @amp, pan: @pan | |
else | |
@sonic_pi.sample @sample, rate: @rate, attack: (duration * @attack), sustain: (duration * @sustain), release: | |
(duration * @release), amp: @amp, pan: @pan | |
end | |
end | |
def dump | |
@sonic_pi.puts @sample.to_s + ', ' + @rate.to_s + ', ' + @distance.to_s + ', ' + @attack.to_s + ', ' + @sustain.to_s + | |
', ' + @release.to_s + ', ' + @amp.to_s + ', ' + @pan.to_s | |
end | |
end | |
#Class for managing a complete range of notes | |
class InstrumentBank | |
@@semitone_step = 2.0 ** (1.0 / 12) | |
@@options_defaults = { | |
attack: 0.1, | |
sustain: 0.7, | |
release: 0.2, | |
amp: 1.0, | |
pan: 0 | |
} | |
def initialize(sonic_pi, options_hash = {}) | |
@sonic_pi = sonic_pi | |
@options_hash = @@options_defaults.merge(options_hash) | |
@notes = Array.new(127) | |
for count in 0..127 | |
@notes[count] = SingleNote.new(@sonic_pi, '', 0, 999, @options_hash) | |
end | |
end | |
def add(note, sample) | |
if note.is_a?(Numeric) | |
midi_note = note | |
else | |
midi_note = @sonic_pi.note(note) | |
end | |
#Update base sample | |
@notes[midi_note].update(sample, 1, 0) | |
#Extend base sample down | |
position = midi_note - 1 | |
distance = 1 | |
rate = 1 / @@semitone_step | |
while position > -1 and @notes[position].distance > distance do | |
@notes[position].update(sample, rate, distance) | |
position = position - 1 | |
distance = distance + 1 | |
rate = rate / @@semitone_step | |
end | |
#Extend base sample up | |
position = midi_note + 1 | |
distance = 1 | |
rate = @@semitone_step | |
while position < 128 and @notes[position].distance > distance do | |
@notes[position].update(sample, rate, distance) | |
position = position + 1 | |
distance = distance + 1 | |
rate = rate * @@semitone_step | |
end | |
if sample.is_a?(String) | |
@sonic_pi.load_sample @sonic_pi.current_sample_pack + '/' + sample | |
else | |
@sonic_pi.load_sample sample | |
end | |
end | |
def play(note, duration) | |
if note.is_a?(Array) | |
note.each do |single_note| | |
play(single_note, duration) | |
end | |
else | |
if note.is_a?(Numeric) | |
midi_note = note | |
else | |
midi_note = @sonic_pi.note(note) | |
end | |
if @notes[midi_note].sample != '' | |
@notes[midi_note].play(duration) | |
end | |
end | |
end | |
def options(options_hash = {}) | |
options_hash = @options_hash.merge(options_hash) | |
for count in 0..127 | |
@notes[count].options(options_hash) | |
end | |
end | |
def dump | |
for count in 0..127 | |
@notes[count].dump | |
end | |
end | |
end | |
#The Entertainer | |
#Set location of instrument samples | |
use_sample_pack '/Users/Robert/Music' | |
#Load grand piano | |
GrandPiano = InstrumentBank.new(self) | |
GrandPiano.add(33, 'piano-f-a1.wav') | |
GrandPiano.add(45, 'piano-f-a2.wav') | |
GrandPiano.add(57, 'piano-f-a3.wav') | |
GrandPiano.add(69, 'piano-f-a4.wav') | |
GrandPiano.add(81, 'piano-f-a5.wav') | |
GrandPiano.add(93, 'piano-f-a6.wav') | |
GrandPiano.add(105, 'piano-f-a7.wav') | |
GrandPiano.options(release: 0.5, amp: 0.5, pan: -0.8) | |
#Load clarinet | |
Clarinet = InstrumentBank.new(self) | |
Clarinet.add(:B3, :clarinet_b3) | |
Clarinet.add(:D4, :clarinet_d4) | |
Clarinet.add(:F4, :clarinet_f4) | |
Clarinet.add(:Gs4, :clarinet_gs4) | |
Clarinet.add(:B4, :clarinet_b4) | |
Clarinet.add(:B5, :clarinet_b5) | |
Clarinet.options(amp: 1.25, pan: 0.8) | |
#Set speed | |
bpm = 180 | |
#Define note and rest durations | |
dottedSemibreve = 24 | |
semibreve = 16 | |
dottedMinim = 12 | |
minim = 8 | |
dottedCrotchet = 6 | |
crotchet = 4 | |
dottedQuaver = 3 | |
quaver = 2 | |
#Define the base unit of time | |
baseUnit = 15.0 / bpm | |
#Define the clarinet score | |
ClarinetScore = [ | |
#Bar 1 | |
[:Rest, semibreve], | |
#Bar 2 | |
[:Rest, semibreve], | |
#Bar 3 | |
[:Rest, semibreve], | |
#Bar 4 | |
[:Rest, minim], | |
[:Rest, crotchet], | |
[:C3, quaver], | |
[:Cs3, quaver], | |
#Bar 5 | |
[:D3,quaver], | |
[:Bb3,crotchet], | |
[:D3,quaver], | |
[:Bb3,crotchet], | |
[:D3,quaver], | |
[:Bb3,quaver + minim], | |
#Bar 6 | |
# Tied above [:Bb3, minim], | |
[:Bb3, quaver], | |
[:Bb3, quaver], | |
[:C4, quaver], | |
[:Cs4, quaver], | |
#Bar 7 | |
[:D4, quaver], | |
[:Bb3, quaver], | |
[:C4, quaver], | |
[:D4, crotchet], | |
[:A3, quaver], | |
[:C4, crotchet], | |
#Bar 8 | |
[:Bb3, minim], | |
[:Rest, crotchet], | |
[:C3, quaver], | |
[:Cs3, quaver], | |
#Bar 9 | |
[:D3,quaver], | |
[:Bb3,crotchet], | |
[:D3,quaver], | |
[:Bb3,crotchet], | |
[:D3,quaver], | |
[:Bb3,quaver + dottedMinim], | |
#Bar 10 | |
# Tied above [:Bb3, dottedMinim], | |
[:G3, quaver], | |
[:F3, quaver], | |
#Bar 11 | |
[:Eb3, quaver], | |
[:G3, quaver], | |
[:Bb3, quaver], | |
[:D4, crotchet], | |
[:C4, quaver], | |
[:Bb3, quaver], | |
[:G3, quaver], | |
#Bar 12 | |
[:C4, minim], | |
[:Rest, crotchet], | |
[:C3, quaver], | |
[:Cs3, quaver], | |
#Bar 13 | |
[:D3,quaver], | |
[:Bb3,crotchet], | |
[:D3,quaver], | |
[:Bb3,crotchet], | |
[:D3,quaver], | |
[:Bb3,quaver + minim + quaver], | |
#Bar 14 | |
# Tied above [:Bb3, minim], | |
# Tied above [:Bb3, quaver], | |
[:Bb3, quaver], | |
[:C4, quaver], | |
[:Cs4, quaver], | |
#Bar 15 | |
[:D4, quaver], | |
[:Bb3, quaver], | |
[:C4, quaver], | |
[:D4, crotchet], | |
[:A3, quaver], | |
[:C4, crotchet], | |
#Bar 16 | |
[:Bb3, dottedMinim], | |
[:Bb3, quaver], | |
[:C4, quaver], | |
#Bar 17 | |
[:D4, quaver], | |
[:Bb3, quaver], | |
[:C4, quaver], | |
[:D4, crotchet], | |
[:Bb3, quaver], | |
[:C4, quaver], | |
[:Bb3, quaver], | |
#Bar 18 | |
[:D4, quaver], | |
[:Bb3, quaver], | |
[:C4, quaver], | |
[:D4, crotchet], | |
[:Bb3, quaver], | |
[:C4, quaver], | |
[:Bb3, quaver], | |
#Bar 19 | |
[:D4, quaver], | |
[:Bb3, quaver], | |
[:C4, quaver], | |
[:D4, crotchet], | |
[:A3, quaver], | |
[:C4, crotchet], | |
#Bar 20 | |
[:Bb3, minim], | |
[:Bb3, crotchet], | |
[:Rest, crotchet] | |
] | |
#Define the grand piano (right hand) score | |
GrandPianoRightHandScore = [ | |
#Bar 1 | |
[:C5, quaver], | |
[:D5, quaver], | |
[:Bb4, quaver], | |
[:G4, crotchet], | |
[:A4, quaver], | |
[:F4, crotchet], | |
#Bar 2 | |
[:C4, quaver], | |
[:D4, quaver], | |
[:Bb3, quaver], | |
[:G3, crotchet], | |
[:A3, quaver], | |
[:F3, crotchet], | |
#Bar 3 | |
[:C3, quaver], | |
[:D3, quaver], | |
[:Bb2, quaver], | |
[:G2, crotchet], | |
[:A2, quaver], | |
[:G2, quaver], | |
[:Gb2, quaver], | |
#Bar 4 | |
[:F2, crotchet], | |
[:Rest, crotchet], | |
[[:F4, :A3], crotchet], | |
[:Rest, crotchet], | |
#Bar 5 | |
[:Rest, crotchet], | |
[[:D3, :F3], crotchet], | |
[:Rest, crotchet], | |
[[:D3, :Ab3], crotchet], | |
#Bar 6 | |
[:Rest, crotchet], | |
[[:Eb3, :G3], crotchet], | |
[:Rest, crotchet], | |
[[:D3, :F4], crotchet], | |
#Bar 7 | |
[:Rest, crotchet], | |
[[:Bb3, :D3], crotchet], | |
[:Rest, crotchet], | |
[[:A3, :Eb3], crotchet], | |
#Bar 8 | |
[:Rest, crotchet], | |
[[:D3, :F3], crotchet], | |
[[:D3, :F3], crotchet], | |
[:Rest, crotchet], | |
#Bar 9 | |
[:Rest, crotchet], | |
[[:D3, :F3], crotchet], | |
[:Rest, crotchet], | |
[[:D3, :Ab3], crotchet], | |
#Bar 10 | |
[:Rest, crotchet], | |
[[:Eb3, :G3], crotchet], | |
[:Rest, crotchet], | |
[[:G3, :B2], crotchet], | |
#Bar 11 | |
[:Rest, crotchet], | |
[[:Bb3, :D3], crotchet], | |
[:Rest, crotchet], | |
[[:Bb3, :D3], crotchet], | |
#Bar 12 | |
[[:A3, :Eb3], minim], | |
[:Rest, minim], | |
#Bar 13 | |
[:Rest, crotchet], | |
[[:D3, :F3], crotchet], | |
[:Rest, crotchet], | |
[[:D3, :Ab3], crotchet], | |
#Bar 14 | |
[:Rest, crotchet], | |
[[:Eb3, :G3], crotchet], | |
[:Rest, crotchet], | |
[[:D3, :F4], crotchet], | |
#Bar 15 | |
[:Rest, crotchet], | |
[[:D3, :Bb3], crotchet], | |
[:Rest, crotchet], | |
[[:A3, :E3], crotchet], | |
#Bar 16 | |
[:Rest, crotchet], | |
[[:D3, :F3], crotchet], | |
[[:D3, :F3], crotchet], | |
[:Rest, crotchet], | |
#Bar 17 | |
[:Rest, crotchet], | |
[[:Bb3, :D3], crotchet], | |
[:Rest, crotchet], | |
[[:Ab3, :D3], crotchet], | |
#Bar 18 | |
[:Rest, crotchet], | |
[[:Eb3, :G3], crotchet], | |
[:Rest, crotchet], | |
[[:Eb3, :Gb3], crotchet], | |
#Bar 19 | |
[:Rest, crotchet], | |
[[:D3, :F3], crotchet], | |
[:Rest, crotchet], | |
[[:Eb3, :C4], crotchet], | |
#Bar 20 | |
[[:D3, :Bb3], minim], | |
[[:D4, :D4], crotchet], | |
[:Rest, crotchet] | |
] | |
#Define the grand piano (left hand) score | |
GrandPianoLeftHandScore = [ | |
#Bar 1 | |
[:Rest, semibreve], | |
#Bar 2 | |
[:Rest, semibreve], | |
#Bar 3 | |
[:Rest, semibreve], | |
#Bar 4 | |
[:Rest, minim], | |
[:F2, crotchet], | |
[:Rest, crotchet], | |
#Bar 5 | |
[:Bb1, crotchet], | |
[:Rest, crotchet], | |
[:F2, crotchet], | |
[:Rest, crotchet], | |
#Bar 6 | |
[:Eb2, crotchet], | |
[:Rest, crotchet], | |
[:D2, crotchet], | |
[:Rest, crotchet], | |
#Bar 7 | |
[:F2, crotchet], | |
[:Rest, crotchet], | |
[:F2, crotchet], | |
[:Rest, crotchet], | |
#Bar 8 | |
[:Bb1, crotchet], | |
[:Rest, crotchet], | |
[:Rest, minim], | |
#Bar 9 | |
[:Bb1, crotchet], | |
[:Rest, crotchet], | |
[:F2, crotchet], | |
[:Rest, crotchet], | |
#Bar 10 | |
[:Eb2, crotchet], | |
[:Rest, crotchet], | |
[:D2, crotchet], | |
[:Db2, crotchet], | |
#Bar 11 | |
[:C2, crotchet], | |
[:Rest, crotchet], | |
[:C2, crotchet], | |
[:Rest, crotchet], | |
#Bar 12 | |
[:Rest, crotchet], | |
[:F1, crotchet], | |
[:G1, crotchet], | |
[:A1, crotchet], | |
#Bar 13 | |
[:Bb1, crotchet], | |
[:Rest, crotchet], | |
[:F2, crotchet], | |
[:Rest, crotchet], | |
#Bar 14 | |
[:Eb2, crotchet], | |
[:Rest, crotchet], | |
[:D2, crotchet], | |
[:Rest, crotchet], | |
#Bar 15 | |
[:F2, crotchet], | |
[:Rest, crotchet], | |
[:F2, crotchet], | |
[:Rest, crotchet], | |
#Bar 16 | |
[:Bb1, crotchet], | |
[:Rest, crotchet], | |
[:Rest, minim], | |
#Bar 17 | |
[:B2, minim], | |
[:Ab2, minim], | |
#Bar 18 | |
[:G2, minim], | |
[:Gb2, minim], | |
#Bar 19 | |
[:F2, minim], | |
[:F2, minim], | |
#Bar 20 | |
[:Bb2, crotchet], | |
[:F2, crotchet], | |
[:Bb1, crotchet], | |
[:Rest, crotchet] | |
] | |
#Set running flags for each score | |
ClarinetRunning = true | |
GrandPianoRightHandRunning = true | |
GrandPianoLeftHandRunning = true | |
#Set thread running to keep everything in time | |
in_thread do | |
while ClarinetRunning or | |
GrandPianoRightHandRunning or | |
GrandPianoLeftHandRunning do | |
cue :tick | |
sleep baseUnit | |
end | |
end | |
#Set thread running the clarinet score | |
in_thread do | |
tickCount = 0 | |
musicPos = -1 | |
while musicPos < ClarinetScore.length - 1 do | |
sync :tick | |
if tickCount == 0 | |
musicPos = musicPos + 1 | |
tickCount = ClarinetScore[musicPos][1].to_i | |
if ClarinetScore[musicPos][0] != :Rest | |
Clarinet.play(ClarinetScore[musicPos][0], tickCount * baseUnit) | |
end | |
end | |
tickCount = tickCount - 1 | |
end | |
ClarinetRunning = false | |
end | |
#Set thread running the grand piano (right hand) score | |
in_thread do | |
tickCount = 0 | |
musicPos = -1 | |
while musicPos < GrandPianoRightHandScore.length - 1 do | |
sync :tick | |
if tickCount == 0 | |
musicPos = musicPos + 1 | |
tickCount = GrandPianoRightHandScore[musicPos][1].to_i | |
if GrandPianoRightHandScore[musicPos][0] != :Rest | |
GrandPiano.play(GrandPianoRightHandScore[musicPos][0], tickCount * baseUnit) | |
end | |
end | |
tickCount = tickCount - 1 | |
end | |
GrandPianoRightHandRunning = false | |
end | |
#Set thread running the grand piano (left hand) score | |
in_thread do | |
tickCount = 0 | |
musicPos = -1 | |
while musicPos < GrandPianoLeftHandScore.length - 1 do | |
sync :tick | |
if tickCount == 0 | |
musicPos = musicPos + 1 | |
tickCount = GrandPianoLeftHandScore[musicPos][1].to_i | |
if GrandPianoLeftHandScore[musicPos][0] != :Rest | |
GrandPiano.play(GrandPianoLeftHandScore[musicPos][0], tickCount * baseUnit) | |
end | |
end | |
tickCount = tickCount - 1 | |
end | |
GrandPianoLeftHandRunning = false | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment