Advertisement
HertzDevil

chords.lua

Apr 17th, 2015
413
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 10.21 KB | None | 0 0
  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
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement