From c915c486108b7e8e94c0a767148ca460ba4b4066 Mon Sep 17 00:00:00 2001 From: Amras Date: Wed, 13 May 2026 09:53:36 +0200 Subject: [PATCH] Cleanup for usability Remove "+" symbol, since we don't need it for recorders Change symbols used to be slightly more intuitive than digits Change a lot of the defaults and other minor tweaks --- .../woodwind_tab_generator.qml | 512 ++++-------------- 1 file changed, 109 insertions(+), 403 deletions(-) diff --git a/woodwind_tab_generator/woodwind_tab_generator.qml b/woodwind_tab_generator/woodwind_tab_generator.qml index ed83426..ae5f529 100644 --- a/woodwind_tab_generator/woodwind_tab_generator.qml +++ b/woodwind_tab_generator/woodwind_tab_generator.qml @@ -64,18 +64,18 @@ MuseScore { // USER SETTINGS (& defaults) //--------------------------------------------------------- - property int userFontSize: 6 + property int userFontSize: 11 property int userJustification: 1 // 0=left,1=center,2=right - property real userOffsetY: 1.2 - property real userLineSpacing: 0.7 + property real userOffsetY: 1.7 + property real userLineSpacing: 0.5 property string userFormatString: "" // mirrors current profile's formatString property string userFontFamily: "Menlo, Consolas, Liberation Mono, Courier New, monospace" property bool userTransposed: false - property int defaultFontSize: 6 + property int defaultFontSize: 11 property int defaultJustification: 1 - property real defaultOffsetY: 1.2 - property real defaultLineSpacing: 0.7 + property real defaultOffsetY: 1.7 + property real defaultLineSpacing: 0.5 property string defaultFontFamily: "Menlo, Consolas, Liberation Mono, Courier New, monospace" property bool defaultTransposed: false @@ -85,348 +85,69 @@ MuseScore { //--------------------------------------------------------- // FINGERING DICTIONARY - // Last bit = plus sign indicator //--------------------------------------------------------- //Dictionary for your woodwind's fingerings, specifying the note and the fingering pattern (for whistles: left-> right, starting from the fipple end). //NOTE: Note specification convention here uses sharps, so e.g. a Bb whistle needs to be defined as A# - //2 indicates a closed hole, 1 indicates a half hole, 0 indicates open hole. The last digit is a reserved section indicating how many plusses (+) should be drawn to signal overblowing. + // // ○ = 0 -// ◐ = 1 -// ● = 2 - - //Example fingering specification for high D whistle overblowing one octave to G6 -// |--------------------------------------------- -//windway -> | | | ● ● ● ○ ○ ○ | -// |--------------------------------------------- -// "G6": " 2 2 2 0 0 0 1 " <- last bit indicates number of +'s to add (i.e. how many times to overblow). Can be >= 0 - +// ● = 1 +// ◐ = | +// ◐ = - +// ○ = o //Example fingering specification for G#5 on a high D whistle // |--------------------------------------------- //windway -> | | | ● ● ◐ ○ ○ ○ | // |--------------------------------------------- -// "G#5": " 2 2 1 0 0 0 0 " <- overblow bit set to zero to indicate first octave -// | | | | | | | -// variables in format string: $1 $2 $3 $4 $5 $6 $+ and then $note would be replaced with 'G#5' +// "G#5": " 1 1 | 0 0 0 +// | | | | | | +// variables in format string: $1 $2 $3 $4 $5 $6 and then $note would be replaced with 'G#5' // These variables are replaced by the corresponding symbol or text when run. //Can have any number of holes in the specification //NOTE: This plugin was generated from a maker's perspective, and thus the note names correspond with the sounding pitch, and not the // typical octave shift down presented in notations. The dictionary can easily be edited to reflect the transposition, however, if desired. - //TODO: Add transposition checkmark which corrects notations to octave down pitch commonly used. //Whistle definitions property var defaultProfiles: [ - { - name: "Low D Whistle [D4-D6] (Prefer half-holing)", - fingeringDict: { - "D4": "2222220", - "D#4": "2222210", - "E4": "2222200", - "F4": "2222100", - "F#4": "2222000", - "G4": "2220000", - "G#4": "2210000", - "A4": "2200000", - "A#4": "2100000", - "B4": "2000000", - "C5": "0220000", - "C#5": "0000000", - "D5": "0222221", - "D#5": "2222211", - "E5": "2222201", - "F5": "2222101", - "F#5": "2222001", - "G5": "2220001", - "G#5": "2210001", - "A5": "2200001", - "A#5": "2100001", - "B5": "2000001", - "C6": "0202221", - "C#6": "0000001", - "D6": "2222222", - }, - formatString: "$1\n$2\n$3\n\n$4\n$5\n$6\n$+\n\n$note" - }, - { - "name": "Eb Whistle [Eb-D6] (Prefer haf-holing)", - "fingeringDict": { - "D#4": "2222220", - "E4": "2222210", - "F4": "2222200", - "F#4": "2222100", - "G4": "2222000", - "G#4": "2220000", - "A4": "2210000", - "A#4": "2200000", - "B4": "2100000", - "C5": "2000000", - "C#5": "1000000", - "D5": "0000000", - "D#5": "0222221", - "E5": "2222211", - "F5": "2222201", - "F#5": "2222101", - "G5": "2222001", - "G#5": "2220001", - "A5": "2210001", - "A#5": "2200001", - "B5": "2100001", - "C6": "2000001", - "C#6": "1000001", - "D6": "0000001" - }, - formatString: "$1\n$2\n$3\n\n$4\n$5\n$6\n$+\n\n$note" - }, - { - "name": "F Whistle [F4-E6] (Prefer haf-holing)", - "fingeringDict": { - "F4": "2222220", - "F#4": "2222210", - "G4": "2222200", - "G#4": "2222100", - "A4": "2222000", - "A#4": "2220000", - "B4": "2210000", - "C5": "2200000", - "C#5": "2100000", - "D5": "2000000", - "D#5": "1000000", - "E5": "0000000", - "F5": "0222221", - "F#5": "2222211", - "G5": "2222201", - "G#5": "2222101", - "A5": "2222001", - "A#5": "2220001", - "B5": "2210001", - "C6": "2200001", - "C#6": "2100001", - "D6": "2000001", - "D#6": "1000001", - "E6": "0000001" - }, - formatString: "$1\n$2\n$3\n\n$4\n$5\n$6\n$+\n\n$note" - }, - { - "name": "G Whistle [G4-F#6] (Prefer haf-holing)", - "fingeringDict": { - "G4": "2222220", - "G#4": "2222210", - "A4": "2222200", - "A#4": "2222100", - "B4": "2222000", - "C5": "2220000", - "C#5": "2210000", - "D5": "2200000", - "D#5": "2100000", - "E5": "2000000", - "F5": "1000000", - "F#5": "0000000", - "G5": "0222221", - "G#5": "2222211", - "A5": "2222201", - "A#5": "2222101", - "B5": "2222001", - "C6": "2220001", - "C#6": "2210001", - "D6": "2200001", - "D#6": "2100001", - "E6": "2000001", - "F6": "1000001", - "F#6": "0000001" - }, - formatString: "$1\n$2\n$3\n\n$4\n$5\n$6\n$+\n\n$note" - }, - { - name: "Custom Chromatic G Whistle [G4-G#6] (9 holes)", - fingeringDict: { - "G4": "2222222220", - "G#4": "2222222200", - "A4": "2222222000", - "A#4": "2222220000", - "B4": "2222200000", - "C5": "2222000000", - "C#5": "2220000000", - "D5": "2202000000", - "D#5": "2022200000", - "E5": "2020000000", - "F5": "0220000000", - "F#5": "0000000000", - "G5": "2222222221", - "G#5": "2222222201", - "A5": "2222222001", - "A#5": "2222220001", - "B5": "2222200001", - "C6": "2222000001", - "C#6": "2220000001", - "D6": "2200000001", - "D#6": "2222222202", - "E6": "2222222002", - "F6": "2222220002", - "F#6": "0222200002", - "G6": "2222222222", - "G#6": "0222222222" - }, - formatString: "$1 \n$2\n$3\n$4\n$5\n\n$6\n$7\n$8\n$9\n$+\n$note" - }, - { - "name": "Bb Whistle [Bb4-A6] (Prefer haf-holing)", - "fingeringDict": { - "A#4": "2222220", - "B4": "2222210", - "C5": "2222200", - "C#5": "2222100", - "D5": "2222000", - "D#5": "2220000", - "E5": "2210000", - "F5": "2200000", - "F#5": "2100000", - "G5": "2000000", - "G#5": "1000000", - "A5": "0000000", - "A#5": "0222221", - "B5": "2222211", - "C6": "2222201", - "C#6": "2222101", - "D6": "2222001", - "D#6": "2220001", - "E6": "2210001", - "F6": "2200001", - "F#6": "2100001", - "G6": "2000001", - "G#6": "1000001", - "A6": "0000001" - }, - formatString: "$1\n$2\n$3\n\n$4\n$5\n$6\n$+\n\n$note" - }, - { - "name": "C Whistle [C4-C6] (Prefer cross-fingering)", - "fingeringDict": { - "C4": "2222220", - "C#4": "2222210", - "D4": "2222200", - "D#4": "2222100", - "E4": "2222000", - "F4": "2220000", - "F#4": "2210000", - "G4": "2200000", - "G#4": "2022220", - "A4": "2000000", - "A#4": "0220000", - "B4": "0000000", - "C5": "0222221", - "C#5": "2222211", - "D5": "2222201", - "D#5": "2222101", - "E5": "2222001", - "F5": "2220001", - "F#5": "2202201", - "G5": "2200001", - "G#5": "2020001", - "A5": "2000001", - "A#5": "0202221", - "B5": "0000001", - "C6": "0222222", - }, - formatString: "$1\n$2\n$3\n\n$4\n$5\n$6\n$+\n\n$note" - }, { "name": "Soprano Recorder", "fingeringDict": { - "C4": "222222220", - "C#4": "222222210", - "D4": "222222200", - "D#4": "222222100", - "E4": "222222000", - "F4": "222200000", - "F#4": "222100000", - "G4": "222000000", - "G#4": "221000000", - "A4": "220000000", - "A#4": "210000000", - "B4": "200000000", - "C5": "000222221", - "C#5": "002222211", - "D5": "002222201", - "D#5": "002222101", - "E5": "002222001", - "F5": "002220001", - "F#5": "002210001", - "G5": "002200001", - "G#5": "002100001", - "A5": "002000001", - "A#5": "001000001", - "B5": "000000001", + "C4": "11111111", + "C#4": "1111111|", + "D4": "11111110", + "D#4": "111111|o", + "E4": "111111oo", + "F4": "11110011", + "F#4": "1111011o", + "G4": "111100oo", + "G#4": "111011|o", + "A4": "111000oo", + "A#4": "110110oo", + "B4": "110000oo", + "C5": "101000oo", + "C#5": "011000oo", + "D5": "001000oo", + "D#5": "0011111o", + "E5": "-11111oo", + "F5": "-111101o", + "F#5": "-11101oo", + "G5": "-11100oo", + "G#5": "-11010oo", + "A5": "-11000oo", + "A#5": "-110011|", + "B5": "-11011oo", + "C6": "-10011oo", + "C#6": "-1011011", + "D6": "-1011011", + "D#6": "-011011o", }, - formatString: "$note\n\n$1\n-\n$2\n$3\n$4\n-\n$5\n$6\n$7\n$8\n$+" + formatString: "$note\n\n$1\n—\n$2\n$3\n$4\n—\n$5\n$6\n$7\n$8" }, - { - name: "High D Whistle [D5-D7] (Prefer half-holing)", - fingeringDict: { - "D5": "2222220", - "D#5": "2222210", - "E5": "2222200", - "F5": "2222100", - "F#5": "2222000", - "G5": "2220000", - "G#5": "2210000", - "A5": "2200000", - "A#5": "2100000", - "B5": "2000000", - "C6": "0220000", - "C#6": "0000000", - "D6": "0222221", - "D#6": "2222211", - "E6": "2222201", - "F6": "2222101", - "F#6": "2222001", - "G6": "2220001", - "G#6": "2210001", - "A6": "2200001", - "A#6": "2100001", - "B6": "2000001", - "C7": "0202221", - "C#7": "0000001", - "D7": "2222222", - }, - formatString: "$1\n$2\n$3\n\n$4\n$5\n$6\n$+\n\n$note" - }, - { - // OCARINA. from hole 1 to 12 they are: - //$1 LThumb, $2 Lindex, $3 LMiddle, $4 Lring, $5 LPinky $6 LSubhole -> - // -> $7 RThumb, $8 RIndex, $9 RMiddle, $10 RRing, $11 RPinky, $12 RSubhole - //Requires centered text - name: "Alto C Ocarina [A4-F6] (12 hole)", - "fingeringDict": { - "A4": "2222222222220", - "A#4": "2222222222200", - "B4": "2222202222020", - "C5": "2222202222200", - "C#5": "2222202222020", - "D5": "2222202222000", - "D#5": "2222202220020", - "E5": "2222202220000", - "F5": "2222202200000", - "F#5": "2222202002000", - "G5": "2222202000000", - "G#5": "2220202002000", - "A5": "2220202000000", - "A#5": "2200202002000", - "B5": "2200202000000", - "C6": "2000202000000", - "C#6": "0000202002000", - "D6": "0000202000000", - "D#6": "0000200002000", - "E6": "0000200000000", - "F6": "0000000000000" - }, - formatString: " $11\n $5 $10\n $4 $12$9\n $3 $8\n $2$6 \n\n $1 $7\n$note" - } ] // Runtime copy of profiles (loaded/saved via preferences) @@ -680,100 +401,86 @@ MuseScore { } //--------------------------------------------------------- - // BUILD ASCII DIAGRAM + // BUILD UTF-8 DIAGRAM //--------------------------------------------------------- //Build fingering text using binary representation of the fingering for a note. - //The last bit is the plus bit, indicating the first octave (0) or the second octave (1), to be annotated with a + function buildFingeringText(binaryString, formatString, noteName) { - if (!binaryString || binaryString.length < 2) + const open = "0" + const closed = "1" + const half_ver = "|" + const half_hor = "-" + const open_split = "o" + if (!binaryString || binaryString.length < 1) return "Invalid fingering pattern. Length is wrong" - if (!formatString || typeof formatString !== "string") - return "ERR" + if (!formatString || typeof formatString !== "string") + return "ERR" - var holeCount = binaryString.length - 1 - var plusBit = binaryString[binaryString.length - 1] + var holeCount = binaryString.length - //------------------------------------------------- - // Validate allowed characters - //------------------------------------------------- + //------------------------------------------------- + // Validate allowed characters + //------------------------------------------------- - for (var i = 0; i < holeCount; i++) { - if (binaryString[i] !== "0" && - binaryString[i] !== "1" && - binaryString[i] !== "1" && - binaryString[i] !== "2" && - binaryString[i] !== "3") - return "Invalid Fingering Pattern Value. Must be one of 0,1,2" - } + for (var i = 0; i < holeCount; i++) { + if (binaryString[i] !== open && + binaryString[i] !== closed && + binaryString[i] !== half_ver && + binaryString[i] !== half_hor && + binaryString[i] !== open_split) + return "Invalid Fingering Pattern Value. Check the config in the .qml file!" + } - var plusInt = parseInt(plusBit) - if (isNaN(plusInt) || plusInt < 0 || plusInt.toString() !== plusBit) { - return "Invalid Plus Bit: must be a positive integer (0, 1, 2, ...)" - } - //------------------------------------------------- - // Clone format string - //------------------------------------------------- + //------------------------------------------------- + // Clone format string + //------------------------------------------------- - var output = formatString + var output = formatString - //------------------------------------------------- - // Replace note placeholder ($note) with note name - //------------------------------------------------- + //------------------------------------------------- + // Replace note placeholder ($note) with note name + //------------------------------------------------- - if (noteName) { - while (output.indexOf("$note") !== -1) { - output = output.replace("$note", noteName) - } - } + if (noteName) { + while (output.indexOf("$note") !== -1) { + output = output.replace("$note", noteName) + } + } - //------------------------------------------------- - // Replace numbered placeholders ($1 … $N) - //------------------------------------------------- + //------------------------------------------------- + // Replace numbered placeholders ($1 … $N) + //------------------------------------------------- - for (var i = holeCount - 1; i >= 0; i--) { - var symbol - if (binaryString[i] === "2") - symbol = String.fromCharCode(0x25CF) // Closed hole - custom font - else if (binaryString[i] === "1") - symbol = String.fromCharCode(0x25D0) // Half hole - custom font - else if (binaryString[i] == "3") - symbol = String.fromCharCode(0x25D2) // Bottom half hole - else if (binaryString[i] == "4") - symbol = String.fromCharCode(0x29B6) // Open Hold with line - else - symbol = String.fromCharCode(0x25CB) // Open Hole - custom font + for (var i = holeCount - 1; i >= 0; i--) { + var symbol + if (binaryString[i] === closed) + symbol = String.fromCharCode(0x25CF) // closed hole + if (binaryString[i] === half_ver) + symbol = String.fromCharCode(0x25D1) // Half hole, covered on the side + if (binaryString[i] == half_hor) + symbol = String.fromCharCode(0x25D2) // Half hole, covered on the bottom + if (binaryString[i] == open_split) + symbol = String.fromCharCode(0x29B6) // Open Hole with line + // symbol = String.fromCharCode(0x25CB) + String.fromCharCode(0x20D2) + if (binaryString[i] == open) + symbol = String.fromCharCode(0x25CB) // Open Hole - var token = "$" + (i+1) - while (output.indexOf(token) !== -1) { - output = output.replace(token, symbol) - } - } + var token = "$" + (i+1) + while (output.indexOf(token) !== -1) { + output = output.replace(token, symbol) + } + } - var plusCount = parseInt(plusBit) - var plusSymbol = "+".repeat(plusCount) - if (plusCount === 0) { - plusSymbol = "" - } + //------------------------------------------------- + // Final validation + //------------------------------------------------- - //------------------------------------------------- - // Replace plus placeholder ($+) with number of plusses based on plus bit - //------------------------------------------------- + if (output.indexOf("$") !== -1) { + console.log("Warning: Unreplaced placeholders in:", output) + } - while (output.indexOf("$+") !== -1) { - output = output.replace("$+", plusSymbol) - } - - //------------------------------------------------- - // Final validation - //------------------------------------------------- - - if (output.indexOf("$") !== -1) { - console.log("Warning: Unreplaced placeholders in:", output) - } - - return output + return output } //--------------------------------------------------------- @@ -954,15 +661,13 @@ MuseScore { var text = newElement(Element.STAFF_TEXT) if (!dict[noteName]) { - text.text = "☒" + diagram = noteName + "\n☒" console.log("Note not in dictionary:", noteName) } else { var diagram = buildFingeringText(dict[noteName], userFormatString, noteName) - - text.text = "" + diagram + ""; - console.log("Note:", noteName, "Diagram:", diagram.replace(/\n/g, "\\n")) } + text.text = "" + diagram + ""; cursor.add(text) formatText(text) @@ -1041,7 +746,8 @@ MuseScore { // Build the full font stack dynamically function getFontStack() { - return "WhistleSymbols" + ", " + userFontFamily; + // return "WhistleSymbols" + ", " + userFontFamily; + return userFontFamily } @@ -1662,7 +1368,7 @@ MuseScore { } Label { - text: "Use placeholders like $1, $2, ..., for the holes, \n$+ for the plus indicator and $note for the note (e.g. G#5)" + text: "Use placeholders like $1, $2, ..., for the holes and $note for the note (e.g. G#5)" font.pixelSize: 10 color: textColor }