diff --git a/woodwind_tab_generator/Fingering.js b/woodwind_tab_generator/Fingering.js new file mode 100644 index 0000000..7882bec --- /dev/null +++ b/woodwind_tab_generator/Fingering.js @@ -0,0 +1,148 @@ +// +--------+ +// | COLORS | +// +--------+ +// You can change the color of the fingering chart here. +// Colors can be hex codes ("#123456") or names ("green") + +function getColor(note) { + const colorMap = { + C: "#ed1c24", + "C#": "#f37021", + D: "#f8931e", + "D#": "#ffc20e", + E: "#fff200", + F: "#bed630", + "F#": "#72bf44", + G: "#00a29d", + "G#": "#007dc5", + A: "#49479d", + "A#": "#8d5ca6", + B: "#c9277d", + } + return colorMap[note] || "gray" +} + +//--------------------------------------------------------- +// INSTRUMENTS +//--------------------------------------------------------- + +// Each woodwind has the form: +// { +// name: +// fingering: +// format: +// }, +// +// NAME: can be anything descriptive. +// +// FINGERING: +// Each note that can be played, then its holes. +// Holes are listed starting from the mouthpiece. +// You can use these symbols to represent whether the holes are covered: +// 0 = ○ +// o = ⦶ +// 1 = ● +// | = ◐ +// - = ◒ +// +// FORMAT: +// Decide how the fingering will be displayed. +// "$note" will be replaced with the name of the note, +// "$1" "$2" etc. will be replaced by the hole symbols, with $1 being closest to the mouthpiece. + + +const woodwinds = [ + { + name: "Soprano Recorder", + fingering: { + "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", + }, + format: +`$note + +$1 +— +$2 +$3 +$4 +— +$5 +$6 +$7 +$8` + }, + + { + "name": "Alto Recorder", + "fingering": { + "F4": "11111111", + "F#4":"1111111|", + "G4": "11111110", + "G#4":"111111|o", + "A4": "111111oo", + "A#4":"11110011", + "B4": "1111011o", + "C5": "111100oo", + "C#5":"111011|o", + "D5": "111000oo", + "D#5":"110110oo", + "E5": "110000oo", + "F5": "101000oo", + "F#5":"011000oo", + "G5": "001000oo", + "G#5":"0011111o", + "A5": "-11111oo", + "A#5":"-111101o", + "B5": "-11101oo", + "C6": "-11100oo", + "C#6":"-11010oo", + "D6": "-11000oo", + "D#6":"-110011|", + "E6": "-11011oo", + "F6": "-10011oo", + "F#6": "-1011011", + "G6": "-1011011", + "G#6": "-011011o", + }, + format: +`$note + +$1 +— +$2 +$3 +$4 +— +$5 +$6 +$7 +$8` + }, +]; diff --git a/woodwind_tab_generator/woodwind_tab_generator.qml b/woodwind_tab_generator/woodwind_tab_generator.qml index ae5f529..c17aeef 100644 --- a/woodwind_tab_generator/woodwind_tab_generator.qml +++ b/woodwind_tab_generator/woodwind_tab_generator.qml @@ -29,6 +29,8 @@ import QtQuick.Controls 2.15 import QtQuick.Layouts 1.15 import MuseScore 3.0 +import "Fingering.js" as Fingering + MuseScore { id: mscore version: "4.4" @@ -68,7 +70,7 @@ MuseScore { property int userJustification: 1 // 0=left,1=center,2=right property real userOffsetY: 1.7 property real userLineSpacing: 0.5 - property string userFormatString: "" // mirrors current profile's formatString + property string userFormatString: "" // mirrors current profile's format property string userFontFamily: "Menlo, Consolas, Liberation Mono, Courier New, monospace" property bool userTransposed: false @@ -83,73 +85,6 @@ MuseScore { property var history: 0 property var modified: false - //--------------------------------------------------------- - // FINGERING DICTIONARY - //--------------------------------------------------------- - - //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# - // - - -// ○ = 0 -// ● = 1 -// ◐ = | -// ◐ = - -// ○ = o - -//Example fingering specification for G#5 on a high D whistle -// |--------------------------------------------- -//windway -> | | | ● ● ◐ ○ ○ ○ | -// |--------------------------------------------- -// "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. - - //Whistle definitions - property var defaultProfiles: [ - { - "name": "Soprano Recorder", - "fingeringDict": { - "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" - }, - ] - // Runtime copy of profiles (loaded/saved via preferences) property var profiles: [] property int currentProfileIndex: 0 @@ -160,16 +95,16 @@ MuseScore { function getCurrentFingeringDict() { - return profiles.length > 0 ? profiles[currentProfileIndex].fingeringDict : {} + return profiles.length > 0 ? profiles[currentProfileIndex].fingering : {} } function getCurrentFormatString() { - return profiles.length > 0 ? profiles[currentProfileIndex].formatString : "" + return profiles.length > 0 ? profiles[currentProfileIndex].format : "" } function setCurrentFormatString(newFormat) { if (profiles.length > 0) { - profiles[currentProfileIndex].formatString = newFormat + profiles[currentProfileIndex].format = newFormat userFormatString = newFormat } } @@ -234,8 +169,8 @@ MuseScore { // Save profiles as JSON string var profilesForStorage = profiles.map(p => ({ name: p.name, - fingeringDict: p.fingeringDict, - formatString: p.formatString + fingering: p.fingering, + format: p.format })) pluginSettings.storedProfiles = JSON.stringify(profilesForStorage) pluginSettings.storedCurrentProfile = currentProfileIndex @@ -257,24 +192,24 @@ MuseScore { var loaded = JSON.parse(profilesStr) profiles = loaded.map(p => ({ name: p.name, - fingeringDict: p.fingeringDict, - formatString: p.formatString + fingering: p.fingering, + format: p.format })) } catch (e) { console.log("Failed to parse saved profiles, using defaults") error("Failed to parse saved profiles, using defaults") - profiles = defaultProfiles.map(p => ({ + profiles = Fingering.woodwinds.map(p => ({ name: p.name, - fingeringDict: JSON.parse(JSON.stringify(p.fingeringDict)), - formatString: p.formatString + fingering: JSON.parse(JSON.stringify(p.fingering)), + format: p.format })) } } else { - profiles = defaultProfiles.map(p => ({ + profiles = Fingering.woodwinds.map(p => ({ name: p.name, - fingeringDict: JSON.parse(JSON.stringify(p.fingeringDict)), - formatString: p.formatString + fingering: JSON.parse(JSON.stringify(p.fingering)), + format: p.format })) } @@ -304,11 +239,11 @@ MuseScore { function resetToDefaults() { getHistory().begin() // if you have undo/redo - // 1. Restore profiles from defaultProfiles - profiles = defaultProfiles.map(p => ({ + // 1. Restore profiles from woodwinds + profiles = Fingering.woodwinds.map(p => ({ name: p.name, - fingeringDict: JSON.parse(JSON.stringify(p.fingeringDict)), - formatString: p.formatString + fingering: JSON.parse(JSON.stringify(p.fingering)), + format: p.format })) currentProfileIndex = 0 @@ -335,8 +270,8 @@ MuseScore { // 4. Persist all changes to Settings pluginSettings.storedProfiles = JSON.stringify(profiles.map(p => ({ name: p.name, - fingeringDict: p.fingeringDict, - formatString: p.formatString + fingering: p.fingering, + format: p.format }))) pluginSettings.storedCurrentProfile = currentProfileIndex pluginSettings.storedFontSize = userFontSize @@ -405,7 +340,7 @@ MuseScore { //--------------------------------------------------------- //Build fingering text using binary representation of the fingering for a note. - function buildFingeringText(binaryString, formatString, noteName) { + function buildFingeringText(binaryString, format, noteName) { const open = "0" const closed = "1" const half_ver = "|" @@ -414,7 +349,7 @@ MuseScore { if (!binaryString || binaryString.length < 1) return "Invalid fingering pattern. Length is wrong" - if (!formatString || typeof formatString !== "string") + if (!format || typeof format !== "string") return "ERR" var holeCount = binaryString.length @@ -436,7 +371,7 @@ MuseScore { // Clone format string //------------------------------------------------- - var output = formatString + var output = format //------------------------------------------------- // Replace note placeholder ($note) with note name @@ -487,24 +422,6 @@ MuseScore { // APPLY TEXT FORMATTING //--------------------------------------------------------- - function getColor(noteName) { - const note = noteName.slice(0, -1) - const colorMap = { - C: "#ed1c24", - "C#": "#f37021", - D: "#f8931e", - "D#": "#ffc20e", - E: "#fff200", - F: "#bed630", - "F#": "#72bf44", - G: "#00a29d", - "G#": "#007dc5", - A: "#49479d", - "A#": "#8d5ca6", - B: "#c9277d", - } - return colorMap[note] || "gray" - } function formatText(text) { console.log("Formatting text - setting fontSize to:", userFontSize) @@ -671,7 +588,8 @@ MuseScore { cursor.add(text) formatText(text) - text.color = getColor(noteName) + const note = noteName.slice(0, -1) + text.color = Fingering.getColor(note) // Verify the text object has the properties set console.log("Text fontSize after format:", text.fontSize) @@ -839,11 +757,11 @@ MuseScore { var oldProfileFormat = getCurrentFormatString() getHistory().add( function() { - // Undo: restore both userFormatString and the profile's formatString + // Undo: restore both userFormatString and the profile's format userFormatString = oldFormat formatInput.text = oldFormat if (profiles.length > 0) { - profiles[currentProfileIndex].formatString = oldProfileFormat + profiles[currentProfileIndex].format = oldProfileFormat } }, function() { @@ -851,7 +769,7 @@ MuseScore { userFormatString = format formatInput.text = format if (profiles.length > 0) { - profiles[currentProfileIndex].formatString = format + profiles[currentProfileIndex].format = format } }, "format string" @@ -871,10 +789,10 @@ MuseScore { // Event handlers (call setters and save) //--------------------------------------------------------- - function formatStringChanged(formatString) { + function formatChanged(format) { getHistory().begin() setModified(true) - setUserFormatString(formatString) + setUserFormatString(format) getHistory().end() saveSettings() } @@ -928,7 +846,7 @@ MuseScore { function profileChanged(index) { if (index < 0 || index >= profiles.length) return currentProfileIndex = index - userFormatString = profiles[index].formatString + userFormatString = profiles[index].format formatInput.text = userFormatString updatePreview() saveSettings() // immediately save profile change @@ -1358,7 +1276,7 @@ MuseScore { } property var previousText: userFormatString onEditingFinished: { - formatStringChanged(formatInput.text) + formatChanged(formatInput.text) updatePreview() } onTextChanged: {