]> git.newer.systems - setclass.git/commitdiff
csv generator
authorTucker Johnson <tucker@newer.systems>
Fri, 20 Jun 2025 16:47:45 +0000 (12:47 -0400)
committerTucker Johnson <tucker@newer.systems>
Fri, 20 Jun 2025 16:47:45 +0000 (12:47 -0400)
generate_setClasses.lua [new file with mode: 0644]
init.lua

diff --git a/generate_setClasses.lua b/generate_setClasses.lua
new file mode 100644 (file)
index 0000000..ce0a19e
--- /dev/null
@@ -0,0 +1,112 @@
+local bit32 = require("bit32") or require("bit")\r
+\r
+-- Convert pitch class to A/B notation\r
+local function pc_to_char(pc)\r
+  if pc < 10 then return tostring(pc)\r
+  elseif pc == 10 then return 'A'\r
+  elseif pc == 11 then return 'B' end\r
+end\r
+\r
+-- Generate a string for a PC set\r
+local function pcs_to_string(pcs)\r
+  local out = {}\r
+  for _, pc in ipairs(pcs) do table.insert(out, pc_to_char(pc)) end\r
+  return "[" .. table.concat(out, "") .. "]"\r
+end\r
+\r
+-- Get all rotations of a sorted set\r
+local function rotations(pcs)\r
+  local out = {}\r
+  local n = #pcs\r
+  for i = 1, n do\r
+    local rot = {}\r
+    local offset = pcs[i]\r
+    for j = 0, n - 1 do\r
+      local val = (pcs[(i + j - 1) % n + 1] - offset + 12) % 12\r
+      table.insert(rot, val)\r
+    end\r
+    table.insert(out, rot)\r
+  end\r
+  return out\r
+end\r
+\r
+-- Get normal form of a set (most packed rotation)\r
+local function normal_form(pcs)\r
+  table.sort(pcs)\r
+  local all_rots = rotations(pcs)\r
+\r
+  -- Find the most compact (smallest span)\r
+  table.sort(all_rots, function(a, b)\r
+    local span_a = (a[#a] - a[1]) % 12\r
+    local span_b = (b[#b] - b[1]) % 12\r
+    if span_a ~= span_b then return span_a < span_b end\r
+\r
+    -- If equal span, choose lex smallest\r
+    for i = 1, #a do\r
+      if a[i] ~= b[i] then return a[i] < b[i] end\r
+    end\r
+    return false\r
+  end)\r
+\r
+  return all_rots[1]\r
+end\r
+\r
+-- Invert a set mod 12\r
+local function invert(pcs)\r
+  local inv = {}\r
+  for _, pc in ipairs(pcs) do\r
+    table.insert(inv, (12 - pc) % 12)\r
+  end\r
+  return inv\r
+end\r
+\r
+-- Get prime form of a set\r
+local function prime_form(pcs)\r
+  local nf1 = normal_form(pcs)\r
+  local nf2 = normal_form(invert(pcs))\r
+\r
+  -- Lexical comparison\r
+  for i = 1, #nf1 do\r
+    if nf1[i] < nf2[i] then return nf1\r
+    elseif nf1[i] > nf2[i] then return nf2 end\r
+  end\r
+  return nf1\r
+end\r
+\r
+-- Convert bitmask to PC set\r
+local function bitmask_to_pcs(n)\r
+  local pcs = {}\r
+  for i = 0, 11 do\r
+    if bit32.band(n, bit32.lshift(1, i)) ~= 0 then\r
+      table.insert(pcs, i)\r
+    end\r
+  end\r
+  return pcs\r
+end\r
+\r
+local function interval_class_vector(pcs)\r
+  local icv = {0, 0, 0, 0, 0, 0}\r
+  table.sort(pcs)\r
+  for i = 1, #pcs do\r
+    for j = i + 1, #pcs do\r
+      local interval = (pcs[j] - pcs[i]) % 12\r
+      local ic = math.min(interval, 12 - interval)\r
+      if ic > 0 and ic <= 6 then\r
+        icv[ic] = icv[ic] +1\r
+      end\r
+    end\r
+  end\r
+  return table.concat(icv)\r
+end\r
+\r
+-- Write the CSV\r
+local output = io.open("set-classes.csv", "w")\r
+for i = 1, 4095 do -- skip 0 (empty set)\r
+  local pcs = bitmask_to_pcs(i)\r
+  local pf = prime_form(pcs)\r
+  local icv = interval_class_vector(pcs)\r
+  output:write(i .. "," .. pcs_to_string(pf) .. ",<" .. icv .. ">\n")\r
+end\r
+output:close()\r
+\r
+print("✅ setclasses.csv generated with correct prime forms.")\r
index 50287338fa5672319c43e104bd3894fb4052f14c..2f4b9f88a3afa7bfa19052c6552b5ff4e01b572c 100644 (file)
--- a/init.lua
+++ b/init.lua
@@ -5,12 +5,12 @@ function M.analyze_selection()
   local input = core.get_visual_selection()
   local result = core.analyze_set(input)
   if result then
-    vim.notify("input:            " .. result.input .. " (" .. result.decimal .. ")")
+    vim.notify("input:           {" .. result.input .. "} (" .. result.decimal .. ")")
     vim.notify("set class:       " .. result.set_class)
     vim.notify("abs. complement: " .. result.complement_class)
     vim.notify("M5:              " .. result.m5_class)
   else
-    vim.notify("Invalid set")
+    vim.notify("unable to parse collection")
   end
 end