/* MuseScore Plugin: Score Chunker * * 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 * ----------- * Split a score into batches of measures * * 04 Nov 2024 | 1.0 | Initial Release */ import QtQuick 2.2 import QtQuick.Controls 1.5 import QtQuick.Layouts 1.3 import QtQuick.Controls.Styles 1.4 import QtQuick.Dialogs 1.2 import FileIO 3.0 import MuseScore 3.0 MuseScore { version: "1.0" description: "Score Chunker" menuPath: "Plugins.Score Chunker" requiresScore: true pluginType: "dialog" // Global variables property var zLog : true // view process log only property var tlog : "" // text output log line property var nofS : 0 // no. of staves property var nofM : 0 // no. of measures property var nofX : 0 // no. of map entries property var nofC : 0 // no. of chunks property var tickEnd : 0 // end of score tick property var msPath : "" property var msFile : "" property var tout : "" // text output file // Score Map property var mIX : [] // Map index property var mMez : [] // Measure property var mTick : [] // Segment ticj // Chunk Map property var cIX : [] property var cMez : [] property var cTick : [] property var cMoz : [] property var cTock : [] FileIO { id: fid; source: msPath } // onRun: {} //================================================================================================================================= function chunkScore() { var ii =0; var nun = 0; // no. of undos required var nix = 0; // maximum index var nox = 0; // padding formatter var tMez = ""; var tMoz = ""; var chuf = txtChunk.text; var msChuf = "" // Path to .mscz chunk mapScore(); mapChunks(); msPath = curScore.path.substring(0, curScore.path.lastIndexOf("/")+1) + curScore.scoreName; var cursor = curScore.newCursor() var selMez; var tockEnd = tickEnd+1; // select beyond start of last tick to select all!! nix = nofC - 1; if (nofM<10) nox=-1; else if (nofM<100) nox=-2 else if (nofM<1000) nox=-3 else nox=-4 for (ii=0; ii0) { curScore.startCmd(); selMez = curScore.selection.selectRange(0,cTock[ii-1],0,nofS); curScore.endCmd(); cmd('time-delete'); nun++; } tMez = "000"+cMez[ii].toString(); tMoz = "000"+cMoz[ii].toString(); tMez = tMez.substr(nox); tMoz = tMoz.substr(nox); msChuf = msPath + "_" + chuf + tMez + "_" + tMoz + ".mscz"; writeScore(curScore, msChuf, "mscz"); if (nun>0) { cmd("undo"); if (nun>1) cmd("undo") } } curScore.selection.clear(); mIX.length = 0; mMez.length = 0; mTick.length = 0; cIX.length = 0; cMez.length = 0; cTick.length = 0; cMoz.length = 0; cTock.length = 0; } //================================================================================================================================= function viewMaps() { var ii = 0; mapScore(); tout = "

Score Chunker " + version + "

"; tout += "

Score Map

"; tout += "

"; tout += ""; for (ii=0; ii < nofX; ++ii) { tout += ""; } tout += "
IXStaffMezTick
 " + mIX[ii] + "  " + mMez[ii]; tout += " " + mTick[ii] + " 

 

"; mapChunks(); tout += "

Chunk Map

"; tout += "

"; tout += ""; for (ii=0; ii < nofC; ++ii) { tout += "
IXMezMozTickTock
 " + cIX[ii]; tout += "  " + cMez[ii]+ "  " + cMoz[ii]; tout += "  " + cTick[ii]+ "  " + cTock[ii]; } tout += "

 

"; tout += ""; popInfo.text = tout; tout = ""; infoWin.visible = true; // Clear Maps for next run and for memory release mIX.length = 0; mMez.length = 0; mTick.length = 0; cIX.length = 0; cMez.length = 0; cTick.length = 0; cMoz.length = 0; cTock.length = 0; } //================================================================================================================================= function mapScore() // Build score map and chunk map { var seg; var mez; var elm; var tick; var tt; nofS = curScore.nstaves; nofX = 0; nofM = 0; mez = curScore.firstMeasure; while (mez) { ++nofM; seg = mez.firstSegment; while (seg) { tick = seg.tick; for (tt=0; tt<4; tt++) { elm = seg.elementAt(tt); if (elm) { if (elm.type == Element.CHORD || elm.type == Element.REST) { mIX.push(nofX); mMez.push(nofM); mTick.push(tick); nofX++; } } } seg = seg.nextInMeasure; } mez = mez.nextMeasure; } tickEnd = curScore.lastSegment.tick; mIX.push(0); // end of score mMez.push(0); mTick.push(tickEnd); nofX++; } //================================================================================================================================= function mapChunks() { var chmin = inpChunk.value; var chmax = chmin + inpExcess.value; var nrem = 0; // measures remaining var nchu = 0; // measures to chunk var mzfr = 0; // measure from var mzto = 0; // measure to var ix = 0; // map index var ox = -1; // other map index var tick = 0; // var tock = 0; nrem = nofM; while (nrem>0) { if (nrem<=chmax) nchu = nrem; else nchu = chmin; mzfr = mzto + 1; mzto = mzfr + nchu - 1; tick = mTick[ix] ix++; while (mMez[ix]>0 && mMez[ix]<=mzto) { ix++; } tock = mTick[ix]; ox++; cIX.push(ox); cMez.push(mzfr); cTick.push(tick); cMoz.push(mzto); cTock.push(tock); nrem -= nchu; } nofC = ox+1; } //================================================================================================================================= // USER INTERFACE id: uiChunker width: 250 height: 105 Label { id: lblChunk x: 10; y:15 width: 30; height:24 color: "#000000" text: "Size" } SpinBox { id: inpChunk x: 60; y:10 width: 60; height: 24 decimals: 0 minimumValue: 1 maximumValue: 20 value: 4 } Button { id: btnApply x: 140; y:10 width: 90; height: 36 text: "Chunk Score"; style: ButtonStyle { background: Rectangle { border.width: 1; border.color: "#999"; color: "#d1e0d1" radius: 15 } } onClicked: chunkScore() } Label { id: lblExtra x: 10; y:45 width: 60; height:24 color: "#000000" text: "Excess" } SpinBox { id: inpExcess x: 60; y:40 width: 60; height: 24 decimals: 0 minimumValue: 0 maximumValue: 5 value: 1 } Button { id: btnViewLog x: 140; y:60 width: 90; height: 36 text: "View Map" style: ButtonStyle { background: Rectangle { border.width: 1; border.color: "#999"; color: "#c9dbe8" radius: 15 } } onClicked: viewMaps() } Label { id: lblSuffix x: 10; y:76 width: 60; height:24 color: "#000000" text: "Suffix" } TextField { id: txtChunk x: 60; y:72 width: 60; height: 24 text: "M" } ApplicationWindow { id: infoWin x: 20; y: 70 width: 300; height: 800 title: "Score Chunker" visible: false TextArea { id: popInfo width: infoWin.width; height: infoWin.height textMargin: 15 textFormat: TextEdit.RichText readOnly: true wrapMode: TextEdit.Wrap text: "" } } }