Module:Keyboard: Difference between revisions
Jump to navigation
Jump to search
mNo edit summary |
ArrowHead294 (talk | contribs) mNo edit summary |
||
| (3 intermediate revisions by 2 users not shown) | |||
| Line 1: | Line 1: | ||
local p = {} | local p = {} | ||
local ET = require("Module:ET") | |||
local rat = require("Module:Rational") | |||
local layout_cache = { | local layout_cache = { | ||
[ | ["0/1"] = "b", | ||
[ | ["1/1"] = "w" | ||
} | } | ||
-- `n`: white keys needed | -- `n`: white keys needed | ||
| Line 11: | Line 12: | ||
function p.auto_layout(n, m, convergents) | function p.auto_layout(n, m, convergents) | ||
if m == 0 then | if m == 0 then | ||
return | return "" | ||
elseif layout_cache[n .. | elseif layout_cache[n .. "/" .. m] then | ||
return layout_cache[n .. | return layout_cache[n .. "/" .. m] | ||
end | end | ||
local s = | local s = "" | ||
local max_i = -1/0 | local max_i = -1 / 0 | ||
for i, ratio in ipairs(convergents) do | for i, ratio in ipairs(convergents) do | ||
local r_n, r_m = rat.as_pair(ratio) | local r_n, r_m = rat.as_pair(ratio) | ||
| Line 29: | Line 30: | ||
local r_n, r_m = rat.as_pair(convergents[max_i]) | local r_n, r_m = rat.as_pair(convergents[max_i]) | ||
local r_layout = p.auto_layout(r_n, r_m, convergents) | local r_layout = p.auto_layout(r_n, r_m, convergents) | ||
if not layout_cache[r_n .. | if not layout_cache[r_n .. "/" .. r_m] then | ||
layout_cache[r_n .. | layout_cache[r_n .. "/" .. r_m] = r_layout | ||
end | end | ||
if n - r_n ~= 1 or m - r_m ~= 2 then | if n - r_n ~= 1 or m - r_m ~= 2 then | ||
return r_layout .. p.auto_layout(n - r_n, m - r_m, convergents) | return r_layout .. p.auto_layout(n - r_n, m - r_m, convergents) | ||
else | else | ||
return r_layout .. | return r_layout .. "bw" | ||
end | end | ||
end | end | ||
| Line 44: | Line 45: | ||
for i = 1, #layout do | for i = 1, #layout do | ||
if compact then | if compact then | ||
if layout:sub(i, i) == | if layout:sub(i, i) == "w" then | ||
pos = pos + 1 | pos = pos + 1 | ||
table.insert(data, { key = | table.insert(data, {key = "w", pos = pos, kind = "full"}) | ||
elseif layout:sub(i - 1, i - 1) == | elseif layout:sub(i - 1, i - 1) == "w" and layout:sub(i + 1, i + 1) == "w" then | ||
table.insert(data, { key = | table.insert(data, { key = "b", pos = pos, kind = "mid"}) | ||
elseif layout:sub(i - 1, i - 1) == | elseif layout:sub(i - 1, i - 1) == "w" then | ||
table.insert(data, { key = | table.insert(data, { key = "b", pos = pos, kind = "right"}) | ||
elseif layout:sub(i + 1, i + 1) == | elseif layout:sub(i + 1, i + 1) == "w" then | ||
table.insert(data, { key = | table.insert(data, { key = "b", pos = pos + 1, kind = "left"}) | ||
else | else | ||
pos = pos + 1 | pos = pos + 1 | ||
table.insert(data, { key = | table.insert(data, { key = "b", pos = pos, kind = "full"}) | ||
end | end | ||
else | else | ||
pos = pos + 1 | pos = pos + 1 | ||
table.insert(data, { key = layout:sub(i, i), pos = pos, kind = | table.insert(data, { key = layout:sub(i, i), pos = pos, kind = "full" }) | ||
end | end | ||
end | end | ||
| Line 67: | Line 68: | ||
function p.ET(frame) | function p.ET(frame) | ||
local et = ET.parse(frame.args[1]) | local et = ET.parse(frame.args[1]) | ||
local from = tonumber(frame.args[ | local from = tonumber(frame.args["From"]) or 0 | ||
local to = tonumber(frame.args[ | local to = tonumber(frame.args["To"]) or et.size - 1 | ||
local base_freq = tonumber(frame.args[ | local base_freq = tonumber(frame.args["Base frequency"]) or 440 | ||
local layout = frame.args[2] | local layout = frame.args[2] | ||
| Line 75: | Line 76: | ||
local fifth = ET.approximate(et, 3/2) | local fifth = ET.approximate(et, 3/2) | ||
local convergents = rat.convergents( | local convergents = rat.convergents( | ||
math.log(3/2)/math.log(2), | math.log(3/2) / math.log(2), | ||
function(ratio) | function(ratio) | ||
local r_n, r_m = rat.as_pair(ratio) | local r_n, r_m = rat.as_pair(ratio) | ||
| Line 84: | Line 85: | ||
end | end | ||
local key_width = tonumber(frame.args[ | local key_width = tonumber(frame.args["Key width"]) or 30 | ||
local key_height = tonumber(frame.args[ | local key_height = tonumber(frame.args["Key height"]) or 60 | ||
local dur = tonumber(frame.args[ | local dur = tonumber(frame.args["Duration"]) or 500 | ||
local gain = tonumber(frame.args[ | local gain = tonumber(frame.args["Gain"]) or 0.1 | ||
local instrument = frame.args[ | local instrument = frame.args["Instrument"] or "sine" | ||
local spectrum_arg = frame.args[ | local spectrum_arg = frame.args["Harmonic spectrum"] | ||
local harmonic_spectrum = nil | local harmonic_spectrum = nil | ||
if spectrum_arg then | if spectrum_arg then | ||
harmonic_spectrum = {} | harmonic_spectrum = {} | ||
for val in spectrum_arg:gmatch( | for val in spectrum_arg:gmatch("%S+") do | ||
table.insert(harmonic_spectrum, tonumber(val)) | table.insert(harmonic_spectrum, tonumber(val)) | ||
end | end | ||
| Line 106: | Line 107: | ||
local freqs_arg = frame.args[1] | local freqs_arg = frame.args[1] | ||
local freqs = {} | local freqs = {} | ||
for freq in freqs_arg:gmatch( | for freq in freqs_arg:gmatch("%S+") do | ||
table.insert(freqs, tonumber(freq)) | table.insert(freqs, tonumber(freq)) | ||
end | end | ||
| Line 112: | Line 113: | ||
local colours = frame.args[2] | local colours = frame.args[2] | ||
local key_width = tonumber(frame.args[ | local key_width = tonumber(frame.args["Key width"]) or 30 | ||
local key_height = tonumber(frame.args[ | local key_height = tonumber(frame.args["Key height"]) or 60 | ||
local dur = tonumber(frame.args[ | local dur = tonumber(frame.args["Duration"]) or 500 | ||
local gain = tonumber(frame.args[ | local gain = tonumber(frame.args["Gain"]) or 0.1 | ||
local instrument = frame.args[ | local instrument = frame.args["Instrument"] or "sine" | ||
local spectrum_arg = frame.args[ | local spectrum_arg = frame.args["Harmonic spectrum"] | ||
local harmonic_spectrum = nil | local harmonic_spectrum = nil | ||
if spectrum_arg then | if spectrum_arg then | ||
harmonic_spectrum = {} | harmonic_spectrum = {} | ||
for val in spectrum_arg:gmatch( | for val in spectrum_arg:gmatch("%S+") do | ||
table.insert(harmonic_spectrum, tonumber(val)) | table.insert(harmonic_spectrum, tonumber(val)) | ||
end | end | ||
| Line 133: | Line 134: | ||
function p.build_ET_keyboard(et, from, to, base_freq, layout, key_width, key_height, dur, gain, instrument, harmonic_spectrum) | function p.build_ET_keyboard(et, from, to, base_freq, layout, key_width, key_height, dur, gain, instrument, harmonic_spectrum) | ||
if #layout ~= et.size then | if #layout ~= et.size then | ||
return | return "<span style=\"color: red;\">Layout for " .. ET.as_string(et) .. " should have exactly " .. et.size .. " elements!</span>" | ||
end | end | ||
local freqs = {} | local freqs = {} | ||
local colours = | local colours = "" | ||
local seps = {} | local seps = {} | ||
for i = from, to do | for i = from, to do | ||
local cents = ET.cents(et, i) | local cents = ET.cents(et, i) | ||
local hz = 2 ^ (math.log(base_freq)/math.log(2) + cents/1200) | local hz = 2 ^ (math.log(base_freq) / math.log(2) + cents / 1200) | ||
table.insert(freqs, hz) | table.insert(freqs, hz) | ||
local j = (i % et.size) + 1 | local j = (i % et.size) + 1 | ||
| Line 157: | Line 158: | ||
key_height = key_height - (key_height % 20) | key_height = key_height - (key_height % 20) | ||
if #freqs ~= #colours then | if #freqs ~= #colours then | ||
return | return "<span style=\"color: red;\">Number of keys (" .. (#freqs) .. ") and of colours (" .. (#colours) .. ") are different!</span>" | ||
end | end | ||
local n = #freqs | local n = #freqs | ||
local positions = p.auto_position(colours, true) | local positions = p.auto_position(colours, true) | ||
local s = | local s = "<div style=\"display: flex; width: fit-content;\">" | ||
for i = 1, n do | for i = 1, n do | ||
s = s .. | s = s .. "<div class=\"sequence-audio sequence-audio-button" | ||
if colours:sub(i, i) == | if colours:sub(i, i) == "w" then | ||
s = s .. | s = s .. " white-key" | ||
elseif colours:sub(i, i) == | elseif colours:sub(i, i) == "b" then | ||
s = s .. | s = s .. " black-key" | ||
end | end | ||
s = s .. | s = s .. "\" data-sequence=\"" .. freqs[i] .. ":" .. dur .. ":" .. gain .. ":0:" .. instrument .. "\"" | ||
if harmonic_spectrum then | if harmonic_spectrum then | ||
s = s .. | s = s .. " data-timbre-" .. instrument .. "=\"" .. table.concat(harmonic_spectrum, " ") .. "\"" | ||
end | end | ||
local width = key_width | local width = key_width | ||
local height = key_height | local height = key_height | ||
if positions[i].kind == | if positions[i].kind == "mid" then | ||
width = 2 * (width / 3) | width = 2 * (width / 3) | ||
height = 13 * (height / 20) | height = 13 * (height / 20) | ||
elseif positions[i].kind == | elseif positions[i].kind == "left" or positions[i].kind == "right" then | ||
width = width / 2 | width = width / 2 | ||
height = 13 * (height / 20) | height = 13 * (height / 20) | ||
end | end | ||
s = s .. | s = s .. " style=\"width: " .. (width - 2) .. "px; height: " .. (height - 2) .. "px; border: 1px solid #7f7f7f;" | ||
if positions[i].kind == | if positions[i].kind == "mid" then | ||
s = s .. | s = s .. " position: absolute; z-index: 1; left: " .. (positions[i].pos * key_width + key_width - width / 2) .. "px;" | ||
elseif positions[i].kind == | elseif positions[i].kind == "left" then | ||
s = s .. | s = s .. " position: absolute; z-index: 1; left: " .. (positions[i].pos * key_width) .. "px;" | ||
elseif positions[i].kind == | elseif positions[i].kind == "right" then | ||
s = s .. | s = s .. " position: absolute; z-index: 1; left: " .. (positions[i].pos * key_width + key_width - width) .. "px;" | ||
end | end | ||
if seps[i] and (positions[i].kind == | if seps[i] and (positions[i].kind == "full" or positions[i].kind == "left") then | ||
s = s .. | s = s .. " border-left: 1px solid #ff0000;" | ||
elseif seps[i - 1] and positions[i - 1].kind == "left" then | |||
s = s .. " border-left: 1px solid #ff0000;" | |||
end | end | ||
if seps[i + 1] and (positions[i].kind == | if seps[i + 1] and (positions[i].kind == "full" or positions[i].kind == "right") then | ||
s = s .. | s = s .. " border-right: 1px solid #ff0000;" | ||
elseif seps[i + 2] and positions[i + 1].kind == "mid" then | |||
s = s .. " border-right: 1px solid #ff0000;" | |||
end | end | ||
s = s .. | s = s .. "\"></div>" | ||
end | end | ||
s = s .. | s = s .. "</div>" | ||
return s | return s | ||
end | end | ||
return p | return p | ||
Latest revision as of 12:51, 19 May 2025
| Introspection summary for Module:Keyboard | |||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
| ||||||||||||||||||||||||||||||
No function descriptions were provided. The Lua code may have further information.
local p = {}
local ET = require("Module:ET")
local rat = require("Module:Rational")
local layout_cache = {
["0/1"] = "b",
["1/1"] = "w"
}
-- `n`: white keys needed
-- `m`: total keys needed
function p.auto_layout(n, m, convergents)
if m == 0 then
return ""
elseif layout_cache[n .. "/" .. m] then
return layout_cache[n .. "/" .. m]
end
local s = ""
local max_i = -1 / 0
for i, ratio in ipairs(convergents) do
local r_n, r_m = rat.as_pair(ratio)
if r_m >= m or r_n > n then
break
elseif n - r_n > m - r_m then
break
else
max_i = i
end
end
local r_n, r_m = rat.as_pair(convergents[max_i])
local r_layout = p.auto_layout(r_n, r_m, convergents)
if not layout_cache[r_n .. "/" .. r_m] then
layout_cache[r_n .. "/" .. r_m] = r_layout
end
if n - r_n ~= 1 or m - r_m ~= 2 then
return r_layout .. p.auto_layout(n - r_n, m - r_m, convergents)
else
return r_layout .. "bw"
end
end
function p.auto_position(layout, compact)
local data = {}
local pos = -1
for i = 1, #layout do
if compact then
if layout:sub(i, i) == "w" then
pos = pos + 1
table.insert(data, {key = "w", pos = pos, kind = "full"})
elseif layout:sub(i - 1, i - 1) == "w" and layout:sub(i + 1, i + 1) == "w" then
table.insert(data, { key = "b", pos = pos, kind = "mid"})
elseif layout:sub(i - 1, i - 1) == "w" then
table.insert(data, { key = "b", pos = pos, kind = "right"})
elseif layout:sub(i + 1, i + 1) == "w" then
table.insert(data, { key = "b", pos = pos + 1, kind = "left"})
else
pos = pos + 1
table.insert(data, { key = "b", pos = pos, kind = "full"})
end
else
pos = pos + 1
table.insert(data, { key = layout:sub(i, i), pos = pos, kind = "full" })
end
end
return data
end
function p.ET(frame)
local et = ET.parse(frame.args[1])
local from = tonumber(frame.args["From"]) or 0
local to = tonumber(frame.args["To"]) or et.size - 1
local base_freq = tonumber(frame.args["Base frequency"]) or 440
local layout = frame.args[2]
if not layout and rat.eq(et.equave, 2) and et.size > 0 then
local fifth = ET.approximate(et, 3/2)
local convergents = rat.convergents(
math.log(3/2) / math.log(2),
function(ratio)
local r_n, r_m = rat.as_pair(ratio)
return r_m > et.size
end
)
layout = p.auto_layout(fifth, et.size, convergents)
end
local key_width = tonumber(frame.args["Key width"]) or 30
local key_height = tonumber(frame.args["Key height"]) or 60
local dur = tonumber(frame.args["Duration"]) or 500
local gain = tonumber(frame.args["Gain"]) or 0.1
local instrument = frame.args["Instrument"] or "sine"
local spectrum_arg = frame.args["Harmonic spectrum"]
local harmonic_spectrum = nil
if spectrum_arg then
harmonic_spectrum = {}
for val in spectrum_arg:gmatch("%S+") do
table.insert(harmonic_spectrum, tonumber(val))
end
end
return p.build_ET_keyboard(et, from, to, base_freq, layout, key_width, key_height, dur, gain, instrument, harmonic_spectrum)
end
function p.build(frame)
local freqs_arg = frame.args[1]
local freqs = {}
for freq in freqs_arg:gmatch("%S+") do
table.insert(freqs, tonumber(freq))
end
local colours = frame.args[2]
local key_width = tonumber(frame.args["Key width"]) or 30
local key_height = tonumber(frame.args["Key height"]) or 60
local dur = tonumber(frame.args["Duration"]) or 500
local gain = tonumber(frame.args["Gain"]) or 0.1
local instrument = frame.args["Instrument"] or "sine"
local spectrum_arg = frame.args["Harmonic spectrum"]
local harmonic_spectrum = nil
if spectrum_arg then
harmonic_spectrum = {}
for val in spectrum_arg:gmatch("%S+") do
table.insert(harmonic_spectrum, tonumber(val))
end
end
return p.build_keyboard(freqs, colours, {}, key_width, key_height, dur, gain, instrument, harmonic_spectrum)
end
function p.build_ET_keyboard(et, from, to, base_freq, layout, key_width, key_height, dur, gain, instrument, harmonic_spectrum)
if #layout ~= et.size then
return "<span style=\"color: red;\">Layout for " .. ET.as_string(et) .. " should have exactly " .. et.size .. " elements!</span>"
end
local freqs = {}
local colours = ""
local seps = {}
for i = from, to do
local cents = ET.cents(et, i)
local hz = 2 ^ (math.log(base_freq) / math.log(2) + cents / 1200)
table.insert(freqs, hz)
local j = (i % et.size) + 1
colours = colours .. layout:sub(j, j)
if j == 1 and i > from then
seps[i - from + 1] = true
end
end
return p.build_keyboard(freqs, colours, seps, key_width, key_height, dur, gain, instrument, harmonic_spectrum)
end
function p.build_keyboard(freqs, colours, seps, key_width, key_height, dur, gain, instrument, harmonic_spectrum)
key_width = key_width - (key_width % 6)
key_height = key_height - (key_height % 20)
if #freqs ~= #colours then
return "<span style=\"color: red;\">Number of keys (" .. (#freqs) .. ") and of colours (" .. (#colours) .. ") are different!</span>"
end
local n = #freqs
local positions = p.auto_position(colours, true)
local s = "<div style=\"display: flex; width: fit-content;\">"
for i = 1, n do
s = s .. "<div class=\"sequence-audio sequence-audio-button"
if colours:sub(i, i) == "w" then
s = s .. " white-key"
elseif colours:sub(i, i) == "b" then
s = s .. " black-key"
end
s = s .. "\" data-sequence=\"" .. freqs[i] .. ":" .. dur .. ":" .. gain .. ":0:" .. instrument .. "\""
if harmonic_spectrum then
s = s .. " data-timbre-" .. instrument .. "=\"" .. table.concat(harmonic_spectrum, " ") .. "\""
end
local width = key_width
local height = key_height
if positions[i].kind == "mid" then
width = 2 * (width / 3)
height = 13 * (height / 20)
elseif positions[i].kind == "left" or positions[i].kind == "right" then
width = width / 2
height = 13 * (height / 20)
end
s = s .. " style=\"width: " .. (width - 2) .. "px; height: " .. (height - 2) .. "px; border: 1px solid #7f7f7f;"
if positions[i].kind == "mid" then
s = s .. " position: absolute; z-index: 1; left: " .. (positions[i].pos * key_width + key_width - width / 2) .. "px;"
elseif positions[i].kind == "left" then
s = s .. " position: absolute; z-index: 1; left: " .. (positions[i].pos * key_width) .. "px;"
elseif positions[i].kind == "right" then
s = s .. " position: absolute; z-index: 1; left: " .. (positions[i].pos * key_width + key_width - width) .. "px;"
end
if seps[i] and (positions[i].kind == "full" or positions[i].kind == "left") then
s = s .. " border-left: 1px solid #ff0000;"
elseif seps[i - 1] and positions[i - 1].kind == "left" then
s = s .. " border-left: 1px solid #ff0000;"
end
if seps[i + 1] and (positions[i].kind == "full" or positions[i].kind == "right") then
s = s .. " border-right: 1px solid #ff0000;"
elseif seps[i + 2] and positions[i + 1].kind == "mid" then
s = s .. " border-right: 1px solid #ff0000;"
end
s = s .. "\"></div>"
end
s = s .. "</div>"
return s
end
return p