From f2f7e866e6354ea0f214001f0cf2e66bac337aae Mon Sep 17 00:00:00 2001 From: Tucker Johnson Date: Fri, 20 Jun 2025 12:47:45 -0400 Subject: [PATCH] csv generator --- generate_setClasses.lua | 112 ++++++++++++++++++++++++++++++++++++++++ init.lua | 4 +- 2 files changed, 114 insertions(+), 2 deletions(-) create mode 100644 generate_setClasses.lua diff --git a/generate_setClasses.lua b/generate_setClasses.lua new file mode 100644 index 0000000..ce0a19e --- /dev/null +++ b/generate_setClasses.lua @@ -0,0 +1,112 @@ +local bit32 = require("bit32") or require("bit") + +-- Convert pitch class to A/B notation +local function pc_to_char(pc) + if pc < 10 then return tostring(pc) + elseif pc == 10 then return 'A' + elseif pc == 11 then return 'B' end +end + +-- Generate a string for a PC set +local function pcs_to_string(pcs) + local out = {} + for _, pc in ipairs(pcs) do table.insert(out, pc_to_char(pc)) end + return "[" .. table.concat(out, "") .. "]" +end + +-- Get all rotations of a sorted set +local function rotations(pcs) + local out = {} + local n = #pcs + for i = 1, n do + local rot = {} + local offset = pcs[i] + for j = 0, n - 1 do + local val = (pcs[(i + j - 1) % n + 1] - offset + 12) % 12 + table.insert(rot, val) + end + table.insert(out, rot) + end + return out +end + +-- Get normal form of a set (most packed rotation) +local function normal_form(pcs) + table.sort(pcs) + local all_rots = rotations(pcs) + + -- Find the most compact (smallest span) + table.sort(all_rots, function(a, b) + local span_a = (a[#a] - a[1]) % 12 + local span_b = (b[#b] - b[1]) % 12 + if span_a ~= span_b then return span_a < span_b end + + -- If equal span, choose lex smallest + for i = 1, #a do + if a[i] ~= b[i] then return a[i] < b[i] end + end + return false + end) + + return all_rots[1] +end + +-- Invert a set mod 12 +local function invert(pcs) + local inv = {} + for _, pc in ipairs(pcs) do + table.insert(inv, (12 - pc) % 12) + end + return inv +end + +-- Get prime form of a set +local function prime_form(pcs) + local nf1 = normal_form(pcs) + local nf2 = normal_form(invert(pcs)) + + -- Lexical comparison + for i = 1, #nf1 do + if nf1[i] < nf2[i] then return nf1 + elseif nf1[i] > nf2[i] then return nf2 end + end + return nf1 +end + +-- Convert bitmask to PC set +local function bitmask_to_pcs(n) + local pcs = {} + for i = 0, 11 do + if bit32.band(n, bit32.lshift(1, i)) ~= 0 then + table.insert(pcs, i) + end + end + return pcs +end + +local function interval_class_vector(pcs) + local icv = {0, 0, 0, 0, 0, 0} + table.sort(pcs) + for i = 1, #pcs do + for j = i + 1, #pcs do + local interval = (pcs[j] - pcs[i]) % 12 + local ic = math.min(interval, 12 - interval) + if ic > 0 and ic <= 6 then + icv[ic] = icv[ic] +1 + end + end + end + return table.concat(icv) +end + +-- Write the CSV +local output = io.open("set-classes.csv", "w") +for i = 1, 4095 do -- skip 0 (empty set) + local pcs = bitmask_to_pcs(i) + local pf = prime_form(pcs) + local icv = interval_class_vector(pcs) + output:write(i .. "," .. pcs_to_string(pf) .. ",<" .. icv .. ">\n") +end +output:close() + +print("✅ setclasses.csv generated with correct prime forms.") diff --git a/init.lua b/init.lua index 5028733..2f4b9f8 100644 --- 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 -- 2.39.5