From 644b7601197dba419e3dcbbfa22d1a5eebe65356 Mon Sep 17 00:00:00 2001 From: laurent Date: Fri, 1 Oct 2021 13:23:38 +0200 Subject: [PATCH] Initial version. --- soloanalyser.qml | 375 +++++++++++++++++++++++ zparkingb/notehelper.js | 555 +++++++++++++++++++++++++++++++++++ zparkingb/selectionhelper.js | 530 +++++++++++++++++++++++++++++++++ 3 files changed, 1460 insertions(+) create mode 100644 soloanalyser.qml create mode 100644 zparkingb/notehelper.js create mode 100644 zparkingb/selectionhelper.js diff --git a/soloanalyser.qml b/soloanalyser.qml new file mode 100644 index 0000000..7cfd228 --- /dev/null +++ b/soloanalyser.qml @@ -0,0 +1,375 @@ +import QtQuick 2.0 +import MuseScore 3.0 +import QtQuick.Dialogs 1.2 +import "zparkingb/selectionhelper.js" as SelHelper +import "zparkingb/notehelper.js" as NoteHelper + +MuseScore { + menuPath: "Plugins.Solo Analayzer" + description: "Colors the notes part of each measure harmony." + version: "1.0" + + readonly property var selHelperVersion: "1.2.0" + readonly property var noteHelperVersion: "1.0.3" + + onRun: { + + if ((typeof(SelHelper.checktVersion) !== 'function') || !SelHelper.checktVersion(selHelperVersion) || + (typeof(NoteHelper.checktVersion) !== 'function') || !NoteHelper.checktVersion(noteHelperVersion)) { + console.log("Invalid zparkingb/selectionhelper.js and zparkingb/notehelper.js versions. Expecting " + + selHelperVersion + " and " + noteHelperVersion + "."); + invalidLibraryDialog.open(); + return; + } + + var score = curScore; + //var cursor = score.newCursor(); + + var chords = SelHelper.getChordsRestsFromCursor(); + + if (chords && (chords.length > 0)) { + console.log("CHORDS FOUND FROM CURSOR"); + } else { + chords = SelHelper.getChordsRestsFromSelection(); + if (chords && (chords.length > 0)) { + console.log("CHORDS FOUND FROM SELECTION"); + } else { + chords = SelHelper.getChordsRestsFromScore(); + console.log("CHORDS FOUND FROM ENTIRE SCORE"); + } + } + + if (!chords || (chords.length == 0)) + return; + + // Notes and Rests + var prevSeg = null; + var curChord = null; + for (var i = 0; i < chords.length; i++) { + var el = chords[i]; + var seg = el.parent; + //console.log(i + ")" + el.userName() + " / " + seg.segmentType); + + // Looking for new Chord symbol + if (!prevSeg || (seg !== prevSeg)) { + // nouveau segment, on y cherche un accord + prevSeg = seg; + + var annotations = seg.annotations; + //console.log(annotations.length + " annotations"); + if (annotations && (annotations.length > 0)) { + for (var j = 0; j < annotations.length; j++) { + var ann = annotations[j]; + //console.log(" (" + i + ") " + ann.userName() + " / " + ann.text + " / " + ann.harmonyType); + if (ann.type === Element.HARMONY) { + // keeping 1st Chord + var c = chordFromText(ann.text); + if (c != null) { + curChord = c; + break; + } + } + } + } + } + + console.log("Using chord : > " + curChord + " < "); + + // Looping in the chord notes + if (el.type === Element.REST) + continue; + var notes = el.notes; + for (var j = 0; j < notes.length; j++) { + var note = notes[j]; + var color = null; + var degree = null; + + // color based on role in chord + if (curChord != null) { + var p = (note.pitch - curChord.pitch) % 12; + if (p < 0) + p += 12; + var color = null; + if (p == 0) { + color = "darkblue"; //"crimson"; + degree = "1"; + } else if ((curChord.n3 != null) && (p == curChord.n3)) { + color = "slateblue"; //"magenta"; + degree = "3"; + } else if ((curChord.n5 != null) && (p == curChord.n5)) { + color = "blue"; //tomato + degree = "5"; + } else if ((curChord.n7 != null) && (p == curChord.n7)) { + color = "teal"; //"purple"; + degree = "7"; + } else if (curChord.keys.indexOf(p) >= 0) { + color = "purple"; //"green"; //slategray dodgerblue + } else { + color = "black"; + } + console.log(note.pitch + "/" + curChord.pitch + " ==> " + p + " ==> " + color); + } else + // no current chord, so resetting the color + { + color = "black"; + } + + note.color = color; + writeDegree(note, degree); + + } + + } + + } + + function writeDegree(note, degree) { + + const degrees = '1;2;3;4;5;6;7;8;9;11;13'; + + var eltext = null; + + if (note.type != Element.NOTE) { + return; + } else { + var el = note.elements; + //debugP(level_DEBUG,"getFingering", note,"type"); + for (var j = 0; j < el.length; j++) { + var e = el[j]; + if (e.type == Element.FINGERING) { + if (degrees.indexOf(e.text) >= 0) { + eltext = e; + break; + } + } + } + } + + if (degree != null) { + if (eltext != null) { + eltext.text = degree; + } else { + var f = newElement(Element.FINGERING); + f.text = degree; + // Turn on note relative placement + f.autoplace = true; + note.add(f); + } + } else { + if (eltext != null) { + note.remove(eltext); + } + } + + } + + function chordFromText(text) { + + if (text.slice(0, 1) === "(") + text = text.substr(1); + + // Root + var root = text.slice(0, 1).toUpperCase(); + var alt = text.slice(1, 2); + if ("bb" === text.slice(1, 3)) { + alt = 'FLAT2'; + text = text.substr(3); + } else if ("x" === alt) { + alt = 'SHARP2'; + text = text.substr(2); + } else if ("b" === alt) { + alt = 'FLAT'; + text = text.substr(2); + } else if ("#" === alt) { + alt = 'SHARP'; + text = text.substr(2); + } else { + alt = 'NONE'; + text = text.substr(1); + } + + var ftpcs = NoteHelper.tpcs.filter(function (e) { + return ((e.raw === root) && (e.accidental === alt)); + }); + + var tpc; + if (ftpcs.length > 0) { + tpc = ftpcs[0]; + } else { + console.log("!! Could not found >>" + root + "-" + alt + "<<"); + return null; + } + + // Chord type + var n3 = null, + n5 = null, + n7 = null; + var keys = [0]; + + text = new chordTextClass(text); + + // Base + // M, Ma, Maj, ma, maj + if (text.startsWith("Maj") || text.startsWith("Ma") || text.startsWith("M") || text.startsWith("maj") || text.startsWith("ma")) { + n3 = 4; + n5 = 7; + } + // m, mi, min, - + else if (text.startsWith("min") || text.startsWith("mi") || text.startsWith("m") || text.startsWith("-")) { + n3 = 3; + n5 = 7; + } + + // dim,o + else if (text.startsWith("dim") || text.startsWith("o")) { + n3 = 3; + n5 = 4; + n7 = 9; + } + + // Half-dim + else if (text.startsWith("0")) { + n3 = 3; + n5 = 4; + n7 = 10; + } + + // Aug + else if (text.startsWith("aug") || text.startsWith("+")) { + n3 = 3; + n5 = 6; + } + + // Maj7 + else if (text.startsWith("t7")) { + n3 = 3; + n5 = 7; + n7 = 11; + } + + // sus2 + else if (text.startsWith("sus2")) { + n3 = 2; + n5 = 7; + } + + // sus4 + else if (text.startsWith("sus4")) { + n3 = 5; + n5 = 7; + } + + // No indication => Major + else { + n3 = 4; + n5 = 7; + } + + // Compléments + if (text.includes("7")) { + n7 = 10; + } + + if (text.includes("b5")) { + n5 = 6; + } else if (text.includes("b5")) { + n5 = 6; + } + + if (text.includes("b9")) { + keys.push(1) + } else if (text.includes("#9")) { + keys.push(3) + } else if (text.includes("9")) { + keys.push(2) + } + + if (text.includes("b11")) { + keys.push(4) + } else if (text.includes("#11")) { + keys.push(6) + } else if (text.includes("11")) { + keys.push(5) + } + + if (text.includes("b13")) { + keys.push(8) + } else if (text.includes("#13")) { + keys.push(10) + } else if (text.includes("13")) { + keys.push(9) + } + + console.log("After analysis : >>" + text + "<<"); + + if (n3 != null) + keys.push(n3); + if (n5 != null) + keys.push(n5); + if (n7 != null) + keys.push(n7); + + var chord = new chordClass(tpc, text, n3, n5, n7, keys); + + return chord; + } + + function chordTextClass(str) { + var s = str; + this.replace = function (r1, r2) { + s = s.replace(r1, r2); + } + this.toString = function () { + return s; + } + + this.startsWith = function (test) { + if (s.startsWith(test)) { + s = s.substr(test.length); + return true; + } else { + return false; + } + } + + this.includes = function (test) { + var pos = s.indexOf(test); + + if (pos >= 0) { + s = s.substr(0, pos) + s.substr(pos + test.length); + return true; + } else { + return false; + } + } + } + + function chordClass(tpc, name, n3, n5, n7, keys) { + this.pitch = tpc.pitch; + this.name = name; + this.root = tpc.raw; + this.accidental = tpc.accidental; + this.n3 = n3; + this.n5 = n5; + this.n7 = n7; + this.keys = (!keys || (keys == null)) ? [] : keys; + + this.toString = function () { + return this.root + " " + this.accidental + " " + this.name + " " + keys.toString(); ; + }; + + } + + MessageDialog { + id: invalidLibraryDialog + icon: StandardIcon.Critical + standardButtons: StandardButton.Ok + title: 'Invalid libraries' + text: "Invalid 'zparkingb/selectionhelper.js' and 'zparkingb/notehelper.js' versions.\nExpecting " + + selHelperVersion + " and " + noteHelperVersion + ".\nSolo Analyser will stop here." + onAccepted: { + Qt.quit() + } + } + +} diff --git a/zparkingb/notehelper.js b/zparkingb/notehelper.js new file mode 100644 index 0000000..34d14d8 --- /dev/null +++ b/zparkingb/notehelper.js @@ -0,0 +1,555 @@ +/********************** +/* Parking B - MuseScore - Note helper +/* v1.0.2 +/* ChangeLog: +/* - 22/7/21: Added restToNote and changeNote function +/* - 25/7/21: Managing of transposing instruments +/* - 5/9/21: v1.0.2 Improved support for transpositing instruments. +/**********************************************/ +// ----------------------------------------------------------------------- +// --- Vesionning----------------------------------------- +// ----------------------------------------------------------------------- + +function checktVersion(expected) { + var version = "1.0.2"; + + var aV = version.split('.').map(function (v) {return parseInt(v);}); + var aE = (expected && (expected != null)) ? expected.split('.').map(function (v) {return parseInt(v);}) : [99]; + if (aE.length == 0) aE = [99]; + + for (var i = 0; (i < aV.length) && (i < aE.length); i++) { + if (!(aV[i] >= aE[i])) return false; + } + + return true; +} +// ----------------------------------------------------------------------- +// --- Processing----------------------------------------- +// ----------------------------------------------------------------------- +/** + * Add some propeprties to the note. Among others, the name of the note, in the format "C4" and "C#4", ... + * The added properties: + * - note.accidentalName : the name of the accidental + * - note.extname.fullname : "C#4" + * - note.extname.name : "C4" + * - note.extname.raw : "C" + * - note.extname.octave : "4" + * @param note The note to be enriched + * @return / + */ +function enrichNote(note) { + // accidental + var id = note.accidentalType; + note.accidentalName = "UNKOWN"; + for (var i = 0; i < accidentals.length; i++) { + var acc = accidentals[i]; + if (id == eval("Accidental." + acc.name)) { + note.accidentalName = acc.name; + break; + } + } + + // note name and octave + var pitch = note.pitch; + var tpitch = pitch + note.tpc2 - note.tpc1; // note displayed as if it has that pitch + note.extname = pitchToName(tpitch, note.tpc2); + note.pitchname = pitchToName(pitch, note.tpc1); + return; + +} + +function pitchToName(npitch, ntpc) { + var tpc = { + 'tpc': 14, + 'name': '?', + 'raw': '?', + 'pitch': 0, + }; + + var pitchnote = pitchnotes[npitch % 12]; + var noteOctave = Math.floor(npitch / 12) - 1; + + for (var i = 0; i < tpcs.length; i++) { + var t = tpcs[i]; + if (ntpc == t.tpc) { + tpc = t; + break; + } + } + + if ((pitchnote == "A" || pitchnote == "B") && tpc.raw == "C") { + noteOctave++; + } else if (pitchnote == "C" && tpc.raw == "B") { + noteOctave--; + } + + return { + "fullname": tpc.name + noteOctave, + "name": tpc.raw + noteOctave, + "raw": tpc.raw, + "octave": noteOctave + }; + +} + +/** + * Reconstructed a note pitch information based on the note name and its accidental + * @param noteName the name of the note, without alteration. Eg "C4", and not "C#4" + * @param accidental the name of the accidental to use. Eg "SHARP2" + * @return a structure with pitch/tpc information +ret.pitch : the pitch of the note +ret.tpc: the value for the note tpc1 and tpc2 + */ +function buildPitchedNote(noteName, accidental) { + + var name = noteName.substr(0, 1); + var octave = parseInt(noteName.substr(1, 3)); + + var a = accidental; + for (var i = 0; i < equivalences.length; i++) { + for (var j = 1; j < equivalences[i].length; j++) { + if (accidental == equivalences[i][j]) { + a = equivalences[i][0]; + break; + } + } + } + + // tpc + var tpc = { + 'tpc': -1, + 'pitch': 0 + }; + for (var i = 0; i < tpcs.length; i++) { + var t = tpcs[i]; + if (name == t.raw && a == t.accidental) { + //console.log("found with "+t.name); + tpc = t; + break; + } + } + + if (tpc.tpc == -1) { + // not found. it means that we have an "exotic" accidental + for (var i = 0; i < tpcs.length; i++) { + var t = tpcs[i]; + if (name == t.raw && 'NONE' == t.accidental) { + //console.log("found with "+t.name + ' NONE'); + tpc = t; + break; + } + } + } + + if (tpc.tpc == -1) { + // not found. Shouldn't occur + tpc.tpc = 0; + } + + // pitch + //console.log("--" + tpc.pitch + "--"); + var pitch = (octave + 1) * 12 + ((tpc.pitch !== undefined) ? tpc.pitch : 0); + + var recompose = { + "pitch": pitch, + // we store the note as a label ("C4"), we want that note to *look* like a "C4" more than to *sound* like "C4". + // ==> we force the representation mode by steiing tpc1 to undefined and specifying tpc2 + //"tpc1" : tpc.tpc, + "tpc2": tpc.tpc + }; + + return recompose; +} + +/** + * + */ +function restToNote(rest, toNote) { + if (rest.type != Element.REST) + return; + + if (toNote === parseInt(toNote)) + toNote = { + "pitch": toNote, + "concertPitch": false, + "sharp_mode": true + }; + + //console.log("==ON A REST=="); + var cur_time = rest.parent.tick; // getting rest's segment's tick + var oCursor = curScore.newCursor(); + oCursor.rewindToTick(cur_time); + oCursor.addNote(toNote.pitch); + oCursor.rewindToTick(cur_time); + var chord = oCursor.element; + var note = chord.notes[0]; + + //debugPitch(level_DEBUG,"Added note",note); + + changeNote(note, toNote); + + //debugPitch(level_DEBUG,"Corrected note",note); + + + return note; +} + +/** + * @param toNote.pitch: the target pitch, + * @param toNote.concertPitch: true|false, false means the note must be dispalyed as having that pitch. + * For a Bb instrument, a pitch=60 (C), with concertPitch=false will be displayed as a C but be played as Bb. + * With concertPitch=true, it will be played as a C and therefor displayed as a D. + * @param toNote.sharp_mode: true|false The preference goes to sharp accidentals (true) or flat accidentals (false) + */ +function changeNote(note, toNote) { + if (note.type != Element.NOTE) { + debug(level_INFO, "! Changing Note of a non-Note element"); + return; + } + + var sharp_mode = (toNote.sharp_mode) ? true : false; + var concertPitch = (toNote.concertPitch) ? true : false; + + //console.log("default is pitch: " + note.pitch + ", tpc: " + note.tpc1 + "/" + note.tpc2 + "/" + note.tpc); + //console.log("requested is pitch: " + ((toNote.pitch === undefined) ? "undefined" : toNote.pitch) + + // ", tpc: " + ((toNote.tpc1 === undefined) ? "undefined" : toNote.tpc1) + "/" + ((toNote.tpc2 === undefined) ? "undefined" : toNote.tpc2)); + + if (!concertPitch) { + // We need to align the pitch, beacause the specified pitched is the score pitch and non the concert pitch² + var dtpc = note.tpc2 - note.tpc1; + var dpitch = deltaTpcToPitch(note.tpc1, note.tpc2); + + note.pitch += dpitch; + + // basic approach. This will be correct but not all the time nice + note.tpc1 -= dtpc; + note.tpc2 -= dtpc; + + } + + var tpc = getPreferredTpc(note.tpc1, sharp_mode); + if (tpc !== null) + note.tpc1 = tpc; + + //delta = toNote.pitch - 60; + var tpc = getPreferredTpc(note.tpc2, sharp_mode); + if (tpc !== null) + note.tpc2 = tpc; + + //console.log("After is pitch: " + note.pitch + ", tpc: " + note.tpc1 + "/" + note.tpc2 + "/" + note.tpc); + + return note; + +} + +function getPreferredTpc(tpc, sharp_mode) { + var delta = tpcToPitch(tpc); + + var tpcs = (sharp_mode) ? sharpTpcs : flatTpcs; + + for (var t = 0; t < tpcs.length; t++) { + if (tpcs[t].pitch == delta) { + return tpcs[t].tpc; + break; + } + } + + return null; + +} + +var pitchnotes = ['C', 'C', 'D', 'D', 'E', 'F', 'F', 'G', 'G', 'A', 'A', 'B']; + +var tpcs = [new tpcClass(-1, 'F♭♭'), + new tpcClass(0, 'C♭♭'), + new tpcClass(1, 'G♭♭'), + new tpcClass(2, 'D♭♭'), + new tpcClass(3, 'A♭♭'), + new tpcClass(4, 'E♭♭'), + new tpcClass(5, 'B♭♭'), + new tpcClass(6, 'F♭'), + new tpcClass(7, 'C♭'), + new tpcClass(8, 'G♭'), + new tpcClass(9, 'D♭'), + new tpcClass(10, 'A♭'), + new tpcClass(11, 'E♭'), + new tpcClass(12, 'B♭'), + new tpcClass(13, 'F'), + new tpcClass(14, 'C'), + new tpcClass(15, 'G'), + new tpcClass(16, 'D'), + new tpcClass(17, 'A'), + new tpcClass(18, 'E'), + new tpcClass(19, 'B'), + new tpcClass(20, 'F♯'), + new tpcClass(21, 'C♯'), + new tpcClass(22, 'G♯'), + new tpcClass(23, 'D♯'), + new tpcClass(24, 'A♯'), + new tpcClass(25, 'E♯'), + new tpcClass(26, 'B♯'), + new tpcClass(27, 'F♯♯'), + new tpcClass(28, 'C♯♯'), + new tpcClass(29, 'G♯♯'), + new tpcClass(30, 'D♯♯'), + new tpcClass(31, 'A♯♯'), + new tpcClass(32, 'E♯♯'), + new tpcClass(33, 'B♯♯'), +]; + +function filterTpcs(sharp_mode) { + + var accidentals = sharp_mode ? ['NONE', 'SHARP', 'SHARP2'] : ['NONE', 'FLAT', 'FLAT2']; + var preferredTpcs; + + // On ne garde que les tpcs correspondant au type d'accord et trié par type d'altération + var preferredTpcs = tpcs.filter(function (e) { + return accidentals.indexOf(e.accidental) >= 0; + }); + + preferredTpcs = preferredTpcs.sort(function (a, b) { + var acca = accidentals.indexOf(a.accidental); + var accb = accidentals.indexOf(b.accidental); + if (acca != accb) + return acca - accb; + return a.pitch - b.pitch; + }); + + for (var i = 0; i < preferredTpcs.length; i++) { + if (preferredTpcs[i].pitch < 0) + preferredTpcs[i].pitch += 12; + //console.log(root + " (" + mode + ") => " + sharp_mode + ": " + preferredTpcs[i].name + "/" + preferredTpcs[i].pitch); + } + + return preferredTpcs; + +} + +var sharpTpcs = filterTpcs(true); +var flatTpcs = filterTpcs(false); + +var accidentals = [{ + 'name': 'NONE', + }, { + 'name': 'FLAT', + }, { + 'name': 'NATURAL', + }, { + 'name': 'SHARP', + }, { + 'name': 'SHARP2', + }, { + 'name': 'FLAT2', + }, { + 'name': 'NATURAL_FLAT', + }, { + 'name': 'NATURAL_SHARP', + }, { + 'name': 'SHARP_SHARP', + }, { + 'name': 'FLAT_ARROW_UP', + }, { + 'name': 'FLAT_ARROW_DOWN', + }, { + 'name': 'NATURAL_ARROW_UP', + }, { + 'name': 'NATURAL_ARROW_DOWN', + }, { + 'name': 'SHARP_ARROW_UP', + }, { + 'name': 'SHARP_ARROW_DOWN', + }, { + 'name': 'SHARP2_ARROW_UP', + }, { + 'name': 'SHARP2_ARROW_DOWN', + }, { + 'name': 'FLAT2_ARROW_UP', + }, { + 'name': 'FLAT2_ARROW_DOWN', + }, { + 'name': 'MIRRORED_FLAT', + }, { + 'name': 'MIRRORED_FLAT2', + }, { + 'name': 'SHARP_SLASH', + }, { + 'name': 'SHARP_SLASH4', + }, { + 'name': 'FLAT_SLASH2', + }, { + 'name': 'FLAT_SLASH', + }, { + 'name': 'SHARP_SLASH3', + }, { + 'name': 'SHARP_SLASH2', + }, { + 'name': 'DOUBLE_FLAT_ONE_ARROW_DOWN', + }, { + 'name': 'FLAT_ONE_ARROW_DOWN', + }, { + 'name': 'NATURAL_ONE_ARROW_DOWN', + }, { + 'name': 'SHARP_ONE_ARROW_DOWN', + }, { + 'name': 'DOUBLE_SHARP_ONE_ARROW_DOWN', + }, { + 'name': 'DOUBLE_FLAT_ONE_ARROW_UP', + }, { + 'name': 'FLAT_ONE_ARROW_UP', + }, { + 'name': 'NATURAL_ONE_ARROW_UP', + }, { + 'name': 'SHARP_ONE_ARROW_UP', + }, { + 'name': 'DOUBLE_SHARP_ONE_ARROW_UP', + }, { + 'name': 'DOUBLE_FLAT_TWO_ARROWS_DOWN', + }, { + 'name': 'FLAT_TWO_ARROWS_DOWN', + }, { + 'name': 'NATURAL_TWO_ARROWS_DOWN', + }, { + 'name': 'SHARP_TWO_ARROWS_DOWN', + }, { + 'name': 'DOUBLE_SHARP_TWO_ARROWS_DOWN', + }, { + 'name': 'DOUBLE_FLAT_TWO_ARROWS_UP', + }, { + 'name': 'FLAT_TWO_ARROWS_UP', + }, { + 'name': 'NATURAL_TWO_ARROWS_UP', + }, { + 'name': 'SHARP_TWO_ARROWS_UP', + }, { + 'name': 'DOUBLE_SHARP_TWO_ARROWS_UP', + }, { + 'name': 'DOUBLE_FLAT_THREE_ARROWS_DOWN', + }, { + 'name': 'FLAT_THREE_ARROWS_DOWN', + }, { + 'name': 'NATURAL_THREE_ARROWS_DOWN', + }, { + 'name': 'SHARP_THREE_ARROWS_DOWN', + }, { + 'name': 'DOUBLE_SHARP_THREE_ARROWS_DOWN', + }, { + 'name': 'DOUBLE_FLAT_THREE_ARROWS_UP', + }, { + 'name': 'FLAT_THREE_ARROWS_UP', + }, { + 'name': 'NATURAL_THREE_ARROWS_UP', + }, { + 'name': 'SHARP_THREE_ARROWS_UP', + }, { + 'name': 'DOUBLE_SHARP_THREE_ARROWS_UP', + }, { + 'name': 'LOWER_ONE_SEPTIMAL_COMMA', + }, { + 'name': 'RAISE_ONE_SEPTIMAL_COMMA', + }, { + 'name': 'LOWER_TWO_SEPTIMAL_COMMAS', + }, { + 'name': 'RAISE_TWO_SEPTIMAL_COMMAS', + }, { + 'name': 'LOWER_ONE_UNDECIMAL_QUARTERTONE', + }, { + 'name': 'RAISE_ONE_UNDECIMAL_QUARTERTONE', + }, { + 'name': 'LOWER_ONE_TRIDECIMAL_QUARTERTONE', + }, { + 'name': 'RAISE_ONE_TRIDECIMAL_QUARTERTONE', + }, { + 'name': 'DOUBLE_FLAT_EQUAL_TEMPERED', + }, { + 'name': 'FLAT_EQUAL_TEMPERED', + }, { + 'name': 'NATURAL_EQUAL_TEMPERED', + }, { + 'name': 'SHARP_EQUAL_TEMPERED', + }, { + 'name': 'DOUBLE_SHARP_EQUAL_TEMPERED', + }, { + 'name': 'QUARTER_FLAT_EQUAL_TEMPERED', + }, { + 'name': 'QUARTER_SHARP_EQUAL_TEMPERED', + }, { + 'name': 'SORI', + }, { + 'name': 'KORON', + } + //,{ 'name': 'UNKNOWN', } +]; + +var equivalences = [ + ['SHARP', 'NATURAL_SHARP'], + ['FLAT', 'NATURAL_FLAT'], + ['NONE', 'NATURAL'], + ['SHARP2', 'SHARP_SHARP'] +]; + +function isEquivAccidental(a1, a2) { + for (var i = 0; i < equivalences.length; i++) { + if ((equivalences[i][0] === a1 && equivalences[i][1] === a2) || + (equivalences[i][0] === a2 && equivalences[i][1] === a1)) + return true; + } + return false; +} + +/* +'tpc': 33, +'name': 'B♯♯', +'pitch': 13, +'accidental': 'SHARP2', +'raw': 'B' + + */ +function tpcClass(tpc, name, accidental) { + this.tpc = tpc; + this.name = name; + + this.raw = name.substring(0, 1); + + this.pitch = tpcToPitch(tpc); + + if (accidental !== undefined) { + this.accidental = accidental; + } else { + + var a = name.substring(1, name.len); + switch (a) { + case '♯♯': + this.accidental = 'SHARP2'; + break; + case '♯': + this.accidental = 'SHARP'; + break; + case '♭♭': + this.accidental = 'FLAT2'; + break; + case '♭': + this.accidental = 'FLAT'; + break; + default: + this.accidental = 'NONE'; + } + } + + this.toString = function () { + return this.raw+" "+this.accidental; + }; + + Object.freeze(this); + +} + +function tpcToPitch(tpc) { + return deltaTpcToPitch(tpc, 14); +} + +function deltaTpcToPitch(tpc1, tpc2) { + var d = ((tpc2 - tpc1) * 5) % 12; + if (d < 0) + d += 12; + return d; +} diff --git a/zparkingb/selectionhelper.js b/zparkingb/selectionhelper.js new file mode 100644 index 0000000..36f026d --- /dev/null +++ b/zparkingb/selectionhelper.js @@ -0,0 +1,530 @@ +/********************** +/* Parking B - MuseScore - Selection helper +/* v1.0.0 +/* v1.2.0 28/9/21 getChordsRestsFromCursor +/**********************************************/ + +// ----------------------------------------------------------------------- +// --- Vesionning----------------------------------------- +// ----------------------------------------------------------------------- + +function checktVersion(expected) { + var version = "1.2.0"; + + var aV = version.split('.').map(function (v) {return parseInt(v);}); + var aE = (expected && (expected != null)) ? expected.split('.').map(function (v) {return parseInt(v);}) : [99]; + if (aE.length == 0) aE = [99]; + + for (var i = 0; (i < aV.length) && (i < aE.length); i++) { + if (!(aV[i] >= aE[i])) return false; + } + + return true; +} + + +// ----------------------------------------------------------------------- +// --- Selection helper -------------------------------------------------- +// ----------------------------------------------------------------------- +/** + * Get all the selected notes from the selection + * @return Note[] : each returned {@link Note} has the following properties: + * - element.type==Element.NOTE + */ +function getNotesFromSelection() { + var selection = curScore.selection; + var el = selection.elements; + var notes = []; + var n = 0; + for (var i = 0; i < el.length; i++) { + var element = el[i]; + if (element.type == Element.NOTE) { + notes[n++] = element; + } + + } + return notes; +} + +/** + * Get all the selected rest from the selection + * @return Note[] : each returned {@link Rest} has the following properties: + * - element.type==Element.REST + */ +function getRestsFromSelection() { + var selection = curScore.selection; + var el = selection.elements; + var rests = []; + for (var i = 0; i < el.length; i++) { + var element = el[i]; + if (element.type == Element.REST) { + rests.push(element); + } + + } + return rests; +} + +/** + * Get all the selected notes and rests from the selection + * @return Note[] : each returned {@link Rest} has the following properties: + * - element.type==Element.REST or Element.NOTE + */ +function getNotesRestsFromSelection() { + var selection = curScore.selection; + var el = selection.elements; + var rests = []; + for (var i = 0; i < el.length; i++) { + var element = el[i]; + if ((element.type == Element.REST) || (element.type == Element.NOTE)) { + rests.push(element); + } + + } + return rests; +} +/** + * Get all the selected chords from the selection + * @return Chord[] : each returned {@link Chord} has the following properties: + * - element.type==Element.CHORD + */ +function getChordsFromSelection() { + var notes = getNotesFromSelection(); + var chords = []; + var prevChord; + for (var i = 0; i < notes.length; i++) { + var element = notes[i]; + var chord = element.parent; + if (!prevChord || (prevChord !== chord)) { + chords.push(chord); + } + prevChord = chord; + } + return chords; +} + +/** + * Get all the selected chords and rests from the selection + * @return Chords[] : each returned {@link Rest} has the following properties: + * - element.type==Element.REST or Element.CHORD + */ +function getChordsRestsFromSelection() { + var notes = getNotesRestsFromSelection(); + + var chords = []; + var prevChord; + for (var i = 0; i < notes.length; i++) { + var element = notes[i]; + + if (element.type === Element.REST) { + chords.push(element); + prevChord = undefined; + } else if (element.type === Element.NOTE) { + var chord = element.parent; + if (!prevChord || (prevChord !== chord)) { + chords.push(chord); + } + prevChord = chord; + } + } + + return chords; +} + +/** + * Get all the selected segments from the selection + * @return Segment[] : each returned {@link Segment} has the following properties: + * - element.type==Element.SEGMENT + */ +function getSegmentsFromSelection() { + // Les segments sur base des notes et accords + var chords = getChordsRestsFromSelection(); + var segments = []; + var prevSeg; + for (var i = 0; i < chords.length; i++) { + var element = chords[i]; + var seg = element.parent; + if (prevSeg && !prevSeg.is(seg)) { + segments.push(seg); + } + prevChord = seg; + } + + return segments; +} + +/** + * Reourne les fingerings sélectionnés + * @return Fingering[] : each returned {@link Fingering} has the following properties: + * - element.type==Element.FINGERING + */ +function getFingeringsFromSelection() { + var selection = curScore.selection; + var el = selection.elements; + var fingerings = []; + for (var i = 0; i < el.length; i++) { + var element = el[i]; + if (element.type == Element.FINGERING) { + fingerings.push(element); + } + } + return fingerings; +} + +/** + * Get all the selected notes based on the cursor. + * Rem: This does not any result in case of the notes are selected inidvidually. + * @param oneNoteBySegment : boolean. If true, only one note by segment will be returned. + * @return Note[] : each returned {@link Note} has the following properties: + * - element.type==Element.NOTE + * + */ +function getNotesFromCursor(oneNoteBySegment) { + var chords = getChordsFromCursor(); + var notes = []; + for (var c = 0; c < chords.length; c++) { + var chord = chords[c]; + var nn = chord.notes; + for (var i = 0; i < nn.length; i++) { + var note = nn[i]; + notes[notes.length] = note; + if (oneNoteBySegment) + break; + } + } + return notes; +} + +/** + * Get all the selected chords based on the cursor. + * Rem: This does not any result in case of the notes are selected inidvidually. + * @return Chord[] : each returned {@link Note} has the following properties: + * - element.type==Element.CHORD + * + */ +function getChordsFromCursor() { + var score = curScore; + var cursor = curScore.newCursor() + var firstTick, + firstStaff, + lastTick, + lastStaff; + // start + cursor.rewind(Cursor.SELECTION_START); + firstTick = cursor.tick; + firstStaff = cursor.track; + // end + cursor.rewind(Cursor.SELECTION_END); + lastTick = cursor.tick; + if (lastTick == 0) { // dealing with some bug when selecting to end. + lastTick = curScore.lastSegment.tick + 1; + } + lastStaff = cursor.track; + debugV(30, "> first", "tick", firstTick); + debugV(30, "> first", "track", firstStaff); + debugV(30, "> last", "tick", lastTick); + debugV(30, "> last", "track", lastStaff); + var chords = []; + + cursor.rewind(Cursor.SELECTION_START); + var segment = cursor.segment; + while (segment && (segment.tick < lastTick)) { + for (var track = firstStaff; track <= lastStaff; track++) { + var element; + element = segment.elementAt(track); + if (element && element.type == Element.CHORD) { + debugV(40, "- segment -", "tick", segment.tick); + debugV(40, "- segment -", "segmentType", segment.segmentType); + debugV(40, "--element", "label", (element) ? element.name : "null"); + chords[chords.length] = element; + } + } + cursor.next(); + segment = cursor.segment; + } + + return chords; +} + +function getChordsRestsFromCursor() { + var score = curScore; + var cursor = curScore.newCursor() + var firstTick, + firstStaff, + lastTick, + lastStaff; + // start + cursor.rewind(Cursor.SELECTION_START); + firstTick = cursor.tick; + firstStaff = cursor.track; + // end + cursor.rewind(Cursor.SELECTION_END); + lastTick = cursor.tick; + if (lastTick == 0) { // dealing with some bug when selecting to end. + lastTick = curScore.lastSegment.tick + 1; + } + lastStaff = cursor.track; + debugV(30, "> first", "tick", firstTick); + debugV(30, "> first", "track", firstStaff); + debugV(30, "> last", "tick", lastTick); + debugV(30, "> last", "track", lastStaff); + var chords = []; + + cursor.rewind(Cursor.SELECTION_START); + var segment = cursor.segment; + while (segment && (segment.tick < lastTick)) { + for (var track = firstStaff; track <= lastStaff; track++) { + var element; + element = segment.elementAt(track); + if (element && (element.type == Element.CHORD || element.type == Element.REST)) { + debugV(40, "- segment -", "tick", segment.tick); + debugV(40, "- segment -", "segmentType", segment.segmentType); + debugV(40, "--element", "label", (element) ? element.name : "null"); + chords[chords.length] = element; + } + + } + cursor.next(); + segment = cursor.segment; + } + + return chords; +} + +/** + * Get all the selected rests based on the cursor. + * Rem: This does not any result in case of the rests and notes are selected inidvidually. + * @return Rest[] : each returned {@link Note} has the following properties: + * - element.type==Element.REST + * + */ +function getRestsFromCursor() { + var score = curScore; + var cursor = curScore.newCursor() + var firstTick, + firstStaff, + lastTick, + lastStaff; + // start + cursor.rewind(Cursor.SELECTION_START); + firstTick = cursor.tick; + firstStaff = cursor.track; + // end + cursor.rewind(Cursor.SELECTION_END); + lastTick = cursor.tick; + if (lastTick == 0) { // dealing with some bug when selecting to end. + lastTick = curScore.lastSegment.tick + 1; + } + lastStaff = cursor.track; + debugV(30, "> first", "tick", firstTick); + debugV(30, "> first", "track", firstStaff); + debugV(30, "> last", "tick", lastTick); + debugV(30, "> last", "track", lastStaff); + var rests = []; + + cursor.rewind(Cursor.SELECTION_START); + var segment = cursor.segment; + while (segment && (segment.tick < lastTick)) { + for (var track = firstStaff; track <= lastStaff; track++) { + var element; + element = segment.elementAt(track); + if (element && element.type == Element.REST) { + debugV(40, "- segment -", "tick", segment.tick); + debugV(40, "- segment -", "segmentType", segment.segmentType); + debugV(40, "--element", "label", (element) ? element.name : "null"); + rests.push(element); + } + } + cursor.next(); + segment = cursor.segment; + } + + return rests; +} + +function getChordsFromCursor() { + var score = curScore; + var cursor = curScore.newCursor() + var firstTick, + firstStaff, + lastTick, + lastStaff; + // start + cursor.rewind(Cursor.SELECTION_START); + firstTick = cursor.tick; + firstStaff = cursor.track; + // end + cursor.rewind(Cursor.SELECTION_END); + lastTick = cursor.tick; + if (lastTick == 0) { // dealing with some bug when selecting to end. + lastTick = curScore.lastSegment.tick + 1; + } + lastStaff = cursor.track; + debugV(30, "> first", "tick", firstTick); + debugV(30, "> first", "track", firstStaff); + debugV(30, "> last", "tick", lastTick); + debugV(30, "> last", "track", lastStaff); + var chords = []; + for (var track = firstStaff; track <= lastStaff; track++) { + + cursor.rewind(Cursor.SELECTION_START); + var segment = cursor.segment; + while (segment && (segment.tick < lastTick)) { + var element; + element = segment.elementAt(track); + if (element && element.type == Element.CHORD) { + debugV(40, "- segment -", "tick", segment.tick); + debugV(40, "- segment -", "segmentType", segment.segmentType); + debugV(40, "--element", "label", (element) ? element.name : "null"); + chords[chords.length] = element; + } + + cursor.next(); + segment = cursor.segment; + } + } + + return chords; +} + +/** + * Get all the selected notes and rests based on the cursor. + * Rem: This does not return any result in case of the rests and notes are selected inidvidually. + * @param oneNoteBySegment : boolean. If true, only one note by segment will be returned. + * @return Rest[] : each returned {@link Note} has the following properties: + * - element.type==Element.REST or Element.NOTE + * + */ +function getNotesRestsFromCursor(oneNoteBySegment) { + var score = curScore; + var cursor = curScore.newCursor() + var firstTick, + firstStaff, + lastTick, + lastStaff; + // start + cursor.rewind(Cursor.SELECTION_START); + firstTick = cursor.tick; + firstStaff = cursor.track; + // end + cursor.rewind(Cursor.SELECTION_END); + lastTick = cursor.tick; + if (lastTick == 0) { // dealing with some bug when selecting to end. + lastTick = curScore.lastSegment.tick + 1; + } + lastStaff = cursor.track; + debugV(30, "> first", "tick", firstTick); + debugV(30, "> first", "track", firstStaff); + debugV(30, "> last", "tick", lastTick); + debugV(30, "> last", "track", lastStaff); + var rests = []; + + cursor.rewind(Cursor.SELECTION_START); + var segment = cursor.segment; + while (segment && (segment.tick < lastTick)) { + for (var track = firstStaff; track <= lastStaff; track++) { + var element; + element = segment.elementAt(track); + if (element && ((element.type == Element.REST) || (element.type == Element.CHORD))) { + debugV(40, "- segment -", "tick", segment.tick); + debugV(40, "- segment -", "segmentType", segment.segmentType); + debugV(40, "--element", "label", (element) ? element.name : "null"); + + if (element.type == Element.CHORD) { + // extracting the notes from the chord + var nn = element.notes; + for (var i = 0; i < nn.length; i++) { + var note = nn[i]; + rests.push(note); + if (oneNoteBySegment) + break; + } + + } else { + rests.push(element); + } + } + } + cursor.next(); + segment = cursor.segment; + } + + return rests; +} + +/** + * Get all the selected segments based on the cursor. + * Rem: This does not return any result in case of the notes are selected inidvidually. + * @return Segment[] : each returned {@link Note} has + * - element.type==Element.SEGMENT + * + */ +function getSegmentsFromCursor() { + var score = curScore; + var cursor = curScore.newCursor() + cursor.rewind(Cursor.SELECTION_END); + var lastTick = cursor.tick; + cursor.rewind(Cursor.SELECTION_START); + var firstTick = cursor.tick; + debugV(30, "> first", "tick", firstTick); + debugV(30, "> last", "tick", lastTick); + var segment = cursor.segment; + debugV(30, "> starting at", "tick", (segment) ? segment.tick : "NO SEGMENT"); + var segments = []; + var s = 0; + while (segment && (segment.tick < lastTick)) { + segments[s++] = segment; + cursor.next(); + segment = cursor.segment; + } + + return segments; +} + +function getChordsRestsFromScore() { + var score = curScore; + var cursor = curScore.newCursor() + var firstTick, + firstStaff, + lastTick, + lastStaff; + // start + cursor.rewind(0); + firstTick = cursor.tick; + firstStaff = cursor.track; + // end + cursor.rewind(Cursor.SELECTION_END); + lastTick = cursor.tick; + if (lastTick == 0) { // dealing with some bug when selecting to end. + lastTick = curScore.lastSegment.tick + 1; + } + lastStaff = cursor.track; + debugV(30, "> first", "tick", firstTick); + debugV(30, "> first", "track", firstStaff); + var chords = []; + + cursor.rewind(Cursor.SELECTION_START); + var segment = cursor.segment; + while (segment) { + for (var track = firstStaff; track <= lastStaff; track++) { + var element; + element = segment.elementAt(track); + if (element && (element.type == Element.CHORD || element.type == Element.REST)) { + debugV(40, "- segment -", "tick", segment.tick); + debugV(40, "- segment -", "segmentType", segment.segmentType); + debugV(40, "--element", "label", (element) ? element.name : "null"); + chords[chords.length] = element; + } + + } + cursor.next(); + segment = cursor.segment; + } + + return chords; +} + + +function debugV(level, label, prop, value) { + console.log(label + " " + prop + ":" + value); +}