// Scordatura Plugin for MuseScore // https://github.com/sammik/musescore-scordatura-tools // sammik 2021 import QtQuick 2.6 import QtQuick.Controls 2.2 import QtQuick.Layouts 1.3 import MuseScore 3.0 MuseScore { id: scordaturaPlugin menuPath: "Plugins.Scordatura Plugin" description: "Plugin for scordatura score writing" version: "1.2.1" requiresScore: false pluginType: "dock" dockArea: "right" width: 246 height: 500 property var score: null property var cursor: null property var corrections: [] readonly property var diatonic: ["C", "D", "E", "F", "G", "A", "B"] readonly property var notePitch: ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"] readonly property var noteTpc: ["F", "C", "G", "D", "A", "E", "B"] readonly property var instrumentsTunings: { "strings.violin": ["G3", "D4", "A4", "E5"], "strings.viola": ["C3", "G3", "D4", "A4"], "strings.cello": ["C2", "G2", "D3", "A3"], "strings.viol": ["G3", "D4", "A4", "D5", "G5"] } property var tunings: [] // [standardTuning, scordaturaTuning] // tuning = [{"string": 0, "name": "E5", "pitch": 76, "tpc": 18}, ...] property var staffLines: [null, null] // [scordaturaStaffLine, translatedStaffLine] // line = [{"name": instrumentLongName, "lines": staffLine}] property bool liveMode: false function findSegment(e) { while (e && e.type != Element.SEGMENT) e = e.parent; return e; } function noNotesOnStaff(element){ var tr = element.track; console.log("FN noNotesOnStaff: ", tr); for (var seg = score.firstSegment(); seg; seg = seg.next) { for (var track = tr + 4; track-- > tr; ) { var el = seg.elementAt(track); if (el && (el.type == Element.CHORD)) { console.log("there are notes"); return false } } } console.log("no notes"); return true } // find tuning of given instrument and return string "C2,G2,..." function findTuning(instrument) { var instName = instrument.instrumentId; if (instrument.stringData.strings.length) return getStringData(instrument, true); else return (instrumentsTunings[instName] || []).toString(); } // get string data of given instrument and return array of names, or pitches function getStringData(instrument, names /*boolean*/ ){ var stringData = instrument.stringData, tuning = []; for (var i = 0; i < stringData.strings.length; ++i) { var pitch = stringData.strings[i].pitch; var name = notePitch[pitch %12] + (Math.floor(pitch/12)-1); tuning.push(names ? name : pitch); } return names ? tuning.toString() : tuning.reverse(); } // convert strings string "G3, D4, A4, E5" to array of objects [{"string": 0, "name": "E5", "pitch": 76, "tpc": 18}, ...] function getTun(stringDef) { if (!stringDef) return; var strgs = stringDef.split(/\s*,\s*/).reverse(); var tun = []; for (var i = 0; i < strgs.length; ++i) { var name = strgs[i][0].toUpperCase() + strgs[i].substring(1), oct = parseInt( name[name.length - 1] ), pitch = notePitch.indexOf(name[0]) + (oct + 1) * 12, tpc = noteTpc.indexOf(name[0]) + 13; if ( name.indexOf("#") !== -1 || name.indexOf("♯") !== -1) { pitch = pitch + 1; tpc = tpc + 7; } if (name.indexOf("b") !== -1 || name.indexOf("♭") !== -1) { pitch = pitch - 1; tpc = tpc - 7; } console.log("FN getTun, ", i, ", ", name, ", ", pitch, ", ", tpc); tun.push({"string": i, "name": name, "pitch": pitch, "tpc": tpc}); } return tun; } function validateTuns() { var valid = true, instruments = []; for (var i = 0; i < tunings.length; ++i) { var tuning = tunings[i], inst = staffLines[i].lines.staff.part.instruments[0], strData = getStringData(inst); console.log("FN validateTuns - string data: ", strData); if (strData.length) { if (strData.length !== tuning.length) { console.log("validate tunings - length doesn't fit: ", strData.length, ", ", tuning.length); valid = false; instruments.push(inst.longName); } else { for (var j = 0; j < tuning.length; ++j) { if (strData[j] !== tuning[j].pitch) { console.log("validate tunings - pitch doesn't fit: ", strData[j], ", ", tuning[j].pitch); valid = false; instruments.push(inst.longName); break; } } } } } return {"valid": valid, "instruments": instruments} } // get selected string function getSelectedString() { var els = score ? score.selection.elements : null; return (els && els.length === 1 && els[0].type == Element.NOTE) ? els[0].string : false; } function guessString(pitch, tuning){ //sort tuning by pitches, to work with crossed strings too var tun = tuning.slice().sort( function(a,b) { return (a.pitch < b.pitch) ? 1 : -1; } ); for (var i = 0; i < tun.length; ++i){ var stringPitch = tun[i].pitch; if (pitch >= stringPitch) { console.log("guessString: ", tun[i].string); return tun[i].string; } } return -1; } function setFret(pitch, stringValue){ var fret = pitch - stringValue; // Check, if can be on that string if (fret > -1) { console.log("setFret: ", fret); return fret; } else return -1; } //check index of note; if note is graceNote, set index of graceChord too function getNoteIndex(note){ console.log("FN getNoteIndex"); var index = [-1, -1]; if (note.type !== Element.NOTE) return index; var chord = note.parent; for ( var i = 0; i < chord.notes.length; ++i ) { if (chord.notes[i].is(note)) index[1] = i; } console.log(note.noteType); console.log(note.noteType != NoteType.NORMAL); if (note.noteType != NoteType.NORMAL) { var mainChord = chord.parent; for ( var j = 0; j < mainChord.graceNotes.length; ++j ) { if (mainChord.graceNotes[j].is(chord)) index[0] = j; } } console.log(index); return index; } function translateNote(note, toScord) { var string = note.string > -1 ? note.string : guessString(note.pitch, tunings[toScord ? 1: 0]), pitchShift = (tunings[0][string].pitch - tunings[1][string].pitch) * (toScord ? 1: -1), tpcShift = (tunings[0][string].tpc - tunings[1][string].tpc) * (toScord ? 1: -1); note.pitch = note.pitch + pitchShift; note.tpc1 = note.tpc1 + tpcShift; note.tpc2 = note.tpc2 + tpcShift; } function translateChord(chord, toScord) { for (var i = 0; i < chord.notes.length; ++i){ translateNote(chord.notes[i], toScord); } for (var j = 0; j < chord.graceNotes.length; ++j) { translateChord(chord.graceNotes[j], toScord); } } function setSelectedString(str) { //var els = score.selection.elements; var sel = score.selection.elements, els = []; for (var i = 0; i < sel.length; ++i){ var el = sel[i]; if (el && el.type == Element.NOTE){ if ( el.firstTiedNote.is(el.lastTiedNote) ){ //no ties els.push(el); } else { //add all tied notes to selection var n = el.firstTiedNote; els.push(n); while (n.tieForward){ n = n.tieForward.endNote; els.push(n); } } } } score.startCmd(); for ( var i = 0; i < els.length; ++i) { var el = els[i], tr = el.track, tun = false, scord = false; if (tr > staffLines[0].lines.track - 1 && tr < staffLines[0].lines.track + 4){ tun = tunings[0]; scord = true; } else if (tr > staffLines[1].lines.track - 1 && tr < staffLines[1].lines.track + 4) tun = tunings[1]; console.log("FN setSelectedStrings - el.track: ", el.track, ", el.pitch: ", el.pitch); if (el && el.type == Element.NOTE && tun && !(tun[str].pitch > el.pitch)) { //if element is note, is on working staff, and note can be on string el.string = str; el.fret = setFret(el.pitch, tun[str].pitch); // check, if note exists in paralel staff and translate it and set string to it too // TODO crossed strings var index = getNoteIndex(el); var gi = index[0]; var ni = index[1]; var seg = findSegment(el); var tr = el.track - (staffLines[0].lines.track - staffLines[1].lines.track) * (scord ? 1 : -1); var chordNew = seg.elementAt(tr); if (chordNew && chordNew.type == Element.CHORD && chordNew.graceNotes.length > gi) { var chordNotes = gi < 0 ? chordNew.notes : chordNew.graceNotes[gi].notes; if (chordNotes.length > ni) { var oldNote = chordNotes[ni]; var newNote = el.clone(); translateNote(newNote, !scord); oldNote.pitch = newNote.pitch; oldNote.tpc1 = newNote.tpc1; oldNote.tpc2 = newNote.tpc2; oldNote.string = newNote.string; oldNote.fret = newNote.fret; } } } } score.endCmd(); } function transcribe(){ var source = transSelect.currentIndex, target = transSelect.currentIndex ^ 1, si = parseInt(staffLines[source].lines.track / 4), ti = parseInt(staffLines[target].lines.track / 4); console.log("FN transcribe: ", source, target, si, ti, staffLines[source].lines.track); if ( !noNotesOnStaff(staffLines[source].lines) ) { // if there are notes to translate, do it score.selection.selectRange(0, score.lastSegment.tick+1, si, si+1); cmd("copy"); score.selection.selectRange(0, score.lastSegment.tick+1, ti, ti+1); score.startCmd(); cmd("delete"); cmd("paste"); //TODO remove scordatura symbol from transcription staff, if already was in scordatura staff var els = score.selection.elements; if (els) { for (var i = 0; i < els.length; ++i) { var el = els[i]; if (el.type == Element.NOTE) { translateNote(el, target === 0); } } } score.endCmd(); } // if mode switched to live, after transcription set liveMode if (modeSwitch.checked) liveMode = true; } function removeTies(chord){ function removeTie(note){ if (note.tieBack) removeElement(note.tieBack); //note.remove(note.tieBack); if (note.tieForward) removeElement(note.tieForward); } for (var i = 0; i < chord.notes.length; ++i){ var note = chord.notes[i]; removeTie(note); } for (var i = 0; i < chord.graceNotes.length; ++i){ var graceChord = chord.graceNotes[i]; removeTies(graceChord); } return chord; } function duplicate(el){ //for live mode - duplicate element: note (chord), or rest var seg = findSegment(el); var tick = seg.tick; var chord = el.type == Element.NOTE ? (el.noteType == NoteType.NORMAL ? el.parent : el.parent.parent) : null; var duration = el.duration || chord.duration; var tr = el.track; var scord = (tr > staffLines[0].lines.track - 1 && tr < staffLines[0].lines.track + 4); if ( !(scord || (tr > staffLines[1].lines.track - 1 && tr < staffLines[1].lines.track + 4) ) ) { console.log("FN duplicate - no working track"); return; } cursor.rewindToTick(tick); cursor.track = tr - (staffLines[0].lines.track - staffLines[1].lines.track) * (scord ? 1 : -1); cursor.setDuration(duration.numerator, duration.denominator); score.startCmd(); cursor.addRest(); if (chord) { var newChord = removeTies(chord.clone()); translateChord(newChord, !scord); cursor.prev(); cursor.add(newChord); } score.endCmd(); } // if changed tied note, apply change to whole tied group function ties(tie, back /*true*/){ console.log("FN ties"); var note = back ? tie.startNote : tie.endNote; duplicate(note); var tie = back ? note.tieBack : note.tieForward; if (tie){ ties(tie, back); } } function hideAccidentals(){ var sel = score ? score.selection : null; if (sel) { score.startCmd(); for (var i = 0; i < sel.elements.length; ++i) { var element = sel.elements[i]; if (element.type == Element.NOTE) { var accidental = element.accidental; if (accidental) { accidental.visible = false; } } } score.endCmd(); } } function muteSwitch(part, on){ var instrs = part.instruments || []; for (var i = 0; i < instrs.length; ++i) { var instr = instrs[i]; var channels = instr.channels; for (var j = 0; j < channels.length; ++j) { var channel = channels[j]; channel.mute = on ? false : true; } } } function createScordaturaSymbol() { console.log("FN createScordaturaSymbol"); var posX, posY, sym, //font = (Qt.fontFamilies().indexOf("Leland") !== -1 ? "Leland" : "Bravura"), alternative = (symAlternative.checkState == Qt.Checked), track = staffLines[0].lines.track; //scordatura staff first track cursor.rewind(Cursor.SCORE_START); cursor.track = track; // to find out, which clef is at the begining, create temporary middle C note score.startCmd(); cursor.addNote(60); score.endCmd(); cursor.prev(); posY = cursor.element.notes[0].posY; // calculate x offset from begining of staves // TODO - check for brackets posX = staffLines[0].lines.pagePos.x - cursor.element.notes[0].pagePos.x + (alternative ? 1 : 0); cmd("undo"); // create lines, notes, accidentals, ledger lines score.startCmd() if (alternative) { // move clefs right var seg = score.firstSegment(); while (seg && seg.elementAt(track).type != Element.CLEF) seg = seg.next; seg.leadingSpace = 4.5; } else { //move longName left var style = score.style, longName = style.value("longInstrumentOffset"); longName.x = -4.5; style.setValue("longInstrumentOffset", longName ); sym = newElement(Element.STAFF_TEXT); sym.text = "staff5LinesWide"; sym.autoplace = false; //sym.fontFace = font; cursor.add(sym); sym.fontSize = 25; sym.offsetX = posX - 2; sym.offsetY = 4; sym = newElement(Element.STAFF_TEXT); sym.text = "staff5LinesWide"; sym.autoplace = false; //sym.fontFace = font; cursor.add(sym); sym.fontSize = 25; sym.offsetX = posX - 5; sym.offsetY = 4; console.log("symbol position x: ", sym.pagePos.x); } //tuning is scordatura tuning var tuning = tunings[1]; for (var i = tuning.length; i-- > 0; ) { var t = tuning[i], y = ( posY + 14 ) - ( diatonic.indexOf(t.name[0]) / 2 ) - ( 3.5 * Number(t.name[t.name.length-1]) ), createLedgerLine = function(offsetY){ console.log("createLedgerLine", offsetY); sym = newElement(Element.STAFF_TEXT); sym.text = "legerLine" sym.autplace = false; //sym.fontFace = font; cursor.add(sym); sym.fontSize = 25; sym.offsetX = posX - 3.4; sym.offsetY = offsetY; } console.log("offset: ", y); console.log("i: ", i) console.log(t.name); //create ledger linef, if neccesary if (i === tuning.length - 1){ if (y > 4.6) { createLedgerLine(7); } if (y > 5.6) { createLedgerLine(8); } if (y > 6.6) { createLedgerLine(9); } } if (i === 0){ if (y < -0.6) { createLedgerLine(1); } if (y < -1.6) { createLedgerLine(0); } if (y < -2.6) { createLedgerLine(-0.999); } } //create noteheads sym = newElement(Element.STAFF_TEXT); sym.text = "noteheadBlack" sym.autplace = false; //sym.fontFace = font; cursor.add(sym); sym.fontSize = 20; sym.offsetX = posX - 3; sym.offsetY = y; //add accidentals if (t.tpc < 13){ sym = newElement(Element.STAFF_TEXT); sym.text = "accidentalFlat" sym.autplace = false; //sym.fontFace = font; cursor.add(sym); sym.fontSize = 20; sym.offsetX = posX - 3.8; sym.offsetY = y + 1.6; } if (t.tpc > 19){ sym = newElement(Element.STAFF_TEXT); sym.text = "accidentalSharp" sym.autplace = false; //sym.fontFace = font; cursor.add(sym); sym.fontSize = 20; sym.offsetX = posX - 3.8; sym.offsetY = y + 1.6; } } curScore.endCmd(); } /* // TODO - rethink, not just notes on segment, but whole paralel polyphony function notesOnSegment(segment, topTrack, bottomTrack){ var notes=[]; for (var track = topTrack; track < bottomTrack + 1; ++track ) { var el = segment.elementAt(track); if (el && (el.type == Element.CHORD) ) { for (var j = 0; j < el.notes.length; ++j ){ notes.push(el.notes[j]) } } } return notes } */ function updateCurrentScore() { console.log("FN update score"); console.log("is curent: ", score && curScore.is(score), ", curScore: ", curScore, ", score: ", score); if (!curScore || !curScore.is(score)){ console.log("reset"); tunings = []; staffLines = [null, null]; menuBar.currentIndex = 1; standDef.text = ""; scordDef.text = ""; staffDefs.state = "DEFAULT"; stringDefs.state = ""; aplyDef.state = ""; modeSwitch.checked = false; } if (curScore && !curScore.is(score)) { console.log("update"); score = curScore; cmd("escape"); cursor = score.newCursor(); // load saved settings var metaTracks = score.metaTag("SCORDATURA_PLUGIN_TRACKS"); var metaScord = score.metaTag("SCORDATURA_PLUGIN_SCORDATURA_TUNING"); var metaStand = score.metaTag("SCORDATURA_PLUGIN_STANDARD_TUNING"); if (metaTracks) { var tracks = metaTracks.split(","); console.log("metaTracks: ", tracks); var scordLines = score.firstSegment().elementAt(tracks[0]); var standLines = score.firstSegment().elementAt(tracks[1]); staffLines = [{"name": scordLines.staff.part.longName, "lines": scordLines}, {"name": standLines.staff.part.longName, "lines": standLines}]; if ( !scordDef.text.length ) scordDef.text = metaScord || findTuning(staffLines[0].lines.staff.part.instruments[0]); if ( !standDef.text.length ) standDef.text = metaStand || findTuning(staffLines[1].lines.staff.part.instruments[0]); } } else if (score && !curScore) { console.log("no score"); score = null; cursor = null; } } onRun: { updateCurrentScore(); } onScoreStateChanged: { updateCurrentScore(); //highlight coresponding string button stringButtons.noteIndex = getSelectedString(); //live mode if (liveMode && !state.undoRedo && state.startLayoutTick > -1){ console.log("liveMode note input"); //temporarily disable liveMode to prevent infinite cerusion liveMode = false; var els = score ? score.selection.elements : []; for (var i = 0; i < els.length; ++i) { var el = els[i]; if ( el.type == Element.REST || el.type == Element.NOTE ) { duplicate(el); var tieBack = el.tieBack; var tieForward = el.tieForward; if (tieBack) { console.log("tie back"); ties(tieBack, true); } if (tieForward){ console.log("tie forward"); ties(tieForward, false); } } } // enable liveMode back again liveMode = true; } // save settings if (staffLines[0] && staffLines[1]){ var tracks = [staffLines[0].lines.track, staffLines[1].lines.track].toString(); var saved = score.metaTag("SCORDATURA_PLUGIN_TRACKS"); if (saved !== tracks) { console.log("Old setting: \n", tracks, "\n", saved); score.startCmd(); score.setMetaTag("SCORDATURA_PLUGIN_TRACKS", tracks); score.endCmd(); console.log("new: ", score.metaTag("SCORDATURA_PLUGIN_TRACKS")); } } if (tunings.length){ var scordTun = []; var standTun = []; for (var i = tunings[1].length; i-- > 0; ) { scordTun.push(tunings[1][i].name); standTun.push(tunings[0][i].name); } var savedScord = score.metaTag("SCORDATURA_PLUGIN_SCORDATURA_TUNING"); var savedStand = score.metaTag("SCORDATURA_PLUGIN_STANDARD_TUNING"); scordTun = scordTun.toString(); standTun = standTun.toString(); if (savedScord !== scordTun) { console.log("Old setting - tunings: \n", scordTun, ", ", standTun); score.startCmd(); score.setMetaTag("SCORDATURA_PLUGIN_SCORDATURA_TUNING", scordTun); score.endCmd(); console.log("newScordTun: ", score.metaTag("SCORDATURA_PLUGIN_SCORDATURA_TUNING")); } if (savedStand !== standTun) { score.startCmd(); score.setMetaTag("SCORDATURA_PLUGIN_STANDARD_TUNING", standTun); score.endCmd(); console.log("newStandTun: ", score.metaTag("SCORDATURA_PLUGIN_STANDARD_TUNING")); } } //highlight coresponding string button stringButtons.noteIndex = getSelectedString(); } // plugin white background Rectangle { color: "#fff" anchors.fill: parent } TabBar { id:menuBar width: parent.width currentIndex: 1 enabled: !!score //position: TabBar.Header TabButton { text: "Main" enabled: stringDefs.state == "DONE" } TabButton { text: "Settings" } TabButton { text: "Tools" } } StackLayout { width: parent.width currentIndex: menuBar.currentIndex anchors.top: menuBar.bottom anchors.topMargin: 10 enabled: !!score Item { id: main Column { id: modeSwitchPanel spacing: 3 leftPadding: 6 rightPadding: 6 Label { text: "Mode" font.pixelSize: 16 } Switch { id: modeSwitch text: "Live (experimental)" onPositionChanged: { console.log("switch changed", position); if (checked){ if ( !noNotesOnStaff(staffLines[transSelect.currentIndex ^ 1].lines) ) nonEmptyWarning.open(); else transcribe(); } else liveMode = false; } } } ToolSeparator { anchors.top: modeSwitchPanel.bottom width: parent.width orientation: Qt.Horizontal } Column { id: stringPanel anchors.top: modeSwitchPanel.bottom anchors.topMargin: 13 spacing: 3 leftPadding: 6 rightPadding: 6 Label { text: "Strings" font.pixelSize: 16 } Repeater { id: stringButtons property var noteIndex: false model: tunings[0] //standTun Button { height: 30 text: modelData.name onClicked: setSelectedString(index) highlighted: index === stringButtons.noteIndex } } } ToolSeparator { anchors.top: stringPanel.bottom width: parent.width orientation: Qt.Horizontal } Column { id: translatorPanel width: parent.width anchors.top: stringPanel.bottom anchors.topMargin: 13 spacing: 6 leftPadding: 6 rightPadding: 6 enabled: !modeSwitch.checked property color textColor: enabled ? "black" : "darkgray" Label { text: "Translate" font.pixelSize: 16 color: translatorPanel.textColor } Row { Label { anchors.verticalCenter: transSelect.verticalCenter text: "Source staff:" color: translatorPanel.textColor } ComboBox { id: transSelect height: 30 model: ["Scordatura", "Sounding"] } } Row { spacing: 16 Label { text: "Target staff:" color: translatorPanel.textColor } Label { text: transSelect.currentIndex ? "Scordatura" : "Sounding" color: translatorPanel.textColor } } Button { width: parent.width - 12 text: "Translate" onClicked: { if ( !noNotesOnStaff(staffLines[transSelect.currentIndex ^ 1].lines) ) { nonEmptyWarning.open(); } else { transcribe(); } } } } ToolSeparator { anchors.top: translatorPanel.bottom width: parent.width orientation: Qt.Horizontal } /* Column { id: correctionPanel anchors.top: translatorPanel.bottom anchors.topMargin: 20 spacing: 3 property bool activeButton: (corrections.length > 0) Label { text: "Collisions" font.pixelSize: 16 anchors.horizontalCenter: parent.horizontalCenter } Button { anchors.horizontalCenter: parent.horizontalCenter text: "Find" //onClicked: ToolTip.visible: hovered ToolTip.delay: Qt.styleHints.mousePressAndHoldInterval ToolTip.text: "Find string collisions" } ToolSeparator { width: 100 topPadding: 0 bottomPadding: 0 orientation: Qt.Horizontal } Row { spacing: 4 anchors.horizontalCenter: parent.horizontalCenter Button { width: 47 text: "<" enabled: correctionPanel.activeButton //onClicked: // prev problem ToolTip.visible: hovered ToolTip.delay: Qt.styleHints.mousePressAndHoldInterval ToolTip.text: "Previous Collision" } Button { width: 47 text: ">" //onClicked: // next problem enabled: correctionPanel.activeButton ToolTip.visible: hovered ToolTip.delay: Qt.styleHints.mousePressAndHoldInterval ToolTip.text: "Next Collision" } } Button { anchors.horizontalCenter: parent.horizontalCenter text: "Fixed" //onClicked: // mark as fixed = remove problem enabled: correctionPanel.activeButton ToolTip.visible: hovered ToolTip.delay: Qt.styleHints.mousePressAndHoldInterval ToolTip.text: "Mark collision as fixed" } } ToolSeparator { anchors.top: correctionPanel.bottom anchors.topMargin: 8 width: parent.width orientation: Qt.Horizontal } */ } Item { id: settings width: parent.width Column { id: staffDefs leftPadding: 6 rightPadding: 6 spacing: 6 Label { text: "Working Staves" font.pixelSize: 16 } states: [ State { name: "DONE" }, State { name: "DEFAULT" } ] Repeater { model: ["Scordatura", "Sounding"] Row { spacing: 3 Column { spacing: 3 Label { text: modelData + " staff: " } Text { id: staffName width: 160 //property var name: "" text: staffLines[index] ? staffLines[index].name : "Select staff and click 'Set'." } } Button { id: setStaff height: parent.height width: 70 text: staffLines[index] ? "Change" : "Set" states: [ State { name: "DEFAULT" when: staffDefs.state == "DEFAULT" }, State { name: "UNSELECTED" PropertyChanges { target: staffName; text: "Select some (staff) element"} }, State { name: "SET" //PropertyChanges { target: setStaff; text: "Change"} //PropertyChanges { target: staffName; text: staffLines[index] ? staffLines[index].name : ""} } ] onClicked: { var tunDef = index ? scordDef : standDef; if (staffLines[index]) { staffLines[index] = null; staffLinesChanged(); modeSwitch.checked = false; //setStaff.state = "" //tunings = []; } else { var sel = score ? score.selection.elements[0] : null; if ( sel && sel.track > -1 ) { var lines = score.firstSegment().elementAt(parseInt(sel.track/4)*4); staffLines[index] = {"name": lines.staff.part.longName, "lines": lines}; staffLinesChanged(); tunDef.text = (index ? score.metaTag("SCORDATURA_PLUGIN_SCORDATURA_TUNING") : score.metaTag("SCORDATURA_PLUGIN_STANDARD_TUNING")) || findTuning(staffLines[index].lines.staff.part.instruments[0]); setStaff.state = "SET"; } else setStaff.state = "UNSELECTED" } staffDefs.state = staffLines.indexOf(null) == -1 ? "DONE" : ""; aplyDef.state = ""; } } } } } ToolSeparator { width: parent.width anchors.top: staffDefs.bottom orientation: Qt.Horizontal } Column { id: stringDefs anchors.top: staffDefs.bottom leftPadding: 6 rightPadding: 6 anchors.topMargin: 12 spacing: 6 enabled: staffLines[0] && staffLines[1] states: [ State { name: "DONE" when: aplyDef.state == "SET" } ] Label { text: "Tunings" font.pixelSize: 16 } Row { spacing: 6 Column { spacing: 3 Label { text: "Scordatura tuning:" font.weight: Font.Bold } TextField { id: scordDef height: 25 width: 158 font.weight: Font.Bold text: "" readOnly: false activeFocusOnPress: true ToolTip.visible: hovered ToolTip.delay: Qt.styleHints.mousePressAndHoldInterval ToolTip.text: "Tuning in format: G3, D#4, Ab4, E5" } Label { text: "Standard tuning:" } TextField { id: standDef height: 25 width: 158 text: "" readOnly: false activeFocusOnPress: true ToolTip.visible: hovered ToolTip.delay: Qt.styleHints.mousePressAndHoldInterval ToolTip.text: "Tuning in format: G3, D#4, Ab4, E5" } } Button { id: aplyDef anchors.bottom: parent.bottom height: 53 width: 70 enabled: aplyDef.state == "SET" || standDef.text && scordDef.text && standDef.text.split(/\s*,\s*/).length === scordDef.text.split(/\s*,\s*/).length text: "Set" states: [ State { name: "SET" PropertyChanges { target: aplyDef; text: "Edit"} PropertyChanges { target: standDef; readOnly: true} PropertyChanges { target: standDef; activeFocusOnPress: false} PropertyChanges { target: scordDef; readOnly: true} PropertyChanges { target: scordDef; activeFocusOnPress: false} } ] ToolTip.visible: hovered ToolTip.delay: Qt.styleHints.mousePressAndHoldInterval ToolTip.text: aplyDef.state == "SET" ? "Edit tunings" : aplyDef.enabled ? "Set tunings" : "Both tunings need to have same count of strings" // qt bug - tooltip doesnt show on disabled button - hack below onClicked: { if (aplyDef.state == "SET") { tunings = []; aplyDef.state = "" modeSwitch.checked = false; muteSwitch(staffLines[0].lines.staff.part, true); //unmute ex-scordatura staff console.log("String defs - set"); } else { tunings = [getTun(standDef.text), getTun(scordDef.text)]; var validator = validateTuns(tunings); console.log("Set string defs - validator: ", validator.valid, ", ", validator.instruments); aplyDef.state = "SET"; muteSwitch(staffLines[0].lines.staff.part); //mute scordatura staff if (validator.valid) { menuBar.currentIndex = 0; validate.visible = false } else { validate.visible = true; validatorMessage.text = "Given string values are different to instrument(s) string data values. \n" + "You should correct it. \n\nCheck instrument(s): " + validator.instruments.toString() + "\n\nScordatura staff needs standard tuning and Sounding staff needs scordatura tuning." } } } } } Rectangle { id: validate visible: false width: settings.width - 12 height: validatorMessage.implicitHeight + validatorButtons.implicitHeight color: "transparent" border.color: "#FF0000" border.width: 1 Text { id: validatorMessage width: parent.width padding: 3 leftPadding: 6 rightPadding: 6 wrapMode: Text.Wrap } Row { id: validatorButtons anchors.bottom: parent.bottom width: parent.width padding: 1 spacing: 6 Button { width: parent.width / 2 - 4 text: "Change" onClicked: { aplyDef.state = ""; validate.visible = false; } } Button { width: parent.width / 2 - 4 text: "Ignore" onClicked: { aplyDef.state = "SET"; menuBar.currentIndex = 0; validate.visible = false } } } } } Item { //HACK - tooltip for disabled button (QT 5 bug) anchors.bottom: stringDefs.bottom anchors.right: stringDefs.right width: aplyDef.width height: aplyDef.height visible: !aplyDef.enabled ToolTip.visible: ma.containsMouse ToolTip.delay: Qt.styleHints.mousePressAndHoldInterval ToolTip.text: "Both tunings need to have same count of strings" MouseArea { id: ma anchors.fill: parent hoverEnabled: true } } } Item { id: tools width: parent.width Column { //id: hideAccs width: parent.width leftPadding: 6 rightPadding: 6 spacing: 6 Label { text: "Hide Accidentals" font.pixelSize: 16 } Text { width: parent.width - 12 text: "Select notes, on which You want to hide accidentals." wrapMode: Text.Wrap } Button { text: "Hide" onClicked: hideAccidentals() } ToolSeparator { width: parent.width - 12 orientation: Qt.Horizontal } Label { text: "Create Scordatura Symbol\n(experimental)" font.pixelSize: 16 } Button { text: "Create" enabled: !!tunings[1] onClicked: createScordaturaSymbol(); } CheckBox { id: symAlternative text: "Alternative" } } } } Dialog { id: nonEmptyWarning width: scordaturaPlugin.width //parent: Overlay.overlay modal: true title: "Warning" standardButtons: Dialog.Ok | Dialog.Cancel onAccepted: { transcribe(); } onRejected: { modeSwitch.checked = false; } background: Rectangle { border.color: "#f00" } Column { spacing: 20 anchors.fill: parent Label { text: staffLines[transSelect.currentIndex ^ 1] ? "" + staffLines[transSelect.currentIndex ^ 1].name + " staff is not empty. Content will be overwritten." : "" width: parent.width wrapMode: Text.Wrap } /*CheckBox { text: "Do not ask again" anchors.right: parent.right }*/ } } }