-
-
Save GanaramInukshuk/4f34eb7b113e8c735c266f8f8e45d865 to your computer and use it in GitHub Desktop.
This is code for a moscalc program; it finds the string for a mos xL ys in its brightest mode, given only x and y.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <iostream> | |
#include <vector> | |
#include <string> | |
#include <sstream> | |
//#include <math.h> | |
std::string CalculateMos(int x, int y); | |
std::string CalculateMosGenpair(int x, int y); | |
int main() { | |
for (int i = 2; i < 21; i++) { | |
std::cout << i << "-note scales:\n"; | |
for (int j = 1; j < i; j++) { | |
int y = i - j; | |
int x = j; | |
std::cout << CalculateMos(x, y) << " - " << x << "L " << y << "s" << std::endl; | |
} | |
std::cout << std::endl; | |
} | |
std::cout << "\nEnd of program reached.\n"; | |
return 0; | |
} | |
// Mos algorithm rules, for a mos xL ys (x L's and y s's): | |
// This algorithm, given x and y, will produce a string corresponding to the | |
// moment-of-symmetry scale xL ys, where the string represents that scale's | |
// brightest mode. There are five main cases that xL ys can fall under: | |
// - If x and y are both 1, then the scale is "Ls" | |
// - If only x is 1, then the scale is one L and however many s's. | |
// - If only y is 1, then the scale is however many L's and one s. | |
// - If x and y share a common factor k, then the mos consists of the mos | |
// string for (x/k)L (y/k)s repeated k times; k > 1 and is the greatest common | |
// factor between x and y. Recursively call this algorithm to find the mos | |
// string corresponding to (x/k)L (y/k)s. | |
// - If x and y are coprime (they have no common factors), the scale is built | |
// recursively. | |
// These cases can be grouped into two broader cases: | |
// - Either x or y is 1 (first three cases) | |
// - Neither x nor y is 1 (remaining cases) | |
// Case 5 is described as follows, given x and y being coprime. Regardless of | |
// whether x or y is larger, the mos is built up recursively from a precursor | |
// scale where its L's and s's are replaced with subsequences of L's and s's | |
// that build up the entire scale. | |
// - Let m1 = min(x, y) and m2 = max(x, y). Let z = m2 mod m1 and w = m1 - z. | |
// z and w are the number of large and small steps in the precursor scale | |
// respectively. Recursively call this algorithm for the mos representing | |
// zL ws. | |
// - Let u = ceil(m2/m1) and v = floor(m2/m1). If x < y, then the replacement | |
// rules are L -> (L and u s's) and s -> (L and v s's). Otherwise, the repl. | |
// rules are L -> (s and u L's) and s -> (s and v L's). | |
// - If x < y, or there are fewer L's than s's, then it's necessary to reverse | |
// the precursor scale before applying the replacement rules. This is to | |
// ensure the final mos string represents the mos in its brightest mode. More | |
// notes down below. | |
// How brightness is ensured: | |
// - The algorithm is recursive so that the mos's brightest mode is always | |
// returned. Consider the prescales LLs and Lss and the two sets of rules: | |
// - L->LLs and s->Ls | |
// - L->Lss and s->Ls | |
// - One way to gauge brightness is to determine whether as many L's as | |
// possible are to the left. Applying the rules to the two prescales produces | |
// these scales, with dashes as delimiters: | |
// - LLs-LLs-Ls and Lss-Lss-Ls | |
// - LLs-Ls-Ls and Lss-Ls-Ls | |
// - Applying the second set of rules results in a scale that's not in its | |
// brightest mode; the correct scales should be Ls-Lss-Lss and Ls-Ls-Lss. | |
// It turns out correcting this is as easy as reversing the prescale first. | |
// - This works no matter what the precursor scale is, because the algoritm is | |
// recursive, and no matter what the replacement rules are, because the rules | |
// will always be L->LL...LLs s->LL...Ls or L->Lss...ss s->Lss...s. | |
std::string CalculateMos(int x, int y) { | |
if (x == 1 || y == 1) { | |
//std::cout << "Either x or y (or both) is 1.\n"; | |
std::string mos; | |
if (x == 1 && y == 1) { | |
mos = "Ls"; | |
} else if (x == 1 && y > 1) { | |
mos += "L"; | |
for (int i = 0; i < y; i++) mos += "s"; | |
} else if (x > 1 && y == 1) { | |
for (int i = 0; i < x; i++) mos += "L"; | |
mos += "s"; | |
} else { | |
mos = "unable to find mos"; | |
} | |
return mos; | |
} else { | |
//std::cout << "Neither x nor y (or both) is 1.\n"; | |
// I'm pulling this GCF calculation from this webpage: | |
// https://www.geeksforgeeks.org/c-program-find-gcd-hcf-two-numbers/ | |
// It may not be entirely efficient, but for smol L and s, it's probs fine | |
int k = std::min(x, y); | |
while (k > 0) { | |
if (x % k == 0 && y % k == 0) break; | |
k--; | |
} | |
std::string mos; | |
if (k != 1) { | |
std::string pre_scale = CalculateMos(x / k, y / k); | |
for (int i = 0; i < k; i++) { | |
mos += pre_scale; | |
} | |
} else if (k == 1) { | |
int m1 = std::min(x, y); | |
int m2 = std::max(x, y); | |
int z = m2 % m1; | |
int w = m1 - z; | |
//std::cout << z << " " << w << std::endl; | |
std::string pre_mos = CalculateMos(z, w); | |
// There are a few conditions under which the precursor scale should be | |
// reversed before procceeding to ensure the final scale is in its | |
// brightest mode. | |
if (x < y) { | |
int len = pre_mos.length(); | |
int n = len - 1; | |
for (int i = 0; i < (len / 2); i++) { | |
//Using the swap method to switch values at each index | |
std::swap(pre_mos[i], pre_mos[n]); | |
n = n - 1; | |
} | |
} | |
std::string l_repl_rule = ""; | |
std::string s_repl_rule = ""; | |
if (x > y) { | |
for (int i = 0; i < (m2 / m1); i++) { | |
l_repl_rule += "L"; | |
s_repl_rule += "L"; | |
} | |
l_repl_rule += "Ls"; | |
s_repl_rule += "s"; | |
} else { | |
l_repl_rule += "Ls"; | |
s_repl_rule += "L"; | |
for (int i = 0; i < (m2 / m1); i++) { | |
l_repl_rule += "s"; | |
s_repl_rule += "s"; | |
} | |
} | |
for (int i = 0; i < pre_mos.size(); i++) { | |
if (pre_mos[i] == 'L') mos += l_repl_rule; | |
else if (pre_mos[i] == 's') mos += s_repl_rule; | |
} | |
} else { | |
mos = "unable to find mos"; | |
} | |
return mos; | |
} | |
} | |
// This algorithm is very similar to the CalculateMos function, with the | |
// following modifications: | |
// - The recursive base case for this function (either x or y or both is 1) | |
// instead returns a scale that contains a delimiter that splits the scale | |
// into two parts. The left one is the chroma-positive generator (CPG) and | |
// the right one is the chroma-negative generator (CNG). | |
// - If both x and y are 1, the scale returned is "L-s". | |
// - If only x is 1, the scale returned is still reducible to "Ls". The scale | |
// returned consists of L, y-1 s's, the delimiter, and a final s. | |
// - If only y is 1, the scale returned is still reducible to "Ls". The scale | |
// returned consists of one L, the delimiter, x-1 L's, and a final s. | |
// - If x and y share a common factor k, the scale will not be duplicated, | |
// since the genpair applies to a single period and not the entire equave. | |
// Instead, the genpair returned represents (x/k)L (y/k)s. k is the GCF of | |
// x and y. | |
// - If x and y are coprime, the algorithm proceeds as normal with the modified | |
// base cases. | |
std::string CalculateMosGenpair(int x, int y) { | |
if (x == 1 || y == 1) { | |
//std::cout << "Either x or y (or both) is 1.\n"; | |
std::string mos = ""; | |
if (x == 1 && y == 1) { | |
mos = "L-s"; | |
} else if (x == 1 && y > 1) { | |
mos += "L"; | |
for (int i = 0; i < y - 1; i++) mos += "s"; | |
mos += "-s"; | |
} else if (x > 1 && y == 1) { | |
mos += "L-"; | |
for (int i = 1; i < x; i++) mos += "L"; | |
mos += "s"; | |
} else { | |
mos = "unable to find mos"; | |
} | |
return mos; | |
} else { | |
//std::cout << "Neither x nor y (or both) is 1.\n"; | |
// I'm pulling this GCF calculation from this webpage: | |
// https://www.geeksforgeeks.org/c-program-find-gcd-hcf-two-numbers/ | |
// It may not be entirely efficient, but for smol L and s, it's probs fine | |
int k = std::min(x, y); | |
while (k > 0) { | |
if (x % k == 0 && y % k == 0) break; | |
k--; | |
} | |
std::string mos; | |
if (k != 1) { | |
/*std::string pre_scale*/ mos = CalculateMosGenpair(x / k, y / k); | |
//for (int i = 0; i < k; i++) { | |
// mos += pre_scale; | |
//} | |
} else if (k == 1) { | |
int m1 = std::min(x, y); | |
int m2 = std::max(x, y); | |
int z = m2 % m1; | |
int w = m1 - z; | |
//std::cout << z << " " << w << std::endl; | |
std::string pre_mos = CalculateMosGenpair(z, w); | |
// There are a few conditions under which the precursor scale should be | |
// reversed before procceeding to ensure the final scale is in its | |
// brightest mode. Code is pulled from here: | |
// https://www.educative.io/answers/how-to-reverse-a-string-in-cpp | |
if (x < y) { | |
int len = pre_mos.length(); | |
int n = len - 1; | |
for (int i = 0; i < (len / 2); i++) { | |
//Using the swap method to switch values at each index | |
std::swap(pre_mos[i], pre_mos[n]); | |
n = n - 1; | |
} | |
} | |
std::string l_repl_rule = ""; | |
std::string s_repl_rule = ""; | |
if (x > y) { | |
for (int i = 0; i < (m2 / m1); i++) { | |
l_repl_rule += "L"; | |
s_repl_rule += "L"; | |
} | |
l_repl_rule += "Ls"; | |
s_repl_rule += "s"; | |
} else { | |
l_repl_rule += "Ls"; | |
s_repl_rule += "L"; | |
for (int i = 0; i < (m2 / m1); i++) { | |
l_repl_rule += "s"; | |
s_repl_rule += "s"; | |
} | |
} | |
for (int i = 0; i < pre_mos.size(); i++) { | |
if (pre_mos[i] == 'L') mos += l_repl_rule; | |
else if (pre_mos[i] == 's') mos += s_repl_rule; | |
else mos += pre_mos[i]; | |
} | |
} else { | |
mos = "unable to find mos"; | |
} | |
return mos; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment