-
-
Save GanaramInukshuk/3b09f806573ecd90745d1d7fad11abdc to your computer and use it in GitHub Desktop.
Moscalc and Modecalc (Jupyter Notebook)
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
{ | |
"cells": [ | |
{ | |
"cell_type": "code", | |
"execution_count": 16, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"import math\n", | |
"\n", | |
"# Mos algorithm rules, for a mos xL ys (x L's and y s's)\n", | |
"# This algorithm, given x and y, will produce a string corresponding to the\n", | |
"# moment-of-symmetry scale xL ys, where the string represents that scale's\n", | |
"# brightest mode. There are five main cases that xL ys can fall under:\n", | |
"# - If x and y are both 1, then the scale is \"Ls\"\n", | |
"# - If only x is 1, then the scale is one L and however many s's.\n", | |
"# - If only y is 1, then the scale is however many L's and one s.\n", | |
"# - If x and y share a common factor k, then the mos consists of the mos\n", | |
"# string for (x/k)L (y/k)s repeated k times; k > 1 and is the greatest common\n", | |
"# factor between x and y. Recursively call this algorithm to find the mos\n", | |
"# string corresponding to (x/k)L (y/k)s.\n", | |
"# - If x and y are coprime (they have no common factors), the scale is built\n", | |
"# recursively.\n", | |
"# These cases can be grouped into two broader cases:\n", | |
"# - Either x or y is 1 (first three cases)\n", | |
"# - Neither x nor y is 1 (remaining cases)\n", | |
"# Case 5 is described as follows, given x and y being coprime. Regardless of\n", | |
"# whether x or y is larger, the mos is built up recursively from a precursor\n", | |
"# scale where its L's and s's are replaced with subsequences of L's and s's\n", | |
"# that build up the entire scale.\n", | |
"# - Let m1 = min(x, y) and m2 = max(x, y). Let z = m2 mod m1 and w = m1 - z.\n", | |
"# z and w are the number of large and small steps in the precursor scale\n", | |
"# respectively. Recursively call this algorithm for the mos representing\n", | |
"# zL ws.\n", | |
"# - Let u = ceil(m2/m1) and v = floor(m2/m1). If x < y, then the replacement\n", | |
"# rules are L -> (L and u s's) and s -> (L and v s's). Otherwise, the repl.\n", | |
"# rules are L -> (s and u L's) and s -> (s and v L's).\n", | |
"# - If x < y, or there are fewer L's than s's, then it's necessary to reverse\n", | |
"# the precursor scale before applying the replacement rules. This is to\n", | |
"# ensure the final mos string represents the mos in its brightest mode. More\n", | |
"# notes down below.\n", | |
"# How brightness is ensured:\n", | |
"# - The algorithm is recursive so that the mos's brightest mode is always\n", | |
"# returned. Consider the prescales LLs and Lss and the two sets of rules:\n", | |
"# - L->LLs and s->Ls\n", | |
"# - L->Lss and s->Ls\n", | |
"# - One way to gauge brightness is to determine whether as many L's as\n", | |
"# possible are to the left. Applying the rules to the two prescales produces\n", | |
"# these scales, with dashes as delimiters:\n", | |
"# - LLs-LLs-Ls and Lss-Lss-Ls\n", | |
"# - LLs-Ls-Ls and Lss-Ls-Ls\n", | |
"# - Applying the second set of rules results in a scale that's not in its\n", | |
"# brightest mode; the correct scales should be Ls-Lss-Lss and Ls-Ls-Lss.\n", | |
"# It turns out correcting this is as easy as reversing the prescale first.\n", | |
"# - This works no matter what the precursor scale is, because the algoritm is\n", | |
"# recursive, and no matter what the replacement rules are, because the rules\n", | |
"# will always be L->LL...LLs s->LL...Ls or L->Lss...ss s->Lss...s.\n", | |
"def CalculateMos(x, y):\n", | |
"\n", | |
" mos_string = \"\"\n", | |
"\n", | |
" if (x == 1 or y == 1):\n", | |
" if (x == 1 and y == 1):\n", | |
" mos_string = \"Ls\"\n", | |
" elif (x == 1 and y > 1):\n", | |
" mos_string = \"L\" + \"s\" * y\n", | |
" elif (x > 1 and y == 1):\n", | |
" mos_string = \"L\" * x + \"s\"\n", | |
" else:\n", | |
" mos_string = \"unable to find mos\"\n", | |
" \n", | |
" else:\n", | |
" \n", | |
" k = min(x, y)\n", | |
" while (k > 0):\n", | |
" if (x % k == 0 and y % k == 0):\n", | |
" break\n", | |
" k -= 1\n", | |
"\n", | |
" if (k != 1):\n", | |
" mos_string = CalculateMos(x // k, y // k) * k\n", | |
" elif (k == 1):\n", | |
" m1 = min(x, y)\n", | |
" m2 = max(x, y)\n", | |
" z = m2 % m1\n", | |
" w = m1 - z\n", | |
"\n", | |
" prescale = CalculateMos(z, w)\n", | |
"\n", | |
" if (x < y):\n", | |
" prescale = prescale[::-1]\n", | |
"\n", | |
" l_rule = \"\"\n", | |
" s_rule = \"\"\n", | |
" if (x > y):\n", | |
" l_rule = \"L\" * math.ceil(m2 / m1) + \"s\"\n", | |
" s_rule = \"L\" * math.floor(m2 / m1) + \"s\"\n", | |
" else:\n", | |
" l_rule = \"L\" + \"s\" * math.ceil(m2 / m1)\n", | |
" s_rule = \"L\" + \"s\" * math.floor(m2 / m1)\n", | |
"\n", | |
" for step in prescale:\n", | |
" if step == \"L\":\n", | |
" mos_string += l_rule\n", | |
" elif step == \"s\":\n", | |
" mos_string += s_rule\n", | |
"\n", | |
" else:\n", | |
" mos_string = \"unable to find mos\"\n", | |
"\n", | |
" return mos_string\n", | |
"\n", | |
"# This follows the same exact procedure as CalculateMos(), with a few modifications:\n", | |
"# The base cases return a scale that's split into two parts; in fact, the entire function\n", | |
"# returns a scale split into two parts. Each half represents one of the scale's generators\n", | |
"# as a substring of the entire scale, where the left half is the chroma-positive generator\n", | |
"# and the right half is the chroma-negative generator (or CPG and CNG). The base cases are:\n", | |
"# - If x and y are both 1, the CPG is L and the CNG s\n", | |
"# - If only x is 1, the CPG is L followed by y-1 s's and the CNG s\n", | |
"# - If only y is 1, the CPG is L and the CNG is x-1 L's followed by s\n", | |
"# For the recursive cases:\n", | |
"# - If x and y have a GCF k that's greater than 1, only return the generators corresponding to\n", | |
"# the mos (x/k)L (y/k)s; the generators apply to one period, which is duplicated k times.\n", | |
"# - For the recursive case, the production rules will now apply to two separated halves of the\n", | |
"# scale, so extra code is needed for each half. Also, in the case where the prescale needs to\n", | |
"# be reversed, reversal is done by first reversing the individual substrings, followed by\n", | |
"# swapping the string for the CPG and CNG.\n", | |
"def CalculateMosGenerators(x, y):\n", | |
"\n", | |
" mos_string_left = \"\"\n", | |
" mos_string_right = \"\"\n", | |
"\n", | |
" if (x == 1 or y == 1):\n", | |
" if (x == 1 and y == 1):\n", | |
" mos_string_left = \"L\"\n", | |
" mos_string_right = \"s\"\n", | |
" elif (x == 1 and y > 1):\n", | |
" mos_string_left = \"L\" + \"s\" * (y - 1)\n", | |
" mos_string_right = \"s\"\n", | |
" elif (x > 1 and y == 1):\n", | |
" mos_string_left = \"L\"\n", | |
" mos_string_right = \"L\" * (x - 1) + \"s\"\n", | |
" else:\n", | |
" mos_string_left = \"unable to find generator\"\n", | |
" mos_string_right = \"unable to find generator\"\n", | |
" \n", | |
" else:\n", | |
" \n", | |
" k = min(x, y)\n", | |
" while (k > 0):\n", | |
" if (x % k == 0 and y % k == 0):\n", | |
" break\n", | |
" k -= 1\n", | |
"\n", | |
" if (k != 1):\n", | |
" mos_string_left, mos_string_right = CalculateMosGenerators(x // k, y // k)\n", | |
" elif (k == 1):\n", | |
" m1 = min(x, y)\n", | |
" m2 = max(x, y)\n", | |
" z = m2 % m1\n", | |
" w = m1 - z\n", | |
"\n", | |
" prescale_left, prescale_right = CalculateMosGenerators(z, w)\n", | |
"\n", | |
" if (x < y):\n", | |
" prescale_left = prescale_left[::-1]\n", | |
" prescale_right = prescale_right[::-1]\n", | |
" \n", | |
" temp = prescale_left\n", | |
" prescale_left = prescale_right\n", | |
" prescale_right = temp\n", | |
"\n", | |
" l_rule = \"\"\n", | |
" s_rule = \"\"\n", | |
" if (x > y):\n", | |
" l_rule = \"L\" * math.ceil(m2 / m1) + \"s\"\n", | |
" s_rule = \"L\" * math.floor(m2 / m1) + \"s\"\n", | |
" else:\n", | |
" l_rule = \"L\" + \"s\" * math.ceil(m2 / m1)\n", | |
" s_rule = \"L\" + \"s\" * math.floor(m2 / m1)\n", | |
"\n", | |
" for step in prescale_left:\n", | |
" if step == \"L\":\n", | |
" mos_string_left += l_rule\n", | |
" elif step == \"s\":\n", | |
" mos_string_left += s_rule\n", | |
" \n", | |
" for step in prescale_right:\n", | |
" if step == \"L\":\n", | |
" mos_string_right += l_rule\n", | |
" elif step == \"s\":\n", | |
" mos_string_right += s_rule\n", | |
"\n", | |
" else:\n", | |
" mos_string_left = \"unable to find generator\"\n", | |
" mos_string_right = \"unable to find generator\"\n", | |
"\n", | |
" return mos_string_left, mos_string_right" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 17, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"# Modecalc function\n", | |
"from enum import unique\n", | |
"from matplotlib.pyplot import sca\n", | |
"from tabulate import *\n", | |
"\n", | |
"def Modecalc(scalestring):\n", | |
"\n", | |
" modes_cpo = []\n", | |
" modes_udp = []\n", | |
"\n", | |
" # Produce the rotations of the scalestring; each rotation is a different\n", | |
" # mode, and the order they're inserted in the array is cyclic permutational\n", | |
" # order, or the order in which the modes appear when using each successive\n", | |
" # note of one mode as the root. Only unique modes are added.\n", | |
" notecount = len(scalestring)\n", | |
" for i in range(notecount):\n", | |
" scale_to_add = scalestring[i:] + scalestring[:i]\n", | |
"\n", | |
" if (modes_udp.count(scale_to_add) == 0):\n", | |
" modes_udp.append(scale_to_add)\n", | |
"\n", | |
" modecount = len(modes_udp)\n", | |
"\n", | |
" # A second set of modes is sorted by modal brightness. UDP notation denotes\n", | |
" # the number of generating intervals going up or down that produces that mode.\n", | |
" # Diatonic ionian, for example, is produced by going up 5 generators (C to G,\n", | |
" # G to D, D to A, A to E, and E to B) and going down 1 generator (C to F), and\n", | |
" # its UDP is therefore 5|1. Going up 6 generators instead produces a scale\n", | |
" # that has F# instead of F natural; this is the lydian mode and its UDP\n", | |
" # is 6|0.\n", | |
" # Multi-period scales are denoted as x|y(k), and UDP becomes tricker to notate\n", | |
" # with arbitrary alterations, since these alterations necessarily require\n", | |
" # a distributionally even scale to refer off of. This function currently only\n", | |
" # knows how to deal with scales that are already distributionally even (so every\n", | |
" # moment-of-symmetry scale in existence and their SVn analogues) and that is\n", | |
" # contingent on sorting scalestrings in lexicographic order.\n", | |
" # Lexicographic ordering of scalestrings makes more sense on scalestrings whose\n", | |
" # steps are distributionally even, such as mosses. Deviations from that, such\n", | |
" # as with a modmos, require its distributionally even form for reference, so \n", | |
" # sorting by UDP may not make much sense in this case.\n", | |
" modes_udp.sort()\n", | |
"\n", | |
" # Modal brightness necessarily requires a scale's rotations be sorted in\n", | |
" # lexicographic ordering. The order in which they appear by rotation is a\n", | |
" # different ordering altogether. For the sake of standardization, rotational\n", | |
" # ordering starts with the first mode in the modal brightness ordering.\n", | |
" for i in range(notecount):\n", | |
" scale_to_add = modes_udp[0][i:] + modes_udp[0][:i]\n", | |
"\n", | |
" if (modes_cpo.count(scale_to_add) == 0):\n", | |
" modes_cpo.append(scale_to_add)\n", | |
"\n", | |
" # Make a pair of tables that shows the interval matrix for the scale,\n", | |
" # where one table depicts modes sorted by CPO and the other by modal brightness.\n", | |
" interval_table_cpo = GenerateIntervalMatrix(modes_cpo)\n", | |
" interval_table_udp = GenerateIntervalMatrix(modes_udp)\n", | |
"\n", | |
" # Generate row headers; this should be the modetsring and their enumeration based\n", | |
" # on the type of ordering.\n", | |
" notecount = len(scalestring)\n", | |
" modecount = len(interval_table_cpo)\n", | |
" for i in range(modecount):\n", | |
" interval_table_cpo[i].insert(0, str(i))\n", | |
" interval_table_cpo[i].insert(0, modes_cpo[i])\n", | |
"\n", | |
" interval_table_udp[i].insert(0, str(i))\n", | |
" interval_table_udp[i].insert(0, modes_udp[i])\n", | |
"\n", | |
" table_header = []\n", | |
" table_header.append(\"Modestring\")\n", | |
" table_header.append(\"Order\")\n", | |
" for i in range(notecount + 1):\n", | |
" table_header.append(str(i) + \"-step\")\n", | |
"\n", | |
" interval_table_cpo.insert(0, table_header)\n", | |
"\n", | |
" interval_table_udp.insert(0, table_header)\n", | |
"\n", | |
" # Generate tables for scale degrees\n", | |
" degree_table_cpo = GenerateDegreeMatrix(modes_cpo)\n", | |
" degree_table_udp = GenerateDegreeMatrix(modes_udp)\n", | |
"\n", | |
" for i in range(modecount):\n", | |
" degree_table_cpo[i].insert(0, str(i))\n", | |
" degree_table_cpo[i].insert(0, modes_cpo[i])\n", | |
"\n", | |
" degree_table_udp[i].insert(0, str(i))\n", | |
" degree_table_udp[i].insert(0, modes_udp[i])\n", | |
"\n", | |
" table_header = []\n", | |
" table_header.append(\"Modestring\")\n", | |
" table_header.append(\"Order\")\n", | |
" for i in range(notecount + 1):\n", | |
" table_header.append(str(i) + \"-deg\")\n", | |
"\n", | |
" degree_table_cpo.insert(0, table_header)\n", | |
" degree_table_udp.insert(0, table_header)\n", | |
"\n", | |
" print(\"\\nScale modes of \" + scalestring + \" sorted by rotational order:\")\n", | |
" print(tabulate(interval_table_cpo))\n", | |
" print(\"Scale modes of \" + scalestring + \" sorted by brigthness:\")\n", | |
" print(tabulate(interval_table_udp))\n", | |
"\n", | |
" print(\"\\nScale degrees of \" + scalestring + \" for each mode (modes sorted by rotation):\")\n", | |
" print(tabulate(degree_table_cpo))\n", | |
" print(\"\\nScale degrees of \" + scalestring + \" for each mode (modes sorted by brightness):\")\n", | |
" print(tabulate(degree_table_udp))\n", | |
"\n", | |
" # for row in interval_table_udp:\n", | |
" # table_row = \"\"\n", | |
" # for entry in row:\n", | |
" # table_row += entry + \"\\t\"\n", | |
" # print(table_row)\n", | |
"\n", | |
" return None\n", | |
"\n", | |
"\n", | |
"# Create an interval matrix out of the provided list of modes\n", | |
"# Table produced this way lacks headers\n", | |
"def GenerateIntervalMatrix(modes):\n", | |
"\n", | |
" interval_matrix = []\n", | |
"\n", | |
" for mode in modes:\n", | |
" row = []\n", | |
" notecount = len(mode) + 1\n", | |
" for i in range(notecount):\n", | |
" row.append(IntervalToStepsum(mode[0:i]))\n", | |
" interval_matrix.append(row)\n", | |
"\n", | |
" return interval_matrix\n", | |
"\n", | |
"def GenerateDegreeMatrix(modes):\n", | |
"\n", | |
" interval_matrix = []\n", | |
" for mode in modes:\n", | |
" row = []\n", | |
" notecount = len(mode) + 1\n", | |
" for i in range(notecount):\n", | |
" row.append(\"\".join(sorted(mode[0:i])))\n", | |
" interval_matrix.append(row)\n", | |
"\n", | |
" degree_matrix = interval_matrix.copy()\n", | |
" unique_intervals = GenerateUniqueIntervalListings(modes)\n", | |
"\n", | |
" modecount = len(modes)\n", | |
" notecount = len(modes[0])\n", | |
"\n", | |
" for i in range(modecount):\n", | |
" for j in range(notecount + 1):\n", | |
" interval_count = len(unique_intervals[j])\n", | |
" interval = interval_matrix[i][j]\n", | |
" degree = str(interval_count - 1 - unique_intervals[j].index(interval))\n", | |
" degree_matrix[i][j] = degree\n", | |
"\n", | |
" return degree_matrix\n", | |
"\n", | |
"# Generate an interval listing matrix, where each column corresponds to a mode\n", | |
"# and each row corresponds to the intervals of a specific size from every mode\n", | |
"# This is basically the transpose of an interval matrix\n", | |
"# def GenerateIntervalListings(modes):\n", | |
"\n", | |
"# interval_listing_matrix = []\n", | |
"\n", | |
"# notecount = len(modes[0])\n", | |
"# modecount = len(modes)\n", | |
"# for i in range(notecount + 1):\n", | |
"# row = []\n", | |
"# for j in range(modecount):\n", | |
"# intervalstring = modes[j][0:i]\n", | |
"# if intervalstring == \"\":\n", | |
"# intervalstring = \"empty\"\n", | |
"# else:\n", | |
"# intervalstring = \"\".join(sorted(intervalstring))\n", | |
"# row.append(intervalstring)\n", | |
"# interval_listing_matrix.append(row)\n", | |
" \n", | |
"# return interval_listing_matrix\n", | |
"\n", | |
"def GenerateUniqueIntervalListings(modes):\n", | |
"\n", | |
" interval_listings = []\n", | |
"\n", | |
" notecount = len(modes[0])\n", | |
" modecount = len(modes)\n", | |
" for i in range(notecount + 1):\n", | |
" row = []\n", | |
" for j in range(modecount):\n", | |
" intervalstring = modes[j][0:i]\n", | |
" intervalstring = \"\".join(sorted(intervalstring))\n", | |
" row.append(intervalstring)\n", | |
" interval_listings.append(row)\n", | |
"\n", | |
" unique_intervals = []\n", | |
"\n", | |
" for intervals in interval_listings:\n", | |
" row = []\n", | |
" for intervalstring in intervals:\n", | |
" if row.count(intervalstring) == 0:\n", | |
" row.append(intervalstring)\n", | |
" row.sort()\n", | |
" unique_intervals.append(row)\n", | |
"\n", | |
" return unique_intervals\n", | |
"\n", | |
"# def IntervalToSortedInterval(intervalstring):\n", | |
"\n", | |
"# return \"\".join(sorted(intervalstring))\n", | |
"\n", | |
"def IntervalToStepsum(intervalstring):\n", | |
"\n", | |
" intervalsum = \"\"\n", | |
"\n", | |
" if intervalstring == \"\":\n", | |
" intervalsum = \"0\"\n", | |
" else:\n", | |
"\n", | |
" stepset = set()\n", | |
"\n", | |
" for step in intervalstring:\n", | |
" stepset.add(step)\n", | |
"\n", | |
" sorted_stepset = list(stepset)\n", | |
" sorted_stepset.sort()\n", | |
"\n", | |
" for step in sorted_stepset:\n", | |
" stepcount = intervalstring.count(step)\n", | |
" if stepcount == 1:\n", | |
" intervalsum += step + \"+\"\n", | |
" else:\n", | |
" intervalsum += str(stepcount) + step + \"+\"\n", | |
"\n", | |
" intervalsum = intervalsum[:-1]\n", | |
"\n", | |
" return intervalsum" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 18, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"def GetTamnamsPrefix(lg_count, sm_count):\n", | |
"\n", | |
" step_count = lg_count + sm_count\n", | |
" x_index = step_count - 2\n", | |
" y_index = lg_count - 1\n", | |
"\n", | |
" names = [\n", | |
" [\"triv\"],\n", | |
" [\"atri\", \"tri\"],\n", | |
" [\"atetra\", \"biwd\", \"tetra\"],\n", | |
" [\"ped\", \"pent\", \"apent\", \"manu\"],\n", | |
" [\"amech\", \"mal\", \"triwood\", \"citry\", \"mech\"],\n", | |
" [\"on\", \"pel\", \"mosh\", \"smi\", \"\", \"arch\"],\n", | |
" [\"apine\", \"subar\", \"checker\", \"tetwud\", \"oneiro\", \"ek\", \"pine\"],\n", | |
" [\"ablu\", \"bal\", \"cher\", \"gram\", \"cthon\", \"hyru\", \"arm\", \"blu\"],\n", | |
" [\"asina\", \"jara\", \"seph\", \"lime\", \"penwud\", \"lem\", \"zal\", \"tara\", \"sina\"]\n", | |
" ]\n", | |
"\n", | |
" if (step_count <= 10) :\n", | |
" return names[x_index][y_index]\n", | |
" else:\n", | |
" return \"\"" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 19, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"# This is a special version of Modecalc specific for mosses: MosModecalc\n", | |
"# Function accepts an additional parameter for formatting (table_fmt):\n", | |
"# - If this is \"mediawiki\", then the output is formatted such that it can be copypasted into a MediaWiki editor to form a table.\n", | |
"# - If left unspecified or blank, then the output is the default formatting for tabulate.\n", | |
"# Notes:\n", | |
"# Because a mos has all the fun properties expected out of the standard diatonic scale (but generalized),\n", | |
"# it makes sense to produce a table where modes are sorted by UDP primarily, which is what MosModecalc does.\n", | |
"# For a general scale, this may not always hold up, hence Modecalc on its own will produce four tables\n", | |
"# where two are sorted by \"brightness\" (not necessarily UDP) and two more by rotational order.\n", | |
"def MosModecalc(x, y, table_fmt=\"\", mosprefix=\"mos\", modenames=[]):\n", | |
"\n", | |
" # Find the scalestring that represents the mos in its brightest mode, and its generators\n", | |
" brightest_mode = CalculateMos(x, y)\n", | |
" cpg, cng = CalculateMosGenerators(x, y)\n", | |
"\n", | |
" # Find the size of the scale and its generator sizes in number of steps\n", | |
" notecount = x + y\n", | |
" cpg_size = len(cpg)\n", | |
" cng_size = len(cng)\n", | |
"\n", | |
" # Use that info to find the number of periods the scale has\n", | |
" # Note that period size is cpg_size + cng_size. This is equivalent to notecount only for single\n", | |
" # period mosses, hence this extra calculation.\n", | |
" period_size = cpg_size + cng_size\n", | |
" number_of_periods = notecount // period_size\n", | |
"\n", | |
" # Find the modes of the mos by producing all of the unique \"shifts\" of the brightest scalestring.\n", | |
" # Storing these shifts in an array and sorting them sorts all of the modes by modal brightness,\n", | |
" # given that the characters that represent the large and small step (L and s typically), when sorted\n", | |
" # in lexicographic order, also represents the order in which those step sizes appear in decreasing\n", | |
" # order of size. (In short, if L comes before s asciibetically, then L should come before s because\n", | |
" # L represents a value that's larger than s.)\n", | |
" # Leaving the modes produced this way unsorted sorts the modes by rotation instead, or rather, the\n", | |
" # order in which modes appear if each successive scale degree of the brightest mode were used as\n", | |
" # the root. If sorted by brightness, the UDP of each mode will necessarily be u|d. Finding a mode's\n", | |
" # rotational order is then calculating d * cpg_size % notecount.\n", | |
" modes = []\n", | |
" for i in range(notecount):\n", | |
" scale_to_add = brightest_mode[i:] + brightest_mode[:i]\n", | |
"\n", | |
" if (modes.count(scale_to_add) == 0):\n", | |
" modes.append(scale_to_add)\n", | |
" modes.sort()\n", | |
" modecount = len(modes)\n", | |
"\n", | |
" # Create the interval matrix\n", | |
" interval_matrix = GenerateIntervalMatrix(modes)\n", | |
"\n", | |
" # Create the interval table header\n", | |
" interval_table_header = []\n", | |
" interval_table_header.append(\"Mode\")\n", | |
" interval_table_header.append(\"UDP\")\n", | |
" interval_table_header.append(\"Mode name\")\n", | |
" interval_table_header.append(\"Rotational order\")\n", | |
" for i in range(notecount + 1):\n", | |
" if i == 0:\n", | |
" interval_table_header.append(mosprefix + \"unison\")\n", | |
" elif i == notecount:\n", | |
" interval_table_header.append(mosprefix + \"octave\")\n", | |
" else:\n", | |
" interval_table_header.append(str(i) + \"-\" + mosprefix + \"step\")\n", | |
"\n", | |
" # Create the rest of the interval table\n", | |
" interval_table = []\n", | |
" for i in range(modecount):\n", | |
" # Create a new row\n", | |
" interval_table.append([])\n", | |
" \n", | |
" # Add the mos step pattern\n", | |
" interval_table[i].append(modes[i])\n", | |
"\n", | |
" # Add the UDP, formatted based on whethr it's mediawiki or not\n", | |
" if number_of_periods == 1:\n", | |
" if (table_fmt == \"mediawiki\"):\n", | |
" interval_table[i].append(str(modecount-1-i) + \"<nowiki>|</nowiki>\" + str(i))\n", | |
" else:\n", | |
" interval_table[i].append(str(modecount-1-i) + \"|\" + str(i))\n", | |
" else:\n", | |
" if (table_fmt == \"mediawiki\"):\n", | |
" interval_table[i].append(str((modecount-1-i)*number_of_periods) + \"<nowiki>|</nowiki>\" + str(i * number_of_periods) + \"(\" + str(number_of_periods) + \")\")\n", | |
" else:\n", | |
" interval_table[i].append(str((modecount-1-i)*number_of_periods) + \"|\" + str(i * number_of_periods) + \"(\" + str(number_of_periods) + \")\")\n", | |
"\n", | |
" # Add the mode names, if provided\n", | |
" # The order of mode names should be in decreasing modal brightness\n", | |
" # If the number of mode names doesn't match the number of modes, don't add\n", | |
" # and default to naming them as \"Mode <number>\"\n", | |
" if len(modenames) == modecount:\n", | |
" interval_table[i].append(modenames[i])\n", | |
" else:\n", | |
" modename = \"Mode \" + str(i+1)\n", | |
" interval_table[i].append(modename)\n", | |
" \n", | |
" # Add the \"rotational order\"\n", | |
" interval_table[i].append(str((i * cpg_size) % period_size))\n", | |
"\n", | |
" # Add the mossteps\n", | |
" for j in range(len(interval_matrix[i])):\n", | |
" interval_table[i].append(interval_matrix[i][j])\n", | |
" \n", | |
" # Append table header to front of the actual table\n", | |
" interval_table = [interval_table_header] + interval_table\n", | |
"\n", | |
"\n", | |
" # Create the degree matrix\n", | |
" degree_matrix = GenerateDegreeMatrix(modes)\n", | |
"\n", | |
" # Go through the degree matrix and convert the numeric values to major/minor/perfect/augmented/diminished accordingly\n", | |
" # - The unison and mosoctave are perfect.\n", | |
" # - The smaller interval size is minor, where the larger is major.\n", | |
" # - For the bright generator, the larger is perfect and the smaller is diminished. Diminished should appear only once.\n", | |
" # - For the dark generator, the larger is augmented and the smaller is perfect. Augmented should appear only once.\n", | |
" # - For multi-period mosses, mosdegrees that appear at each period are perfect, on top of the unison and mosoctave.\n", | |
" # Additionally, the generators lose their designation of augmented/perfect/diminished and instead are classed as major/minor.\n", | |
" # A few notes:\n", | |
" # - The unison is reached by going up zero notes; the mosoctave is reached by going up however many steps there are in the mos.\n", | |
" # - The generators are reached by going up however many steps there are in each generator. Since the CPG and CNG are\n", | |
" # distinct values that, when added, produce the mosoctave (or period for multi-period mosses), the sizes of these\n", | |
" # generators (in mossteps) are enough to determine where they are in the table.\n", | |
" degree_matrix_to_text = degree_matrix.copy()\n", | |
" for j in range(notecount + 1):\n", | |
" for i in range(modecount):\n", | |
" if j % period_size == 0:\n", | |
" degree_matrix_to_text[i][j] = \"perfect\"\n", | |
" elif j == cpg_size and (number_of_periods == 1):\n", | |
" if degree_matrix[i][j] == \"0\":\n", | |
" degree_matrix_to_text[i][j] = \"diminished\"\n", | |
" elif degree_matrix[i][j] == \"1\":\n", | |
" degree_matrix_to_text[i][j] = \"perfect\"\n", | |
" elif j == cng_size and (number_of_periods == 1):\n", | |
" if degree_matrix[i][j] == \"0\":\n", | |
" degree_matrix_to_text[i][j] = \"perfect\"\n", | |
" elif degree_matrix[i][j] == \"1\":\n", | |
" degree_matrix_to_text[i][j] = \"augmented\"\n", | |
" else:\n", | |
" if degree_matrix[i][j] == \"0\":\n", | |
" degree_matrix_to_text[i][j] = \"minor\"\n", | |
" elif degree_matrix[i][j] == \"1\":\n", | |
" degree_matrix_to_text[i][j] = \"major\"\n", | |
"\n", | |
" # Create the degree table header\n", | |
" degree_table_header = []\n", | |
" degree_table_header.append(\"Mode\")\n", | |
" degree_table_header.append(\"UDP\")\n", | |
" degree_table_header.append(\"Mode name\")\n", | |
" degree_table_header.append(\"Rotational order\")\n", | |
" for i in range(notecount + 1):\n", | |
" degree_table_header.append(str(i) + \"-\" + mosprefix + \"degree\")\n", | |
"\n", | |
" # Create the rest of the degree table\n", | |
" degree_table = []\n", | |
" for i in range(modecount):\n", | |
" # Add a new row to the table\n", | |
" degree_table.append([])\n", | |
"\n", | |
" # Add the mos step pattern\n", | |
" degree_table[i].append(modes[i])\n", | |
"\n", | |
" # Add the UDP\n", | |
" # Format it depending on whether it's a mediawiki table or not\n", | |
" if number_of_periods == 1:\n", | |
" if (table_fmt == \"mediawiki\"):\n", | |
" degree_table[i].append(str(modecount-1-i) + \"<nowiki>|</nowiki>\" + str(i))\n", | |
" else:\n", | |
" degree_table[i].append(str(modecount-1-i) + \"|\" + str(i))\n", | |
" else:\n", | |
" if (table_fmt == \"mediawiki\"):\n", | |
" degree_table[i].append(str((modecount-1-i)*number_of_periods) + \"<nowiki>|</nowiki>\" + str(i * number_of_periods) + \"(\" + str(number_of_periods) + \")\")\n", | |
" else:\n", | |
" degree_table[i].append(str((modecount-1-i)*number_of_periods) + \"|\" + str(i * number_of_periods) + \"(\" + str(number_of_periods) + \")\")\n", | |
"\n", | |
" # Add the mode names, if provided\n", | |
" # The order of mode names should be in decreasing modal brightness\n", | |
" # If the number of mode names doesn't match the number of modes, don't add\n", | |
" # and default to naming them as \"Mode <number>\"\n", | |
" if len(modenames) == modecount:\n", | |
" degree_table[i].append(modenames[i])\n", | |
" else:\n", | |
" modename = \"Mode \" + str(i+1)\n", | |
" degree_table[i].append(modename)\n", | |
"\n", | |
" # Add the \"rotational order\" for the mode\n", | |
" degree_table[i].append(str((i * cpg_size) % period_size))\n", | |
"\n", | |
" # Add the mosdegrees\n", | |
" for j in range(len(degree_matrix_to_text[i])):\n", | |
" degree_table[i].append(degree_matrix_to_text[i][j])\n", | |
"\n", | |
" degree_table = [degree_table_header] + degree_table\n", | |
"\n", | |
" # Print the table\n", | |
" print(tabulate(interval_table, headers=\"firstrow\", tablefmt=table_fmt))\n", | |
" print(tabulate(degree_table, headers=\"firstrow\", tablefmt=table_fmt))\n", | |
"\n", | |
"\n", | |
" return None" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 20, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"# This is a special version of Modecalc specific for mosses: MosModecalc\n", | |
"# Function accepts an additional parameter for formatting (table_fmt):\n", | |
"# - If this is \"mediawiki\", then the output is formatted such that it can be copypasted into a MediaWiki editor to form a table.\n", | |
"# - If left unspecified or blank, then the output is the default formatting for tabulate.\n", | |
"# Notes:\n", | |
"# Because a mos has all the fun properties expected out of the standard diatonic scale (but generalized),\n", | |
"# it makes sense to produce a table where modes are sorted by UDP primarily, which is what MosModecalc does.\n", | |
"# For a general scale, this may not always hold up, hence Modecalc on its own will produce four tables\n", | |
"# where two are sorted by \"brightness\" (not necessarily UDP) and two more by rotational order.\n", | |
"def MosModecalcOnetable(x, y, table_fmt=\"\", mosprefix=\"mos\", modenames=[]):\n", | |
"\n", | |
" # Find the scalestring that represents the mos in its brightest mode, and its generators\n", | |
" brightest_mode = CalculateMos(x, y)\n", | |
" cpg, cng = CalculateMosGenerators(x, y)\n", | |
"\n", | |
" # Find the size of the scale and its generator sizes in number of steps\n", | |
" notecount = x + y\n", | |
" cpg_size = len(cpg)\n", | |
" cng_size = len(cng)\n", | |
"\n", | |
" # Use that info to find the number of periods the scale has\n", | |
" # Note that period size is cpg_size + cng_size. This is equivalent to notecount only for single\n", | |
" # period mosses, hence this extra calculation.\n", | |
" period_size = cpg_size + cng_size\n", | |
" number_of_periods = notecount // period_size\n", | |
"\n", | |
" # Find the modes of the mos by producing all of the unique \"shifts\" of the brightest scalestring.\n", | |
" # Storing these shifts in an array and sorting them sorts all of the modes by modal brightness,\n", | |
" # given that the characters that represent the large and small step (L and s typically), when sorted\n", | |
" # in lexicographic order, also represents the order in which those step sizes appear in decreasing\n", | |
" # order of size. (In short, if L comes before s asciibetically, then L should come before s because\n", | |
" # L represents a value that's larger than s.)\n", | |
" # Leaving the modes produced this way unsorted sorts the modes by rotation instead, or rather, the\n", | |
" # order in which modes appear if each successive scale degree of the brightest mode were used as\n", | |
" # the root. If sorted by brightness, the UDP of each mode will necessarily be u|d. Finding a mode's\n", | |
" # rotational order is then calculating d * cpg_size % notecount.\n", | |
" modes = []\n", | |
" for i in range(notecount):\n", | |
" scale_to_add = brightest_mode[i:] + brightest_mode[:i]\n", | |
"\n", | |
" if (modes.count(scale_to_add) == 0):\n", | |
" modes.append(scale_to_add)\n", | |
" modes.sort()\n", | |
" modecount = len(modes)\n", | |
"\n", | |
" # Create the interval matrix\n", | |
" interval_matrix = GenerateIntervalMatrix(modes)\n", | |
"\n", | |
" # Create the degree matrix\n", | |
" degree_matrix = GenerateDegreeMatrix(modes)\n", | |
"\n", | |
" # Go through the degree matrix and convert the numeric values to major/minor/perfect/augmented/diminished accordingly\n", | |
" # - The unison and mosoctave are perfect.\n", | |
" # - The smaller interval size is minor, where the larger is major.\n", | |
" # - For the bright generator, the larger is perfect and the smaller is diminished. Diminished should appear only once.\n", | |
" # - For the dark generator, the larger is augmented and the smaller is perfect. Augmented should appear only once.\n", | |
" # - For multi-period mosses, mosdegrees that appear at each period are perfect, on top of the unison and mosoctave.\n", | |
" # Additionally, the generators lose their designation of augmented/perfect/diminished and instead are classed as major/minor.\n", | |
" # A few notes:\n", | |
" # - The unison is reached by going up zero notes; the mosoctave is reached by going up however many steps there are in the mos.\n", | |
" # - The generators are reached by going up however many steps there are in each generator. Since the CPG and CNG are\n", | |
" # distinct values that, when added, produce the mosoctave (or period for multi-period mosses), the sizes of these\n", | |
" # generators (in mossteps) are enough to determine where they are in the table.\n", | |
" degree_matrix_to_text = degree_matrix.copy()\n", | |
" for j in range(notecount + 1):\n", | |
" for i in range(modecount):\n", | |
" if j % period_size == 0:\n", | |
" degree_matrix_to_text[i][j] = \"perfect\"\n", | |
" elif j == cpg_size and (number_of_periods == 1):\n", | |
" if degree_matrix[i][j] == \"0\":\n", | |
" degree_matrix_to_text[i][j] = \"diminished\"\n", | |
" elif degree_matrix[i][j] == \"1\":\n", | |
" degree_matrix_to_text[i][j] = \"perfect\"\n", | |
" elif j == cng_size and (number_of_periods == 1):\n", | |
" if degree_matrix[i][j] == \"0\":\n", | |
" degree_matrix_to_text[i][j] = \"perfect\"\n", | |
" elif degree_matrix[i][j] == \"1\":\n", | |
" degree_matrix_to_text[i][j] = \"augmented\"\n", | |
" else:\n", | |
" if degree_matrix[i][j] == \"0\":\n", | |
" degree_matrix_to_text[i][j] = \"minor\"\n", | |
" elif degree_matrix[i][j] == \"1\":\n", | |
" degree_matrix_to_text[i][j] = \"major\"\n", | |
"\n", | |
" # Create the interval table header\n", | |
" interval_table_header = []\n", | |
" interval_table_header.append(\"Mode\")\n", | |
" interval_table_header.append(\"UDP\")\n", | |
" interval_table_header.append(\"Mode name\")\n", | |
" interval_table_header.append(\"Rotational order\")\n", | |
" for i in range(notecount + 1):\n", | |
" if i == 0:\n", | |
" interval_table_header.append(mosprefix + \"unison (\" + str(i) + \"-\" + mosprefix + \"degree)\")\n", | |
" elif i == notecount:\n", | |
" interval_table_header.append(mosprefix + \"octave (\" + str(i) + \"-\" +mosprefix + \"degree)\")\n", | |
" else:\n", | |
" interval_table_header.append(str(i) + \"-\" + mosprefix + \"step (\" +str(i) + \"-\" +mosprefix + \"degree)\")\n", | |
"\n", | |
" # Create the rest of the interval table\n", | |
" interval_table = []\n", | |
" for i in range(modecount):\n", | |
" # Create a new row\n", | |
" interval_table.append([])\n", | |
" \n", | |
" # Add the mos step pattern\n", | |
" interval_table[i].append(modes[i])\n", | |
"\n", | |
" # Add the UDP, formatted based on whethr it's mediawiki or not\n", | |
" if number_of_periods == 1:\n", | |
" if (table_fmt == \"mediawiki\"):\n", | |
" interval_table[i].append(str(modecount-1-i) + \"<nowiki>|</nowiki>\" + str(i))\n", | |
" else:\n", | |
" interval_table[i].append(str(modecount-1-i) + \"|\" + str(i))\n", | |
" else:\n", | |
" if (table_fmt == \"mediawiki\"):\n", | |
" interval_table[i].append(str((modecount-1-i)*number_of_periods) + \"<nowiki>|</nowiki>\" + str(i * number_of_periods) + \"(\" + str(number_of_periods) + \")\")\n", | |
" else:\n", | |
" interval_table[i].append(str((modecount-1-i)*number_of_periods) + \"|\" + str(i * number_of_periods) + \"(\" + str(number_of_periods) + \")\")\n", | |
"\n", | |
" # Add the mode names, if provided\n", | |
" # The order of mode names should be in decreasing modal brightness\n", | |
" # If the number of mode names doesn't match the number of modes, don't add\n", | |
" # and default to naming them as \"Mode <number>\"\n", | |
" if len(modenames) == modecount:\n", | |
" interval_table[i].append(modenames[i])\n", | |
" else:\n", | |
" modename = \"Mode \" + str(i+1)\n", | |
" interval_table[i].append(modename)\n", | |
" \n", | |
" # Add the \"rotational order\"\n", | |
" interval_table[i].append(str((i * cpg_size) % period_size))\n", | |
"\n", | |
" # Add the mossteps\n", | |
" for j in range(len(interval_matrix[i])):\n", | |
" interval_table[i].append(interval_matrix[i][j] + \" (\" + degree_matrix[i][j] + \")\")\n", | |
" \n", | |
" # Append table header to front of the actual table\n", | |
" interval_table = [interval_table_header] + interval_table\n", | |
"\n", | |
"\n", | |
"\n", | |
" # Print the table\n", | |
" print(tabulate(interval_table, headers=\"firstrow\", tablefmt=table_fmt))\n" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 21, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"Mode UDP Mode name Rotational order smiunison (0-smidegree) 1-smistep (1-smidegree) 2-smistep (2-smidegree) 3-smistep (3-smidegree) 4-smistep (4-smidegree) 5-smistep (5-smidegree) 6-smistep (6-smidegree) smioctave (7-smidegree)\n", | |
"------- ----- ----------- ------------------ ------------------------- ------------------------- ------------------------- ------------------------- ------------------------- ------------------------- ------------------------- -------------------------\n", | |
"LLsLsLs 6|0 Mode 1 0 0(0) L(1) 2L(1) 2L+s(1) 3L+s(1) 3L+2s(1) 4L+2s(1) 4L+3s(0)\n", | |
"LsLLsLs 5|1 Mode 2 5 0(0) L(1) L+s(0) 2L+s(1) 3L+s(1) 3L+2s(1) 4L+2s(1) 4L+3s(0)\n", | |
"LsLsLLs 4|2 Mode 3 3 0(0) L(1) L+s(0) 2L+s(1) 2L+2s(0) 3L+2s(1) 4L+2s(1) 4L+3s(0)\n", | |
"LsLsLsL 3|3 Mode 4 1 0(0) L(1) L+s(0) 2L+s(1) 2L+2s(0) 3L+2s(1) 3L+3s(0) 4L+3s(0)\n", | |
"sLLsLsL 2|4 Mode 5 6 0(0) s(0) L+s(0) 2L+s(1) 2L+2s(0) 3L+2s(1) 3L+3s(0) 4L+3s(0)\n", | |
"sLsLLsL 1|5 Mode 6 4 0(0) s(0) L+s(0) L+2s(0) 2L+2s(0) 3L+2s(1) 3L+3s(0) 4L+3s(0)\n", | |
"sLsLsLL 0|6 Mode 7 2 0(0) s(0) L+s(0) L+2s(0) 2L+2s(0) 2L+3s(0) 3L+3s(0) 4L+3s(0)\n" | |
] | |
} | |
], | |
"source": [ | |
"lg_count = 4\n", | |
"sm_count = 3\n", | |
"MosModecalcOnetable(lg_count, sm_count, mosprefix=GetTamnamsPrefix(lg_count, sm_count))\n" | |
] | |
} | |
], | |
"metadata": { | |
"kernelspec": { | |
"display_name": "Python 3.9.12 ('base')", | |
"language": "python", | |
"name": "python3" | |
}, | |
"language_info": { | |
"codemirror_mode": { | |
"name": "ipython", | |
"version": 3 | |
}, | |
"file_extension": ".py", | |
"mimetype": "text/x-python", | |
"name": "python", | |
"nbconvert_exporter": "python", | |
"pygments_lexer": "ipython3", | |
"version": "3.9.12" | |
}, | |
"orig_nbformat": 4, | |
"vscode": { | |
"interpreter": { | |
"hash": "eca05a44a48bc7782e9f30921d98d7664616a388f5fc77845300f50c37270011" | |
} | |
} | |
}, | |
"nbformat": 4, | |
"nbformat_minor": 2 | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment