|
|
| Line 3: |
Line 3: |
| local mosm = require('Module:MOS modes') | | local mosm = require('Module:MOS modes') |
| local et = require('Module:ET') | | local et = require('Module:ET') |
| | local mosnot = require('MOS notation') |
| local p = {} | | local p = {} |
|
| |
| -- Helper function for creating a genchain, or specifically, a nominal-accidental chain.
| |
| -- This can only work in one direction at a time, so it's necessary to call this twice,
| |
| -- once for each direction (going up by the bright generator, or down). One genchain
| |
| -- is generated for each period, so this returns an array of arrays.
| |
| -- 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.
| |
| -- Specific notation is needed to interpret this into note names.
| |
| -- 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"
| |
| -- 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 that produces a gamut, a sequence of note names with accidentals, for an edo |
| Line 229: |
Line 100: |
| -- 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 293: |
Line 164: |
| 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 |
| | | |
| Line 314: |
Line 185: |
| 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] |
| Line 353: |
Line 224: |
| | | |
| -- 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 = mosnot.nomacc_chain(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 |