HertzDevil icon

chords.lua

HertzDevil | PRO | 04/17/15 09:07:30 AM UTC | 0 ⭐ | 781 👁️ | Never ⏰ | []
Lua |

10.21 KB

|

None

|

0 👍

/

0 👎

foldl = function(f, z, l)
  for i = 1, #l do z = f(z, l[i]) end
  return z
end
 
foldl1 = function(f, l)
  return foldl(f, table.remove(l, 1), l)
end
 
foldr = function(f, z, l)
  for i = #l, 1, -1 do z = f(l[i], z) end
  return z
end
 
foldr1 = function(f, l)
  return foldr(f, table.remove(l), l)
end
 
map = function(f, l)
  local z = table.sub(l)
  for k in pairs(l) do z[k] = f(rawget(l, k)) end
  return z
end
 
filter = function(pr, l)
  return foldl(function(c, v)
                 return pr(v) == true and table.insert(c, v) or c
               end, {}, l)
end
 
zipWith = function(f, a, b)
  local n = {}
  for i = 1, math.min(#a, #b) do n[i] = f(a[i], b[i]) end
  return n
end
 
elem = function(t, x)
  return foldl(function(c, v) return c or v == x end, false, t)
end
 
enum = function(i, j)
  local z = {}
  for k = i, j do z[#z + 1] = k end
  return z
end
 
table.join = function(a, b)
  return foldl(function(c, v)
                 table.insert(c, v); return c
               end, a, b)
end
 
table.sub = function(t, i, j)
  local n = {}
  i = i or 1
  j = j or #t
  table.move(t, i, j, 1, n)
  return n
end
 
table.rotate = function(t, k)
  local n = {}
  for i = 1, #t do
    n[i] = t[(i + k - 1) % #t + 1]
  end
  return n
end
 
tableStr = function(t, ind)
  ind = ind or 0
  local str = '{\n'
  local mLen = string.len(#t) + 1
  for k in pairs(t) do
    local kStr = k
    local xStr = rawget(t, k)
    if type(xStr) == "boolean" then
      xStr = xStr and "True" or "False"
    elseif type(xStr) == "string" then
      xStr = '"' .. xStr .. '"'
    elseif type(xStr) == "table" then
      xStr = tableStr(xStr, ind + 1)
    end
    str = str .. string.rep(' ', ind + mLen - string.len(kStr)) .. kStr .. ": " .. xStr .. '\n'
  end
  str = str .. string.rep(' ', ind) .. '}'
  if ind == 0 then str = str .. '\n' end
  return str
end
 
tableStr2 = function(t)
  local str = '{'
  for k in pairs(t) do
    local kStr = k
    local xStr = rawget(t, k)
    if type(xStr) == "boolean" then
      xStr = xStr and "True" or "False"
    elseif type(xStr) == "string" then
      xStr = '"' .. xStr .. '"'
    elseif type(xStr) == "table" then
      xStr = tableStr2(xStr)
    end
    str = str .. ' ' .. kStr .. ": " .. xStr .. ","
  end
  return str == '{' and "{ }" or string.sub(str, 1, string.len(str) - 1) .. " }"
end
 
cartProd = function(...)
  local _cartProd = function(a, b)
    local l = {}
    for _, v1 in pairs(a) do
      for _, v2 in pairs(b) do
        local _v1 = v1
        if type(_v1) ~= "table" then _v1 = {_v1} end
        if type(v2) ~= "table" then v2 = {v2} end
        l[#l + 1] = table.join(_v1, v2)
      end
    end
    return l
  end
  
  return foldr1(_cartProd, table.pack(...))
end
 
lexgt = function(a, b)
  for i = 1, #a do
    if b[i] == nil then return 1
    elseif a[i] > b[i] then return 1
    elseif a[i] < b[i] then return -1
    end
  end
  if #a < #b then return -1 end
  return 0
end
 
revlexgt = function(a, b)
  if #a > #b then return 1 end
  if #a < #b then return -1 end
  for i = #a, 1, -1 do
    if a[i] > b[i + #b - #a] then return 1
    elseif a[i] < b[i + #b - #a] then return -1
    end
  end
  return 0
end
 
--main
 
DESC = [[
Usage: lua chords.lua filename [option]
Options: 
  -u: use unicode characters
  -n: allow omitted extensions
 -nl: allow omitted chord tones
  -a: allow all avoid tones
  -f: allow all chords without checking
  -m: disallow all minor ninths
  -s: disallow suspended chords]]
 
-- command line argument flags
for i = 2, #arg do
  if arg[i] == "-u" then bUnicode = true
  elseif arg[i] == "-n" then bNoUpper = true
  elseif arg[i] == "-nl" then bNoLower = true
  elseif arg[i] == "-a" then bAllowAvoid = true
  elseif arg[i] == "-f" then bAllowAll = true
  elseif arg[i] == "-m" then bStrict = true
  elseif arg[i] == "-s" then bNoSus = true end
end
 
-- semitone offsets from chord root
local SEMITONES = {{0}
                  ,{ 4,  3,  5,  2}
                  ,{ 7,  6,  8}
                  ,{10,  9, 11}
                  ,{14, 13, 15}
                  ,{17, 16, 18}
                  ,{21, 20}}
 
-- chord factor strings
local CHORD_STR = {{"1"}
                  ,{"3" , "b3" , "4"  , "2"}
                  ,{"5" , "b5" , "#5"}
                  ,{"b7", "6"  , "M7"}
                  ,{"9" , "b9" , "#9"}
                  ,{"11", "b11", "#11"}
                  ,{"13", "b13"}}
 
if bNoUpper then for i = 5, 7 do table.insert(SEMITONES[i], -1) table.insert(CHORD_STR[i], "") end end
if bNoLower then for i = 2, 4 do table.insert(SEMITONES[i], -1) table.insert(CHORD_STR[i], "") end end
if bNoSus then
  table.remove(SEMITONES[2], 4)
  table.remove(SEMITONES[2], 3)
  table.remove(CHORD_STR[2], 4)
  table.remove(CHORD_STR[2], 3)
end
 
local chordStr = function(l)
  local sym = ""   -- chord symbol
  local scale = {} -- scale table
  local unalt = 4  -- extension index
  
  -- highest extension resulting from stacking thirds
  while (l[unalt] ~= -1 and unalt < 7) do unalt = unalt + 1 end
  -- highest unaltered extension in the chord
  while ((l[unalt] ~= SEMITONES[unalt][1] or l[unalt] == -1) and unalt > 4) do unalt = unalt - 1 end
  -- do not allow abbreviated extensions sixth chords
  if l[4] == 9 then unalt = 4 end
  
  -- chord symbol for the lower structure
  sym = (l[2] == 3 and "m" or "")
     .. (l[4] == 11 and "M" or "")
     .. (l[4] == 9 and "6" or l[4] ~= -1 and tostring(unalt * 2 - 1) or "")
     .. (l[3] == 6 and "b5" or l[3] == 8 and "#5" or "")
  if sym == "m6b5" then sym = "dim7" end
  
  -- append alterations
  for i = 5, 7 do
    if l[i] ~= -1 then
      local acc = l[i] - SEMITONES[i][1]
      if acc ~= 0 or i > unalt then
        -- add correct accidentals
        -- acc > 0 and string.rep('#', acc) or acc < 0 and string.rep('b', -acc) or '/'
        sym = sym .. (acc == 1 and '#' or acc == -1 and 'b' or '/')
                  .. tostring(i * 2 - 1)
      end
    end
  end
  
  -- special cases for the lower structure
  sym = sym .. (l[2] == 5 and "sus4" or l[2] == 2 and "sus2" or "")
            .. (l[2] == -1 and "no3" or "")
            .. (l[3] == -1 and "no5" or "")
  if sym == "" then sym = "M" end
  if sym == "mb5" then sym = "dim" end
  
  -- correctly label added chords
  sym = string.gsub(sym, "^/", "add")
  if foldl(function(a, b) return b ~= -1 or a end, false, table.sub(l, 5, 7)) then
    sym = string.gsub(sym, "^b", "addb")
    sym = string.gsub(sym, "^#", "add#")
  end
  if bUnicode then
    sym = string.gsub(string.gsub(sym, "b", "\u{266D}"), "#", "\u{266F}")
  end
  
  -- remove omitted notes from the chord table
  l = filter(function(x) return x ~= -1 end, l)
  -- reduce octaves from the chord tables
  scale = map(function(x) return x % 12 end, table.sub(l))
  -- sort the scale table
  table.sort(scale)
  
  -- replace chord table with spellings of chord factors
  local flatten = foldl(table.join, {}, CHORD_STR)
  for k, v in pairs(foldl(table.join, {}, SEMITONES)) do
    l = map(function(x)
              return x == v and flatten[k] or x
            end, l)
  end
  
  -- reorder the scale table to the largest inversion
  -- scale = foldl(function(l, k)
  --                 local n = table.rotate(l, k)
  --                 n = map(function(x) return (x - n[1]) % 12 end, n)
  --                 return revlexgt(l, n) == 1 and l or n
  --               end, scale, enum(1, #scale - 1))
  
  -- return a 2-tuple containing formatted string and scale table
  return {string.format("%-24s%-24s%s", table.concat(l, "-"), table.concat(scale, " "), sym), scale}
end
 
local validChord = function(l)
  if bAllowAll then return true end
  
  -- strict definition of avoid note:
  --  any chord tone forming a minor ninth above another chord tone
  --  but allow b9 unless M7 is present
  --  also allow b13 when both b3 and b7 are present
  --  but disallow minor seconds (e.g. #9 and b11, #5 and 6) and octaves (e.g. b3 and #9)
  local red = map(function(x) return x % 12 end, l)
  
  return (foldl1(function(a, b)
                   return a and b
                 end, zipWith(function(a, b)
                                return a == -1 or b == -1
                                    or b - a ~= 13
                                    or ((b == 13 and l[4] ~= 11)
                                     or (b == 20 and l[2] == 3 and l[4] == 10))
                                    and not bStrict
                              end
                             ,table.sub(l, 1, 3), table.sub(l, 5, 7)))
      and not (l[2] == 2 and l[5] == 15) -- special case
       or bAllowAvoid)
     and foldl1(function(a, b)
                  return a and b
                end, zipWith(function(a, b) return a == -1 or b == -1 or b - a > 1 end
                            ,table.sub(l, 1, 6), table.sub(l, 2, 7)))
     and foldl1(function(t, x)
                  return t and (l[x] == -1 or not elem(table.sub(red, 1, x - 1), red[x]))
                end, enum(1, 7))
end
 
local getChords = function()
  return map(chordStr, filter(validChord, cartProd(table.unpack(SEMITONES))))
end
 
if not arg[1] then
  print(DESC)
else
  local ALL_CHORDS = {}
  
  for i = 7, bAllowAll and 2 or 3, -1 do
    if SEMITONES[i][#SEMITONES[i]] == -1 then table.remove(SEMITONES[i]) end
    local chordTable = getChords()
    
    -- this sorts the chords by the reduced scale
    table.sort(chordTable, function(x, y) return lexgt(x[2], y[2]) == -1 end)
    
    file = io.open(arg[1] .. "_" .. tostring(i * 2 - 1) .. ".txt", "w")
    file:write(tableStr(map(function(x) return x[1] end, chordTable)))
    file:close()
    
    ALL_CHORDS = table.join(chordTable, ALL_CHORDS)
    SEMITONES[i] = {-1}
  end
  
  -- this sorts the chords by the reduced scale
  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)
  
  -- this sorts the chords by the chord name
  -- table.sort(ALL_CHORDS, function(x, y) return string.sub(x[1], 49) < string.sub(y[1], 49) end)
  
  file = io.open(arg[1] .. "_all.txt", "w")
  file:write(tableStr(map(function(x) return x[1] end, ALL_CHORDS)))
  file:close()
end

Comments

  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  • Aheujfihdid icon
    04/02/26 01:58:09 AM UTC
    text |

    0 B

    |

    0 👍

    /

    0 👎

    Best t33n active cpx link daily updates!
     Copy and paste the link in a new browser tab and then hit enter!
     https://crazyporn.@xxx/members/157354/?asgtbndr=1&play=true&spon=adworld&zone=400
     Remove the @ 
  • Fixzsi icon
    04/05/26 02:47:59 AM UTC
    text |

    0 B

    |

    0 👍

    /

    0 👎

    Best t33n active cpx link daily updates!
     Copy and paste the link in a new browser tab and then hit enter!
     https://crazyporn.xxx/members/157354/?asgtbndr=1&play=true&spon=adworld&zone=400