SHARE
TWEET

chords.lua

HertzDevil Apr 17th, 2015 (edited) 273 Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. foldl = function(f, z, l)
  2.   for i = 1, #l do z = f(z, l[i]) end
  3.   return z
  4. end
  5.  
  6. foldl1 = function(f, l)
  7.   return foldl(f, table.remove(l, 1), l)
  8. end
  9.  
  10. foldr = function(f, z, l)
  11.   for i = #l, 1, -1 do z = f(l[i], z) end
  12.   return z
  13. end
  14.  
  15. foldr1 = function(f, l)
  16.   return foldr(f, table.remove(l), l)
  17. end
  18.  
  19. map = function(f, l)
  20.   local z = table.sub(l)
  21.   for k in pairs(l) do z[k] = f(rawget(l, k)) end
  22.   return z
  23. end
  24.  
  25. filter = function(pr, l)
  26.   return foldl(function(c, v)
  27.                  return pr(v) == true and table.insert(c, v) or c
  28.                end, {}, l)
  29. end
  30.  
  31. zipWith = function(f, a, b)
  32.   local n = {}
  33.   for i = 1, math.min(#a, #b) do n[i] = f(a[i], b[i]) end
  34.   return n
  35. end
  36.  
  37. elem = function(t, x)
  38.   return foldl(function(c, v) return c or v == x end, false, t)
  39. end
  40.  
  41. enum = function(i, j)
  42.   local z = {}
  43.   for k = i, j do z[#z + 1] = k end
  44.   return z
  45. end
  46.  
  47. table.join = function(a, b)
  48.   return foldl(function(c, v)
  49.                  table.insert(c, v); return c
  50.                end, a, b)
  51. end
  52.  
  53. table.sub = function(t, i, j)
  54.   local n = {}
  55.   i = i or 1
  56.   j = j or #t
  57.   table.move(t, i, j, 1, n)
  58.   return n
  59. end
  60.  
  61. table.rotate = function(t, k)
  62.   local n = {}
  63.   for i = 1, #t do
  64.     n[i] = t[(i + k - 1) % #t + 1]
  65.   end
  66.   return n
  67. end
  68.  
  69. tableStr = function(t, ind)
  70.   ind = ind or 0
  71.   local str = '{\n'
  72.   local mLen = string.len(#t) + 1
  73.   for k in pairs(t) do
  74.     local kStr = k
  75.     local xStr = rawget(t, k)
  76.     if type(xStr) == "boolean" then
  77.       xStr = xStr and "True" or "False"
  78.     elseif type(xStr) == "string" then
  79.       xStr = '"' .. xStr .. '"'
  80.     elseif type(xStr) == "table" then
  81.       xStr = tableStr(xStr, ind + 1)
  82.     end
  83.     str = str .. string.rep(' ', ind + mLen - string.len(kStr)) .. kStr .. ": " .. xStr .. '\n'
  84.   end
  85.   str = str .. string.rep(' ', ind) .. '}'
  86.   if ind == 0 then str = str .. '\n' end
  87.   return str
  88. end
  89.  
  90. tableStr2 = function(t)
  91.   local str = '{'
  92.   for k in pairs(t) do
  93.     local kStr = k
  94.     local xStr = rawget(t, k)
  95.     if type(xStr) == "boolean" then
  96.       xStr = xStr and "True" or "False"
  97.     elseif type(xStr) == "string" then
  98.       xStr = '"' .. xStr .. '"'
  99.     elseif type(xStr) == "table" then
  100.       xStr = tableStr2(xStr)
  101.     end
  102.     str = str .. ' ' .. kStr .. ": " .. xStr .. ","
  103.   end
  104.   return str == '{' and "{ }" or string.sub(str, 1, string.len(str) - 1) .. " }"
  105. end
  106.  
  107. cartProd = function(...)
  108.   local _cartProd = function(a, b)
  109.     local l = {}
  110.     for _, v1 in pairs(a) do
  111.       for _, v2 in pairs(b) do
  112.         local _v1 = v1
  113.         if type(_v1) ~= "table" then _v1 = {_v1} end
  114.         if type(v2) ~= "table" then v2 = {v2} end
  115.         l[#l + 1] = table.join(_v1, v2)
  116.       end
  117.     end
  118.     return l
  119.   end
  120.  
  121.   return foldr1(_cartProd, table.pack(...))
  122. end
  123.  
  124. lexgt = function(a, b)
  125.   for i = 1, #a do
  126.     if b[i] == nil then return 1
  127.     elseif a[i] > b[i] then return 1
  128.     elseif a[i] < b[i] then return -1
  129.     end
  130.   end
  131.   if #a < #b then return -1 end
  132.   return 0
  133. end
  134.  
  135. revlexgt = function(a, b)
  136.   if #a > #b then return 1 end
  137.   if #a < #b then return -1 end
  138.   for i = #a, 1, -1 do
  139.     if a[i] > b[i + #b - #a] then return 1
  140.     elseif a[i] < b[i + #b - #a] then return -1
  141.     end
  142.   end
  143.   return 0
  144. end
  145.  
  146. --main
  147.  
  148. DESC = [[
  149. Usage: lua chords.lua filename [option]
  150. Options:
  151.   -u: use unicode characters
  152.   -n: allow omitted extensions
  153.  -nl: allow omitted chord tones
  154.   -a: allow all avoid tones
  155.   -f: allow all chords without checking
  156.   -m: disallow all minor ninths
  157.   -s: disallow suspended chords]]
  158.  
  159. -- command line argument flags
  160. for i = 2, #arg do
  161.   if arg[i] == "-u" then bUnicode = true
  162.   elseif arg[i] == "-n" then bNoUpper = true
  163.   elseif arg[i] == "-nl" then bNoLower = true
  164.   elseif arg[i] == "-a" then bAllowAvoid = true
  165.   elseif arg[i] == "-f" then bAllowAll = true
  166.   elseif arg[i] == "-m" then bStrict = true
  167.   elseif arg[i] == "-s" then bNoSus = true end
  168. end
  169.  
  170. -- semitone offsets from chord root
  171. local SEMITONES = {{0}
  172.                   ,{ 4,  3,  5,  2}
  173.                   ,{ 7,  6,  8}
  174.                   ,{10,  9, 11}
  175.                   ,{14, 13, 15}
  176.                   ,{17, 16, 18}
  177.                   ,{21, 20}}
  178.  
  179. -- chord factor strings
  180. local CHORD_STR = {{"1"}
  181.                   ,{"3" , "b3" , "4"  , "2"}
  182.                   ,{"5" , "b5" , "#5"}
  183.                   ,{"b7", "6"  , "M7"}
  184.                   ,{"9" , "b9" , "#9"}
  185.                   ,{"11", "b11", "#11"}
  186.                   ,{"13", "b13"}}
  187.  
  188. if bNoUpper then for i = 5, 7 do table.insert(SEMITONES[i], -1) table.insert(CHORD_STR[i], "") end end
  189. if bNoLower then for i = 2, 4 do table.insert(SEMITONES[i], -1) table.insert(CHORD_STR[i], "") end end
  190. if bNoSus then
  191.   table.remove(SEMITONES[2], 4)
  192.   table.remove(SEMITONES[2], 3)
  193.   table.remove(CHORD_STR[2], 4)
  194.   table.remove(CHORD_STR[2], 3)
  195. end
  196.  
  197. local chordStr = function(l)
  198.   local sym = ""   -- chord symbol
  199.   local scale = {} -- scale table
  200.   local unalt = 4  -- extension index
  201.  
  202.   -- highest extension resulting from stacking thirds
  203.   while (l[unalt] ~= -1 and unalt < 7) do unalt = unalt + 1 end
  204.   -- highest unaltered extension in the chord
  205.   while ((l[unalt] ~= SEMITONES[unalt][1] or l[unalt] == -1) and unalt > 4) do unalt = unalt - 1 end
  206.   -- do not allow abbreviated extensions sixth chords
  207.   if l[4] == 9 then unalt = 4 end
  208.  
  209.   -- chord symbol for the lower structure
  210.   sym = (l[2] == 3 and "m" or "")
  211.      .. (l[4] == 11 and "M" or "")
  212.      .. (l[4] == 9 and "6" or l[4] ~= -1 and tostring(unalt * 2 - 1) or "")
  213.      .. (l[3] == 6 and "b5" or l[3] == 8 and "#5" or "")
  214.   if sym == "m6b5" then sym = "dim7" end
  215.  
  216.   -- append alterations
  217.   for i = 5, 7 do
  218.     if l[i] ~= -1 then
  219.       local acc = l[i] - SEMITONES[i][1]
  220.       if acc ~= 0 or i > unalt then
  221.         -- add correct accidentals
  222.         -- acc > 0 and string.rep('#', acc) or acc < 0 and string.rep('b', -acc) or '/'
  223.         sym = sym .. (acc == 1 and '#' or acc == -1 and 'b' or '/')
  224.                   .. tostring(i * 2 - 1)
  225.       end
  226.     end
  227.   end
  228.  
  229.   -- special cases for the lower structure
  230.   sym = sym .. (l[2] == 5 and "sus4" or l[2] == 2 and "sus2" or "")
  231.             .. (l[2] == -1 and "no3" or "")
  232.             .. (l[3] == -1 and "no5" or "")
  233.   if sym == "" then sym = "M" end
  234.   if sym == "mb5" then sym = "dim" end
  235.  
  236.   -- correctly label added chords
  237.   sym = string.gsub(sym, "^/", "add")
  238.   if foldl(function(a, b) return b ~= -1 or a end, false, table.sub(l, 5, 7)) then
  239.     sym = string.gsub(sym, "^b", "addb")
  240.     sym = string.gsub(sym, "^#", "add#")
  241.   end
  242.   if bUnicode then
  243.     sym = string.gsub(string.gsub(sym, "b", "\u{266D}"), "#", "\u{266F}")
  244.   end
  245.  
  246.   -- remove omitted notes from the chord table
  247.   l = filter(function(x) return x ~= -1 end, l)
  248.   -- reduce octaves from the chord tables
  249.   scale = map(function(x) return x % 12 end, table.sub(l))
  250.   -- sort the scale table
  251.   table.sort(scale)
  252.  
  253.   -- replace chord table with spellings of chord factors
  254.   local flatten = foldl(table.join, {}, CHORD_STR)
  255.   for k, v in pairs(foldl(table.join, {}, SEMITONES)) do
  256.     l = map(function(x)
  257.               return x == v and flatten[k] or x
  258.             end, l)
  259.   end
  260.  
  261.   -- reorder the scale table to the largest inversion
  262.   -- scale = foldl(function(l, k)
  263.   --                 local n = table.rotate(l, k)
  264.   --                 n = map(function(x) return (x - n[1]) % 12 end, n)
  265.   --                 return revlexgt(l, n) == 1 and l or n
  266.   --               end, scale, enum(1, #scale - 1))
  267.  
  268.   -- return a 2-tuple containing formatted string and scale table
  269.   return {string.format("%-24s%-24s%s", table.concat(l, "-"), table.concat(scale, " "), sym), scale}
  270. end
  271.  
  272. local validChord = function(l)
  273.   if bAllowAll then return true end
  274.  
  275.   -- strict definition of avoid note:
  276.   --  any chord tone forming a minor ninth above another chord tone
  277.   --  but allow b9 unless M7 is present
  278.   --  also allow b13 when both b3 and b7 are present
  279.   --  but disallow minor seconds (e.g. #9 and b11, #5 and 6) and octaves (e.g. b3 and #9)
  280.   local red = map(function(x) return x % 12 end, l)
  281.  
  282.   return (foldl1(function(a, b)
  283.                    return a and b
  284.                  end, zipWith(function(a, b)
  285.                                 return a == -1 or b == -1
  286.                                     or b - a ~= 13
  287.                                     or ((b == 13 and l[4] ~= 11)
  288.                                      or (b == 20 and l[2] == 3 and l[4] == 10))
  289.                                     and not bStrict
  290.                               end
  291.                              ,table.sub(l, 1, 3), table.sub(l, 5, 7)))
  292.       and not (l[2] == 2 and l[5] == 15) -- special case
  293.        or bAllowAvoid)
  294.      and foldl1(function(a, b)
  295.                   return a and b
  296.                 end, zipWith(function(a, b) return a == -1 or b == -1 or b - a > 1 end
  297.                             ,table.sub(l, 1, 6), table.sub(l, 2, 7)))
  298.      and foldl1(function(t, x)
  299.                   return t and (l[x] == -1 or not elem(table.sub(red, 1, x - 1), red[x]))
  300.                 end, enum(1, 7))
  301. end
  302.  
  303. local getChords = function()
  304.   return map(chordStr, filter(validChord, cartProd(table.unpack(SEMITONES))))
  305. end
  306.  
  307. if not arg[1] then
  308.   print(DESC)
  309. else
  310.   local ALL_CHORDS = {}
  311.  
  312.   for i = 7, bAllowAll and 2 or 3, -1 do
  313.     if SEMITONES[i][#SEMITONES[i]] == -1 then table.remove(SEMITONES[i]) end
  314.     local chordTable = getChords()
  315.    
  316.     -- this sorts the chords by the reduced scale
  317.     table.sort(chordTable, function(x, y) return lexgt(x[2], y[2]) == -1 end)
  318.    
  319.     file = io.open(arg[1] .. "_" .. tostring(i * 2 - 1) .. ".txt", "w")
  320.     file:write(tableStr(map(function(x) return x[1] end, chordTable)))
  321.     file:close()
  322.    
  323.     ALL_CHORDS = table.join(chordTable, ALL_CHORDS)
  324.     SEMITONES[i] = {-1}
  325.   end
  326.  
  327.   -- this sorts the chords by the reduced scale
  328.   table.sort(ALL_CHORDS, function(x, y) if #x[2] > #y[2] then return false end if #y[2] > #x[2] then return true end return lexgt(x[2], y[2]) == -1 end)
  329.  
  330.   -- this sorts the chords by the chord name
  331.   -- table.sort(ALL_CHORDS, function(x, y) return string.sub(x[1], 49) < string.sub(y[1], 49) end)
  332.  
  333.   file = io.open(arg[1] .. "_all.txt", "w")
  334.   file:write(tableStr(map(function(x) return x[1] end, ALL_CHORDS)))
  335.   file:close()
  336. end
RAW Paste Data
We use cookies for various purposes including analytics. By continuing to use Pastebin, you agree to our use of cookies as described in the Cookies Policy. OK, I Understand
Top