「モジュール:MOS」の版間の差分
編集の要約なし タグ: 差し戻し済み |
編集の要約なし |
||
| (同じ利用者による、間の7版が非表示) | |||
| 1行目: | 1行目: | ||
-- | -- Module for working with mosses in lua code; this serves as a "library" for | ||
local | -- mos-related modules and thus does not have a corresponding template. | ||
local | -- Functionality includes: | ||
local | -- - 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 | -------------------------------------------------------------------------------- | ||
------------------------------- 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 | |||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
----------------------------- | -------------------------------- BASE FUNCTIONS -------------------------------- | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
-- Create a new mos | -- Create a new mos. (Contains the number of large and small steps, and 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 | ||
-- | -- Pasre a mos from its scalesig. | ||
function p.parse(unparsed) | function p.parse(unparsed) | ||
local nL, ns, equave = unparsed:match( | 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( | 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 ------------------------------- | |||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
-- 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", or "xL ys<p/q>" for nonoctave scales. | ||
function p.as_string(mos) | |||
local suffix = '' | |||
if not rat.eq(mos.equave, 2) then | |||
function p.as_string(mos | suffix = '⟨' .. rat.as_ratio(mos.equave):lower() .. '⟩' | ||
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. | ||
function p.as_long_string(mos) | |||
local suffix = '' | |||
if not rat.eq(mos.equave, 2) then | |||
function p.as_long_string(mos | suffix = string.format(" (%s-equivalent)", rat.as_ratio(mos.equave):lower()) | ||
end | end | ||
return '' .. mos.nL .. 'L ' .. mos.ns .. 's' .. suffix | |||
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[ | 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 | ||
local s_string = "" | local s_string = "" | ||
if math.abs(interval[ | if math.abs(interval['s']) == 0 then | ||
s_string = "" | s_string = "" | ||
elseif math.abs(interval[ | elseif math.abs(interval['s']) == 1 then | ||
s_string = "s" | s_string = "s" | ||
else | else | ||
s_string = string.format("%ds", math.abs(interval[ | s_string = string.format("%ds", math.abs(interval['s'])) | ||
end | end | ||
if interval[ | if interval['L'] == 0 and interval['s'] == 0 then | ||
return "0" | return "0" | ||
elseif interval[ | elseif interval['L'] == 0 and interval['s'] ~= 0 then | ||
return s_string | return s_string | ||
elseif interval[ | 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 | ||
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 .. | result = result .. 's' | ||
else | else | ||
current_L = current_L + 1 | current_L = current_L + 1 | ||
result = result .. | 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 = | 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 going DOWN: 0 is the | ||
-- | -- brightest mode, 1 is 2nd brightest, etc... | ||
function p.mode_from_mos(mos, bright_gens_going_down) | |||
return p.rotate_mode(p.brightest_mode(mos), bright_gens_going_down * p.bright_gen_step_count(mos)) | |||
function p. | |||
return p.rotate_mode(p.brightest_mode(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 | -- List all unique rotations for a mode. Order of modes is by rotation. | ||
-- 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 | -- (where p divides s with remainder = 0), then there will be p modes. It's also | ||
-- | -- 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 | 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 | -- 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 -- | 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 = | for i = 1, #mode_string + 1 do | ||
local interval = p.interval_from_step_sequence(string.sub(mode_string, 0, | 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 | ||
-- 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 | --------------- INTERVAL FUNCTIONS FOR PERFECTABLE INTERVALS ------------------- | ||
------------------ (IE, GENERATORS 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. | ||
-- | -- Bright gen has two sizes: perfect (large) and diminished (small). The size | ||
-- large | -- 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 = {[ | 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[ | 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. | ||
-- | -- Dark gen has two sizes: augmented (large) and perfect (small). The size given | ||
-- | -- 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 | -- 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, | ||
[ | ['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. | ||
-- | -- Equave intervals only have one size: perfect. Equave and period intervals are | ||
-- | -- the same for single-period mosses. | ||
function p.equave(mos) | function p.equave(mos) | ||
return { | return { | ||
[ | ['L'] = mos.nL, | ||
[ | ['s'] = mos.ns | ||
} | } | ||
end | end | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
------------------ | ------------------ INTERVAL FUNCTIONS FOR SIMPLE INTERVALS --------------------- | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
-- | -- 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 { [ | return { ['L'] = 0, ['s'] = 0 } | ||
end | end | ||
-- | -- 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 { [ | return { ['L'] = 1, ['s'] = -1 } | ||
end | end | ||
-- | -- Compute 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 | ||
-- | -- Compute the vector for a single large step. | ||
function p.large_step() | function p.large_step() | ||
return { [ | return { ['L'] = 1, ['s'] = 0 } | ||
end | end | ||
-- | -- Compute the vector for a single small step. | ||
function p.small_step() | function p.small_step() | ||
return { [ | return { ['L'] = 0, ['s'] = 1 } | ||
end | end | ||
-- | -- Compute 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 | ||
| 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 { [ | 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. | ||
-- | -- The step_count param is the number of mossteps in the interval. EG, in 5L 2s, | ||
-- - size_offset | -- the large 2-mosstep is "LL", so the corresponding vector has L=2, s=0. | ||
-- | -- Mossteps larger than the equave (eg, the minor 9th in non-xen music theory) | ||
-- | -- are allowed. | ||
-- | -- 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. | ||
-- | -- - s: small step. | ||
-- | -- - c: a chroma; the difference between a large and small step. | ||
-- | -- - A: an augmented step; a large step plus 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, 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 | ||
| 565行目: | 486行目: | ||
end | end | ||
-- | -- Given a mos, compute the number of steps in its equave (L's plus s's). | ||
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[ | 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 | -- If the number of chromas from a small (EG minor) interval is desired, then | ||
-- | -- using the param size_offset can be used: 0 for chromas from large size, -1 | ||
-- | -- for chromas from small size. This can exceed the range [-1, 0] if needed. | ||
-- | -- 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[ | return interval['L'] - base_interval['L'] - size_offset | ||
end | end | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
--------------- INTERVAL ARITHMETIC | ----------------------- 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'], | ||
[ | ['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'], | ||
[ | ['s'] = interval_1['s'] - interval_2['s'] | ||
} | } | ||
end | end | ||
-- | -- 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 | ||
| 630行目: | 552行目: | ||
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 | ||
-------------------------------------------------------------------------------- | |||
---------------------- 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 | -- interval to add to produce the period. | ||
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. | ||
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 " | 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 | end | ||
| 835行目: | 827行目: | ||
-- Tester function | -- Tester function | ||
function p.tester() | function p.tester() | ||
-- | --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 p.interval_from_mos(p.new(5,2), 4, 1) | |||
--return p.interval_from_step_sequence("LLLdLLc") | |||
--return p.mode_from_mos(p.new(5,2), -90673) | |||
--return p.mode_to_step_matrix(p.brightest_mode(p.new(5,4))) | |||
--return p.mode_rotations("LssLLssL") | |||
--return p.mode_rotations_to_step_matrices("LLsLsAs") | |||
--return p.mode_from_mos(p.new(5,2),1) | |||
local string_return = "" | |||
for i = 1, 7 do | |||
string_return = string_return .. p.mode_from_mos(p.new(5,2), i-1) .. "\n" | |||
end | |||
return string_return | |||
end | end | ||
return p | return p | ||