From 838d85a85cd3be163dc926a762cdd3dda2eb3edc Mon Sep 17 00:00:00 2001 From: Eism Date: Wed, 1 Jun 2022 16:06:46 +0300 Subject: [PATCH] Plugins list for MU4 --- share/plugins/Modal_Tuning.qml | 1613 +++++++++++++++++ share/plugins/NewRetrograde.qml | 110 ++ share/plugins/abc_import.qml | 146 -- share/plugins/addCourtesyAccidentals.qml | 315 ++++ share/plugins/configCourtesyAccidentals.qml | 659 +++++++ share/plugins/createscore.qml | 67 - share/plugins/helloqml/helloqml.qml | 74 - .../helloqml/translations/locale_de.qm | Bin 620 -> 0 bytes .../helloqml/translations/locale_de.ts | 42 - share/plugins/mirror-intervals-3.qml | 250 +++ share/plugins/notenames-interactive.qml | 377 ---- share/plugins/notenames.qml | 140 +- share/plugins/panel.qml | 55 - share/plugins/random.qml | 84 - share/plugins/random2.qml | 128 -- share/plugins/removeCourtesyAccidentals.qml | 314 ++++ share/plugins/run.qml | 46 - share/plugins/scorelist.qml | 60 - share/plugins/tuning.qml | 1481 +++++++++++++++ share/plugins/view.qml | 50 - share/plugins/walk.qml | 108 -- 21 files changed, 4811 insertions(+), 1308 deletions(-) create mode 100644 share/plugins/Modal_Tuning.qml create mode 100644 share/plugins/NewRetrograde.qml delete mode 100644 share/plugins/abc_import.qml create mode 100644 share/plugins/addCourtesyAccidentals.qml create mode 100644 share/plugins/configCourtesyAccidentals.qml delete mode 100644 share/plugins/createscore.qml delete mode 100644 share/plugins/helloqml/helloqml.qml delete mode 100644 share/plugins/helloqml/translations/locale_de.qm delete mode 100644 share/plugins/helloqml/translations/locale_de.ts create mode 100644 share/plugins/mirror-intervals-3.qml delete mode 100644 share/plugins/notenames-interactive.qml delete mode 100644 share/plugins/panel.qml delete mode 100644 share/plugins/random.qml delete mode 100644 share/plugins/random2.qml create mode 100644 share/plugins/removeCourtesyAccidentals.qml delete mode 100644 share/plugins/run.qml delete mode 100644 share/plugins/scorelist.qml create mode 100644 share/plugins/tuning.qml delete mode 100644 share/plugins/view.qml delete mode 100644 share/plugins/walk.qml diff --git a/share/plugins/Modal_Tuning.qml b/share/plugins/Modal_Tuning.qml new file mode 100644 index 0000000000000..794ac68a82c16 --- /dev/null +++ b/share/plugins/Modal_Tuning.qml @@ -0,0 +1,1613 @@ +// Apply a choice of tempraments and tunings. +// Copyright (C) 2018-2019 Bill Hails +// +// 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 . + +import MuseScore 3.0 +import QtQuick 2.2 +import QtQuick.Controls 1.1 +import QtQuick.Controls.Styles 1.3 +import QtQuick.Layouts 1.1 +import QtQuick.Dialogs 1.1 +import FileIO 3.0 + +MuseScore { + version: "3.3.2" + menuPath: "Plugins.Playback.Modal_Tuning" + description: "Apply various temperaments and tunings" + pluginType: "dialog" + width: 730 + height: 730 + + property var offsetTextWidth: 40; + property var offsetLabelAlignment: 0x02 | 0x40; + + property var history: 0; + + // set true if customisations are made to the tuning + property var modified: false; + + /** + * See http://leware.net/temper/temper.htm and specifically http://leware.net/temper/cents.htm + * + * I've taken the liberty of adding the Bach/Lehman temperament http://www.larips.com which was + * my original motivation for doing this. + * + * These values are in cents. One cent is defined as 100th of an equal tempered semitone. + * Each row is ordered in the cycle of fifths, so C, G, D, A, E, B, F#, C#, G#/Ab, Eb, Bb, F; + * and the values are offsets from the equal tempered value. + * + * However for tunings who's default root note is not C, the values are pre-rotated so that applying the + * root note rotation will put the first value of the sequence at the root note. + */ + property var equal: { + 'offsets': [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + 'root': 0, + 'pure': 0, + 'name': "equal" + } + property var tuning01: { + 'offsets': [0.0, 2.0, 3.9, 5.9, 7.8, 9.8, -9.8, -7.8, -7.8, -5.9, -3.9, -2.0], + 'root': 0, + 'pure': 0, + 'name': "tuning01" + } + property var tuning02: { + 'offsets': [0.0, 2.0, 3.9, -15.6, -13.7, -11.7, -9.8, 11.7, 13.7, 15.6, 17.6, -2.0], + 'root': 0, + 'pure': 0, + 'name': "tuning02" + } + property var tuning03: { + 'offsets': [0.0, 2.0, 3.9, 5.9, -35.2, -33.2, -9.8, -7.8, -7.8, -25.4, -3.9, -2.0], + 'root': 0, + 'pure': 0, + 'name': "tuning03" + } + property var tuning04: { + 'offsets': [0.0, 2.0, 3.9, 5.9, -35.2, -11.7, -9.8, -7.8, 13.7, -5.9, -3.9, -2.0], + 'root': 0, + 'pure': 0, + 'name': "tuning04" + } + property var tuning05: { + 'offsets': [0.0, 2.0, 3.9, -37.1, -35.2, 9.8, -9.8, -7.8, -7.8, -5.9, -3.9, -2.0], + 'root': 0, + 'pure': 0, + 'name': "tuning05" + } + property var tuning06: { + 'offsets': [0.0, 2.0, 3.9, 5.9, -56.7, -54.7, -9.8, -7.8, 13.7, -5.9, -3.9, -2.0], + 'root': 0, + 'pure': 0, + 'name': "tuning06" + } + property var tuning07: { + 'offsets': [0.0, 2.0, 3.9, 5.9, -56.7, -11.7, -9.8, -7.8, 13.7, -5.9, -3.9, -2.0], + 'root': 0, + 'pure': 0, + 'name': "tuning07" + } + property var tuning08: { + 'offsets': [0.0, 2.0, 3.9, -15.6, -13.7, -33.2, 9.8, 11.7, 13.7, 43.3, -3.9, -2.0], + 'root': 0, + 'pure': 0, + 'name': "tuning08" + } + property var tuning09: { + 'offsets': [0.0, 2.0, 3.9, 5.9, -13.7, -33.2, -9.8, -7.8, 13.7, 15.6, -3.9, -2.0], + 'root': 0, + 'pure': 0, + 'name': "tuning09" + } + property var tuning10: { + 'offsets': [0.0, 2.0, 3.9, 5.9, -13.7, -11.7, -9.8, 11.7, 13.7, 15.6, -3.9, -2.0], + 'root': 0, + 'pure': 0, + 'name': "tuning10" + } + property var tuning11: { + 'offsets': [0.0, 2.0, 3.9, 5.9, -13.7, -11.7, -9.8, -7.8, 13.7, 15.6, 17.6, -2.0], + 'root': 0, + 'pure': 0, + 'name': "tuning11" + } + property var tuning12: { + 'offsets': [0.0, 2.0, 3.9, 5.9, -13.7, -11.7, -9.8, 11.7, 13.7, -5.9, -3.9, -2.0], + 'root': 0, + 'pure': 0, + 'name': "tuning12" + } + property var tuning13: { + 'offsets': [0.0, 2.0, 3.9, 5.9, 7.8, 9.8, -9.8, -7.8, -7.8, -5.9, -3.9, -2.0], + 'root': 0, + 'pure': 0, + 'name': "tuning13" + } + property var tuning14: { + 'offsets': [0.0, 2.0, 3.9, 5.9, -56.7, -33.2, -31.3, -7.8, 13.7, -5.9, -3.9, -2.0], + 'root': 0, + 'pure': 0, + 'name': "tuning14" + } + property var tuning15: { + 'offsets': [0.0, 2.0, 3.9, 5.9, 7.8, -33.2, -31.3, -29.3, 13.7, -5.9, -3.9, -2.0], + 'root': 0, + 'pure': 0, + 'name': "tuning15" + } + property var tuning16: { + 'offsets': [0.0, 2.0, 3.9, -15.6, -35.2, -11.7, 9.8, 11.7, 13.7, -5.9, -3.9, -2.0], + 'root': 0, + 'pure': 0, + 'name': "tuning16" + } + property var tuning17: { + 'offsets': [0.0, 2.0, -17.6, -15.6, 7.8, 9.8, 9.8, 11.7, -7.8, -5.9, -3.9, -2.0], + 'root': 0, + 'pure': 0, + 'name': "tuning17" + } + property var tuning18: { + 'offsets': [0.0, 2.0, 3.9, 5.9, 7.8, 9.8, -9.8, -7.8, -7.8, 43.3, 17.6, 19.6], + 'root': 0, + 'pure': 0, + 'name': "tuning18" + } + property var tuning19: { + 'offsets': [0.0, 2.0, 3.9, 5.9, 7.8, 9.8, -31.3, -29.3, 13.7, -5.9, -3.9, -2.0], + 'root': 0, + 'pure': 0, + 'name': "tuning19" + } + property var tuning20: { + 'offsets': [0.0, 2.0, 3.9, 5.9, 7.8, -54.7, -31.3, -29.3, 13.7, -5.9, -3.9, -2.0], + 'root': 0, + 'pure': 0, + 'name': "tuning20" + } + property var tuning21: { + 'offsets': [0.0, 2.0, 3.9, -15.6, -13.7, -33.2, 9.8, 11.7, 13.7, -5.9, -3.9, -2.0], + 'root': 0, + 'pure': 0, + 'name': "tuning21" + } + property var tuning22: { + 'offsets': [0.0, 2.0, 3.9, -15.6, -13.7, -33.2, 58.9, 11.7, 13.7, 43.3, -3.9, -2.0], + 'root': 0, + 'pure': 0, + 'name': "tuning22" + } + property var tuning23: { + 'offsets': [0.0, 2.0, 3.9, 5.9, -35.2, -33.2, -9.8, 39.4, 41.4, -25.4, -3.9, -2.0], + 'root': 0, + 'pure': 0, + 'name': "tuning23" + } + property var tuning24: { + 'offsets': [0.0, 2.0, 3.9, 5.9, -56.7, -33.2, -31.3, -7.8, 13.7, -5.9, -23.5, -2.0], + 'root': 0, + 'pure': 0, + 'name': "tuning24" + } + + property var currentTemperament: equal; + property var currentRoot: 0; + property var currentPureTone: 0; + property var currentTweak: 0.0; + + onRun: { + if (!curScore) { + error("No score open.\nThis plugin requires an open score to run.\n") + Qt.quit() + } + } + + function getHistory() { + if (history == 0) { + history = new commandHistory() + } + return history + } + + function applyTemperament() + { + var selection = new scoreSelection() + curScore.startCmd() + selection.map(filterNotes, reTune(getFinalTuning())) + if (annotateValue.checkedState == Qt.Checked) { + selection.map(filterNotes, annotate) + } + curScore.endCmd() + return true + } + + function filterNotes(element) + { + return element.type == Ms.CHORD + } + + function annotate(chord, cursor) + { + for (var i = 0; i < chord.notes.length; i++) { + var note = chord.notes[i] + var text = newElement(Element.STAFF_TEXT); + text.text = '' + note.tuning + text.autoplace = true + text.fontSize = 7 // smaller + if (cursor.voice == 0 || cursor.voice == 2) { + text.placement = Placement.ABOVE + } else { + text.placement = Placement.BELOW + } + cursor.add(text) + } + } + + function reTune(tuning) { + return function(chord, cursor) { + for (var i = 0; i < chord.notes.length; i++) { + var note = chord.notes[i] + note.tuning = tuning(note.pitch) + } + } + } + + function scoreSelection() { + const SCORE_START = 0 + const SELECTION_START = 1 + const SELECTION_END = 2 + var fullScore + var startStaff + var endStaff + var endTick + var inRange + var rewind + var cursor = curScore.newCursor() + cursor.rewind(SELECTION_START) + if (cursor.segment) { + startStaff = cursor.staffIdx + cursor.rewind(SELECTION_END) + endStaff = cursor.staffIdx; + endTick = 0 // unused + if (cursor.tick === 0) { + endTick = curScore.lastSegment.tick + 1; + } else { + endTick = cursor.tick; + } + inRange = function() { + return cursor.segment && cursor.tick < endTick + } + rewind = function (voice, staff) { + // no idea why, but if there is a selection then + // we need to rewind the cursor *before* setting + // the voice and staff index. + cursor.rewind(SELECTION_START) + cursor.voice = voice + cursor.staffIdx = staff + } + } else { + startStaff = 0 + endStaff = curScore.nstaves - 1 + inRange = function () { + return cursor.segment + } + rewind = function (voice, staff) { + // no idea why, but if there's no selection then + // we need to rewind the cursor *after* setting + // the voice and staff index. + cursor.voice = voice + cursor.staffIdx = staff + cursor.rewind(SCORE_START) + } + } + + this.map = function(filter, process) { + for (var staff = startStaff; staff <= endStaff; staff++) { + for (var voice = 0; voice < 4; voice++) { + rewind(voice, staff) + while (inRange()) { + if (cursor.element && filter(cursor.element)) { + process(cursor.element, cursor) + } + cursor.next() + } + } + } + } + } + + function error(errorMessage) { + errorDialog.text = qsTr(errorMessage) + errorDialog.open() + } + + /** + * map a note (pitch modulo 12) to a value in one of the above tables + * then adjust for the choice of pure note and tweak. + */ + function lookUp(note, table) { + var i = ((note * 7) - currentRoot + 12) % 12; + var offset = table.offsets[i]; + var j = (currentPureTone - currentRoot + 12) % 12; + var pureNoteAdjustment = table.offsets[j]; + var finalOffset = offset - pureNoteAdjustment; + var tweakFinalOffset = finalOffset + parseFloat(tweakValue.text); + return tweakFinalOffset + } + + /** + * returns a function for use by recalculate() + * + * We use an abstract function here because recalculate can be passed + * a different function, i.e. when restoring from a save file. + */ + function getTuning() { + return function(pitch) { + return lookUp(pitch, currentTemperament); + } + } + + function getFinalTuning() { + return function(pitch) { + pitch = pitch % 12 + switch (pitch) { + case 0: + return getFinalOffset(final_c) + case 1: + return getFinalOffset(final_c_sharp) + case 2: + return getFinalOffset(final_d) + case 3: + return getFinalOffset(final_e_flat) + case 4: + return getFinalOffset(final_e) + case 5: + return getFinalOffset(final_f) + case 6: + return getFinalOffset(final_f_sharp) + case 7: + return getFinalOffset(final_g) + case 8: + return getFinalOffset(final_g_sharp) + case 9: + return getFinalOffset(final_a) + case 10: + return getFinalOffset(final_b_flat) + case 11: + return getFinalOffset(final_b) + default: + error("unrecognised pitch: " + pitch) + } + } + } + + function getFinalOffset(textField) { + return parseFloat(textField.text) + } + + function recalculate(tuning) { + var old_final_c = final_c.text + var old_final_c_sharp = final_c_sharp.text + var old_final_d = final_d.text + var old_final_e_flat = final_e_flat.text + var old_final_e = final_e.text + var old_final_f = final_f.text + var old_final_f_sharp = final_f_sharp.text + var old_final_g = final_g.text + var old_final_g_sharp = final_g_sharp.text + var old_final_a = final_a.text + var old_final_b_flat = final_b_flat.text + var old_final_b = final_b.text + getHistory().add( + function () { + final_c.text = old_final_c + final_c.previousText = old_final_c + final_c_sharp.text = old_final_c_sharp + final_c_sharp.previousText = old_final_c_sharp + final_d.text = old_final_d + final_d.previousText = old_final_d + final_e_flat.text = old_final_e_flat + final_e_flat.previousText = old_final_e_flat + final_e.text = old_final_e + final_e.previousText = old_final_e + final_f.text = old_final_f + final_f.previousText = old_final_f + final_f_sharp.text = old_final_f_sharp + final_f_sharp.previousText = old_final_f_sharp + final_g.text = old_final_g + final_g.previousText = old_final_g + final_g_sharp.text = old_final_g_sharp + final_g_sharp.previousText = old_final_g_sharp + final_a.text = old_final_a + final_a.previousText = old_final_a + final_b_flat.text = old_final_b_flat + final_b_flat.previousText = old_final_b_flat + final_b.text = old_final_b + final_b.previousText = old_final_b + }, + function() { + final_c.text = tuning(0).toFixed(1) + final_c.previousText = final_c.text + final_c_sharp.text = tuning(1).toFixed(1) + final_c_sharp.previousText = final_c_sharp.text + final_d.text = tuning(2).toFixed(1) + final_d.previousText = final_d.text + final_e_flat.text = tuning(3).toFixed(1) + final_e_flat.previousText = final_e_flat.text + final_e.text = tuning(4).toFixed(1) + final_e.previousText = final_e.text + final_f.text = tuning(5).toFixed(1) + final_f.previousText = final_f.text + final_f_sharp.text = tuning(6).toFixed(1) + final_f_sharp.previousText = final_f_sharp.text + final_g.text = tuning(7).toFixed(1) + final_g.previousText = final_g.text + final_g_sharp.text = tuning(8).toFixed(1) + final_g_sharp.previousText = final_g_sharp.text + final_a.text = tuning(9).toFixed(1) + final_a.previousText = final_a.text + final_b_flat.text = tuning(10).toFixed(1) + final_b_flat.previousText = final_b_flat.text + final_b.text = tuning(11).toFixed(1) + final_b.previousText = final_b.text + }, + "final offsets" + ) + } + + function setCurrentTemperament(temperament) { + var oldTemperament = currentTemperament + getHistory().add( + function() { + currentTemperament = oldTemperament + checkCurrentTemperament() + }, + function() { + currentTemperament = temperament + checkCurrentTemperament() + }, + "current temperament" + ) + } + + function checkCurrentTemperament() { + switch (currentTemperament.name) { + case "equal": + equal_button.checked = true + return + case "tuning01": + tuning01_button.checked = true + return + case "tuning02": + tuning02_button.checked = true + return + case "tuning03": + tuning03_button.checked = true + return + case "tuning04": + tuning04_button.checked = true + return + case "tuning05": + tuning05_button.checked = true + return + case "tuning06": + tuning06_button.checked = true + return + case "tuning07": + tuning07_button.checked = true + return + case "tuning08": + tuning08_button.checked = true + return + case "tuning09": + tuning09_button.checked = true + return + case "tuning10": + tuning10_button.checked = true + return + case "tuning11": + tuning11_button.checked = true + return + case "tuning12": + tuning12_button.checked = true + return + case "tuning13": + tuning13_button.checked = true + return + case "tuning14": + tuning14_button.checked = true + return + case "tuning15": + tuning15_button.checked = true + return + case "tuning16": + tuning16_button.checked = true + return + case "tuning17": + tuning17_button.checked = true + return + case "tuning18": + tuning18_button.checked = true + return + case "tuning19": + tuning19_button.checked = true + return + case "tuning20": + tuning20_button.checked = true + return + case "tuning21": + tuning21_button.checked = true + return + case "tuning22": + tuning22_button.checked = true + return + case "tuning23": + tuning23_button.checked = true + return + case "tuning24": + tuning24_button.checked = true + return + + } + } + + function lookupTemperament(temperamentName) { + switch (temperamentName) { + case "equal": + return equal + case "tuning01": + return tuning01 + case "tuning02": + return tuning02 + case "tuning03": + return tuning03 + case "tuning04": + return tuning04 + case "tuning05": + return tuning05 + case "tuning06": + return tuning06 + case "tuning07": + return tuning07 + case "tuning08": + return tuning08 + case "tuning09": + return tuning09 + case "tuning10": + return tuning10 + case "tuning11": + return tuning11 + case "tuning12": + return tuning12 + case "tuning13": + return tuning13 + case "tuning14": + return tuning14 + case "tuning15": + return tuning15 + case "tuning16": + return tuning16 + case "tuning17": + return tuning17 + case "tuning18": + return tuning18 + case "tuning19": + return tuning19 + case "tuning20": + return tuning20 + case "tuning21": + return tuning21 + case "tuning22": + return tuning22 + case "tuning23": + return tuning23 + case "tuning24": + return tuning24 + + } + } + + function setCurrentRoot(root) { + var oldRoot = currentRoot + getHistory().add( + function () { + currentRoot = oldRoot + checkCurrentRoot() + }, + function() { + currentRoot = root + checkCurrentRoot() + }, + "current root" + ) + } + + function checkCurrentRoot() { + switch (currentRoot) { + case 0: + root_c.checked = true + break + case 1: + root_g.checked = true + break + case 2: + root_d.checked = true + break + case 3: + root_a.checked = true + break + case 4: + root_e.checked = true + break + case 5: + root_b.checked = true + break + case 6: + root_f_sharp.checked = true + break + case 7: + root_c_sharp.checked = true + break + case 8: + root_g_sharp.checked = true + break + case 9: + root_e_flat.checked = true + break + case 10: + root_b_flat.checked = true + break + case 11: + root_f.checked = true + break + } + } + + function setCurrentPureTone(pureTone) { + var oldPureTone = currentPureTone + getHistory().add( + function () { + currentPureTone = oldPureTone + checkCurrentPureTone() + }, + function() { + currentPureTone = pureTone + checkCurrentPureTone() + }, + "current pure tone" + ) + } + + function setCurrentTweak(tweak) { + var oldTweak = currentTweak + getHistory().add( + function () { + currentTweak = oldTweak + checkCurrentTweak() + }, + function () { + currentTweak = tweak + checkCurrentTweak() + }, + "current tweak" + ) + } + + function checkCurrentTweak() { + tweakValue.text = currentTweak.toFixed(1) + } + + function checkCurrentPureTone() { + switch (currentPureTone) { + case 0: + pure_c.checked = true + break + case 1: + pure_g.checked = true + break + case 2: + pure_d.checked = true + break + case 3: + pure_a.checked = true + break + case 4: + pure_e.checked = true + break + case 5: + pure_b.checked = true + break + case 6: + pure_f_sharp.checked = true + break + case 7: + pure_c_sharp.checked = true + break + case 8: + pure_g_sharp.checked = true + break + case 9: + pure_e_flat.checked = true + break + case 10: + pure_b_flat.checked = true + break + case 11: + pure_f.checked = true + break + } + } + + function setModified(state) { + var oldModified = modified + getHistory().add( + function () { + modified = oldModified + }, + function () { + modified = state + }, + "modified" + ) + } + + function temperamentClicked(temperament) { + getHistory().begin() + setCurrentTemperament(temperament) + setCurrentRoot(currentTemperament.root) + setCurrentPureTone(currentTemperament.pure) + setCurrentTweak(0.0) + recalculate(getTuning()) + getHistory().end() + } + + function rootNoteClicked(note) { + getHistory().begin() + setModified(true) + setCurrentRoot(note) + setCurrentPureTone(note) + setCurrentTweak(0.0) + recalculate(getTuning()) + getHistory().end() + } + + function pureToneClicked(note) { + getHistory().begin() + setModified(true) + setCurrentPureTone(note) + setCurrentTweak(0.0) + recalculate(getTuning()) + getHistory().end() + } + + function tweaked() { + getHistory().begin() + setModified(true) + setCurrentTweak(parseFloat(tweakValue.text)) + recalculate(getTuning()) + getHistory().end() + } + + function editingFinishedFor(textField) { + var oldText = textField.previousText + var newText = textField.text + getHistory().begin() + setModified(true) + getHistory().add( + function () { + textField.text = oldText + }, + function () { + textField.text = newText + }, + "edit ".concat(textField.name) + ) + getHistory().end() + textField.previousText = newText + } + + Rectangle { + color: "lightgrey" + anchors.fill: parent + + GridLayout { + columns: 2 + anchors.fill: parent + anchors.margins: 10 + GroupBox { + title: "Tuning" + ColumnLayout { + ExclusiveGroup { id: tempamentTypeGroup } + RadioButton { + id: equal_button + text: "Equal" + checked: true + exclusiveGroup: tempamentTypeGroup + onClicked: { temperamentClicked(equal) } + } + RadioButton { + id: tuning01_button + text: "Melodic 1# 2b" + exclusiveGroup: tempamentTypeGroup + onClicked: { temperamentClicked(tuning01) } + } + RadioButton { + id: tuning02_button + text: "Harmonic 1# 2b" + exclusiveGroup: tempamentTypeGroup + onClicked: { temperamentClicked(tuning02) } + } + RadioButton { + id: tuning03_button + text: "Rast, Sikah" + exclusiveGroup: tempamentTypeGroup + onClicked: { temperamentClicked(tuning03) } + } + RadioButton { + id: tuning04_button + text: "Suznak, Huzam" + exclusiveGroup: tempamentTypeGroup + onClicked: { temperamentClicked(tuning04) } + } + RadioButton { + id: tuning05_button + text: "Nayruz" + exclusiveGroup: tempamentTypeGroup + onClicked: { temperamentClicked(tuning05) } + } + RadioButton { + id: tuning06_button + text: "Bayati, Kurd, Huseyni" + exclusiveGroup: tempamentTypeGroup + onClicked: { temperamentClicked(tuning06) } + } + RadioButton { + id: tuning07_button + text: "Qarjighar" + exclusiveGroup: tempamentTypeGroup + onClicked: { temperamentClicked(tuning07) } + } + RadioButton { + id: tuning08_button + text: "Saba, Basta Nikar, Zanjaran" + exclusiveGroup: tempamentTypeGroup + onClicked: { temperamentClicked(tuning08) } + } + RadioButton { + id: tuning09_button + text: "Hijaz, Nikriz" + exclusiveGroup: tempamentTypeGroup + onClicked: { temperamentClicked(tuning09) } + } + RadioButton { + id: tuning10_button + text: "Nawa'athar, Shad Araban" + exclusiveGroup: tempamentTypeGroup + onClicked: { temperamentClicked(tuning10) } + } + RadioButton { + id: tuning11_button + text: "Shehnaz" + exclusiveGroup: tempamentTypeGroup + onClicked: { temperamentClicked(tuning11) } + } + RadioButton { + id: tuning12_button + text: "Nahawand, Hijaz Kar" + exclusiveGroup: tempamentTypeGroup + onClicked: { temperamentClicked(tuning12) } + } + RadioButton { + id: tuning13_button + text: "Nahawand, Hijaz Kar Kurd" + exclusiveGroup: tempamentTypeGroup + onClicked: { temperamentClicked(tuning13) } + } + RadioButton { + id: tuning14_button + text: "Iraq, Yekah, Nawa" + exclusiveGroup: tempamentTypeGroup + onClicked: { temperamentClicked(tuning14) } + } + RadioButton { + id: tuning15_button + text: "Farahnak, Yekah, Nawa" + exclusiveGroup: tempamentTypeGroup + onClicked: { temperamentClicked(tuning15) } + } + RadioButton { + id: tuning16_button + text: "Jiharkah" + exclusiveGroup: tempamentTypeGroup + onClicked: { temperamentClicked(tuning16) } + } + RadioButton { + id: tuning17_button + text: "Ajam Ashyran, Shawq Afza" + exclusiveGroup: tempamentTypeGroup + onClicked: { temperamentClicked(tuning17) } + } + RadioButton { + id: tuning18_button + text: "Hisar" + exclusiveGroup: tempamentTypeGroup + onClicked: { temperamentClicked(tuning18) } + } + RadioButton { + id: tuning19_button + text: "Nishaburek (Rast in D & A)" + exclusiveGroup: tempamentTypeGroup + onClicked: { temperamentClicked(tuning19) } + } + RadioButton { + id: tuning20_button + text: "Nishaburek (Rast in D, Bayati in A)" + exclusiveGroup: tempamentTypeGroup + onClicked: { temperamentClicked(tuning20) } + } + RadioButton { + id: tuning21_button + text: "Saba Zamzam" + exclusiveGroup: tempamentTypeGroup + onClicked: { temperamentClicked(tuning21) } + } + RadioButton { + id: tuning22_button + text: "Rakb" + exclusiveGroup: tempamentTypeGroup + onClicked: { temperamentClicked(tuning22) } + } + RadioButton { + id: tuning23_button + text: "Sikah Baladi" + exclusiveGroup: tempamentTypeGroup + onClicked: { temperamentClicked(tuning23) } + } + RadioButton { + id: tuning24_button + text: "Iraq (Cadence)" + exclusiveGroup: tempamentTypeGroup + onClicked: { temperamentClicked(tuning24) } + } + } + } + + ColumnLayout { + GroupBox { + title: "Advanced" + ColumnLayout { + GroupBox { + title: "Root Note" + GridLayout { + columns: 4 + anchors.margins: 10 + ExclusiveGroup { id: rootNoteGroup } + RadioButton { + text: "C" + checked: true + exclusiveGroup: rootNoteGroup + id: root_c + onClicked: { rootNoteClicked(0) } + } + RadioButton { + text: "G" + exclusiveGroup: rootNoteGroup + id: root_g + onClicked: { rootNoteClicked(1) } + } + RadioButton { + text: "D" + exclusiveGroup: rootNoteGroup + id: root_d + onClicked: { rootNoteClicked(2) } + } + RadioButton { + text: "A" + exclusiveGroup: rootNoteGroup + id: root_a + onClicked: { rootNoteClicked(3) } + } + RadioButton { + text: "E" + exclusiveGroup: rootNoteGroup + id: root_e + onClicked: { rootNoteClicked(4) } + } + RadioButton { + text: "B" + exclusiveGroup: rootNoteGroup + id: root_b + onClicked: { rootNoteClicked(5) } + } + RadioButton { + text: "F#" + exclusiveGroup: rootNoteGroup + id: root_f_sharp + onClicked: { rootNoteClicked(6) } + } + RadioButton { + text: "C#" + exclusiveGroup: rootNoteGroup + id: root_c_sharp + onClicked: { rootNoteClicked(7) } + } + RadioButton { + text: "G#" + exclusiveGroup: rootNoteGroup + id: root_g_sharp + onClicked: { rootNoteClicked(8) } + } + RadioButton { + text: "Eb" + exclusiveGroup: rootNoteGroup + id: root_e_flat + onClicked: { rootNoteClicked(9) } + } + RadioButton { + text: "Bb" + exclusiveGroup: rootNoteGroup + id: root_b_flat + onClicked: { rootNoteClicked(10) } + } + RadioButton { + text: "F" + exclusiveGroup: rootNoteGroup + id: root_f + onClicked: { rootNoteClicked(11) } + } + } + } + + GroupBox { + title: "Pure Tone" + GridLayout { + columns: 4 + anchors.margins: 10 + ExclusiveGroup { id: pureToneGroup } + RadioButton { + text: "C" + checked: true + id: pure_c + exclusiveGroup: pureToneGroup + onClicked: { pureToneClicked(0) } + } + RadioButton { + text: "G" + id: pure_g + exclusiveGroup: pureToneGroup + onClicked: { pureToneClicked(1) } + } + RadioButton { + text: "D" + id: pure_d + exclusiveGroup: pureToneGroup + onClicked: { pureToneClicked(2) } + } + RadioButton { + text: "A" + id: pure_a + exclusiveGroup: pureToneGroup + onClicked: { pureToneClicked(3) } + } + RadioButton { + text: "E" + id: pure_e + exclusiveGroup: pureToneGroup + onClicked: { pureToneClicked(4) } + } + RadioButton { + text: "B" + id: pure_b + exclusiveGroup: pureToneGroup + onClicked: { pureToneClicked(5) } + } + RadioButton { + text: "F#" + id: pure_f_sharp + exclusiveGroup: pureToneGroup + onClicked: { pureToneClicked(6) } + } + RadioButton { + text: "C#" + id: pure_c_sharp + exclusiveGroup: pureToneGroup + onClicked: { pureToneClicked(7) } + } + RadioButton { + text: "G#" + id: pure_g_sharp + exclusiveGroup: pureToneGroup + onClicked: { pureToneClicked(8) } + } + RadioButton { + text: "Eb" + id: pure_e_flat + exclusiveGroup: pureToneGroup + onClicked: { pureToneClicked(9) } + } + RadioButton { + text: "Bb" + id: pure_b_flat + exclusiveGroup: pureToneGroup + onClicked: { pureToneClicked(10) } + } + RadioButton { + text: "F" + id: pure_f + exclusiveGroup: pureToneGroup + onClicked: { pureToneClicked(11) } + } + } + } + + GroupBox { + title: "Tweak" + RowLayout { + TextField { + Layout.maximumWidth: offsetTextWidth + id: tweakValue + text: "0.0" + readOnly: false + validator: DoubleValidator { bottom: -99.9; decimals: 1; notation: DoubleValidator.StandardNotation; top: 99.9 } + property var previousText: "0.0" + property var name: "tweak" + onEditingFinished: { tweaked() } + } + } + } + + GroupBox { + title: "Final Offsets" + GridLayout { + columns: 8 + anchors.margins: 0 + + Label { + text: "C" + Layout.alignment: offsetLabelAlignment + } + TextField { + Layout.maximumWidth: offsetTextWidth + id: final_c + text: "0.0" + readOnly: false + validator: DoubleValidator { bottom: -99.9; decimals: 1; notation: DoubleValidator.StandardNotation; top: 99.9 } + property var previousText: "0.0" + property var name: "final C" + onEditingFinished: { editingFinishedFor(final_c) } + } + + Label { + text: "G" + Layout.alignment: offsetLabelAlignment + } + TextField { + Layout.maximumWidth: offsetTextWidth + id: final_g + text: "0.0" + readOnly: false + validator: DoubleValidator { bottom: -99.9; decimals: 1; notation: DoubleValidator.StandardNotation; top: 99.9 } + property var previousText: "0.0" + property var name: "final G" + onEditingFinished: { editingFinishedFor(final_g) } + } + + Label { + text: "D" + Layout.alignment: offsetLabelAlignment + } + TextField { + Layout.maximumWidth: offsetTextWidth + id: final_d + text: "0.0" + readOnly: false + validator: DoubleValidator { bottom: -99.9; decimals: 1; notation: DoubleValidator.StandardNotation; top: 99.9 } + property var previousText: "0.0" + property var name: "final D" + onEditingFinished: { editingFinishedFor(final_d) } + } + + Label { + text: "A" + Layout.alignment: offsetLabelAlignment + } + TextField { + Layout.maximumWidth: offsetTextWidth + id: final_a + text: "0.0" + readOnly: false + validator: DoubleValidator { bottom: -99.9; decimals: 1; notation: DoubleValidator.StandardNotation; top: 99.9 } + property var previousText: "0.0" + property var name: "final A" + onEditingFinished: { editingFinishedFor(final_a) } + } + + Label { + text: "E" + Layout.alignment: offsetLabelAlignment + } + TextField { + Layout.maximumWidth: offsetTextWidth + id: final_e + text: "0.0" + readOnly: false + validator: DoubleValidator { bottom: -99.9; decimals: 1; notation: DoubleValidator.StandardNotation; top: 99.9 } + property var previousText: "0.0" + property var name: "final E" + onEditingFinished: { editingFinishedFor(final_e) } + } + + Label { + text: "B" + Layout.alignment: offsetLabelAlignment + } + TextField { + Layout.maximumWidth: offsetTextWidth + id: final_b + text: "0.0" + readOnly: false + validator: DoubleValidator { bottom: -99.9; decimals: 1; notation: DoubleValidator.StandardNotation; top: 99.9 } + property var previousText: "0.0" + property var name: "final B" + onEditingFinished: { editingFinishedFor(final_b) } + } + + Label { + text: "F#" + Layout.alignment: offsetLabelAlignment + } + TextField { + Layout.maximumWidth: offsetTextWidth + id: final_f_sharp + text: "0.0" + readOnly: false + validator: DoubleValidator { bottom: -99.9; decimals: 1; notation: DoubleValidator.StandardNotation; top: 99.9 } + property var previousText: "0.0" + property var name: "final F#" + onEditingFinished: { editingFinishedFor(final_f_sharp) } + } + + Label { + text: "C#" + Layout.alignment: offsetLabelAlignment + } + TextField { + Layout.maximumWidth: offsetTextWidth + id: final_c_sharp + text: "0.0" + readOnly: false + validator: DoubleValidator { bottom: -99.9; decimals: 1; notation: DoubleValidator.StandardNotation; top: 99.9 } + property var previousText: "0.0" + property var name: "final C#" + onEditingFinished: { editingFinishedFor(final_c_sharp) } + } + + Label { + text: "G#" + Layout.alignment: offsetLabelAlignment + } + TextField { + Layout.maximumWidth: offsetTextWidth + id: final_g_sharp + text: "0.0" + readOnly: false + validator: DoubleValidator { bottom: -99.9; decimals: 1; notation: DoubleValidator.StandardNotation; top: 99.9 } + property var previousText: "0.0" + property var name: "final G#" + onEditingFinished: { editingFinishedFor(final_g_sharp) } + } + + Label { + text: "Eb" + Layout.alignment: offsetLabelAlignment + } + TextField { + Layout.maximumWidth: offsetTextWidth + id: final_e_flat + text: "0.0" + readOnly: false + validator: DoubleValidator { bottom: -99.9; decimals: 1; notation: DoubleValidator.StandardNotation; top: 99.9 } + property var previousText: "0.0" + property var name: "final Eb" + onEditingFinished: { editingFinishedFor(final_e_flat) } + } + + Label { + text: "Bb" + Layout.alignment: offsetLabelAlignment + } + TextField { + Layout.maximumWidth: offsetTextWidth + id: final_b_flat + text: "0.0" + readOnly: false + validator: DoubleValidator { bottom: -99.9; decimals: 1; notation: DoubleValidator.StandardNotation; top: 99.9 } + property var previousText: "0.0" + property var name: "final Bb" + onEditingFinished: { editingFinishedFor(final_b_flat) } + } + + Label { + text: "F" + Layout.alignment: offsetLabelAlignment + } + TextField { + Layout.maximumWidth: offsetTextWidth + id: final_f + text: "0.0" + readOnly: false + validator: DoubleValidator { bottom: -99.9; decimals: 1; notation: DoubleValidator.StandardNotation; top: 99.9 } + property var previousText: "0.0" + property var name: "final F" + onEditingFinished: { editingFinishedFor(final_f) } + } + } + } + RowLayout { + Button { + id: saveButton + text: qsTranslate("PrefsDialogBase", "Save") + onClicked: { + // declaring this directly in the saveDialog's properties doesn't seem to work + saveDialog.folder = Qt.resolvedUrl("file://" + filePath) + saveDialog.visible = true + } + } + Button { + id: loadButton + text: qsTranslate("PrefsDialogBase", "Load") + onClicked: { + loadDialog.folder = Qt.resolvedUrl("file://" + filePath) + loadDialog.visible = true + } + } + Button { + id: undoButton + text: qsTranslate("PrefsDialogBase", "Undo") + onClicked: { + getHistory().undo() + } + } + Button { + id: redoButton + text: qsTranslate("PrefsDialogBase", "Redo") + onClicked: { + getHistory().redo() + } + } + } + } + } + + RowLayout { + Button { + id: applyButton + text: qsTranslate("PrefsDialogBase", "Apply") + onClicked: { + if (applyTemperament()) { + if (modified) { + quitDialog.open() + } else { + Qt.quit() + } + } + } + } + Button { + id: cancelButton + text: qsTranslate("PrefsDialogBase", "Cancel") + onClicked: { + if (modified) { + quitDialog.open() + } else { + Qt.quit() + } + } + } + CheckBox { + id: annotateValue + text: qsTr("Annotate") + checked: false + } + } + } + } + } + + MessageDialog { + id: errorDialog + title: "Error" + text: "" + onAccepted: { + errorDialog.close() + } + } + + MessageDialog { + id: quitDialog + title: "Quit?" + text: "Do you want to quit the plugin?" + detailedText: "It looks like you have made customisations to this tuning, you could save them to a file before quitting if you like." + standardButtons: StandardButton.Ok | StandardButton.Cancel + onAccepted: { + Qt.quit() + } + onRejected: { + quitDialog.close() + } + } + + FileIO { + id: saveFile + source: "" + } + + FileIO { + id: loadFile + source: "" + } + + function getFile(dialog) { + var source = dialog.fileUrl.toString().substring(7) // strip the 'file://' prefix + return source + } + + function formatCurrentValues() { + var data = { + offsets: [ + parseFloat(final_c.text), + parseFloat(final_c_sharp.text), + parseFloat(final_d.text), + parseFloat(final_e_flat.text), + parseFloat(final_e.text), + parseFloat(final_f.text), + parseFloat(final_f_sharp.text), + parseFloat(final_g.text), + parseFloat(final_g_sharp.text), + parseFloat(final_a.text), + parseFloat(final_b_flat.text), + parseFloat(final_b.text) + ], + temperament: currentTemperament.name, + root: currentRoot, + pure: currentPureTone, + tweak: currentTweak + }; + return(JSON.stringify(data)) + } + + function restoreSavedValues(data) { + getHistory().begin() + setCurrentTemperament(lookupTemperament(data.temperament)) + setCurrentRoot(data.root) + setCurrentPureTone(data.pure) + // support older save files + if (data.hasOwnProperty('tweak')) { + setCurrentTweak(data.tweak) + } else { + setCurrentTweak(0.0) + } + recalculate( + function(pitch) { + return data.offsets[pitch % 12] + } + ) + getHistory().end() + } + + FileDialog { + id: loadDialog + title: "Please choose a file" + sidebarVisible: true + onAccepted: { + loadFile.source = getFile(loadDialog) + var data = JSON.parse(loadFile.read()) + restoreSavedValues(data) + loadDialog.visible = false + } + onRejected: { + loadDialog.visible = false + } + visible: false + } + + FileDialog { + id: saveDialog + title: "Please name a file" + sidebarVisible: true + selectExisting: false + onAccepted: { + saveFile.source = getFile(saveDialog) + saveFile.write(formatCurrentValues()) + saveDialog.visible = false + } + onRejected: { + saveDialog.visible = false + } + visible: false + } + + // Command pattern for undo/redo + function commandHistory() { + function Command(undo_fn, redo_fn, label) { + this.undo = undo_fn + this.redo = redo_fn + this.label = label // for debugging + } + + var history = [] + var index = -1 + var transaction = 0 + var maxHistory = 30 + + function newHistory(commands) { + if (index < maxHistory) { + index++ + history = history.slice(0, index) + } else { + history = history.slice(1, index) + } + history.push(commands) + } + + this.add = function(undo, redo, label) { + var command = new Command(undo, redo, label) + command.redo() + if (transaction) { + history[index].push(command) + } else { + newHistory([command]) + } + } + + this.undo = function() { + if (index != -1) { + history[index].slice().reverse().forEach( + function(command) { + command.undo() + } + ) + index-- + } + } + + this.redo = function() { + if ((index + 1) < history.length) { + index++ + history[index].forEach( + function(command) { + command.redo() + } + ) + } + } + + this.begin = function() { + if (transaction) { + throw new Error("already in transaction") + } + newHistory([]) + transaction = 1 + } + + this.end = function() { + if (!transaction) { + throw new Error("not in transaction") + } + transaction = 0 + } + } +} +// vim: ft=javascript diff --git a/share/plugins/NewRetrograde.qml b/share/plugins/NewRetrograde.qml new file mode 100644 index 0000000000000..9863c454706ea --- /dev/null +++ b/share/plugins/NewRetrograde.qml @@ -0,0 +1,110 @@ +//=========================================================================== +// New Retrograde +// https://github.com/ellejohara/newretrograde +// +// Copyright (C) 2020 Astrid Lydia Johannsen (ellejohara) +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 3 +// as published by the Free Software Foundation and appearing in +// the file LICENSE +//=========================================================================== + +import QtQuick 2.0 +import MuseScore 3.0 + +MuseScore { + menuPath: "Plugins.NewRetrograde" + description: "Takes a selection of notes and reverses them." + version: "1.0" + + function retrogradeSelection() { + var cursor = curScore.newCursor(); // get the selection + cursor.rewind(2); // go to the end of the selection + + if(!cursor.segment) { // if nothing selected + console.log('nothing selected'); // say "nothing selected" + Qt.quit(); // then quit + } else { + var endTick = cursor.tick; // mark the selection end tick + cursor.rewind(1); // go to the beginning of the selection + var startTick = cursor.tick; // mark the selection start tick + } + //console.log(startTick + ' - ' + endTick); // display selection start and end ticks + + var noteArray = []; // create a blank array + + + while(cursor.segment && cursor.tick < endTick) { // while in the selection + var e = cursor.element; // put current element into variable e + if(e) { // if e exists + if(e.type == Element.CHORD) { // if e is a note or chord + var pitches = []; // put the note pitches of each chord into an array + var notes = e.notes; + for(var i = 0; i < notes.length; i++) { // iterate through each note in chord + var note = notes[i]; // get the note pitch number + pitches.push(note.pitch); // put pitch number into variable + } + } + + if(e.type == Element.REST) { // if e is a rest + var pitches = 'REST'; // "REST" as pitch + } + + var numer = e.duration.numerator; // numerator of duration + var denom = e.duration.denominator; // denominator of duration + + noteArray.push([pitches, numer, denom]); + } + cursor.next(); // move to next tick + } + + noteArray.reverse(); // this does the retrograde (reverse array) + cursor.rewind(1); // go back to beginning of selection + + // this section rewrites the selection with the reversed array + for(var i = 0; i < noteArray.length; i++) { + var noteDur = noteArray[i]; + var pitches = noteDur[0]; // get note and chord pitches + var numer = noteDur[1]; // duration numerator + var denom = noteDur[2]; // duration denominator + + // set the duration + cursor.setDuration(numer, denom); + + // if there is only a single note + if(pitches.length == 1) { + cursor.addNote(pitches[0]); // add note + } + + // if there is a chord or rest + if(pitches.length > 1) { + + // if rest + if(pitches === 'REST') { + cursor.addRest () // add rest + } else { + + // if chord + for(var j = 0; j < pitches.length; j++) { + var pitch = pitches[j]; + if(j == 0) { // loop through each pitch of chord + // write the root note in a new cursor position + cursor.addNote(pitch); // root of chord + } else { + // write the notes to the same cursor position + cursor.addNote(pitch, cursor); // remainder of notes in chord + } + } + } + } + + } // end for + } + + + onRun: { + retrogradeSelection(); + Qt.quit() + } +} diff --git a/share/plugins/abc_import.qml b/share/plugins/abc_import.qml deleted file mode 100644 index 590e38bda5ae0..0000000000000 --- a/share/plugins/abc_import.qml +++ /dev/null @@ -1,146 +0,0 @@ -/* - * SPDX-License-Identifier: GPL-3.0-only - * MuseScore-CLA-applies - * - * MuseScore - * Music Composition & Notation - * - * Copyright (C) 2021 MuseScore BVBA and others - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * 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 . - */ - -import QtQuick 2.1 -import QtQuick.Dialogs 1.0 -import QtQuick.Controls 1.0 -import MuseScore 3.0 -import FileIO 3.0 - -MuseScore { - menuPath: "Plugins.ABC Import" - version: "3.0" - description: qsTr("This plugin imports ABC text from a file or the clipboard. Internet connection is required.") - requiresScore: false - pluginType: "dialog" - - id:window - width: 800; height: 500; - onRun: {} - - FileIO { - id: myFileAbc - onError: console.log(msg + " Filename = " + myFileAbc.source) - } - - FileIO { - id: myFile - source: tempPath() + "/my_file.xml" - onError: console.log(msg) - } - - FileDialog { - id: fileDialog - title: qsTr("Please choose a file") - onAccepted: { - var filename = fileDialog.fileUrl - //console.log("You chose: " + filename) - - if(filename){ - myFileAbc.source = filename - //read abc file and put it in the TextArea - abcText.text = myFileAbc.read() - } - } - } - - Label { - id: textLabel - wrapMode: Text.WordWrap - text: qsTr("Paste your ABC tune here (or click button to load a file)\nYou need to be connected to internet for this plugin to work") - font.pointSize:12 - anchors.left: window.left - anchors.top: window.top - anchors.leftMargin: 10 - anchors.topMargin: 10 - } - - // Where people can paste their ABC tune or where an ABC file is put when opened - TextArea { - id:abcText - anchors.top: textLabel.bottom - anchors.left: window.left - anchors.right: window.right - anchors.bottom: buttonOpenFile.top - anchors.topMargin: 10 - anchors.bottomMargin: 10 - anchors.leftMargin: 10 - anchors.rightMargin: 10 - width:parent.width - height:400 - wrapMode: TextEdit.WrapAnywhere - textFormat: TextEdit.PlainText - } - - Button { - id : buttonOpenFile - text: qsTr("Open file") - anchors.bottom: window.bottom - anchors.left: abcText.left - anchors.topMargin: 10 - anchors.bottomMargin: 10 - anchors.leftMargin: 10 - onClicked: { - fileDialog.open(); - } - } - - Button { - id : buttonConvert - text: qsTr("Import") - anchors.bottom: window.bottom - anchors.right: abcText.right - anchors.topMargin: 10 - anchors.bottomMargin: 10 - anchors.rightMargin: 10 - onClicked: { - var content = "content=" + encodeURIComponent(abcText.text) - //console.log("content : " + content) - - var request = new XMLHttpRequest() - request.onreadystatechange = function() { - if (request.readyState == XMLHttpRequest.DONE) { - var response = request.responseText - //console.log("responseText : " + response) - myFile.write(response) - readScore(myFile.source) - Qt.quit() - } - } - request.open("POST", "https://abc2xml.appspot.com/abcrenderer", true) - request.setRequestHeader("Content-Type", "application/x-www-form-urlencoded") - request.send(content) - } - } - - Button { - id : buttonCancel - text: qsTr("Cancel") - anchors.bottom: window.bottom - anchors.right: buttonConvert.left - anchors.topMargin: 10 - anchors.bottomMargin: 10 - onClicked: { - Qt.quit(); - } - } - } diff --git a/share/plugins/addCourtesyAccidentals.qml b/share/plugins/addCourtesyAccidentals.qml new file mode 100644 index 0000000000000..b3338abf3b00c --- /dev/null +++ b/share/plugins/addCourtesyAccidentals.qml @@ -0,0 +1,315 @@ +//============================================== +// add courtesy accidentals v1.0 +// +// Copyright (C)2012-2019 Jörn Eichler (heuchi) +// +// 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 . +//============================================== + +import QtQuick 2.0 +import QtQuick.Dialogs 1.2 +import MuseScore 3.0 + +MuseScore { + version: "1.0" + description: "This plugin adds courtesy accidentals" + menuPath: "Plugins.Accidentals.Add Courtesy Accidentals" + requiresScore: true + + // configuration + // This has changed for MuseScore v3 + // 0 = no bracket, 1 = parenthesis, 2 = bracket + property int useBracket: 0 + + // if nothing is selected process whole score + property bool processAll: false + + MessageDialog { + id: versionError + visible: false + title: "Unsupported MuseScore Version" + text: "This plugin needs MuseScore v3.0.5 or higher" + onAccepted: { Qt.quit() } + } + + // function tpcName + // + // return name of note + + function tpcName(tpc) { + var tpcNames = new Array( + "Fbb", "Cbb", "Gbb", "Dbb", "Abb", "Ebb", "Bbb", + "Fb", "Cb", "Gb", "Db", "Ab", "Eb", "Bb", + "F", "C", "G", "D", "A", "E", "B", + "F#", "C#", "G#", "D#", "A#", "E#", "B#", + "F##", "C##", "G##", "D##", "A##", "E##", "B##" + ); + + return(tpcNames[tpc+1]); + } + + // function getEndStaffOfPart + // + // return the first staff that does not belong to + // the part containing given start staff. + + function getEndStaffOfPart(startStaff) { + var startTrack = startStaff * 4; + var parts = curScore.parts; + + for(var i = 0; i < parts.length; i++) { + var part = parts[i]; + + if( (part.startTrack <= startTrack) + && (part.endTrack > startTrack) ) { + return(part.endTrack/4); + } + } + + // not found! + console.log("error: part for " + startStaff + " not found!"); + Qt.quit(); + } + + // function processNote + // + // for each measure we create a table that contains + // the actual 'noteName' of each 'noteClass' + // + // a 'noteClass' is the natural name of a space + // or line of the staff and the octave: + // C5, F6, B3 are 'noteClass' + // + // a 'noteName' would be C, F#, Bb for example + // (we don't need the octave here) + // + // curMeasureArray[] = + + function processNote(note,prevMeasureArray,curMeasureArray) { + var octave=Math.floor(note.pitch/12); + + // use tpc1 instead of tpc for octave correction + // since this will also work for transposing instruments + // correct octave for Cb and Cbb + if(note.tpc1 == 7 || note.tpc1 == 0) { + octave++; // belongs to higher octave + } + // correct octave for B# and B## + if(note.tpc1 == 26 || note.tpc1 == 33) { + octave--; // belongs to lower octave + } + + var noteName = tpcName(note.tpc); + var noteClass = noteName.charAt(0)+octave; + + // remember note for next measure + curMeasureArray[noteClass]=noteName; + + // check if current note needs courtesy acc + if(typeof prevMeasureArray[noteClass] !== 'undefined') { + if(prevMeasureArray[noteClass] != noteName) { + // this note needs an accidental + // if there's none present anyway + if(note.accidental == null) { + // calculate type of needed accidental + var accidental=Accidental.NONE; + if(note.tpc < 6) { + accidental = Accidental.FLAT2; + } else if(note.tpc < 13) { + accidental = Accidental.FLAT; + } else if(note.tpc < 20) { + accidental = Accidental.NATURAL; + } else if(note.tpc < 27) { + accidental = Accidental.SHARP; + } else { + accidental = Accidental.SHARP2; + } + note.accidentalType = accidental; + // put bracket on accidental + note.accidental.accidentalBracket = useBracket; + } + } + // delete entry to make sure we don't create the + // same accidental again in the same measure + delete prevMeasureArray[noteClass]; + } + } + + // function processPart + // + // do the actual work: process all given tracks in parallel + // add courtesy accidentals where needed. + // + // We go through all tracks simultaneously, because we also want courtesy + // accidentals for notes across different staves when they are in the + // same octave and for notes of different voices in the same octave + + function processPart(cursor,endTick,startTrack,endTrack) { + if(processAll) { + // we need to reset track first, otherwise + // rewind(0) doesn't work correctly + cursor.track=0; + cursor.rewind(0); + } else { + cursor.rewind(1); + } + + var segment = cursor.segment; + + // we use the cursor to know measure boundaries + cursor.nextMeasure(); + + var curMeasureArray = new Array(); + var prevMeasureArray = new Array(); + + // we use a segment, because the cursor always proceeds to + // the next element in the given track and we don't know + // in which track the element is. + var inLastMeasure=false; + while(segment && (processAll || segment.tick < endTick)) { + // check if still inside same measure + if(!inLastMeasure && !(segment.tick < cursor.tick)) { + // new measure + prevMeasureArray = curMeasureArray; + curMeasureArray = new Array(); + if(!cursor.nextMeasure()) { + inLastMeasure=true; + } + } + + // we search for key signatures in first voice of + // first staff: + var keySigTrack = startTrack - (startTrack % 4); + + for(var track=startTrack; track 0) { + var graceChords = segment.elementAt(track).graceNotes; + + for(var j=0;j endStaff) { + curEndStaff = endStaff; + } + + // do the work + processPart(cursor,endTick,curStartStaff*4,curEndStaff*4); + + // next part + curStartStaff = curEndStaff; + } + + //curScore.doLayout(); + //curScore.endCmd(); + + console.log("end add courtesy accidentals"); + Qt.quit(); + } + + onRun: { + if(mscoreMajorVersion == 3 && mscoreMinorVersion == 0 + && mscoreUpdateVersion < 5) { + versionError.open(); + } else { + addAcc(); + } + } +} diff --git a/share/plugins/configCourtesyAccidentals.qml b/share/plugins/configCourtesyAccidentals.qml new file mode 100644 index 0000000000000..5a508c350f1bb --- /dev/null +++ b/share/plugins/configCourtesyAccidentals.qml @@ -0,0 +1,659 @@ +//============================================== +// courtesy accidentals v1.0 +// +// Copyright (C)2012-2019 Jörn Eichler (heuchi) +// +// 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 . +//============================================== + +import QtQuick 2.2 +import QtQuick.Layouts 1.1 +import QtQuick.Controls 1.3 +import QtQuick.Dialogs 1.2 +import Qt.labs.settings 1.0 +import MuseScore 3.0 + +MuseScore { + version: "1.0" + description: "This plugin adds courtesy accidentals" + menuPath: "Plugins.Accidentals.Configure Courtesy Accidentals" + + requiresScore: true + + // configuration + property bool useBracket: false + + property var typeNextMeasure: 1 + property var typeNumMeasures: 2 + property var typeEvent: 3 + property var typeDodecaphonic: 4 + + property var eventFullRest: 1 + property var eventDoubleBar: 2 + property var eventRehearsalMark: 4 + property var eventEndScore: 8 // we don't really need this, but... + + property var operationMode; + property var numMeasures; + property var eventTypes; + + MessageDialog { + id: versionError + visible: false + title: "Unsupported MuseScore Version" + text: "This plugin needs MuseScore v3.0.5 or higher" + onAccepted: { Qt.quit() } + } + + // Error dialog + + MessageDialog { + id: errorDialog + visible: false + icon: StandardIcon.Warning + } + + // Dialog window + + function setUseBracketState() { + if (optDodecaphonic.checked == true) { + // disable brackets + optUseBracket.enabled = false; + optUseBracket.opacity = 0.5; + } else { + optUseBracket.enabled = true; + optUseBracket.opacity = 1.0; + } + } + + Dialog { + id: configDialog + visible: true + + contentItem: Rectangle { + id: rect1 + implicitWidth: 290 + implicitHeight: 290 + color: "lightgrey" + + ColumnLayout { + id: col1 + + ExclusiveGroup {id: typeGroup} + + Label { + text: "Add courtesy accidentals for" + } + + RowLayout { + Rectangle { // for indentation + width: 10 + } + + ColumnLayout { + + Rectangle {height: 2} + RadioButton { + id: optNextMeasure + text: "notes up to the next measure" + checked: true + exclusiveGroup: typeGroup + onClicked: { setUseBracketState(); } + } + + Rectangle {height: 2} + RowLayout { + RadioButton { + id: optNumMeasures + text: "notes up to the next" + exclusiveGroup: typeGroup + onClicked: { setUseBracketState(); } + } + + SpinBox { + id: valNumMeasures + implicitWidth: 45 + horizontalAlignment: Qt.AlignRight + decimals: 0 + minimumValue: 2 + maximumValue: 99 + } + + Label { + text: "measures" + } + } + + RowLayout { + RadioButton { + Layout.alignment: Qt.AlignTop | Qt.AlignLeft + id: optEvent + text: "notes up to the" + exclusiveGroup: typeGroup + onClicked: { setUseBracketState(); } + } + + ColumnLayout { + CheckBox { + id: optFullRest + text: "next full measure rest" + checked: true + } + CheckBox { + id: optDoubleBar + text: "next double bar line" + checked: true + } + CheckBox { + id: optRehearsalMark + text: "next rehearsal mark" + checked: false + } + CheckBox { + id: optEndScore + text: "end of the score" + checked: true + } + } + } + + Rectangle {height: 2} + RadioButton { + id: optDodecaphonic + text:"all notes (dodecaphonic style)" + exclusiveGroup: typeGroup + onClicked: { setUseBracketState(); } + } + } + } + + Rectangle {height: 4} + + // Parenthesis option + CheckBox { + id: optUseBracket + text: "Put accidentals in parenthesis" + checked: false + Layout.alignment: Qt.AlignVCenter | Qt.AlignRight + } + + // preserve user settings + Settings { + category: "CourtesyAccidentalPlugin" + property alias typeNextMeasure: optNextMeasure.checked + property alias typeNumMeasures: optNumMeasures.checked + property alias valueNumMeasure: valNumMeasures.value + property alias typeEvent: optEvent.checked + property alias typeFullRest: optFullRest.checked + property alias typeDoubleBar: optDoubleBar.checked + property alias typeRehearsalM: optRehearsalMark.checked + property alias typeEndScore: optEndScore.checked + property alias valueDodecaphonic: optDodecaphonic.checked + property alias valueUseBracket: optUseBracket.checked + } + } + // The buttons + + Button { + text:"Add accidentals" + anchors { + top: col1.bottom + topMargin: 15 + left: rect1.left + leftMargin: 10 + } + onClicked: { + configDialog.visible = false; + var hasError = false; + + // set configuration + useBracket = optUseBracket.checked; + + // set type + if (optNextMeasure.checked) { + operationMode = typeNextMeasure; + } else if (optNumMeasures.checked) { + operationMode = typeNumMeasures; + numMeasures = valNumMeasures.value; + } else if (optEvent.checked) { + operationMode = typeEvent; + eventTypes = 0; + if (optFullRest.checked) { + eventTypes |= eventFullRest; + } + if (optDoubleBar.checked) { + eventTypes |= eventDoubleBar; + } + if (optRehearsalMark.checked) { + eventTypes |= eventRehearsalMark; + } + if (optEndScore.checked) { + eventTypes |= eventEndScore; + } + if (!eventTypes) { + // show error: at least one item needs to be selected + //console.log("ERROR: configuration"); + hasError = true; + errorDialog.text = "No terminating event selected"; + errorDialog.visible = true; + } + } else if (optDodecaphonic.checked) { + operationMode = typeDodecaphonic; + } + + if (!hasError) { + curScore.startCmd(); + addAcc(); + curScore.endCmd(); + } + Qt.quit(); + } + } + + Button { + text: "Cancel" + anchors { + top: col1.bottom + topMargin: 15 + right: rect1.right + rightMargin: 10 + } + onClicked: { + configDialog.visible = false; + Qt.quit(); + } + } + } + } + + // if nothing is selected process whole score + property bool processAll: false + + // function tpcName + // + // return name of note + + function tpcName(tpc) { + var tpcNames = new Array( + "Fbb", "Cbb", "Gbb", "Dbb", "Abb", "Ebb", "Bbb", + "Fb", "Cb", "Gb", "Db", "Ab", "Eb", "Bb", + "F", "C", "G", "D", "A", "E", "B", + "F#", "C#", "G#", "D#", "A#", "E#", "B#", + "F##", "C##", "G##", "D##", "A##", "E##", "B##" + ); + + return(tpcNames[tpc+1]); + } + + // function getEndStaffOfPart + // + // return the first staff that does not belong to + // the part containing given start staff. + + function getEndStaffOfPart(startStaff) { + var startTrack = startStaff * 4; + var parts = curScore.parts; + + for(var i = 0; i < parts.length; i++) { + var part = parts[i]; + + if( (part.startTrack <= startTrack) + && (part.endTrack > startTrack) ) { + return(part.endTrack/4); + } + } + + // not found! + console.log("error: part for " + startStaff + " not found!"); + Qt.quit(); + } + + // function addAccidental + // + // add correct accidental to note + + function addAccidental(note) { + if(note.accidental == null) { + // calculate type of needed accidental + var accidental=Accidental.NONE; + if(note.tpc < 6) { + accidental = Accidental.FLAT2; + } else if(note.tpc < 13) { + accidental = Accidental.FLAT; + } else if(note.tpc < 20) { + accidental = Accidental.NATURAL; + } else if(note.tpc < 27) { + accidental = Accidental.SHARP; + } else { + accidental = Accidental.SHARP2; + } + note.accidentalType = accidental; + // put bracket on accidental if not in dodecaphonic mode + if (operationMode != typeDodecaphonic + && note.accidental) { + if(useBracket) { + note.accidental.accidentalBracket = 1; + } else { + note.accidental.accidentalBracket = 0; + } + } + } + } + + // function processNote + // + // for each measure we create a table that contains + // the actual 'noteName' of each 'noteClass' + // + // a 'noteClass' is the natural name of a space + // or line of the staff and the octave: + // C5, F6, B3 are 'noteClass' + // + // a 'noteName' would be C, F#, Bb for example + // (we don't need the octave here) + // + // we also remember the measure number that note was found + // if we operate in typeNumMeasures mode. Thus: + // + // curMeasureArray[] = [,] + + function processNote(note,prevMeasureArray,curMeasureArray,curMeasureNum) { + var octave=Math.floor(note.pitch/12); + + // use tpc1 instead of tpc for octave correction + // since this will also work for transposing instruments + // correct octave for Cb and Cbb + if(note.tpc1 == 7 || note.tpc1 == 0) { + octave++; // belongs to higher octave + } + // correct octave for B# and B## + if(note.tpc1 == 26 || note.tpc1 == 33) { + octave--; // belongs to lower octave + } + + var noteName = tpcName(note.tpc); + var noteClass = noteName.charAt(0)+octave; + + // remember note for next measure + curMeasureArray[noteClass]=[noteName,curMeasureNum]; + + if (operationMode == typeDodecaphonic) { + addAccidental(note); + } else if (typeof prevMeasureArray[noteClass] !== 'undefined') { + // check if current note needs courtesy acc + if(prevMeasureArray[noteClass][0] != noteName) { + // this note needs an accidental + // if there's none present anyway + addAccidental(note); + } + // delete entry to make sure we don't create the + // same accidental again in the same measure + delete prevMeasureArray[noteClass]; + } + } + + // function processPart + // + // do the actual work: process all given tracks in parallel + // add courtesy accidentals where needed. + // + // We go through all tracks simultaneously, because we also want courtesy + // accidentals for notes across different staves when they are in the + // same octave and for notes of different voices in the same octave + + function processPart(cursor,endTick,startTrack,endTrack) { + if(processAll) { + // we need to reset track first, otherwise + // rewind(0) doesn't work correctly + cursor.track=0; + cursor.rewind(0); + } else { + cursor.rewind(1); + } + + var curMeasureNum = 0; + var segment = cursor.segment; + + var curMeasureArray = new Array(); + var prevMeasureArray = new Array(); + + // we use a segment, because the cursor always proceeds to + // the next element in the given track and we don't know + // in which track the next element is. + + while(segment && (processAll || segment.tick < endTick)) { + // we search for key signatures and bar lines + // in first voice of first staff: + var keySigTrack = startTrack - (startTrack % 4); + + // check for new measure + if(segment.elementAt(keySigTrack) + && segment.elementAt(keySigTrack).type == Element.BAR_LINE) { + // if double bar line and in nextEvent mode check + // if this leads to reset of prevMeasureArray + + curMeasureNum++; + + // depending on operationMode: update prevMeasureArray + switch (operationMode) { + case typeNextMeasure: + prevMeasureArray = curMeasureArray; + break; + + case typeNumMeasures: + // delete all entries that are too old + var toDelete = []; + for (var n in prevMeasureArray) { + if (curMeasureNum - prevMeasureArray[n][1] > numMeasures) { + toDelete.push(n); + } + } + // now delete, otherwise iterating (n in prevMeasureArray) will not work + for (var x = 0; x < toDelete.length; x++) + delete prevMeasureArray[toDelete[x]]; + // fall through! + case typeEvent: + // copy entries from curMeasureArray + for (var n in curMeasureArray) { + prevMeasureArray[n] = curMeasureArray[n]; + } + break; + } + + // if barline is double, might need to forget + // previous mesaure... + var barLine = segment.elementAt(keySigTrack); + if ((operationMode==typeEvent) + && (eventTypes & eventDoubleBar) + && (barLine.barLineType == BarLine.DOUBLE)) { + prevMeasureArray = new Array(); + } + + // reset curMeasureArray + curMeasureArray = new Array(); + } + + // check for new key signature + // we only do this for the first track of the first staff + // this means we miss the event of having two different + // key signatures in different staves of the same part + // This remains for future version if needed + // we look inside this loop to make sure we don't miss + // any segments. This could be improved for speed. + // A KeySig that has generated == true was created by + // layout, and is probably at the beginning of a new line + // so we don't need it. + + if (segment.elementAt(keySigTrack) + && segment.elementAt(keySigTrack).type == Element.KEYSIG + && (!segment.elementAt(keySigTrack).generated)) { + //console.log("found KEYSIG"); + // just forget the previous measure info + // to not generate any courtesy accidentals + prevMeasureArray = new Array(); + } + + // BUG: access to annotations is broken in 2.0.3 + // + // check for rehearsal mark + //var annotations = segment.annotations; + + //if (annotations && annotations.length > 0) { + // for (var i = 0; i < annotations.length; i++) { + // var mark = annotations[i]; + // if (mark.type == Element.REHEARSAL_MARK) { + // if (operationMode == typeEvent + // && (eventTypes & eventRehearsalMark)) { + // // reset array + // prevMeasureArray = new Array(); + // } + // console.log("found rehearsal mark"); + // } + // } + //} + + // if we find a full measure rest, it needs to be in the whole part + var allTracksFullMeasureRest = true; + var restFound = false; + + // scann music + for(var track=startTrack; track 0) { + var graceChords = segment.elementAt(track).graceNotes; + + for(var j=0;j endStaff) { + curEndStaff = endStaff; + } + + // do the work + processPart(cursor,endTick,curStartStaff*4,curEndStaff*4); + + // next part + curStartStaff = curEndStaff; + } + } + + onRun: { + console.log("MuseScore Version = "+mscoreVersion); + console.log("MajorVersion = "+mscoreMajorVersion); + console.log("MinorVersion = "+mscoreMinorVersion); + console.log("UpdateVersion= "+mscoreUpdateVersion); + + if (mscoreMajorVersion == 3 && mscoreMinorVersion == 0 + && mscoreUpdateVersion < 5) { + configDialog.visible = false; + versionError.open(); + } + + // These options don't work in MuseScore v3 + optDoubleBar.checked = false; + optDoubleBar.enabled = false; + optDoubleBar.opacity = 0.5; + optFullRest.checked = false; + optFullRest.enabled = false; + optFullRest.opacity = 0.5; + optRehearsalMark.checked = false; + optRehearsalMark.enabled = false; + optRehearsalMark.opacity = 0.5; + } +} diff --git a/share/plugins/createscore.qml b/share/plugins/createscore.qml deleted file mode 100644 index 123b0a0f1aa1e..0000000000000 --- a/share/plugins/createscore.qml +++ /dev/null @@ -1,67 +0,0 @@ -/* - * SPDX-License-Identifier: GPL-3.0-only - * MuseScore-CLA-applies - * - * MuseScore - * Music Composition & Notation - * - * Copyright (C) 2021 MuseScore BVBA and others - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * 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 . - */ -import QtQuick 2.0 -import MuseScore 3.0 - -MuseScore { - version: "3.0" - description: "This demo plugin creates a new score." - menuPath: "Plugins.createscore" - requiresScore: false - - onRun: { - console.log("hello createscore"); - var score = newScore("Test-Score", "piano", 5); - var numerator = 3; - var denominator = 4; - - score.addText("title", "==Test-Score=="); - score.addText("subtitle", "subtitle"); - score.addText("composer", "Composer"); - score.addText("lyricist", "Lyricist"); - - var cursor = score.newCursor(); - cursor.track = 0; - - cursor.rewind(0); - var ts = newElement(Element.TIMESIG); - ts.timesig = fraction(numerator, denominator); - cursor.add(ts); - - cursor.rewind(0); - cursor.setDuration(1, 4); - cursor.addNote(60); - - cursor.next(); - cursor.setDuration(3, 8); - cursor.addNote(64); - - cursor.next(); - cursor.setDuration(1, 4); - cursor.addNote(68); - - cursor.next(); - cursor.addNote(72); - - Qt.quit(); - } - } diff --git a/share/plugins/helloqml/helloqml.qml b/share/plugins/helloqml/helloqml.qml deleted file mode 100644 index 0a5da47a58565..0000000000000 --- a/share/plugins/helloqml/helloqml.qml +++ /dev/null @@ -1,74 +0,0 @@ -/* - * SPDX-License-Identifier: GPL-3.0-only - * MuseScore-CLA-applies - * - * MuseScore - * Music Composition & Notation - * - * Copyright (C) 2021 MuseScore BVBA and others - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * 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 . - */ -import QtQuick 2.0 -import MuseScore 3.0 - - -MuseScore { - menuPath: "Plugins.helloQml" - version: "3.0" - description: qsTr("This demo plugin shows some basic tasks.") - pluginType: "dialog" - - width: 150 - height: 75 - onRun: { - console.log(qsTr("hello world")); - var score = curScore - console.log(curScore) - console.log(score.name) - var m - m = score.firstMeasure() - while (m) { - console.log(qsTr("measure")) - var segment = m.first() - while (segment) { - var element - element = segment.elementAt(0) - if (element && element.type == Element.CHORD) { - console.log(qsTr(" element")) - console.log(element.beamMode) - if (element.beamMode == BeamMode.NO) - console.log(" beam no") - } - segment = segment.next() - } - m = m.nextMeasure() - } - } - - Rectangle { - color: "grey" - anchors.fill: parent - - Text { - anchors.centerIn: parent - text: qsTr("Hello Qml") - } - - MouseArea { - anchors.fill: parent - onClicked: Qt.quit() - } - } - } - diff --git a/share/plugins/helloqml/translations/locale_de.qm b/share/plugins/helloqml/translations/locale_de.qm deleted file mode 100644 index 77ccc4a0537ad6ff1ce24cbdc06263864624df9d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 620 zcmZ`$Jxc>Y5S=rS4`X8tBEe!53!4iF78YtDDk%*HY(0}Tw`6Zuvlj#Y0{?)WjkWm! zY1Ga_@F!SXS_u{+zB}bexVX!(^JeGGyq(<~wice>Z?0dK79Wpq?_YO_Xu~NF%0$jx zX07rC{FE6EK7ik{Tb~;EJI6)b(?#K-vVwDaX1cPDlO)m9Tn5fO@!#GfMS=pDrZ68R zWno3e5=sO@hdJQ9FO)LpfpQ{tDdrx6(@rr9%vQ7~^OnJL$-8* z|Lc{gHd3b!Eh>3Ip%y9TdZ5z!ecHt+1liaaD4KwQvIA)9;Z+jTV7f$H0)_A+Psd0k z=oHPb;WO21OU>3deaqGaf=@gvn;d$!W2 - - - - helloqml - - - This demo plugin shows some basic tasks. - Dieses Demo-Plugin zeigt einige einfache Funktionen. - - - - hello world - Hallo Welt - - - - measure - Takt - - - - segment - Segment - - - - ---hello segment - ---hallo Segment - - - - element - Element - - - - Hello Qml - Hallo Qml - - - diff --git a/share/plugins/mirror-intervals-3.qml b/share/plugins/mirror-intervals-3.qml new file mode 100644 index 0000000000000..8f9da6ff0f5c1 --- /dev/null +++ b/share/plugins/mirror-intervals-3.qml @@ -0,0 +1,250 @@ +// Mirror intervals chromatically about a given pivot note. +// Copyright (C) 2018 Bill Hails +// +// 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 . + +import MuseScore 3.0 +import QtQuick 2.2 +import QtQuick.Controls 1.1 +import QtQuick.Controls.Styles 1.3 +import QtQuick.Layouts 1.1 +import QtQuick.Dialogs 1.1 + +MuseScore { + version: "3.0.2" + menuPath: "Plugins.Composing Tools.Mirror Intervals" + description: "Mirrors (inverts) intervals about a given pivot note" + pluginType: "dialog" + width: 250 + height: 150 + + onRun: { + if (!curScore) { + error("No score open.\nThis plugin requires an open score to run.\n") + Qt.quit() + } + } + + function applyMirrorIntervals() + { + var selection = getSelection() + if (selection === null) { + error("No selection.\nThis plugin requires a current selection to run.\n") + Qt.quit() + } + curScore.startCmd() + mapOverSelection(selection, filterNotes, mirrorIntervals(getMirrorType(), getPivotNote())) + curScore.endCmd() + } + + function mapOverSelection(selection, filter, process) { + selection.cursor.rewind(1) + for ( + var segment = selection.cursor.segment; + segment && segment.tick < selection.endTick; + segment = segment.next + ) { + for (var track = selection.startTrack; track < selection.endTrack; track++) { + var element = segment.elementAt(track) + if (element) { + if (filter(element)) { + process(element, track) + } + } + } + } + } + + function filterNotes(element) + { + return element.type == Ms.CHORD + } + + function mirrorIntervals(mirrorType, pivotNote) + { + if (mirrorType == 0) { + return diatonicMirror(pivotNote) + } else { + return chromaticMirror(pivotNote) + } + } + + function chromaticMirror(pivotNote) + { + var pivots = []; + return function(chord, track) { + for (var i = 0; i < chord.notes.length; i++) { + var note = chord.notes[i] + note.tpc1 = lookupTpc(pivotNote, note.tpc1) + note.tpc2 = lookupTpc(pivotNote, note.tpc2) + if (!(track in pivots)) { + pivots[track] = nearestPivot(pivotNote, note.pitch); + } + note.pitch = performPivot(pivots[track], note.pitch); + } + } + } + + function nearestPivot(pivotNote, pitch) + { + var root = pitch - (pitch % 12) + var pivot = root + pivotNote + if ((pitch - pivot) > 6) { + pivot += 12 + } else if ((pitch - pivot) < -6) { + pivot -= 12 + } + return pivot + } + + function performPivot(pivot, pitch) + { + var diff = pivot - pitch; + return pivot + diff + } + + function diatonicMirror(pivotNote) + { + return function(chord) { + error("diatonic\nnot implemented yet"); + } + } + + function getSelection() { + var cursor = curScore.newCursor() + cursor.rewind(1) + if (!cursor.segment) { + return null + } + var selection = { + cursor: cursor, + startTick: cursor.tick, + endTick: null, + startStaff: cursor.staffIdx, + endStaff: null, + startTrack: null, + endTrack: null + } + cursor.rewind(2) + selection.endStaff = cursor.staffIdx + 1 + if (cursor.tick == 0) { + selection.endTick = curScore.lastSegment.tick + 1 + } else { + selection.endTick = cursor.tick + } + selection.startTrack = selection.startStaff * 4 + selection.endTrack = selection.endStaff * 4 + return selection + } + + property int mirrorType: 1 + + function getMirrorType() + { + return mirrorType + } + + function getPivotNote() + { + return pivotNote.model.get(pivotNote.currentIndex).note + } + + function error(errorMessage) { + errorDialog.text = qsTr(errorMessage) + errorDialog.open() + } + + property var tpcMap: [ + [30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 09, 08, 07, 06, 05, 04, 03, 02, 01, 00, 23, 22, 21, 20], + [32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 09, 08, 07, 06, 05, 04, 03, 02, 01, 00, 23, 22], + [34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 09, 08, 07, 06, 05, 04, 03, 02, 01, 00], + [12, 11, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 09, 08, 07, 06, 05, 04, 03, 02], + [14, 13, 12, 11, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 09, 08, 07, 06, 05, 04], + [28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 09, 08, 07, 06, 05, 04, 03, 02, 01, 00, 23, 22, 21, 20, 19, 18], + [30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 09, 08, 07, 06, 05, 04, 03, 02, 01, 00, 23, 22, 21, 20], + [32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 09, 08, 07, 06, 05, 04, 03, 02, 01, 00, 23, 22], + [34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 09, 08, 07, 06, 05, 04, 03, 02, 01, 00], + [12, 11, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 09, 08, 07, 06, 05, 04, 03, 02], + [14, 13, 12, 11, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 09, 08, 07, 06, 05, 04], + [28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 09, 08, 07, 06, 05, 04, 03, 02, 01, 00, 23, 22, 21, 20, 19, 18] + ]; + + function lookupTpc(pivot, tpc) + { + // tpc starts at -1 + return tpcMap[pivot][tpc + 1] - 1; + } + + Rectangle { + color: "lightgrey" + anchors.fill: parent + + GridLayout { + columns: 2 + anchors.fill: parent + anchors.margins: 10 + Label { + text: "Pivot" + } + ComboBox { + id: pivotNote + model: ListModel { + id: pivotNoteList + ListElement { text: "G"; note: 7; } + ListElement { text: "G♯"; note: 8; } + ListElement { text: "A"; note: 9; } + ListElement { text: "B♭"; note: 10; } + ListElement { text: "B"; note: 11; } + ListElement { text: "C"; note: 0; } + ListElement { text: "C♯"; note: 1; } + ListElement { text: "D"; note: 2; } + ListElement { text: "E♭"; note: 3; } + ListElement { text: "E"; note: 4; } + ListElement { text: "F"; note: 5; } + ListElement { text: "F♯"; note: 6; } + } + currentIndex: 5 + style: ComboBoxStyle { + font.family: 'MScore Text' + font.pointSize: 14 + } + } + Button { + id: applyButton + text: qsTranslate("PrefsDialogBase", "Apply") + onClicked: { + applyMirrorIntervals() + Qt.quit() + } + } + Button { + id: cancelButton + text: qsTranslate("PrefsDialogBase", "Cancel") + onClicked: { + Qt.quit() + } + } + } + } + + MessageDialog { + id: errorDialog + title: "Error" + text: "" + onAccepted: { + Qt.quit() + } + visible: false + } +} diff --git a/share/plugins/notenames-interactive.qml b/share/plugins/notenames-interactive.qml deleted file mode 100644 index 56bbba765be49..0000000000000 --- a/share/plugins/notenames-interactive.qml +++ /dev/null @@ -1,377 +0,0 @@ -/* - * SPDX-License-Identifier: GPL-3.0-only - * MuseScore-CLA-applies - * - * MuseScore - * Music Composition & Notation - * - * Copyright (C) 2021 MuseScore BVBA and others - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * 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 . - */ - -import QtQuick 2.2 -import QtQuick.Controls 2.0 -import MuseScore 3.0 - -MuseScore { - version: "3.5" - description: qsTr("This plugin names notes as per your language setting") - menuPath: "Plugins.Notes." + qsTr("Note Names (Interactive)") - pluginType: "dock" - - implicitHeight: controls.implicitHeight * 1.5 - implicitWidth: controls.implicitWidth - - // Small note name size is fraction of the full font size. - property var defaultFontSize - property var fontSizeMini: 0.7; - - property int nstaves: 0 // for validators in staff number inputs - - property bool inCmd: false - - function ensureCmdStarted() { - if (!inCmd) { - curScore.startCmd(); - inCmd = true; - } - } - - function ensureCmdEnded() { - if (inCmd) { - curScore.endCmd(); - inCmd = false; - } - } - - function findSegment(el) { - while (el && el.type != Element.SEGMENT) - el = el.parent; - return el; - } - - function getChordName(chord) { - var text = ""; - var notes = chord.notes; - var sep = "\n"; // change to "," if you want them horizontally (anybody?) - for (var i = 0; i < notes.length; i++) { - if ((curScore.selection.elements.length && !notes[i].selected) || !notes[i].visible) - continue // skip notes a that are not selected or invisible - if (text) // only if text isn't empty - text = sep + text; // any but top note - if (typeof notes[i].tpc === "undefined") // like for grace notes ?!? - return; - switch (notes[i].tpc) { - case -1: text = qsTranslate("InspectorAmbitus", "F♭♭") + text; break; - case 0: text = qsTranslate("InspectorAmbitus", "C♭♭") + text; break; - case 1: text = qsTranslate("InspectorAmbitus", "G♭♭") + text; break; - case 2: text = qsTranslate("InspectorAmbitus", "D♭♭") + text; break; - case 3: text = qsTranslate("InspectorAmbitus", "A♭♭") + text; break; - case 4: text = qsTranslate("InspectorAmbitus", "E♭♭") + text; break; - case 5: text = qsTranslate("InspectorAmbitus", "B♭♭") + text; break; - case 6: text = qsTranslate("InspectorAmbitus", "F♭") + text; break; - case 7: text = qsTranslate("InspectorAmbitus", "C♭") + text; break; - - case 8: text = qsTranslate("InspectorAmbitus", "G♭") + text; break; - case 9: text = qsTranslate("InspectorAmbitus", "D♭") + text; break; - case 10: text = qsTranslate("InspectorAmbitus", "A♭") + text; break; - case 11: text = qsTranslate("InspectorAmbitus", "E♭") + text; break; - case 12: text = qsTranslate("InspectorAmbitus", "B♭") + text; break; - case 13: text = qsTranslate("InspectorAmbitus", "F") + text; break; - case 14: text = qsTranslate("InspectorAmbitus", "C") + text; break; - case 15: text = qsTranslate("InspectorAmbitus", "G") + text; break; - case 16: text = qsTranslate("InspectorAmbitus", "D") + text; break; - case 17: text = qsTranslate("InspectorAmbitus", "A") + text; break; - case 18: text = qsTranslate("InspectorAmbitus", "E") + text; break; - case 19: text = qsTranslate("InspectorAmbitus", "B") + text; break; - - case 20: text = qsTranslate("InspectorAmbitus", "F♯") + text; break; - case 21: text = qsTranslate("InspectorAmbitus", "C♯") + text; break; - case 22: text = qsTranslate("InspectorAmbitus", "G♯") + text; break; - case 23: text = qsTranslate("InspectorAmbitus", "D♯") + text; break; - case 24: text = qsTranslate("InspectorAmbitus", "A♯") + text; break; - case 25: text = qsTranslate("InspectorAmbitus", "E♯") + text; break; - case 26: text = qsTranslate("InspectorAmbitus", "B♯") + text; break; - case 27: text = qsTranslate("InspectorAmbitus", "F♯♯") + text; break; - case 28: text = qsTranslate("InspectorAmbitus", "C♯♯") + text; break; - case 29: text = qsTranslate("InspectorAmbitus", "G♯♯") + text; break; - case 30: text = qsTranslate("InspectorAmbitus", "D♯♯") + text; break; - case 31: text = qsTranslate("InspectorAmbitus", "A♯♯") + text; break; - case 32: text = qsTranslate("InspectorAmbitus", "E♯♯") + text; break; - case 33: text = qsTranslate("InspectorAmbitus", "B♯♯") + text; break; - default: text = qsTr("?") + text; break; - } // end switch tpc - - // octave, middle C being C4 - //text += (Math.floor(notes[i].pitch / 12) - 1) - // or - //text += (Math.floor(notes[i].ppitch / 12) - 1) - - // change below false to true for courtesy- and microtonal accidentals - // you might need to come up with suitable translations - // only #, b, natural and possibly also ## seem to be available in UNICODE - if (false) { - switch (notes[i].userAccidental) { - case 0: break; - case 1: text = qsTranslate("accidental", "Sharp") + text; break; - case 2: text = qsTranslate("accidental", "Flat") + text; break; - case 3: text = qsTranslate("accidental", "Double sharp") + text; break; - case 4: text = qsTranslate("accidental", "Double flat") + text; break; - case 5: text = qsTranslate("accidental", "Natural") + text; break; - case 6: text = qsTranslate("accidental", "Flat-slash") + text; break; - case 7: text = qsTranslate("accidental", "Flat-slash2") + text; break; - case 8: text = qsTranslate("accidental", "Mirrored-flat2") + text; break; - case 9: text = qsTranslate("accidental", "Mirrored-flat") + text; break; - case 10: text = qsTranslate("accidental", "Mirrored-flat-slash") + text; break; - case 11: text = qsTranslate("accidental", "Flat-flat-slash") + text; break; - case 12: text = qsTranslate("accidental", "Sharp-slash") + text; break; - case 13: text = qsTranslate("accidental", "Sharp-slash2") + text; break; - case 14: text = qsTranslate("accidental", "Sharp-slash3") + text; break; - case 15: text = qsTranslate("accidental", "Sharp-slash4") + text; break; - case 16: text = qsTranslate("accidental", "Sharp arrow up") + text; break; - case 17: text = qsTranslate("accidental", "Sharp arrow down") + text; break; - case 18: text = qsTranslate("accidental", "Sharp arrow both") + text; break; - case 19: text = qsTranslate("accidental", "Flat arrow up") + text; break; - case 20: text = qsTranslate("accidental", "Flat arrow down") + text; break; - case 21: text = qsTranslate("accidental", "Flat arrow both") + text; break; - case 22: text = qsTranslate("accidental", "Natural arrow down") + text; break; - case 23: text = qsTranslate("accidental", "Natural arrow up") + text; break; - case 24: text = qsTranslate("accidental", "Natural arrow both") + text; break; - case 25: text = qsTranslate("accidental", "Sori") + text; break; - case 26: text = qsTranslate("accidental", "Koron") + text; break; - default: text = qsTr("?") + text; break; - } // end switch userAccidental - } // end if courtesy- and microtonal accidentals - } // end for note - - return text; - } - - function getGraceNoteNames(graceChordsList) { - var names = []; - // iterate through all grace chords - for (var chordNum = 0; chordNum < graceChordsList.length; chordNum++) { - var chord = graceChordsList[chordNum]; - var chordName = getChordName(chord); - // append the name to the list of names - names.push(chordName); - - } - return names; - } - - function getAllChords(el) { - // List chords in the following order: - // 1) Leading grace notes; - // 2) Chord itself; - // 3) Trailing grace notes. - - if (!el || el.type != Element.CHORD) - return []; - - var chord = el; - var allChords = [ chord ]; - - // Search for grace notes - var graceChords = chord.graceNotes; - for (var chordNum = 0; chordNum < graceChords.length; chordNum++) { - var graceChord = graceChords[chordNum]; - var noteType = graceChord.noteType; - - switch (noteType) { - case NoteType.GRACE8_AFTER: - case NoteType.GRACE16_AFTER: - case NoteType.GRACE32_AFTER: - leadingLifo.push(graceChord); // append trailing grace chord to list - break; - default: - allChords.unshift(graceChord); // prepend leading grace chord to list - break; - } - } - - return allChords; - } - - function isNoteName(el) { - return el.type == Element.STAFF_TEXT; // TODO: how to distinguish note names from all staff texts? - } - - function getExistingNoteNames(segment, track) { - var annotations = segment.annotations; - var noteNames = []; - - for (var i = 0; i < annotations.length; ++i) { - var a = annotations[i]; - if (a.track != track) - continue; - if (isNoteName(a)) - noteNames.push(a); - } - - return noteNames; - } - - function handleChordAtCursor(cursor) { - var allNoteNames = getExistingNoteNames(cursor.segment, cursor.track); - var allChords = getAllChords(cursor.element); - var chordIdx = 0; - - for (; chordIdx < allChords.length; ++chordIdx) { - var chord = allChords[chordIdx]; - var noteName = allNoteNames[chordIdx]; - - var chordProperties = { - "offsetX": chord.posX, - "fontSize" : chord.noteType == NoteType.NORMAL ? defaultFontSize : (defaultFontSize * fontSizeMini), - "placement": (chord.voice & 1) ? Placement.BELOW : Placement.ABOVE, // place below for voice 1 and voice 3 (numbered as 2 and 4 in user interface) - "text": getChordName(chord) - } - - if (!noteName) { - // Note name does not exist, add a new one - ensureCmdStarted(); - var nameText = newElement(Element.STAFF_TEXT); - for (var prop in chordProperties) { - if (nameText[prop] != chordProperties[prop]) - nameText[prop] = chordProperties[prop]; - } - cursor.add(nameText); - } else { - // Note name exists, ensure it is up to date - for (var prop in chordProperties) { - if (noteName[prop] != chordProperties[prop]) { - ensureCmdStarted(); - noteName[prop] = chordProperties[prop]; - } - } - } // end if/else noteName - } // end for allChords - - // Remove the remaining redundant note names, if any - for (; chordIdx < allNoteNames.length; ++chordIdx) { - ensureCmdStarted(); - var noteName = allNoteNames[chordIdx]; - removeElement(noteName); - } - } // end handleChordAtCursor() - - function processRange(startTick, endTick, firstStaff, lastStaff) { - if (startTick < 0) - startTick = 0; - if (endTick < 0) - endTick = Infinity; // process the entire score - - var cursor = curScore.newCursor(); - - for (var staff = firstStaff; staff <= lastStaff; staff++) { - for (var voice = 0; voice < 4; voice++) { - cursor.voice = voice; - cursor.staffIdx = staff; - cursor.rewindToTick(startTick); - - while (cursor.segment && cursor.tick <= endTick) { - handleChordAtCursor(cursor); - cursor.next(); - } // end while segment - - } // end for voice - } // end for staff - - ensureCmdEnded(); - } - - function getStavesRange() { - if (allStavesCheckBox.checked) - return [0, curScore.nstaves]; - - var firstStaff = firstStaffInput.acceptableInput ? +firstStaffInput.text : curScore.nstaves; - var lastStaff = lastStaffInput.acceptableInput ? +lastStaffInput.text : -1; - - return [firstStaff, lastStaff]; - } - - onScoreStateChanged: { - if (inCmd) // prevent recursion from own changes - return; - if (state.undoRedo) // try not to interfere with undo/redo commands - return; - - nstaves = curScore.nstaves; // needed for validators in staff number inputs - - if (!noteNamesEnabledCheckBox.checked) - return; - if (!curScore || state.startLayoutTick < 0) // nothing to process? - return; - - var stavesRange = getStavesRange(); - - processRange(state.startLayoutTick, state.endLayoutTick, stavesRange[0], stavesRange[1]); - } - - onRun: { - defaultFontSize = newElement(Element.STAFF_TEXT).fontSize; - } - - Column { - id: controls - - CheckBox { - id: noteNamesEnabledCheckBox - text: "Enable notes naming" - } - CheckBox { - id: allStavesCheckBox - checked: true - text: "All staves" - } - - Grid { - id: staffRangeControls - columns: 2 - spacing: 4 - enabled: !allStavesCheckBox.checked - - Text { - height: firstStaffInput.height - verticalAlignment: Text.AlignVCenter - text: "first staff:" - } - TextField { - id: firstStaffInput - text: "0" - validator: IntValidator { bottom: 0; top: nstaves - 1 } - onTextChanged: { - if (+lastStaffInput.text < +text) - lastStaffInput.text = text - } - } - - Text { - height: lastStaffInput.height - verticalAlignment: Text.AlignVCenter - text: "last staff:" - } - TextField { - id: lastStaffInput - text: "0" - validator: IntValidator { bottom: 0; top: nstaves - 1 } - onTextChanged: { - if (text !== "" && (+firstStaffInput.text > +text)) - firstStaffInput.text = text - } - } - } - } -} diff --git a/share/plugins/notenames.qml b/share/plugins/notenames.qml index c196b4a6a0f76..2125db759d03d 100644 --- a/share/plugins/notenames.qml +++ b/share/plugins/notenames.qml @@ -1,95 +1,93 @@ -/* - * SPDX-License-Identifier: GPL-3.0-only - * MuseScore-CLA-applies - * - * MuseScore - * Music Composition & Notation - * - * Copyright (C) 2021 MuseScore BVBA and others - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * 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 . - */ +//============================================================================= +// MuseScore +// Music Composition & Notation +// +// Note Names Plugin +// +// Copyright (C) 2012 Werner Schweer +// Copyright (C) 2013 - 2021 Joachim Schmitz +// Copyright (C) 2014 Jörn Eichler +// Copyright (C) 2020 Johan Temmerman +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2 +// as published by the Free Software Foundation and appearing in +// the file LICENCE.GPL +//============================================================================= import QtQuick 2.2 import MuseScore 3.0 MuseScore { - version: "3.5" - description: qsTr("This plugin names notes as per your language setting") - menuPath: "Plugins.Notes." + qsTr("Note Names") + version: "3.6" + description: "This plugin names notes as per your language setting" + menuPath: "Plugins.Notes." + "Note Names" // Small note name size is fraction of the full font size. - property var fontSizeMini: 0.7; + property real fontSizeMini: 0.7; function nameChord (notes, text, small) { var sep = "\n"; // change to "," if you want them horizontally (anybody?) + var oct = ""; + var name; for (var i = 0; i < notes.length; i++) { - if ((curScore.selection.elements.length && !notes[i].selected) || !notes[i].visible) - continue // skip notes that are not selected or invisible + if (!notes[i].visible) + continue // skip invisible notes if (text.text) // only if text isn't empty text.text = sep + text.text; if (small) - text.fontSize *= fontSizeMini + text.fontSize *= fontSizeMini if (typeof notes[i].tpc === "undefined") // like for grace notes ?!? return switch (notes[i].tpc) { - case -1: text.text = qsTranslate("InspectorAmbitus", "F♭♭") + text.text; break; - case 0: text.text = qsTranslate("InspectorAmbitus", "C♭♭") + text.text; break; - case 1: text.text = qsTranslate("InspectorAmbitus", "G♭♭") + text.text; break; - case 2: text.text = qsTranslate("InspectorAmbitus", "D♭♭") + text.text; break; - case 3: text.text = qsTranslate("InspectorAmbitus", "A♭♭") + text.text; break; - case 4: text.text = qsTranslate("InspectorAmbitus", "E♭♭") + text.text; break; - case 5: text.text = qsTranslate("InspectorAmbitus", "B♭♭") + text.text; break; - case 6: text.text = qsTranslate("InspectorAmbitus", "F♭") + text.text; break; - case 7: text.text = qsTranslate("InspectorAmbitus", "C♭") + text.text; break; - - case 8: text.text = qsTranslate("InspectorAmbitus", "G♭") + text.text; break; - case 9: text.text = qsTranslate("InspectorAmbitus", "D♭") + text.text; break; - case 10: text.text = qsTranslate("InspectorAmbitus", "A♭") + text.text; break; - case 11: text.text = qsTranslate("InspectorAmbitus", "E♭") + text.text; break; - case 12: text.text = qsTranslate("InspectorAmbitus", "B♭") + text.text; break; - case 13: text.text = qsTranslate("InspectorAmbitus", "F") + text.text; break; - case 14: text.text = qsTranslate("InspectorAmbitus", "C") + text.text; break; - case 15: text.text = qsTranslate("InspectorAmbitus", "G") + text.text; break; - case 16: text.text = qsTranslate("InspectorAmbitus", "D") + text.text; break; - case 17: text.text = qsTranslate("InspectorAmbitus", "A") + text.text; break; - case 18: text.text = qsTranslate("InspectorAmbitus", "E") + text.text; break; - case 19: text.text = qsTranslate("InspectorAmbitus", "B") + text.text; break; - - case 20: text.text = qsTranslate("InspectorAmbitus", "F♯") + text.text; break; - case 21: text.text = qsTranslate("InspectorAmbitus", "C♯") + text.text; break; - case 22: text.text = qsTranslate("InspectorAmbitus", "G♯") + text.text; break; - case 23: text.text = qsTranslate("InspectorAmbitus", "D♯") + text.text; break; - case 24: text.text = qsTranslate("InspectorAmbitus", "A♯") + text.text; break; - case 25: text.text = qsTranslate("InspectorAmbitus", "E♯") + text.text; break; - case 26: text.text = qsTranslate("InspectorAmbitus", "B♯") + text.text; break; - case 27: text.text = qsTranslate("InspectorAmbitus", "F♯♯") + text.text; break; - case 28: text.text = qsTranslate("InspectorAmbitus", "C♯♯") + text.text; break; - case 29: text.text = qsTranslate("InspectorAmbitus", "G♯♯") + text.text; break; - case 30: text.text = qsTranslate("InspectorAmbitus", "D♯♯") + text.text; break; - case 31: text.text = qsTranslate("InspectorAmbitus", "A♯♯") + text.text; break; - case 32: text.text = qsTranslate("InspectorAmbitus", "E♯♯") + text.text; break; - case 33: text.text = qsTranslate("InspectorAmbitus", "B♯♯") + text.text; break; - default: text.text = qsTr("?") + text.text; break; + case -1: name = qsTranslate("InspectorAmbitus", "F♭♭"); break; + case 0: name = qsTranslate("InspectorAmbitus", "C♭♭"); break; + case 1: name = qsTranslate("InspectorAmbitus", "G♭♭"); break; + case 2: name = qsTranslate("InspectorAmbitus", "D♭♭"); break; + case 3: name = qsTranslate("InspectorAmbitus", "A♭♭"); break; + case 4: name = qsTranslate("InspectorAmbitus", "E♭♭"); break; + case 5: name = qsTranslate("InspectorAmbitus", "B♭♭"); break; + case 6: name = qsTranslate("InspectorAmbitus", "F♭"); break; + case 7: name = qsTranslate("InspectorAmbitus", "C♭"); break; + + case 8: name = qsTranslate("InspectorAmbitus", "G♭"); break; + case 9: name = qsTranslate("InspectorAmbitus", "D♭"); break; + case 10: name = qsTranslate("InspectorAmbitus", "A♭"); break; + case 11: name = qsTranslate("InspectorAmbitus", "E♭"); break; + case 12: name = qsTranslate("InspectorAmbitus", "B♭"); break; + case 13: name = qsTranslate("InspectorAmbitus", "F"); break; + case 14: name = qsTranslate("InspectorAmbitus", "C"); break; + case 15: name = qsTranslate("InspectorAmbitus", "G"); break; + case 16: name = qsTranslate("InspectorAmbitus", "D"); break; + case 17: name = qsTranslate("InspectorAmbitus", "A"); break; + case 18: name = qsTranslate("InspectorAmbitus", "E"); break; + case 19: name = qsTranslate("InspectorAmbitus", "B"); break; + + case 20: name = qsTranslate("InspectorAmbitus", "F♯"); break; + case 21: name = qsTranslate("InspectorAmbitus", "C♯"); break; + case 22: name = qsTranslate("InspectorAmbitus", "G♯"); break; + case 23: name = qsTranslate("InspectorAmbitus", "D♯"); break; + case 24: name = qsTranslate("InspectorAmbitus", "A♯"); break; + case 25: name = qsTranslate("InspectorAmbitus", "E♯"); break; + case 26: name = qsTranslate("InspectorAmbitus", "B♯"); break; + case 27: name = qsTranslate("InspectorAmbitus", "F♯♯"); break; + case 28: name = qsTranslate("InspectorAmbitus", "C♯♯"); break; + case 29: name = qsTranslate("InspectorAmbitus", "G♯♯"); break; + case 30: name = qsTranslate("InspectorAmbitus", "D♯♯"); break; + case 31: name = qsTranslate("InspectorAmbitus", "A♯♯"); break; + case 32: name = qsTranslate("InspectorAmbitus", "E♯♯"); break; + case 33: name = qsTranslate("InspectorAmbitus", "B♯♯"); break; + default: name = qsTr("?") + text.text; break; } // end switch tpc // octave, middle C being C4 - //text.text += (Math.floor(notes[i].pitch / 12) - 1) + //oct = (Math.floor(notes[i].pitch / 12) - 1) // or - //text.text += (Math.floor(notes[i].ppitch / 12) - 1) + //oct = (Math.floor(notes[i].ppitch / 12) - 1) // or even this, similar to the Helmholtz system but one octave up //var octaveTextPostfix = [",,,,,", ",,,,", ",,,", ",,", ",", "", "'", "''", "'''", "''''", "'''''"]; - //text.text += octaveTextPostfix[Math.floor(notes[i].pitch / 12)]; + //oct = octaveTextPostfix[Math.floor(notes[i].pitch / 12)]; + text.text = name + oct + text.text // change below false to true for courtesy- and microtonal accidentals // you might need to come up with suitable translations @@ -194,8 +192,8 @@ MuseScore { // First...we need to scan grace notes for existence and break them // into their appropriate lists with the correct ordering of notes. - var leadingLifo = new Array(); // List for leading grace notes - var trailingFifo = new Array(); // List for trailing grace notes + var leadingLifo = Array(); // List for leading grace notes + var trailingFifo = Array(); // List for trailing grace notes var graceChords = cursor.element.graceNotes; // Build separate lists of leading and trailing grace note chords. if (graceChords.length > 0) { diff --git a/share/plugins/panel.qml b/share/plugins/panel.qml deleted file mode 100644 index 557c5439bc8b0..0000000000000 --- a/share/plugins/panel.qml +++ /dev/null @@ -1,55 +0,0 @@ -/* - * SPDX-License-Identifier: GPL-3.0-only - * MuseScore-CLA-applies - * - * MuseScore - * Music Composition & Notation - * - * Copyright (C) 2021 MuseScore BVBA and others - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * 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 . - */ -import QtQuick 2.0 -import MuseScore 3.0 - - -MuseScore { - menuPath: "Plugins.panel" - version: "3.0" - description: "This demo plugin creates a GUI panel." - requiresScore: false - - pluginType: "dock" - dockArea: "left" - - width: 150 - height: 75 - onRun: console.log("hello panel"); - - Rectangle { - color: "grey" - anchors.fill: parent - - Text { - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - text: "Hello Panel" - } - - MouseArea { - anchors.fill: parent - onClicked: Qt.quit() - } - } - } - diff --git a/share/plugins/random.qml b/share/plugins/random.qml deleted file mode 100644 index 4233709174665..0000000000000 --- a/share/plugins/random.qml +++ /dev/null @@ -1,84 +0,0 @@ -/* - * SPDX-License-Identifier: GPL-3.0-only - * MuseScore-CLA-applies - * - * MuseScore - * Music Composition & Notation - * - * Copyright (C) 2021 MuseScore BVBA and others - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * 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 . - */ -import QtQuick 2.1 -import MuseScore 3.0 - -MuseScore { - version: "3.0" - description: "Create random score." - menuPath: "Plugins.random" - requiresScore: false - - function addNote(key, cursor) { - var cdur = [ 0, 2, 4, 5, 7, 9, 11 ]; - // c g d e - var keyo = [ 0, 7, 2, 4 ]; - - var idx = Math.random() * 6; - var octave = Math.floor(Math.random() * 2); - var pitch = cdur[Math.floor(idx)] + octave * 12 + 60 + keyo[key]; - cursor.addNote(pitch); - } - - onRun: { - var measures = 18; //in 4/4 default time signature - var numerator = 3; - var denominator = 4; - var octaves = 2; - var key = 3; - - var score = newScore("Random.mscz", "piano", measures); - - score.addText("title", "==Random=="); - score.addText("subtitle", "subtitle"); - - var cursor = score.newCursor(); - cursor.track = 0; - - cursor.rewind(0); - - var ts = newElement(Element.TIMESIG); - ts.timesig = fraction(numerator, denominator); - cursor.add(ts); - - cursor.rewind(0); - - var realMeasures = Math.ceil(measures * denominator / numerator); - console.log(realMeasures); - var notes = realMeasures * 4; //number of 1/4th notes - - for (var i = 0; i < notes; ++i) { - - if (Math.random() < 0.5) { - cursor.setDuration(1, 8); - addNote(key, cursor); - addNote(key, cursor); - } - else { - cursor.setDuration(1, 4); - addNote(key, cursor); - } - - } - Qt.quit(); - } - } diff --git a/share/plugins/random2.qml b/share/plugins/random2.qml deleted file mode 100644 index a729781df6b62..0000000000000 --- a/share/plugins/random2.qml +++ /dev/null @@ -1,128 +0,0 @@ -/* - * SPDX-License-Identifier: GPL-3.0-only - * MuseScore-CLA-applies - * - * MuseScore - * Music Composition & Notation - * - * Copyright (C) 2021 MuseScore BVBA and others - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * 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 . - */ -import QtQuick 2.1 -import MuseScore 3.0 -import QtQuick.Controls 1.0 -import QtQuick.Layouts 1.0 - -MuseScore { - version: "3.0" - description: "Create random score." - menuPath: "Plugins.random2" - requiresScore: false - pluginType: "dock" - dockArea: "left" - width: 150 - height: 75 - - onRun: { } - - function addNote(key, cursor) { - var cdur = [ 0, 2, 4, 5, 7, 9, 11 ]; - // c g d e - var keyo = [ 0, 7, 2, 4 ]; - - var idx = Math.random() * 6; - var octave = Math.floor(Math.random() * octaves.value); - var pitch = cdur[Math.floor(idx)] + octave * 12 + 60 + keyo[key]; - console.log("Add note pitch "+pitch); - cursor.addNote(pitch); - } - - function createScore() { - var measures = 18; //in 4/4 default time signature - var numerator = 3; - var denominator = 4; - var key = 2; //index in keyo from addNote function above - - var score = newScore("Random2.mscz", "piano", measures); - - score.startCmd(); - score.addText("title", "==Random2=="); - score.addText("subtitle", "Another subtitle"); - - var cursor = score.newCursor(); - cursor.track = 0; - - cursor.rewind(0); - - var ts = newElement(Element.TIMESIG); - ts.timesig = fraction(numerator, denominator); - cursor.add(ts); - - var realMeasures = Math.ceil(measures * denominator / numerator); - console.log(realMeasures); - var notes = realMeasures * 4; //number of 1/4th notes - - for (var staff = 0; staff < 2; ++staff) { //piano has two staves to fill - cursor.track = staff * 4; //4 voice tracks per staff - cursor.rewind(0); //go to the start of the score - //add notes - for (var i = 0; i < notes; ++i) { - if (Math.random() < 0.4) { - console.log("Adding two notes at ", i); - cursor.setDuration(1, 8); - addNote(key, cursor); - addNote(key, cursor); - } - else { - console.log("Adding note at ", i); - cursor.setDuration(1, 4); - addNote(key, cursor); - } - } //done adding notes to this staff - } - score.endCmd(); - Qt.quit(); - } - - GridLayout { - anchors.fill: parent - columns: 2 - rowSpacing: 5 - - - Text { - text: "Octaves" - color: "white" - } - - SpinBox { - id: octaves - minimumValue: 1 - maximumValue: 3 - stepSize: 1 - Layout.fillWidth: true - Layout.preferredHeight: 25 - value: 1 - } - - Button { - text: "create" - Layout.columnSpan: 2 - Layout.fillWidth: true - onClicked: { - createScore() - } - } - } - } diff --git a/share/plugins/removeCourtesyAccidentals.qml b/share/plugins/removeCourtesyAccidentals.qml new file mode 100644 index 0000000000000..e8e6047fa18af --- /dev/null +++ b/share/plugins/removeCourtesyAccidentals.qml @@ -0,0 +1,314 @@ +//============================================== +// remove courtesy accidentals v1.0 +// +// Copyright (C)2012-2019 Jörn Eichler (heuchi) +// +// 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 . +//============================================== + +import QtQuick 2.0 +import QtQuick.Dialogs 1.2 +import MuseScore 3.0 + +MuseScore { + version: "1.0" + description: "This plugin removes courtesy accidentals" + menuPath: "Plugins.Accidentals.Remove Courtesy Accidentals" + + //pluginType: "dock" + requiresScore: true + + // if nothing is selected process whole score + property bool processAll: false + + MessageDialog { + id: versionError + visible: false + title: "Unsupported MuseScore Version" + text: "This plugin needs MuseScore v3.0.5 or higher" + onAccepted: { Qt.quit() } + } + + // function tpcName + // + // return name of note + + function tpcName(tpc) { + var tpcNames = new Array( + "Fbb", "Cbb", "Gbb", "Dbb", "Abb", "Ebb", "Bbb", + "Fb", "Cb", "Gb", "Db", "Ab", "Eb", "Bb", + "F", "C", "G", "D", "A", "E", "B", + "F#", "C#", "G#", "D#", "A#", "E#", "B#", + "F##", "C##", "G##", "D##", "A##", "E##", "B##" + ); + + return(tpcNames[tpc+1]); + } + + // function processNote + // + // for each measure we create a table that contains + // the actual 'noteName' of each 'noteClass' + // + // a 'noteClass' is the natural name of a space + // or line of the staff and the octave: + // C5, F6, B3 are 'noteClass' + // + // a 'noteName' would be C, F#, Bb for example + // (we don't need the octave here) + // + // curMeasureArray[] = + + function processNote(note,curMeasureArray,keySig) { + var octave=Math.floor(note.pitch/12); + + // correct octave for Cb and Cbb + if(note.tpc1 == 7 || note.tpc1 == 0) { + octave++; // belongs to higher octave + } + // correct octave for B# and B## + if(note.tpc1 == 26 || note.tpc1 == 33) { + octave--; // belongs to lower octave + } + + var noteName = tpcName(note.tpc); + var noteClass = noteName.charAt(0)+octave; + + // a tied back note never needs an accidental + if (note.tieBack != null) { + if(note.accidental != null) { + // security checks + var thisPitch = note.pitch; + var thisAcc = note.accidentalType; + + // remove + note.accidentalType = Accidental.NONE; + + // if pitch changed, we were wrong... + if(note.pitch != thisPitch) { + console.log("ERROR1: pitch of note changed!"); + //note.color = "#ff0000"; + note.accidentalType = thisAcc; + } + } + // if the tied back note is not part of + // the current key sig, we need to remeber it. + //if( ! (note.tpc > keySig+12 && note.tpc < keySig+20)) { + // curMeasureArray[noteClass]=noteName; + //} + + // we're done for a tied back note. + return; + } + + // check if current note needs acc + if(typeof curMeasureArray[noteClass] !== 'undefined') { + // we have information on the previous note + // in the same measure: + // if this note is the same noteClass and noteName + // it doesn't need an accidental + if(curMeasureArray[noteClass] == noteName) { + // remove accidental if present + if(note.accidental != null) { + // security checks + var thisPitch = note.pitch; + var thisAcc = note.accidentalType; + + // remove + note.accidentalType = Accidental.NONE; + + // if pitch changed, we were wrong... + if(note.pitch != thisPitch) { + console.log("ERROR2: pitch of note changed!"); + //note.color = "#ff0000"; + note.accidentalType = thisAcc; + } + } + } + } else { + // we don't have this note in the current measure + // so it depends on the current key signature + if(note.tpc > keySig+12 && note.tpc < keySig+20) { + // we don't need an accidental in the current key sig + // remove accidental if present + if(note.accidental != null) { + // security checks + var thisPitch = note.pitch; + var thisAcc = note.accidentalType; + + // remove + note.accidentalType = Accidental.NONE; + + // if pitch changed, we were wrong... + if(note.pitch != thisPitch) { + console.log("ERROR3: pitch of note changed!"); + //note.color = "#ff0000"; + note.accidentalType = thisAcc; + console.log("KeySig="+keySig+", tpc="+note.tpc); + } + } + } + } + + curMeasureArray[noteClass]=noteName; + } + + // function processPart + // + // do the actual work: process all given tracks in parallel + // add courtesy accidentals where needed. + // + // We go through all tracks simultaneously, because we also want courtesy + // accidentals for notes across different staves when they are in the + // same octave and for notes of different voices in the same octave + + function processPart(cursor,endTick,startTrack,endTrack) { + if(processAll) { + // we need to reset track first, otherwise + // rewind(0) doesn't work correctly + // we need to set staffIdx and voice to + // get correct key signature. + cursor.staffIdx = startTrack / 4; + cursor.voice = 0; + cursor.rewind(0); + } else { + cursor.rewind(1); + // we need to set staffIdx and voice to + // get correct key signature. + cursor.staffIdx = startTrack / 4; + cursor.voice = 0; + } + + var segment = cursor.segment; + + // we use the cursor to know measure boundaries + // and to get the current key signature + var keySig = cursor.keySignature; + cursor.nextMeasure(); + + var curMeasureArray = new Array(); + + // we use a segment, because the cursor always proceeds to + // the next element in the given track and we don't know + // in which track the element is. + var inLastMeasure=false; + while(segment && (processAll || segment.tick < endTick)) { + // check if still inside same measure + if(!inLastMeasure && !(segment.tick < cursor.tick)) { + // new measure + curMeasureArray = new Array(); + keySig = cursor.keySignature; + if(!cursor.nextMeasure()) { + inLastMeasure=true; + } + } + + for(var track=startTrack; track 0) { + var graceChords = segment.elementAt(track).graceNotes; + + for(var j=0;j. - */ - -import QtQuick 2.0 -import MuseScore 3.0 - -MuseScore { - menuPath: "Plugins.run" - version: "3.0" - description: "This demo plugin runs an external command." - requiresScore: false - - QProcess { - id: proc - } - - onRun: { - console.log("run ls"); - //proc.start("/bin/ls"); // Linux, Mac(?) - proc.start("cmd.exe /c dir"); // Windows - var val = proc.waitForFinished(30000); - if (val) - console.log(proc.readAllStandardOutput()); - Qt.quit() - } - } - diff --git a/share/plugins/scorelist.qml b/share/plugins/scorelist.qml deleted file mode 100644 index 982e451642bd9..0000000000000 --- a/share/plugins/scorelist.qml +++ /dev/null @@ -1,60 +0,0 @@ -/* - * SPDX-License-Identifier: GPL-3.0-only - * MuseScore-CLA-applies - * - * MuseScore - * Music Composition & Notation - * - * Copyright (C) 2021 MuseScore BVBA and others - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * 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 . - */ -import QtQuick 2.0 -import MuseScore 3.0 - - -MuseScore { - menuPath: "Plugins.scorelist" - version: "3.0" - description: "This test plugin iterates through the score list." - pluginType: "dialog" - - width: 150 - height: 75 - Component.onCompleted: { - console.log("hello scorelist"); - for (var i = 0; i < scores.length; i++) { - console.log(scores[i].name); - scoreList.text = scoreList.text + scores[i].name + '\n'; - } - } - - Rectangle { - color: "grey" - anchors.fill: parent - - Text { - id: scoreList - anchors.fill: parent - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - text: "" - } - - MouseArea { - anchors.fill: parent - onClicked: Qt.quit() - } - } - } - diff --git a/share/plugins/tuning.qml b/share/plugins/tuning.qml new file mode 100644 index 0000000000000..dba694558620f --- /dev/null +++ b/share/plugins/tuning.qml @@ -0,0 +1,1481 @@ +// Apply a choice of tempraments and tunings. +// Copyright (C) 2018-2019 Bill Hails +// +// 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 . + +import MuseScore 3.0 +import QtQuick 2.2 +import QtQuick.Controls 1.1 +import QtQuick.Controls.Styles 1.3 +import QtQuick.Layouts 1.1 +import QtQuick.Dialogs 1.1 +import FileIO 3.0 + +MuseScore { + version: "3.0.5" + menuPath: "Plugins.Playback.Tuning" + description: "Apply various temperaments and tunings" + pluginType: "dialog" + width: 550 + height: 500 + + property var offsetTextWidth: 40; + property var offsetLabelAlignment: 0x02 | 0x80; + + property var history: 0; + + // set true if customisations are made to the tuning + property var modified: false; + + /** + * See http://leware.net/temper/temper.htm and specifically http://leware.net/temper/cents.htm + * + * I've taken the liberty of adding the Bach/Lehman temperament http://www.larips.com which was + * my original motivation for doing this. + * + * These values are in cents. One cent is defined as 100th of an equal tempered semitone. + * Each row is ordered in the cycle of fifths, so C, G, D, A, E, B, F#, C#, G#/Ab, Eb, Bb, F; + * and the values are offsets from the equal tempered value. + * + * However for tunings who's default root note is not C, the values are pre-rotated so that applying the + * root note rotation will put the first value of the sequence at the root note. + */ + property var equal: { + 'offsets': [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + 'root': 0, + 'pure': 0, + 'name': "equal" + } + property var pythagorean: { + 'offsets': [-6.0, -4.0, -2.0, 0.0, 2.0, 4.0, 6.0, 8.0, 10.0, 12.0, 14.0, 16.0], + 'root': 9, + 'pure': 3, + 'name': "pythagorean" + } + property var aaron: { + 'offsets': [10.5, 7.0, 3.5, 0.0, -3.5, -7.0, -10.5, -14.0, -17.5, -21.0, -24.5, -28.0], + 'root': 9, + 'pure': 3, + 'name': "aaron" + } + property var silberman: { + 'offsets': [5.0, 3.3, 1.7, 0.0, -1.7, -3.3, -5.0, -6.7, -8.3, -10.0, -11.7, -13.3], + 'root': 9, + 'pure': 3, + 'name': "silberman" + } + property var salinas: { + 'offsets': [16.0, 10.7, 5.3, 0.0, -5.3, -10.7, -16.0, -21.3, -26.7, -32.0, -37.3, -42.7], + 'root': 9, + 'pure': 3, + 'name': "salinas" + } + property var kirnberger: { + 'offsets': [0.0, -3.5, -7.0, -10.5, -14.0, -12.0, -10.0, -10.0, -8.0, -6.0, -4.0, -2.0], + 'root': 0, + 'pure': 0, + 'name': "kirnberger" + } + property var vallotti: { + 'offsets': [0.0, -2.0, -4.0, -6.0, -8.0, -10.0, -8.0, -6.0, -4.0, -2.0, 0.0, 2.0], + 'root': 0, + 'pure': 0, + 'name': "vallotti" + } + property var werkmeister: { + 'offsets': [0.0, -4.0, -8.0, -12.0, -10.0, -8.0, -12.0, -10.0, -8.0, -6.0, -4.0, -2.0], + 'root': 0, + 'pure': 0, + 'name': "werkmeister" + } + property var marpurg: { + 'offsets': [0.0, 2.0, 4.0, 6.0, 0.0, 2.0, 4.0, 6.0, 0.0, 2.0, 4.0, 6.0], + 'root': 0, + 'pure': 0, + 'name': "marpurg" + } + property var just: { + 'offsets': [0.0, 2.0, 4.0, -16.0, -14.0, -12.0, -10.0, -30.0, -28.0, 16.0, 18.0, -2.0], + 'root': 0, + 'pure': 0, + 'name': "just" + } + property var meanSemitone: { + 'offsets': [0.0, -3.5, -7.0, -10.5, -14.0, 3.5, 0.0, -3.5, -7.0, -10.5, -14.0, -17.5], + 'root': 6, + 'pure': 6, + 'name': "meanSemitone" + } + property var grammateus: { + 'offsets': [-2.0, 0.0, 2.0, 4.0, 6.0, 8.0, 10.0, 0.0, 2.0, 4.0, 6.0, 8.0], + 'root': 11, + 'pure': 1, + 'name': "grammateus" + } + property var french: { + 'offsets': [0.0, -2.5, -5.0, -7.5, -10.0, -12.5, -13.0, -13.0, -11.0, -6.0, -1.5, 2.5], + 'root': 0, + 'pure': 0, + 'name': "french" + } + property var french2: { + 'offsets': [0.0, -3.5, -7.0, -10.5, -14.0, -17.5, -18.2, -19.0, -17.0, -10.5, -3.5, 3.5], + 'root': 0, + 'pure': 0, + 'name': "french2" + } + property var rameau: { + 'offsets': [0.0, -3.5, -7.0, -10.5, -14.0, -17.5, -15.5, -13.5, -11.5, -2.0, 7.0, 3.5], + 'root': 0, + 'pure': 0, + 'name': "rameau" + } + property var irrFr17e: { + 'offsets': [-8.0, -2.0, 3.0, 0.0, -3.0, -6.0, -9.0, -12.0, -15.0, -18.0, -21.0, -24.0], + 'root': 9, + 'pure': 3, + 'name': "irrFr17e" + } + property var bachLehman: { + 'offsets': [0.0, -2.0, -3.9, -5.9, -7.8, -5.9, -3.9, -2.0, -2.0, -2.0, -2.0, 2.0], + 'root': 0, + 'pure': 3, + 'name': "bachLehman" + } + + property var currentTemperament: equal; + property var currentRoot: 0; + property var currentPureTone: 0; + property var currentTweak: 0.0; + + onRun: { + if (!curScore) { + error("No score open.\nThis plugin requires an open score to run.\n") + Qt.quit() + } + } + + function getHistory() { + if (history == 0) { + history = new commandHistory() + } + return history + } + + function applyTemperament() + { + var selection = new scoreSelection() + curScore.startCmd() + selection.map(filterNotes, reTune(getFinalTuning())) + if (annotateValue.checkedState == Qt.Checked) { + selection.map(filterNotes, annotate) + } + curScore.endCmd() + return true + } + + function filterNotes(element) + { + return element.type == Ms.CHORD + } + + function annotate(chord, cursor) + { + function addText(noteIndex, placement) { + var note = chord.notes[noteIndex] + var text = newElement(Element.STAFF_TEXT); + text.text = '' + note.tuning + text.autoplace = true + text.fontSize = 7 // smaller + text.placement = placement + cursor.add(text) + } + + if (cursor.voice == 0 || cursor.voice == 2) { + for (var index = 0; index < chord.notes.length; index++) { + addText(index, Placement.ABOVE) + } + } else { + for (var index = chord.notes.length - 1; index >= 0; index--) { + addText(index, Placement.BELOW) + } + } + } + + function reTune(tuning) { + return function(chord, cursor) { + for (var i = 0; i < chord.notes.length; i++) { + var note = chord.notes[i] + note.tuning = tuning(note.pitch) + } + } + } + + function scoreSelection() { + const SCORE_START = 0 + const SELECTION_START = 1 + const SELECTION_END = 2 + var fullScore + var startStaff + var endStaff + var endTick + var inRange + var rewind + var cursor = curScore.newCursor() + cursor.rewind(SELECTION_START) + if (cursor.segment) { + startStaff = cursor.staffIdx + cursor.rewind(SELECTION_END) + endStaff = cursor.staffIdx; + endTick = 0 // unused + if (cursor.tick === 0) { + endTick = curScore.lastSegment.tick + 1; + } else { + endTick = cursor.tick; + } + inRange = function() { + return cursor.segment && cursor.tick < endTick + } + rewind = function (voice, staff) { + // no idea why, but if there is a selection then + // we need to rewind the cursor *before* setting + // the voice and staff index. + cursor.rewind(SELECTION_START) + cursor.voice = voice + cursor.staffIdx = staff + } + } else { + startStaff = 0 + endStaff = curScore.nstaves - 1 + inRange = function () { + return cursor.segment + } + rewind = function (voice, staff) { + // no idea why, but if there's no selection then + // we need to rewind the cursor *after* setting + // the voice and staff index. + cursor.voice = voice + cursor.staffIdx = staff + cursor.rewind(SCORE_START) + } + } + + this.map = function(filter, process) { + for (var staff = startStaff; staff <= endStaff; staff++) { + for (var voice = 0; voice < 4; voice++) { + rewind(voice, staff) + while (inRange()) { + if (cursor.element && filter(cursor.element)) { + process(cursor.element, cursor) + } + cursor.next() + } + } + } + } + } + + function error(errorMessage) { + errorDialog.text = qsTr(errorMessage) + errorDialog.open() + } + + /** + * map a note (pitch modulo 12) to a value in one of the above tables + * then adjust for the choice of pure note and tweak. + */ + function lookUp(note, table) { + var i = ((note * 7) - currentRoot + 12) % 12; + var offset = table.offsets[i]; + var j = (currentPureTone - currentRoot + 12) % 12; + var pureNoteAdjustment = table.offsets[j]; + var finalOffset = offset - pureNoteAdjustment; + var tweakFinalOffset = finalOffset + parseFloat(tweakValue.text); + return tweakFinalOffset + } + + /** + * returns a function for use by recalculate() + * + * We use an abstract function here because recalculate can be passed + * a different function, i.e. when restoring from a save file. + */ + function getTuning() { + return function(pitch) { + return lookUp(pitch, currentTemperament); + } + } + + function getFinalTuning() { + return function(pitch) { + pitch = pitch % 12 + switch (pitch) { + case 0: + return getFinalOffset(final_c) + case 1: + return getFinalOffset(final_c_sharp) + case 2: + return getFinalOffset(final_d) + case 3: + return getFinalOffset(final_e_flat) + case 4: + return getFinalOffset(final_e) + case 5: + return getFinalOffset(final_f) + case 6: + return getFinalOffset(final_f_sharp) + case 7: + return getFinalOffset(final_g) + case 8: + return getFinalOffset(final_g_sharp) + case 9: + return getFinalOffset(final_a) + case 10: + return getFinalOffset(final_b_flat) + case 11: + return getFinalOffset(final_b) + default: + error("unrecognised pitch: " + pitch) + } + } + } + + function getFinalOffset(textField) { + return parseFloat(textField.text) + } + + function recalculate(tuning) { + var old_final_c = final_c.text + var old_final_c_sharp = final_c_sharp.text + var old_final_d = final_d.text + var old_final_e_flat = final_e_flat.text + var old_final_e = final_e.text + var old_final_f = final_f.text + var old_final_f_sharp = final_f_sharp.text + var old_final_g = final_g.text + var old_final_g_sharp = final_g_sharp.text + var old_final_a = final_a.text + var old_final_b_flat = final_b_flat.text + var old_final_b = final_b.text + getHistory().add( + function () { + final_c.text = old_final_c + final_c.previousText = old_final_c + final_c_sharp.text = old_final_c_sharp + final_c_sharp.previousText = old_final_c_sharp + final_d.text = old_final_d + final_d.previousText = old_final_d + final_e_flat.text = old_final_e_flat + final_e_flat.previousText = old_final_e_flat + final_e.text = old_final_e + final_e.previousText = old_final_e + final_f.text = old_final_f + final_f.previousText = old_final_f + final_f_sharp.text = old_final_f_sharp + final_f_sharp.previousText = old_final_f_sharp + final_g.text = old_final_g + final_g.previousText = old_final_g + final_g_sharp.text = old_final_g_sharp + final_g_sharp.previousText = old_final_g_sharp + final_a.text = old_final_a + final_a.previousText = old_final_a + final_b_flat.text = old_final_b_flat + final_b_flat.previousText = old_final_b_flat + final_b.text = old_final_b + final_b.previousText = old_final_b + }, + function() { + final_c.text = tuning(0).toFixed(1) + final_c.previousText = final_c.text + final_c_sharp.text = tuning(1).toFixed(1) + final_c_sharp.previousText = final_c_sharp.text + final_d.text = tuning(2).toFixed(1) + final_d.previousText = final_d.text + final_e_flat.text = tuning(3).toFixed(1) + final_e_flat.previousText = final_e_flat.text + final_e.text = tuning(4).toFixed(1) + final_e.previousText = final_e.text + final_f.text = tuning(5).toFixed(1) + final_f.previousText = final_f.text + final_f_sharp.text = tuning(6).toFixed(1) + final_f_sharp.previousText = final_f_sharp.text + final_g.text = tuning(7).toFixed(1) + final_g.previousText = final_g.text + final_g_sharp.text = tuning(8).toFixed(1) + final_g_sharp.previousText = final_g_sharp.text + final_a.text = tuning(9).toFixed(1) + final_a.previousText = final_a.text + final_b_flat.text = tuning(10).toFixed(1) + final_b_flat.previousText = final_b_flat.text + final_b.text = tuning(11).toFixed(1) + final_b.previousText = final_b.text + }, + "final offsets" + ) + } + + function setCurrentTemperament(temperament) { + var oldTemperament = currentTemperament + getHistory().add( + function() { + currentTemperament = oldTemperament + checkCurrentTemperament() + }, + function() { + currentTemperament = temperament + checkCurrentTemperament() + }, + "current temperament" + ) + } + + function checkCurrentTemperament() { + switch (currentTemperament.name) { + case "equal": + equal_button.checked = true + return + case "pythagorean": + pythagorean_button.checked = true + return + case "aaron": + aaron_button.checked = true + return + case "silberman": + silberman_button.checked = true + return + case "salinas": + salinas_button.checked = true + return + case "kirnberger": + kirnberger_button.checked = true + return + case "vallotti": + vallotti_button.checked = true + return + case "werkmeister": + werkmeister_button.checked = true + return + case "marpurg": + marpurg_button.checked = true + return + case "just": + just_button.checked = true + return + case "meanSemitone": + meanSemitone_button.checked = true + return + case "grammateus": + grammateus_button.checked = true + return + case "french": + french_button.checked = true + return + case "french2": + french2_button.checked = true + return + case "rameau": + rameau_button.checked = true + return + case "irrFr17e": + irrFr17e_button.checked = true + return + case "bachLehman": + bachLehman_button.checked = true + return + } + } + + function lookupTemperament(temperamentName) { + switch (temperamentName) { + case "equal": + return equal + case "pythagorean": + return pythagorean + case "aaron": + return aaron + case "silberman": + return silberman + case "salinas": + return salinas + case "kirnberger": + return kirnberger + case "vallotti": + return vallotti + case "werkmeister": + return werkmeister + case "marpurg": + return marpurg + case "just": + return just + case "meanSemitone": + return meanSemitone + case "grammateus": + return grammateus + case "french": + return french + case "french2": + return french2 + case "rameau": + return rameau + case "irrFr17e": + return irrFr17e + case "bachLehman": + return bachLehman + } + } + + function setCurrentRoot(root) { + var oldRoot = currentRoot + getHistory().add( + function () { + currentRoot = oldRoot + checkCurrentRoot() + }, + function() { + currentRoot = root + checkCurrentRoot() + }, + "current root" + ) + } + + function checkCurrentRoot() { + switch (currentRoot) { + case 0: + root_c.checked = true + break + case 1: + root_g.checked = true + break + case 2: + root_d.checked = true + break + case 3: + root_a.checked = true + break + case 4: + root_e.checked = true + break + case 5: + root_b.checked = true + break + case 6: + root_f_sharp.checked = true + break + case 7: + root_c_sharp.checked = true + break + case 8: + root_g_sharp.checked = true + break + case 9: + root_e_flat.checked = true + break + case 10: + root_b_flat.checked = true + break + case 11: + root_f.checked = true + break + } + } + + function setCurrentPureTone(pureTone) { + var oldPureTone = currentPureTone + getHistory().add( + function () { + currentPureTone = oldPureTone + checkCurrentPureTone() + }, + function() { + currentPureTone = pureTone + checkCurrentPureTone() + }, + "current pure tone" + ) + } + + function setCurrentTweak(tweak) { + var oldTweak = currentTweak + getHistory().add( + function () { + currentTweak = oldTweak + checkCurrentTweak() + }, + function () { + currentTweak = tweak + checkCurrentTweak() + }, + "current tweak" + ) + } + + function checkCurrentTweak() { + tweakValue.text = currentTweak.toFixed(1) + } + + function checkCurrentPureTone() { + switch (currentPureTone) { + case 0: + pure_c.checked = true + break + case 1: + pure_g.checked = true + break + case 2: + pure_d.checked = true + break + case 3: + pure_a.checked = true + break + case 4: + pure_e.checked = true + break + case 5: + pure_b.checked = true + break + case 6: + pure_f_sharp.checked = true + break + case 7: + pure_c_sharp.checked = true + break + case 8: + pure_g_sharp.checked = true + break + case 9: + pure_e_flat.checked = true + break + case 10: + pure_b_flat.checked = true + break + case 11: + pure_f.checked = true + break + } + } + + function setModified(state) { + var oldModified = modified + getHistory().add( + function () { + modified = oldModified + }, + function () { + modified = state + }, + "modified" + ) + } + + function temperamentClicked(temperament) { + getHistory().begin() + setCurrentTemperament(temperament) + setCurrentRoot(currentTemperament.root) + setCurrentPureTone(currentTemperament.pure) + setCurrentTweak(0.0) + recalculate(getTuning()) + getHistory().end() + } + + function rootNoteClicked(note) { + getHistory().begin() + setModified(true) + setCurrentRoot(note) + setCurrentPureTone(note) + setCurrentTweak(0.0) + recalculate(getTuning()) + getHistory().end() + } + + function pureToneClicked(note) { + getHistory().begin() + setModified(true) + setCurrentPureTone(note) + setCurrentTweak(0.0) + recalculate(getTuning()) + getHistory().end() + } + + function tweaked() { + getHistory().begin() + setModified(true) + setCurrentTweak(parseFloat(tweakValue.text)) + recalculate(getTuning()) + getHistory().end() + } + + function editingFinishedFor(textField) { + var oldText = textField.previousText + var newText = textField.text + getHistory().begin() + setModified(true) + getHistory().add( + function () { + textField.text = oldText + }, + function () { + textField.text = newText + }, + "edit ".concat(textField.name) + ) + getHistory().end() + textField.previousText = newText + } + + Rectangle { + color: "lightgrey" + anchors.fill: parent + + GridLayout { + columns: 2 + anchors.fill: parent + anchors.margins: 10 + GroupBox { + title: "Temperament" + ColumnLayout { + ExclusiveGroup { id: tempamentTypeGroup } + RadioButton { + id: equal_button + text: "Equal" + checked: true + exclusiveGroup: tempamentTypeGroup + onClicked: { temperamentClicked(equal) } + } + RadioButton { + id: pythagorean_button + text: "Pythagorean" + exclusiveGroup: tempamentTypeGroup + onClicked: { temperamentClicked(pythagorean) } + } + RadioButton { + id: aaron_button + text: "Aaron" + exclusiveGroup: tempamentTypeGroup + onClicked: { temperamentClicked(aaron) } + } + RadioButton { + id: silberman_button + text: "Silberman" + exclusiveGroup: tempamentTypeGroup + onClicked: { temperamentClicked(silberman) } + } + RadioButton { + id: salinas_button + text: "Salinas" + exclusiveGroup: tempamentTypeGroup + onClicked: { temperamentClicked(salinas) } + } + RadioButton { + id: kirnberger_button + text: "Kirnberger" + exclusiveGroup: tempamentTypeGroup + onClicked: { temperamentClicked(kirnberger) } + } + RadioButton { + id: vallotti_button + text: "Vallotti" + exclusiveGroup: tempamentTypeGroup + onClicked: { temperamentClicked(vallotti) } + } + RadioButton { + id: werkmeister_button + text: "Werkmeister" + exclusiveGroup: tempamentTypeGroup + onClicked: { temperamentClicked(werkmeister) } + } + RadioButton { + id: marpurg_button + text: "Marpurg" + exclusiveGroup: tempamentTypeGroup + onClicked: { temperamentClicked(marpurg) } + } + RadioButton { + id: just_button + text: "Just" + exclusiveGroup: tempamentTypeGroup + onClicked: { temperamentClicked(just) } + } + RadioButton { + id: meanSemitone_button + text: "Mean Semitone" + exclusiveGroup: tempamentTypeGroup + onClicked: { temperamentClicked(meanSemitone) } + } + RadioButton { + id: grammateus_button + text: "Grammateus" + exclusiveGroup: tempamentTypeGroup + onClicked: { temperamentClicked(grammateus) } + } + RadioButton { + id: french_button + text: "French" + exclusiveGroup: tempamentTypeGroup + onClicked: { temperamentClicked(french) } + } + RadioButton { + id: french2_button + text: "Tempérament Ordinaire" + exclusiveGroup: tempamentTypeGroup + onClicked: { temperamentClicked(french2) } + } + RadioButton { + id: rameau_button + text: "Rameau" + exclusiveGroup: tempamentTypeGroup + onClicked: { temperamentClicked(rameau) } + } + RadioButton { + id: irrFr17e_button + text: "Irr Fr 17e" + exclusiveGroup: tempamentTypeGroup + onClicked: { temperamentClicked(irrFr17e) } + } + RadioButton { + id: bachLehman_button + text: "Bach/Lehman" + exclusiveGroup: tempamentTypeGroup + onClicked: { temperamentClicked(bachLehman) } + } + } + } + + ColumnLayout { + GroupBox { + title: "Advanced" + ColumnLayout { + GroupBox { + title: "Root Note" + GridLayout { + columns: 4 + anchors.margins: 10 + ExclusiveGroup { id: rootNoteGroup } + RadioButton { + text: "C" + checked: true + exclusiveGroup: rootNoteGroup + id: root_c + onClicked: { rootNoteClicked(0) } + } + RadioButton { + text: "G" + exclusiveGroup: rootNoteGroup + id: root_g + onClicked: { rootNoteClicked(1) } + } + RadioButton { + text: "D" + exclusiveGroup: rootNoteGroup + id: root_d + onClicked: { rootNoteClicked(2) } + } + RadioButton { + text: "A" + exclusiveGroup: rootNoteGroup + id: root_a + onClicked: { rootNoteClicked(3) } + } + RadioButton { + text: "E" + exclusiveGroup: rootNoteGroup + id: root_e + onClicked: { rootNoteClicked(4) } + } + RadioButton { + text: "B" + exclusiveGroup: rootNoteGroup + id: root_b + onClicked: { rootNoteClicked(5) } + } + RadioButton { + text: "F#" + exclusiveGroup: rootNoteGroup + id: root_f_sharp + onClicked: { rootNoteClicked(6) } + } + RadioButton { + text: "C#" + exclusiveGroup: rootNoteGroup + id: root_c_sharp + onClicked: { rootNoteClicked(7) } + } + RadioButton { + text: "G#" + exclusiveGroup: rootNoteGroup + id: root_g_sharp + onClicked: { rootNoteClicked(8) } + } + RadioButton { + text: "Eb" + exclusiveGroup: rootNoteGroup + id: root_e_flat + onClicked: { rootNoteClicked(9) } + } + RadioButton { + text: "Bb" + exclusiveGroup: rootNoteGroup + id: root_b_flat + onClicked: { rootNoteClicked(10) } + } + RadioButton { + text: "F" + exclusiveGroup: rootNoteGroup + id: root_f + onClicked: { rootNoteClicked(11) } + } + } + } + + GroupBox { + title: "Pure Tone" + GridLayout { + columns: 4 + anchors.margins: 10 + ExclusiveGroup { id: pureToneGroup } + RadioButton { + text: "C" + checked: true + id: pure_c + exclusiveGroup: pureToneGroup + onClicked: { pureToneClicked(0) } + } + RadioButton { + text: "G" + id: pure_g + exclusiveGroup: pureToneGroup + onClicked: { pureToneClicked(1) } + } + RadioButton { + text: "D" + id: pure_d + exclusiveGroup: pureToneGroup + onClicked: { pureToneClicked(2) } + } + RadioButton { + text: "A" + id: pure_a + exclusiveGroup: pureToneGroup + onClicked: { pureToneClicked(3) } + } + RadioButton { + text: "E" + id: pure_e + exclusiveGroup: pureToneGroup + onClicked: { pureToneClicked(4) } + } + RadioButton { + text: "B" + id: pure_b + exclusiveGroup: pureToneGroup + onClicked: { pureToneClicked(5) } + } + RadioButton { + text: "F#" + id: pure_f_sharp + exclusiveGroup: pureToneGroup + onClicked: { pureToneClicked(6) } + } + RadioButton { + text: "C#" + id: pure_c_sharp + exclusiveGroup: pureToneGroup + onClicked: { pureToneClicked(7) } + } + RadioButton { + text: "G#" + id: pure_g_sharp + exclusiveGroup: pureToneGroup + onClicked: { pureToneClicked(8) } + } + RadioButton { + text: "Eb" + id: pure_e_flat + exclusiveGroup: pureToneGroup + onClicked: { pureToneClicked(9) } + } + RadioButton { + text: "Bb" + id: pure_b_flat + exclusiveGroup: pureToneGroup + onClicked: { pureToneClicked(10) } + } + RadioButton { + text: "F" + id: pure_f + exclusiveGroup: pureToneGroup + onClicked: { pureToneClicked(11) } + } + } + } + + GroupBox { + title: "Tweak" + RowLayout { + TextField { + Layout.maximumWidth: offsetTextWidth + id: tweakValue + text: "0.0" + readOnly: false + validator: DoubleValidator { bottom: -99.9; decimals: 1; notation: DoubleValidator.StandardNotation; top: 99.9 } + property var previousText: "0.0" + property var name: "tweak" + onEditingFinished: { tweaked() } + } + } + } + + GroupBox { + title: "Final Offsets" + GridLayout { + columns: 8 + anchors.margins: 0 + + Label { + text: "C" + Layout.alignment: offsetLabelAlignment + } + TextField { + Layout.maximumWidth: offsetTextWidth + id: final_c + text: "0.0" + readOnly: false + validator: DoubleValidator { bottom: -99.9; decimals: 1; notation: DoubleValidator.StandardNotation; top: 99.9 } + property var previousText: "0.0" + property var name: "final C" + onEditingFinished: { editingFinishedFor(final_c) } + } + + Label { + text: "G" + Layout.alignment: offsetLabelAlignment + } + TextField { + Layout.maximumWidth: offsetTextWidth + id: final_g + text: "0.0" + readOnly: false + validator: DoubleValidator { bottom: -99.9; decimals: 1; notation: DoubleValidator.StandardNotation; top: 99.9 } + property var previousText: "0.0" + property var name: "final G" + onEditingFinished: { editingFinishedFor(final_g) } + } + + Label { + text: "D" + Layout.alignment: offsetLabelAlignment + } + TextField { + Layout.maximumWidth: offsetTextWidth + id: final_d + text: "0.0" + readOnly: false + validator: DoubleValidator { bottom: -99.9; decimals: 1; notation: DoubleValidator.StandardNotation; top: 99.9 } + property var previousText: "0.0" + property var name: "final D" + onEditingFinished: { editingFinishedFor(final_d) } + } + + Label { + text: "A" + Layout.alignment: offsetLabelAlignment + } + TextField { + Layout.maximumWidth: offsetTextWidth + id: final_a + text: "0.0" + readOnly: false + validator: DoubleValidator { bottom: -99.9; decimals: 1; notation: DoubleValidator.StandardNotation; top: 99.9 } + property var previousText: "0.0" + property var name: "final A" + onEditingFinished: { editingFinishedFor(final_a) } + } + + Label { + text: "E" + Layout.alignment: offsetLabelAlignment + } + TextField { + Layout.maximumWidth: offsetTextWidth + id: final_e + text: "0.0" + readOnly: false + validator: DoubleValidator { bottom: -99.9; decimals: 1; notation: DoubleValidator.StandardNotation; top: 99.9 } + property var previousText: "0.0" + property var name: "final E" + onEditingFinished: { editingFinishedFor(final_e) } + } + + Label { + text: "B" + Layout.alignment: offsetLabelAlignment + } + TextField { + Layout.maximumWidth: offsetTextWidth + id: final_b + text: "0.0" + readOnly: false + validator: DoubleValidator { bottom: -99.9; decimals: 1; notation: DoubleValidator.StandardNotation; top: 99.9 } + property var previousText: "0.0" + property var name: "final B" + onEditingFinished: { editingFinishedFor(final_b) } + } + + Label { + text: "F#" + Layout.alignment: offsetLabelAlignment + } + TextField { + Layout.maximumWidth: offsetTextWidth + id: final_f_sharp + text: "0.0" + readOnly: false + validator: DoubleValidator { bottom: -99.9; decimals: 1; notation: DoubleValidator.StandardNotation; top: 99.9 } + property var previousText: "0.0" + property var name: "final F#" + onEditingFinished: { editingFinishedFor(final_f_sharp) } + } + + Label { + text: "C#" + Layout.alignment: offsetLabelAlignment + } + TextField { + Layout.maximumWidth: offsetTextWidth + id: final_c_sharp + text: "0.0" + readOnly: false + validator: DoubleValidator { bottom: -99.9; decimals: 1; notation: DoubleValidator.StandardNotation; top: 99.9 } + property var previousText: "0.0" + property var name: "final C#" + onEditingFinished: { editingFinishedFor(final_c_sharp) } + } + + Label { + text: "G#" + Layout.alignment: offsetLabelAlignment + } + TextField { + Layout.maximumWidth: offsetTextWidth + id: final_g_sharp + text: "0.0" + readOnly: false + validator: DoubleValidator { bottom: -99.9; decimals: 1; notation: DoubleValidator.StandardNotation; top: 99.9 } + property var previousText: "0.0" + property var name: "final G#" + onEditingFinished: { editingFinishedFor(final_g_sharp) } + } + + Label { + text: "Eb" + Layout.alignment: offsetLabelAlignment + } + TextField { + Layout.maximumWidth: offsetTextWidth + id: final_e_flat + text: "0.0" + readOnly: false + validator: DoubleValidator { bottom: -99.9; decimals: 1; notation: DoubleValidator.StandardNotation; top: 99.9 } + property var previousText: "0.0" + property var name: "final Eb" + onEditingFinished: { editingFinishedFor(final_e_flat) } + } + + Label { + text: "Bb" + Layout.alignment: offsetLabelAlignment + } + TextField { + Layout.maximumWidth: offsetTextWidth + id: final_b_flat + text: "0.0" + readOnly: false + validator: DoubleValidator { bottom: -99.9; decimals: 1; notation: DoubleValidator.StandardNotation; top: 99.9 } + property var previousText: "0.0" + property var name: "final Bb" + onEditingFinished: { editingFinishedFor(final_b_flat) } + } + + Label { + text: "F" + Layout.alignment: offsetLabelAlignment + } + TextField { + Layout.maximumWidth: offsetTextWidth + id: final_f + text: "0.0" + readOnly: false + validator: DoubleValidator { bottom: -99.9; decimals: 1; notation: DoubleValidator.StandardNotation; top: 99.9 } + property var previousText: "0.0" + property var name: "final F" + onEditingFinished: { editingFinishedFor(final_f) } + } + } + } + RowLayout { + Button { + id: saveButton + text: qsTranslate("PrefsDialogBase", "Save") + onClicked: { + // declaring this directly in the saveDialog's properties doesn't seem to work + saveDialog.folder = Qt.resolvedUrl("file://" + filePath) + saveDialog.visible = true + } + } + Button { + id: loadButton + text: qsTranslate("PrefsDialogBase", "Load") + onClicked: { + loadDialog.folder = Qt.resolvedUrl("file://" + filePath) + loadDialog.visible = true + } + } + Button { + id: undoButton + text: qsTranslate("PrefsDialogBase", "Undo") + onClicked: { + getHistory().undo() + } + } + Button { + id: redoButton + text: qsTranslate("PrefsDialogBase", "Redo") + onClicked: { + getHistory().redo() + } + } + } + } + } + + RowLayout { + Button { + id: applyButton + text: qsTranslate("PrefsDialogBase", "Apply") + onClicked: { + if (applyTemperament()) { + if (modified) { + quitDialog.open() + } else { + Qt.quit() + } + } + } + } + Button { + id: cancelButton + text: qsTranslate("PrefsDialogBase", "Cancel") + onClicked: { + if (modified) { + quitDialog.open() + } else { + Qt.quit() + } + } + } + CheckBox { + id: annotateValue + text: qsTr("Annotate") + checked: false + } + } + } + } + } + + MessageDialog { + id: errorDialog + title: "Error" + text: "" + onAccepted: { + errorDialog.close() + } + } + + MessageDialog { + id: quitDialog + title: "Quit?" + text: "Do you want to quit the plugin?" + detailedText: "It looks like you have made customisations to this tuning, you could save them to a file before quitting if you like." + standardButtons: StandardButton.Ok | StandardButton.Cancel + onAccepted: { + Qt.quit() + } + onRejected: { + quitDialog.close() + } + } + + FileIO { + id: saveFile + source: "" + } + + FileIO { + id: loadFile + source: "" + } + + function getFile(dialog) { + var source = dialog.fileUrl.toString().substring(7) // strip the 'file://' prefix + return source + } + + function formatCurrentValues() { + var data = { + offsets: [ + parseFloat(final_c.text), + parseFloat(final_c_sharp.text), + parseFloat(final_d.text), + parseFloat(final_e_flat.text), + parseFloat(final_e.text), + parseFloat(final_f.text), + parseFloat(final_f_sharp.text), + parseFloat(final_g.text), + parseFloat(final_g_sharp.text), + parseFloat(final_a.text), + parseFloat(final_b_flat.text), + parseFloat(final_b.text) + ], + temperament: currentTemperament.name, + root: currentRoot, + pure: currentPureTone, + tweak: currentTweak + }; + return(JSON.stringify(data)) + } + + function restoreSavedValues(data) { + getHistory().begin() + setCurrentTemperament(lookupTemperament(data.temperament)) + setCurrentRoot(data.root) + setCurrentPureTone(data.pure) + // support older save files + if (data.hasOwnProperty('tweak')) { + setCurrentTweak(data.tweak) + } else { + setCurrentTweak(0.0) + } + recalculate( + function(pitch) { + return data.offsets[pitch % 12] + } + ) + getHistory().end() + } + + FileDialog { + id: loadDialog + title: "Please choose a file" + sidebarVisible: true + onAccepted: { + loadFile.source = getFile(loadDialog) + var data = JSON.parse(loadFile.read()) + restoreSavedValues(data) + loadDialog.visible = false + } + onRejected: { + loadDialog.visible = false + } + visible: false + } + + FileDialog { + id: saveDialog + title: "Please name a file" + sidebarVisible: true + selectExisting: false + onAccepted: { + saveFile.source = getFile(saveDialog) + saveFile.write(formatCurrentValues()) + saveDialog.visible = false + } + onRejected: { + saveDialog.visible = false + } + visible: false + } + + // Command pattern for undo/redo + function commandHistory() { + function Command(undo_fn, redo_fn, label) { + this.undo = undo_fn + this.redo = redo_fn + this.label = label // for debugging + } + + var history = [] + var index = -1 + var transaction = 0 + var maxHistory = 30 + + function newHistory(commands) { + if (index < maxHistory) { + index++ + history = history.slice(0, index) + } else { + history = history.slice(1, index) + } + history.push(commands) + } + + this.add = function(undo, redo, label) { + var command = new Command(undo, redo, label) + command.redo() + if (transaction) { + history[index].push(command) + } else { + newHistory([command]) + } + } + + this.undo = function() { + if (index != -1) { + history[index].slice().reverse().forEach( + function(command) { + command.undo() + } + ) + index-- + } + } + + this.redo = function() { + if ((index + 1) < history.length) { + index++ + history[index].forEach( + function(command) { + command.redo() + } + ) + } + } + + this.begin = function() { + if (transaction) { + throw new Error("already in transaction") + } + newHistory([]) + transaction = 1 + } + + this.end = function() { + if (!transaction) { + throw new Error("not in transaction") + } + transaction = 0 + } + } +} +// vim: ft=javascript diff --git a/share/plugins/view.qml b/share/plugins/view.qml deleted file mode 100644 index 43851277bb0b6..0000000000000 --- a/share/plugins/view.qml +++ /dev/null @@ -1,50 +0,0 @@ -/* - * SPDX-License-Identifier: GPL-3.0-only - * MuseScore-CLA-applies - * - * MuseScore - * Music Composition & Notation - * - * Copyright (C) 2021 MuseScore BVBA and others - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * 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 . - */ -import QtQuick 2.0 -import MuseScore 3.0 - -MuseScore { - version: "3.0" - description: "Demo plugin to demonstrate the use of a ScoreView" - menuPath: "Plugins.ScoreView" - pluginType: "dialog" - - width: 400 - height: 400 - Component.onCompleted: { - if (typeof curScore === 'undefined') - Qt.quit(); - - scoreview.setScore(curScore); - } - - ScoreView { - id: scoreview - anchors.fill: parent - color: "white" - MouseArea { - anchors.fill: parent - onClicked: Qt.quit() - } - } - } - diff --git a/share/plugins/walk.qml b/share/plugins/walk.qml deleted file mode 100644 index 30fa634ced8f1..0000000000000 --- a/share/plugins/walk.qml +++ /dev/null @@ -1,108 +0,0 @@ -/* - * SPDX-License-Identifier: GPL-3.0-only - * MuseScore-CLA-applies - * - * MuseScore - * Music Composition & Notation - * - * Copyright (C) 2021 MuseScore BVBA and others - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * 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 . - */ - -import QtQuick 2.0 -import MuseScore 3.0 - -MuseScore { - version: "3.1" - description: "This test plugin walks through all available plugin elements in a score" - menuPath: "Plugins.Walk" - requiresScore: true; - - onRun: { - var SegmentTypes = Object.freeze({ - 0: "Invalid", - 1: "BeginBarLine", - 2: "HeaderClef", - 4: "KeySig", - 8: "Ambitus", - 16: "TimeSig", - 32: "StartRepeatBarLine", - 64: "Clef", - 128: "BarLine", - 256: "Breath", - 512: "ChordRest", - 1024: "EndBarLine", - 2048: "KeySigAnnounce", - 4096: "TimeSigAnnounce" - }); - - var partListing = ""; - var parts = curScore.parts; - for (var p in parts) { - partListing += parts[p].partName + "\n"; - var instrumentListing = ""; - for (var i in parts[p].instruments) { - instrumentListing += "\t" + parts[p].instruments[i].instrumentId + " (" + parts[p].instruments[i].channels.length + " Channel(s))\n"; - } - if (parts[p].instruments.length <= 1) { - partListing = partListing.slice(0, -1); - } - partListing += instrumentListing; - } - console.log("=== Part Listing ===\n" + partListing); - - var measure = curScore.firstMeasure; - var measureCounter = 1; - while (measure) { - var measureInfo = "== Measure " + measureCounter + " ==\n"; - for (var e in measure.elements) { - measureInfo += measure.elements[e].type + " " + measure.elements[e].name + "\n"; - } - var segment = measure.firstSegment; - while (segment) { - var segmentInfo = segment.tick + "\t" + SegmentTypes[Number(segment.segmentType)]; - if (segment.annotations && segment.annotations.length) { - for (var a in segment.annotations) { - segmentInfo += "\n\tAN: " + segment.annotations[a].type + "\t" + segment.annotations[a].name; - } - } - - for (var t = 0; t < curScore.ntracks; ++t) { - var el = segment.elementAt(t); - if (el) { - segmentInfo += "\n\tEL: " + el.type + "\t" + el.name + "\tTrack: " + t; - if (el.type == Element.CHORD) { - segmentInfo += "\n\t\t\tGrace notes: " + el.graceNotes.length; - segmentInfo += "\n\t\t\tNotes:"; - for (var n in el.notes) { - segmentInfo += "\n\t\t\t\t" + el.notes[n].pitch + "\ttieBack: " + (el.notes[n].tieBack != null) + "\ttieForward: " + (el.notes[n].tieForward != null); - for (var notEl in el.notes[n].elements) { - segmentInfo += "\n\t\t\t\t" + el.notes[n].elements[notEl].type + "\t" + el.notes[n].elements[notEl].name; - } - } - } - } - } - - measureInfo += segmentInfo + "\n"; - segment = segment.nextInMeasure; - } - console.log(measureInfo); - measure = measure.nextMeasure; - ++measureCounter; - } - - Qt.quit(); // WARNING: this kills off ALL active plugins! - } -}