Skip to content
This repository has been archived by the owner on Jun 1, 2024. It is now read-only.

Commit

Permalink
Merge pull request #90 from mfwolffe/main
Browse files Browse the repository at this point in the history
rest shifting according to clarified spec
  • Loading branch information
hcientist committed Feb 13, 2024
2 parents 0268377 + 4c64e89 commit 2465bb9
Show file tree
Hide file tree
Showing 2 changed files with 88 additions and 53 deletions.
17 changes: 11 additions & 6 deletions components/flatEditor.js
Original file line number Diff line number Diff line change
Expand Up @@ -647,14 +647,19 @@ function FlatEditor({
setRefId(score.scoreId);
})
.catch((e) => {
e.message = `flat error: ${e.message}, not loaded from scoreId, score: ${JSON.stringify(score)}, orig: ${orig}, colors: ${colors}`;

// **** Hi - even with optional chaining, line 654 causing runtime errors (I *think* when scores get locked).
// Otherwise chaining resolves rare, seemingly random errors accessing message property

// e?.message = `flat error: ${e.message}, not loaded from scoreId, score: ${JSON.stringify(score)}, orig: ${orig}, colors: ${colors}`;
if (debugMsg){
e.message = `${e.message}, debugMsg: ${debugMsg}`;
console.error(`debugMsg: ${debugMsg}`;
// e.message = `${e.message}, debugMsg: ${debugMsg}`;
}
// console.error('score not loaded from scoreId');
// console.error('score', score);
// console.error('orig', orig);
// console.error('colors', colors);
console.error('score not loaded from scoreId');
console.error('score', score);
console.error('orig', orig);
console.error('colors', colors);
// console.error(e);
throw e;
})
Expand Down
124 changes: 77 additions & 47 deletions lib/variations.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,34 +4,72 @@ import { hasBasePath } from "next/dist/server/router";
* Takes a score and strips it of unnecessary properties, and rebuilds
* according to spec (e.g., 2/4 -> 4/4, uuid's removed)
*
*
* @param {*} orig the original score from flat
* @param {*} key the key; could omit and do this in the function, but it's even uglier code
* @param {*} clef the clef; could also omit
* @param {*} notes The student's motive (notes only), could omit
* @param {*} div The orig divisions, again, could omit
* @returns
* @returns the corrected score
*/
function correctScore(orig, key, clef, notes, div) {
const origDupe = JSON.parse(JSON.stringify(orig));
const result = JSON.parse(JSON.stringify(template));
const resultScorePart = result['score-partwise']['part-list']['score-part'][0];
const origScorePart = origDupe['score-partwise']['part-list']['score-part'][0];
const resultScorePart = result['score-partwise']['part-list']['score-part'][0];
const origScorePart = origDupe['score-partwise']['part-list']['score-part'][0];
const resultAttributes = result['score-partwise']['part'][0].measure[0].attributes[0];

Object.keys(resultScorePart).forEach((key) => {
if (Object.hasOwn(origScorePart, key) && key != "uuid") {
resultScorePart[key] = origScorePart[key];
}
});

result['score-partwise']['part'][0].measure[0].attributes[0].clef = clef;
result['score-partwise']['part'][0].measure[0].attributes[0].key = key;
result['score-partwise']['part'][0].measure[0].attributes[0].divisions = "" + div; // I think necessary for rendering
resultAttributes.clef = clef;
resultAttributes.key = key;
resultAttributes.divisions = "" + div; // I think necessary for rendering
result['score-partwise']['part'][0].measure[0].note = [...notes];
console.log("score immediately after correction", result);
return result;
}

/**
* rebuild given measure with attributes that are
* more in line for CPR's spec
*
* Still necessary despite template approach being taken
*
* @param {*} measure measure to be corrected
* @returns corrected measure
*/
function correctMeasure(measure) {
const defaultDiv = 2; // the default division, assuming shifts will be no less than 8th note
let alteredMeasure = JSON.parse(JSON.stringify(measure)); // deep copy
let origDivisions = alteredMeasure.attributes[0].divisions; // the original division
let beatDivision = defaultDiv / origDivisions; // conversion factor for reconstruction
console.log("beatDivision", beatDivision);
console.log("origDivisions", alteredMeasure.attributes[0].divisions);
console.log("inside correctMeasure measure", JSON.parse(JSON.stringify(alteredMeasure)));

// check if reconstruction necessary
let modify = origDivisions != defaultDiv && (alteredMeasure.attributes[0].divisions = defaultDiv);

// only modify if necessary
modify && (alteredMeasure.note.forEach((note) => {
note['$adagio-location'].timePos = Math.floor(note['$adagio-location'].timePos * beatDivision);
note.duration = Math.floor(note.duration * beatDivision);
}));

// an explicit property describing whether a beat is a rest makes life easier
// (idt you can *always* use null in the way you can in a lang like C, so
// this is more out of an abundance of caution)
alteredMeasure.note.forEach((note) => {
note.isRest = (Boolean) (note.rest ?? false);
});
console.log(`outgoing corrected Measure w/ isRest property`, [...alteredMeasure.note]);

return alteredMeasure;
}

/**
* given a measure, generate that measure's retrograde
*
Expand Down Expand Up @@ -66,36 +104,6 @@ function mwRetrograde(orig) {
return result;
}

/**
* rebuild given measure with attributes that are
* more in line for CPR's spec
*
* Still necessary despite template approach being taken
*
* @param {*} measure measure to be corrected
* @returns corrected measure
*/
function correctMeasure(measure) {
const defaultDiv = 2; // the default division, assuming shifts will be no less than 8th note
let alteredMeasure = JSON.parse(JSON.stringify(measure)); // deep copy
let origDivisions = alteredMeasure.attributes[0].divisions; // the original division
let beatDivision = defaultDiv / origDivisions; // conversion factor for reconstruction
console.log("beatDivision", beatDivision);
console.log("origDivisions", alteredMeasure.attributes[0].divisions);
console.log("inside correctMeasure measure", JSON.parse(JSON.stringify(alteredMeasure)));

// check if reconstruction necessary
let modify = origDivisions != defaultDiv && (alteredMeasure.attributes[0].divisions = defaultDiv);

// only modify if necessary
modify && (alteredMeasure.note.forEach((note) => {
note['$adagio-location'].timePos = Math.floor(note['$adagio-location'].timePos * beatDivision);
note.duration = Math.floor(note.duration * beatDivision);
}));

return alteredMeasure;
}

/**
* Performs a rhythmic shift by eighths, wrapping shifted notes
* back to measure start, coalescing notes that were split by
Expand All @@ -106,8 +114,8 @@ function correctMeasure(measure) {
* @returns shifted measure
*/
function mwRhythmicShift(orig, shift) {
const measure = JSON.parse(JSON.stringify(orig)); // deep copy
const numNotes = orig.note.length; // original # of notes
const measure = JSON.parse(JSON.stringify(orig));
const numNotes = orig.note.length;

const maxNotes = orig.attributes[0].time.beats * measure.attributes[0].divisions; // max # notes possible (restricted to 8ths)
let pitchList = [...measure.note];
Expand Down Expand Up @@ -140,9 +148,8 @@ function mwRhythmicShift(orig, shift) {
// check the split candidates, and perform split if necessary.
for (let note of checkList) {
if (note.duration > shift || note.uPos - maxNotes < 0) {
let splitNote = JSON.parse(JSON.stringify(note)); // 1/2 of the note to split
let splitNote = JSON.parse(JSON.stringify(note));

// perform the splitting
splitNote['$adagio-location'].timePos = splitNote.uPos;
splitNote.duration -= 1;
note.duration -= 1;
Expand All @@ -157,11 +164,12 @@ function mwRhythmicShift(orig, shift) {
measure.note = pitchList;
return measure;
}

/**
* Performs a melodic shift by eighths on a given measure,
* i.e., circular permutation
* Note: rhythms remain the same, only *pitches* are
* shifted
* shifted (as long as rests are not involved)
*
* @param {*} orig the measure to shift
* @param {*} shift amount of eighths to shift by
Expand All @@ -171,13 +179,31 @@ function mwMelodicShift(orig, shift) {
const result = JSON.parse(JSON.stringify(orig));
const pitchList = [];

// push the correct attribute for this note
// IMPORTANT: flat freaks out if a note has pitch and rest properties au même temps
for (let note of result.note) {
pitchList.push(note.pitch);
pitchList.push(note.isRest ? note.rest : note.pitch);
}

// move the first note to the end of the list, call it shiftList
const shiftList = pitchList.splice(shift).concat(pitchList);

// perform the observable permutation
for (let note of result.note) {
note.pitch = shiftList.shift();
const temp = shiftList.shift();
const isPitch = temp.hasOwnProperty('step');
const which = isPitch ? 'pitch' : 'rest';

// if one is a pitch and the other is a rest, ensure correct
// property is deleted and the new one is set
if (isPitch == note.isRest) {
delete note[isPitch ? 'rest' : 'pitch'];
note[which] = temp;
note.isRest = !isPitch;
} else {
// otherwise they're both pitches/both rests and no change is necessary
note[isPitch ? 'pitch' : 'rest'] = temp;
}
}

return result;
Expand All @@ -194,8 +220,8 @@ function mwMelodicShift(orig, shift) {
*/
function mwRhythmicMelodicShift(orig, shift) {
let result = JSON.parse(JSON.stringify(orig));
result = mwRhythmicShift(result, shift);
result = mwMelodicShift(result, shift);
result = mwRhythmicShift(result, shift);
return result;
}

Expand Down Expand Up @@ -224,10 +250,15 @@ Array.prototype.addAll = function(elements) {
function mwMergeVariations(orig, measures) {
const result = JSON.parse(JSON.stringify(orig));
measures.forEach((measure) => {

// this inner loop may be unnecessary - flat did not used to
// get mad about numerical duration not matching note type
// however, semantically, it is probably best to leave here
measure.note.forEach((note) => {
note.duration = note.duration.toString();
note.type = note.duration === "1" ? "eighth" : "quarter";
});
// there was a weird issue with divisions needing to be stringified?
measure.attributes[0].divisions = measure.attributes[0].divisions.toString();
});
console.log("after merge modifications", measures);
Expand Down Expand Up @@ -266,8 +297,7 @@ function mwCreateVariations(origStr) {
const givenMeasure = correctedScore['score-partwise'].part[0].measure[0]; // grab the motive...
const correctedMeasure = correctMeasure(givenMeasure); // ...then correct it
console.log("incoming score", orig);
console.log("outgoing key", key);
console.log("outgoing clef", clef);
console.log(`outgoing key: ${key}; outgoing clef: ${clef}`);
console.log("corrected score", correctedScore);
console.log("given measure", givenMeasure);
console.log("corrected measure", correctedMeasure);
Expand Down

0 comments on commit 2465bb9

Please sign in to comment.