From 8a1b407c205279e79090bd076ff6722455f086d6 Mon Sep 17 00:00:00 2001
From: Alessandro Culatti <99362337+looptailG@users.noreply.github.com>
Date: Wed, 27 Nov 2024 22:07:38 +0100
Subject: [PATCH 1/7] Added script for generating the plugin
---
.gitignore | 3 +-
out/Generate31EDOPlugin.py | 61 ++++++++++++++++++++++++++++++++++++++
2 files changed, 63 insertions(+), 1 deletion(-)
create mode 100644 out/Generate31EDOPlugin.py
diff --git a/.gitignore b/.gitignore
index 1fcb152..565c2c0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,2 @@
-out
+out/*
+!out/Generate31EDOPlugin.py
diff --git a/out/Generate31EDOPlugin.py b/out/Generate31EDOPlugin.py
new file mode 100644
index 0000000..2d8697e
--- /dev/null
+++ b/out/Generate31EDOPlugin.py
@@ -0,0 +1,61 @@
+import os
+import shutil
+import re
+
+
+PLUGIN_FOLDER_NAME = "31edo_tuner"
+SOURCE_FOLDER = "../source"
+LOGS_FOLDER = f"{PLUGIN_FOLDER_NAME}/logs"
+README_PATH = "../README.md"
+LICENSE_PATH = "../LICENSE"
+THUMBNAIL_PATH = "../thumbnail/31EdoThumbnail.png"
+FILES_TO_COPY = [
+ LICENSE_PATH,
+ THUMBNAIL_PATH,
+]
+
+
+def main():
+ try:
+ shutil.copytree(SOURCE_FOLDER, PLUGIN_FOLDER_NAME, dirs_exist_ok=True)
+ for file_path in FILES_TO_COPY:
+ file_name = file_path[file_path.rindex("/") + 1:]
+ shutil.copyfile(file_path, f"{PLUGIN_FOLDER_NAME}/{file_name}")
+ if not os.path.exists(LOGS_FOLDER):
+ os.makedirs(LOGS_FOLDER)
+
+ version_number = get_version_number()
+ output_folder_name = f"{PLUGIN_FOLDER_NAME}_{version_number}"
+ temporary_folder = "tmp"
+ shutil.copytree(PLUGIN_FOLDER_NAME, f"{temporary_folder}/{PLUGIN_FOLDER_NAME}", dirs_exist_ok=True)
+ shutil.make_archive(output_folder_name, "zip", temporary_folder)
+ shutil.rmtree(temporary_folder)
+
+ except Exception as e:
+ print(e)
+ input()
+
+
+def get_version_number() -> str:
+ version_number_pattern = re.compile(r"version:\s*\"(.+)\";")
+ for root, _, files in os.walk(SOURCE_FOLDER):
+ for file_name in files:
+ if simplify_file_name(PLUGIN_FOLDER_NAME) not in simplify_file_name(file_name):
+ continue
+
+ file_path = os.path.join(root, file_name)
+ with open(file_path, "r") as file:
+ for line in file:
+ match = version_number_pattern.match(line.strip())
+ if match:
+ return match.group(1)
+
+ raise Exception("Could not get the version number.")
+
+
+def simplify_file_name(file_name: str) -> str:
+ return file_name.replace("_", "").lower()
+
+
+if __name__ == "__main__":
+ main()
From 0113f7314916b82f10387e299115fecacbaac270 Mon Sep 17 00:00:00 2001
From: Alessandro Culatti <99362337+looptailG@users.noreply.github.com>
Date: Wed, 27 Nov 2024 23:10:37 +0100
Subject: [PATCH 2/7] Added iteration utils
---
source/31EdoTuner.qml | 310 ++++++++++++----------------------
source/libs/IterationUtils.js | 148 ++++++++++++++++
2 files changed, 254 insertions(+), 204 deletions(-)
create mode 100644 source/libs/IterationUtils.js
diff --git a/source/31EdoTuner.qml b/source/31EdoTuner.qml
index 80753c8..33c1e90 100644
--- a/source/31EdoTuner.qml
+++ b/source/31EdoTuner.qml
@@ -21,6 +21,7 @@ import FileIO 3.0
import MuseScore 3.0
import "libs/AccidentalUtils.js" as AccidentalUtils
import "libs/DateUtils.js" as DateUtils
+import "libs/IterationUtils.js" as IterationUtils
import "libs/NoteUtils.js" as NoteUtils
import "libs/StringUtils.js" as StringUtils
import "libs/TuningUtils.js" as TuningUtils
@@ -31,14 +32,14 @@ MuseScore
description: "Retune the selection, or the whole score if nothing is selected, to 31EDO.";
categoryCode: "playback";
thumbnailName: "31EdoThumbnail.png";
- version: "2.0.1";
+ version: "2.1.0";
property variant settings: {};
// Size in cents of an EDO step.
property var stepSize: 1200.0 / 31;
// Difference in cents between a 12EDO and a 31EDO fifth.
- property var fifthDeviation: 700 - 18 * stepSize;
+ property var fifthDeviation: 700.0 - 18 * stepSize;
// Reference note, which has a tuning offset of zero.
property var referenceNote: "";
@@ -172,196 +173,19 @@ MuseScore
logger.log("Log level set to: " + logger.currentLogLevel);
logger.log("Reference note set to: " + referenceNote);
- curScore.startCmd();
-
- // Calculate the portion of the score to tune.
- var cursor = curScore.newCursor();
- var startStaff;
- var endStaff;
- var startTick;
- var endTick;
- cursor.rewind(Cursor.SELECTION_START);
- if (!cursor.segment)
- {
- logger.log("Tuning the entire score.");
- startStaff = 0;
- endStaff = curScore.nstaves - 1;
- startTick = 0;
- endTick = curScore.lastSegment.tick + 1;
- }
- else
- {
- logger.log("Tuning only the current selection.");
- startStaff = cursor.staffIdx;
- startTick = cursor.tick;
- cursor.rewind(Cursor.SELECTION_END);
- endStaff = cursor.staffIdx;
- if (cursor.tick == 0)
- {
- // If the selection includes the last measure of the score,
- // .rewind() overflows and goes back to tick 0.
- endTick = curScore.lastSegment.tick + 1;
- }
- else
+ IterationUtils.iterate(
+ curScore,
{
- endTick = cursor.tick;
- }
- logger.trace("Tuning only ticks: " + startTick + " - " + endTick);
- logger.trace("Tuning only staffs: " + startStaff + " - " + endStaff);
- }
-
- tunedNotes = 0;
- totalNotes = 0;
- // Loop on the portion of the score to tune.
- for (var staff = startStaff; staff <= endStaff; staff++)
- {
- for (var voice = 0; voice < 4; voice++)
- {
- logger.log("Tuning Staff: " + staff + "; Voice: " + voice);
-
- cursor.voice = voice;
- cursor.staffIdx = staff;
- cursor.rewindToTick(startTick);
-
- currentCustomKeySignature = {};
- previousAccidentals = {};
-
- // Loop on elements of a voice.
- while (cursor.segment && (cursor.tick < endTick))
- {
- if (cursor.segment.tick == cursor.measure.firstSegment.tick)
- {
- // New measure, empty the previous accidentals map.
- previousAccidentals = {};
- }
-
- // Check for key signature change.
- // TODO: This implementation is very ineffcient, as this piece of code is called on every element when the key signature is not empty. Find a way to call this only when the key signature actually change.
- if (cursor.keySignature)
- {
- // The key signature has changed, empty the custom
- // key signature map.
- // TODO: This if is necessary only because the previous if is not true only when there is an actual key signature change. This way we check if the mapping was not empty before, and thus actually needs to be emptied now.
- if (Object.keys(currentCustomKeySignature).length != 0)
- {
- logger.log("Key signature change, emptying the custom key signature map.");
- currentCustomKeySignature = {};
- }
- }
- // Check if there is a text indicating a custom key
- // signature change.
- for (var i = 0; i < cursor.segment.annotations.length; i++)
- {
- var annotationText = cursor.segment.annotations[i].text;
- if (annotationText)
- {
- annotationText = annotationText.replace(/\s*/g, "");
- if (customKeySignatureRegex.test(annotationText))
- {
- logger.log("Applying the current custom key signature: " + annotationText);
- currentCustomKeySignature = {};
- try
- {
- var annotationTextSplitted = annotationText.split(".");
- for (var j = 0; j < annotationTextSplitted.length; j++)
- {
- var currentNote = customKeySignatureNoteOrder[j];
- var currentAccidental = annotationTextSplitted[j].trim();
- var accidentalName = "";
- switch (currentAccidental)
- {
- // Non-microtonal accidentals
- // are automatically handled by
- // Musescore even in custom key
- // signatures, so we only have
- // to check for microtonal
- // accidentals.
- case "bb":
- case "b":
- case "":
- case "h":
- case "#":
- case "x":
- break;
-
- case "db":
- accidentalName = "MIRRORED_FLAT2";
- break;
-
- case "d":
- accidentalName = "MIRRORED_FLAT";
- break;
-
- case "t":
- accidentalName = "SHARP_SLASH";
- break;
-
- case "t#":
- accidentalName = "SHARP_SLASH4";
- break;
-
- default:
- throw "Unsupported accidental in the custom key signature: " + currentAccidental;
- }
- if (accidentalName != "")
- {
- currentCustomKeySignature[currentNote] = accidentalName;
- }
- }
- }
- catch (error)
- {
- logger.error(error);
- currentCustomKeySignature = {};
- }
- }
- }
- }
-
- // Tune notes.
- if (cursor.element && (cursor.element.type == Element.CHORD))
- {
- // Iterate through every grace chord.
- var graceChords = cursor.element.graceNotes;
- for (var i = 0; i < graceChords.length; i++)
- {
- var notes = graceChords[i].notes;
- for (var j = 0; j < notes.length; j++)
- {
- try
- {
- notes[j].tuning = calculateTuningOffset(notes[j]);
- }
- catch (error)
- {
- logger.error(error);
- }
- }
- }
-
- // Iterate through every chord note.
- var notes = cursor.element.notes;
- for (var i = 0; i < notes.length; i++)
- {
- try
- {
- notes[i].tuning = calculateTuningOffset(notes[i]);
- }
- catch (error)
- {
- logger.error(error);
- }
- }
- }
-
- cursor.next();
- }
- }
- }
+ "onStaffStart": onStaffStart,
+ "onNewMeasure": onNewMeasure,
+ "onKeySignatureChange": onKeySignatureChange,
+ "onAnnotation": onAnnotation,
+ "onNote": onNote
+ },
+ logger
+ );
logger.log("Notes tuned: " + tunedNotes + " / " + totalNotes);
-
- curScore.endCmd();
}
catch (error)
{
@@ -372,24 +196,101 @@ MuseScore
quit();
}
}
-
- /**
- * Returns the amount of cents necessary to tune the input note to 31EDO.
- */
- function calculateTuningOffset(note)
+
+ function onStaffStart()
+ {
+ currentCustomKeySignature = {};
+ previousAccidentals = {};
+ }
+
+ function onNewMeasure()
+ {
+ previousAccidentals = {};
+ }
+
+ function onKeySignatureChange(keySignature)
{
- totalNotes += 1;
+ logger.log("Key signature change, emptying the custom key signature map.");
+ currentCustomKeySignature = {};
+ }
- var noteLetter = NoteUtils.getNoteLetter(note, "tpc");
- var accidentalName = AccidentalUtils.getAccidentalName(note);
- var noteOctave = NoteUtils.getOctave(note);
- var noteNameOctave = noteLetter + noteOctave;
- var completeNoteName = noteLetter + " " + accidentalName + " " + noteOctave;
+ function onAnnotation(annotationText)
+ {
+ annotationText = annotationText.replace(/\s*/g, "");
+ if (customKeySignatureRegex.test(annotationText))
+ {
+ logger.log("Applying custom key signature: " + annotationText);
+ currentCustomKeySignature = {};
+ try
+ {
+ let annotationTextSplitted = annotationText.split(".");
+ for (let i = 0; i < annotationTextSplitted.length; i++)
+ {
+ let currentNote = customKeySignatureNoteOrder[i];
+ let currentAccidental = annotationTextSplitted[i];
+ let accidentalName = "";
+ switch (currentAccidental)
+ {
+ // Non-microtonal accidentals are automatically handled
+ // by Musescore even in custom key signatures, so we
+ // only have to check for microtonal accidentals.
+ case "bb":
+ case "b":
+ case "":
+ case "h":
+ case "#":
+ case "x":
+ break;
+
+ case "db":
+ accidentalName = "MIRRORED_FLAT2";
+ break;
+
+ case "d":
+ accidentalName = "MIRRORED_FLAT";
+ break;
+
+ case "t":
+ accidentalName = "SHARP_SLASH";
+ break;
+
+ case "t#":
+ accidentalName = "SHARP_SLASH4";
+ break;
+
+ default:
+ throw "Unsupported accidental in the custom key signature: " + currentAccidental;
+ }
+ if (accidentalName != "")
+ {
+ currentCustomKeySignature[currentNote] = accidentalName;
+ }
+ }
+ }
+ catch (error)
+ {
+ logger.error(error);
+ currentCustomKeySignature = {};
+ }
+ }
+ }
+
+ function onNote(note)
+ {
+ totalNotes++;
+
+ let tuningOffset;
+
+ let noteLetter = NoteUtils.getNoteLetter(note, "tpc");
+ let accidentalName = AccidentalUtils.getAccidentalName(note);
+ let noteOctave = NoteUtils.getOctave(note);
+ let noteNameOctave = noteLetter + noteOctave;
+ let completeNoteName = noteLetter + " " + accidentalName + " " + noteOctave;
logger.trace("Tuning note: " + completeNoteName);
try
{
- var tuningOffset = -TuningUtils.circleOfFifthsDistance(note, referenceNote) * fifthDeviation;
+ tuningOffset = -TuningUtils.circleOfFifthsDistance(note, referenceNote) * fifthDeviation;
logger.trace("Base tuning offset: " + tuningOffset);
// Certain accidentals, like the microtonal accidentals, are not
@@ -448,15 +349,16 @@ MuseScore
logger.trace("Offsetting the tuning by " + edoSteps + " EDO steps.");
}
- tunedNotes += 1;
logger.trace("Final tuning offset: " + tuningOffset);
- return tuningOffset;
}
catch (error)
{
logger.error("Encontered the following exception while tuning " + completeNoteName + ": " + error);
// Leave the tuning of the input note unchanged.
- return note.tuning;
+ tuningOffset = 0.0;
}
+
+ note.tuning = tuningOffset;
+ tunedNotes++;
}
}
diff --git a/source/libs/IterationUtils.js b/source/libs/IterationUtils.js
new file mode 100644
index 0000000..ab6e6b4
--- /dev/null
+++ b/source/libs/IterationUtils.js
@@ -0,0 +1,148 @@
+/*
+ A collection of functions and constants for iterating over a score.
+ Copyright (C) 2024 Alessandro Culatti
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+*/
+
+const VERSION = "1.0.0";
+
+function iterate(curScore, actions, logger)
+{
+ let onStaffStart = actions.onStaffStart || null;
+ let onNewMeasure = actions.onNewMeasure || null;
+ let onKeySignatureChange = actions.onKeySignatureChange || null;
+ let onAnnotation = actions.onAnnotation || null;
+ let onNote = actions.onNote || null;
+
+ curScore.startCmd();
+ let cursor = curScore.newCursor();
+
+ // Calculate the portion of the score to iterate on.
+ let startStaff;
+ let endStaff;
+ let startTick;
+ let endTick;
+ cursor.rewind(Cursor.SELECTION_START);
+ if (!cursor.segment)
+ {
+ logger.log("Tuning the entire score.");
+ startStaff = 0;
+ endStaff = curScore.nstaves - 1;
+ startTick = 0;
+ endTick = curScore.lastSegment.tick;
+ }
+ else
+ {
+ logger.log("Tuning only the current selection.");
+ startStaff = cursor.staffIdx;
+ startTick = cursor.tick;
+ cursor.rewind(Cursor.SELECTION_END);
+ endStaff = cursor.staffIdx;
+ if (cursor.tick == 0)
+ {
+ // If the selection includes the last note of the score, .rewind()
+ // overflows and goes back to tick 0.
+ endTick = curScore.lastSegment.tick;
+ }
+ else
+ {
+ endTick = cursor.tick;
+ }
+ logger.trace("Tuning only ticks: " + startTick + " - " + endTick);
+ logger.trace("Tuning only staffs: " + startStaff + " - " + endStaff);
+ }
+
+ // Iterate on the score.
+ for (let staff = startStaff; staff <= endStaff; staff++)
+ {
+ for (let voice = 0; voice < 4; voice++)
+ {
+ logger.log("Tuning Staff: " + staff + "; Voice: " + voice);
+
+ cursor.voice = voice;
+ cursor.staffIdx = staff;
+ cursor.rewindToTick(startTick);
+
+ let previousKeySignature = cursor.keySignature;
+
+ if (onStaffStart)
+ {
+ onStaffStart();
+ }
+
+ // Loop on the element of the current staff.
+ while (cursor.segment && (cursor.tick <= endTick))
+ {
+ if (cursor.segment.tick == cursor.measure.firstSegment.tick)
+ {
+ if (onNewMeasure)
+ {
+ onNewMeasure();
+ }
+ }
+
+ if (cursor.keySignature != previousKeySignature)
+ {
+ if (onKeySignatureChange)
+ {
+ onKeySignatureChange(cursor.keySignature);
+ }
+ previousKeySignature = cursor.keySignature;
+ }
+
+ for (let i = 0; i < cursor.segment.annotations.length; i++)
+ {
+ let annotationText = cursor.segment.annotations[i].text;
+ if (annotationText)
+ {
+ if (onAnnotation)
+ {
+ onAnnotation(annotationText);
+ }
+ }
+ }
+
+ if (cursor.element && (cursor.element.type == Element.CHORD))
+ {
+ let graceChords = cursor.element.graceNotes;
+ for (let i = 0; i < graceChords.length; i++)
+ {
+ let notes = graceChords[i].notes;
+ for (let j = 0; j < notes.length; j++)
+ {
+ if (onNote)
+ {
+ onNote(notes[j]);
+ }
+ }
+ }
+
+ let notes = cursor.element.notes;
+ for (let i = 0; i < notes.length; i++)
+ {
+ if (onNote)
+ {
+ onNote(notes[i]);
+ }
+ }
+ }
+
+ cursor.next();
+ }
+ }
+ }
+
+ curScore.endCmd();
+}
From 0f242d39f403314d789f457c7056a37505c102b7 Mon Sep 17 00:00:00 2001
From: Alessandro Culatti <99362337+looptailG@users.noreply.github.com>
Date: Thu, 28 Nov 2024 21:41:22 +0100
Subject: [PATCH 3/7] Updated string utils
---
source/libs/StringUtils.js | 54 ++++++++++++++++++++++++++++++++------
1 file changed, 46 insertions(+), 8 deletions(-)
diff --git a/source/libs/StringUtils.js b/source/libs/StringUtils.js
index 16eb36b..aee99dc 100644
--- a/source/libs/StringUtils.js
+++ b/source/libs/StringUtils.js
@@ -16,7 +16,7 @@
along with this program. If not, see .
*/
-const VERSION = "1.0.0";
+const VERSION = "1.0.1";
/**
* Split the input string using the tab character, and replace the escaped
@@ -25,12 +25,50 @@ const VERSION = "1.0.0";
function parseTsvRow(s)
{
s = s.split("\t");
- for (var i = 0; i < s.length; i++)
+ // QML does not support lookbehind in regex, which would be necessary to
+ // properly unescape the characters, so we have to manually loop on the
+ // strings and check for escape characters.
+ for (let i = 0; i < s.length; i++)
{
- s[i] = s[i].replace(/\\t/g, "\t");
- s[i] = s[i].replace(/\\\\/g, "\\");
- s[i] = s[i].replace(/\\n/g, "\n");
- s[i] = s[i].replace(/\\r/g, "\r");
+ let unescapedString = "";
+ let escapedString = s[i];
+ let j = 0;
+ while (j < escapedString.length)
+ {
+ let c = escapedString.charAt(j);
+ if (c == "\\")
+ {
+ let nextCharacter = escapedString.charAt(++j);
+ switch (nextCharacter)
+ {
+ case "\\":
+ unescapedString += "\\";
+ break;
+
+ case "n":
+ unescapedString += "\n";
+ break;
+
+ case "r":
+ unescapedString += "\r";
+ break;
+
+ case "t":
+ unescapedString += "\t";
+ break;
+
+ default:
+ throw "Invalid escape sequence: " + c + nextCharacter;
+ }
+ }
+ else
+ {
+ unescapedString += c;
+ }
+
+ j++;
+ }
+ s[i] = unescapedString;
}
return s;
}
@@ -54,7 +92,7 @@ function formatForTsv(s)
function removeEmptyRows(s)
{
s = s.split("\n");
- for (var i = s.length - 1; i >= 0; i--)
+ for (let i = s.length - 1; i >= 0; i--)
{
if (s[i].trim() == "")
{
@@ -75,7 +113,7 @@ function roundToOneDecimalDigit(n)
{
throw "The input is not numeric: " + n;
}
- var roundedNumber = "" + (Math.round(n * 10) / 10);
+ let roundedNumber = "" + (Math.round(n * 10) / 10);
if (Number.isInteger(n))
{
roundedNumber += ".0";
From 5e7fa7b9d5b186224c08ab1f1c008a3908e969b2 Mon Sep 17 00:00:00 2001
From: Alessandro Culatti <99362337+looptailG@users.noreply.github.com>
Date: Thu, 28 Nov 2024 22:43:03 +0100
Subject: [PATCH 4/7] Updated tuning utils
---
source/31EdoTuner.qml | 19 ++++++++-
source/libs/TuningUtils.js | 79 +++++++++++++++++++++++++++++++++++++-
2 files changed, 96 insertions(+), 2 deletions(-)
diff --git a/source/31EdoTuner.qml b/source/31EdoTuner.qml
index 33c1e90..2fd0d5b 100644
--- a/source/31EdoTuner.qml
+++ b/source/31EdoTuner.qml
@@ -279,6 +279,23 @@ MuseScore
{
totalNotes++;
+ try
+ {
+ note.tuning = TuningUtils.edoTuningOffset(
+ note, NoteUtils.getNoteLetter(note, "tpc"), AccidentalUtils.getAccidentalName(note), NoteUtils.getOctave(note), referenceNote,
+ stepSize, fifthDeviation, supportedAccidentals, AccidentalUtils.ACCIDENTAL_DATA,
+ previousAccidentals, currentCustomKeySignature,
+ logger
+ );
+ tunedNotes++;
+ }
+ catch (error)
+ {
+ logger.error(error);
+ }
+
+/* totalNotes++;
+
let tuningOffset;
let noteLetter = NoteUtils.getNoteLetter(note, "tpc");
@@ -359,6 +376,6 @@ MuseScore
}
note.tuning = tuningOffset;
- tunedNotes++;
+ tunedNotes++;*/
}
}
diff --git a/source/libs/TuningUtils.js b/source/libs/TuningUtils.js
index 03ab02b..4d9dfbc 100644
--- a/source/libs/TuningUtils.js
+++ b/source/libs/TuningUtils.js
@@ -16,7 +16,7 @@
along with this program. If not, see .
*/
-const VERSION = "1.1.1";
+const VERSION = "1.2.0";
// Size in cents of a justly tuned perfect fifth.
const JUST_FIFTH = 1200.0 * Math.log2(3 / 2);
@@ -80,3 +80,80 @@ function circleOfFifthsDistance(n1, n2, tpcMode = "tpc1")
return n1Tpc - n2Tpc;
}
+
+/**
+ * Calculate the tuning offset for an EDO tuning.
+ */
+function edoTuningOffset(
+ note, noteName, accidental, octave, referenceNote,
+ stepSize, fifthDeviation, supportedAccidentals, accidentalData,
+ previousAccidentals, customKeySignature,
+ logger
+) {
+ logger.trace("Tuning note: " + noteName + " " + accidental + " " + octave);
+ let actualAccidental = accidental;
+ let effectiveAccidental = accidental;
+ let fullNoteName = noteName + accidental;
+ let noteNameOctave = noteName + octave;
+
+ let tuningOffset;
+
+ tuningOffset = -circleOfFifthsDistance(note, referenceNote) * fifthDeviation;
+ logger.trace("Base tuning offset: " + tuningOffset);
+
+ // Certain accidentals, like the microtonal accidentals, are not
+ // conveyed by the tpc property, but are instead handled directly via a
+ // tuning offset.
+ // Check which accidental is applied to the note.
+ if (effectiveAccidental == "NONE")
+ {
+ // If the note does not have any accidental applied to it, check if
+ // the same note previously in the measure was modified by a
+ // microtonal accidental.
+ if (previousAccidentals.hasOwnProperty(noteNameOctave))
+ {
+ effectiveAccidental = previousAccidentals[noteNameOctave];
+ logger.trace("Applying the following accidental to the current note from a previous note in the measure: " + effectiveAccidental);
+ }
+ // If the note still does not have an accidental applied to itself,
+ // check if it's modified by a custom key signature.
+ if (effectiveAccidental == "NONE")
+ {
+ if (customKeySignature.hasOwnProperty(noteName))
+ {
+ effectiveAccidental = customKeySignature[noteName];
+ logger.trace("Applying the following accidental from the custom key signature: " + effectiveAccidental);
+ }
+ }
+ }
+ else
+ {
+ // Save the accidental in the previous accidentals map for this
+ // note.
+ previousAccidentals[noteNameOctave] = actualAccidental;
+ }
+
+ // Check if the accidental is handled by a tuning offset.
+ if (!accidentalData[effectiveAccidental]["TPC"])
+ {
+ // Undo the default tuning offset which is applied to certain
+ // accidentals.
+ // The default tuning offset is applied only if an actual microtonal
+ // accidental is applied to the current note.
+ let actualAccidentalOffset = accidentalData[actualAccidental]["DEFAULT_OFFSET"];
+ tuningOffset -= actualAccidentalOffset;
+ logger.trace("Undoing the default tuning offset of: " + actualAccidentalOffset);
+
+ // Apply the tuning offset for this specific accidental.
+ let edoSteps = supportedAccidentals[effectiveAccidental];
+ if (edoSteps === undefined)
+ {
+ throw "Unsupported accidental: " + effectiveAccidental;
+ }
+ tuningOffset += edoSteps * stepSize;
+ logger.trace("Offsetting the tuning by " + edoSteps + " EDO steps.");
+ }
+
+ logger.trace("Final tuning offset: " + tuningOffset);
+ return tuningOffset;
+}
\ No newline at end of file
From 9360108f1afc2cf4ce173e8ec9290ec303cdb63b Mon Sep 17 00:00:00 2001
From: Alessandro Culatti <99362337+looptailG@users.noreply.github.com>
Date: Thu, 28 Nov 2024 22:52:56 +0100
Subject: [PATCH 5/7] Updated tuning utils to 1.3.0
---
source/libs/TuningUtils.js | 76 ++++++++++++++++++++++++++++++++++++--
1 file changed, 72 insertions(+), 4 deletions(-)
diff --git a/source/libs/TuningUtils.js b/source/libs/TuningUtils.js
index 4d9dfbc..658a65d 100644
--- a/source/libs/TuningUtils.js
+++ b/source/libs/TuningUtils.js
@@ -16,10 +16,10 @@
along with this program. If not, see .
*/
-const VERSION = "1.2.0";
+const VERSION = "1.3.0";
// Size in cents of a justly tuned perfect fifth.
-const JUST_FIFTH = 1200.0 * Math.log2(3 / 2);
+const JUST_FIFTH = intervalInCents(3 / 2);
// Size in cents of a 12EDO perfect fifth.
const DEFAULT_FIFTH = 700.0;
// Size in cents of the smallest fifth in the diatonic range. It's equal to the
@@ -30,7 +30,7 @@ const SMALLEST_DIATONIC_FIFTH = 1200.0 / 7 * 4;
const LARGEST_DIATONIC_FIFTH = 1200.0 / 5 * 3;
// Size in cents of the syntonic comma.
-const SYNTONIC_COMMA = 1200.0 * Math.log2(81 / 80);
+const SYNTONIC_COMMA = intervalInCents(81 / 80);
// Note distance in the circle of fifths, from the note C.
const CIRCLE_OF_FIFTHS_DISTANCE = {
@@ -81,6 +81,74 @@ function circleOfFifthsDistance(n1, n2, tpcMode = "tpc1")
return n1Tpc - n2Tpc;
}
+/**
+ * Return the input frequency ratio in cents.
+ */
+function intervalInCents(frequencyRatio)
+{
+ return 1200 * Math.log2(frequencyRatio);
+}
+
+/**
+ * Return the offset in cents for the input scale degree in the harmonic scale.
+ */
+function harmonicScaleOffset(scaleDegree)
+{
+ var harmonic = harmonicScaleHarmonic(scaleDegree);
+ var interval = intervalInCents(harmonic / 16);
+ var defaultTuning = 100.0 * scaleDegree;
+ return interval - defaultTuning;
+}
+
+/**
+ * Return the harmonic corresponding to the input scale degree in the harmonic
+ * scale.
+ */
+function harmonicScaleHarmonic(scaleDegree)
+{
+ switch (scaleDegree)
+ {
+ case 0:
+ return 16;
+
+ case 1:
+ return 17;
+
+ case 2:
+ return 18;
+
+ case 3:
+ return 19;
+
+ case 4:
+ return 20;
+
+ case 5:
+ return 21;
+
+ case 6:
+ return 22;
+
+ case 7:
+ return 24;
+
+ case 8:
+ return 26;
+
+ case 9:
+ return 27;
+
+ case 10:
+ return 28;
+
+ case 11:
+ return 30;
+
+ default:
+ throw "Invalid scale degree: " + scaleDegree;
+ }
+}
+
/**
* Calculate the tuning offset for an EDO tuning.
*/
@@ -156,4 +224,4 @@ function edoTuningOffset(
logger.trace("Final tuning offset: " + tuningOffset);
return tuningOffset;
-}
\ No newline at end of file
+}
From 9f33fb89fe7cae75ea1076c5374e5e8e3a0dc7bf Mon Sep 17 00:00:00 2001
From: Alessandro Culatti <99362337+looptailG@users.noreply.github.com>
Date: Thu, 28 Nov 2024 22:56:22 +0100
Subject: [PATCH 6/7] Removed commented code
---
source/31EdoTuner.qml | 84 -------------------------------------------
1 file changed, 84 deletions(-)
diff --git a/source/31EdoTuner.qml b/source/31EdoTuner.qml
index 2fd0d5b..914feb7 100644
--- a/source/31EdoTuner.qml
+++ b/source/31EdoTuner.qml
@@ -293,89 +293,5 @@ MuseScore
{
logger.error(error);
}
-
-/* totalNotes++;
-
- let tuningOffset;
-
- let noteLetter = NoteUtils.getNoteLetter(note, "tpc");
- let accidentalName = AccidentalUtils.getAccidentalName(note);
- let noteOctave = NoteUtils.getOctave(note);
- let noteNameOctave = noteLetter + noteOctave;
- let completeNoteName = noteLetter + " " + accidentalName + " " + noteOctave;
- logger.trace("Tuning note: " + completeNoteName);
-
- try
- {
- tuningOffset = -TuningUtils.circleOfFifthsDistance(note, referenceNote) * fifthDeviation;
- logger.trace("Base tuning offset: " + tuningOffset);
-
- // Certain accidentals, like the microtonal accidentals, are not
- // conveyed by the tpc property, but are instead handled directly
- // via a tuning offset.
- // Check which accidental is applied to the note.
- if (accidentalName == "NONE")
- {
- // If the note does not have any accidental applied to it, check
- // if the same note previously in the measure was modified by a
- // microtonal accidental.
- if (previousAccidentals.hasOwnProperty(noteNameOctave))
- {
- accidentalName = previousAccidentals[noteNameOctave];
- logger.trace("Applying to the following accidental to the current note from a previous note within the measure: " + accidentalName);
- }
- // If the note still does not have an accidental applied to it,
- // check if it's modified by a custom key signature.
- if (accidentalName == "NONE")
- {
- if (currentCustomKeySignature.hasOwnProperty(noteLetter))
- {
- accidentalName = currentCustomKeySignature[noteLetter];
- logger.trace("Applying the following accidental from a custom key signature: " + accidentalName);
- }
- }
- }
- else
- {
- // Save the accidental in the previous accidentals map for this
- // note.
- previousAccidentals[noteNameOctave] = accidentalName;
- }
- // Check if the accidental is handled by a tuning offset.
- if (!AccidentalUtils.ACCIDENTAL_DATA[accidentalName]["TPC"])
- {
- // Undo the default tuning offset which is applied to certain
- // accidentals.
- // The default tuning offset is applied only if an actual
- // microtonal accidental is applied to the current note. For
- // this reason, we must check getAccidentalName() on the current
- // note, it is not sufficient to check the value saved in
- // accidentalName.
- var actualAccidentalName = AccidentalUtils.getAccidentalName(note);
- var actualAccidentalOffset = AccidentalUtils.ACCIDENTAL_DATA[actualAccidentalName]["DEFAULT_OFFSET"];
- tuningOffset -= actualAccidentalOffset;
- logger.trace("Undoing the default tuning offset of: " + actualAccidentalOffset);
-
- // Apply the tuning offset for this specific accidental.
- var edoSteps = supportedAccidentals[accidentalName];
- if (edoSteps === undefined)
- {
- throw "Unsupported accidental: " + accidentalName;
- }
- tuningOffset += edoSteps * stepSize;
- logger.trace("Offsetting the tuning by " + edoSteps + " EDO steps.");
- }
-
- logger.trace("Final tuning offset: " + tuningOffset);
- }
- catch (error)
- {
- logger.error("Encontered the following exception while tuning " + completeNoteName + ": " + error);
- // Leave the tuning of the input note unchanged.
- tuningOffset = 0.0;
- }
-
- note.tuning = tuningOffset;
- tunedNotes++;*/
}
}
From ae34ef8f4f07fa7f7950e8e6e359ae49d0ba9258 Mon Sep 17 00:00:00 2001
From: Alessandro Culatti <99362337+looptailG@users.noreply.github.com>
Date: Thu, 28 Nov 2024 23:36:37 +0100
Subject: [PATCH 7/7] Added support for staff text on the current staff only
---
source/31EdoTuner.qml | 4 ++--
source/libs/IterationUtils.js | 19 +++++++++++++++++--
2 files changed, 19 insertions(+), 4 deletions(-)
diff --git a/source/31EdoTuner.qml b/source/31EdoTuner.qml
index 914feb7..91dac54 100644
--- a/source/31EdoTuner.qml
+++ b/source/31EdoTuner.qml
@@ -214,9 +214,9 @@ MuseScore
currentCustomKeySignature = {};
}
- function onAnnotation(annotationText)
+ function onAnnotation(annotation)
{
- annotationText = annotationText.replace(/\s*/g, "");
+ let annotationText = annotation.text.replace(/\s*/g, "");
if (customKeySignatureRegex.test(annotationText))
{
logger.log("Applying custom key signature: " + annotationText);
diff --git a/source/libs/IterationUtils.js b/source/libs/IterationUtils.js
index ab6e6b4..392b922 100644
--- a/source/libs/IterationUtils.js
+++ b/source/libs/IterationUtils.js
@@ -18,12 +18,15 @@
const VERSION = "1.0.0";
+const ELEMENT_STAFF_TEXT = 48;
+
function iterate(curScore, actions, logger)
{
let onStaffStart = actions.onStaffStart || null;
let onNewMeasure = actions.onNewMeasure || null;
let onKeySignatureChange = actions.onKeySignatureChange || null;
let onAnnotation = actions.onAnnotation || null;
+ let staffTextOnCurrentStaffOnly = actions.staffTextOnCurrentStaffOnly || true;
let onNote = actions.onNote || null;
curScore.startCmd();
@@ -104,12 +107,24 @@ function iterate(curScore, actions, logger)
for (let i = 0; i < cursor.segment.annotations.length; i++)
{
- let annotationText = cursor.segment.annotations[i].text;
+ let annotation = cursor.segment.annotations[i];
+ let annotationText = annotation.text;
if (annotationText)
{
if (onAnnotation)
{
- onAnnotation(annotationText);
+ if ((annotation.type === ELEMENT_STAFF_TEXT) && staffTextOnCurrentStaffOnly)
+ {
+ let annotationPart = annotation.staff.part;
+ if ((4 * staff >= annotationPart.startTrack) && (4 * staff < annotationPart.endTrack))
+ {
+ onAnnotation(annotation);
+ }
+ }
+ else
+ {
+ onAnnotation(annotation);
+ }
}
}
}