//--------------------------------------------------------------------- // AddFKey MuseScore 3 plugin //--------------------------------------------------------------------- //============================================================================= // MuseScore // Music Composition & Notation // // Create F key part for diatonic accordion accompagnement MuseScore music score // // Copyright (C) 2020 Jean-Michel Bencetti // // 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 // //============================================================================= //-------------------------------------------------------------------------- /* Ce plugin ajoute une ligne Basse-Accord à une tablature obtenue par DiatonicTab sous MuseScore Auteur : Jean-Michel Bencetti Version courrante : 0.01 Date : v0.00.01 : 2020-04-18 : développement initial ---------------------------------------------------------------------------- */ import QtQuick 2.2 import MuseScore 3.0 import QtQuick.Controls 1.1 import QtQuick.Controls.Styles 1.3 import QtQuick.Layouts 1.1 import FileIO 3.0 MuseScore { version: "1.01.06.20200419.0858" description: "Write F Key part on a diatonic accordion sheet to play left hand" menuPath: "Plugins.DiatonicTab.addFkey" pluginType: "dialog" requiresScore: true width: 460 height: 350 //----------------------------------------------------- // Set here the language : FR = French, EN = English property string lang: "FR" //----------------------------------------------------- property var globalCursor: null; property var startTick: 0; property var endTick: 0; property var fullScore:true; property var tabNotes : { "C" : 0, "C#" : 1, "Db" : 1,"DB" : 1, "D" : 2, "D#" : 3, "Eb" : 3,"EB" : 3, "E" : 4, "E#" : 5, "Fb" : 4,"FB" : 4, "F" : 5, "F#" : 6, "Gb" : 6,"GB" : 6, "G" : 7, "G#" : 8, "Ab" : 8,"AB" : 8, "A" : 9, "A#" : 10,"Bb" : 10,"BB" : 10, "B" : 11,"B#" : 0, "Cb" : 11, "CB" : 11, }; //----------------------------------------------------- // Fichiers JSON pour la mémorisation des parametres //----------------------------------------------------- FileIO { id: myParameterFile source: homePath() + "/addFkey.json" onError: console.log(msg) } //----------------------------------------------------- // Critères à choisir dans la boîte de dialogue //----------------------------------------------------- property var parametres: { "anacrouze" : "NON", // Il y a ou pas une Anacrouze "doublerLesBasses": "NON", // Doubler les basses (son bando) "numPattern" : 0, // numéro du pattern "thePattern" : "", // Pattern s'il provient de la saisie "pasSilence" : "1/4", // pas pour poser les silences, noire par défaut } // ------------------------------------------------------------------- // Description de la fenêtre de dialogue //-------------------------------------------------------------------- GridLayout { anchors.fill: parent anchors.margins: 10 columns: 2 Label { Layout.columnSpan : 2 width: parent.width elide: Text.ElideNone horizontalAlignment: Qt.AlignCenter font.bold: true font.pointSize: 12.2 text: (lang == "FR") ? qsTr("Accompagnement Main Gauche") : qsTr("Left Hand part") } //------------------------------------------------ // Choix du Pattern Basse/Accord //------------------------------------------------ GroupBox { Layout.columnSpan : 2 Layout.fillWidth: true width: parent.width title : (lang == "FR") ? qsTr("Choix du Pattern : ") : qsTr("Pattern choice : ") GridLayout { columns : 2 Layout.fillWidth: true width: parent.width Label { width: parent.width wrapMode: Label.Wrap horizontalAlignment: Qt.AlignRight text: (lang == "FR") ? qsTr("Patterns de base : ") : qsTr("Basic patterns : ") } ComboBox { id: comboChoixPattern editable: false model: [ // La liste de choix doit être dans le même ordre que le tableau tabmodeleClavier // La liste de choix doit être dans le même ordre que le tableau tabmodeleClavier { text: qsTr("2/2 Ba") }, { text: qsTr("2/4 BaBa") }, { text: qsTr("2/4 Ba") }, { text: qsTr("3/4 Baa") }, { text: qsTr("3/4 BaB") }, { text: qsTr("3/4 B-B") }, { text: qsTr("4/4 BaBa") }, { text: qsTr("4/4 Ba-a") }, { text: qsTr("5/4 BaaBa") }, { text: qsTr("5/4 BaBaa") }, { text: qsTr("6/8 B-aB-a") }, { text: qsTr("6/8 BaaBaa") }, { text: qsTr("7/8 BaaBaBa") }, { text: qsTr("7/8 BaBaBaa") }, { text: qsTr("7/8 B-aBaBa") }, { text: qsTr("7/8 B--a-a-") }, { text: qsTr("8/8 BaaBaaBa") }, { text: qsTr("8/8 BaaBaBaa") }, { text: qsTr("9/8 B-aB-aB-a") }, { text: qsTr("9/8 BaaBaaBaa") }, { text: qsTr("12/8 B-aB-aB-aB-a")}, ] // Récupère le code du modèle de clavier onActivated: { parametres["numPattern"] = index console.log("Pattern : " + model[index].text) inputTextPattern.text = model[index].text.replace(/(\d+)\/(\d+)\s(\w+)/,"$3") parametres["thePattern"] = inputTextPattern.text } } // comboBox Label { width: parent.width Layout.columnSpan : 2 Layout.fillWidth: true wrapMode: Label.Wrap horizontalAlignment: Qt.AlignCenter text: (lang == "FR") ? qsTr("Ou") : qsTr("Or") } //------------------------------------------------ // Pattern personnalisé //------------------------------------------------ Label { width: parent.width / 2 wrapMode: Label.Wrap horizontalAlignment: Qt.AlignRight text: (lang == "FR") ? qsTr("Pattern personnalisé : ") : qsTr("Personalised pattern : ") } TextField { id : inputTextPattern placeholderText : "BaaBaa" onEditingFinished : { console.log("inputTextPattern = " + text) parametres["thePattern"] = text } } } // GridLayout } // GroupBox GroupBox { Layout.columnSpan : 2 Layout.fillWidth: true width: parent.width title : (lang == "FR") ? qsTr("Basses/accords : ") : qsTr("Bass/Chords : ") GridLayout { columns : 2 Layout.fillWidth: true width: parent.width //------------------------------------------------ // Choix du pas pour poser les Basses/Accords //------------------------------------------------ Label { width: parent.width wrapMode: Label.Wrap horizontalAlignment: Qt.AlignRight text: (lang == "FR") ? qsTr("Durée des Basses et des Accords : ") : qsTr("Bass and chords length : ") } ComboBox { id: comboChoixPasSilence editable: false model: [ // La liste de choix doit être dans le même ordre que le tableau tabmodeleClavier { text: (lang=="FR") ? "Noire":"Quarter note", value:"1/4" }, // soupir { text: (lang=="FR") ? "Croche":"Eighth note", value:"1/8" }, // demi soupir { text: (lang=="FR") ? "Double croche":"sixteenth note", value:"1/16" }, // Quart de soupir ] // Récupère le code du modèle de clavier onActivated: { parametres["pasSilence"] = index parametres["pasSilence"] = model[index].value // console.log("pasSilence : " + model[index].text) } } // ComboBox } // GroupBox } // GridLayout //------------------------------------------------ // Anacrouze et Doubler les basses //------------------------------------------------ GroupBox { Layout.columnSpan : 2 Layout.fillWidth: true width: parent.width title : (lang == "FR") ? qsTr("Autre : ") : qsTr("Other : ") GridLayout { Layout.fillWidth: true width: parent.width columns: 2 CheckBox { id: cbAnacrouse Layout.columnSpan: 1 text: (lang == "FR" ) ? qsTr("Il y a une anacrouse") : qsTr("Anacrouse on first measure") checked: parametres.anacrouse == "OUI" } CheckBox { id: cbDoublerLesBasses Layout.columnSpan: 1 text: (lang == "FR" ) ? qsTr("Doubler les basses (bando)") : qsTr("Double Bass (bando)") checked: parametres.doublerLesBasses == "OUI" } } // RowLayout } // ------ Les boutons ----------- RowLayout { Layout.fillWidth: true width: parent.width Layout.alignment: Qt.AlignCenter Layout.columnSpan: 2 Button { id: okButton isDefault: true text: qsTr("OK") onClicked: { // Mémorise les parametres pour la prochaine fois parametres.thePattern = inputTextPattern.text parametres.anacrouze = (cbAnacrouse.checked)? "OUI" : "NON"; parametres.doublerLesBasses = (cbDoublerLesBasses.checked)? "OUI" : "NON"; myParameterFile.write(JSON.stringify(parametres)) curScore.startCmd(); // ----------------------------------------------------------------------- // Il semble que la lélection se perde pendant la durée du travail. Il est donc // néssessaire d'utiliser une gestion golable du curseur // Le curseur renviendra en début de sélection en repartant du début et en avancant // jusqu'à starTick qui aura été mémorisé ici. // ----------------------------------------------------------------------- // Cherche à savoir s'il s'agit de la partition entière ou d'une sélection globalCursor = curScore.newCursor() globalCursor.staffIdx = 1 // On ne traite que la portée numéro 2 globalCursor.rewind(Cursor.SELECTION_START) // rembobine au début de la sélection if (!globalCursor.segment) { // pas de sélection fullScore = true } else { fullScore = false } globalCursor.rewind(Cursor.SELECTION_END) // passe derrière le dernier segment et fixe tick = 0 if (globalCursor.tick === 0) { // ceci survient lorsque la sélection contient la dernière mesure endTick = curScore.lastSegment.tick + 1; } else { endTick = globalCursor.tick } // ----------------------------------------------------------------------- if (fullScore) { // si pas de sélection // console.log("Pas de sélection") globalCursor.rewind(Cursor.SCORE_START) // rembobine au début de la partition } else { // si sélection // console.log("Il y a une sélection") globalCursor.rewind(Cursor.SELECTION_START) // rembobine au début de la sélection } startTick = globalCursor.tick // --------------------------------- doAddFKey(); // --------------------------------- curScore.endCmd(); Qt.quit(); } } Button { id: cancelButton text: (lang=="FR")?qsTr( "Annuler"):qsTr("Cancel") onClicked: { parametres.thePattern = inputTextPattern.text myParameterFile.write(JSON.stringify(parametres)) Qt.quit(); } } } // RowLayout des boutons Label { Layout.columnSpan: 2 Layout.fillWidth: true width: parent.width wrapMode: Label.Wrap horizontalAlignment: Qt.AlignCenter text: "v" + version } } // Fin de la description de la boîte de dialogue //------------------------------------------------------------------------------ // A l'activation du plugin, il se passe : // - Lecture du fichier de paramètres // - Remise des parametres dans la boîte de dialogue //------------------------------------------------------------------------------ onRun: { console.log("Kenavo") //------------------------------------------------------------------------------ // Lecture du fichier de parametres //------------------------------------------------------------------------------ // console.log("Lecture du fichier de parametres") parametres = JSON.parse(myParameterFile.read()) inputTextPattern.text = parametres.thePattern cbAnacrouse.checked = (parametres.anacrouze == "OUI") cbDoublerLesBasses.checked = (parametres.doublerLesBasses == "OUI") comboChoixPattern.currentIndex = parametres.numPattern //------------------------------------------------------------------------------ // Remet le bon pas de silence dans la comboBox //------------------------------------------------------------------------------ var numPasSilence comboChoixPasSilence.currentIndex = 0 for (numPasSilence = 0; numPasSilence < comboChoixPasSilence.model.length; numPasSilence++){ if (comboChoixPasSilence.model[numPasSilence].value == parametres.pasSilence){ comboChoixPasSilence.currentIndex = numPasSilence } } } // onRun //------------------------------------------------------------------------------ // Fonction principale doAddFKey //------------------------------------------------------------------------------ function doAddFKey() { var accordMg = "", // Accord à décomposer en Basse-Accord oldAccordMg = "", // Accord précédent basseAJouer = "" , // Basse à jouer accordAJouer = ""; // Accord à jouer var thePattern = inputTextPattern.text // console.log("Entrée dans la fonction doAddFKey") // Vérifie s'il y a bien au moins deux portées if (curScore.nstaves < 2) { console.log((lang=="FR")?qsTr("Il faut ajouter une portée en clé de Fa") : qsTr("You need add an F key part")) Qt.quit() } // ----------------------------------------------------------------------- // ----------------------------------------------------------------------- // ----------------------------------------------------------------------- // Écriture de l'accompagnement main gauche var nbMesures = curScore.nmeasures; if ((fullScore)&&(parametres.anacrouse == "OUI")) { globalCursor.nextMeasure() nbMesures -= 1; console.log("Il y a une anacrouze"); } globalCursor.staffIdx = curScore.nstaves - 1; // On ne traite que la dernière portée // Pour sécuriser la sortie de la boucle, on compte les notes à créer var nbNotes = nbMesures * thePattern.length var temps = 0 var note = 0 console.log("Entrée boucle de travail") console.log("endTick = " + (endTick-1)) var deltaTick = 1; while (deltaTick && globalCursor.segment && (globalCursor.tick < (endTick)) ){ // && (note++ < nbNotes)) { // var lastTick = globalCursor.tick; // Recherche des accords (genre Am ou Em ou E7 ...) // console.log("cursor.tick = "+globalCursor.tick) var aCount = 0; var annotation = globalCursor.segment.annotations[aCount]; while (annotation) { if (annotation.type == Element.HARMONY){ // console.log("Symbole d'accord : " + annotation.text); accordMg = annotation.text } annotation = globalCursor.segment.annotations[++aCount]; } // Si on a trouvé une nouvelle harmonie , on cherche la basse et l'accord if (accordMg != oldAccordMg) { // console.log("accordMG trouvé : " + accordMg) // Cherche si renversement if (accordMg.match("/")) { accordAJouer = accordMg.split("/")[0] basseAJouer = accordMg.split("/")[1] } else { basseAJouer = (accordMg.match("^[A-Ga-g]#")) ? accordMg[0]+"#" : (accordMg.match("^[A-Ga-g][Bb]")) ? accordMg[0] + "b" : accordMg[0] accordAJouer = accordMg } oldAccordMg = accordMg // console.log("Basse : " + basseAJouer + " Accord : " + accordAJouer) } // Fabrication de la ligne clé de Fa // ----------------------------------------------------------------------- // Détermine la longueur de note/accord/silence à ajouter var duree = 1; while ((thePattern[temps+duree] == "-") && (duree++ < thePattern.length)); globalCursor.setDuration(duree, parametres.pasSilence.split("/")[1]); // ----------------------------------------------------------------------- // Ajouts de notes ou d'un silence if ((accordAJouer != "") && (basseAJouer!="")){ var accordComplet = (thePattern[temps] == "A") || (thePattern[temps] == "C"); switch (thePattern[temps]) { case "B" : case "b" : case "C" : case "A" : // console.log("On ajoute une basse : " + basseAJouer + " pitch = " + (36+tabNotes[basseAJouer]) + " Durée = " + duree) globalCursor.addNote(36+tabNotes[basseAJouer]); if (parametres.doublerLesBasses == "OUI") globalCursor.addNote(36+12+tabNotes[basseAJouer],true); // if (parametres.addStaccato == "OUI") // cmd("add-tenuto") if (!accordComplet) break; case "a": case "c" : var fonda = accordAJouer.replace(/^([A-Ga-g][#bB]?).*/,"$1").toUpperCase(); var tierce = (accordAJouer.match(/^[A-Ga-g][#bB]?[m0O-]/)) ? 3 : (accordAJouer.match(/^[A-Ga-g][#bB]?dim/)) ? 3 : (accordAJouer.match(/^[A-Ga-g][#bB]?sus4/)) ? 5 : (accordAJouer.match(/^[A-Ga-g][#bB]?5/)) ? 0 : 4; var quinte = (accordAJouer.match(/^[A-Ga-g][#bB]?[oO0]/)) ? 6 : (accordAJouer.match(/^[A-Ga-g][#bB]?dim/)) ? 6 : (accordAJouer.match(/^[A-Ga-g][#bB]?aug/)) ? 8 : 7; var septieme = (accordAJouer.match(/^[A-Ga-g][#bB]?M7/)) ? 11 : (accordAJouer.match(/^[A-Ga-g][#bB]?m?7/)) ? 10 : (accordAJouer.match(/^[A-Ga-g][#bB]?.*9/)) ? 10 : (accordAJouer.match(/^[A-Ga-g][#bB]?\^/)) ? 11 : (accordAJouer.match(/^[A-Ga-g][#bB]?[Oo]/)) ? 9 : 0 ; var neuvieme = (accordAJouer.match(/^[A-Ga-g][#bB]?.*9/)) ? 14 : 0 ; globalCursor.addNote(48+tabNotes[fonda], accordComplet) if (tierce) globalCursor.addNote(48+((tabNotes[fonda]+tierce)%12),true) globalCursor.addNote(48+((tabNotes[fonda]+quinte)%12),true) if (septieme) globalCursor.addNote(48+((tabNotes[fonda]+septieme)%12),true) if (neuvieme) globalCursor.addNote(48+((tabNotes[fonda]+neuvieme)%12),true) // if (parametres.addStaccato == "OUI") // cmd("add-staccato") // console.log("On ajoute un accord : " + fonda + " pitch = " + (48+tabNotes[fonda]) + " Durée = " + duree) break case "-" : // Si c'est "2", on ne fait rien, on a posé une note double duration // console.log("On a trouvé un -, on ajoute rien") note+=1 break default: // console.log("On ajoute un silence" + " Durée = " + duree) // var silence = newElement(Element.REST) // globalCursor.add(silence) globalCursor.addNote(48) if (globalCursor.tick != lastTick) // Si le curseur a avancé on recule d'une note globalCursor.prev() var chord = globalCursor.element; if (chord.notes) { while (chord.notes.length > 1) chord.remove(chord.notes[0]); removeElement(chord.notes[0]) } globalCursor.next() // on s'avance pour la note d'après. break } // fin du switch temps = (temps+duree) % thePattern.length } else { // Si on a pas d'accord, on avance. globalCursor.next() } deltaTick = globalCursor.tick - lastTick; // Vérifie si le curseur a avancé } // fin du while (cursor.segment && (fullScore || cursor.tick < endTick)) } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ }