Published
Edited
Feb 25, 2022
Importers
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
trans = transposeSet(pcs, pcs.length, transpose.indexOf(transposition))
Insert cell
inver = invertSet(pcs, pcs.length, inverse.indexOf(inversion))
Insert cell
Insert cell
Insert cell
matrixOptions = ["T", "T y", "I", "I y"]
Insert cell
subsetOptions = ["cardinality", "Trichords", "Tetrachords", "Pentachords", "Hexachords", "Septachords", "Octachords", "Nonachords"]
Insert cell
supersetOptions = ["cardinality", "Trichords", "Tetrachords", "Pentachords", "Hexachords", "Septachords", "Octachords", "Nonachords"]
Insert cell
function format(vect) {
return `[${vect.join(", ")}]`;
}
Insert cell
// Returns the normal form, by returning an array.
// "pcs" is an integer array of pcs
// "size" is the cardinality of "pcs"
// https://www.mta.ca/pc-set/pc-set_new/pages/page04/page04.html

function getNorm(pcs, size) {
pcs.sort(compare); //Sorts from lowest to highest
var norm_can = new Array();
norm_can[0] = new Array();
norm_can[0] = pcs;
for (let i = 1; i < size; i++) {
//This will produce all the normal form choices
norm_can[i] = new Array();
for (let j = 0; j < size; j++) {
if (j == size - 1) norm_can[i][j] = norm_can[i - 1][0];
else norm_can[i][j] = norm_can[i - 1][j + 1];
}
}
//Lets pick the best choice.
var winner = norm_can[0];
for (let i = 1; i < size; i++) {
winner = compareNormCan(winner, norm_can[i], size);
}

return winner;
}
Insert cell
//for sorting, see getNorm

function compare(a, b) {
return a - b;
}
Insert cell
// set1 and set2 are arrays. Returns the best normal form candidate
// (see above function for how it is used).

function compareNormCan(set1, set2, size) {
let is_diff = false;
let i = size - 1;
while (is_diff == false) {
let diff1 = mod_12_sub(set1[i], set1[0]);
let diff2 = mod_12_sub(set2[i], set2[0]);

if (diff1 == diff2) {
if (i == 1) {
if (set1[0] < set2[0]) return set1;
else return set2;
} else is_diff = false;
}

if (diff1 < diff2) return set1;
if (diff1 > diff2) return set2;
i--;
}
}
Insert cell
// pc_a and pc_b are integers. Returns the difference between
// two modulo 12 numbers. (pc_a - pc_b)

function mod_12_sub(pc_a, pc_b) {
if (pc_a < pc_b) return 12 + pc_a - pc_b;
else return pc_a - pc_b;
}
Insert cell
// Returns the prime form. norm is the normal form of the set.
// size is its cardinality.
// https://www.mta.ca/pc-set/pc-set_new/pages/page05/page05.html

function getPrime(norm, size) {
if (size > 1) {
if (size == 2) {
var prime = new Array();

prime[0] = 0;

let temp = mod_12_sub(norm[1], norm[0]);

if (temp > 6) prime[1] = 12 - temp;
else prime[1] = temp;

return prime;
}

size = size - 1;

/* This is the size of the interval array. ex. For the set
[10,11,2,6] size would be three because the intervals are 1,3,4. 1,3,4
would be the value of "intervals"*/

let inverse = normInverse(norm, size + 1);

// Sets inverse equal to the normal form of the inverse of "norm".

let choice = compareNormCan(norm, inverse, size);

/* This works because you use the same process to compare normal form
canditates as when you compare the normal form of your set to the inversion
of your set (which is also put in normal form). The "winner" is the set
packed from the right. The
"winner" has the intervals between each number converted into the array
called intervals. */

let intervals = new Array(size);

for (var i = 0; i < size; i++) {
intervals[i] = mod_12_sub(choice[i + 1], choice[i]);
}

return getPrime1(intervals, size);

// Builds a set starting on 0 using "intervals"
} else {
return norm;
}
}
Insert cell
// Returns the normal form of the inverse of "norm". "size" is the cardinality
// of the set. Used only inside getPrime().
function normInverse(norm, size) {
let inverse = new Array(size);

for (let i = 0; i < size; i++) inverse[i] = 12 - norm[i];

if (size === 1 && norm[0] === 0) {
inverse[0] = 0;
}
return getNorm(inverse, size);
}
Insert cell
// Returns the prime form. intervals is the interval array mentioned above.
// This function is only used inside getPrime(). size is the size of the
// interval array (ie. (cardinality of the set) - 1)
function getPrime1(intervals, size) {
let prime = new Array(size + 1);

prime[0] = 0;

for (let i = 1; i <= size; i++) prime[i] = prime[i - 1] + intervals[i - 1];

return prime;
}
Insert cell
// Returns the Forte name of the primeForm given.

function getForte(primeForm) {
if ( primeForm.length > 2 && primeForm.length < 10 ){
let primeFormString = primeForm.join(",");
for (let i = 0; i < chords[primeForm.length].length; i++) {
if (primeFormString === chords[primeForm.length][i].primeForm){
return chords[primeForm.length][i].forteName;
}
}
}else{
return "";
}
}
Insert cell
// Transposes the "normalForm" by the given "amount". "size" is the cardinality
// https://www.mta.ca/pc-set/pc-set_new/pages/page05/page05.html

function transposeSet(normalForm, size, amount) {
if (size > 1) {
var new_set = new Array(size);

for (let i = 0; i < size; i++)
new_set[i] = mod_12_add(normalForm[i], amount);

new_set = getNorm(new_set, size);

// for sets like the whole tone scale, the set will most likely not be in
// normal form, so I had to add the above function call.

return new_set;
} else {
return normalForm;
}
}
Insert cell
// pc_a and pc_b are integers. Returns the sum of two modulo 12 numbers.
// (pc_a + pc_b)

function mod_12_add(pc_a, pc_b) {
let temp = pc_a + pc_b;

if (temp > 11) return temp - 12;
else return temp;
}
Insert cell
// Inverts the "normalForm" by the given "amount". "size" is the cardinality
// https://www.mta.ca/pc-set/pc-set_new/pages/page05/page05.html
function invertSet(normalForm, size, amount) {
var new_set = new Array(size);

new_set = transposeSet(normInverse(normalForm, size), size, amount);

new_set = getNorm(new_set, size);

/* for sets like the whole tone scale, the set will most likely not be in normal form, so I had to add the above function call. */

return new_set;
}
Insert cell
// gets the complement of the set checked off in "form" and calls
// allCheckboxestoTextandMain
// https://www.mta.ca/pc-set/pc-set_new/pages/page14/page14.html

function getComplement(vect) {
let resultado = [];
for (let i = 0; i < 12; i++) {
for (let j = 0; j < vect.length; j++) {
if (i === vect[j]) {
i++;
}
}
if (i < 12) {
resultado.push(i);
}
}

return resultado;
}
Insert cell
// Returns, as an array, the ic vector of a prime form.
// https://www.mta.ca/pc-set/pc-set_new/pages/page06/page06.html
function icVector(primeForm) {
let numIcs = [0, 0, 0, 0, 0, 0];
let vectInversion = [0, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1];
let dif;
if (primeForm.length > 1) {
for (let i = 0; i < primeForm.length; i++) {
let j = i + 1;
for (; j < primeForm.length; j++) {
dif = primeForm[j] - primeForm[i];
if (dif > 6) {
dif = vectInversion[dif];
}
numIcs[dif - 1]++;
}
}
}
return numIcs;
}
Insert cell
// returns the forte name of the Z-mate of the "primeForm" (which is an integer
// array). Returns "None" if there isn't one.

function ZMate(primeForm) {
if ( primeForm.length > 1 && primeForm.length < 10 ){
let iVector = new Array();
let chord = chords[primeForm.length];
iVector = icVector(primeForm);
var zMate_candidate; // Prime form of Z-mate candidate
var candidateVector = new Array();
for (let i = 0; i < chord.length; i++) {
zMate_candidate = convertToArray(chord[i].primeForm);
candidateVector = icVector(zMate_candidate);
if ( areArraysEqual(candidateVector, iVector) &&
!areArraysEqual(primeForm, zMate_candidate)){
return chord[i].forteName;
}
}
}
return "None";
}
Insert cell
// converts the "pf" string (from the huge two-dimensional array in the main()
// function) to an integer array.

function convertToArray(pf) {
pf = pf.split(",");

for (let i = 0; i < pf.length; i++) pf[i] = parseInt(pf[i]);

return pf;
}
Insert cell
// Compares two arrays (a and b) of equal length. Returns true is they are
// equal, otherwise it returns false.

function areArraysEqual(a, b) {
let aS = a.join(",");
let bS = b.join(",");

if (aS === bS) return true;
else return false;
}
Insert cell
// This function displays the values of n in which
// transpositional invariance occurs. The normal form for this function must be
// used and NOT the prime form.
// normalForm is an array

function getTransInvariance(normalForm) {
var count = 0;
let transN = "";
var transposed;
if ( normalForm.length > 1 ){
for (let i = 0; i < 12; i++) {
transposed = transposeSet(normalForm, normalForm.length, i);
if (areArraysEqual(transposed, normalForm)) {
if (count != 0) transN += ", ";
transN += i;
count++;
}
}
}
return transN;
}
Insert cell
// This function displays the values of n in which inversional
// invariance occurs. The normal form for this function must be
// used and NOT the prime form.
// normalForm is an array

function getInvertInvariance(normalForm) {
var count = 0;
let invertN = "";
let inverted;

if ( normalForm.length > 1 ){
for (let i = 0; i < 12; i++) {
inverted = invertSet(normalForm, normalForm.length, i);
if (areArraysEqual(inverted, normalForm)) {
if (count != 0) invertN += ", ";
invertN += i;
count++;
}
}
}
return invertN;
}
Insert cell
// Displays the number of subsets of a given cardinality, according to Forte
// name.
// "chords" must come from the two-dimensional array (ex. all the septachords)
// primeForm: the prime form as an array
// subSize = cardinality of the subset

function getSubsets(chords, primeForm, subSize) {
var subset_i = new Array();
let result="";
// subset_i is the array of indexes that are picked out from primeForm.
// For example, if subset_i = (0,1,3) and primeForm = (0,2,3,4,5), then
// subset would be (0,2,4)
if (subSize!= 0 && subSize < primeForm.length){
for (let i = 0; i < subSize; i++) subset_i[i] = i;
var subset;
var cont = true; // Couldn't use the keyword "continue" as a variable!
var forteList = new Array();
// Listing of every occurrence of a subset Forte name
let j = 0;
while (cont == true) {
subset = getPcs(subset_i, primeForm);
subset = getNorm(subset, subSize);
subset = getPrime(subset, subSize);
forteList[j] = getForte(subset);
subset_i = nextSub_Superset(subset_i, primeForm.length);
if (subset_i == "done") {
cont = false;
}
j++;
}
var tally = searchList(forteList, chords);
// tally is a parallel array of numbers that correspond to the chords array.
// For example if chords = trichords, tally[1] (we skip 0) is the number of
// occurrences of set 3-1 as a subset.
result = displaySubsetData(tally, chords);
}
return result;
}
Insert cell
// displays the number of subsets there
// are for a given set, according to Forte name.
// chords is the list of chords of one cardinality.
// tally is a parallel array of numbers that correspond to the chords array.
// For example, if chords = trichords, tally[1] (we skip 0) is the number of
// occurances of set 3-1 as a subset.

function displaySubsetData(tally, chords) {
let subResults = "";

for (let i = 1; i < tally.length; i++) {
if (tally[i] != 0)
subResults += chords[i - 1].forteName + ": " + tally[i] + ";"+ "\n";
}
return subResults;
}
Insert cell
// Returns the pitch classes extracted from "pcs" according to "set_i".
// For example for set_i = (0,1,3) and pcs = (1,3,7,9), the function will
// return (1,7,9)

function getPcs(set_i, pcs) {
var temp = new Array();
for (let i = 0; i < set_i.length; i++) temp[i] = pcs[set_i[i]];
return temp;
}
Insert cell
// Returns the next value of set_i (see above function).
// When the last one has been reached, it returns "done"

function nextSub_Superset(set_i, primeSize) {
let size = set_i.length;
if (set_i[size - 1] == primeSize - 1) {
if (set_i[0] == primeSize - size) return "done";
for (let i = 0; i < size; i++) {
if (primeSize - size == set_i[i] - i) {
// That is, the number at index 'i' should not go any higher
// For example, if subSize = 4, primeSize = 6, and subset_i[3]
// = 5, we must do the following:
set_i[i - 1]++;
for (let j = i; j < size; j++) set_i[j] = set_i[j - 1] + 1;
return set_i;
}
}
} else {
set_i[size - 1]++;
return set_i;
}
}
Insert cell
// Returns an array of numbers that correspond to the chords array.
// For example, if chords = pentachords and tally is the array returned, then
// tally[1] (we skip 0) is the number of occurances of set 5-1 as a subset/superset.
// "forteList" is the list of forte names that are subsets or supersets of
// a given set.

function searchList(forteList, chords) {
var tally = new Array();
var temp = new Array();
for (let i = 0; i < chords.length + 1; i++) {
// "+ 1" is necessary because tally begins at index 1
tally[i] = 0;
}
for (
let i = 0;
i < forteList.length;
i++ // (ie. tally[0] always equals 0).
) {
forteList[i] = forteList[i].replace("z", "");
temp = forteList[i].split("-");
tally[temp[1]]++;
}
return tally;
}
Insert cell
// Displays the number of supersets of a given cardinality, according to Forte
// name.
// "chords" must come from the two-dimensional array (ex. all the septachords)
// primeForm: the prime form as an array
// subSize = cardinality of the superset

function getSupersets(chords, primeForm, superSize) {
let result="";
if (superSize!= 0 && superSize > primeForm.length && primeForm.length > 2){
var superset;
var cont = true; // Couldn't use the keyword "continue" as a variable!
var forteList = new Array();
// Listing of every occurance of a superset Forte name
let i = 0;
var found;
var allSuperPcs = new Array();
// all the pcs that can be added to primeForm
var superPcs = new Array();
// Pcs added to primeForm. These are extracted from allSuperPcs.
var k = -1; // index for superPcs;
for (let i = 0; i < 12; i++) {
found = false;
for (let j = 0; j < primeForm.length; j++) {
if (primeForm[j] == i) found = true;
}
if (found == false) {
k++;
allSuperPcs[k] = i;
}
}
var superset_i = new Array();
// superset_i is the array of indexes that are picked out from allSuperPcs.
// For example, if subset_i = (0,3) and primeForm = (0,3,4), then
// superset would be (0,1,3,4,6)
for (let z = 0; z < superSize - primeForm.length; z++) superset_i[z] = z;
let w = 0;
while (cont == true) {
superset = getPcs(superset_i, allSuperPcs);
superset = primeForm.concat(superset);
superset = getNorm(superset, superSize);
superset = getPrime(superset, superSize);
forteList[w] = getForte(superset);
superset_i = nextSub_Superset(superset_i, allSuperPcs.length);
if (superset_i == "done") {
cont = false;
}
w++;
}
var tally = searchList(forteList, chords);
// tally is a parallel array of numbers that correspond to the chords array.
// For example if chords = trichords, tally[1] (we skip 0) is the number of
// occurances of set 3-1 as a subset.
result = displaySupersetData(tally, chords);
}
return result;
}
Insert cell
// displays the number of supersets there
// are for a given set, according to Forte name.
// chords is the list of chords of one cardinality.
// tally is a parallel array of numbers that correspond to the chords array.
// For example if chords = trichords, tally[1] (we skip 0) is the number of
// occurrences of set 3-1 as a subset.

function displaySupersetData(tally, chords) {
let superResults = "";
for (let i = 1; i < tally.length; i++) {
if (tally[i] != 0)
superResults += chords[i - 1].forteName + ": " + tally[i] + ";"+ "\n";
}
return superResults;
}
Insert cell
// Part from https://next.observablehq.com/@fil/levenshtein-transition
// displays, in a viewBox, a T or I-matrix for the normal form of the set.
// Also, the y-axis may be inverted

function matrix(vector, op) {
const h = 24,
w = 26;
let vectorY,
t;

if (op === "T y" || op === "I y"){
vectorY = invertSet(vector, vector.length, 0);
} else {
vectorY = vector;
}

let svg = `<svg viewBox="-15,-15,${w * vector.length + 35},${h * vector.length +
35}" width="${w * vector.length + 50}" height="${h * vector.length +
50}" text-anchor="middle" dominant-baseline="middle">`;

for (let j = 0; j <= vector.length; j++) {
for (let i = 0; i <= vector.length; i++) {
if(op === "T" || op === "T y"){
t = vector[i-1] - vectorY[j-1];
if ( t < 0 ){
t = t + 12;
}
}else{
t = vector[i-1] + vectorY[j-1];
if ( t > 11 ){
t = t - 12;
}
}
if (i === 0 && j === 0) t = " ";
else if (i === 0) t = vectorY[j - 1];
else if (j === 0) t = vector[i - 1];

svg += `<text x="${w * i}" y="${h * j}"${
i * j === 0 && i + j > 0 ? 'font-style="italic" font-weight="bold"' : ""
}>${t}</text>`;
}
}

return html([svg]);
}
Insert cell
import { createFretboard, tonal, d3 } from '@awhitty/fretboard'
Insert cell
Insert cell
import { rangeSlider } from '@mootari/range-slider'
Insert cell
Insert cell
function diff(a, b) {
return Math.pow(Math.pow(a[0] - b[0], 2) + Math.pow(a[1] - b[1], 2), 0.5);
}
Insert cell
diff([0, 1], [3, 5])
Insert cell
function esCoherenteArmonicamente(a, b) {
return true;
}
Insert cell
{
const seed = [
[0, 1],
[3, 5]
];

const res = [];
for (let i = 0; i < 12; i++) {
for (let j = 0; j < 6; j++) {
for (let i2 = 0; i2 < 12; i2++) {
for (let j2 = 0; j2 < 6; j2++) {
const test = [
[i, j],
[i2, j2]
];
// res.push([[i,j], [i2, j2]]);
// las dos cuerdas son iguales
if (j === j2) {
continue;
}

const distanciaMec = Math.abs(
diff(seed[0], seed[1]) - diff(test[0], test[1])
);
// Si no son equivalentes mecánicamente (la distancia entre las notas es la misma)
if (distanciaMec > 0.5) {
continue;
}

if (!esCoherenteArmonicamente(test[0], test[1])){
continue;
}
res.push(`${i},${j} ${i2},${j2} (${distanciaMec})`);
}
}
}
}

return res;
}
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more