|
|
| (14 intermediate revisions by 3 users not shown) |
| Line 1: |
Line 1: |
| local mos = require('Module:MOS')
| |
| local rat = require('Module:Rational')
| |
| local mosm = require('Module:MOS modes')
| |
| local et = require('Module:ET')
| |
| local p = {} | | local p = {} |
|
| |
|
| -- Helper function for creating a genchain, a sequence of named pitches where consecutive
| | local et = require("Module:ET") |
| -- pitches are a generator apart. This can only work in one direction at a time, so it's
| | local mos = require("Module:MOS") |
| -- necessary to call this twice if both an ascending and descending chain are needed. For
| | local mosm = require("Module:MOS modes") |
| -- a multi-period mos, multiple genchains are returned as an array of arrays, where each
| | local mosnot = require("Module:MOS notation") |
| -- array has indices denote the number of generators going up (or down) and the element
| | local rat = require("Module:Rational") |
| -- denote the named pitch. For the single-period case, it's a size-1 array whose element
| | local utils = require("Module:Utils") |
| -- is a single genchain.
| | local yesno = require("Module:Yesno") |
| -- This genchain is agnostic of notation, and only denotes the mossteps needed to reach
| |
| -- a note, followed by the number of chromas. For example, F# is reached going up 3
| |
| -- mossteps and adding one chroma; Fb is the same except subtracting one chroma.
| |
| -- Parameters:
| |
| -- - input_mos - the mos itself represented as a data structure from Module:MOS
| |
| -- - genchain_init_per_period - how many named pitches per period are there without accidentals added?
| |
| -- This is either the value u or d for the UDP of up|dp.
| |
| -- - genchain_length_per_period - how many generators should the genchain extend after the root?
| |
| -- - going_up - bool; whether the genchain is going up or down; true for up, false for down
| |
| function p.mos_genchain(input_mos, genchain_init_per_period, genchain_length_per_period, going_up)
| |
| -- Default parameters for testing
| |
| --[[
| |
| local input_mos = input_mos or mos.new(5, 2, 2)
| |
| local genchain_init_per_period = genchain_init_per_period or 5
| |
| local genchain_length_per_period = genchain_length_per_period or 10
| |
| local note_symbols = note_symbols or "CDEFGAB"
| |
| local chroma_symbol = chroma_symbol or "#"
| |
| local going_up = going_up or true
| |
| ]]--
| |
|
| |
| -- Get the number of mossteps per period and equave
| |
| local mossteps_per_equave = input_mos.nL + input_mos.ns
| |
| local periods_per_equave = rat.gcd(input_mos.nL, input_mos.ns)
| |
| local mossteps_per_period = mossteps_per_equave / periods_per_equave
| |
|
| |
| --[[
| |
| -- Split the note symbols string into subsets
| |
| -- This is only necessary if the mos is multi-period
| |
| local note_subsets = {}
| |
| for i = 1, periods_per_equave do
| |
| local start_index = (i - 1) * mossteps_per_period + 1
| |
| local stop_index = i * mossteps_per_period
| |
| local substr = string.sub(note_symbols, start_index, stop_index)
| |
| table.insert(note_subsets, substr)
| |
| end
| |
| ]]--
| |
|
| |
| -- Create the genchain for each period
| |
| local genchains = {}
| |
| for i = 1, periods_per_equave do
| |
| --local note_names = note_subsets[i]
| |
|
| |
| -- Get the size of the generator in mossteps
| |
| local gen = mos.bright_gen(input_mos)
| |
| local gen_in_mossteps = gen['L'] + gen['s']
| |
|
| |
| -- If the genchain is descending (ie, going_up is false), switch to
| |
| -- using the dark gen in mossteps, which is the period complement
| |
| -- of the bright gen; going up by the dark gen is the same as going
| |
| -- down by the bright gen
| |
| if not going_up then
| |
| gen_in_mossteps = mossteps_per_period - gen_in_mossteps
| |
| end
| |
|
| |
| -- Use this value, with modular arithmteic, as an index to get the note name
| |
| local accumulator = 0
| |
|
| |
| -- Create a genchain that initially starts at the root
| |
| --local root = string.sub(note_names, 1, 1)
| |
| --local genchain = { root }
| |
| local root_offest = (i - 1) * mossteps_per_period -- To make sure that, across all periods, every note has a unique index
| |
| local root = { ['mossteps'] = root_offest, ['chromas'] = 0 }
| |
| local genchain = { root }
| |
|
| |
| -- Create the rest of the genchain
| |
| for j = 1, genchain_length_per_period do
| |
| -- Increment the index by the generator
| |
| accumulator = accumulator + gen_in_mossteps
| |
|
| |
| -- Convert the accumulator into an index
| |
| local index = accumulator % mossteps_per_period
| |
|
| |
| -- Add accidentals
| |
| -- This is negative if the genchain is descending
| |
| local accidentals_to_add = 0
| |
| if j > genchain_init_per_period then
| |
| accidentals_to_add = math.ceil((j - genchain_init_per_period) / mossteps_per_period)
| |
| end
| |
| if not going_up then
| |
| accidentals_to_add = accidentals_to_add * -1
| |
| end
| |
|
| |
| -- Get the final note name
| |
| local note_name = {}
| |
| note_name['mossteps'] = index + root_offest -- Mossteps needed to reach a note
| |
| note_name['chromas'] = accidentals_to_add -- How many chromas
| |
|
| |
| -- Add the note name
| |
| table.insert(genchain, note_name)
| |
| end
| |
|
| |
| -- Add the genchain
| |
| table.insert(genchains, genchain)
| |
| end
| |
|
| |
| return genchains
| |
| end
| |
| | |
| -- Helper function for parsing a step ratio entered as a string "p/q"
| |
| function p.parse_step_ratio(step_ratio_unparsed)
| |
|
| |
| local parsed = {}
| |
| for entry in string.gmatch(step_ratio_unparsed, '([^/]+)') do
| |
| local trimmed = entry:gsub("^%s*(.-)%s*$", "%1")
| |
| table.insert(parsed, trimmed) -- Add to array
| |
| end
| |
|
| |
| local ratio = { tonumber(parsed[1]), tonumber(parsed[2]) }
| |
| return ratio
| |
| end
| |
|
| |
|
| -- Helper function for parsing a UDP entered as a string "up-dp" | | -- Helper function for the function that has "frame" as a parameter |
| -- To avoid potential issues, the "-" character is used instead of "|"
| |
| function p.parse_udp(step_ratio_unparsed) | |
|
| |
| local parsed = {}
| |
| for entry in string.gmatch(step_ratio_unparsed, '([^,]+)') do
| |
| local trimmed = entry:gsub("^%s*(.-)%s*$", "%1")
| |
| table.insert(parsed, trimmed) -- Add to array
| |
| end
| |
|
| |
| local udp = { tonumber(parsed[1]), tonumber(parsed[2]) }
| |
| return udp
| |
| end
| |
| | |
| -- Function that produces a gamut, a sequence of note names with accidentals, for an edo
| |
| function p.mos_gamut(input_mos, udp, step_ratio, note_symbols, chroma_plus_symbol, chroma_minus_symbol) | | function p.mos_gamut(input_mos, udp, step_ratio, note_symbols, chroma_plus_symbol, chroma_minus_symbol) |
| -- Default parameters for input mos and step ratio (5L 2s and 2:1 step ratio) | | -- Default parameters for input mos and step ratio (5L 2s and 2:1 step ratio) |
| Line 145: |
Line 17: |
| -- Get the number of mossteps per period and equave | | -- Get the number of mossteps per period and equave |
| local mossteps_per_equave = input_mos.nL + input_mos.ns | | local mossteps_per_equave = input_mos.nL + input_mos.ns |
| local periods_per_equave = rat.gcd(input_mos.nL, input_mos.ns) | | local periods_per_equave = utils._gcd(input_mos.nL, input_mos.ns) |
| local mossteps_per_period = mossteps_per_equave / periods_per_equave | | local mossteps_per_period = mossteps_per_equave / periods_per_equave |
| | | |
| Line 191: |
Line 63: |
| local kp = step_ratio[1] | | local kp = step_ratio[1] |
| local kq = step_ratio[2] | | local kq = step_ratio[2] |
| local k = rat.gcd(kp, kq) | | local k = utils._gcd(kp, kq) |
| local num = kp / k | | local num = kp / k |
| local den = kq / k | | local den = kq / k |
| Line 226: |
Line 98: |
| | | |
| -- How long are the genchains? Length is per period | | -- How long are the genchains? Length is per period |
| local ascending_genchain_length = gens_up_per_period + genchain_extend | | -- Genchain length counts the root, hence the +1 |
| local descending_genchain_length = gens_down_per_period + genchain_extend | | local ascending_genchain_length = gens_up_per_period + genchain_extend + 1 |
| | local descending_genchain_length = gens_down_per_period + genchain_extend + 1 |
| | | |
| -- Get the ascending and descending genchains | | -- Get the ascending and descending genchains |
| -- The genchains are notationally agnostic so notation needs to be applied to them | | -- The genchains are notationally agnostic so notation needs to be applied to them |
| local ascending_genchain = p.mos_genchain(input_mos, gens_up_per_period, ascending_genchain_length, true) | | local ascending_genchain = mosnot.mos_nomacc_chain(input_mos, gens_up_per_period, ascending_genchain_length, true) |
| local descending_genchain = p.mos_genchain(input_mos, gens_down_per_period, descending_genchain_length, false) | | local descending_genchain = mosnot.mos_nomacc_chain(input_mos, gens_down_per_period, descending_genchain_length, false) |
| | | |
| -- Create an empty gamut | | -- Create an empty gamut |
| Line 242: |
Line 115: |
| -- How many esteps are the bright and dark generators? | | -- How many esteps are the bright and dark generators? |
| local bright_gen = mos.bright_gen(input_mos) | | local bright_gen = mos.bright_gen(input_mos) |
| local esteps_per_bright_gen = bright_gen['L'] * num + bright_gen['s'] * den | | local esteps_per_bright_gen = bright_gen["L"] * num + bright_gen["s"] * den |
| local esteps_per_dark_gen = esteps_per_period - esteps_per_bright_gen | | local esteps_per_dark_gen = esteps_per_period - esteps_per_bright_gen |
| | | |
| Line 253: |
Line 126: |
| -- Convert the notationally agnostic form into a form that uses given notation | | -- Convert the notationally agnostic form into a form that uses given notation |
| local note = ascending_genchain[j][i] | | local note = ascending_genchain[j][i] |
| local note_symbol = string.sub(note_symbols, note['mossteps'] + 1, note['mossteps'] + 1) | | local note_symbol = string.sub(note_symbols, note["Mossteps"] + 1, note["Mossteps"] + 1) |
| local chroma_count = note['chromas'] | | local chroma_count = note["Chromas"] |
| local note_name = note_symbol .. string.rep(chroma_plus_symbol, chroma_count) | | local note_name = note_symbol .. string.rep(chroma_plus_symbol, chroma_count) |
| | | |
| Line 266: |
Line 139: |
| -- Convert the notationally agnostic form into a form that uses given notation | | -- Convert the notationally agnostic form into a form that uses given notation |
| local note = descending_genchain[j][i] | | local note = descending_genchain[j][i] |
| local note_symbol = string.sub(note_symbols, note['mossteps'] + 1, note['mossteps'] + 1) | | local note_symbol = string.sub(note_symbols, note["Mossteps"] + 1, note["Mossteps"] + 1) |
| local chroma_count = note['chromas'] * -1 | | local chroma_count = note["Chromas"] * -1 |
| local note_name = note_symbol .. string.rep(chroma_minus_symbol, chroma_count) | | local note_name = note_symbol .. string.rep(chroma_minus_symbol, chroma_count) |
| | | |
| Line 289: |
Line 162: |
| function p.mos_gamut_frame(frame) | | function p.mos_gamut_frame(frame) |
| -- Default parameters for input mos and step ratio (5L 2s and 2:1 step ratio) | | -- Default parameters for input mos and step ratio (5L 2s and 2:1 step ratio) |
| local input_mos_unparsed = frame.args['Scale Signature'] | | local input_mos_unparsed = frame.args["Scale Signature"] |
| local input_mos = mos.parse(input_mos_unparsed) or mos.new(2, 5, 2) | | local input_mos = mos.parse(input_mos_unparsed) or mos.new(2, 5, 2) |
| | | |
| -- Step ratio | | -- Step ratio |
| local step_ratio = { 2, 1 } | | local step_ratio = { 2, 1 } |
| if string.len(frame.args['Step Ratio']) > 0 then | | if string.len(frame.args["Step Ratio"]) > 0 then |
| step_ratio = p.parse_step_ratio(frame.args['Step Ratio']) | | step_ratio = mosnot.parse_step_ratio(frame.args["Step Ratio"]) |
| end | | end |
| | | |
| -- Get the number of mossteps per period and equave | | -- Get the number of mossteps per period and equave |
| local mossteps_per_equave = input_mos.nL + input_mos.ns | | local mossteps_per_equave = input_mos.nL + input_mos.ns |
| local periods_per_equave = rat.gcd(input_mos.nL, input_mos.ns) | | local periods_per_equave = utils._gcd(input_mos.nL, input_mos.ns) |
| local mossteps_per_period = mossteps_per_equave / periods_per_equave | | local mossteps_per_period = mossteps_per_equave / periods_per_equave |
| | | |
| Line 315: |
Line 188: |
| udp = { 5, 1 } | | udp = { 5, 1 } |
| end | | end |
| if string.len(frame.args['UDP']) > 0 then | | if string.len(frame.args["UDP"]) > 0 then |
| udp = p.parse_udp(frame.args['UDP']) | | udp = mosnot.parse_udp(frame.args["UDP"]) |
| end | | end |
| local generators_up = udp[1] | | local generators_up = udp[1] |
| local generators_down = udp[2] | | local generators_down = udp[2] |
| | | |
|
| | -- Get notation: naturals (or nominals), sharp symbol, and flat symbol |
| -- Get note symbols | | local notation_default = { ["Naturals"] = string.sub("JKLMNOPQRSTUVWXYZ", 1, mossteps_per_equave), ["Sharp"] = "&", ["Flat"] = "@" } |
| -- If this param was blank, default to diamond-mos; limited to 17 note names
| |
| -- But if it's blank and the scalesig is 5L 2s, default to standard notation
| |
| -- This order of operations allows for overriding standard notation for 5L 2s
| |
| local note_symbols_main = "JKLMNOPQRSTUVWXYZ" | |
| local note_symbols = string.sub(note_symbols_main, 1, mossteps_per_equave)
| |
| if scale_sig == "5L 2s" then | | if scale_sig == "5L 2s" then |
| note_symbols = "CDEFGAB" | | notation_default["Naturals"] = "CDEFGAB" |
| end
| | notation_default["Sharp"] = "#" |
| -- If a value was entered, override the default value
| | notation_default["Flat"] = "b" |
| if string.len(frame.args['Note Symbols']) > 0 then
| |
| note_symbols = frame.args['Note Symbols'] | |
| end
| |
|
| |
| -- Get accidental symbols
| |
| -- If this param was blank, default to diamond-mos symbols & and @
| |
| -- unless the mos is 5L 2s, then it's sharp and flat # and b
| |
| -- This order of operations allows for overriding standard notation for 5L 2s
| |
| local chroma_plus_symbol = "&"
| |
| local chroma_minus_symbol = "@"
| |
| if scale_sig == "5L 2s" then
| |
| chroma_plus_symbol = "#" | |
| chroma_minus_symbol = "b"
| |
| end
| |
| -- If value(s) were entered, override the default values
| |
| if string.len(frame.args['Sharp Symbol']) > 0 then
| |
| chroma_plus_symbol = frame.args['Sharp Symbol']
| |
| end
| |
| if string.len(frame.args['Flat Symbol']) > 0 then
| |
| chroma_minus_symbol = frame.args['Flat Symbol']
| |
| end | | end |
| | local notation = mosnot.parse_notation(frame.args["Notation"]) or notation_default |
| | local note_symbols = notation["Naturals"] |
| | local chroma_plus_symbol = notation["Sharp"] |
| | local chroma_minus_symbol = notation["Flat"] |
| | | |
| -- Get the gamut | | -- Get the gamut |
| local gamut = p.mos_gamut(input_mos, udp, step_ratio, note_symbols, chroma_plus_symbol, chroma_minus_symbol) | | local gamut = p.mos_gamut(input_mos, udp, step_ratio, note_symbols, chroma_plus_symbol, chroma_minus_symbol) |
|
| |
| -- Old code for a horizontal table; the default is now a vertical table
| |
| --[[
| |
| -- Format the gamut as a table
| |
| local result = '{| class="wikitable"\n'
| |
|
| |
| -- Create the first row; this needs an edo for the header, followed by the
| |
| -- steps
| |
| local steps_in_et = input_mos.nL * step_ratio[1] + input_mos.ns * step_ratio[2]
| |
| local et_for_mos = et.new(steps_in_et, input_mos.equave)
| |
| local result = result .. "! Steps of " .. et.as_string(et_for_mos) .. "\n"
| |
| local step_ratio_gcd = rat.gcd(step_ratio[1], step_ratio[2]) -- GCD of the sizes of L and s, in case L:s isn't simplified
| |
| for i = 1, #gamut do
| |
| result = result .. "!" .. (i - 1) * step_ratio_gcd .. "\n"
| |
| end
| |
|
| |
| -- The second row contains the note names
| |
| local result = result .. "|-\n"
| |
| local result = result .. "! Note names on " .. string.sub(note_symbols, 1, 1) .. "\n"
| |
| for i = 1, #gamut do
| |
| -- Get the note name
| |
| local note_name = gamut[i]
| |
|
| |
| -- If the note name has a slash, replace it with a newline
| |
| note_name = note_name:gsub("/", "\n")
| |
|
| |
| -- If note name string is one character, it's a natural so the cell is white
| |
| -- For anything else, the cell is black (actually gray) to mimic a piano
| |
| if string.len(note_name) == 1 then
| |
| result = result .. '|bgcolor="white"|'.. note_name .. " \n\n"
| |
| else
| |
| result = result .. '|bgcolor="gray"|'.. note_name .. "\n"
| |
| end
| |
| end
| |
|
| |
| result = result .. "|}"
| |
| ]]--
| |
|
| |
| --[[
| |
| -- Format the gamut as a table
| |
| local result = '{| class="wikitable"\n'
| |
|
| |
| -- Produce the headers
| |
| local steps_in_et = input_mos.nL * step_ratio[1] + input_mos.ns * step_ratio[2]
| |
| local et_for_mos = et.new(steps_in_et, input_mos.equave)
| |
| result = result .. "! Steps of " .. et.as_string(et_for_mos) .. " !! Note name\n"
| |
|
| |
| -- Add the rows
| |
| local step_ratio_gcd = rat.gcd(step_ratio[1], step_ratio[2]) -- GCD of the sizes of L and s, in case L:s isn't simplified
| |
|
| |
| -- If note name string is one character, it's a natural so the row is white
| |
| -- For anything else, the row is black (actually gray) to mimic a piano
| |
| for i = 1, #gamut do
| |
| -- Get the note name
| |
| local note_name = gamut[i]
| |
|
| |
| -- If the note name has a slash, replace it with a comma
| |
| note_name = note_name:gsub("/", ", ")
| |
|
| |
| result = result .. "|-\n"
| |
| if string.len(note_name) == 1 then
| |
| result = result .. '|' .. step_ratio_gcd * (i-1) .. "||" .. note_name .. " \n\n"
| |
| else
| |
| result = result .. '|bgcolor="#c8ccd1"|' .. step_ratio_gcd * (i-1) .. '||bgcolor="#c8ccd1"|' .. note_name .. " \n\n"
| |
| end
| |
| end
| |
|
| |
| result = result .. "|}"
| |
| ]]--
| |
|
| |
|
| -- Since the gamut on a mos page is just text, so will this | | -- Since the gamut on a mos page is just text, so will this |
| Line 440: |
Line 223: |
| result = result .. "'''" .. gamut[#gamut] .. "'''" | | result = result .. "'''" .. gamut[#gamut] .. "'''" |
| | | |
| return result | | -- Debugger option |
| | local debugg = yesno(frame.args["debug"]) |
| | if debugg == true then |
| | result = "<syntaxhighlight lang=\"wikitext\">" .. result .. "</syntaxhighlight>" |
| | end |
| | |
| | return frame:preprocess(result) |
| end | | end |
|
| |
|
| return p | | return p |