Skip to content

Instantly share code, notes, and snippets.

@RBilsland
Last active April 22, 2016 13:43
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save RBilsland/aa2bd2a9498cb6cb9187 to your computer and use it in GitHub Desktop.
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.
#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