The original reason I tried Sonic Pi was for its xenharmonic capabilities. I’ve written a bunch of variations of functions for building scales, but these are probably more useful for how we usually use Sonic Pi.
# Equal Temperament, edx = "equal divisions of x"
define :edx do
| mode = [2,2,1,2,2,2],
tet = 12,
equave = 2,
root = 60 |
notes = [midi_to_hz(root)]
for i in 0..(mode.length-1) do
notes = notes.append(notes[i] * equave ** (1.0*mode[i] / tet))
end
notes.ring.map { |n| hz_to_midi(n) }
end
# Just Intonation
define :ji do
| values = [9/8.0, 5/4.0, 4/3.0, 3/2.0, 5/3.0, 15/8.0],
root = 60 |
notes = [midi_to_hz(root)]
for i in 0..(values.length - 1) do
notes = notes.append(notes[0] * values[i])
end
notes.ring.map { |n| hz_to_midi(n) }
end
# convert midi to sample pitch: values
define :midi_pitch do |notes, center=nil|
if center.nil?
center = notes[0] + (notes[notes.length - 1] - notes[0]) / 2
end
notes.map { |n| n - center }
end
# Usage examples
use_synth :bass_foundation
# Bohlen-Pierce scale, equal tempered (13 equal divisions of tritave)
bp = edx([1]*23, 13, 3, 52)
# Bohlen-Pierce, Moll I (decatonic mode)
moll1 = edx([2,1,2,1,1,2,1,2,1], 13, 3, 60)
# Diatonic 12 TET
play_pattern(edx(), sustain: 0.75)
sleep 0.5
# Diatonic 12 JI
play_pattern(ji(), sustain: 0.75)
sleep 0.5
# `sample` example
10.times do
sample :bass_thick_c, pitch: midi_pitch(moll1).shuffle.tick(:tic1), amp: 1
sleep 1.5
end
sleep 2
# BP Just Intonation
bpj = ji([27/25.0,25/21.0,9/7.0,7/5.0,75/49.0,5/3.0,9/5.0,49/25.0,15/7.0,7/3.0,63/25.0,25/9.0,3/1.0],
48)
play_pattern_timed(bpj.values_at(6, 4, 8, 9) + [:r] + bpj.values_at(3, 2, 4, 6),
[1, 0.5, 0.5, 0.5, 0.5])
sleep 1
# 7 EDO/TET
t7 = edx([1]*10, 7, 2, 54)
play_pattern_timed(t7.values_at(6, 4, 8, 9) + [:r] + t7.values_at(3, 2, 4, 6),
[1, 0.5, 0.5, 0.5, 0.5])
Update: A function for finding rational approximations of values above 1.
val
is a float, the value to be approximated.
stop
is the largest allowed denominator.
match
is the minimum proximity to val
required for inclusion as an approximation.
Returns an array of size three arrays:
best_ratios[i][0]
is the numerator of the rational.
best_ratios[i][1]
is the denominator of the rational.
best_ratios[i][2]
is a decimal representation of the rational.
define :rationalize do |val, stop=17, match=0.1|
n = 2.0
d = 1.0
# Initialize
best = (n/d - val).abs
init = true
n2 = n
while init do
n2 += 1
new = (n2/d - val).abs
if (new > best)
init = false
else
n = n2
end
end
if best <= match
best_ratios = [[n,d,n/d]]
else
best_ratios = []
end
# Main loop
while d < stop do
update = false
n += 1
d += 1
ratio = n/d
if (ratio - val).abs < best
update = true
elsif ratio.abs < val
n += 1
ratio = n/d
if (ratio - val).abs < best
update = true
end
elsif ratio > val
n -= 1
ratio = n/d
if (ratio - val).abs < best
update = true
end
end
if update
best = (ratio - val).abs
if best <= match
best_ratios = best_ratios.append([n,d,ratio])
end
end
end
best_ratios
end