diff --git a/source/FifthGeneratedTuner.qml b/source/FifthGeneratedTuner.qml index e923b21..abb1209 100644 --- a/source/FifthGeneratedTuner.qml +++ b/source/FifthGeneratedTuner.qml @@ -22,6 +22,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 @@ -32,7 +33,7 @@ MuseScore description: "Retune the selection, or the whole score if nothing is selected, using the specified fifth size."; categoryCode: "playback"; thumbnailName: "FifthGeneratedTunerThumbnail.png"; - version: "1.3.1"; + version: "1.3.2"; pluginType: "dialog"; property var padding: 10; @@ -846,99 +847,15 @@ MuseScore { logger.log("Tuning notes."); - 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) + IterationUtils.iterate( + curScore, { - // If the selection includes the last measure of the score, - // .rewind() overflows and goes back to tick 0. - endTick = curScore.lastSegment.tick + 1; - } - else - { - endTick = cursor.tick; - } - logger.trace("Tuning only ticks: " + startTick + " - " + endTick); - logger.trace("Tuning only staffs: " + startStaff + " - " + endStaff); - } - - // 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); - - while (cursor.segment && (cursor.tick < endTick)) - { - // Tune notes. - if (cursor.element && (cursor.element.type == Element.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); - } - } - } - - 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(); - } - } - } + "onNote": onNote + }, + logger + ); logger.log("Notes tuned: " + tunedNotes + " / " + totalNotes); - - curScore.endCmd(); } catch (error) { @@ -950,27 +867,21 @@ MuseScore } } - /** - * Returns the amount of cents necessary to tune the input note to 31EDO. - */ - function calculateTuningOffset(note) + function onNote(note) { totalNotes += 1; - logger.trace("Tuning note: " + NoteUtils.getNoteLetter(note) + " " + AccidentalUtils.getAccidentalName(note) + " " + NoteUtils.getOctave(note)); - try { + logger.trace("Tuning note: " + NoteUtils.getNoteLetter(note) + " " + AccidentalUtils.getAccidentalName(note) + " " + NoteUtils.getOctave(note)); var tuningOffset = -TuningUtils.circleOfFifthsDistance(note, referenceNote) * fifthDeviation; - tunedNotes += 1; logger.trace("Tuning offset: " + tuningOffset); - return tuningOffset; + note.tuning = tuningOffset; + tunedNotes += 1; } catch (error) { logger.error(error); - // Leave the tuning of the input note unchanged. - return note.tuning; } } diff --git a/source/libs/IterationUtils.js b/source/libs/IterationUtils.js new file mode 100644 index 0000000..392b922 --- /dev/null +++ b/source/libs/IterationUtils.js @@ -0,0 +1,163 @@ +/* + 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 <https://www.gnu.org/licenses/>. +*/ + +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(); + 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 annotation = cursor.segment.annotations[i]; + let annotationText = annotation.text; + if (annotationText) + { + if (onAnnotation) + { + 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); + } + } + } + } + + 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(); +}