「モジュール:MOS」の版間の差分

Furcht968 (トーク | 投稿記録)
編集の要約なし
タグ: 差し戻し済み
Furcht968 (トーク | 投稿記録)
編集の要約なし
 
(同じ利用者による、間の7版が非表示)
1行目: 1行目:
-- This module follows [[User:Ganaram inukshuk/Provisional style guide for Lua]]
-- Module for working with mosses in lua code; this serves as a "library" for
local et    = require("Module:ET")
-- mos-related modules and thus does not have a corresponding template.
local rat  = require("Module:Rational")
-- Functionality includes:
local utils = require("Module:Utils")
-- - Creating/parsing mosses
-- - Creating scalesigs (string representations) of mosses
-- - Finding certain modes of a mos
-- - Finding generators for a mos
-- - Producing vectors for simple mos intervals
-- - Interval arithmetic, in the form of adding vectors of L's and s's, and
--  period/equave-reducing intervals
-- - Finding equal tunings for mosses
local rat = require('Module:Rational')
local utils = require('Module:Utils')
local et = require('Module:ET')
local p = {}
 
-- Naming scheme for function names:
-- - Functions related to mosses don't have any special names.
-- - Functions related to a mos's modes generally end with "mode".
-- - Functions related to a mos's generators, equave, or period contain the
--  corresponding interval as part of its name.
-- - Functions related to intervals generally begin with "interval".
-- - Interval complement/reduce functions end with "complement" and "reduce".
-- - Functions that produce strings generally have the phrase "as string".
-- - Functions that "count" something generally end with "count".
-- - If a function requires an interval and mos as input, the interval(s) come
--  after the mos.
-- - Functions that have to do with equal tunings will have "et" in its name.


local p = {}
--------------------------------------------------------------------------------
------------------------------- HELPER FUNCTIONS -------------------------------
--------------------------------------------------------------------------------
 
function p.find_item_in_table(table, item)
local item_found = false
for i = 1, #table do
if table[i] == item then
item_found = true
break
end
end
return item_found
end


--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
----------------------------- MOS-CREATING FUNCTIONS ---------------------------
-------------------------------- BASE FUNCTIONS --------------------------------
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------


-- Create a new mos as a table containing the counts for large and small steps,
-- Create a new mos. (Contains the number of large and small steps, and equave.)
-- plus the equave.
function p.new(nL, ns, equave)
function p.new(nL, ns, equave)
local nL = nL or 5
local nL = nL or 5
local ns = ns or 2
local ns = ns or 2
local equave = equave or 2
local equave = equave or 2
return { nL = nL, ns = ns, equave = equave }
return { nL = nL, ns = ns, equave = equave }
end
end


-- Parse a mos from its scalesig "xL ys<p/q>" or "xL ys (p/q-equivalent)".
-- Pasre a mos from its scalesig.
-- If no equave "p/q" is provided, it's assumed to be 2/1-equivalent.
function p.parse(unparsed)
function p.parse(unparsed)
local nL, ns, equave = unparsed:match("^(%d+)[Ll].-(%d+)[Ss]%s*(.*)$")
local nL, ns, equave = unparsed:match('^(%d+)[Ll]%s*(%d+)[Ss]%s*(.*)$')
nL = tonumber(nL)
nL = tonumber(nL)
ns = tonumber(ns)
ns = tonumber(ns)
equave = equave:match("^%((.*)-equivalent%)$") or equave:match("^⟨(.*)⟩$") or equave:match("^<(.*)>$") or "2/1" -- Assumes this is a rational ratio written a/b
equave = equave:match('^%((.*)-equivalent%)$') or equave:match('^⟨(.*)⟩$') or equave:match('^<(.*)>$') or '2/1' -- Assumes this is a rational ratio written a/b
equave = rat.parse(equave)
equave = rat.parse(equave)
if nL == nil or ns == nil or equave == nil then
if nL == nil or ns == nil or equave == nil then
return nil
return nil
end
end
return p.new(nL, ns, equave)
return p.new(nL, ns, equave)
end
end


--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
---------------------- VALIDATION AND CHECKING FUNCTIONS -----------------------
------------------------------- STRING FUNCTIONS -------------------------------
--------------------------------------------------------------------------------
 
-- Is the mos xL ys valid (x and y are greater than 0)?
function p.is_valid(mos)
return mos.nL > 0 and mos.ns > 0
end
 
-- Is the mos xL ys octave-equivalent?
function p.is_octave_equivalent(mos)
return rat.eq(mos.equave, rat.new(2))
end
 
-- Is the mos nL ns? (Root mos, with root in the sense of being the root of
-- the scale tree.)
function p.is_root_mos(mos)
return mos.nL == mos.ns
end
 
--------------------------------------------------------------------------------
---------------------------- STRING/LINK FUNCTIONS -----------------------------
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------


-- Construct a string representation (scalesig) for a MOS structure.
-- Construct a string representation (scalesig) for a MOS structure.
-- Scalesig is "xL ys <p/q>" for valid mosses, omitting <p/q> for 2/1 scales.
-- Scalesig is "xL ys", or "xL ys<p/q>" for nonoctave scales.
-- Degenerate mosses (nL 0s or 0L ns) produce a string for its corresponding
function p.as_string(mos)
-- et (n-ed-p/q).
local suffix = ''
-- Option to use nbsp is provided using the second param; default is nbsp.
if not rat.eq(mos.equave, 2) then
function p.as_string(mos, use_nbsp)
suffix = '' .. rat.as_ratio(mos.equave):lower() .. ''
if p.is_valid(mos) then
local use_nbsp = (use_nbsp == nil and true or use_nbsp)
local suffix = ""
if not rat.eq(mos.equave, 2) then
suffix = "" .. rat.as_ratio(mos.equave):lower() .. ""
end
return mos.nL .. "L" .. (use_nbsp and "&nbsp;" or " ") .. mos.ns .. "s" .. suffix
else
return math.max(mos.nL, mos.ns) .. p.et_suffix(mos)
end
end
return '' .. mos.nL .. 'L ' .. mos.ns .. 's' .. suffix
end
end


-- Construct a longer string representation for a MOS structure.
-- Construct a longer string representation for a MOS structure.
-- Scalesig is "xL ys", or "xL ys (p/q-equivalent)" for nonoctave scales.
-- Scalesig is "xL ys", or "xL ys (p/q-equivalent)" for nonoctave scales.
-- Degenerate mosses (nL 0s or 0L ns) produce a string for its corresponding
function p.as_long_string(mos)
-- et (n-ed-p/q).
local suffix = ''
-- Option to use nbsp is provided using the second param; default is nbsp.
if not rat.eq(mos.equave, 2) then
function p.as_long_string(mos, use_nbsp)
suffix = string.format(" (%s-equivalent)", rat.as_ratio(mos.equave):lower())
if p.is_valid(mos) then
local use_nbsp = (use_nbsp ~= nil and use_nbsp or true)
local suffix = ""
if not rat.eq(mos.equave, 2) then
suffix = (use_nbsp and "&nbsp;" or " ") .. string.format("(%s-equivalent)", rat.as_ratio(mos.equave):lower())
end
return mos.nL .. "L" .. (use_nbsp and "&nbsp;" or " ") .. mos.ns .. "s" .. suffix
else
return math.max(mos.nL, mos.ns) .. p.et_suffix(mos)
end
end
 
-- Construct the link to a mos. If the mos is a degenerate (nL 0s) mos, then it
-- will link to the corresponding equal-division page n-ed-p/q and display the
-- link text as an ed, rather than a mos.
function p.as_link(mos)
local link = p.as_long_string(mos)
local text = p.as_string(mos)
if link == text then
return string.format("[[%s]]", link)
else
return string.format("[[%s|%s]]", link, text)
end
end
end
return '' .. mos.nL .. 'L ' .. mos.ns .. 's' .. suffix
 
-- Construct the link to a mos, where the displayed text is the long string
-- instead. Degenerate mosses link to the corresponding equal-division page.
function p.as_long_link(mos)
local link = p.as_long_string(mos)
return string.format("[[%s]]", link)
end
end


121行目: 98行目:
-- Quantity of L's as a string
-- Quantity of L's as a string
local L_string = ""
local L_string = ""
if interval["L"] == 0 then
if interval['L'] == 0 then
L_string = ""
L_string = ""
elseif interval["L"] == 1 then
elseif interval['L'] == 1 then
L_string = "L"
L_string = "L"
else
else
L_string = string.format("%dL", interval["L"])
L_string = string.format("%dL", interval['L'])
end
end
-- Quantity of s's as a string
-- Quantity of s's as a string
local s_string = ""
local s_string = ""
if math.abs(interval["s"]) == 0 then
if math.abs(interval['s']) == 0 then
s_string = ""
s_string = ""
elseif math.abs(interval["s"]) == 1 then
elseif math.abs(interval['s']) == 1 then
s_string = "s"
s_string = "s"
else
else
s_string = string.format("%ds", math.abs(interval["s"]))
s_string = string.format("%ds", math.abs(interval['s']))
end
end
if interval["L"] == 0 and interval["s"] == 0 then
if interval['L'] == 0 and interval['s'] == 0 then
return "0"
return "0"
elseif interval["L"] == 0 and interval["s"] ~= 0 then  
elseif interval['L'] == 0 and interval['s'] ~= 0 then  
return s_string
return s_string
elseif interval["L"] ~= 0 and interval["s"] == 0 then  
elseif interval['L'] ~= 0 and interval['s'] == 0 then  
return L_string
return L_string
else
else
return L_string .. (interval["s"] > 0 and " + " or " - ") .. s_string
return L_string .. (interval['s'] > 0 and " + " or " - ") .. s_string
end
end
end
-- Return the equave by itself as a string.
function p.equave_as_string(mos)
return rat.as_ratio(mos.equave)
end
-- Return the equave enclosed in brackets.
function p.equave_as_enclosed_string(mos)
return "⟨" .. rat.as_ratio(mos.equave) .. "⟩"
end
--------------------------------------------------------------------------------
----------------------- MOS RELATIVE/OPERATION FUNCTIONS -----------------------
--------------------------------------------------------------------------------
-- Find the parent mos of a mos. May return invalid mosses (nL 0s), meant to
-- represent equal divisions of the octave (or arbitrary equave).
function p.parent(mos)
return p.new(math.min(mos.nL, mos.ns), math.abs(mos.nL-mos.ns), mos.equave)
end
-- Find the root of a mos nxL nys as nL ns.
function p.root(mos)
local num_periods = p.period_count(mos)
return p.new(num_periods, num_periods, mos.equave)
end
-- Find the two child mosses of a mos xL ys as (x+y)L xs and xL x+ys.
function p.children(mos)
return p.new(mos.nL+mos.ns, mos.nL, mos.equave), p.new(mos.nL, mos.nL+mos.ns, mos.equave)
end
-- Find the sister of a mos xL ys as yL xs.
function p.sister(mos)
return p.new(mos.ns, mos.nL, mos.equave)
end
-- Find the neutralized form of a mos. May return invalid mosses (nL 0s), meant
-- to represent equal divisions of the octave (or arbitrary equave).
function p.neutralized(mos)
if mos.nL > mos.ns then
return p.new(mos.nL-mos.ns, 2*mos.ns, mos.equave)
else
return p.new(2*mos.nL, mos.ns-mos.nL, mos.equave)
end
end
-- Find the two interleaved mosses of a mos xL ys as (2x+y)L ys and xL (x+2y)s.
function p.interleaved(mos)
return p.new(mos.nL*2+mos.ns, mos.ns, mos.equave), p.new(mos.nL, mos.ns*2+mos.nL, mos.equave)
end
end


213行目: 139行目:
local d = utils._gcd(nL, ns)
local d = utils._gcd(nL, ns)
if d > 1 then -- use single period mos, with period as new equave
if d > 1 then -- use single period mos, with period as new equave
nL = utils._round_dec(nL / d)
nL = utils._round_dec(nL/d)
ns = utils._round_dec(ns / d)
ns = utils._round_dec(ns/d)
end
end
local current_L, current_s = 0, 0
local current_L, current_s = 0, 0
local result = ""
local result = ''
while current_L < nL or current_s < ns do
while current_L < nL or current_s < ns do
if (current_s + 1) * nL <= ns * (current_L) then
if (current_s + 1) * nL <= ns * (current_L) then
             current_s = current_s + 1
             current_s = current_s + 1
             result = result .. "s"
             result = result .. 's'
         else
         else
             current_L = current_L + 1
             current_L = current_L + 1
             result = result .. "L"
             result = result .. 'L'
         end
         end
end
end
230行目: 156行目:
end
end


-- Find the darkest true-mos mode of a mos. It's the reverse of the brightest mode.
-- Find the darkest true-mos mode of a mos.
-- It's the reverse of the brightest mode.
function p.darkest_mode(mos)
function p.darkest_mode(mos)
local nL = mos.nL
local nL = mos.nL
236行目: 163行目:
local d = utils._gcd(nL, ns)
local d = utils._gcd(nL, ns)
if d > 1 then -- use single period mos, with period as new equave
if d > 1 then -- use single period mos, with period as new equave
nL = utils._round_dec(nL / d)
nL = utils._round_dec(nL/d)
ns = utils._round_dec(ns / d)
ns = utils._round_dec(ns/d)
end
end
local current_L, current_s = 0, 0
local current_L, current_s = 0, 0
local result = ""
local result = ''
while current_L < nL or current_s < ns do
while current_L < nL or current_s < ns do
if (current_s + 1) * nL <= ns * (current_L) then
if (current_s + 1) * nL <= ns * (current_L) then
             current_s = current_s + 1
             current_s = current_s + 1
             result = "s" .. result -- !esreveR
             result = 's' .. result -- !esreveR
         else
         else
             current_L = current_L + 1
             current_L = current_L + 1
             result = "L" .. result -- !esreveR
             result = 'L' .. result -- !esreveR
         end
         end
end
end
return string.rep(result, d)
return string.rep(result, d)
end
end


-- Given a mos, return a mode based on how it's ranked by modal brightness.
-- Given a mos, return a mode based on how it's ranked by modal brightness.
-- Ordering here is based on the number of BRIGHT GENS DOWN PER PERIOD:
-- Ordering here is based on the number of bright gens going DOWN: 0 is the
-- 0 is the brightest mode, 1 is 2nd brightest, etc...
-- brightest mode, 1 is 2nd brightest, etc...
-- To go by darkness, pass in p-d-1 for the 2nd arg, where p is the period count
function p.mode_from_mos(mos, bright_gens_going_down)
-- and d is the number of DARK GENS UP PER PERIOD.
return p.rotate_mode(p.brightest_mode(mos), bright_gens_going_down * p.bright_gen_step_count(mos))
function p.mode_by_brightness(mos, bright_gens_down)
return p.rotate_mode(p.brightest_mode(mos), bright_gens_down * p.bright_gen_step_count(mos))
end
end
--------------------------------------------------------------------------------
--------------------------- MODE ROTATION FUNCTIONS ----------------------------
--------------------------------------------------------------------------------


-- Given a mos, list all modes in descending order of brightness.
-- Given a mos, list all modes in descending order of brightness.
274行目: 202行目:
current_mode = p.rotate_mode(current_mode, bright_gen_step_count)
current_mode = p.rotate_mode(current_mode, bright_gen_step_count)
end
end
return modes
return modes
end
end


-- List all unique rotations for a mode, by order of leftward shifts. Order by
-- List all unique rotations for a mode. Order of modes is by rotation.
-- rotation will usually give a different order compared to order by brightness,
-- but this is expected if the order isn't by brightness (EG, modmosses).
-- Note: there will always be s/p modes, where s is the number of steps in the
-- Note: there will always be s/p modes, where s is the number of steps in the
-- entered mode, and p is the period of repetition. At most, there will be s
-- entered mode, and p is the period of repetition. At most, there will be s
-- modes, but if there is a substring of length p that repeats within the mode
-- modes, but if there is a substring of length p that repeats within the mode
-- (where s mod p = 0), then there will be p modes. If the mode has one step
-- (where p divides s with remainder = 0), then there will be p modes. It's also
-- type, then there is only one mode.
-- possible to have only one mode, but this can only happen if there is only one
-- step size, meaning it's a unary scale (only one step size).
function p.mode_rotations(mode_string)
function p.mode_rotations(mode_string)
local rotations = {}
local rotations = {}
local current_mode = mode_string
local current_mode = mode_string
for i = 1, #mode_string do
for i = 1, #mode_string do
if not utils.table_contains(rotations, current_mode) then
if not p.find_item_in_table(rotations, current_mode) then
table.insert(rotations, current_mode)
table.insert(rotations, current_mode)
end
end
299行目: 225行目:


-- Rotate a mode by shifting the step sequence to the left. Negative values
-- Rotate a mode by shifting the step sequence to the left. Negative values
-- shift it to the right. Helper function for mode_by_brightness().
-- shift it to the right. Helper function for mode_from_mos().
function p.rotate_mode(mode_string, shift_amt)
function p.rotate_mode(mode_string, shift_amt)
local shift_amt = shift_amt == nil and 1 or shift_amt % #mode_string -- Default is 1
local shift_amt = shift_amt == nil and 1 or shift_amt % #mode_string -- Defualt is 1
local first = string.sub(mode_string, 1, shift_amt)
local first = string.sub(mode_string, 1, shift_amt)
local second = string.sub(mode_string, shift_amt + 1, #mode_string)
local second = string.sub(mode_string, shift_amt + 1, #mode_string)
315行目: 242行目:
function p.mode_to_step_matrix(mode_string)
function p.mode_to_step_matrix(mode_string)
local matrix = {}
local matrix = {}
for i = 0, #mode_string do
for i = 1, #mode_string + 1 do
local interval = p.interval_from_step_sequence(string.sub(mode_string, 0, i))
local steps = i - 1
local interval = p.interval_from_step_sequence(string.sub(mode_string, 0, steps))
table.insert(matrix, interval)
table.insert(matrix, interval)
end
end
return matrix
return matrix
end
end
-- TODO?: replaces mode_to_step_matrices/mode_rotations_to_step_matrices with
-- one function called modes_to_step_matrices? Encompasses functionality of both
-- functions, but step patterns for either are generated into the same function,
-- where the modes as strings are passed in.


-- Given a mos, produce every step matrix for every mode. Modes are listed in
-- Given a mos, produce every step matrix for every mode. Modes are listed in
337行目: 258行目:
table.insert(matrices, p.mode_to_step_matrix(modes[i]))
table.insert(matrices, p.mode_to_step_matrix(modes[i]))
end
end
return matrices
return matrices
end
end
349行目: 269行目:
table.insert(matrices, p.mode_to_step_matrix(modes[i]))
table.insert(matrices, p.mode_to_step_matrix(modes[i]))
end
end
return matrices
return matrices
end
end
380行目: 299行目:


--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
--------------- FUNCTIONS FOR GENERATOR AND PERIOD INTERVALS -------------------
--------------- INTERVAL FUNCTIONS FOR PERFECTABLE INTERVALS -------------------
------------------ (IE, GENERATORS AND PERIOD INTERVALS) -----------------------
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------


-- Compute the bright gen as a vector of L's and s's. Since all mosstep
-- Compute the bright gen as a vector of L's and s's.
-- intervals (excluding the root and period) have two sizes, this returns the
-- Bright gen has two sizes: perfect (large) and diminished (small). The size
-- large/perfect size.
-- given by this function is the large size.
function p.bright_gen(mos)
function p.bright_gen(mos)
local nL = mos.nL
local nL = mos.nL
391行目: 311行目:
local d = utils._gcd(nL, ns)
local d = utils._gcd(nL, ns)
if d > 1 then -- use single period mos, with period as new equave
if d > 1 then -- use single period mos, with period as new equave
nL = utils._round_dec(nL / d)
nL = utils._round_dec(nL/d)
ns = utils._round_dec(ns / d)
ns = utils._round_dec(ns/d)
end
end
local min_dist = 2; -- the distance we get will always be <= sqrt(2)
local min_dist = 2; -- the distance we get will always be <= sqrt(2)
local current_L, current_s = 0, 0
local current_L, current_s = 0, 0
local result = {["L"] = 0, ["s"] = 0}  
local result = {['L'] = 0, ['s'] = 0}  
while current_L < nL or current_s < ns do
while current_L < nL or current_s < ns do
if (current_s + 1) * nL <= ns * (current_L) then
if (current_s + 1) * nL <= ns * (current_L) then
404行目: 324行目:
end
end
     if current_L < nL or current_s < ns then -- check to exclude (current_L, current_s) = (nL, ns)
     if current_L < nL or current_s < ns then -- check to exclude (current_L, current_s) = (nL, ns)
     local distance_here = math.abs(nL * current_s - ns * current_L) / math.sqrt(nL^2 + ns^2)
     local distance_here = math.abs(nL*current_s - ns*current_L)/math.sqrt(nL^2 + ns^2)
     if distance_here < min_dist then
     if distance_here < min_dist then
     min_dist = distance_here
     min_dist = distance_here
     result["L"] = current_L
     result['L'] = current_L
     result["s"] = current_s
     result['s'] = current_s
     end
     end
     end
     end
end
end
return result
return result
end
end


-- Compute the dark gen as a vector of L's and s's. Since all mosstep
-- Compute the dark gen as a vector of L's and s's.
-- intervals (excluding the root and period) have two sizes, this returns the
-- Dark gen has two sizes: augmented (large) and perfect (small). The size given
-- small/perfect size.
-- by this function is the small size. It's the period complement of the bright
-- gen.
function p.dark_gen(mos)
function p.dark_gen(mos)
local bright_gen = p.bright_gen(mos)
local bright_gen = p.bright_gen(mos)
425行目: 345行目:


-- Compute the period as a vector of L's and s's.
-- Compute the period as a vector of L's and s's.
-- Period intervals as mossteps only appear as one size.
-- Period intervals only have one size: perfect.
function p.period(mos)  
function p.period(mos)  
local gcd = utils._gcd(mos.nL, mos.ns)
local gcd = utils._gcd(mos.nL, mos.ns)
return {
return {
["L"] = mos.nL / gcd,
['L'] = mos.nL / gcd,
["s"] = mos.ns / gcd
['s'] = mos.ns / gcd
}
}
end
end


-- Compute the equave as a vector of L's and s's.
-- Compute the equave as a vector of L's and s's.
-- Equaves as mossteps only appear as one size. For a single-period mos, this
-- Equave intervals only have one size: perfect. Equave and period intervals are
-- is the same as p.period().
-- the same for single-period mosses.
function p.equave(mos)  
function p.equave(mos)  
return {
return {
["L"] = mos.nL,
['L'] = mos.nL,
["s"] = mos.ns
['s'] = mos.ns
}
}
end
end


--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
------------------- FUNCTIONS FOR SINGLE-STEP INTERVALS ------------------------
------------------ INTERVAL FUNCTIONS FOR SIMPLE INTERVALS ---------------------
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------


-- Return the unison as a vector of L's and s's.
-- Compute the unison as a vector of L's and s's.
-- The unison is denoted by moving up from the root by zero steps, and thus does
-- The unison is denoted by moving up from the root by zero steps, and thus does
-- not need a mos as input. It's basically a zero vector.
-- not need a mos as input. It's basically a zero vector.
-- The unison only has one size: perfect.
-- The unison only has one size: perfect.
function p.unison()
function p.unison()
return { ["L"] = 0, ["s"] = 0 }
return { ['L'] = 0, ['s'] = 0 }
end
end


-- Return the vector for a single chroma. It's a large step minus a small step.
-- Compute the vector for a single chroma. It's a large step minus a small step.
-- Adding or subtracting any interval by this interval changes its "size".
-- Adding or subtracting any interval by this interval changes its "size".
function p.chroma()
function p.chroma()
return { ["L"] = 1, ["s"] = -1 }
return { ['L'] = 1, ['s'] = -1 }
end
end


-- Return the vector for an augmented step. It's a large step plus a chroma.
-- Compute the vector for an augmented step. It's a large step plus a chroma.
function p.augmented_step()
function p.augmented_step()
return { ["L"] = 2, ["s"] = -1 }
return { ['L'] = 2, ['s'] = -1 }
end
end


-- Return the vector for a single large step.
-- Compute the vector for a single large step.
function p.large_step()
function p.large_step()
return { ["L"] = 1, ["s"] = 0 }
return { ['L'] = 1, ['s'] = 0 }
end
end


-- Return the vector for a single small step.
-- Compute the vector for a single small step.
function p.small_step()
function p.small_step()
return { ["L"] = 0, ["s"] = 1 }
return { ['L'] = 0, ['s'] = 1 }
end
end


-- Return the vector for a diminished step. It's a small step minus a chroma.
-- Compute the vector for a diminished step. It's a small step minus a chroma.
function p.diminished_step()
function p.diminished_step()
return { ["L"] = -1, ["s"] = 2 }
return { ['L'] = -1, ['s'] = 2 }
end
end


488行目: 408行目:
-- Create a new interval using step counts (the quantities of L's and s's).
-- Create a new interval using step counts (the quantities of L's and s's).
function p.interval_from_step_counts(i, j)
function p.interval_from_step_counts(i, j)
return { ["L"] = i, ["s"] = j }
return { ['L'] = i, ['s'] = j }
end
end


-- Compute an arbitrary mos interval as a vector of L's and s's. Params:
-- Compute an arbitrary mos interval as a vector of L's and s's.
-- - step_count: the number of steps subtended by the mosstep.
-- The step_count param is the number of mossteps in the interval. EG, in 5L 2s,
-- - size_offset: denotes whether to return the large size (0) or the small
-- the large 2-mosstep is "LL", so the corresponding vector has L=2, s=0.
--   size (-1) (or if this is a period interval, the diminished size). Values
-- Mossteps larger than the equave (eg, the minor 9th in non-xen music theory)
--   other than 0 or 1 represent alterations by multiple chromas, such as
-- are allowed.
--   augmented (1) or diminished (-2).
-- The size_offset denotes whether the interval is the large size (0) or the
-- small size (-1). This can exceed the range of [-1, 0] to represent intervals
-- raised/lowered by multiple chromas (augmented, diminished, etc).
-- Note that for period intervals (eg, the root and equave), there is only one
-- size (0 = perfect), so -1 is diminished and 1 is augmented.
-- EG, a perfect 4-diastep (perf. 5th) is 4 steps. Since it's the large size,
-- the offset is 0, but to get the diminished 5th, the offset should be -1.
function p.interval_from_mos(mos, step_count, size_offset)
function p.interval_from_mos(mos, step_count, size_offset)
local size_offset = size_offset or 0 -- Optional param; defaults to large size
local size_offset = size_offset or 0 -- Optional param; defaults to large size
513行目: 439行目:
-- and s's. This also serves as a helper function for p.interval_from_mos().
-- and s's. This also serves as a helper function for p.interval_from_mos().
-- Sequences of steps can be entered, where each step is one of five sizes:
-- Sequences of steps can be entered, where each step is one of five sizes:
-- - L: large step.
-- - L: large step.
-- - s: small step.
-- - s: small step.
-- - c: a chroma; the difference between a large and small step.
-- - c: a chroma; the difference between a large and small step.
-- - A: an augmented step; a large step plus a chroma.
-- - A: an augmented step; a large step plus a chroma.
-- - d: a diminished step, or diesis; a small step minus a chroma.
-- - d: a diminished step, or diesis; a small step minus a chroma.
function p.interval_from_step_sequence(step_sequence)
function p.interval_from_step_sequence(step_sequence)
local mossteps = #step_sequence
local mossteps = #step_sequence
543行目: 469行目:
------------------------------- COUNT FUNCTIONS --------------------------------
------------------------------- COUNT FUNCTIONS --------------------------------
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
-- Given a mos, return the number of steps.
function p.step_count(mos)
return mos.nL + mos.ns
end


-- Given a mos, compute the number of steps in its bright gen (L's plus s's).
-- Given a mos, compute the number of steps in its bright gen (L's plus s's).
function p.bright_gen_step_count(mos)
function p.bright_gen_step_count(mos)
local interval = p.bright_gen(mos)
local interval = p.bright_gen(mos)
return interval["L"] + interval["s"]
return interval['L'] + interval['s']
end
end


565行目: 486行目:
end
end


-- TODO: deprecate this since "equave_step_count" is redundant and longer than
-- Given a mos, compute the number of steps in its equave (L's plus s's).
-- "step count".
function p.equave_step_count(mos)
function p.equave_step_count(mos)
return mos.nL + mos.ns
return mos.nL + mos.ns
580行目: 500行目:
-- can be negative, resulting in a negative output.
-- can be negative, resulting in a negative output.
function p.interval_step_count(interval)
function p.interval_step_count(interval)
return interval["L"] + interval["s"]
return interval['L'] + interval['s']
end
end


587行目: 507行目:
-- perfect size (for period/root/equave intervals). This requires the mos as
-- perfect size (for period/root/equave intervals). This requires the mos as
-- input.
-- input.
-- size_offset denotes whether to count chromas from the large size; changing
-- If the number of chromas from a small (EG minor) interval is desired, then
-- this to -1 counts chromas from the small size. Like size_offset for
-- using the param size_offset can be used: 0 for chromas from large size, -1
-- interval_from_mos, this can be used to denote altered mossteps (augmented,
-- for chromas from small size. This can exceed the range [-1, 0] if needed.
-- diminished, etc).
-- EG, a diminished 2-diastep (dim. 3rd) has the vector {0,2}. It's reached by
-- either lowering the major 2-step by 2 chromas, or lowering the minor 2-step
-- by 1 chroma.
function p.interval_chroma_count(interval, mos, size_offset)
function p.interval_chroma_count(interval, mos, size_offset)
local size_offset = size_offset or 0 -- Default of 0.
local size_offset = size_offset or 0 -- Default of 0.
596行目: 518行目:
local base_interval = p.interval_from_mos(mos, step_count, 0)
local base_interval = p.interval_from_mos(mos, step_count, 0)
return interval["L"] - base_interval["L"] - size_offset
return interval['L'] - base_interval['L'] - size_offset
end
end


--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
--------------- INTERVAL ARITHMETIC AND MANIPULATION FUNCTIONS -----------------
----------------------- INTERVAL ARITHMETIC FUNCTIONS --------------------------
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------


606行目: 528行目:
function p.interval_add(interval_1, interval_2)
function p.interval_add(interval_1, interval_2)
return {  
return {  
["L"] = interval_1["L"] + interval_2["L"],
['L'] = interval_1['L'] + interval_2['L'],
["s"] = interval_1["s"] + interval_2["s"]
['s'] = interval_1['s'] + interval_2['s']
}
}
end
end
614行目: 536行目:
function p.interval_sub(interval_1, interval_2)
function p.interval_sub(interval_1, interval_2)
return {  
return {  
["L"] = interval_1["L"] - interval_2["L"],
['L'] = interval_1['L'] - interval_2['L'],
["s"] = interval_1["s"] - interval_2["s"]
['s'] = interval_1['s'] - interval_2['s']
}
}
end
end


-- Stack an interval, or repeatedly add the same interval to itself.
-- Repeatedly add the same interval to itself.
function p.interval_mul(interval, amt)
function p.interval_mul(interval, amt)
return {  
return {  
["L"] = interval["L"] * amt,
['L'] = interval['L'] * amt,
["s"] = interval["s"] * amt
['s'] = interval['s'] * amt
}
}
end
end
630行目: 552行目:
function p.interval_eq(interval_1, interval_2)
function p.interval_eq(interval_1, interval_2)
return  
return  
interval_1["L"] == interval_2["L"] and
interval_1['L'] == interval_2['L'] and
interval_1["s"] == interval_2["s"]
interval_1['s'] == interval_2['s']
end
end
--------------------------------------------------------------------------------
---------------------- INTERVAL MANIPULATION FUNCTIONS -------------------------
--------------------------------------------------------------------------------


-- Given an interval vector and a mos, find its period complement. This is the
-- Given an interval vector and a mos, find its period complement. This is the
-- interval to add to produce the period. For single-period mosses, the period
-- interval to add to produce the period.
-- complement is the same as the equave complement.
function p.period_complement(interval, mos)
function p.period_complement(interval, mos)
local sign = p.interval_step_count(interval) < 0 and -1 or 1
local sign = p.interval_step_count(interval) < 0 and -1 or 1
653行目: 577行目:
-- Given an interval vector and a mos, period-reduce it. This works like
-- Given an interval vector and a mos, period-reduce it. This works like
-- modular arithmetic, so passing a negative interval returns a positive one.
-- modular arithmetic, so passing a negative interval returns a positive one.
-- For single-period mosses, period-reducing is the same as octave-reducing, or
-- equave-reducing (for nonoctave scales).
function p.period_reduce(interval, mos)
function p.period_reduce(interval, mos)
local step_count = p.interval_step_count(interval)
local step_count = p.interval_step_count(interval)
682行目: 604行目:
function p.normalize_interval(interval)
function p.normalize_interval(interval)
return p.interval_step_count(interval) < 0 and p.interval_mul(interval, -1) or interval
return p.interval_step_count(interval) < 0 and p.interval_mul(interval, -1) or interval
end
--------------------------------------------------------------------------------
---------------------------- EQUAL-TUNING FUNCTIONS ----------------------------
--------------------------------------------------------------------------------
-- Given a mos and a step ratio, return an equal tuning (or equal division).
-- The step ratio is entered as a 2-element array to allow non-simplified
-- ratios to be entered. (The rational module isn't suitable since it simplifies
-- ratios.)
function p.mos_to_et(mos, step_ratio)
local et_size = mos.nL * step_ratio[1] + mos.ns * step_ratio[2]
return et.new(et_size, mos.equave, rat.as_ratio(mos.equave))
end
-- Given a mos and a step ratio, return the number of et-steps for its bright
-- generator.
function p.bright_gen_to_et_steps(mos, step_ratio)
return p.interval_to_et_steps(p.bright_gen(mos), step_ratio)
end
-- Given a mos and a step ratio, return the number of et-steps for its dark
-- generator.
function p.dark_gen_to_et_steps(mos, step_ratio)
return p.interval_to_et_steps(p.dark_gen(mos), step_ratio)
end
-- Given a mos and a step ratio, return the number of et-steps for its period.
function p.period_to_et_steps(mos, step_ratio)
return p.interval_to_et_steps(p.period(mos), step_ratio)
end
-- Given a mos and a step ratio, return the number of et-steps for its equave.
function p.equave_to_et_steps(mos, step_ratio)
return p.interval_to_et_steps(p.equave(mos), step_ratio)
end
-- Given an interval vector and step ratio, compute the number of et-steps it
-- corresponds to.
function p.interval_to_et_steps(interval, step_ratio)
return interval['L'] * step_ratio[1] + interval['s'] * step_ratio[2]
end
--------------------------------------------------------------------------------
------------------------------- CENT FUNCTIONS ---------------------------------
--------------------------------------------------------------------------------
-- Given a mos and a step ratio, return the number of cents for its bright gen.
function p.bright_gen_to_cents(mos, step_ratio)
local interval_steps = p.interval_to_et_steps(p.bright_gen(mos), step_ratio)
local equave_steps = p.equave_to_et_steps(mos, step_ratio)
return interval_steps * rat.cents(mos.equave) / equave_steps
end
-- Given a mos and a step ratio, return the number of cents for its dark gen.
function p.dark_gen_to_cents(mos, step_ratio)
local interval_steps = p.interval_to_et_steps(p.dark_gen(mos), step_ratio)
local equave_steps = p.equave_to_et_steps(mos, step_ratio)
return interval_steps * rat.cents(mos.equave) / equave_steps
end
-- Given a mos and a step ratio, return the number of cents for its period.
-- The period is the interval at which the step pattern repeats, so no step
-- ratio is needed.
function p.period_to_cents(mos)
return rat.cents(mos.equave) / p.period_count(mos)
end
-- Given a mos and a step ratio, return the number of cents for its equave.
-- The period is the interval at which the step pattern repeats, and the equave
-- is a multiple of that (at least for multi-period mosses), so no step ratio is
-- needed.
function p.equave_to_cents(mos)
return rat.cents(mos.equave)
end
-- Given an interval vector and step ratio, convert it to cents. This requires
-- info about the mos itself.
function p.interval_to_cents(interval, mos, step_ratio)
local interval_steps = p.interval_to_et_steps(interval, step_ratio)
local equave_steps = p.equave_to_et_steps(mos, step_ratio)
return interval_steps * rat.cents(mos.equave) / equave_steps
end
--------------------------------------------------------------------------------
------------ UNUSED FUNCTIONS OR FUNCTIONS TO MOVE TO OTHER MODULES ------------
--------------------------------------------------------------------------------
-- Given a mos, find the ancestor mos with a target note count (default 10)
-- or less; to be moved to tamnams module
function p.find_ancestor(mos, target_note_count)
local mos = mos or p.new(5, 2)
local target_note_count = target_note_count or 10
local z = mos.nL
local w = mos.ns
while (z ~= w) and (z + w > target_note_count) do
local m1 = math.max(z, w)
local m2 = math.min(z, w)
-- For use with updating ancestor mos chunks
local z_prev = z
-- Update step ratios
z = m2
w = m1 - m2
end
return p.new(z, w, mos.equave)
end
end


731行目: 763行目:
function p.et_suffix(mos)
function p.et_suffix(mos)
if rat.eq(mos.equave, rat.new(2)) then
if rat.eq(mos.equave, rat.new(2)) then
return "edo"
return "平均律"
elseif rat.eq(mos.equave, rat.new(3)) then
elseif rat.eq(mos.equave, rat.new(3)) then
return "edt"
return "edt"
787行目: 819行目:
local mos_et = p.as_et(mos, step_ratio, suffix)
local mos_et = p.as_et(mos, step_ratio, suffix)
return et.backslash_display(mos_et, p.interval_to_et_steps(interval, step_ratio))
return et.backslash_display(mos_et, p.interval_to_et_steps(interval, step_ratio))
end
--------------------------------------------------------------------------------
------------------------------- CENT FUNCTIONS ---------------------------------
--------------------------------------------------------------------------------
-- Given a mos and a step ratio, return the number of cents for its bright gen.
function p.bright_gen_to_cents(mos, step_ratio)
local interval_steps = p.interval_to_et_steps(p.bright_gen(mos), step_ratio)
local equave_steps = p.equave_to_et_steps(mos, step_ratio)
return interval_steps * rat.cents(mos.equave) / equave_steps
end
-- Given a mos and a step ratio, return the number of cents for its dark gen.
function p.dark_gen_to_cents(mos, step_ratio)
local interval_steps = p.interval_to_et_steps(p.dark_gen(mos), step_ratio)
local equave_steps = p.equave_to_et_steps(mos, step_ratio)
return interval_steps * rat.cents(mos.equave) / equave_steps
end
-- Given a mos and a step ratio, return the number of cents for its period.
-- The period is the interval at which the step pattern repeats, so no step
-- ratio is needed.
function p.period_to_cents(mos)
return rat.cents(mos.equave) / p.period_count(mos)
end
-- Given a mos and a step ratio, return the number of cents for its equave.
-- The period is the interval at which the step pattern repeats, and the equave
-- is a multiple of that (at least for multi-period mosses), so no step ratio is
-- needed.
function p.equave_to_cents(mos)
return rat.cents(mos.equave)
end
-- Given an interval vector and step ratio, convert it to cents. This requires info about the mos itself.
function p.interval_to_cents(interval, mos, step_ratio)
local interval_steps = p.interval_to_et_steps(interval, step_ratio)
local equave_steps = p.equave_to_et_steps(mos, step_ratio)
return interval_steps * rat.cents(mos.equave) / equave_steps
end
end


835行目: 827行目:
-- Tester function
-- Tester function
function p.tester()
function p.tester()
local input_mos = p.new(4,1,3)
local step_ratio = {2,1}
local interval_vector = {["L"] = 3, ["s"] = 1}
--return p.as_string(input_mos, false)


--return p.as_et(p.new(5,2), {2,1})
--local interval = p.dark_gen(p.new(5,2))
--return p.interval_chroma_count(interval, p.new(5,2), -1)
--[[
--return p.equave_reduce({['L']=-3,['s']=-1},p.new(5,2))
return  
--return p.interval_from_mos(p.new(5,2), 4, 1)
p.mode_by_brightness(p.new(5,2), 0) .. " " .. p.mode_by_brightness(p.new(5,2), 6-6) .. "\n" ..
--return p.interval_from_step_sequence("LLLdLLc")
p.mode_by_brightness(p.new(5,2), 1) .. " " .. p.mode_by_brightness(p.new(5,2), 6-5) .. "\n" ..
--return p.mode_from_mos(p.new(5,2), -90673)
p.mode_by_brightness(p.new(5,2), 2) .. " " .. p.mode_by_brightness(p.new(5,2), 6-4) .. "\n" ..
--return p.mode_to_step_matrix(p.brightest_mode(p.new(5,4)))
p.mode_by_brightness(p.new(5,2), 3) .. " " .. p.mode_by_brightness(p.new(5,2), 6-3) .. "\n" ..
--return p.mode_rotations("LssLLssL")
p.mode_by_brightness(p.new(5,2), 4) .. " " .. p.mode_by_brightness(p.new(5,2), 6-2) .. "\n" ..
--return p.mode_rotations_to_step_matrices("LLsLsAs")
p.mode_by_brightness(p.new(5,2), 5) .. " " .. p.mode_by_brightness(p.new(5,2), 6-1) .. "\n" ..
--return p.mode_from_mos(p.new(5,2),1)
p.mode_by_brightness(p.new(5,2), 6) .. " " .. p.mode_by_brightness(p.new(5,2), 6-0)
local string_return = ""
]]--
for i = 1, 7 do
string_return = string_return .. p.mode_from_mos(p.new(5,2), i-1) .. "\n"
return
end
p.as_string(p.new(5,2))        .. "\n" ..
return string_return
p.as_string(p.new(4,5,3))      .. "\n" ..
p.as_long_string(p.new(5,2))  .. "\n" ..
p.as_long_string(p.new(4,5,3)) .. "\n" ..
p.as_link(p.new(5,2))          .. "\n" ..
p.as_link(p.new(4,5,3))       .. "\n" ..
p.as_long_link(p.new(5,2))     .. "\n" ..
p.as_long_link(p.new(4,5,3))  .. "\n" ..
p.as_string(p.new(5,0))       .. "\n" ..
p.as_string(p.new(4,0,3))      .. "\n" ..
p.as_long_string(p.new(5,0))  .. "\n" ..
p.as_long_string(p.new(4,0,3)) .. "\n" ..
p.as_link(p.new(5,0))         .. "\n" ..
p.as_link(p.new(4,0,3))        .. "\n" ..
p.as_long_link(p.new(5,0))    .. "\n" ..
p.as_long_link(p.new(4,0,3))  .. "\n" ..
p.as_string(p.new(0,2))        .. "\n" ..
p.as_string(p.new(0,5,3))      .. "\n" ..
p.as_long_string(p.new(0,2))  .. "\n" ..
p.as_long_string(p.new(0,5,3)) .. "\n" ..
p.as_link(p.new(0,2))          .. "\n" ..
p.as_link(p.new(0,5,3))        .. "\n" ..
p.as_long_link(p.new(0,2))    .. "\n" ..
p.as_long_link(p.new(0,5,3))
end
end


return p
return p