/* Copyright © 2024 yonah_ag * * This program is free software; you can redistribute it 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 accompanying LICENSE file. * * Description * ----------- * */ import MuseScore 3.0 import QtQuick 2.2 import QtQuick.Controls 1.5 import QtQuick.Dialogs 1.2 import QtQuick.Layouts 1.3 import FileIO 3.0 // import QtQuick.Controls.Styles 1.3 // import Qt.labs.settings 1.0 MuseScore { description: "Bow Direction"; requiresScore: true; version: "0.5.5"; menuPath: "Plugins.Bow Direction"; pluginType: "dialog"; // "dock"; property var noteName : [ "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"] property var bowText : "xdup45678r" property var mTick : [] property var mBow : [] property var mSlut : [] property var mSlur : [] property var tinfo : "" property var mxPath : ""; FileIO { id: fid; source: mxPath } // ======================================================================================================================= function mapAll() { infoWin.visible = true; var ix=0; var tick; var info; var elm; var etyp var zunk; // unknown element var zsys; // parent is system tinfo = ""; tinfo += "

All Elements

"; tinfo += ""; tinfo += ""; cmd('select-all') var selected = curScore.selection.elements; for (var ee in selected) { elm = selected[ee]; zunk = false; zsys = false; etyp = elm.type; switch (etyp) { case Element.NOTE: if (elm.noteType==0) { tick = elm.parent.parent.tick; info = noteName[(elm.pitch % 12)]; if (elm.tieForward) info += " ~f"; if (elm.tieBack) info += " ~b"; } else { tick = elm.parent.parent.parent.tick + " *"; // grace note info = noteName[(elm.pitch % 12)] + " *"; } break; case Element.STEM: case Element.HOOK: case Element.ACCIDENTAL: case Element.ARPEGGIO: tick = ""; info = ""; break; case Element.REST: case Element.BAR_LINE: case Element.DYNAMIC: case Element.HARMONY: case Element.TEMPO_TEXT: case Element.CLEF: tick = elm.parent.tick; switch (etyp) { case Element.REST: info = elm.duration.str; break; case Element.DYNAMIC: info = "v = " + elm.velocity; break; case Element.HARMONY: info = elm.text; break; default: info = ""; } break; case Element.ARTICULATION: case Element.LYRICS: if (etyp==Element.ARTICULATION) { info = elm.symbol; if (info==2652) { info = "Down bow"; } else if (info==2677) { info = "Up bow"; } } else { info = elm.text; } tick = elm.parent.parent.tick; break; case Element.STAFF_TEXT: tick = elm.parent.tick; info = elm.text.substring(0,20); break; case Element.NOTEDOT: tick = elm.parent.parent.parent.tick; info = ""; break; case Element.BREATH: tick = elm.parent.tick; info = elm.symbol; break; case Element.BEAM: case Element.TIE: case Element.SLUR: case Element.TEXTLINE: case Element.VIBRATO: zsys = true; tick = ""; info = ""; break; default: tick = ""; info = ""; zunk = true; } ++ix; tinfo += ""; } curScore.selection.clear(); tinfo += "
 index  Track  Element Name  SegTick " tinfo += " Details  ^Parent^   ^^Parent^^ 
" + ix + " "; tinfo += "" + elm.track; tinfo += " " + elm.type + " - " + elm.name + " "; tinfo += "" + tick + " "; tinfo += " " + info + " "; if (zunk) tinfo += "  "; else if (zsys) tinfo += " System "; else { tinfo += " " + elm.parent.name + " "; tinfo += " " + elm.parent.parent.name + " "; } tinfo += "
"; popInfo.text = tinfo; } // ======================================================================================================================= function mapBow() { infoWin.visible = true; var ix=0; var ee; var tick; var tock = -1; // previous note tick (for chord detection) var elm; var etyp; var zelm; var zunk; // unknown element var zsys; // parent is system var nslur = 0; // count of slurs var info; tinfo = ""; tinfo += "

Bowing Elements

"; tinfo += ""; tinfo += ""; cmd('select-all') var selected = curScore.selection.elements; for (ee in selected) { elm = selected[ee]; if (elm.track > 0) continue; // only process first track zunk = false; zsys = false; etyp = elm.type; switch (etyp) { case Element.NOTE: if (elm.noteType==0) { tick = elm.parent.parent.tick; if (tick > tock) { // no bow change on chords tock = tick; zelm = true; info = noteName[(elm.pitch % 12)]; if (elm.tieForward) info += " ~f"; if (elm.tieBack) info += " ~b"; } else zelm = false; } break; case Element.REST: zelm = true; tick = elm.parent.tick; tock = tick; info = elm.duration.str; break; case Element.ARTICULATION: info = elm.symbol; zelm = false; if (info==2652) { info = "Down bow"; tick = elm.parent.parent.tick; zelm = true; } else if (info==2677) { info = "Up bow"; tick = elm.parent.parent.tick; zelm = true; } break; case Element.STAFF_TEXT: info = elm.text; zelm = (info == "pizz." || info == "arco"); if (zelm) tick = elm.parent.tick; break; case Element.BREATH: zelm = true; tick = elm.parent.tick; info = elm.symbol; break; case Element.TIE: zelm = true; zsys = true; tick = ""; info = ""; break; case Element.SLUR: ++nslur; zelm = true; zsys = true; tick = ""; info = ""; break; default: zelm = false; } if (zelm) { ++ix; tinfo += ""; } } curScore.selection.clear(); tinfo += "
 index  Track  Element Name  SegTick " tinfo += " Details  ^Parent^   ^^Parent^^ 
" + ix + " "; tinfo += "" + elm.track; tinfo += " " + elm.name + " "; tinfo += "" + tick + " "; tinfo += " " + info + " "; if (zsys) tinfo += " System "; else { tinfo += " " + elm.parent.name + " "; tinfo += " " + elm.parent.parent.name + " "; } tinfo += "

"; tinfo += "Slur count = " + nslur + "

"; if (nslur > 0 ) { tinfo += ""; var mxFile; // musicxml file contents var dmul; // divisions multiplier var tock; // slur tick mxPath = curScore.path.substring(0, curScore.path.lastIndexOf("/")+1) + curScore.scoreName+".musicxml"; writeScore(curScore, mxPath, "musicxml"); mxFile = fid.read(); mxFile = mxFile.replace(/[^]+?([^]+?)<\/divisions>/, '$1'); ix = mxFile.search("\n"); dmul = 480 / mxFile.substring(0,ix); mxFile = mxFile.replace(/[^]+/,''); mxFile = mxFile.replace(/[^]+?<\/note>/g,''); mxFile = mxFile.replace(/[^]+?(|)/g,'$2\n'); mxFile = mxFile.replace(/<\/duration>/g,''); mxFile = mxFile.replace(/\n\n/g,'\nz\n'); fid.write(mxFile); tick = 0; ix = mxFile.search('\n'); while (ix >= 0) { elm = mxFile.substring(0,ix); if (isNaN(elm)) { if (elm == "z") break; tinfo += ""; } else { tock = tick; tick += dmul * elm; } mxFile = mxFile.substring(ix+1); ix = mxFile.search('\n'); } mxFile = ''; } tinfo += ""; popInfo.text = tinfo; } // ======================================================================================================================= function mapTick() { var ix; var ox; var ibow; var tick; var info; var elm; var etyp; var ee; var tock = -1; // previous note tick (for chord detection) var nslur = 0; // count slurs if (inpMapType.currentIndex==2) infoBow.visible = true; cmd('select-all') var selected = curScore.selection.elements; mTick.length = 0; mBow.length = 0; ix = -1; ibow = 1; for (var ee in selected) { elm = selected[ee]; if (elm.track > 0) continue; // only process first track etyp = elm.type; switch (etyp) { case Element.NOTE: tick = elm.parent.parent.tick; if (elm.noteType==0 && tick > tock) { // no bow change on chords tock = tick; if (elm.tieBack) ibow = 3-ibow; ix++; mTick.push(tick); mBow.push(ibow); if (ibow<3) ibow = 3-ibow; } break; case Element.REST: tick = elm.parent.tick; tock = tick; ix++; mTick.push(tick); mBow.push(9); ibow = 1; break; case Element.ARTICULATION: info = elm.symbol; if (info==2652) { mBow[ix]=1; ibow = 2; } else if (info==2677) { mBow[ix]=2; ibow = 1; } break; case Element.STAFF_TEXT: info = elm.text; if (info=="pizz.") { ibow = 3; } else if (info=="arco") { ibow = 1; } break; case Element.BREATH: tick = elm.parent.tick; ibow =1; break; case Element.SLUR: ++nslur; break; // case Element.TIE: // handled via note tieBack property // break; } } curScore.selection.clear(); if (nslur > 0 ) { tinfo += "
TickSlur
" + tock + "" + elm.substr(8,1) + "
"; var mxFile; // musicxml file contents var dmul; // divisions multiplier var tock; // slur tick mxPath = curScore.path.substring(0, curScore.path.lastIndexOf("/")+1) + curScore.scoreName+".musicxml"; writeScore(curScore, mxPath, "musicxml"); mxFile = fid.read(); mxFile = mxFile.replace(/[^]+?([^]+?)<\/divisions>/, '$1'); ix = mxFile.search("\n"); dmul = 480 / mxFile.substring(0,ix); mxFile = mxFile.replace(/[^]+/,''); mxFile = mxFile.replace(/[^]+?<\/note>/g,''); mxFile = mxFile.replace(/[^]+?(|)/g,'$2\n'); mxFile = mxFile.replace(/<\/duration>/g,''); mxFile = mxFile.replace(/\n\n/g,'\nz\n'); fid.write(mxFile); tick = 0; ix = mxFile.search('\n'); while (ix >= 0) { elm = mxFile.substring(0,ix); if (isNaN(elm)) { if (elm == "z") break; mSlut.push(tock); mSlur.push(elm.substr(8,1)) } else { tock = tick; tick += dmul * elm; } mxFile = mxFile.substring(ix+1); ix = mxFile.search('\n'); } mxFile = ''; ix=0; ox=0; for (ee=0; ee<=nslur; ee++) { // Now patch the slurs into the main map tock = mSlut[ox]; // slur start tick = mTick[ix]; while (tick < tock) { ix++; tick = mTick[ix] } ibow = mBow[ix]; // bow code at start of slur ox++; tock = mSlut[ox]; // slur stop ix++; tick = mTick[ix]; while (tick <= tock) { mBow[ix] = ibow; ix++; tick = mTick[ix]; } ox++; } } if (inpMapType.currentIndex==2) { tinfo = ""; tinfo += "

Bow @ Tick

"; tinfo += "

TickSlur
"; tinfo += ""; for (ee = 0; ee < mTick.length; ee++) { tinfo += ""; } if (nslur > 0 ){ tinfo += ""; for (ee = 0; ee <= nslur; ee++) { tinfo += ""; } } tinfo += "
 SegTick  Bow 
"+mTick[ee]+" "; tinfo += ""+bowText.substring(mBow[ee],mBow[ee]+1)+" 
Slurs 
"+mSlut[ee]+" "+mSlur[ee]+"
"; popBow.text = tinfo; } else { curScore.startCmd(); var cur = curScore.newCursor(); var tob; // text object for (ee = 0; ee < mTick.length; ee++) { cur.rewindToTick(mTick[ee]); tob = newElement(Element.STAFF_TEXT); tob.text = bowText.substring(mBow[ee],mBow[ee]+1); tob.color = "#0000D0"; tob.placement = Placement.ABOVE; tob.align = 8; tob.fontFace = "FreeSans"; // tob.offsetX = 0.5; tob.offsetY = -5; tob.fontSize = 8; tob.fontStyle = 0; // 0:Normal, 1:Bold tob.autoplace = true; cur.add(tob); } curScore.endCmd(); } } // ======================================================================================================================= function mapScore() { console.time("Process"); switch (inpMapType.currentIndex) { case 0: mapAll(); break; case 1: mapBow(); break; case 2: case 3: mapTick(); break; default: tinfo = "Unknown processing option!"; } tinfo = ""; console.timeEnd("Process"); } // ======================================================================================================================= onRun: {} width: 300; height: 50; RowLayout { id: uiRow1 x: 15; y:15 Label { id: lblRow2 Layout.preferredWidth: 50 color: "#000000" text: "Process" } ComboBox { id: inpMapType model: ["Map all elements", "Map bowing elements", "Map bow @ tick", "Add bow text to score"] Layout.leftMargin: 0 Layout.preferredWidth: 175 currentIndex: 0 } Button { id: btnMapIt Layout.leftMargin: 0 Layout.preferredWidth: 40 Layout.preferredHeight: 24 text: "Go" onClicked: mapScore(); } } ApplicationWindow { id: infoWin x: 20; y: 50 width: 550; height: 900 title: "Score Map" visible: false TextArea { id: popInfo width: infoWin.width; height: infoWin.height textMargin: 15 textFormat: TextEdit.RichText readOnly: true wrapMode: TextEdit.Wrap text: "Processing..." } } ApplicationWindow { id: infoBow x: 20; y: 50 width: 200; height: 900 title: "Bowing" visible: false TextArea { id: popBow width: infoBow.width; height: infoBow.height textMargin: 15 textFormat: TextEdit.RichText readOnly: true wrapMode: TextEdit.Wrap text: "Processing..." } } }