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
```