Skip to content

Instantly share code, notes, and snippets.

@GanaramInukshuk
Created July 14, 2022 20:32
Show Gist options
  • Save GanaramInukshuk/4f34eb7b113e8c735c266f8f8e45d865 to your computer and use it in GitHub Desktop.
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.
#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