Skip to content

Commit

Permalink
Merge pull request #1 from robmckinnon/einfach-einem
Browse files Browse the repository at this point in the history
v0.1.0 "einfach einem" release
  • Loading branch information
robmckinnon authored Oct 31, 2020
2 parents 5fc2130 + 2c677e5 commit be76553
Show file tree
Hide file tree
Showing 13 changed files with 1,241 additions and 1 deletion.
25 changes: 24 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,24 @@
# pitfalls
# Pitfalls

> item: There are _pitfalls_ in using equal division within which
<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
> to calculate 2-interval-patterns which the unwary traveler
<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
> will most certainly fall into.
<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
> - Erv Wilson [0]
### Changelog

#### v0.1.0 _einfach einem_

* Define scale as a sequence of `Ls` steps.
* Configure large `L` and small `s` step sizes, and scale `notes` length.
* Configure pitch using `base` note, `Hz` tuning, `octave`, and scale `mode` and `tonic` note.
* See scale intervals rendered next to sequence, e.g. `M3`, `P5`, etc.
* Arpeggiate notes in scale.
* Play scale notes using _Grid_ as a isomorphic keyboard.

### References

[0] [An Introduction to the Moments of Symmetry](http://anaphoria.com/wilsonintroMOS.html)
78 changes: 78 additions & 0 deletions lib/Intervals.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
Intervals = {}

function Intervals:new(scale)
local s = setmetatable({}, { __index = Intervals })

s.scale = scale
s.int_labels = {}
s.int_errors = {}
s.divisions = {}
s.ratios = {}

local division = 0
local intervals = {}
s.ratios[1] = 1
for i = 1,scale.length do
division = division + scale:step_value(i)
s.divisions[i] = division
s.ratios[i+1] = fn.ratio(division, scale.edivisions)

if (i < scale.length) then
local nearest = fn.nearest_interval2(s.ratios[i+1], ratiointervals)
local closeness = nearest[1]
local int_label = nearest[2]
s.int_labels[i+1] = int_label
s.int_errors[i+1] = closeness
if int_label ~= "" then
intervals[int_label] = s.ratios[i]
fn.dprint(i+1, int_label)
end
end
end

return s
end

function Intervals:ratio(i)
return self.ratios[ i ]
end

function Intervals:interval_label(i)
return self.int_labels[ i ]
end

function Intervals:interval_error(i)
return self.int_errors[ i ]
end

function Intervals:nearest_degree_to(r, threshold)
local min = 1
local degree = nil
for i, v in pairs(self.ratios) do
diff = math.abs( (r - v) / r )
if diff < min then
min = diff
degree = i
end
end
if threshold == nil then
return degree
else
return (min < threshold and degree) or 1
end
end

-- print('nearest P4', nearest_degree_to(4/3))
-- for lab, chord in pairs(chords) do
-- local match = true
-- for i, interval in pairs(chord) do
-- if intervals[interval] == nil then
-- match = false
-- end
-- end
-- if match then
-- fn.dprint(lab, chord)
-- end
-- end
-- print("--")

52 changes: 52 additions & 0 deletions lib/Pitches.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@

local BASE_OCTAVE = 4
Pitches = {}

function get_freqx(base_freq, edo, index, oct, base_octave)
local f = base_freq * fn.ratio(index-1, edo)
if (oct < base_octave) then
f = f / (2 ^ (base_octave - oct))
elseif (oct > base_octave) then
f = f * (2 ^ (oct - base_octave))
end
return f
end

function midi_to_hz(n, tuning)
return tuning * (2 ^ ((n - 69) / 12.0))
end

function Pitches:new(scale, intervals, tuning, midi_start)
local p = setmetatable({}, { __index = Pitches })
p.freqs = {}
p.degrees = {}
p.octdegfreqs = {}
local index = 0
local f = nil
local base_freq = midi_to_hz(midi_start, tuning)
for oct = 0,8 do
p.octdegfreqs[oct+1] = {}
f = fn.get_freq(base_freq, scale.edivisions, scale.tonic, oct, BASE_OCTAVE)
for deg = 1,scale.length do
index = index + 1
p.freqs[index] = f * intervals:ratio(deg)
p.degrees[index] = deg
p.octdegfreqs[oct+1][deg] = p.freqs[index]
end
end

return p
end

function Pitches:degree(index)
return self.degrees[index]
end

function Pitches:freq(index)
return self.freqs[index]
end

function Pitches:octdegfreq(oct, deg)
return self.octdegfreqs[oct+1][deg]
end

163 changes: 163 additions & 0 deletions lib/Scale.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
local MAX_STEPS = 16
local MIN_STEPS = 3
local MAX_STEP_SIZE = 1001
local L = 2
local S = 1
local LABELS = {"s", "L"}

Scale = {}

function Scale:new(large, small, sequence)
local s = setmetatable({}, { __index = Scale })
s.step = {}
s.stepbackup = {L,L,S,L,L,L,S,L,L,L,S,L,L,L,S,L}
for i = 1, #sequence do
local char = sequence:sub(i,i)
s.step[i] = (char == "L" and L) or S
end
s.large = large
s.small = small
s.length = #sequence
s.divisions = {}
s.edivisions = nil
s.tonic = 1
s.mode = 1
return s
end

function Scale:step_size(i)
return LABELS[self.step[ self:offset(i) ]]
end

function Scale:sequence()
local seq = {}
for i = 1, #self.step do
seq[i] = self:step_size(i)
end
return table.concat(seq,"")
end

function Scale:step_value(i)
return (self.step[ self:offset(i) ] == L and self.large) or self.small
end

function Scale:offset(i)
if (self.mode == 1) then
return i
else
local offset = ((self.mode - 1) + i)
if (offset > self.length) then
return offset % self.length
else
return offset
end
end
end

function Scale:set_large(l)
self.large = l
end

function Scale:set_small(s)
self.small = s
end

function Scale:set_mode(mode)
self.mode = mode
end

function Scale:change_mode(d)
local orig = self.mode
self.mode = util.clamp(self.mode+d, 1, self.length)
return orig ~= self.mode
end

function Scale:change_tonic(d)
local orig = self.tonic
self.tonic = util.clamp(self.tonic+d, 1, self.edivisions)
return orig ~= self.tonic
end

function Scale:update_edo()
local orig = self.edivisions
local edo = 0
for i = 1,self.length do
edo = edo + self:step_value(i)
end
self.edivisions = edo
local changed = orig ~= self.edivisions
if changed then
self.tonic = util.clamp(self.tonic, 1, self.edivisions)
end
return changed
end

function Scale:change_step(d, i)
local index = self:offset(i)
local orig = self.step[ index ]
self.step[ index ] = util.clamp(self.step[ index ]+d, S, L)
self.stepbackup[ index ] = self.step[ index ]
local changed = orig ~= self.step[ index ]
if changed then
self:update_edo()
end
return changed
end

function Scale:change_large(d)
local orig = self.large
-- local value = self.large + d
-- while fn.gcd(self.small,value)~=1 do
-- value = value + d
-- end
-- maxi = (d==1 and value) or (self.small + 1)
self:set_large(util.clamp(self.large + d, self.small + 1, self.large + 1))

local changed = self.large~=orig
if changed then
self:update_edo()
end
return changed
end

function Scale:change_small(d)
local orig = self.small
local value = self.small + d
-- while fn.gcd(value,self.large)~=1 do
-- value = value + d
-- end

self:set_small(util.clamp(value, 1, self.large - 1))

local changed = self.small~=orig
if changed then
self:update_edo()
end
return changed
end

function Scale:change_length(d)
local orig = self.length
if d == 1 then
self.length = util.clamp(self.length + 1, 1, MAX_STEPS)
end
if d == -1 then
self.length = util.clamp(self.length - 1, MIN_STEPS, self.length)
end

local changed = self.length~=orig
if changed then
self.mode = 1
if d == 1 then
self.step[self.length] = self.stepbackup[self.length] or L
end
if d == -1 then
if (self.length > MIN_STEPS) then
table.remove(self.step, #self.step)
end
end
self:update_edo()
end
return changed
end

65 changes: 65 additions & 0 deletions lib/ScaleIntervals.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
-- maps ratio labels to ratio fractions
-- ratiointervals = include("pitfalls/lib/ratios")

-- represents intervals for scale in given mode
-- include("pitfalls/lib/Intervals")

ScaleIntervals = {}

-- Holds intervals starting from each degree of the scale.

function ScaleIntervals:new(scale)
local s = setmetatable({}, { __index = ScaleIntervals })

local sscale = Scale:new(scale.large, scale.small, scale:sequence())
sscale:update_edo()
s.degree_to_mode = {}
s.degree_intervals = {}
local mode = nil
local tmp = nil
for deg = 1,scale.length do
fn.dprint("deg", deg)
mode = (scale.mode + deg - 1) % scale.length
if mode == 0 then
mode = scale.length
end
fn.dprint("mode", mode)
s.degree_to_mode[deg] = mode
sscale:set_mode(mode)
fn.dprint("smode", sscale.mode)
fn.dprint(":sequence()", sscale:sequence())
fn.dprint(Intervals.new)
tmp = Intervals:new(sscale)
fn.dprint("tmp", tmp)
s.degree_intervals[deg] = tmp
fn.dprint("int", s.degree_intervals[deg])
end

return s
end

function ScaleIntervals:intervals(deg)
deg = deg or 1
return self.degree_intervals[deg]
end

function ScaleIntervals:ratio(i, deg)
return self:intervals(deg).ratios[ i ]
end

function ScaleIntervals:interval_labels(deg)
return self:intervals(deg).int_labels
end

function ScaleIntervals:interval_label(i, deg)
return self:intervals(deg).int_labels[ i ]
end

function ScaleIntervals:interval_error(i, deg)
return self:intervals(deg).int_errors[ i ]
end

function ScaleIntervals:nearest_degree_to(r, threshold)
return self.degree_intervals[1]:nearest_degree_to(r, threshold)
end

Loading

0 comments on commit be76553

Please sign in to comment.