Recursive structure of MOS scales: Difference between revisions
No edit summary |
→Finding a generator: Fixed an error in one of the algorithms; replace some pseudocode with python code; will revisit for further rewriting (to newer, personal standards) |
||
Line 201: | Line 201: | ||
### Let z = m2 mod m1 and w = m1 - z. | ### Let z = m2 mod m1 and w = m1 - z. | ||
### Let prescale be the mos string for zL ws. Recursively call this algorithm to find the scale for zL ws; the final scale will be based on this. | ### Let prescale be the mos string for zL ws. Recursively call this algorithm to find the scale for zL ws; the final scale will be based on this. | ||
### If x | ### If x < y, reverse the order of characters in the prescale. This is only needed if there are more L's than s's in the final scale. | ||
### To produce the final scale, the L's and s's of the prescale must be replaced with substrings consisting of L's and s's. Let u = ceil(m2/m1) and v = floor(m2/m1). | ### To produce the final scale, the L's and s's of the prescale must be replaced with substrings consisting of L's and s's. Let u = ceil(m2/m1) and v = floor(m2/m1). | ||
#### If x > y, every instance of an L in the prescale is replaced with one L and u s's, and every s replaced with one L and v s's. This produces the final scale in its brightest mode. | #### If x > y, every instance of an L in the prescale is replaced with one L and u s's, and every s replaced with one L and v s's. This produces the final scale in its brightest mode. | ||
#### If y > x, every instance of an L in the prescale is replaced with u L's and one s, and every s replaced with v L's and one s. This produces the final scale in its brightest mode. | #### If y > x, every instance of an L in the prescale is replaced with u L's and one s, and every s replaced with v L's and one s. This produces the final scale in its brightest mode. | ||
=== | === Python code === | ||
The code shown here is a Python function that implements the algorithm described above.<!-- TODO: Add comments to code --><syntaxhighlight lang="python3" line="1"> | |||
def CalculateMos(x, y): | |||
mos_string = "" | |||
if (x == 1 or y == 1): | |||
if (x == 1 and y == 1): | |||
mos_string = "Ls" | |||
elif (x == 1 and y > 1): | |||
mos_string = "L" + "s" * y | |||
elif (x > 1 and y == 1): | |||
mos_string = "L" * x + "s" | |||
else: | |||
mos_string = "unable to find mos" | |||
else: | |||
k = min(x, y) | |||
while (k > 0): | |||
if (x % k == 0 and y % k == 0): | |||
break | |||
k -= 1 | |||
if (k != 1): | |||
mos_string = CalculateMos(x // k, y // k) * k | |||
elif (k == 1): | |||
m1 = min(x, y) | |||
m2 = max(x, y) | |||
z = m2 % m1 | |||
w = m1 - z | |||
prescale = CalculateMos(z, w) | |||
if (x < y): | |||
prescale = prescale[::-1] | |||
l_rule = "" | |||
s_rule = "" | |||
if (x > y): | |||
l_rule = "L" * math.ceil(m2 / m1) + "s" | |||
s_rule = "L" * math.floor(m2 / m1) + "s" | |||
else: | |||
l_rule = "L" + "s" * math.ceil(m2 / m1) | |||
s_rule = "L" + "s" * math.floor(m2 / m1) | |||
for step in prescale: | |||
if step == "L": | |||
mos_string += l_rule | |||
elif step == "s": | |||
mos_string += s_rule | |||
else: | |||
mos_string = "unable to find mos" | |||
return mos_string | |||
</syntaxhighlight> | |||
=== Example === | === Example === | ||
To find the generators for 5L 7s, we can start at step 2.3.1, as the mos is neither multi-period nor 1L 1s. Using these steps, we can recursively find the mos 2L 3s, which means we recursively find the mos 1L 1s. | |||
Since our mos is Ls, we need to expand L and s to find the mos for 2L 3s. Since we have more small steps than large steps, we need to reverse the scale into sL. Since we recursively called this algorithm for 2L 3s, we can deduce that L is replaced with Lss, and s with Ls, using steps 2.3.5.1 and 2.3.5.2, producing the mos 2L 3s represented as LsLss. | |||
Since our mos is LsLss, we need to expand L and s again to find the mos for 5L 7s. Since we have more small steps than large steps, we need to reverse the scale into ssLsL. Since we recursively called this algorithm for 5L 7s, we can deduce that L is replaced with Lss and s with Ls. | |||
With that, our final mos in its brightest mode is LsLsLssLsLss. | |||
To make sure that we have the correct mos, we can relate it back to 5L 2s, whose mos LLLsLLs. By replacing "Ls" with L, we can reduce our mos into that for 5L 2s. | |||
== Finding a generator == | == Finding a generator == | ||
The recursive structure also allows a recursive algorithm to find the generator: | The recursive structure also allows a recursive algorithm to find the generator. Note that every mos actually has two generators: its bright and dark generator. Whichever generator is used depends on the situation, so the algorithm, which is a modification of the mos-finding algorithm from last section, finds both. The reason why this works is that the reduction both ''preserves'' and ''reflects'' the generator; see the ''proofs'' section. | ||
=== General algorithm === | |||
This is the general algorithm for finding a moment of symmetry scale's bright and dark generators for a mos xL ys as two (unequal) halves of the mos pattern xL ys. This algorithm will produce that scale's generators without any prior knowledge of how the scale's steps are ordered. This algorithm is recursive. Let x be the number of L's and y the number of s's. | |||
# If either x or y is equal to 1 (base cases): | |||
## If both x and y are equal to 1, then the bright generator is "L" and the dark generator is "s". | |||
## If only x is equal to 1, then the bright generator is "L" followed by y-1 s's, and the dark generator is "s". | |||
## If only y is equal to 1, then the bright generator is "L" and the dark generator is x-1 L's followed by "s". | |||
# If neither x nor y is equal to 1 (recursive cases): | |||
## Let k be the greatest common factor of x and y. | |||
## If x and y share a common factor k, where k is greater than 1, then recursively call this algorithm to find the generators for (x/k)L (y/k)s; the generators returned this way will apply to the period rather than the octave. | |||
## If x and y don't share a common factor that is greater than 1 (if x and y are coprime), then: | |||
### Let m1 = min(x, y) and m2 = max(x, y). | |||
### Let z = m2 mod m1 and w = m1 - z. | |||
### Let pregen1 and pregen2 be the bright and dark generators for the mos zL ws. Recursively call this algorithm to find the generators for zL ws; the final scale's generators will be based on this. | |||
### If x < y, reverse the order of characters in pregen1 and pregen2, then swap pregen1 and pregen2. This is only needed if there are more L's than s's in the scale. | |||
### To produce the scale's generators, the L's and s's of the two generators must be replaced with substrings consisting of L's and s's. Let u = ceil(m2/m1) and v = floor(m2/m1). | |||
#### If x > y, every instance of an L in both generators is replaced with one L and u s's, and every s replaced with one L and v s's. This produces the final scale's generators, where pregen1 is the bright generator and pregen2 is the dark generator. | |||
#### If y > x, every instance of an L in both generators is replaced with u L's and one s, and every s replaced with v L's and one s. This produces the final scale's generators, where pregen1 is the bright generator and pregen2 is the dark generator. | |||
=== Python code === | |||
The code shown here is a Python function that implements the algorithm described above.<!-- TODO: Add comments to code --><syntaxhighlight lang="python3" line="1"> | |||
def CalculateMosGenerators(x, y): | |||
mos_string_left = "" | |||
mos_string_right = "" | |||
if (x == 1 or y == 1): | |||
if (x == 1 and y == 1): | |||
mos_string_left = "L" | |||
mos_string_right = "s" | |||
elif (x == 1 and y > 1): | |||
mos_string_left = "L" + "s" * (y - 1) | |||
mos_string_right = "s" | |||
elif (x > 1 and y == 1): | |||
mos_string_left = "L" | |||
mos_string_right = "L" * (x - 1) + "s" | |||
else: | |||
mos_string_left = "unable to find generator" | |||
mos_string_right = "unable to find generator" | |||
else: | |||
k = min(x, y) | |||
while (k > 0): | |||
if (x % k == 0 and y % k == 0): | |||
break | |||
k -= 1 | |||
if (k != 1): | |||
mos_string_left, mos_string_right = CalculateMosGenerators(x // k, y // k) | |||
elif (k == 1): | |||
m1 = min(x, y) | |||
m2 = max(x, y) | |||
z = m2 % m1 | |||
w = m1 - z | |||
prescale_left, prescale_right = CalculateMosGenerators(z, w) | |||
if (x < y): | |||
prescale_left = prescale_left[::-1] | |||
prescale_right = prescale_right[::-1] | |||
temp = prescale_left | |||
prescale_left = prescale_right | |||
prescale_right = temp | |||
l_rule = "" | |||
s_rule = "" | |||
if (x > y): | |||
l_rule = "L" * math.ceil(m2 / m1) + "s" | |||
s_rule = "L" * math.floor(m2 / m1) + "s" | |||
else: | |||
l_rule = "L" + "s" * math.ceil(m2 / m1) | |||
s_rule = "L" + "s" * math.floor(m2 / m1) | |||
for step in prescale_left: | |||
if step == "L": | |||
mos_string_left += l_rule | |||
elif step == "s": | |||
mos_string_left += s_rule | |||
for step in prescale_right: | |||
if step == "L": | |||
mos_string_right += l_rule | |||
elif step == "s": | |||
mos_string_right += s_rule | |||
else: | |||
mos_string_left = "unable to find generator" | |||
mos_string_right = "unable to find generator" | |||
return mos_string_left, mos_string_right | |||
</syntaxhighlight> | |||
=== Example === | |||
To find the generators for 5L 7s, we can start at step 2.3.1, as the mos is neither multi-period nor 1L 1s. Using these steps, we can recursively find the generators for the mos 2L 3s, which means we recursively find the generators for 1L 1s. | |||
Since our generators are L and s, we need to expand L and s to find the generators for 2L 3s. Since we have more small steps than large steps, we need to reverse and swap the generators, so our bright generator is s and dark generator is L. Since we recursively called this algorithm for 2L 3s, we can deduce that L is replaced with Lss, and s with Ls, using steps 2.3.5.1 and 2.3.5.2, producing the generator pair of Ls and Lss. | |||
Since our generators are now Ls and Lss, we need to expand them again to find the generators for 5L 7s. Since we have more small steps than large steps, we need to reverse and swap the generators, so our bright generator is ssL and dark generator is sL. Since we recursively called this algorithm for 5L 7s, we can deduce that L is replaced with Lss and s with Ls. | |||
With that, our final generator pair is LsLsLss and LsLss. | |||
To make sure that we have the correct generator pair, we can relate it back to 5L 2s, whose generator pair is LLLs and LLs. By replacing "Ls" with L, we can reduce our generator pair into that for 5L 2s. | |||
==Tree of MOS patterns== | ==Tree of MOS patterns== |