From b77e739fd33b783862f493c9bf12b1a95dcd2fb4 Mon Sep 17 00:00:00 2001 From: JB91653 Date: Sun, 7 Dec 2025 20:30:09 -0800 Subject: [PATCH 1/7] Create harmonica_tablature_MS46 Harmonica Tab plugin, cleaned up code, stylized selection box, added text color and size options, added tremolo 24-hole harmonica, added chromatic 10-hole harmonica, updated default selections. --- harmonica_tablature_MS46 | 768 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 768 insertions(+) create mode 100644 harmonica_tablature_MS46 diff --git a/harmonica_tablature_MS46 b/harmonica_tablature_MS46 new file mode 100644 index 0000000..85cf489 --- /dev/null +++ b/harmonica_tablature_MS46 @@ -0,0 +1,768 @@ +//============================================================================= +// MuseScore +// Music Composition & Notation +// +// Harmonica Tabs Plugin +// +// +// 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 +//============================================================================= + +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import MuseScore 3.0 + +MuseScore { + + version: "4.6" + pluginType: "dialog" + title: "Harmonica Tablature Setup" + thumbnailName: "harmonica_tabs.png" + +// ------ OPTIONS ------- + property string sep : "\n" // change to "," if you want tabs horizontally + property string bendChar : "'" // change to "b" if you want bend to be noted with b +// ------ OPTIONS ------- + + id: window + width: 550 + height: 300 + + property var itemTextX1 : 20 + property var itemTextY1 : 20 + property var itemTextY2 : 20 + property var xPositionInit : 0.0 + property var yPositionInit : 0.0 + property color dropdownBase: "lightsteelblue" + property color dropdownText: "#001B2E" + + Rectangle { + id: mainInput + anchors.fill: parent + color: "#000A57" + + function comboStyle() { + } + + Column { + id: column + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + anchors.margins: 10 + spacing: 17 + //z: 5 + anchors.topMargin: 25 + height: parent.height - 70 + + Row { + id:keyRow + spacing: 20 + width: parent.width + x: 20 + + Label { + font.pointSize: 14 + font.family: "Inter" + //font.weight: Font.ExtraBold + font.bold: true + color: "#00C8D7" + anchors.verticalCenter: parent.verticalCenter + width: parent.width / 4 + horizontalAlignment: Text.AlignRight + text: "Harmonica key:" + } + + ComboBox { + id: keyBox + anchors.verticalCenter: parent.verticalCenter + textRole: "text" + implicitWidth: 50 + implicitHeight: 25 + + background: Rectangle { + color: dropdownBase + radius: 12 + clip: true + } + + contentItem: Text { + text: keyBox.displayText + color: dropdownText + font.pointSize: 12 + font.bold: true + verticalAlignment: Text.AlignVCenter + elide: Text.ElideRight + leftPadding: 15 + } + + currentIndex: 17 + model: ListModel { + id: keylist + property var key: undefined // Initialize property with default value (updated F# to 54) + ListElement { text: "Low G"; harpkey: 43 } + ListElement { text: "Low Ab"; harpkey: 44 } + ListElement { text: "Low A"; harpkey: 45 } + ListElement { text: "Low Bb"; harpkey: 46 } + ListElement { text: "Low B"; harpkey: 47 } + ListElement { text: "Low C"; harpkey: 48 } + ListElement { text: "Low C#"; harpkey: 49 } + ListElement { text: "Low D"; harpkey: 50 } + ListElement { text: "Low Eb"; harpkey: 51 } + ListElement { text: "Low E"; harpkey: 52 } + ListElement { text: "Low F"; harpkey: 53 } + ListElement { text: "Low F#"; harpkey: 54 } + ListElement { text: "G"; harpkey: 55 } + ListElement { text: "Ab"; harpkey: 56 } + ListElement { text: "A"; harpkey: 57 } + ListElement { text: "Bb"; harpkey: 58 } + ListElement { text: "B"; harpkey: 59 } + ListElement { text: "C"; harpkey: 60 } + ListElement { text: "Db"; harpkey: 61 } + ListElement { text: "D"; harpkey: 62 } + ListElement { text: "Eb"; harpkey: 63 } + ListElement { text: "E"; harpkey: 64 } + ListElement { text: "F"; harpkey: 65 } + ListElement { text: "F#"; harpkey: 66 } + ListElement { text: "High G"; harpkey: 67 } + } + + onCurrentIndexChanged: { + // Validate currentIndex + if (currentIndex >= 0 && currentIndex < keylist.count) { + // Retrieve the current item + var currentItem = keylist.get(currentIndex); + // Debugging output + console.debug("Selected item:", currentItem.text, ", Harpkey:", currentItem.harpkey); + // Set the key property + keylist.key = currentItem.harpkey; + } else { + console.error("Invalid currentIndex:", currentIndex); + } + } + + Component.onCompleted: { + // Set initial harpkey value + if (currentIndex >= 0 && currentIndex < keylist.count) { + var initialItem = keylist.get(currentIndex); + keylist.key = initialItem.harpkey; + console.debug("Initial item:", initialItem.text, ", Harpkey:", initialItem.harpkey); + } else { + console.error("Invalid initial currentIndex:", currentIndex); + } + } + } + } + + + Row { + id:typeRow + spacing: 20 + width: parent.width + x: 20 + + Label { + font.pointSize: 14 + font.family: "Inter" + //font.weight: Font.ExtraBold + font.bold: true + color: "#00C8D7" + anchors.verticalCenter: parent.verticalCenter + width: parent.width / 4 + horizontalAlignment: Text.AlignRight + text: "Harmonica type:" + } + + ComboBox { + id: typeBox + anchors.verticalCenter: parent.verticalCenter + textRole: "text" + implicitWidth: 300 + implicitHeight: 25 + + background: Rectangle { + color: dropdownBase + radius: 12 + clip: true + } + + contentItem: Text { + text: typeBox.displayText + color: dropdownText + font.pointSize: 12 + font.bold: true + verticalAlignment: Text.AlignVCenter + elide: Text.ElideRight + leftPadding: 15 + } + + currentIndex: 0 + model: ListModel { + id: harp + property var tuning: undefined // Initialize property with default value, updated list order for consistency through code + ListElement { text: "Blues Harp (Richter)"; tuning: 1 } + ListElement { text: "Richter valved"; tuning: 2 } + ListElement { text: "Country"; tuning: 3 } + ListElement { text: "Chromatic 10 Hole"; tuning: 4 } + ListElement { text: "Chromatic 12 Hole"; tuning: 5 } + ListElement { text: "Chromatic 16 Hole"; tuning: 6 } + ListElement { text: "Circular (Seydel), valved"; tuning: 7 } + ListElement { text: "TrueChromatic Diatonic, valved"; tuning: 8 } + ListElement { text: "Natural Minor"; tuning: 9 } + ListElement { text: "Melody Maker"; tuning: 10 } + ListElement { text: "Circular (Inversed for blow 1), valved "; tuning: 11 } + ListElement { text: "Paddy Richter (Brendan Power), valved"; tuning: 12 } + ListElement { text: "Power Bender (Brendan Power), valved"; tuning: 13 } + ListElement { text: "Power Draw (Brendan Power), valved"; tuning: 14 } + ListElement { text: "Tremolo 24-hole"; tuning: 15 } // (added tremolo 24-hole option) + } + + onCurrentIndexChanged: { + // Validate currentIndex + if (currentIndex >= 0 && currentIndex < harp.count) { + // Retrieve the current item + var currentItem = harp.get(currentIndex); + // Debugging output + console.debug("Selected item:", currentItem.text, ", Tuning:", currentItem.tuning); + // Set the tuning property + harp.tuning = currentItem.tuning; + } else { + console.error("Invalid currentIndex:", currentIndex); + } + } + + Component.onCompleted: { + // Set initial tuning value + if (currentIndex >= 0 && currentIndex < harp.count) { + var initialItem = harp.get(currentIndex); + harp.tuning = initialItem.tuning; + console.debug("Initial item:", initialItem.text, ", Tuning:", initialItem.tuning); + } else { + console.error("Invalid initial currentIndex:", currentIndex); + } + } + } + } + + Row { + id:positionRow + spacing: 20 + width: parent.width + x: 20 + + Label { + font.pointSize: 14 + font.family: "Inter" + //font.weight: Font.ExtraBold + font.bold: true + color: "#00C8D7" + anchors.verticalCenter: parent.verticalCenter + width: parent.width / 4 + horizontalAlignment: Text.AlignRight + text: "Position:" + } + + ComboBox { + id: positionBox + anchors.verticalCenter: parent.verticalCenter + textRole: "text" + implicitWidth: 125 + implicitHeight: 25 + + background: Rectangle { + color: dropdownBase + radius: 12 + clip: true + } + + contentItem: Text { + text: positionBox.displayText + color: dropdownText + font.pointSize: 12 + font.bold: true + verticalAlignment: Text.AlignVCenter + elide: Text.ElideRight + leftPadding: 15 + } + + currentIndex: 0 + model: ListModel { + id: placetext + property var position: undefined // Initialize property with default value + ListElement { text: "Above staff"; position: "above" } + ListElement { text: "Below staff"; position: "below" } + } + + onCurrentIndexChanged: { + // Validate currentIndex + if (currentIndex >= 0 && currentIndex < placetext.count) { + // Retrieve the current item + var currentItem = placetext.get(currentIndex); + // Debugging output + console.debug("Selected item:", currentItem.text, ", Position:", currentItem.position); + // Set the position property + placetext.position = currentItem.position; + } else { + console.error("Invalid currentIndex:", currentIndex); + } + } + + Component.onCompleted: { + // Set initial position value + if (currentIndex >= 0 && currentIndex < placetext.count) { + var initialItem = placetext.get(currentIndex); + placetext.position = initialItem.position; + console.debug("Initial item:", initialItem.text, ", Position:", initialItem.position); + } else { + console.error("Invalid initial currentIndex:", currentIndex); + } + } + } + } + + Row { + id:colorRow + spacing: 20 + width: parent.width + x: 20 + + Label { + font.pointSize: 14 + font.family: "Inter" + //font.weight: Font.ExtraBold + font.bold: true + color: "#00C8D7" + anchors.verticalCenter: parent.verticalCenter + width: parent.width / 4 + horizontalAlignment: Text.AlignRight + text: "Color:" + } + + ComboBox { + id: colorBox + anchors.verticalCenter: parent.verticalCenter + textRole: "text" + implicitWidth: 100 + implicitHeight: 25 + + background: Rectangle { + color: dropdownBase + radius: 12 + clip: true + } + + contentItem: Text { + text: colorBox.displayText + color: dropdownText + font.pointSize: 12 + font.bold: true + verticalAlignment: Text.AlignVCenter + elide: Text.ElideRight + leftPadding: 15 + } + + currentIndex: 2 + model: ListModel { + id: colorlist + property var textColor: undefined // Initialize property with default value (added color option) + ListElement { text: "Black"; colorkey: "black" } + ListElement { text: "Brown"; colorkey: "brown" } + ListElement { text: "Red"; colorkey: "red" } + ListElement { text: "Pink"; colorkey: "deeppink" } + ListElement { text: "Orange"; colorkey: "orange" } + ListElement { text: "Green"; colorkey: "green" } + ListElement { text: "Blue"; colorkey: "blue" } + ListElement { text: "Purple"; colorkey: "purple" } + } + + onCurrentIndexChanged: { + // Validate currentIndex + if (currentIndex >= 0 && currentIndex < colorlist.count) { + // Retrieve the current item + var currentItem = colorlist.get(currentIndex); + // Debugging output + console.debug("Selected item:", currentItem.text, ", Color:", currentItem.colorkey); + // Set the color property + colorlist.textColor = currentItem.colorkey; + } else { + console.error("Invalid currentIndex:", currentIndex); + } + } + + Component.onCompleted: { + // Set initial color value + if (currentIndex >= 0 && currentIndex < colorlist.count) { + var initialItem = colorlist.get(currentIndex); + colorlist.textColor = initialItem.colorkey; + console.debug("Initial item:", initialItem.text, ", Color:", initialItem.colorkey); + } else { + console.error("Invalid initial currentIndex:", currentIndex); + } + } + } + } + + Row { + id: sizeRow + spacing: 20 + width: parent.width + x: 20 + + Label { + font.pointSize: 14 + font.family: "Inter" + //font.weight: Font.ExtraBold + font.bold: true + color: "#00C8D7" + anchors.verticalCenter: parent.verticalCenter + width: parent.width / 4 + horizontalAlignment: Text.AlignRight + text: "Font Size:" + } + + ComboBox { + id: sizeBox + anchors.verticalCenter: parent.verticalCenter + textRole: "text" + implicitWidth: 50 + implicitHeight: 25 + + background: Rectangle { + color: dropdownBase + radius: 12 + clip: true + } + + contentItem: Text { + text: sizeBox.displayText + color: dropdownText + font.pointSize: 12 + font.bold: true + verticalAlignment: Text.AlignVCenter + elide: Text.ElideRight + leftPadding: 15 + } + + currentIndex: 1 + model: ListModel { + id: sizelist + property var textSize: undefined // Initialize property with default value (added font size option) + ListElement { text: "10"; sizekey: 10 } + ListElement { text: "12"; sizekey: 12 } + ListElement { text: "14"; sizekey: 14 } + ListElement { text: "16"; sizekey: 16 } + ListElement { text: "18"; sizekey: 18 } + } + + onCurrentIndexChanged: { + // Validate currentIndex + if (currentIndex >= 0 && currentIndex < sizelist.count) { + // Retrieve the current item + var currentItem = sizelist.get(currentIndex); + // Debugging output + console.debug("Selected item:", currentItem.text, ", Color:", currentItem.sizekey); + // Set the color property + sizelist.textSize = currentItem.sizekey; + } else { + console.error("Invalid currentIndex:", currentIndex); + } + } + + Component.onCompleted: { + // Set initial font size value + if (currentIndex >= 0 && currentIndex < sizelist.count) { + var initialItem = sizelist.get(currentIndex); + sizelist.textSize = initialItem.sizekey; + console.debug("Initial item:", initialItem.text, ", Color:", initialItem.sizekey); + } else { + console.error("Invalid initial currentIndex:", currentIndex); + } + } + } + } + } + } + + Row { + id: buttonRow + height: 40 + anchors.bottomMargin: 20 + anchors.bottom: parent.bottom + anchors.horizontalCenter: parent.horizontalCenter + spacing: 20 + + Button { + id: applyButton + width: 110 + height: 40 + text: "Apply" + background: Rectangle { + color: "#49A26E" + anchors.fill: parent + radius: 18 + } + + contentItem: Text { + text: applyButton.text + color: "#003D12" + font.pointSize: 18 + font.bold: true + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + + onClicked: { + apply() + quit() + } + } + + Button { + id: cancelButton + width: 110 + height: 40 + text: "Cancel" + background: Rectangle { + color: "#E56579" + anchors.fill: parent + radius: 18 + } + + contentItem: Text { + text: cancelButton.text + color: "#680000" + //color: "#315611" + font.pointSize: 18 + font.bold: true + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + + onClicked: { + quit() + } + } + } + + function tabNotes(notes, text) { // updated order for consistency throughout code + + var richter = ["+1", "-1b", "-1", "+1o", "+2", "-2bb", "-2b", "+3", "-3bbb", "-3bb", "-3b", "-3", + "+4", "-4b", "-4", "+4o", "+5", "-5", "+5o", "+6", "-6b", "-6", "+6o", "-7", + "+7", "-7o", "-8", "+8b", "+8", "-9", "+9b", "+9", "-9o", "-10", "+10bb", "+10b", + "+10", "-10o" ]; //Standard Richter tuning with overbends (updated -2 to +3 (same note) as it's used more often) + + var richterValved = ["+1", "-1b", "-1", "+2b", "+2", "-2bb", "-2b", "-2", "-3bbb", "-3bb", "-3b", "-3", + "+4", "-4b", "-4", "+5b", "+5", "-5", "+6b", "+6", "-6b", "-6", "-7b", "-7", + "+7", "-8b", "-8", "+8b", "+8", "-9", "+9b", "+9", "-10b", "-10", "+10bb", "+10b", + "+10" ]; + richterValved[-2] = "+1bb"; richterValved[-1] = "+1b"; //Two notes below the key at blow 1 + + var country = ["+1", "-1b", "-1", "+1o", "+2", "-2bb", "-2b", "-2", "-3bbb", "-3bb", "-3b", "-3", + "+4", "-4b", "-4", "+4o", "+5", "-5b", "-5", "+6", "-6b", "-6", "+6o", "-7", + "+7", "-7o", "-8", "+8b", "+8", "-9", "+9b", "+9", "-9o", "-10", "+10bb", "+10b", + "+10", "-10o" ]; + + var chromatic10hole = ["+1", '+1s', "-1", "-1s", "+2", "-2", "-2s", "+3", "+3s", "-3", "-3s","-4", + "+5", "+5s", "-5", "-5s", "+6", "-6", "-6s", "+7", "+7s", "-7", "-7s", "-8", + "+8", "+8s", "-9", "-9s", "+9", "-10", "-10s", "+10", "+10s" ]; + + var standardChromatic = ["+1", '+1s', "-1", "-1s", "+2", "-2", "-2s", "+3", "+3s", "-3", "-3s","-4", + "+5", "+5s", "-5", "-5s", "+6", "-6", "-6s", "+7", "+7s", "-7", "-7s", "-8", + "+8", "+8s", "-9", "-9s", "+10", "-10", "-10s", "+11", "+11s", "-11", "-11s", "-12", + "+12", "+12s", "-12", "-12s" ]; // updated +4 to +5 (same note) as it's used more often + + var chromatic16H = ["+1.", "+1.s", "-1.", "-1.s", "+2.", "-2.", "-2.s", "+3.", "+3.s", "-3.", "-3.s","-4.", + "+1", "+1<", "-1", "-1s", "+2", "-2", "-2s", "+3", "+3s", "-3", "-3s","-4", + "+4", "+5s", "-5", "-5s", "+6", "-6", "-6s", "+7", "+7s", "-7", "-7s", "-8", + "+8", "+8s", "-9", "-9s", "+10", "-10", "-10s", "+11", "+11s", "-11", "-11s", "-12", + "+12", "+12s", "-12", "-12s" ]; + + var zirkValved = ["+1", "-1b", "-1", "+2b", "+2", "-2", "+3b", "+3", "-3b", "-3", "+4", "-4b", + "-4", "+5b", "+5", "-5b", "-5", "+6", "-6b", "-6", "+7b", "+7", "-7", "+8b", + "+8", "-8b", "-8", "+9b", "+9", "-9", "10b", "+10", "-10b", "-10" ]; // Circular/Spiral tuned diatonic + // Key per Seydel "G"on blow 1, C major at draw 2, A minor at draw 1 + + var trueChrom = ["+1", "-1b", "-1", "+2", "-2b", "-2", "+3b", "+3", "-3b", "-3", "+4", "-4b", + "-4", "+5b", "+5", "-5b", "-5", "+6", "-6b", "-6", "+7b", "+7", "-7b", "-7", + "+8", "-8b", "-8", "+9b", "+9", "-9b", "-9", "+10", "-10b", "-10" ]; //True Chromatic diatonic, valves + //Another side of the spiral logic is expanded in the “True Chromatic” tuning, designed by Eugene Ivanov. + //All chords can be arranged in a continuous, looped progression on major and minor triads: + //C Eb G Bb D F A C E G B D Gb A Db E Ab B Eb Gb Bb Db F Ab C (and looped on C minor after that). + + var naturalMinor = ["+1", "-1b", "-1", "+2", "-2bbb", "-2bb", "-2b", "-2", "-3bb", "-3b", "-3", "+3o", + "+4", "-4b", "-4", "+5", "-5b", "-5", "+5o", "+6", "-6b", "-6", "-7", "+7b", + "+7", "-7o", "-8", "+8", "-8o", "-9", "+9b", "+9", "-9o", "-10", "+10bb", "+10b", + "+10", "-10o" ]; //Labeled by blow 1 like Hohner. Seydel and Lee Okar labels by draw 2 + + var melodyMaker = [ , , , , , // label by draw 2 + "+1", "-1b", "-1", "+1o","+2", "-2bb","-2b", "-2", "+2o", "+3", "-3b", "-3", + "+4", "-4b", "-4", "+4o", "+5", "-5b", "-5", "+6", "-6b", "-6", "+6o", "-7", + "+7", "-7o", "-8", "+8b", "+8", "-8o", "-9", "+9", "-9o", "-10", "+10bb", "+10b", + "+10", "-10o" ]; + + var spiral_b1 = ["+1", "-1b", "-1", "+2b", "+2", "-2", "+3b", "+3", "-3b", "-3", "+4b", "+4", + "-4", "+5b", "+5", "-5b", "-5", "+6", "-6b", "-6", "+7b", "+7b", "-7", "-7", + "+8", "-8b", "-8", "+9b", "+9", "-9", "+10b", "+10", "-10b", "-10" ]; // Circular/Spiral tuned diatonic + // Inversed for Blow 1. Key of C major scale starts at blow 1 + + var paddyRichter = ["+1", "-1b", "-1", "+2b", "+2", "-2bb", "-2b", "-2", "+3b", "+3", "-3b", "-3", + "+4", "-4b", "-4", "+5b", "+5", "-5", "+6b", "+6", "-6b", "-6", "-7b", "-7", + "+7", "-8b", "-8", "+8b", "+8", "-9", "+9b", "+9", "-10b", "-10", "+10bb", "+10b", + "+10" ]; + paddyRichter[-2] = "+1bb"; paddyRichter[-1] = "+1b"; //Two notes below the key at blow 1 + // Brendan Power's tuning, half valved + + var powerBender = ["+1", "-1b", "-1", "+2b", "+2", "-2bb", "-2b", "-2", "-3bbb", "-3bb", "-3b", "-3", + "+4", "-4b", "-4", "-5b", "-5", "+6", "-6b", "-6", "+7b", "+7", "-7b", "-7", + "+8", "-8b", "-8", "+9b", "+9", "-9bb", "-9b", "-9", "+10b", "+10", "-10bb", "-10b", + "-10" ]; + powerBender[-2] = "+1bb"; powerBender[-1] = "+1b"; //Two notes below the key at blow 1 + // Brendan Power's tuning, half valved + + var powerDraw = ["+1", "-1b", "-1", "+2b", "+2", "-2bb", "-2b", "-2", "-3bbb", "-3bb", "-3b", "-3", + "+4", "-4b", "-4", "+5b", "+5", "-5", "+6b", "+6", "-6b", "-6", "-7b", "-7", + "+8", "-8b", "-8", "+9b", "+9", "-9bb", "-9b", "-9", "+10b", "+10", "-10bb", "-10b", + "-10" ]; + powerDraw[-2] = "+1bb"; powerDraw[-1] = "+1b"; //Two notes below the key at blow 1 + // Brendan Power's tuning, half valved + + var tremolo24 = ["+3", "-2b", "-2", "+3o", "+5", "-4", "+7o", "+7", "-6b", "-6", "-8b", "-8", + "+9", "-10b", "-10", "+11o", "+11", "-12", "+13o", "+13", "-14b", "-14", "-16b", "-16", + "+15", "-16o", "-18", "+17b", "+17", "-20", "+19b", "+19", "-20o", "-22", "+24b", "+24", + "+21", "-22o" ]; //Standard Richter tuning with overbends (added tremolo24 notes) + + + var tuning = richter + switch (harp.tuning) { + case 1: tuning = richter; break; + case 2: tuning = richterValved; break; + case 3: tuning = country; break; + case 4: tuning = chromatic10hole; break; + case 5: tuning = standardChromatic; break; + case 6: tuning = chromatic16H; break; + case 7: tuning = zirkValved; break; + case 8: tuning = trueChrom; break; + case 9: tuning = naturalMinor; break; + case 10: tuning = melodyMaker; break; + case 11: tuning = spiral_b1; break; + case 12: tuning = paddyRichter; break; + case 13: tuning = powerBender; break; + case 14: tuning = powerDraw; break; + case 15: tuning = tremolo24; break; // (added tremolo24 option) + default: tuning = richter; break; + } + + var harpkey = keylist.key + console.log("harpkey set to " + keylist.key) + + for (var i = 0; i < notes.length; i++) { + + if ( i > 0 ) + text.text = sep + text.text; + + if (typeof notes[i].pitch === "undefined") // just in case + return + var tab = tuning[notes[i].pitch - harpkey]; + if (typeof tab === "undefined") + text.text = "X"; + else { + if (bendChar !== "b") + tab = tab.replace(/b/g, bendChar); + text.text = tab + text.text; + } + } + } + + function applyToSelection(func) { + if (typeof curScore === 'undefined') + quit(); + var cursor = curScore.newCursor(); + var startStaff; + var endStaff; + var endTick; + var fullScore = false; + var textposition = (placetext.position === "above" ? Placement.ABOVE : Placement.BELOW); + var textcolor = colorlist.textColor // added color option + var textsize = sizelist.textSize // added font size option + + cursor.rewind(1); + if (!cursor.segment) { // no selection + fullScore = true; + startStaff = 0; // start with 1st staff + endStaff = curScore.nstaves - 1; // and end with last + } else { + startStaff = cursor.staffIdx; + cursor.rewind(2); + if (cursor.tick == 0) { + // this happens when the selection includes + // the last measure of the score. + // rewind(2) goes behind the last segment (where + // there's none) and sets tick=0 + endTick = curScore.lastSegment.tick + 1; + } else { + endTick = cursor.tick; + } + endStaff = cursor.staffIdx; + } + console.log(startStaff + " - " + endStaff + " - " + endTick) + + for (var staff = startStaff; staff <= endStaff; staff++) { + for (var voice = 0; voice < 4; voice++) { + cursor.rewind(1); // beginning of selection + cursor.voice = voice; + cursor.staffIdx = staff; + + if (fullScore) // no selection + cursor.rewind(0); // beginning of score + + while (cursor.segment && (fullScore || cursor.tick < endTick)) { + if (cursor.element && cursor.element.type == Element.CHORD) { + var text = newElement(Element.LYRICS); + + var graceChords = cursor.element.graceNotes; + for (var i = 0; i < graceChords.length; i++) { + // iterate through all grace chords + var notes = graceChords[i].notes; + tabNotes(notes, text); + // TODO: deal with placement of grace note on the x axis + text.placement = textposition + text.color = textcolor // added color option + text.offset = Qt.point(-2 * (graceChords.length - i), 0) + text.fontSize = textsize // added font size option + cursor.add(text); + // new text for next element + text = newElement(Element.LYRICS); + } + + var notes = cursor.element.notes; + tabNotes(notes, text); + text.placement = textposition + text.fontSize = textsize // added font size option + text.color = textcolor // added color option + + cursor.add(text); + } // end if CHORD + cursor.next(); + } // end while segment + } // end for voice + } // end for staff + quit(); + } // end applyToSelection() + + function apply() { + curScore.startCmd() + applyToSelection(tabNotes) + curScore.endCmd() + } + + onRun: { + if (typeof curScore === 'undefined') + quit(); + } +} + + From 3151e09a478fdf4d7c093590c2dbc460fe1e246b Mon Sep 17 00:00:00 2001 From: JB91653 Date: Sun, 7 Dec 2025 20:30:47 -0800 Subject: [PATCH 2/7] Add files via upload --- harmonica_tabs.png | Bin 0 -> 74868 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 harmonica_tabs.png diff --git a/harmonica_tabs.png b/harmonica_tabs.png new file mode 100644 index 0000000000000000000000000000000000000000..efb91dff1b86dc022c24c3692895902f921f6c09 GIT binary patch literal 74868 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 Date: Sun, 7 Dec 2025 20:34:49 -0800 Subject: [PATCH 3/7] Update README.md --- README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5a9f7ad..3ef75b4 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -This is a plugin for MuseScore 3 to add harmonica tablature notation below the notes. It currently supports +This is a plugin for MuseScore 4.6 to add harmonica tablature notation with options for color of tabs, tab font size, and placement of tabs above or below the notes. It currently supports * the Hohner Highlander only, for both side, A highlander and D - [Reference](http://musescore.org/sites/musescore.org/files/Hohner%20Highlander%20scale.jpg) * Diatonic A - [Reference](http://harmopoint.com/harmonica-virtuel/) [and for overblows and overdraws](http://www.overblow.com/?menuid=26) @@ -7,8 +7,10 @@ This is a plugin for MuseScore 3 to add harmonica tablature notation below the n * Diatonic D - [Reference](http://musescore.org/sites/musescore.org/files/Lee%20Oskar%20Diatonic%20D.jpg) [and for overblows and overdraws](http://www.overblow.com/?menuid=26) * Diatonic F - [Reference](http://harmopoint.com/harmonica-virtuel/) [and for overblows and overdraws](http://www.overblow.com/?menuid=26) * Diatonic G - [Reference](http://musescore.org/sites/musescore.org/files/Lee%20Oskar%20%20Diatonic%20G.jpg) [and for overblows and overdraws](http://www.overblow.com/?menuid=26) + * Chromatic C, 10 holes - * Chromatic C, 12 holes - [Reference](http://musescore.org/sites/musescore.org/files/12%20Hole%20Chromatic%20slide%20Harmonica.txt) * Chromatic C, 16 holes - [Reference](https://coast2coastmusic.com/chromatic/tuning_charts.shtml) + * Tremolo C, 24 holes - If you want to have your harmonica added to the it, please contact me, or better, do a pull request. @@ -22,10 +24,11 @@ If you want to have your harmonica added to the it, please contact me, or better * "' = 3 halfsteps bend * +X? = overblow * -X? = overdraw -* "<" = slide +* "s" = slide Bends, over blow, over draw may be supported if it's the only way to play the note. If there are two holes for one note, a choice has been made (draw) ##More info See the official [project page](http://musescore.org/en/project/harmonicatablature) + From d6ff308d3bed5e35d1369f9fbc33e0194449524b Mon Sep 17 00:00:00 2001 From: JB91653 Date: Sun, 7 Dec 2025 20:41:36 -0800 Subject: [PATCH 4/7] Delete harmonica_tablature_MS46 --- harmonica_tablature_MS46 | 768 --------------------------------------- 1 file changed, 768 deletions(-) delete mode 100644 harmonica_tablature_MS46 diff --git a/harmonica_tablature_MS46 b/harmonica_tablature_MS46 deleted file mode 100644 index 85cf489..0000000 --- a/harmonica_tablature_MS46 +++ /dev/null @@ -1,768 +0,0 @@ -//============================================================================= -// MuseScore -// Music Composition & Notation -// -// Harmonica Tabs Plugin -// -// -// 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 -//============================================================================= - -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import MuseScore 3.0 - -MuseScore { - - version: "4.6" - pluginType: "dialog" - title: "Harmonica Tablature Setup" - thumbnailName: "harmonica_tabs.png" - -// ------ OPTIONS ------- - property string sep : "\n" // change to "," if you want tabs horizontally - property string bendChar : "'" // change to "b" if you want bend to be noted with b -// ------ OPTIONS ------- - - id: window - width: 550 - height: 300 - - property var itemTextX1 : 20 - property var itemTextY1 : 20 - property var itemTextY2 : 20 - property var xPositionInit : 0.0 - property var yPositionInit : 0.0 - property color dropdownBase: "lightsteelblue" - property color dropdownText: "#001B2E" - - Rectangle { - id: mainInput - anchors.fill: parent - color: "#000A57" - - function comboStyle() { - } - - Column { - id: column - anchors.left: parent.left - anchors.right: parent.right - anchors.top: parent.top - anchors.margins: 10 - spacing: 17 - //z: 5 - anchors.topMargin: 25 - height: parent.height - 70 - - Row { - id:keyRow - spacing: 20 - width: parent.width - x: 20 - - Label { - font.pointSize: 14 - font.family: "Inter" - //font.weight: Font.ExtraBold - font.bold: true - color: "#00C8D7" - anchors.verticalCenter: parent.verticalCenter - width: parent.width / 4 - horizontalAlignment: Text.AlignRight - text: "Harmonica key:" - } - - ComboBox { - id: keyBox - anchors.verticalCenter: parent.verticalCenter - textRole: "text" - implicitWidth: 50 - implicitHeight: 25 - - background: Rectangle { - color: dropdownBase - radius: 12 - clip: true - } - - contentItem: Text { - text: keyBox.displayText - color: dropdownText - font.pointSize: 12 - font.bold: true - verticalAlignment: Text.AlignVCenter - elide: Text.ElideRight - leftPadding: 15 - } - - currentIndex: 17 - model: ListModel { - id: keylist - property var key: undefined // Initialize property with default value (updated F# to 54) - ListElement { text: "Low G"; harpkey: 43 } - ListElement { text: "Low Ab"; harpkey: 44 } - ListElement { text: "Low A"; harpkey: 45 } - ListElement { text: "Low Bb"; harpkey: 46 } - ListElement { text: "Low B"; harpkey: 47 } - ListElement { text: "Low C"; harpkey: 48 } - ListElement { text: "Low C#"; harpkey: 49 } - ListElement { text: "Low D"; harpkey: 50 } - ListElement { text: "Low Eb"; harpkey: 51 } - ListElement { text: "Low E"; harpkey: 52 } - ListElement { text: "Low F"; harpkey: 53 } - ListElement { text: "Low F#"; harpkey: 54 } - ListElement { text: "G"; harpkey: 55 } - ListElement { text: "Ab"; harpkey: 56 } - ListElement { text: "A"; harpkey: 57 } - ListElement { text: "Bb"; harpkey: 58 } - ListElement { text: "B"; harpkey: 59 } - ListElement { text: "C"; harpkey: 60 } - ListElement { text: "Db"; harpkey: 61 } - ListElement { text: "D"; harpkey: 62 } - ListElement { text: "Eb"; harpkey: 63 } - ListElement { text: "E"; harpkey: 64 } - ListElement { text: "F"; harpkey: 65 } - ListElement { text: "F#"; harpkey: 66 } - ListElement { text: "High G"; harpkey: 67 } - } - - onCurrentIndexChanged: { - // Validate currentIndex - if (currentIndex >= 0 && currentIndex < keylist.count) { - // Retrieve the current item - var currentItem = keylist.get(currentIndex); - // Debugging output - console.debug("Selected item:", currentItem.text, ", Harpkey:", currentItem.harpkey); - // Set the key property - keylist.key = currentItem.harpkey; - } else { - console.error("Invalid currentIndex:", currentIndex); - } - } - - Component.onCompleted: { - // Set initial harpkey value - if (currentIndex >= 0 && currentIndex < keylist.count) { - var initialItem = keylist.get(currentIndex); - keylist.key = initialItem.harpkey; - console.debug("Initial item:", initialItem.text, ", Harpkey:", initialItem.harpkey); - } else { - console.error("Invalid initial currentIndex:", currentIndex); - } - } - } - } - - - Row { - id:typeRow - spacing: 20 - width: parent.width - x: 20 - - Label { - font.pointSize: 14 - font.family: "Inter" - //font.weight: Font.ExtraBold - font.bold: true - color: "#00C8D7" - anchors.verticalCenter: parent.verticalCenter - width: parent.width / 4 - horizontalAlignment: Text.AlignRight - text: "Harmonica type:" - } - - ComboBox { - id: typeBox - anchors.verticalCenter: parent.verticalCenter - textRole: "text" - implicitWidth: 300 - implicitHeight: 25 - - background: Rectangle { - color: dropdownBase - radius: 12 - clip: true - } - - contentItem: Text { - text: typeBox.displayText - color: dropdownText - font.pointSize: 12 - font.bold: true - verticalAlignment: Text.AlignVCenter - elide: Text.ElideRight - leftPadding: 15 - } - - currentIndex: 0 - model: ListModel { - id: harp - property var tuning: undefined // Initialize property with default value, updated list order for consistency through code - ListElement { text: "Blues Harp (Richter)"; tuning: 1 } - ListElement { text: "Richter valved"; tuning: 2 } - ListElement { text: "Country"; tuning: 3 } - ListElement { text: "Chromatic 10 Hole"; tuning: 4 } - ListElement { text: "Chromatic 12 Hole"; tuning: 5 } - ListElement { text: "Chromatic 16 Hole"; tuning: 6 } - ListElement { text: "Circular (Seydel), valved"; tuning: 7 } - ListElement { text: "TrueChromatic Diatonic, valved"; tuning: 8 } - ListElement { text: "Natural Minor"; tuning: 9 } - ListElement { text: "Melody Maker"; tuning: 10 } - ListElement { text: "Circular (Inversed for blow 1), valved "; tuning: 11 } - ListElement { text: "Paddy Richter (Brendan Power), valved"; tuning: 12 } - ListElement { text: "Power Bender (Brendan Power), valved"; tuning: 13 } - ListElement { text: "Power Draw (Brendan Power), valved"; tuning: 14 } - ListElement { text: "Tremolo 24-hole"; tuning: 15 } // (added tremolo 24-hole option) - } - - onCurrentIndexChanged: { - // Validate currentIndex - if (currentIndex >= 0 && currentIndex < harp.count) { - // Retrieve the current item - var currentItem = harp.get(currentIndex); - // Debugging output - console.debug("Selected item:", currentItem.text, ", Tuning:", currentItem.tuning); - // Set the tuning property - harp.tuning = currentItem.tuning; - } else { - console.error("Invalid currentIndex:", currentIndex); - } - } - - Component.onCompleted: { - // Set initial tuning value - if (currentIndex >= 0 && currentIndex < harp.count) { - var initialItem = harp.get(currentIndex); - harp.tuning = initialItem.tuning; - console.debug("Initial item:", initialItem.text, ", Tuning:", initialItem.tuning); - } else { - console.error("Invalid initial currentIndex:", currentIndex); - } - } - } - } - - Row { - id:positionRow - spacing: 20 - width: parent.width - x: 20 - - Label { - font.pointSize: 14 - font.family: "Inter" - //font.weight: Font.ExtraBold - font.bold: true - color: "#00C8D7" - anchors.verticalCenter: parent.verticalCenter - width: parent.width / 4 - horizontalAlignment: Text.AlignRight - text: "Position:" - } - - ComboBox { - id: positionBox - anchors.verticalCenter: parent.verticalCenter - textRole: "text" - implicitWidth: 125 - implicitHeight: 25 - - background: Rectangle { - color: dropdownBase - radius: 12 - clip: true - } - - contentItem: Text { - text: positionBox.displayText - color: dropdownText - font.pointSize: 12 - font.bold: true - verticalAlignment: Text.AlignVCenter - elide: Text.ElideRight - leftPadding: 15 - } - - currentIndex: 0 - model: ListModel { - id: placetext - property var position: undefined // Initialize property with default value - ListElement { text: "Above staff"; position: "above" } - ListElement { text: "Below staff"; position: "below" } - } - - onCurrentIndexChanged: { - // Validate currentIndex - if (currentIndex >= 0 && currentIndex < placetext.count) { - // Retrieve the current item - var currentItem = placetext.get(currentIndex); - // Debugging output - console.debug("Selected item:", currentItem.text, ", Position:", currentItem.position); - // Set the position property - placetext.position = currentItem.position; - } else { - console.error("Invalid currentIndex:", currentIndex); - } - } - - Component.onCompleted: { - // Set initial position value - if (currentIndex >= 0 && currentIndex < placetext.count) { - var initialItem = placetext.get(currentIndex); - placetext.position = initialItem.position; - console.debug("Initial item:", initialItem.text, ", Position:", initialItem.position); - } else { - console.error("Invalid initial currentIndex:", currentIndex); - } - } - } - } - - Row { - id:colorRow - spacing: 20 - width: parent.width - x: 20 - - Label { - font.pointSize: 14 - font.family: "Inter" - //font.weight: Font.ExtraBold - font.bold: true - color: "#00C8D7" - anchors.verticalCenter: parent.verticalCenter - width: parent.width / 4 - horizontalAlignment: Text.AlignRight - text: "Color:" - } - - ComboBox { - id: colorBox - anchors.verticalCenter: parent.verticalCenter - textRole: "text" - implicitWidth: 100 - implicitHeight: 25 - - background: Rectangle { - color: dropdownBase - radius: 12 - clip: true - } - - contentItem: Text { - text: colorBox.displayText - color: dropdownText - font.pointSize: 12 - font.bold: true - verticalAlignment: Text.AlignVCenter - elide: Text.ElideRight - leftPadding: 15 - } - - currentIndex: 2 - model: ListModel { - id: colorlist - property var textColor: undefined // Initialize property with default value (added color option) - ListElement { text: "Black"; colorkey: "black" } - ListElement { text: "Brown"; colorkey: "brown" } - ListElement { text: "Red"; colorkey: "red" } - ListElement { text: "Pink"; colorkey: "deeppink" } - ListElement { text: "Orange"; colorkey: "orange" } - ListElement { text: "Green"; colorkey: "green" } - ListElement { text: "Blue"; colorkey: "blue" } - ListElement { text: "Purple"; colorkey: "purple" } - } - - onCurrentIndexChanged: { - // Validate currentIndex - if (currentIndex >= 0 && currentIndex < colorlist.count) { - // Retrieve the current item - var currentItem = colorlist.get(currentIndex); - // Debugging output - console.debug("Selected item:", currentItem.text, ", Color:", currentItem.colorkey); - // Set the color property - colorlist.textColor = currentItem.colorkey; - } else { - console.error("Invalid currentIndex:", currentIndex); - } - } - - Component.onCompleted: { - // Set initial color value - if (currentIndex >= 0 && currentIndex < colorlist.count) { - var initialItem = colorlist.get(currentIndex); - colorlist.textColor = initialItem.colorkey; - console.debug("Initial item:", initialItem.text, ", Color:", initialItem.colorkey); - } else { - console.error("Invalid initial currentIndex:", currentIndex); - } - } - } - } - - Row { - id: sizeRow - spacing: 20 - width: parent.width - x: 20 - - Label { - font.pointSize: 14 - font.family: "Inter" - //font.weight: Font.ExtraBold - font.bold: true - color: "#00C8D7" - anchors.verticalCenter: parent.verticalCenter - width: parent.width / 4 - horizontalAlignment: Text.AlignRight - text: "Font Size:" - } - - ComboBox { - id: sizeBox - anchors.verticalCenter: parent.verticalCenter - textRole: "text" - implicitWidth: 50 - implicitHeight: 25 - - background: Rectangle { - color: dropdownBase - radius: 12 - clip: true - } - - contentItem: Text { - text: sizeBox.displayText - color: dropdownText - font.pointSize: 12 - font.bold: true - verticalAlignment: Text.AlignVCenter - elide: Text.ElideRight - leftPadding: 15 - } - - currentIndex: 1 - model: ListModel { - id: sizelist - property var textSize: undefined // Initialize property with default value (added font size option) - ListElement { text: "10"; sizekey: 10 } - ListElement { text: "12"; sizekey: 12 } - ListElement { text: "14"; sizekey: 14 } - ListElement { text: "16"; sizekey: 16 } - ListElement { text: "18"; sizekey: 18 } - } - - onCurrentIndexChanged: { - // Validate currentIndex - if (currentIndex >= 0 && currentIndex < sizelist.count) { - // Retrieve the current item - var currentItem = sizelist.get(currentIndex); - // Debugging output - console.debug("Selected item:", currentItem.text, ", Color:", currentItem.sizekey); - // Set the color property - sizelist.textSize = currentItem.sizekey; - } else { - console.error("Invalid currentIndex:", currentIndex); - } - } - - Component.onCompleted: { - // Set initial font size value - if (currentIndex >= 0 && currentIndex < sizelist.count) { - var initialItem = sizelist.get(currentIndex); - sizelist.textSize = initialItem.sizekey; - console.debug("Initial item:", initialItem.text, ", Color:", initialItem.sizekey); - } else { - console.error("Invalid initial currentIndex:", currentIndex); - } - } - } - } - } - } - - Row { - id: buttonRow - height: 40 - anchors.bottomMargin: 20 - anchors.bottom: parent.bottom - anchors.horizontalCenter: parent.horizontalCenter - spacing: 20 - - Button { - id: applyButton - width: 110 - height: 40 - text: "Apply" - background: Rectangle { - color: "#49A26E" - anchors.fill: parent - radius: 18 - } - - contentItem: Text { - text: applyButton.text - color: "#003D12" - font.pointSize: 18 - font.bold: true - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } - - onClicked: { - apply() - quit() - } - } - - Button { - id: cancelButton - width: 110 - height: 40 - text: "Cancel" - background: Rectangle { - color: "#E56579" - anchors.fill: parent - radius: 18 - } - - contentItem: Text { - text: cancelButton.text - color: "#680000" - //color: "#315611" - font.pointSize: 18 - font.bold: true - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } - - onClicked: { - quit() - } - } - } - - function tabNotes(notes, text) { // updated order for consistency throughout code - - var richter = ["+1", "-1b", "-1", "+1o", "+2", "-2bb", "-2b", "+3", "-3bbb", "-3bb", "-3b", "-3", - "+4", "-4b", "-4", "+4o", "+5", "-5", "+5o", "+6", "-6b", "-6", "+6o", "-7", - "+7", "-7o", "-8", "+8b", "+8", "-9", "+9b", "+9", "-9o", "-10", "+10bb", "+10b", - "+10", "-10o" ]; //Standard Richter tuning with overbends (updated -2 to +3 (same note) as it's used more often) - - var richterValved = ["+1", "-1b", "-1", "+2b", "+2", "-2bb", "-2b", "-2", "-3bbb", "-3bb", "-3b", "-3", - "+4", "-4b", "-4", "+5b", "+5", "-5", "+6b", "+6", "-6b", "-6", "-7b", "-7", - "+7", "-8b", "-8", "+8b", "+8", "-9", "+9b", "+9", "-10b", "-10", "+10bb", "+10b", - "+10" ]; - richterValved[-2] = "+1bb"; richterValved[-1] = "+1b"; //Two notes below the key at blow 1 - - var country = ["+1", "-1b", "-1", "+1o", "+2", "-2bb", "-2b", "-2", "-3bbb", "-3bb", "-3b", "-3", - "+4", "-4b", "-4", "+4o", "+5", "-5b", "-5", "+6", "-6b", "-6", "+6o", "-7", - "+7", "-7o", "-8", "+8b", "+8", "-9", "+9b", "+9", "-9o", "-10", "+10bb", "+10b", - "+10", "-10o" ]; - - var chromatic10hole = ["+1", '+1s', "-1", "-1s", "+2", "-2", "-2s", "+3", "+3s", "-3", "-3s","-4", - "+5", "+5s", "-5", "-5s", "+6", "-6", "-6s", "+7", "+7s", "-7", "-7s", "-8", - "+8", "+8s", "-9", "-9s", "+9", "-10", "-10s", "+10", "+10s" ]; - - var standardChromatic = ["+1", '+1s', "-1", "-1s", "+2", "-2", "-2s", "+3", "+3s", "-3", "-3s","-4", - "+5", "+5s", "-5", "-5s", "+6", "-6", "-6s", "+7", "+7s", "-7", "-7s", "-8", - "+8", "+8s", "-9", "-9s", "+10", "-10", "-10s", "+11", "+11s", "-11", "-11s", "-12", - "+12", "+12s", "-12", "-12s" ]; // updated +4 to +5 (same note) as it's used more often - - var chromatic16H = ["+1.", "+1.s", "-1.", "-1.s", "+2.", "-2.", "-2.s", "+3.", "+3.s", "-3.", "-3.s","-4.", - "+1", "+1<", "-1", "-1s", "+2", "-2", "-2s", "+3", "+3s", "-3", "-3s","-4", - "+4", "+5s", "-5", "-5s", "+6", "-6", "-6s", "+7", "+7s", "-7", "-7s", "-8", - "+8", "+8s", "-9", "-9s", "+10", "-10", "-10s", "+11", "+11s", "-11", "-11s", "-12", - "+12", "+12s", "-12", "-12s" ]; - - var zirkValved = ["+1", "-1b", "-1", "+2b", "+2", "-2", "+3b", "+3", "-3b", "-3", "+4", "-4b", - "-4", "+5b", "+5", "-5b", "-5", "+6", "-6b", "-6", "+7b", "+7", "-7", "+8b", - "+8", "-8b", "-8", "+9b", "+9", "-9", "10b", "+10", "-10b", "-10" ]; // Circular/Spiral tuned diatonic - // Key per Seydel "G"on blow 1, C major at draw 2, A minor at draw 1 - - var trueChrom = ["+1", "-1b", "-1", "+2", "-2b", "-2", "+3b", "+3", "-3b", "-3", "+4", "-4b", - "-4", "+5b", "+5", "-5b", "-5", "+6", "-6b", "-6", "+7b", "+7", "-7b", "-7", - "+8", "-8b", "-8", "+9b", "+9", "-9b", "-9", "+10", "-10b", "-10" ]; //True Chromatic diatonic, valves - //Another side of the spiral logic is expanded in the “True Chromatic” tuning, designed by Eugene Ivanov. - //All chords can be arranged in a continuous, looped progression on major and minor triads: - //C Eb G Bb D F A C E G B D Gb A Db E Ab B Eb Gb Bb Db F Ab C (and looped on C minor after that). - - var naturalMinor = ["+1", "-1b", "-1", "+2", "-2bbb", "-2bb", "-2b", "-2", "-3bb", "-3b", "-3", "+3o", - "+4", "-4b", "-4", "+5", "-5b", "-5", "+5o", "+6", "-6b", "-6", "-7", "+7b", - "+7", "-7o", "-8", "+8", "-8o", "-9", "+9b", "+9", "-9o", "-10", "+10bb", "+10b", - "+10", "-10o" ]; //Labeled by blow 1 like Hohner. Seydel and Lee Okar labels by draw 2 - - var melodyMaker = [ , , , , , // label by draw 2 - "+1", "-1b", "-1", "+1o","+2", "-2bb","-2b", "-2", "+2o", "+3", "-3b", "-3", - "+4", "-4b", "-4", "+4o", "+5", "-5b", "-5", "+6", "-6b", "-6", "+6o", "-7", - "+7", "-7o", "-8", "+8b", "+8", "-8o", "-9", "+9", "-9o", "-10", "+10bb", "+10b", - "+10", "-10o" ]; - - var spiral_b1 = ["+1", "-1b", "-1", "+2b", "+2", "-2", "+3b", "+3", "-3b", "-3", "+4b", "+4", - "-4", "+5b", "+5", "-5b", "-5", "+6", "-6b", "-6", "+7b", "+7b", "-7", "-7", - "+8", "-8b", "-8", "+9b", "+9", "-9", "+10b", "+10", "-10b", "-10" ]; // Circular/Spiral tuned diatonic - // Inversed for Blow 1. Key of C major scale starts at blow 1 - - var paddyRichter = ["+1", "-1b", "-1", "+2b", "+2", "-2bb", "-2b", "-2", "+3b", "+3", "-3b", "-3", - "+4", "-4b", "-4", "+5b", "+5", "-5", "+6b", "+6", "-6b", "-6", "-7b", "-7", - "+7", "-8b", "-8", "+8b", "+8", "-9", "+9b", "+9", "-10b", "-10", "+10bb", "+10b", - "+10" ]; - paddyRichter[-2] = "+1bb"; paddyRichter[-1] = "+1b"; //Two notes below the key at blow 1 - // Brendan Power's tuning, half valved - - var powerBender = ["+1", "-1b", "-1", "+2b", "+2", "-2bb", "-2b", "-2", "-3bbb", "-3bb", "-3b", "-3", - "+4", "-4b", "-4", "-5b", "-5", "+6", "-6b", "-6", "+7b", "+7", "-7b", "-7", - "+8", "-8b", "-8", "+9b", "+9", "-9bb", "-9b", "-9", "+10b", "+10", "-10bb", "-10b", - "-10" ]; - powerBender[-2] = "+1bb"; powerBender[-1] = "+1b"; //Two notes below the key at blow 1 - // Brendan Power's tuning, half valved - - var powerDraw = ["+1", "-1b", "-1", "+2b", "+2", "-2bb", "-2b", "-2", "-3bbb", "-3bb", "-3b", "-3", - "+4", "-4b", "-4", "+5b", "+5", "-5", "+6b", "+6", "-6b", "-6", "-7b", "-7", - "+8", "-8b", "-8", "+9b", "+9", "-9bb", "-9b", "-9", "+10b", "+10", "-10bb", "-10b", - "-10" ]; - powerDraw[-2] = "+1bb"; powerDraw[-1] = "+1b"; //Two notes below the key at blow 1 - // Brendan Power's tuning, half valved - - var tremolo24 = ["+3", "-2b", "-2", "+3o", "+5", "-4", "+7o", "+7", "-6b", "-6", "-8b", "-8", - "+9", "-10b", "-10", "+11o", "+11", "-12", "+13o", "+13", "-14b", "-14", "-16b", "-16", - "+15", "-16o", "-18", "+17b", "+17", "-20", "+19b", "+19", "-20o", "-22", "+24b", "+24", - "+21", "-22o" ]; //Standard Richter tuning with overbends (added tremolo24 notes) - - - var tuning = richter - switch (harp.tuning) { - case 1: tuning = richter; break; - case 2: tuning = richterValved; break; - case 3: tuning = country; break; - case 4: tuning = chromatic10hole; break; - case 5: tuning = standardChromatic; break; - case 6: tuning = chromatic16H; break; - case 7: tuning = zirkValved; break; - case 8: tuning = trueChrom; break; - case 9: tuning = naturalMinor; break; - case 10: tuning = melodyMaker; break; - case 11: tuning = spiral_b1; break; - case 12: tuning = paddyRichter; break; - case 13: tuning = powerBender; break; - case 14: tuning = powerDraw; break; - case 15: tuning = tremolo24; break; // (added tremolo24 option) - default: tuning = richter; break; - } - - var harpkey = keylist.key - console.log("harpkey set to " + keylist.key) - - for (var i = 0; i < notes.length; i++) { - - if ( i > 0 ) - text.text = sep + text.text; - - if (typeof notes[i].pitch === "undefined") // just in case - return - var tab = tuning[notes[i].pitch - harpkey]; - if (typeof tab === "undefined") - text.text = "X"; - else { - if (bendChar !== "b") - tab = tab.replace(/b/g, bendChar); - text.text = tab + text.text; - } - } - } - - function applyToSelection(func) { - if (typeof curScore === 'undefined') - quit(); - var cursor = curScore.newCursor(); - var startStaff; - var endStaff; - var endTick; - var fullScore = false; - var textposition = (placetext.position === "above" ? Placement.ABOVE : Placement.BELOW); - var textcolor = colorlist.textColor // added color option - var textsize = sizelist.textSize // added font size option - - cursor.rewind(1); - if (!cursor.segment) { // no selection - fullScore = true; - startStaff = 0; // start with 1st staff - endStaff = curScore.nstaves - 1; // and end with last - } else { - startStaff = cursor.staffIdx; - cursor.rewind(2); - if (cursor.tick == 0) { - // this happens when the selection includes - // the last measure of the score. - // rewind(2) goes behind the last segment (where - // there's none) and sets tick=0 - endTick = curScore.lastSegment.tick + 1; - } else { - endTick = cursor.tick; - } - endStaff = cursor.staffIdx; - } - console.log(startStaff + " - " + endStaff + " - " + endTick) - - for (var staff = startStaff; staff <= endStaff; staff++) { - for (var voice = 0; voice < 4; voice++) { - cursor.rewind(1); // beginning of selection - cursor.voice = voice; - cursor.staffIdx = staff; - - if (fullScore) // no selection - cursor.rewind(0); // beginning of score - - while (cursor.segment && (fullScore || cursor.tick < endTick)) { - if (cursor.element && cursor.element.type == Element.CHORD) { - var text = newElement(Element.LYRICS); - - var graceChords = cursor.element.graceNotes; - for (var i = 0; i < graceChords.length; i++) { - // iterate through all grace chords - var notes = graceChords[i].notes; - tabNotes(notes, text); - // TODO: deal with placement of grace note on the x axis - text.placement = textposition - text.color = textcolor // added color option - text.offset = Qt.point(-2 * (graceChords.length - i), 0) - text.fontSize = textsize // added font size option - cursor.add(text); - // new text for next element - text = newElement(Element.LYRICS); - } - - var notes = cursor.element.notes; - tabNotes(notes, text); - text.placement = textposition - text.fontSize = textsize // added font size option - text.color = textcolor // added color option - - cursor.add(text); - } // end if CHORD - cursor.next(); - } // end while segment - } // end for voice - } // end for staff - quit(); - } // end applyToSelection() - - function apply() { - curScore.startCmd() - applyToSelection(tabNotes) - curScore.endCmd() - } - - onRun: { - if (typeof curScore === 'undefined') - quit(); - } -} - - From 774f2b68860516bd681e2e307b60248c9c096c7b Mon Sep 17 00:00:00 2001 From: JB91653 Date: Sun, 7 Dec 2025 20:42:41 -0800 Subject: [PATCH 5/7] Add files via upload --- harmonica_tablature_color_MS46.qml | 769 +++++++++++++++++++++++++++++ 1 file changed, 769 insertions(+) create mode 100644 harmonica_tablature_color_MS46.qml diff --git a/harmonica_tablature_color_MS46.qml b/harmonica_tablature_color_MS46.qml new file mode 100644 index 0000000..bba5d82 --- /dev/null +++ b/harmonica_tablature_color_MS46.qml @@ -0,0 +1,769 @@ +//============================================================================= +// MuseScore +// Music Composition & Notation +// +// Harmonica Tabs Plugin +// +// +// 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 +//============================================================================= + +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import MuseScore 3.0 + +MuseScore { + + version: "4.6" + description: "Harmonica Tab plugin, cleaned up code, stylized selection box, added text color and size options, added tremolo 24-hole harmonica, added chromatic 10-hole harmonica, updated default selections. Updated by Joshua Buys" + pluginType: "dialog" + title: "Harmonica Tablature Setup" + thumbnailName: "harmonica_tabs.png" + +// ------ OPTIONS ------- + property string sep : "\n" // change to "," if you want tabs horizontally + property string bendChar : "'" // change to "b" if you want bend to be noted with b +// ------ OPTIONS ------- + + id: window + width: 550 + height: 300 + + property var itemTextX1 : 20 + property var itemTextY1 : 20 + property var itemTextY2 : 20 + property var xPositionInit : 0.0 + property var yPositionInit : 0.0 + property color dropdownBase: "lightsteelblue" + property color dropdownText: "#001B2E" + + Rectangle { + id: mainInput + anchors.fill: parent + color: "#000A57" + + function comboStyle() { + } + + Column { + id: column + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + anchors.margins: 10 + spacing: 17 + //z: 5 + anchors.topMargin: 25 + height: parent.height - 70 + + Row { + id:keyRow + spacing: 20 + width: parent.width + x: 20 + + Label { + font.pointSize: 14 + font.family: "Inter" + //font.weight: Font.ExtraBold + font.bold: true + color: "#00C8D7" + anchors.verticalCenter: parent.verticalCenter + width: parent.width / 4 + horizontalAlignment: Text.AlignRight + text: "Harmonica key:" + } + + ComboBox { + id: keyBox + anchors.verticalCenter: parent.verticalCenter + textRole: "text" + implicitWidth: 50 + implicitHeight: 25 + + background: Rectangle { + color: dropdownBase + radius: 12 + clip: true + } + + contentItem: Text { + text: keyBox.displayText + color: dropdownText + font.pointSize: 12 + font.bold: true + verticalAlignment: Text.AlignVCenter + elide: Text.ElideRight + leftPadding: 15 + } + + currentIndex: 17 + model: ListModel { + id: keylist + property var key: undefined // Initialize property with default value (updated F# to 54) + ListElement { text: "Low G"; harpkey: 43 } + ListElement { text: "Low Ab"; harpkey: 44 } + ListElement { text: "Low A"; harpkey: 45 } + ListElement { text: "Low Bb"; harpkey: 46 } + ListElement { text: "Low B"; harpkey: 47 } + ListElement { text: "Low C"; harpkey: 48 } + ListElement { text: "Low C#"; harpkey: 49 } + ListElement { text: "Low D"; harpkey: 50 } + ListElement { text: "Low Eb"; harpkey: 51 } + ListElement { text: "Low E"; harpkey: 52 } + ListElement { text: "Low F"; harpkey: 53 } + ListElement { text: "Low F#"; harpkey: 54 } + ListElement { text: "G"; harpkey: 55 } + ListElement { text: "Ab"; harpkey: 56 } + ListElement { text: "A"; harpkey: 57 } + ListElement { text: "Bb"; harpkey: 58 } + ListElement { text: "B"; harpkey: 59 } + ListElement { text: "C"; harpkey: 60 } + ListElement { text: "Db"; harpkey: 61 } + ListElement { text: "D"; harpkey: 62 } + ListElement { text: "Eb"; harpkey: 63 } + ListElement { text: "E"; harpkey: 64 } + ListElement { text: "F"; harpkey: 65 } + ListElement { text: "F#"; harpkey: 66 } + ListElement { text: "High G"; harpkey: 67 } + } + + onCurrentIndexChanged: { + // Validate currentIndex + if (currentIndex >= 0 && currentIndex < keylist.count) { + // Retrieve the current item + var currentItem = keylist.get(currentIndex); + // Debugging output + console.debug("Selected item:", currentItem.text, ", Harpkey:", currentItem.harpkey); + // Set the key property + keylist.key = currentItem.harpkey; + } else { + console.error("Invalid currentIndex:", currentIndex); + } + } + + Component.onCompleted: { + // Set initial harpkey value + if (currentIndex >= 0 && currentIndex < keylist.count) { + var initialItem = keylist.get(currentIndex); + keylist.key = initialItem.harpkey; + console.debug("Initial item:", initialItem.text, ", Harpkey:", initialItem.harpkey); + } else { + console.error("Invalid initial currentIndex:", currentIndex); + } + } + } + } + + + Row { + id:typeRow + spacing: 20 + width: parent.width + x: 20 + + Label { + font.pointSize: 14 + font.family: "Inter" + //font.weight: Font.ExtraBold + font.bold: true + color: "#00C8D7" + anchors.verticalCenter: parent.verticalCenter + width: parent.width / 4 + horizontalAlignment: Text.AlignRight + text: "Harmonica type:" + } + + ComboBox { + id: typeBox + anchors.verticalCenter: parent.verticalCenter + textRole: "text" + implicitWidth: 300 + implicitHeight: 25 + + background: Rectangle { + color: dropdownBase + radius: 12 + clip: true + } + + contentItem: Text { + text: typeBox.displayText + color: dropdownText + font.pointSize: 12 + font.bold: true + verticalAlignment: Text.AlignVCenter + elide: Text.ElideRight + leftPadding: 15 + } + + currentIndex: 0 + model: ListModel { + id: harp + property var tuning: undefined // Initialize property with default value, updated list order for consistency through code + ListElement { text: "Blues Harp (Richter)"; tuning: 1 } + ListElement { text: "Richter valved"; tuning: 2 } + ListElement { text: "Country"; tuning: 3 } + ListElement { text: "Chromatic 10 Hole"; tuning: 4 } + ListElement { text: "Chromatic 12 Hole"; tuning: 5 } + ListElement { text: "Chromatic 16 Hole"; tuning: 6 } + ListElement { text: "Circular (Seydel), valved"; tuning: 7 } + ListElement { text: "TrueChromatic Diatonic, valved"; tuning: 8 } + ListElement { text: "Natural Minor"; tuning: 9 } + ListElement { text: "Melody Maker"; tuning: 10 } + ListElement { text: "Circular (Inversed for blow 1), valved "; tuning: 11 } + ListElement { text: "Paddy Richter (Brendan Power), valved"; tuning: 12 } + ListElement { text: "Power Bender (Brendan Power), valved"; tuning: 13 } + ListElement { text: "Power Draw (Brendan Power), valved"; tuning: 14 } + ListElement { text: "Tremolo 24-hole"; tuning: 15 } // (added tremolo 24-hole option) + } + + onCurrentIndexChanged: { + // Validate currentIndex + if (currentIndex >= 0 && currentIndex < harp.count) { + // Retrieve the current item + var currentItem = harp.get(currentIndex); + // Debugging output + console.debug("Selected item:", currentItem.text, ", Tuning:", currentItem.tuning); + // Set the tuning property + harp.tuning = currentItem.tuning; + } else { + console.error("Invalid currentIndex:", currentIndex); + } + } + + Component.onCompleted: { + // Set initial tuning value + if (currentIndex >= 0 && currentIndex < harp.count) { + var initialItem = harp.get(currentIndex); + harp.tuning = initialItem.tuning; + console.debug("Initial item:", initialItem.text, ", Tuning:", initialItem.tuning); + } else { + console.error("Invalid initial currentIndex:", currentIndex); + } + } + } + } + + Row { + id:positionRow + spacing: 20 + width: parent.width + x: 20 + + Label { + font.pointSize: 14 + font.family: "Inter" + //font.weight: Font.ExtraBold + font.bold: true + color: "#00C8D7" + anchors.verticalCenter: parent.verticalCenter + width: parent.width / 4 + horizontalAlignment: Text.AlignRight + text: "Position:" + } + + ComboBox { + id: positionBox + anchors.verticalCenter: parent.verticalCenter + textRole: "text" + implicitWidth: 125 + implicitHeight: 25 + + background: Rectangle { + color: dropdownBase + radius: 12 + clip: true + } + + contentItem: Text { + text: positionBox.displayText + color: dropdownText + font.pointSize: 12 + font.bold: true + verticalAlignment: Text.AlignVCenter + elide: Text.ElideRight + leftPadding: 15 + } + + currentIndex: 0 + model: ListModel { + id: placetext + property var position: undefined // Initialize property with default value + ListElement { text: "Above staff"; position: "above" } + ListElement { text: "Below staff"; position: "below" } + } + + onCurrentIndexChanged: { + // Validate currentIndex + if (currentIndex >= 0 && currentIndex < placetext.count) { + // Retrieve the current item + var currentItem = placetext.get(currentIndex); + // Debugging output + console.debug("Selected item:", currentItem.text, ", Position:", currentItem.position); + // Set the position property + placetext.position = currentItem.position; + } else { + console.error("Invalid currentIndex:", currentIndex); + } + } + + Component.onCompleted: { + // Set initial position value + if (currentIndex >= 0 && currentIndex < placetext.count) { + var initialItem = placetext.get(currentIndex); + placetext.position = initialItem.position; + console.debug("Initial item:", initialItem.text, ", Position:", initialItem.position); + } else { + console.error("Invalid initial currentIndex:", currentIndex); + } + } + } + } + + Row { + id:colorRow + spacing: 20 + width: parent.width + x: 20 + + Label { + font.pointSize: 14 + font.family: "Inter" + //font.weight: Font.ExtraBold + font.bold: true + color: "#00C8D7" + anchors.verticalCenter: parent.verticalCenter + width: parent.width / 4 + horizontalAlignment: Text.AlignRight + text: "Color:" + } + + ComboBox { + id: colorBox + anchors.verticalCenter: parent.verticalCenter + textRole: "text" + implicitWidth: 100 + implicitHeight: 25 + + background: Rectangle { + color: dropdownBase + radius: 12 + clip: true + } + + contentItem: Text { + text: colorBox.displayText + color: dropdownText + font.pointSize: 12 + font.bold: true + verticalAlignment: Text.AlignVCenter + elide: Text.ElideRight + leftPadding: 15 + } + + currentIndex: 2 + model: ListModel { + id: colorlist + property var textColor: undefined // Initialize property with default value (added color option) + ListElement { text: "Black"; colorkey: "black" } + ListElement { text: "Brown"; colorkey: "brown" } + ListElement { text: "Red"; colorkey: "red" } + ListElement { text: "Pink"; colorkey: "deeppink" } + ListElement { text: "Orange"; colorkey: "orange" } + ListElement { text: "Green"; colorkey: "green" } + ListElement { text: "Blue"; colorkey: "blue" } + ListElement { text: "Purple"; colorkey: "purple" } + } + + onCurrentIndexChanged: { + // Validate currentIndex + if (currentIndex >= 0 && currentIndex < colorlist.count) { + // Retrieve the current item + var currentItem = colorlist.get(currentIndex); + // Debugging output + console.debug("Selected item:", currentItem.text, ", Color:", currentItem.colorkey); + // Set the color property + colorlist.textColor = currentItem.colorkey; + } else { + console.error("Invalid currentIndex:", currentIndex); + } + } + + Component.onCompleted: { + // Set initial color value + if (currentIndex >= 0 && currentIndex < colorlist.count) { + var initialItem = colorlist.get(currentIndex); + colorlist.textColor = initialItem.colorkey; + console.debug("Initial item:", initialItem.text, ", Color:", initialItem.colorkey); + } else { + console.error("Invalid initial currentIndex:", currentIndex); + } + } + } + } + + Row { + id: sizeRow + spacing: 20 + width: parent.width + x: 20 + + Label { + font.pointSize: 14 + font.family: "Inter" + //font.weight: Font.ExtraBold + font.bold: true + color: "#00C8D7" + anchors.verticalCenter: parent.verticalCenter + width: parent.width / 4 + horizontalAlignment: Text.AlignRight + text: "Font Size:" + } + + ComboBox { + id: sizeBox + anchors.verticalCenter: parent.verticalCenter + textRole: "text" + implicitWidth: 50 + implicitHeight: 25 + + background: Rectangle { + color: dropdownBase + radius: 12 + clip: true + } + + contentItem: Text { + text: sizeBox.displayText + color: dropdownText + font.pointSize: 12 + font.bold: true + verticalAlignment: Text.AlignVCenter + elide: Text.ElideRight + leftPadding: 15 + } + + currentIndex: 1 + model: ListModel { + id: sizelist + property var textSize: undefined // Initialize property with default value (added font size option) + ListElement { text: "10"; sizekey: 10 } + ListElement { text: "12"; sizekey: 12 } + ListElement { text: "14"; sizekey: 14 } + ListElement { text: "16"; sizekey: 16 } + ListElement { text: "18"; sizekey: 18 } + } + + onCurrentIndexChanged: { + // Validate currentIndex + if (currentIndex >= 0 && currentIndex < sizelist.count) { + // Retrieve the current item + var currentItem = sizelist.get(currentIndex); + // Debugging output + console.debug("Selected item:", currentItem.text, ", Color:", currentItem.sizekey); + // Set the color property + sizelist.textSize = currentItem.sizekey; + } else { + console.error("Invalid currentIndex:", currentIndex); + } + } + + Component.onCompleted: { + // Set initial font size value + if (currentIndex >= 0 && currentIndex < sizelist.count) { + var initialItem = sizelist.get(currentIndex); + sizelist.textSize = initialItem.sizekey; + console.debug("Initial item:", initialItem.text, ", Color:", initialItem.sizekey); + } else { + console.error("Invalid initial currentIndex:", currentIndex); + } + } + } + } + } + } + + Row { + id: buttonRow + height: 40 + anchors.bottomMargin: 20 + anchors.bottom: parent.bottom + anchors.horizontalCenter: parent.horizontalCenter + spacing: 20 + + Button { + id: applyButton + width: 110 + height: 40 + text: "Apply" + background: Rectangle { + color: "#49A26E" + anchors.fill: parent + radius: 18 + } + + contentItem: Text { + text: applyButton.text + color: "#003D12" + font.pointSize: 18 + font.bold: true + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + + onClicked: { + apply() + quit() + } + } + + Button { + id: cancelButton + width: 110 + height: 40 + text: "Cancel" + background: Rectangle { + color: "#E56579" + anchors.fill: parent + radius: 18 + } + + contentItem: Text { + text: cancelButton.text + color: "#680000" + //color: "#315611" + font.pointSize: 18 + font.bold: true + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + + onClicked: { + quit() + } + } + } + + function tabNotes(notes, text) { // updated order for consistency throughout code + + var richter = ["+1", "-1b", "-1", "+1o", "+2", "-2bb", "-2b", "+3", "-3bbb", "-3bb", "-3b", "-3", + "+4", "-4b", "-4", "+4o", "+5", "-5", "+5o", "+6", "-6b", "-6", "+6o", "-7", + "+7", "-7o", "-8", "+8b", "+8", "-9", "+9b", "+9", "-9o", "-10", "+10bb", "+10b", + "+10", "-10o" ]; //Standard Richter tuning with overbends (updated -2 to +3 (same note) as it's used more often) + + var richterValved = ["+1", "-1b", "-1", "+2b", "+2", "-2bb", "-2b", "-2", "-3bbb", "-3bb", "-3b", "-3", + "+4", "-4b", "-4", "+5b", "+5", "-5", "+6b", "+6", "-6b", "-6", "-7b", "-7", + "+7", "-8b", "-8", "+8b", "+8", "-9", "+9b", "+9", "-10b", "-10", "+10bb", "+10b", + "+10" ]; + richterValved[-2] = "+1bb"; richterValved[-1] = "+1b"; //Two notes below the key at blow 1 + + var country = ["+1", "-1b", "-1", "+1o", "+2", "-2bb", "-2b", "-2", "-3bbb", "-3bb", "-3b", "-3", + "+4", "-4b", "-4", "+4o", "+5", "-5b", "-5", "+6", "-6b", "-6", "+6o", "-7", + "+7", "-7o", "-8", "+8b", "+8", "-9", "+9b", "+9", "-9o", "-10", "+10bb", "+10b", + "+10", "-10o" ]; + + var chromatic10hole = ["+1", '+1s', "-1", "-1s", "+2", "-2", "-2s", "+3", "+3s", "-3", "-3s","-4", + "+5", "+5s", "-5", "-5s", "+6", "-6", "-6s", "+7", "+7s", "-7", "-7s", "-8", + "+8", "+8s", "-9", "-9s", "+9", "-10", "-10s", "+10", "+10s" ]; + + var standardChromatic = ["+1", '+1s', "-1", "-1s", "+2", "-2", "-2s", "+3", "+3s", "-3", "-3s","-4", + "+5", "+5s", "-5", "-5s", "+6", "-6", "-6s", "+7", "+7s", "-7", "-7s", "-8", + "+8", "+8s", "-9", "-9s", "+10", "-10", "-10s", "+11", "+11s", "-11", "-11s", "-12", + "+12", "+12s", "-12", "-12s" ]; // updated +4 to +5 (same note) as it's used more often + + var chromatic16H = ["+1.", "+1.s", "-1.", "-1.s", "+2.", "-2.", "-2.s", "+3.", "+3.s", "-3.", "-3.s","-4.", + "+1", "+1<", "-1", "-1s", "+2", "-2", "-2s", "+3", "+3s", "-3", "-3s","-4", + "+4", "+5s", "-5", "-5s", "+6", "-6", "-6s", "+7", "+7s", "-7", "-7s", "-8", + "+8", "+8s", "-9", "-9s", "+10", "-10", "-10s", "+11", "+11s", "-11", "-11s", "-12", + "+12", "+12s", "-12", "-12s" ]; + + var zirkValved = ["+1", "-1b", "-1", "+2b", "+2", "-2", "+3b", "+3", "-3b", "-3", "+4", "-4b", + "-4", "+5b", "+5", "-5b", "-5", "+6", "-6b", "-6", "+7b", "+7", "-7", "+8b", + "+8", "-8b", "-8", "+9b", "+9", "-9", "10b", "+10", "-10b", "-10" ]; // Circular/Spiral tuned diatonic + // Key per Seydel "G"on blow 1, C major at draw 2, A minor at draw 1 + + var trueChrom = ["+1", "-1b", "-1", "+2", "-2b", "-2", "+3b", "+3", "-3b", "-3", "+4", "-4b", + "-4", "+5b", "+5", "-5b", "-5", "+6", "-6b", "-6", "+7b", "+7", "-7b", "-7", + "+8", "-8b", "-8", "+9b", "+9", "-9b", "-9", "+10", "-10b", "-10" ]; //True Chromatic diatonic, valves + //Another side of the spiral logic is expanded in the “True Chromatic” tuning, designed by Eugene Ivanov. + //All chords can be arranged in a continuous, looped progression on major and minor triads: + //C Eb G Bb D F A C E G B D Gb A Db E Ab B Eb Gb Bb Db F Ab C (and looped on C minor after that). + + var naturalMinor = ["+1", "-1b", "-1", "+2", "-2bbb", "-2bb", "-2b", "-2", "-3bb", "-3b", "-3", "+3o", + "+4", "-4b", "-4", "+5", "-5b", "-5", "+5o", "+6", "-6b", "-6", "-7", "+7b", + "+7", "-7o", "-8", "+8", "-8o", "-9", "+9b", "+9", "-9o", "-10", "+10bb", "+10b", + "+10", "-10o" ]; //Labeled by blow 1 like Hohner. Seydel and Lee Okar labels by draw 2 + + var melodyMaker = [ , , , , , // label by draw 2 + "+1", "-1b", "-1", "+1o","+2", "-2bb","-2b", "-2", "+2o", "+3", "-3b", "-3", + "+4", "-4b", "-4", "+4o", "+5", "-5b", "-5", "+6", "-6b", "-6", "+6o", "-7", + "+7", "-7o", "-8", "+8b", "+8", "-8o", "-9", "+9", "-9o", "-10", "+10bb", "+10b", + "+10", "-10o" ]; + + var spiral_b1 = ["+1", "-1b", "-1", "+2b", "+2", "-2", "+3b", "+3", "-3b", "-3", "+4b", "+4", + "-4", "+5b", "+5", "-5b", "-5", "+6", "-6b", "-6", "+7b", "+7b", "-7", "-7", + "+8", "-8b", "-8", "+9b", "+9", "-9", "+10b", "+10", "-10b", "-10" ]; // Circular/Spiral tuned diatonic + // Inversed for Blow 1. Key of C major scale starts at blow 1 + + var paddyRichter = ["+1", "-1b", "-1", "+2b", "+2", "-2bb", "-2b", "-2", "+3b", "+3", "-3b", "-3", + "+4", "-4b", "-4", "+5b", "+5", "-5", "+6b", "+6", "-6b", "-6", "-7b", "-7", + "+7", "-8b", "-8", "+8b", "+8", "-9", "+9b", "+9", "-10b", "-10", "+10bb", "+10b", + "+10" ]; + paddyRichter[-2] = "+1bb"; paddyRichter[-1] = "+1b"; //Two notes below the key at blow 1 + // Brendan Power's tuning, half valved + + var powerBender = ["+1", "-1b", "-1", "+2b", "+2", "-2bb", "-2b", "-2", "-3bbb", "-3bb", "-3b", "-3", + "+4", "-4b", "-4", "-5b", "-5", "+6", "-6b", "-6", "+7b", "+7", "-7b", "-7", + "+8", "-8b", "-8", "+9b", "+9", "-9bb", "-9b", "-9", "+10b", "+10", "-10bb", "-10b", + "-10" ]; + powerBender[-2] = "+1bb"; powerBender[-1] = "+1b"; //Two notes below the key at blow 1 + // Brendan Power's tuning, half valved + + var powerDraw = ["+1", "-1b", "-1", "+2b", "+2", "-2bb", "-2b", "-2", "-3bbb", "-3bb", "-3b", "-3", + "+4", "-4b", "-4", "+5b", "+5", "-5", "+6b", "+6", "-6b", "-6", "-7b", "-7", + "+8", "-8b", "-8", "+9b", "+9", "-9bb", "-9b", "-9", "+10b", "+10", "-10bb", "-10b", + "-10" ]; + powerDraw[-2] = "+1bb"; powerDraw[-1] = "+1b"; //Two notes below the key at blow 1 + // Brendan Power's tuning, half valved + + var tremolo24 = ["+3", "-2b", "-2", "+3o", "+5", "-4", "+7o", "+7", "-6b", "-6", "-8b", "-8", + "+9", "-10b", "-10", "+11o", "+11", "-12", "+13o", "+13", "-14b", "-14", "-16b", "-16", + "+15", "-16o", "-18", "+17b", "+17", "-20", "+19b", "+19", "-20o", "-22", "+24b", "+24", + "+21", "-22o" ]; //Standard Richter tuning with overbends (added tremolo24 notes) + + + var tuning = richter + switch (harp.tuning) { + case 1: tuning = richter; break; + case 2: tuning = richterValved; break; + case 3: tuning = country; break; + case 4: tuning = chromatic10hole; break; + case 5: tuning = standardChromatic; break; + case 6: tuning = chromatic16H; break; + case 7: tuning = zirkValved; break; + case 8: tuning = trueChrom; break; + case 9: tuning = naturalMinor; break; + case 10: tuning = melodyMaker; break; + case 11: tuning = spiral_b1; break; + case 12: tuning = paddyRichter; break; + case 13: tuning = powerBender; break; + case 14: tuning = powerDraw; break; + case 15: tuning = tremolo24; break; // (added tremolo24 option) + default: tuning = richter; break; + } + + var harpkey = keylist.key + console.log("harpkey set to " + keylist.key) + + for (var i = 0; i < notes.length; i++) { + + if ( i > 0 ) + text.text = sep + text.text; + + if (typeof notes[i].pitch === "undefined") // just in case + return + var tab = tuning[notes[i].pitch - harpkey]; + if (typeof tab === "undefined") + text.text = "X"; + else { + if (bendChar !== "b") + tab = tab.replace(/b/g, bendChar); + text.text = tab + text.text; + } + } + } + + function applyToSelection(func) { + if (typeof curScore === 'undefined') + quit(); + var cursor = curScore.newCursor(); + var startStaff; + var endStaff; + var endTick; + var fullScore = false; + var textposition = (placetext.position === "above" ? Placement.ABOVE : Placement.BELOW); + var textcolor = colorlist.textColor // added color option + var textsize = sizelist.textSize // added font size option + + cursor.rewind(1); + if (!cursor.segment) { // no selection + fullScore = true; + startStaff = 0; // start with 1st staff + endStaff = curScore.nstaves - 1; // and end with last + } else { + startStaff = cursor.staffIdx; + cursor.rewind(2); + if (cursor.tick == 0) { + // this happens when the selection includes + // the last measure of the score. + // rewind(2) goes behind the last segment (where + // there's none) and sets tick=0 + endTick = curScore.lastSegment.tick + 1; + } else { + endTick = cursor.tick; + } + endStaff = cursor.staffIdx; + } + console.log(startStaff + " - " + endStaff + " - " + endTick) + + for (var staff = startStaff; staff <= endStaff; staff++) { + for (var voice = 0; voice < 4; voice++) { + cursor.rewind(1); // beginning of selection + cursor.voice = voice; + cursor.staffIdx = staff; + + if (fullScore) // no selection + cursor.rewind(0); // beginning of score + + while (cursor.segment && (fullScore || cursor.tick < endTick)) { + if (cursor.element && cursor.element.type == Element.CHORD) { + var text = newElement(Element.LYRICS); + + var graceChords = cursor.element.graceNotes; + for (var i = 0; i < graceChords.length; i++) { + // iterate through all grace chords + var notes = graceChords[i].notes; + tabNotes(notes, text); + // TODO: deal with placement of grace note on the x axis + text.placement = textposition + text.color = textcolor // added color option + text.offset = Qt.point(-2 * (graceChords.length - i), 0) + text.fontSize = textsize // added font size option + cursor.add(text); + // new text for next element + text = newElement(Element.LYRICS); + } + + var notes = cursor.element.notes; + tabNotes(notes, text); + text.placement = textposition + text.fontSize = textsize // added font size option + text.color = textcolor // added color option + + cursor.add(text); + } // end if CHORD + cursor.next(); + } // end while segment + } // end for voice + } // end for staff + quit(); + } // end applyToSelection() + + function apply() { + curScore.startCmd() + applyToSelection(tabNotes) + curScore.endCmd() + } + + onRun: { + if (typeof curScore === 'undefined') + quit(); + } +} + + From ee243bfb549fc84c1b5baa4c0d38b2e6e8f7e98f Mon Sep 17 00:00:00 2001 From: JB91653 Date: Sun, 7 Dec 2025 20:50:24 -0800 Subject: [PATCH 6/7] Delete harmonica_tablature_color_MS46.qml --- harmonica_tablature_color_MS46.qml | 769 ----------------------------- 1 file changed, 769 deletions(-) delete mode 100644 harmonica_tablature_color_MS46.qml diff --git a/harmonica_tablature_color_MS46.qml b/harmonica_tablature_color_MS46.qml deleted file mode 100644 index bba5d82..0000000 --- a/harmonica_tablature_color_MS46.qml +++ /dev/null @@ -1,769 +0,0 @@ -//============================================================================= -// MuseScore -// Music Composition & Notation -// -// Harmonica Tabs Plugin -// -// -// 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 -//============================================================================= - -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import MuseScore 3.0 - -MuseScore { - - version: "4.6" - description: "Harmonica Tab plugin, cleaned up code, stylized selection box, added text color and size options, added tremolo 24-hole harmonica, added chromatic 10-hole harmonica, updated default selections. Updated by Joshua Buys" - pluginType: "dialog" - title: "Harmonica Tablature Setup" - thumbnailName: "harmonica_tabs.png" - -// ------ OPTIONS ------- - property string sep : "\n" // change to "," if you want tabs horizontally - property string bendChar : "'" // change to "b" if you want bend to be noted with b -// ------ OPTIONS ------- - - id: window - width: 550 - height: 300 - - property var itemTextX1 : 20 - property var itemTextY1 : 20 - property var itemTextY2 : 20 - property var xPositionInit : 0.0 - property var yPositionInit : 0.0 - property color dropdownBase: "lightsteelblue" - property color dropdownText: "#001B2E" - - Rectangle { - id: mainInput - anchors.fill: parent - color: "#000A57" - - function comboStyle() { - } - - Column { - id: column - anchors.left: parent.left - anchors.right: parent.right - anchors.top: parent.top - anchors.margins: 10 - spacing: 17 - //z: 5 - anchors.topMargin: 25 - height: parent.height - 70 - - Row { - id:keyRow - spacing: 20 - width: parent.width - x: 20 - - Label { - font.pointSize: 14 - font.family: "Inter" - //font.weight: Font.ExtraBold - font.bold: true - color: "#00C8D7" - anchors.verticalCenter: parent.verticalCenter - width: parent.width / 4 - horizontalAlignment: Text.AlignRight - text: "Harmonica key:" - } - - ComboBox { - id: keyBox - anchors.verticalCenter: parent.verticalCenter - textRole: "text" - implicitWidth: 50 - implicitHeight: 25 - - background: Rectangle { - color: dropdownBase - radius: 12 - clip: true - } - - contentItem: Text { - text: keyBox.displayText - color: dropdownText - font.pointSize: 12 - font.bold: true - verticalAlignment: Text.AlignVCenter - elide: Text.ElideRight - leftPadding: 15 - } - - currentIndex: 17 - model: ListModel { - id: keylist - property var key: undefined // Initialize property with default value (updated F# to 54) - ListElement { text: "Low G"; harpkey: 43 } - ListElement { text: "Low Ab"; harpkey: 44 } - ListElement { text: "Low A"; harpkey: 45 } - ListElement { text: "Low Bb"; harpkey: 46 } - ListElement { text: "Low B"; harpkey: 47 } - ListElement { text: "Low C"; harpkey: 48 } - ListElement { text: "Low C#"; harpkey: 49 } - ListElement { text: "Low D"; harpkey: 50 } - ListElement { text: "Low Eb"; harpkey: 51 } - ListElement { text: "Low E"; harpkey: 52 } - ListElement { text: "Low F"; harpkey: 53 } - ListElement { text: "Low F#"; harpkey: 54 } - ListElement { text: "G"; harpkey: 55 } - ListElement { text: "Ab"; harpkey: 56 } - ListElement { text: "A"; harpkey: 57 } - ListElement { text: "Bb"; harpkey: 58 } - ListElement { text: "B"; harpkey: 59 } - ListElement { text: "C"; harpkey: 60 } - ListElement { text: "Db"; harpkey: 61 } - ListElement { text: "D"; harpkey: 62 } - ListElement { text: "Eb"; harpkey: 63 } - ListElement { text: "E"; harpkey: 64 } - ListElement { text: "F"; harpkey: 65 } - ListElement { text: "F#"; harpkey: 66 } - ListElement { text: "High G"; harpkey: 67 } - } - - onCurrentIndexChanged: { - // Validate currentIndex - if (currentIndex >= 0 && currentIndex < keylist.count) { - // Retrieve the current item - var currentItem = keylist.get(currentIndex); - // Debugging output - console.debug("Selected item:", currentItem.text, ", Harpkey:", currentItem.harpkey); - // Set the key property - keylist.key = currentItem.harpkey; - } else { - console.error("Invalid currentIndex:", currentIndex); - } - } - - Component.onCompleted: { - // Set initial harpkey value - if (currentIndex >= 0 && currentIndex < keylist.count) { - var initialItem = keylist.get(currentIndex); - keylist.key = initialItem.harpkey; - console.debug("Initial item:", initialItem.text, ", Harpkey:", initialItem.harpkey); - } else { - console.error("Invalid initial currentIndex:", currentIndex); - } - } - } - } - - - Row { - id:typeRow - spacing: 20 - width: parent.width - x: 20 - - Label { - font.pointSize: 14 - font.family: "Inter" - //font.weight: Font.ExtraBold - font.bold: true - color: "#00C8D7" - anchors.verticalCenter: parent.verticalCenter - width: parent.width / 4 - horizontalAlignment: Text.AlignRight - text: "Harmonica type:" - } - - ComboBox { - id: typeBox - anchors.verticalCenter: parent.verticalCenter - textRole: "text" - implicitWidth: 300 - implicitHeight: 25 - - background: Rectangle { - color: dropdownBase - radius: 12 - clip: true - } - - contentItem: Text { - text: typeBox.displayText - color: dropdownText - font.pointSize: 12 - font.bold: true - verticalAlignment: Text.AlignVCenter - elide: Text.ElideRight - leftPadding: 15 - } - - currentIndex: 0 - model: ListModel { - id: harp - property var tuning: undefined // Initialize property with default value, updated list order for consistency through code - ListElement { text: "Blues Harp (Richter)"; tuning: 1 } - ListElement { text: "Richter valved"; tuning: 2 } - ListElement { text: "Country"; tuning: 3 } - ListElement { text: "Chromatic 10 Hole"; tuning: 4 } - ListElement { text: "Chromatic 12 Hole"; tuning: 5 } - ListElement { text: "Chromatic 16 Hole"; tuning: 6 } - ListElement { text: "Circular (Seydel), valved"; tuning: 7 } - ListElement { text: "TrueChromatic Diatonic, valved"; tuning: 8 } - ListElement { text: "Natural Minor"; tuning: 9 } - ListElement { text: "Melody Maker"; tuning: 10 } - ListElement { text: "Circular (Inversed for blow 1), valved "; tuning: 11 } - ListElement { text: "Paddy Richter (Brendan Power), valved"; tuning: 12 } - ListElement { text: "Power Bender (Brendan Power), valved"; tuning: 13 } - ListElement { text: "Power Draw (Brendan Power), valved"; tuning: 14 } - ListElement { text: "Tremolo 24-hole"; tuning: 15 } // (added tremolo 24-hole option) - } - - onCurrentIndexChanged: { - // Validate currentIndex - if (currentIndex >= 0 && currentIndex < harp.count) { - // Retrieve the current item - var currentItem = harp.get(currentIndex); - // Debugging output - console.debug("Selected item:", currentItem.text, ", Tuning:", currentItem.tuning); - // Set the tuning property - harp.tuning = currentItem.tuning; - } else { - console.error("Invalid currentIndex:", currentIndex); - } - } - - Component.onCompleted: { - // Set initial tuning value - if (currentIndex >= 0 && currentIndex < harp.count) { - var initialItem = harp.get(currentIndex); - harp.tuning = initialItem.tuning; - console.debug("Initial item:", initialItem.text, ", Tuning:", initialItem.tuning); - } else { - console.error("Invalid initial currentIndex:", currentIndex); - } - } - } - } - - Row { - id:positionRow - spacing: 20 - width: parent.width - x: 20 - - Label { - font.pointSize: 14 - font.family: "Inter" - //font.weight: Font.ExtraBold - font.bold: true - color: "#00C8D7" - anchors.verticalCenter: parent.verticalCenter - width: parent.width / 4 - horizontalAlignment: Text.AlignRight - text: "Position:" - } - - ComboBox { - id: positionBox - anchors.verticalCenter: parent.verticalCenter - textRole: "text" - implicitWidth: 125 - implicitHeight: 25 - - background: Rectangle { - color: dropdownBase - radius: 12 - clip: true - } - - contentItem: Text { - text: positionBox.displayText - color: dropdownText - font.pointSize: 12 - font.bold: true - verticalAlignment: Text.AlignVCenter - elide: Text.ElideRight - leftPadding: 15 - } - - currentIndex: 0 - model: ListModel { - id: placetext - property var position: undefined // Initialize property with default value - ListElement { text: "Above staff"; position: "above" } - ListElement { text: "Below staff"; position: "below" } - } - - onCurrentIndexChanged: { - // Validate currentIndex - if (currentIndex >= 0 && currentIndex < placetext.count) { - // Retrieve the current item - var currentItem = placetext.get(currentIndex); - // Debugging output - console.debug("Selected item:", currentItem.text, ", Position:", currentItem.position); - // Set the position property - placetext.position = currentItem.position; - } else { - console.error("Invalid currentIndex:", currentIndex); - } - } - - Component.onCompleted: { - // Set initial position value - if (currentIndex >= 0 && currentIndex < placetext.count) { - var initialItem = placetext.get(currentIndex); - placetext.position = initialItem.position; - console.debug("Initial item:", initialItem.text, ", Position:", initialItem.position); - } else { - console.error("Invalid initial currentIndex:", currentIndex); - } - } - } - } - - Row { - id:colorRow - spacing: 20 - width: parent.width - x: 20 - - Label { - font.pointSize: 14 - font.family: "Inter" - //font.weight: Font.ExtraBold - font.bold: true - color: "#00C8D7" - anchors.verticalCenter: parent.verticalCenter - width: parent.width / 4 - horizontalAlignment: Text.AlignRight - text: "Color:" - } - - ComboBox { - id: colorBox - anchors.verticalCenter: parent.verticalCenter - textRole: "text" - implicitWidth: 100 - implicitHeight: 25 - - background: Rectangle { - color: dropdownBase - radius: 12 - clip: true - } - - contentItem: Text { - text: colorBox.displayText - color: dropdownText - font.pointSize: 12 - font.bold: true - verticalAlignment: Text.AlignVCenter - elide: Text.ElideRight - leftPadding: 15 - } - - currentIndex: 2 - model: ListModel { - id: colorlist - property var textColor: undefined // Initialize property with default value (added color option) - ListElement { text: "Black"; colorkey: "black" } - ListElement { text: "Brown"; colorkey: "brown" } - ListElement { text: "Red"; colorkey: "red" } - ListElement { text: "Pink"; colorkey: "deeppink" } - ListElement { text: "Orange"; colorkey: "orange" } - ListElement { text: "Green"; colorkey: "green" } - ListElement { text: "Blue"; colorkey: "blue" } - ListElement { text: "Purple"; colorkey: "purple" } - } - - onCurrentIndexChanged: { - // Validate currentIndex - if (currentIndex >= 0 && currentIndex < colorlist.count) { - // Retrieve the current item - var currentItem = colorlist.get(currentIndex); - // Debugging output - console.debug("Selected item:", currentItem.text, ", Color:", currentItem.colorkey); - // Set the color property - colorlist.textColor = currentItem.colorkey; - } else { - console.error("Invalid currentIndex:", currentIndex); - } - } - - Component.onCompleted: { - // Set initial color value - if (currentIndex >= 0 && currentIndex < colorlist.count) { - var initialItem = colorlist.get(currentIndex); - colorlist.textColor = initialItem.colorkey; - console.debug("Initial item:", initialItem.text, ", Color:", initialItem.colorkey); - } else { - console.error("Invalid initial currentIndex:", currentIndex); - } - } - } - } - - Row { - id: sizeRow - spacing: 20 - width: parent.width - x: 20 - - Label { - font.pointSize: 14 - font.family: "Inter" - //font.weight: Font.ExtraBold - font.bold: true - color: "#00C8D7" - anchors.verticalCenter: parent.verticalCenter - width: parent.width / 4 - horizontalAlignment: Text.AlignRight - text: "Font Size:" - } - - ComboBox { - id: sizeBox - anchors.verticalCenter: parent.verticalCenter - textRole: "text" - implicitWidth: 50 - implicitHeight: 25 - - background: Rectangle { - color: dropdownBase - radius: 12 - clip: true - } - - contentItem: Text { - text: sizeBox.displayText - color: dropdownText - font.pointSize: 12 - font.bold: true - verticalAlignment: Text.AlignVCenter - elide: Text.ElideRight - leftPadding: 15 - } - - currentIndex: 1 - model: ListModel { - id: sizelist - property var textSize: undefined // Initialize property with default value (added font size option) - ListElement { text: "10"; sizekey: 10 } - ListElement { text: "12"; sizekey: 12 } - ListElement { text: "14"; sizekey: 14 } - ListElement { text: "16"; sizekey: 16 } - ListElement { text: "18"; sizekey: 18 } - } - - onCurrentIndexChanged: { - // Validate currentIndex - if (currentIndex >= 0 && currentIndex < sizelist.count) { - // Retrieve the current item - var currentItem = sizelist.get(currentIndex); - // Debugging output - console.debug("Selected item:", currentItem.text, ", Color:", currentItem.sizekey); - // Set the color property - sizelist.textSize = currentItem.sizekey; - } else { - console.error("Invalid currentIndex:", currentIndex); - } - } - - Component.onCompleted: { - // Set initial font size value - if (currentIndex >= 0 && currentIndex < sizelist.count) { - var initialItem = sizelist.get(currentIndex); - sizelist.textSize = initialItem.sizekey; - console.debug("Initial item:", initialItem.text, ", Color:", initialItem.sizekey); - } else { - console.error("Invalid initial currentIndex:", currentIndex); - } - } - } - } - } - } - - Row { - id: buttonRow - height: 40 - anchors.bottomMargin: 20 - anchors.bottom: parent.bottom - anchors.horizontalCenter: parent.horizontalCenter - spacing: 20 - - Button { - id: applyButton - width: 110 - height: 40 - text: "Apply" - background: Rectangle { - color: "#49A26E" - anchors.fill: parent - radius: 18 - } - - contentItem: Text { - text: applyButton.text - color: "#003D12" - font.pointSize: 18 - font.bold: true - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } - - onClicked: { - apply() - quit() - } - } - - Button { - id: cancelButton - width: 110 - height: 40 - text: "Cancel" - background: Rectangle { - color: "#E56579" - anchors.fill: parent - radius: 18 - } - - contentItem: Text { - text: cancelButton.text - color: "#680000" - //color: "#315611" - font.pointSize: 18 - font.bold: true - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } - - onClicked: { - quit() - } - } - } - - function tabNotes(notes, text) { // updated order for consistency throughout code - - var richter = ["+1", "-1b", "-1", "+1o", "+2", "-2bb", "-2b", "+3", "-3bbb", "-3bb", "-3b", "-3", - "+4", "-4b", "-4", "+4o", "+5", "-5", "+5o", "+6", "-6b", "-6", "+6o", "-7", - "+7", "-7o", "-8", "+8b", "+8", "-9", "+9b", "+9", "-9o", "-10", "+10bb", "+10b", - "+10", "-10o" ]; //Standard Richter tuning with overbends (updated -2 to +3 (same note) as it's used more often) - - var richterValved = ["+1", "-1b", "-1", "+2b", "+2", "-2bb", "-2b", "-2", "-3bbb", "-3bb", "-3b", "-3", - "+4", "-4b", "-4", "+5b", "+5", "-5", "+6b", "+6", "-6b", "-6", "-7b", "-7", - "+7", "-8b", "-8", "+8b", "+8", "-9", "+9b", "+9", "-10b", "-10", "+10bb", "+10b", - "+10" ]; - richterValved[-2] = "+1bb"; richterValved[-1] = "+1b"; //Two notes below the key at blow 1 - - var country = ["+1", "-1b", "-1", "+1o", "+2", "-2bb", "-2b", "-2", "-3bbb", "-3bb", "-3b", "-3", - "+4", "-4b", "-4", "+4o", "+5", "-5b", "-5", "+6", "-6b", "-6", "+6o", "-7", - "+7", "-7o", "-8", "+8b", "+8", "-9", "+9b", "+9", "-9o", "-10", "+10bb", "+10b", - "+10", "-10o" ]; - - var chromatic10hole = ["+1", '+1s', "-1", "-1s", "+2", "-2", "-2s", "+3", "+3s", "-3", "-3s","-4", - "+5", "+5s", "-5", "-5s", "+6", "-6", "-6s", "+7", "+7s", "-7", "-7s", "-8", - "+8", "+8s", "-9", "-9s", "+9", "-10", "-10s", "+10", "+10s" ]; - - var standardChromatic = ["+1", '+1s', "-1", "-1s", "+2", "-2", "-2s", "+3", "+3s", "-3", "-3s","-4", - "+5", "+5s", "-5", "-5s", "+6", "-6", "-6s", "+7", "+7s", "-7", "-7s", "-8", - "+8", "+8s", "-9", "-9s", "+10", "-10", "-10s", "+11", "+11s", "-11", "-11s", "-12", - "+12", "+12s", "-12", "-12s" ]; // updated +4 to +5 (same note) as it's used more often - - var chromatic16H = ["+1.", "+1.s", "-1.", "-1.s", "+2.", "-2.", "-2.s", "+3.", "+3.s", "-3.", "-3.s","-4.", - "+1", "+1<", "-1", "-1s", "+2", "-2", "-2s", "+3", "+3s", "-3", "-3s","-4", - "+4", "+5s", "-5", "-5s", "+6", "-6", "-6s", "+7", "+7s", "-7", "-7s", "-8", - "+8", "+8s", "-9", "-9s", "+10", "-10", "-10s", "+11", "+11s", "-11", "-11s", "-12", - "+12", "+12s", "-12", "-12s" ]; - - var zirkValved = ["+1", "-1b", "-1", "+2b", "+2", "-2", "+3b", "+3", "-3b", "-3", "+4", "-4b", - "-4", "+5b", "+5", "-5b", "-5", "+6", "-6b", "-6", "+7b", "+7", "-7", "+8b", - "+8", "-8b", "-8", "+9b", "+9", "-9", "10b", "+10", "-10b", "-10" ]; // Circular/Spiral tuned diatonic - // Key per Seydel "G"on blow 1, C major at draw 2, A minor at draw 1 - - var trueChrom = ["+1", "-1b", "-1", "+2", "-2b", "-2", "+3b", "+3", "-3b", "-3", "+4", "-4b", - "-4", "+5b", "+5", "-5b", "-5", "+6", "-6b", "-6", "+7b", "+7", "-7b", "-7", - "+8", "-8b", "-8", "+9b", "+9", "-9b", "-9", "+10", "-10b", "-10" ]; //True Chromatic diatonic, valves - //Another side of the spiral logic is expanded in the “True Chromatic” tuning, designed by Eugene Ivanov. - //All chords can be arranged in a continuous, looped progression on major and minor triads: - //C Eb G Bb D F A C E G B D Gb A Db E Ab B Eb Gb Bb Db F Ab C (and looped on C minor after that). - - var naturalMinor = ["+1", "-1b", "-1", "+2", "-2bbb", "-2bb", "-2b", "-2", "-3bb", "-3b", "-3", "+3o", - "+4", "-4b", "-4", "+5", "-5b", "-5", "+5o", "+6", "-6b", "-6", "-7", "+7b", - "+7", "-7o", "-8", "+8", "-8o", "-9", "+9b", "+9", "-9o", "-10", "+10bb", "+10b", - "+10", "-10o" ]; //Labeled by blow 1 like Hohner. Seydel and Lee Okar labels by draw 2 - - var melodyMaker = [ , , , , , // label by draw 2 - "+1", "-1b", "-1", "+1o","+2", "-2bb","-2b", "-2", "+2o", "+3", "-3b", "-3", - "+4", "-4b", "-4", "+4o", "+5", "-5b", "-5", "+6", "-6b", "-6", "+6o", "-7", - "+7", "-7o", "-8", "+8b", "+8", "-8o", "-9", "+9", "-9o", "-10", "+10bb", "+10b", - "+10", "-10o" ]; - - var spiral_b1 = ["+1", "-1b", "-1", "+2b", "+2", "-2", "+3b", "+3", "-3b", "-3", "+4b", "+4", - "-4", "+5b", "+5", "-5b", "-5", "+6", "-6b", "-6", "+7b", "+7b", "-7", "-7", - "+8", "-8b", "-8", "+9b", "+9", "-9", "+10b", "+10", "-10b", "-10" ]; // Circular/Spiral tuned diatonic - // Inversed for Blow 1. Key of C major scale starts at blow 1 - - var paddyRichter = ["+1", "-1b", "-1", "+2b", "+2", "-2bb", "-2b", "-2", "+3b", "+3", "-3b", "-3", - "+4", "-4b", "-4", "+5b", "+5", "-5", "+6b", "+6", "-6b", "-6", "-7b", "-7", - "+7", "-8b", "-8", "+8b", "+8", "-9", "+9b", "+9", "-10b", "-10", "+10bb", "+10b", - "+10" ]; - paddyRichter[-2] = "+1bb"; paddyRichter[-1] = "+1b"; //Two notes below the key at blow 1 - // Brendan Power's tuning, half valved - - var powerBender = ["+1", "-1b", "-1", "+2b", "+2", "-2bb", "-2b", "-2", "-3bbb", "-3bb", "-3b", "-3", - "+4", "-4b", "-4", "-5b", "-5", "+6", "-6b", "-6", "+7b", "+7", "-7b", "-7", - "+8", "-8b", "-8", "+9b", "+9", "-9bb", "-9b", "-9", "+10b", "+10", "-10bb", "-10b", - "-10" ]; - powerBender[-2] = "+1bb"; powerBender[-1] = "+1b"; //Two notes below the key at blow 1 - // Brendan Power's tuning, half valved - - var powerDraw = ["+1", "-1b", "-1", "+2b", "+2", "-2bb", "-2b", "-2", "-3bbb", "-3bb", "-3b", "-3", - "+4", "-4b", "-4", "+5b", "+5", "-5", "+6b", "+6", "-6b", "-6", "-7b", "-7", - "+8", "-8b", "-8", "+9b", "+9", "-9bb", "-9b", "-9", "+10b", "+10", "-10bb", "-10b", - "-10" ]; - powerDraw[-2] = "+1bb"; powerDraw[-1] = "+1b"; //Two notes below the key at blow 1 - // Brendan Power's tuning, half valved - - var tremolo24 = ["+3", "-2b", "-2", "+3o", "+5", "-4", "+7o", "+7", "-6b", "-6", "-8b", "-8", - "+9", "-10b", "-10", "+11o", "+11", "-12", "+13o", "+13", "-14b", "-14", "-16b", "-16", - "+15", "-16o", "-18", "+17b", "+17", "-20", "+19b", "+19", "-20o", "-22", "+24b", "+24", - "+21", "-22o" ]; //Standard Richter tuning with overbends (added tremolo24 notes) - - - var tuning = richter - switch (harp.tuning) { - case 1: tuning = richter; break; - case 2: tuning = richterValved; break; - case 3: tuning = country; break; - case 4: tuning = chromatic10hole; break; - case 5: tuning = standardChromatic; break; - case 6: tuning = chromatic16H; break; - case 7: tuning = zirkValved; break; - case 8: tuning = trueChrom; break; - case 9: tuning = naturalMinor; break; - case 10: tuning = melodyMaker; break; - case 11: tuning = spiral_b1; break; - case 12: tuning = paddyRichter; break; - case 13: tuning = powerBender; break; - case 14: tuning = powerDraw; break; - case 15: tuning = tremolo24; break; // (added tremolo24 option) - default: tuning = richter; break; - } - - var harpkey = keylist.key - console.log("harpkey set to " + keylist.key) - - for (var i = 0; i < notes.length; i++) { - - if ( i > 0 ) - text.text = sep + text.text; - - if (typeof notes[i].pitch === "undefined") // just in case - return - var tab = tuning[notes[i].pitch - harpkey]; - if (typeof tab === "undefined") - text.text = "X"; - else { - if (bendChar !== "b") - tab = tab.replace(/b/g, bendChar); - text.text = tab + text.text; - } - } - } - - function applyToSelection(func) { - if (typeof curScore === 'undefined') - quit(); - var cursor = curScore.newCursor(); - var startStaff; - var endStaff; - var endTick; - var fullScore = false; - var textposition = (placetext.position === "above" ? Placement.ABOVE : Placement.BELOW); - var textcolor = colorlist.textColor // added color option - var textsize = sizelist.textSize // added font size option - - cursor.rewind(1); - if (!cursor.segment) { // no selection - fullScore = true; - startStaff = 0; // start with 1st staff - endStaff = curScore.nstaves - 1; // and end with last - } else { - startStaff = cursor.staffIdx; - cursor.rewind(2); - if (cursor.tick == 0) { - // this happens when the selection includes - // the last measure of the score. - // rewind(2) goes behind the last segment (where - // there's none) and sets tick=0 - endTick = curScore.lastSegment.tick + 1; - } else { - endTick = cursor.tick; - } - endStaff = cursor.staffIdx; - } - console.log(startStaff + " - " + endStaff + " - " + endTick) - - for (var staff = startStaff; staff <= endStaff; staff++) { - for (var voice = 0; voice < 4; voice++) { - cursor.rewind(1); // beginning of selection - cursor.voice = voice; - cursor.staffIdx = staff; - - if (fullScore) // no selection - cursor.rewind(0); // beginning of score - - while (cursor.segment && (fullScore || cursor.tick < endTick)) { - if (cursor.element && cursor.element.type == Element.CHORD) { - var text = newElement(Element.LYRICS); - - var graceChords = cursor.element.graceNotes; - for (var i = 0; i < graceChords.length; i++) { - // iterate through all grace chords - var notes = graceChords[i].notes; - tabNotes(notes, text); - // TODO: deal with placement of grace note on the x axis - text.placement = textposition - text.color = textcolor // added color option - text.offset = Qt.point(-2 * (graceChords.length - i), 0) - text.fontSize = textsize // added font size option - cursor.add(text); - // new text for next element - text = newElement(Element.LYRICS); - } - - var notes = cursor.element.notes; - tabNotes(notes, text); - text.placement = textposition - text.fontSize = textsize // added font size option - text.color = textcolor // added color option - - cursor.add(text); - } // end if CHORD - cursor.next(); - } // end while segment - } // end for voice - } // end for staff - quit(); - } // end applyToSelection() - - function apply() { - curScore.startCmd() - applyToSelection(tabNotes) - curScore.endCmd() - } - - onRun: { - if (typeof curScore === 'undefined') - quit(); - } -} - - From b07edf382a68158b3f0a51f8874539ba7a0aa72c Mon Sep 17 00:00:00 2001 From: JB91653 Date: Sun, 7 Dec 2025 21:38:06 -0800 Subject: [PATCH 7/7] Cleaned up code, stylized selection box, added text color and size options, added tremolo 24-hole harmonica, added chromatic 10-hole harmonica, updated default selections. --- harmonica_tablature.qml | 688 ++++++++++++++++++++++++++++++++-------- 1 file changed, 563 insertions(+), 125 deletions(-) diff --git a/harmonica_tablature.qml b/harmonica_tablature.qml index a35d294..c4a3c43 100644 --- a/harmonica_tablature.qml +++ b/harmonica_tablature.qml @@ -11,16 +11,18 @@ // the file LICENCE.GPL //============================================================================= -import QtQuick 2.9 -import QtQuick.Controls 1.5 -import QtQuick.Layouts 1.3 +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts import MuseScore 3.0 MuseScore { - version: "3.0" - description: "Harmonica Tab plugin" - menuPath: "Plugins.Harmonica Tablature" + + version: "4.6" + description: "Harmonica Tab plugin, cleaned up code, stylized selection box, added text color and size options, added tremolo 24-hole harmonica, added chromatic 10-hole harmonica, updated default selections. Updated by Joshua Buys" pluginType: "dialog" + title: "Harmonica Tablature Setup" + thumbnailName: "harmonica_tabs.png" // ------ OPTIONS ------- property string sep : "\n" // change to "," if you want tabs horizontally @@ -28,118 +30,531 @@ MuseScore { // ------ OPTIONS ------- id: window - width:280 - height: 180 - ColumnLayout { - id: column - anchors.margins : 10 - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - height: 90 - - ComboBox { - currentIndex: 17 - model: ListModel { - id: keylist - property var key - ListElement { text: "Low G"; harpkey: 43 } - ListElement { text: "Low Ab"; harpkey: 44 } - ListElement { text: "Low A"; harpkey: 45 } - ListElement { text: "Low Bb"; harpkey: 46 } - ListElement { text: "Low B"; harpkey: 47 } - ListElement { text: "Low C"; harpkey: 48 } - ListElement { text: "Low C#"; harpkey: 49 } - ListElement { text: "Low D"; harpkey: 50 } - ListElement { text: "Low Eb"; harpkey: 51 } - ListElement { text: "Low E"; harpkey: 52 } - ListElement { text: "Low F"; harpkey: 53 } - ListElement { text: "Low F#"; harpkey: 52 } - ListElement { text: "G"; harpkey: 55 } - ListElement { text: "Ab"; harpkey: 56 } - ListElement { text: "A"; harpkey: 57 } - ListElement { text: "Bb"; harpkey: 58 } - ListElement { text: "B"; harpkey: 59 } - ListElement { text: "C"; harpkey: 60 } - ListElement { text: "Db"; harpkey: 61 } - ListElement { text: "D"; harpkey: 62 } - ListElement { text: "Eb"; harpkey: 63 } - ListElement { text: "E"; harpkey: 64 } - ListElement { text: "F"; harpkey: 65 } - ListElement { text: "F#"; harpkey: 66 } - ListElement { text: "High G"; harpkey: 67 } - } - width: 100 - onCurrentIndexChanged: { - console.debug(keylist.get(currentIndex).text + ", " + keylist.get(currentIndex).harpkey) - keylist.key = keylist.get(currentIndex).harpkey - } + width: 550 + height: 300 + + property var itemTextX1 : 20 + property var itemTextY1 : 20 + property var itemTextY2 : 20 + property var xPositionInit : 0.0 + property var yPositionInit : 0.0 + property color dropdownBase: "lightsteelblue" + property color dropdownText: "#001B2E" + + Rectangle { + id: mainInput + anchors.fill: parent + color: "#000A57" + + function comboStyle() { } - ComboBox { - currentIndex: 0 - model: ListModel { - id: harp - property var tuning - ListElement { text: "Blues Harp (Richter)"; tuning: 1 } - ListElement { text: "Richter valved"; tuning: 2 } - ListElement { text: "Paddy Richter (Brendan Power), valved"; tuning: 10 } - ListElement { text: "Natural Minor"; tuning: 7 } - ListElement { text: "Melody Maker"; tuning: 8 } - ListElement { text: "Country"; tuning: 3 } - ListElement { text: "Circular (Seydel), valved"; tuning: 5 } - ListElement { text: "Circular (Inversed for blow 1), valved "; tuning: 9 } - ListElement { text: "TrueChromatic Diatonic, valved"; tuning: 6 } - ListElement { text: "Power Bender (Brendan Power), valved"; tuning: 11 } - ListElement { text: "Power Draw (Brendan Power), valved"; tuning: 12 } - ListElement { text: "Standard Chromatic"; tuning: 4 } + + Column { + id: column + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + anchors.margins: 10 + spacing: 17 + //z: 5 + anchors.topMargin: 25 + height: parent.height - 70 + + Row { + id:keyRow + spacing: 20 + width: parent.width + x: 20 + + Label { + font.pointSize: 14 + font.family: "Inter" + //font.weight: Font.ExtraBold + font.bold: true + color: "#00C8D7" + anchors.verticalCenter: parent.verticalCenter + width: parent.width / 4 + horizontalAlignment: Text.AlignRight + text: "Harmonica key:" + } + + ComboBox { + id: keyBox + anchors.verticalCenter: parent.verticalCenter + textRole: "text" + implicitWidth: 50 + implicitHeight: 25 + + background: Rectangle { + color: dropdownBase + radius: 12 + clip: true + } + + contentItem: Text { + text: keyBox.displayText + color: dropdownText + font.pointSize: 12 + font.bold: true + verticalAlignment: Text.AlignVCenter + elide: Text.ElideRight + leftPadding: 15 + } + + currentIndex: 17 + model: ListModel { + id: keylist + property var key: undefined // Initialize property with default value (updated F# to 54) + ListElement { text: "Low G"; harpkey: 43 } + ListElement { text: "Low Ab"; harpkey: 44 } + ListElement { text: "Low A"; harpkey: 45 } + ListElement { text: "Low Bb"; harpkey: 46 } + ListElement { text: "Low B"; harpkey: 47 } + ListElement { text: "Low C"; harpkey: 48 } + ListElement { text: "Low C#"; harpkey: 49 } + ListElement { text: "Low D"; harpkey: 50 } + ListElement { text: "Low Eb"; harpkey: 51 } + ListElement { text: "Low E"; harpkey: 52 } + ListElement { text: "Low F"; harpkey: 53 } + ListElement { text: "Low F#"; harpkey: 54 } + ListElement { text: "G"; harpkey: 55 } + ListElement { text: "Ab"; harpkey: 56 } + ListElement { text: "A"; harpkey: 57 } + ListElement { text: "Bb"; harpkey: 58 } + ListElement { text: "B"; harpkey: 59 } + ListElement { text: "C"; harpkey: 60 } + ListElement { text: "Db"; harpkey: 61 } + ListElement { text: "D"; harpkey: 62 } + ListElement { text: "Eb"; harpkey: 63 } + ListElement { text: "E"; harpkey: 64 } + ListElement { text: "F"; harpkey: 65 } + ListElement { text: "F#"; harpkey: 66 } + ListElement { text: "High G"; harpkey: 67 } + } + + onCurrentIndexChanged: { + // Validate currentIndex + if (currentIndex >= 0 && currentIndex < keylist.count) { + // Retrieve the current item + var currentItem = keylist.get(currentIndex); + // Debugging output + console.debug("Selected item:", currentItem.text, ", Harpkey:", currentItem.harpkey); + // Set the key property + keylist.key = currentItem.harpkey; + } else { + console.error("Invalid currentIndex:", currentIndex); + } + } + + Component.onCompleted: { + // Set initial harpkey value + if (currentIndex >= 0 && currentIndex < keylist.count) { + var initialItem = keylist.get(currentIndex); + keylist.key = initialItem.harpkey; + console.debug("Initial item:", initialItem.text, ", Harpkey:", initialItem.harpkey); + } else { + console.error("Invalid initial currentIndex:", currentIndex); + } + } + } } - width: 100 - onCurrentIndexChanged: { - console.debug(harp.get(currentIndex).text + ", " + harp.get(currentIndex).tuning) - harp.tuning = harp.get(currentIndex).tuning + + + Row { + id:typeRow + spacing: 20 + width: parent.width + x: 20 + + Label { + font.pointSize: 14 + font.family: "Inter" + //font.weight: Font.ExtraBold + font.bold: true + color: "#00C8D7" + anchors.verticalCenter: parent.verticalCenter + width: parent.width / 4 + horizontalAlignment: Text.AlignRight + text: "Harmonica type:" + } + + ComboBox { + id: typeBox + anchors.verticalCenter: parent.verticalCenter + textRole: "text" + implicitWidth: 300 + implicitHeight: 25 + + background: Rectangle { + color: dropdownBase + radius: 12 + clip: true + } + + contentItem: Text { + text: typeBox.displayText + color: dropdownText + font.pointSize: 12 + font.bold: true + verticalAlignment: Text.AlignVCenter + elide: Text.ElideRight + leftPadding: 15 + } + + currentIndex: 0 + model: ListModel { + id: harp + property var tuning: undefined // Initialize property with default value, updated list order for consistency through code + ListElement { text: "Blues Harp (Richter)"; tuning: 1 } + ListElement { text: "Richter valved"; tuning: 2 } + ListElement { text: "Country"; tuning: 3 } + ListElement { text: "Chromatic 10 Hole"; tuning: 4 } + ListElement { text: "Chromatic 12 Hole"; tuning: 5 } + ListElement { text: "Chromatic 16 Hole"; tuning: 6 } + ListElement { text: "Circular (Seydel), valved"; tuning: 7 } + ListElement { text: "TrueChromatic Diatonic, valved"; tuning: 8 } + ListElement { text: "Natural Minor"; tuning: 9 } + ListElement { text: "Melody Maker"; tuning: 10 } + ListElement { text: "Circular (Inversed for blow 1), valved "; tuning: 11 } + ListElement { text: "Paddy Richter (Brendan Power), valved"; tuning: 12 } + ListElement { text: "Power Bender (Brendan Power), valved"; tuning: 13 } + ListElement { text: "Power Draw (Brendan Power), valved"; tuning: 14 } + ListElement { text: "Tremolo 24-hole"; tuning: 15 } // (added tremolo 24-hole option) + } + + onCurrentIndexChanged: { + // Validate currentIndex + if (currentIndex >= 0 && currentIndex < harp.count) { + // Retrieve the current item + var currentItem = harp.get(currentIndex); + // Debugging output + console.debug("Selected item:", currentItem.text, ", Tuning:", currentItem.tuning); + // Set the tuning property + harp.tuning = currentItem.tuning; + } else { + console.error("Invalid currentIndex:", currentIndex); + } + } + + Component.onCompleted: { + // Set initial tuning value + if (currentIndex >= 0 && currentIndex < harp.count) { + var initialItem = harp.get(currentIndex); + harp.tuning = initialItem.tuning; + console.debug("Initial item:", initialItem.text, ", Tuning:", initialItem.tuning); + } else { + console.error("Invalid initial currentIndex:", currentIndex); + } + } + } } - } - ComboBox { - currentIndex: 1 - model: ListModel { - id: placetext - property var position - ListElement { text: "Above staff"; position: "above" } - ListElement { text: "Below staff"; position: "below" } + + Row { + id:positionRow + spacing: 20 + width: parent.width + x: 20 + + Label { + font.pointSize: 14 + font.family: "Inter" + //font.weight: Font.ExtraBold + font.bold: true + color: "#00C8D7" + anchors.verticalCenter: parent.verticalCenter + width: parent.width / 4 + horizontalAlignment: Text.AlignRight + text: "Position:" + } + + ComboBox { + id: positionBox + anchors.verticalCenter: parent.verticalCenter + textRole: "text" + implicitWidth: 125 + implicitHeight: 25 + + background: Rectangle { + color: dropdownBase + radius: 12 + clip: true + } + + contentItem: Text { + text: positionBox.displayText + color: dropdownText + font.pointSize: 12 + font.bold: true + verticalAlignment: Text.AlignVCenter + elide: Text.ElideRight + leftPadding: 15 + } + + currentIndex: 0 + model: ListModel { + id: placetext + property var position: undefined // Initialize property with default value + ListElement { text: "Above staff"; position: "above" } + ListElement { text: "Below staff"; position: "below" } + } + + onCurrentIndexChanged: { + // Validate currentIndex + if (currentIndex >= 0 && currentIndex < placetext.count) { + // Retrieve the current item + var currentItem = placetext.get(currentIndex); + // Debugging output + console.debug("Selected item:", currentItem.text, ", Position:", currentItem.position); + // Set the position property + placetext.position = currentItem.position; + } else { + console.error("Invalid currentIndex:", currentIndex); + } + } + + Component.onCompleted: { + // Set initial position value + if (currentIndex >= 0 && currentIndex < placetext.count) { + var initialItem = placetext.get(currentIndex); + placetext.position = initialItem.position; + console.debug("Initial item:", initialItem.text, ", Position:", initialItem.position); + } else { + console.error("Invalid initial currentIndex:", currentIndex); + } + } + } } - width: 100 - onCurrentIndexChanged: { - console.debug(placetext.get(currentIndex).text + ", " + placetext.get(currentIndex).position) - placetext.position = placetext.get(currentIndex).position + + Row { + id:colorRow + spacing: 20 + width: parent.width + x: 20 + + Label { + font.pointSize: 14 + font.family: "Inter" + //font.weight: Font.ExtraBold + font.bold: true + color: "#00C8D7" + anchors.verticalCenter: parent.verticalCenter + width: parent.width / 4 + horizontalAlignment: Text.AlignRight + text: "Color:" + } + + ComboBox { + id: colorBox + anchors.verticalCenter: parent.verticalCenter + textRole: "text" + implicitWidth: 100 + implicitHeight: 25 + + background: Rectangle { + color: dropdownBase + radius: 12 + clip: true + } + + contentItem: Text { + text: colorBox.displayText + color: dropdownText + font.pointSize: 12 + font.bold: true + verticalAlignment: Text.AlignVCenter + elide: Text.ElideRight + leftPadding: 15 + } + + currentIndex: 2 + model: ListModel { + id: colorlist + property var textColor: undefined // Initialize property with default value (added color option) + ListElement { text: "Black"; colorkey: "black" } + ListElement { text: "Brown"; colorkey: "brown" } + ListElement { text: "Red"; colorkey: "red" } + ListElement { text: "Pink"; colorkey: "deeppink" } + ListElement { text: "Orange"; colorkey: "orange" } + ListElement { text: "Green"; colorkey: "green" } + ListElement { text: "Blue"; colorkey: "blue" } + ListElement { text: "Purple"; colorkey: "purple" } + } + + onCurrentIndexChanged: { + // Validate currentIndex + if (currentIndex >= 0 && currentIndex < colorlist.count) { + // Retrieve the current item + var currentItem = colorlist.get(currentIndex); + // Debugging output + console.debug("Selected item:", currentItem.text, ", Color:", currentItem.colorkey); + // Set the color property + colorlist.textColor = currentItem.colorkey; + } else { + console.error("Invalid currentIndex:", currentIndex); + } + } + + Component.onCompleted: { + // Set initial color value + if (currentIndex >= 0 && currentIndex < colorlist.count) { + var initialItem = colorlist.get(currentIndex); + colorlist.textColor = initialItem.colorkey; + console.debug("Initial item:", initialItem.text, ", Color:", initialItem.colorkey); + } else { + console.error("Invalid initial currentIndex:", currentIndex); + } + } + } + } + + Row { + id: sizeRow + spacing: 20 + width: parent.width + x: 20 + + Label { + font.pointSize: 14 + font.family: "Inter" + //font.weight: Font.ExtraBold + font.bold: true + color: "#00C8D7" + anchors.verticalCenter: parent.verticalCenter + width: parent.width / 4 + horizontalAlignment: Text.AlignRight + text: "Font Size:" + } + + ComboBox { + id: sizeBox + anchors.verticalCenter: parent.verticalCenter + textRole: "text" + implicitWidth: 50 + implicitHeight: 25 + + background: Rectangle { + color: dropdownBase + radius: 12 + clip: true + } + + contentItem: Text { + text: sizeBox.displayText + color: dropdownText + font.pointSize: 12 + font.bold: true + verticalAlignment: Text.AlignVCenter + elide: Text.ElideRight + leftPadding: 15 + } + + currentIndex: 1 + model: ListModel { + id: sizelist + property var textSize: undefined // Initialize property with default value (added font size option) + ListElement { text: "10"; sizekey: 10 } + ListElement { text: "12"; sizekey: 12 } + ListElement { text: "14"; sizekey: 14 } + ListElement { text: "16"; sizekey: 16 } + ListElement { text: "18"; sizekey: 18 } + } + + onCurrentIndexChanged: { + // Validate currentIndex + if (currentIndex >= 0 && currentIndex < sizelist.count) { + // Retrieve the current item + var currentItem = sizelist.get(currentIndex); + // Debugging output + console.debug("Selected item:", currentItem.text, ", Color:", currentItem.sizekey); + // Set the color property + sizelist.textSize = currentItem.sizekey; + } else { + console.error("Invalid currentIndex:", currentIndex); + } + } + + Component.onCompleted: { + // Set initial font size value + if (currentIndex >= 0 && currentIndex < sizelist.count) { + var initialItem = sizelist.get(currentIndex); + sizelist.textSize = initialItem.sizekey; + console.debug("Initial item:", initialItem.text, ", Color:", initialItem.sizekey); + } else { + console.error("Invalid initial currentIndex:", currentIndex); + } + } + } } } } - RowLayout { + + Row { + id: buttonRow + height: 40 + anchors.bottomMargin: 20 + anchors.bottom: parent.bottom anchors.horizontalCenter: parent.horizontalCenter - anchors.top: column.bottom - height: 70 + spacing: 20 + Button { - id: okButton - text: "Ok" + id: applyButton + width: 110 + height: 40 + text: "Apply" + background: Rectangle { + color: "#49A26E" + anchors.fill: parent + radius: 18 + } + + contentItem: Text { + text: applyButton.text + color: "#003D12" + font.pointSize: 18 + font.bold: true + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + onClicked: { apply() - Qt.quit() + quit() } } + Button { - id: closeButton - text: "Close" - onClicked: { Qt.quit() } - } + id: cancelButton + width: 110 + height: 40 + text: "Cancel" + background: Rectangle { + color: "#E56579" + anchors.fill: parent + radius: 18 + } + + contentItem: Text { + text: cancelButton.text + color: "#680000" + //color: "#315611" + font.pointSize: 18 + font.bold: true + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + onClicked: { + quit() + } + } } - function tabNotes(notes, text) { + function tabNotes(notes, text) { // updated order for consistency throughout code - var richter = ["+1", "-1b", "-1", "+1o", "+2", "-2bb", "-2b", "-2", "-3bbb", "-3bb", "-3b", "-3", + var richter = ["+1", "-1b", "-1", "+1o", "+2", "-2bb", "-2b", "+3", "-3bbb", "-3bb", "-3b", "-3", "+4", "-4b", "-4", "+4o", "+5", "-5", "+5o", "+6", "-6b", "-6", "+6o", "-7", "+7", "-7o", "-8", "+8b", "+8", "-9", "+9b", "+9", "-9o", "-10", "+10bb", "+10b", - "+10", "-10o" ]; //Standard Richter tuning with overbends + "+10", "-10o" ]; //Standard Richter tuning with overbends (updated -2 to +3 (same note) as it's used more often) var richterValved = ["+1", "-1b", "-1", "+2b", "+2", "-2bb", "-2b", "-2", "-3bbb", "-3bb", "-3b", "-3", "+4", "-4b", "-4", "+5b", "+5", "-5", "+6b", "+6", "-6b", "-6", "-7b", "-7", @@ -147,20 +562,23 @@ MuseScore { "+10" ]; richterValved[-2] = "+1bb"; richterValved[-1] = "+1b"; //Two notes below the key at blow 1 - var paddyRichter = ["+1", "-1b", "-1", "+2b", "+2", "-2bb", "-2b", "-2", "+3b", "+3", "-3b", "-3", - "+4", "-4b", "-4", "+5b", "+5", "-5", "+6b", "+6", "-6b", "-6", "-7b", "-7", - "+7", "-8b", "-8", "+8b", "+8", "-9", "+9b", "+9", "-10b", "-10", "+10bb", "+10b", - "+10" ]; - paddyRichter[-2] = "+1bb"; paddyRichter[-1] = "+1b"; //Two notes below the key at blow 1 - // Brendan Power's tuning, half valved - var country = ["+1", "-1b", "-1", "+1o", "+2", "-2bb", "-2b", "-2", "-3bbb", "-3bb", "-3b", "-3", "+4", "-4b", "-4", "+4o", "+5", "-5b", "-5", "+6", "-6b", "-6", "+6o", "-7", "+7", "-7o", "-8", "+8b", "+8", "-9", "+9b", "+9", "-9o", "-10", "+10bb", "+10b", "+10", "-10o" ]; + var chromatic10hole = ["+1", '+1s', "-1", "-1s", "+2", "-2", "-2s", "+3", "+3s", "-3", "-3s","-4", + "+5", "+5s", "-5", "-5s", "+6", "-6", "-6s", "+7", "+7s", "-7", "-7s", "-8", + "+8", "+8s", "-9", "-9s", "+9", "-10", "-10s", "+10", "+10s" ]; + var standardChromatic = ["+1", '+1s', "-1", "-1s", "+2", "-2", "-2s", "+3", "+3s", "-3", "-3s","-4", - "+4", "+4s", "-5", "-5s", "+6", "-6", "-6s", "+7", "+7s", "-7", "-7s", "-8", + "+5", "+5s", "-5", "-5s", "+6", "-6", "-6s", "+7", "+7s", "-7", "-7s", "-8", + "+8", "+8s", "-9", "-9s", "+10", "-10", "-10s", "+11", "+11s", "-11", "-11s", "-12", + "+12", "+12s", "-12", "-12s" ]; // updated +4 to +5 (same note) as it's used more often + + var chromatic16H = ["+1.", "+1.s", "-1.", "-1.s", "+2.", "-2.", "-2.s", "+3.", "+3.s", "-3.", "-3.s","-4.", + "+1", "+1<", "-1", "-1s", "+2", "-2", "-2s", "+3", "+3s", "-3", "-3s","-4", + "+4", "+5s", "-5", "-5s", "+6", "-6", "-6s", "+7", "+7s", "-7", "-7s", "-8", "+8", "+8s", "-9", "-9s", "+10", "-10", "-10s", "+11", "+11s", "-11", "-11s", "-12", "+12", "+12s", "-12", "-12s" ]; @@ -192,6 +610,13 @@ MuseScore { "+8", "-8b", "-8", "+9b", "+9", "-9", "+10b", "+10", "-10b", "-10" ]; // Circular/Spiral tuned diatonic // Inversed for Blow 1. Key of C major scale starts at blow 1 + var paddyRichter = ["+1", "-1b", "-1", "+2b", "+2", "-2bb", "-2b", "-2", "+3b", "+3", "-3b", "-3", + "+4", "-4b", "-4", "+5b", "+5", "-5", "+6b", "+6", "-6b", "-6", "-7b", "-7", + "+7", "-8b", "-8", "+8b", "+8", "-9", "+9b", "+9", "-10b", "-10", "+10bb", "+10b", + "+10" ]; + paddyRichter[-2] = "+1bb"; paddyRichter[-1] = "+1b"; //Two notes below the key at blow 1 + // Brendan Power's tuning, half valved + var powerBender = ["+1", "-1b", "-1", "+2b", "+2", "-2bb", "-2b", "-2", "-3bbb", "-3bb", "-3b", "-3", "+4", "-4b", "-4", "-5b", "-5", "+6", "-6b", "-6", "+7b", "+7", "-7b", "-7", "+8", "-8b", "-8", "+9b", "+9", "-9bb", "-9b", "-9", "+10b", "+10", "-10bb", "-10b", @@ -205,21 +630,29 @@ MuseScore { "-10" ]; powerDraw[-2] = "+1bb"; powerDraw[-1] = "+1b"; //Two notes below the key at blow 1 // Brendan Power's tuning, half valved + + var tremolo24 = ["+3", "-2b", "-2", "+3o", "+5", "-4", "+7o", "+7", "-6b", "-6", "-8b", "-8", + "+9", "-10b", "-10", "+11o", "+11", "-12", "+13o", "+13", "-14b", "-14", "-16b", "-16", + "+15", "-16o", "-18", "+17b", "+17", "-20", "+19b", "+19", "-20o", "-22", "+24b", "+24", + "+21", "-22o" ]; //Standard Richter tuning with overbends (added tremolo24 notes) var tuning = richter - switch (harp.tuning) { + switch (harp.tuning) { // updated order for consistency throughout code case 1: tuning = richter; break; case 2: tuning = richterValved; break; case 3: tuning = country; break; - case 4: tuning = standardChromatic; break; - case 5: tuning = zirkValved; break; - case 6: tuning = trueChrom; break; - case 7: tuning = naturalMinor; break; - case 8: tuning = melodyMaker; break; - case 9: tuning = spiral_b1; break; - case 10: tuning = paddyRichter; break; - case 11: tuning = powerBender; break; - case 12: tuning = powerDraw; break; + case 4: tuning = chromatic10hole; break; + case 5: tuning = standardChromatic; break; + case 6: tuning = chromatic16H; break; + case 7: tuning = zirkValved; break; + case 8: tuning = trueChrom; break; + case 9: tuning = naturalMinor; break; + case 10: tuning = melodyMaker; break; + case 11: tuning = spiral_b1; break; + case 12: tuning = paddyRichter; break; + case 13: tuning = powerBender; break; + case 14: tuning = powerDraw; break; + case 15: tuning = tremolo24; break; // (added tremolo24 option) default: tuning = richter; break; } @@ -240,21 +673,22 @@ MuseScore { if (bendChar !== "b") tab = tab.replace(/b/g, bendChar); text.text = tab + text.text; - } + } } } function applyToSelection(func) { if (typeof curScore === 'undefined') - Qt.quit(); + quit(); var cursor = curScore.newCursor(); var startStaff; var endStaff; var endTick; var fullScore = false; - var textposition = (placetext.position === "above" ? Placement.ABOVE : Placement.BELOW); - + var textcolor = colorlist.textColor // added color option + var textsize = sizelist.textSize // added font size option + cursor.rewind(1); if (!cursor.segment) { // no selection fullScore = true; @@ -287,7 +721,7 @@ MuseScore { while (cursor.segment && (fullScore || cursor.tick < endTick)) { if (cursor.element && cursor.element.type == Element.CHORD) { - var text = newElement(Element.STAFF_TEXT); + var text = newElement(Element.LYRICS); var graceChords = cursor.element.graceNotes; for (var i = 0; i < graceChords.length; i++) { @@ -296,15 +730,19 @@ MuseScore { tabNotes(notes, text); // TODO: deal with placement of grace note on the x axis text.placement = textposition - text.offset = Qt.point(-40 * (graceChords.length - i), 0) + text.color = textcolor // added color option + text.offset = Qt.point(-2 * (graceChords.length - i), 0) + text.fontSize = textsize // added font size option cursor.add(text); // new text for next element - text = newElement(Element.STAFF_TEXT); + text = newElement(Element.LYRICS); } var notes = cursor.element.notes; tabNotes(notes, text); text.placement = textposition + text.fontSize = textsize // added font size option + text.color = textcolor // added color option cursor.add(text); } // end if CHORD @@ -312,7 +750,7 @@ MuseScore { } // end while segment } // end for voice } // end for staff - Qt.quit(); + quit(); } // end applyToSelection() function apply() { @@ -323,6 +761,6 @@ MuseScore { onRun: { if (typeof curScore === 'undefined') - Qt.quit(); + quit(); } }