Module:MOS: Difference between revisions
Bugfixed interval_as_string for zero-counts |
mNo edit summary |
||
| (54 intermediate revisions by 2 users not shown) | |||
| Line 1: | Line 1: | ||
-- | -- This module follows [[User:Ganaram inukshuk/Provisional style guide for Lua]] | ||
local et = require("Module:ET") | |||
local rat = require("Module:Rational") | |||
local utils = require("Module:Utils") | |||
local | |||
local | |||
local | |||
local p = {} | local p = {} | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
------------------------------ | ----------------------------- MOS-CREATING FUNCTIONS --------------------------- | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
-- Create a new mos | -- Create a new mos as a table containing the counts for large and small steps, | ||
-- 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)". | ||
-- 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( | local nL, ns, equave = unparsed:match("^(%d+)[Ll].-(%d+)[Ss]%s*(.*)$") | ||
nL = tonumber(nL) | nL = tonumber(nL) | ||
ns = tonumber(ns) | ns = tonumber(ns) | ||
equave = equave:match( | 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 | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
------------------------------- STRING FUNCTIONS | ---------------------- VALIDATION AND CHECKING 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", | -- Scalesig is "xL ys <p/q>" for valid mosses, omitting <p/q> for 2/1 scales. | ||
function p.as_string(mos) | -- Degenerate mosses (nL 0s or 0L ns) produce a string for its corresponding | ||
local suffix = | -- et (n-ed-p/q). | ||
-- Option to use nbsp is provided using the second param; default is nbsp. | |||
function p.as_string(mos, use_nbsp) | |||
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 " " or " ") .. mos.ns .. "s" .. suffix | |||
else | |||
return math.max(mos.nL, mos.ns) .. p.et_suffix(mos) | |||
end | end | ||
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. | ||
function p.as_long_string(mos) | -- Degenerate mosses (nL 0s or 0L ns) produce a string for its corresponding | ||
local suffix = | -- et (n-ed-p/q). | ||
-- Option to use nbsp is provided using the second param; default is nbsp. | |||
function p.as_long_string(mos, use_nbsp) | |||
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 " " or " ") .. string.format("(%s-equivalent)", rat.as_ratio(mos.equave):lower()) | |||
end | |||
return mos.nL .. "L" .. (use_nbsp and " " or " ") .. mos.ns .. "s" .. suffix | |||
else | |||
return math.max(mos.nL, mos.ns) .. p.et_suffix(mos) | |||
end | 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 | |||
-- 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 | ||
| Line 95: | Line 118: | ||
-- where i and j are the quantities for L and s. | -- where i and j are the quantities for L and s. | ||
function p.interval_as_string(interval) | function p.interval_as_string(interval) | ||
-- Quantity of L's as a string | -- Quantity of L's as a string | ||
if interval[ | local L_string = "" | ||
if interval["L"] == 0 then | |||
L_string = "" | L_string = "" | ||
elseif interval[ | elseif interval["L"] == 1 then | ||
L_string = "L" | L_string = "L" | ||
else | else | ||
L_string = string.format("%dL", interval[ | L_string = string.format("%dL", interval["L"]) | ||
end | end | ||
-- Quantity of s's as a string | -- Quantity of s's as a string | ||
if math.abs(interval[ | local s_string = "" | ||
if math.abs(interval["s"]) == 0 then | |||
elseif math.abs(interval[ | s_string = "" | ||
elseif math.abs(interval["s"]) == 1 then | |||
s_string = "s" | |||
else | else | ||
s_string = string.format("%ds", math.abs(interval["s"])) | |||
end | end | ||
if | if interval["L"] == 0 and interval["s"] == 0 then | ||
return "0" | return "0" | ||
elseif | elseif interval["L"] == 0 and interval["s"] ~= 0 then | ||
return s_string | |||
elseif interval["L"] ~= 0 and interval["s"] == 0 then | |||
return L_string | return L_string | ||
else | else | ||
return L_string .. (interval[ | return L_string .. (interval["s"] > 0 and " + " or " - ") .. s_string | ||
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 | ||
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 | ||
| Line 139: | Line 213: | ||
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 .. | result = result .. "s" | ||
else | else | ||
current_L = current_L + 1 | current_L = current_L + 1 | ||
result = result .. | result = result .. "L" | ||
end | end | ||
end | end | ||
| Line 156: | Line 230: | ||
end | end | ||
-- Find the darkest true-mos mode of a mos. | -- 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 | ||
| Line 163: | Line 236: | ||
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 -- !esreveR | ||
else | else | ||
current_L = current_L + 1 | current_L = current_L + 1 | ||
result = | 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 | -- Ordering here is based on the number of BRIGHT GENS DOWN PER PERIOD: | ||
-- 0 is the brightest mode, 1 is 2nd brightest, etc... | |||
function p. | -- To go by darkness, pass in p-d-1 for the 2nd arg, where p is the period count | ||
return p.rotate_mode(p.brightest_mode(mos), | -- and d is the number of DARK GENS UP PER PERIOD. | ||
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 | ||
-- Given a mos, list all modes in descending order of brightness. | -- Given a mos, list all modes in descending order of brightness. | ||
| Line 202: | Line 274: | ||
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. Order | -- List all unique rotations for a mode, by order of leftward shifts. Order by | ||
-- 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 p | -- (where s mod p = 0), then there will be p modes. If the mode has one step | ||
-- | -- type, then there is only one mode. | ||
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 | if not utils.table_contains(rotations, current_mode) then | ||
table.insert(rotations, current_mode) | table.insert(rotations, current_mode) | ||
end | end | ||
| Line 225: | Line 299: | ||
-- 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 | -- shift it to the right. Helper function for mode_by_brightness(). | ||
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 -- | local shift_amt = shift_amt == nil and 1 or shift_amt % #mode_string -- Default 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) | ||
| Line 242: | Line 315: | ||
function p.mode_to_step_matrix(mode_string) | function p.mode_to_step_matrix(mode_string) | ||
local matrix = {} | local matrix = {} | ||
for i = | for i = 0, #mode_string do | ||
local interval = p.interval_from_step_sequence(string.sub(mode_string, 0, i)) | |||
local interval = p.interval_from_step_sequence(string.sub(mode_string, 0, | |||
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 | ||
| Line 258: | Line 337: | ||
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 | ||
| Line 269: | Line 349: | ||
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 | |||
-- Given an input mos, produce its modal union. | |||
-- This is a listing of every interval's large and small sizes. | |||
function p.modal_union(input_mos) | |||
local brightest_mode = p.brightest_mode(input_mos) | |||
local darkest_mode = p.darkest_mode (input_mos) | |||
local interval_count = p.equave_step_count(input_mos) + 1 | |||
local modal_union = {} | |||
for i = 1, interval_count do | |||
local bright_step_seq = string.sub(brightest_mode, 1, i-1) | |||
local dark_step_seq = string.sub(darkest_mode , 1, i-1) | |||
local bright_interval = p.interval_from_step_sequence(bright_step_seq) | |||
local dark_interval = p.interval_from_step_sequence(dark_step_seq ) | |||
if p.interval_eq(bright_interval, dark_interval) then | |||
table.insert(modal_union, bright_interval) | |||
else | |||
table.insert(modal_union, dark_interval ) | |||
table.insert(modal_union, bright_interval) | |||
end | |||
end | |||
return modal_union | |||
end | end | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
--------------- | --------------- FUNCTIONS FOR GENERATOR AND PERIOD INTERVALS ------------------- | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
-- Compute the bright gen as a vector of L's and s's. | -- Compute the bright gen as a vector of L's and s's. Since all mosstep | ||
-- | -- intervals (excluding the root and period) have two sizes, this returns the | ||
-- | -- large/perfect size. | ||
function p.bright_gen(mos) | function p.bright_gen(mos) | ||
local nL = mos.nL | local nL = mos.nL | ||
| Line 285: | Line 391: | ||
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 = {[ | 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 | ||
| Line 298: | Line 404: | ||
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[ | result["L"] = current_L | ||
result[ | 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. | -- Compute the dark gen as a vector of L's and s's. Since all mosstep | ||
-- | -- intervals (excluding the root and period) have two sizes, this returns the | ||
-- | -- small/perfect size. | ||
function p.dark_gen(mos) | function p.dark_gen(mos) | ||
local bright_gen = p.bright_gen(mos) | local bright_gen = p.bright_gen(mos) | ||
| Line 319: | Line 425: | ||
-- 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 only | -- Period intervals as mossteps only appear as one size. | ||
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, | ||
[ | ["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 | ||
-- the same | -- is the same as p.period(). | ||
function p.equave(mos) | function p.equave(mos) | ||
return { | return { | ||
[ | ["L"] = mos.nL, | ||
[ | ["s"] = mos.ns | ||
} | } | ||
end | end | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
------------------ | ------------------- FUNCTIONS FOR SINGLE-STEP INTERVALS ------------------------ | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
-- | -- Return 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 { [ | return { ["L"] = 0, ["s"] = 0 } | ||
end | end | ||
-- | -- Return 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 { [ | return { ["L"] = 1, ["s"] = -1 } | ||
end | end | ||
-- | -- Return the vector for an augmented step. It's a large step plus a chroma. | ||
function p.augmented_step() | function p.augmented_step() | ||
return { [ | return { ["L"] = 2, ["s"] = -1 } | ||
end | end | ||
-- | -- Return the vector for a single large step. | ||
function p.large_step() | function p.large_step() | ||
return { [ | return { ["L"] = 1, ["s"] = 0 } | ||
end | end | ||
-- | -- Return the vector for a single small step. | ||
function p.small_step() | function p.small_step() | ||
return { [ | return { ["L"] = 0, ["s"] = 1 } | ||
end | end | ||
-- | -- Return the vector for a diminished step. It's a small step minus a chroma. | ||
function p.diminished_step() | function p.diminished_step() | ||
return { [ | return { ["L"] = -1, ["s"] = 2 } | ||
end | end | ||
| Line 382: | Line 488: | ||
-- 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 { [ | return { ["L"] = i, ["s"] = j } | ||
end | end | ||
-- Compute an arbitrary mos interval as a vector of L's and s's. | -- Compute an arbitrary mos interval as a vector of L's and s's. Params: | ||
-- | -- - step_count: the number of steps subtended by the mosstep. | ||
-- - size_offset: denotes whether to return the large size (0) or the small | |||
-- | -- size (-1) (or if this is a period interval, the diminished size). Values | ||
-- other than 0 or 1 represent alterations by multiple chromas, such as | |||
-- augmented (1) or diminished (-2). | |||
-- | |||
-- | |||
-- | |||
- | |||
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 | ||
| Line 413: | Line 513: | ||
-- 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 | ||
| Line 443: | Line 543: | ||
------------------------------- 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[ | return interval["L"] + interval["s"] | ||
end | end | ||
| Line 460: | Line 565: | ||
end | end | ||
-- | -- TODO: deprecate this since "equave_step_count" is redundant and longer than | ||
-- "step count". | |||
function p.equave_step_count(mos) | function p.equave_step_count(mos) | ||
return mos.nL + mos.ns | return mos.nL + mos.ns | ||
| Line 474: | Line 580: | ||
-- 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[ | return interval["L"] + interval["s"] | ||
end | end | ||
| Line 481: | Line 587: | ||
-- 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 | ||
-- this to -1 counts chromas from the small size. Like size_offset for | |||
-- | -- interval_from_mos, this can be used to denote altered mossteps (augmented, | ||
-- | -- diminished, etc). | ||
-- | |||
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. | ||
| Line 492: | Line 596: | ||
local base_interval = p.interval_from_mos(mos, step_count, 0) | local base_interval = p.interval_from_mos(mos, step_count, 0) | ||
return interval[ | return interval["L"] - base_interval["L"] - size_offset | ||
end | end | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
--------------- INTERVAL ARITHMETIC AND MANIPULATION FUNCTIONS ----------------- | |||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
| Line 502: | Line 606: | ||
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"], | ||
[ | ["s"] = interval_1["s"] + interval_2["s"] | ||
} | } | ||
end | end | ||
| Line 510: | Line 614: | ||
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"], | ||
[ | ["s"] = interval_1["s"] - interval_2["s"] | ||
} | } | ||
end | end | ||
-- | -- Stack an interval, or 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, | ||
[ | ["s"] = interval["s"] * amt | ||
} | } | ||
end | end | ||
| Line 526: | Line 630: | ||
function p.interval_eq(interval_1, interval_2) | function p.interval_eq(interval_1, interval_2) | ||
return | return | ||
interval_1[ | interval_1["L"] == interval_2["L"] and | ||
interval_1[ | interval_1["s"] == interval_2["s"] | ||
end | end | ||
-- 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. | -- interval to add to produce the period. For single-period mosses, 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 | ||
| Line 551: | Line 653: | ||
-- 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) | ||
| Line 588: | Line 692: | ||
-- ratios to be entered. (The rational module isn't suitable since it simplifies | -- ratios to be entered. (The rational module isn't suitable since it simplifies | ||
-- ratios.) | -- ratios.) | ||
function p. | function p.as_et(mos, step_ratio, suffix) | ||
local suffix = suffix or nil | |||
local et_size = mos.nL * step_ratio[1] + mos.ns * step_ratio[2] | local et_size = mos.nL * step_ratio[1] + mos.ns * step_ratio[2] | ||
return et.new(et_size, mos.equave, | return et.new(et_size, mos.equave, suffix) | ||
end | end | ||
| Line 599: | Line 704: | ||
end | end | ||
-- Given a mos and a step ratio, return the number of et-steps for its dark | -- 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) | function p.dark_gen_to_et_steps(mos, step_ratio) | ||
return p.interval_to_et_steps(p.dark_gen(mos), step_ratio) | return p.interval_to_et_steps(p.dark_gen(mos), step_ratio) | ||
| Line 613: | Line 717: | ||
function p.equave_to_et_steps(mos, step_ratio) | function p.equave_to_et_steps(mos, step_ratio) | ||
return p.interval_to_et_steps(p.equave(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 | |||
-------------------------------------------------------------------------------- | |||
------------------------ EQUAL-TUNING STRING FUNCTIONS ------------------------- | |||
-------------------------------------------------------------------------------- | |||
-- Given a mos, return its equal temperament suffix as a string (edo, edt, edf, or ed-p/q). | |||
function p.et_suffix(mos) | |||
if rat.eq(mos.equave, rat.new(2)) then | |||
return "edo" | |||
elseif rat.eq(mos.equave, rat.new(3)) then | |||
return "edt" | |||
elseif rat.eq(mos.equave, rat.new(3, 2)) then | |||
return "edf" | |||
else | |||
return "ed" .. rat.as_ratio(mos.equave) | |||
end | |||
end | |||
-- Given a mos and step ratio, return its equal temperament as a string "{steps}\{division}{suffix}". | |||
function p.et_string(mos, step_ratio, suffix) | |||
local suffix = suffix or nil | |||
local et_mos = p.as_et(mos, step_ratio, suffix) | |||
return et.as_string(et_mos) | |||
end | |||
-- Given a mos and step ratio, compute the number of et-steps for its bright gen | |||
-- as a string "{steps}\{division}{suffix}". | |||
function p.bright_gen_to_et_string(mos, step_ratio, suffix) | |||
return p.interval_to_et_string(p.bright_gen(mos), mos, step_ratio, suffix) | |||
end | |||
-- Given a mos and step ratio, compute the number of et-steps for its dark gen, | |||
-- as a string "{steps}\{division}{suffix}". | |||
function p.dark_gen_to_et_string(mos, step_ratio, suffix) | |||
return p.interval_to_et_string(p.dark_gen(mos), mos, step_ratio, suffix) | |||
end | |||
-- Given a mos and step ratio, compute the number of et-steps for its period, | |||
-- as a string "{steps}\{division}{suffix}". | |||
function p.period_to_et_string(mos, step_ratio, suffix) | |||
return p.interval_to_et_string(p.period(mos), mos, step_ratio, suffix) | |||
end | |||
-- Given a mos, compute the number of et-steps for its period, reduced, | |||
-- as a string "{steps}\{division}{suffix}". Does not reuqire a step ratio. | |||
-- NOTE: no such function for returning only the number of steps is needed since | |||
-- that's the same as period_count(). | |||
function p.reduced_period_to_et_string(mos, suffix) | |||
return p.interval_to_et_string({["L"] = 1, ["s"] = 1}, p.root(mos), {1,0}, suffix) | |||
end | |||
-- Given a mos and step ratio, compute the number of et-steps for its equave, | |||
-- as a string "{steps}\{division}{suffix}". | |||
function p.equave_to_et_string(mos, step_ratio, suffix) | |||
return p.interval_to_et_string(p.equave(mos), mos, step_ratio, suffix) | |||
end | end | ||
-- Given an interval vector and step ratio, compute the number of et-steps it | -- Given an interval vector and step ratio, compute the number of et-steps it | ||
-- corresponds to. | -- corresponds to, as a string "{steps}\{division}{suffix}". Requires info | ||
function p. | -- about the mos itself. | ||
return interval | function p.interval_to_et_string(interval, mos, step_ratio, suffix) | ||
local suffix = suffix or nil | |||
local mos_et = p.as_et(mos, step_ratio, suffix) | |||
return et.backslash_display(mos_et, p.interval_to_et_steps(interval, step_ratio)) | |||
end | end | ||
| Line 654: | Line 822: | ||
end | end | ||
-- Given an interval vector and step ratio, convert it to cents. This requires | -- 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) | function p.interval_to_cents(interval, mos, step_ratio) | ||
local interval_steps = p.interval_to_et_steps(interval, step_ratio) | local interval_steps = p.interval_to_et_steps(interval, step_ratio) | ||
local equave_steps = p.equave_to_et_steps(mos, step_ratio) | local equave_steps = p.equave_to_et_steps(mos, step_ratio) | ||
return interval_steps * rat.cents(mos.equave) / equave_steps | return interval_steps * rat.cents(mos.equave) / equave_steps | ||
end | end | ||
| Line 696: | Line 835: | ||
-- 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}) | ||
--return p. | |||
--[[ | |||
return | |||
p.mode_by_brightness(p.new(5,2), 0) .. " " .. p.mode_by_brightness(p.new(5,2), 6-6) .. "\n" .. | |||
p.mode_by_brightness(p.new(5,2), 1) .. " " .. p.mode_by_brightness(p.new(5,2), 6-5) .. "\n" .. | |||
p.mode_by_brightness(p.new(5,2), 2) .. " " .. p.mode_by_brightness(p.new(5,2), 6-4) .. "\n" .. | |||
p.mode_by_brightness(p.new(5,2), 3) .. " " .. p.mode_by_brightness(p.new(5,2), 6-3) .. "\n" .. | |||
p.mode_by_brightness(p.new(5,2), 4) .. " " .. p.mode_by_brightness(p.new(5,2), 6-2) .. "\n" .. | |||
p.mode_by_brightness(p.new(5,2), 5) .. " " .. p.mode_by_brightness(p.new(5,2), 6-1) .. "\n" .. | |||
p.mode_by_brightness(p.new(5,2), 6) .. " " .. p.mode_by_brightness(p.new(5,2), 6-0) | |||
]]-- | |||
return | |||
p.as_string(p.new(5,2)) .. "\n" .. | |||
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 | ||