import QtQuick 2.0
import QtQuick.Controls 1.3
import QtQuick.Controls.Styles 1.3
import MuseScore 3.0
MuseScore {
version: "1.2"
description: "Retune selection to 24-TET, or whole score if nothing selected."
menuPath: "Plugins.Notes.Retune Quarter-TET"
pluginType: "dialog"
width: 600
height: 480
// WARNING! This doesn't validate the accidental code!
property variant customKeySigRegex: /\.(.*)\.(.*)\.(.*)\.(.*)\.(.*)\.(.*)\.(.*)/g
property variant centOffsets: {
'a': {
'-5': 0,
'-4': -150,
'-3': 0,
'-2': 0,
'-1': -50,
0: 0,
1: 50,
2: 0,
3: 50 * 8 - 200,
4: 150,
5: 0
},
'b': {
'-5': 0,
'-4': -150,
'-3': 0,
'-2': 0,
'-1': -50,
0: 0,
1: 50,
2: 0,
3: 50 * 8 - 200,
4: 150,
5: 0
},
'c': {
'-5': 0,
'-4': -150,
'-3': 0,
'-2': 0,
'-1': -50,
0: 0,
1: 50,
2: 0,
3: 50 * 8 - 200,
4: 150,
5: 0
},
'd': {
'-5': 0,
'-4': -150,
'-3': 0,
'-2': 0,
'-1': -50,
0: 0,
1: 50,
2: 0,
3: 50 * 8 - 200,
4: 150,
5: 0
},
'e': {
'-5': 0,
'-4': -150,
'-3': 0,
'-2': 0,
'-1': -50,
0: 0,
1: 50,
2: 0,
3: 50 * 8 - 200,
4: 150,
5: 0
},
'f': {
'-5': 0,
'-4': -150,
'-3': 0,
'-2': 0,
'-1': -50,
0: 0,
1: 50,
2: 0,
3: 50 * 8 - 200,
4: 150,
5: 0
},
'g': {
'-5': 0,
'-4': -150,
'-3': 0,
'-2': 0,
'-1': -50,
0: 0,
1: 50,
2: 0,
3: 50 * 8 - 200,
4: 150,
5: 0
}
}
Rectangle {
color: "white"
anchors.fill: parent
Text {
text: "C"
x: 50
y: 15
}
TextField {
id: c
x: 80
y: 10
width: 80
height: 30
}
Text {
text: "D"
x: 50
y: 55
}
TextField {
id: d
x: 80
y: 50
width: 80
height: 30
}
Text {
text: "E"
x: 50
y: 95
}
TextField {
id: e
x: 80
y: 90
width: 80
height: 30
}
Text {
text: "F"
x: 50
y: 135
}
TextField {
id: f
x: 80
y: 130
width: 80
height: 30
}
Text {
text: "G"
x: 50
y: 175
}
TextField {
id: g
x: 80
y: 170
width: 80
height: 30
}
Text {
text: "A"
x: 50
y: 215
}
TextField {
id: a
x: 80
y: 210
width: 80
height: 30
}
Text {
text: "B"
x: 50
y: 255
}
TextField {
id: b
x: 80
y: 250
width: 80
height: 30
}
Text {
text: 'Usage guide'
x: 250
y: 230
onLinkActivated: Qt.openUrlExternally(link)
}
Button {
x: 250
y: 400
width: 140
height: 60
text: "Retune!"
onClicked: {
var parms = {};
/*
key signature as denoted by the TextFields.
THIS SHOULD BE READONLY!
*/
parms.keySig = {
'c': convertAccidentalToSteps(c.text),
'd': convertAccidentalToSteps(d.text),
'e': convertAccidentalToSteps(e.text),
'f': convertAccidentalToSteps(f.text),
'g': convertAccidentalToSteps(g.text),
'a': convertAccidentalToSteps(a.text),
'b': convertAccidentalToSteps(b.text),
};
/*
Used for StaffText declared custom key signature.
No worries about handling system text vs staff text as
the annotation automatically applies appropriately.
Will be reset to the original TextField denoted key signature
at the start of each staff, although using both TextField
and StaffText(22)/SystemText(21) methods of custom key sig
entry is STRONGLY DISCOURAGED due to extreme unpredicatability.
*/
parms.currKeySig = parms.keySig
parms.accidentals = {};
applyToNotesInSelection(tuneNote, parms);
Qt.quit();
}
}
}
function convertAccidentalToSteps(acc) {
switch(acc.trim()) {
case 'bb':
return -5;
case 'db':
return -4;
case 'bv':
return -3;
case 'b':
return -2;
case 'v':
case 'd':
return -1;
case '':
return 0;
case '^':
case '+':
return 1;
case '#':
return 2;
case '#^':
return 3;
case '#+':
return 4;
case 'x':
return 5;
default:
return 0;
}
}
function convertAccidentalToStepsOrNull(acc) {
switch(acc.trim()) {
case 'bb':
return -5;
case 'db':
return -4;
case 'bv':
return -3;
case 'b':
return -2;
case 'v':
case 'd':
return -1;
case '':
return 0;
case '^':
case '+':
return 1;
case '#':
return 2;
case '#^':
return 3;
case '#+':
return 4;
case 'x':
return 5;
default:
return null;
}
}
// Takes in annotations[].text and returns either
// a key signature object if str is a valid custom key sig code or null.
//
// Valid key sig code is denoted as such:
// .c.d.e.f.g.a.b
// where identifiers c thru b denote a valid accidental code of which
// will apply to the respective notes.
//
// For example, this is F-down major: .v.v.v.v.v.v.bv
//
// whitespace can be placed between dots and accidentals for readability.
//
// For the natural accidental, blank or whitespace will both work.
//
// Assign the key signature object to the parms.currKeySig field!
function scanCustomKeySig(str) {
str = str.trim();
var keySig = {};
var res = str.match(customKeySigRegex);
if (res === null)
return null;
var acc = convertAccidentalToStepsOrNull(res[1].trim());
if (acc !== null)
keySig.c = acc;
else
return null;
acc = convertAccidentalToStepsOrNull(res[2].trim());
if (acc !== null)
keySig.d = acc;
else
return null;
acc = convertAccidentalToStepsOrNull(res[3].trim());
if (acc !== null)
keySig.e = acc;
else
return null;
acc = convertAccidentalToStepsOrNull(res[4].trim());
if (acc !== null)
keySig.f = acc;
else
return null;
acc = convertAccidentalToStepsOrNull(res[5].trim());
if (acc !== null)
keySig.g = acc;
else
return null;
acc = convertAccidentalToStepsOrNull(res[6].trim());
if (acc !== null)
keySig.a = acc;
else
return null;
acc = convertAccidentalToStepsOrNull(res[7].trim());
if (acc !== null)
keySig.b = acc;
else
return null;
return keySig;
}
// Apply the given function to all notes in selection
// or, if nothing is selected, in the entire score
function applyToNotesInSelection(func, parms) {
var cursor = curScore.newCursor();
cursor.rewind(1);
var startStaff;
var endStaff;
var endTick;
var fullScore = false;
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)
// -------------- Actual thing here -----------------------
for (var staff = startStaff; staff <= endStaff; staff++) {
for (var voice = 0; voice < 4; voice++) {
cursor.rewind(1); // sets voice to 0
cursor.voice = voice; //voice has to be set after goTo
cursor.staffIdx = staff;
if (fullScore)
cursor.rewind(0) // if no selection, beginning of score
var measureCount = 0;
// After every track/voice, reset the currKeySig back to the original keySig
parms.currKeySig = parms.keySig;
console.log("currKeySig reset");
// Loop elements of a voice
while (cursor.segment && (fullScore || cursor.tick < endTick)) {
// Reset accidentals if new measure.
if (cursor.segment.tick == cursor.measure.firstSegment.tick) {
parms.accidentals = {};
measureCount ++;
console.log("Reset accidentals - " + measureCount);
}
// Check for StaffText key signature changes.
for (var i = 0, annotation = cursor.segment.annotations[i]; i < cursor.segment.annotations.length; i++) {
var maybeKeySig = scanCustomKeySig(annotation.text);
if (maybeKeySig !== null) {
parms.currKeySig = maybeKeySig;
console.log("detected new customer keySig: " + annotation.text);
}
}
if (cursor.element) {
if (cursor.element.type == Element.CHORD) {
var graceChords = cursor.element.graceNotes;
for (var i = 0; i < graceChords.length; i++) {
// iterate through all grace chords
var notes = graceChords[i].notes;
for (var j = 0; j < notes.length; j++)
func(notes[j], parms);
}
var notes = cursor.element.notes;
for (var i = 0; i < notes.length; i++) {
var note = notes[i];
func(note, parms);
}
}
}
cursor.next();
}
}
}
}
function tuneNote(note, parms) {
var tpc = note.tpc;
var acc = note.accidental;
// If tpc is non-natural, there's no need to go through additional steps,
// since accidentals and key sig are already taken into consideration
// to produce a non-screw-up tpc.
// However, if tpc is natural, it needs to be checked against acc and
// the key signature to truly determine what note it is.
/*
^ #v v b^ -> 1 diesis
# b -> 2 diesis
#^ bv -> 3 diesis
#+ db -> 4 diesis
x bb -> 5 diesis
31-TET | TPC | ACC
C | 14 (C) | NONE = C
| 19 (B) | SHARP_ARROW_UP = B#^
| 2 (Dbb) | FLAT2 = Dbb
------------------------------------------------
C^ | 14 (C) | NATURAL_ARROW_UP = C^
| 14 (C) | SHARP_ARROW_DOWN = C#v
| 19 (B) | SHARP_SLASH4 = B#+
| 16 (D) | MIRRORED_FLAT2 = Ddb
------------------------------------------------
C# | 21 (C#) | SHARP = C#
| 33 (Bx) | SHARP2 = Bx
| 16 (D) | FLAT_ARROW_DOWN = Dbv
------------------------------------------------
Db | 9 (Db) | FLAT = Db
| 14 (C) | SHARP_ARROW_UP = C#^
------------------------------------------------
Dv | 16 (D) | NATURAL_ARROW_DOWN = Dv
| 16 (D) | FLAT_ARROW_UP = Db^
| 14 (C) | SHARP_SLASH4 = C#+
------------------------------------------------
D | 16 (D) | NONE = D
| 28 (Cx) | SHARP2 = Cx
| 4 (Ebb) | FLAT2 = Ebb
------------------------------------------------
D^ | 16 (D) | NATURAL_ARROW_UP = D^
| 16 (D) | SHARP_ARROW_DOWN = D#v
| 18 (E) | MIRRORED_FLAT2 = Edb
------------------------------------------------
D# | 23 (D#) | SHARP = D#
| 18 (E) | FLAT_ARROW_DOWN = Ebv
------------------------------------------------
Eb | 11 (Eb) | FLAT = Eb
| 16 (D) | SHARP_ARROW_UP = D#^
| -1 (Fbb) | FLAT2 = Fbb
------------------------------------------------
Ev | 18 (E) | NATURAL_ARROW_DOWN = Ev
| 18 (E) | FLAT_ARROW_UP = Eb^
| 16 (D) | SHARP_SLASH4 = D#+
| 13 (F) | MIRRORED_FLAT2 = Fdb
------------------------------------------------
E | 18 (E) | NONE = E
| 30 (Dx) | SHARP2 = Dx
| 13 (F) | FLAT_ARROW_DOWN = Fbv
------------------------------------------------
E^ | 18 (E) | NATURAL_ARROW_UP = E^
| 18 (E) | SHARP_ARROW_DOWN = E#v
| 6 (Fb) | FLAT = Fb
------------------------------------------------
Fv | 13 (F) | NATURAL_ARROW_DOWN = Fv
| 13 (F) | FLAT_ARROW_UP = Fb^
| 25 (E#) | SHARP = E#
------------------------------------------------
F | 13 (F) | NONE = F
| 18 (E) | SHARP_ARROW_UP = E#^
| 1 (Gbb) | FLAT2 = Gbb
------------------------------------------------
F^ | 13 (F) | NATURAL_ARROW_UP = F^
| 13 (F) | SHARP_ARROW_DOWN = F#v
| 18 (E) | SHARP_SLASH4 = E#+
| 15 (G) | MIRRORED_FLAT2 = Gdb
------------------------------------------------
F# | 20 (F#) | SHARP = F#
| 32 (Ex) | SHARP2 = Ex
| 15 (G) | FLAT_ARROW_DOWN = Gbv
------------------------------------------------
Gb | 8 (Gb) | FLAT = Gb
| 13 (F) | SHARP_ARROW_UP = F#^
------------------------------------------------
Gv | 15 (G) | NATURAL_ARROW_DOWN = Gv
| 15 (G) | FLAT_ARROW_UP = Gb^
| 13 (F) | SHARP_SLASH4 = F#+
------------------------------------------------
G | 15 (G) | NONE = G
| 27 (Fx) | SHARP2 = Fx
| 3 (Abb) | FLAT2 = Abb
------------------------------------------------
G^ | 15 (G) | NATURAL_ARROW_UP = G^
| 15 (G) | SHARP_ARROW_DOWN = G#v
| 17 (A) | MIRRORED_FLAT2 = Adb
------------------------------------------------
G# | 22 (G#) | SHARP = G#
| 17 (A) | FLAT_ARROW_DOWN = Abv
------------------------------------------------
Ab | 10 (Ab) | FLAT = Ab
| 15 (G) | SHARP_ARROW_UP = G#^
------------------------------------------------
Av | 17 (A) | NATURAL_ARROW_DOWN = Av
| 17 (A) | FLAT_ARROW_UP = Ab^
| 15 (G) | SHARP_SLASH4 = G#+
------------------------------------------------
A | 17 (A) | NONE = A
| 29 (Gx) | SHARP2 = Gx
| 5 (Bbb) | FLAT2 = Bbb
------------------------------------------------
A^ | 17 (A) | NATURAL_ARROW_UP = A^
| 17 (A) | SHARP_ARROW_DOWN = A#v
| 19 (B) | MIRRORED_FLAT2 = Bdb
------------------------------------------------
A# | 24 (A#) | SHARP = A#
| 19 (B) | FLAT_ARROW_DOWN = Bbv
------------------------------------------------
Bb | 12 (Bb) | FLAT = Bb
| 17 (A) | SHARP_ARROW_UP = A#^
| 0 (Cbb) | FLAT2 = Cbb
------------------------------------------------
Bv | 19 (B) | NATURAL_ARROW_DOWN = Bv
| 19 (B) | FLAT_ARROW_UP = Bb^
| 17 (A) | SHARP_SLASH4 = A#+
| 14 (C) | MIRRORED_FLAT2 = Cdb
------------------------------------------------
B | 19 (B) | NONE = B
| 31 (Ax) | SHARP2 = Ax
| 14 (C) | FLAT_ARROW_DOWN = Cbv
------------------------------------------------
B^ | 19 (B) | NATURAL_ARROW_UP = B^
| 19 (B) | SHARP_ARROW_DOWN = B#v
| 7 (Cb) | FLAT = Cb
------------------------------------------------
Cv | 14 (C) | NATURAL_ARROW_DOWN = Cv
| 14 (C) | FLAT_ARROW_UP = Cb^
| 26 (B#) | SHARP = B#
*/
switch(tpc) {
case -1: //Fbb
note.tuning = centOffsets['f'][-5];
return;
case 0: //Cbb
note.tuning = centOffsets['c'][-5];
return;
case 1: //Gbb
note.tuning = centOffsets['g'][-5];
return;
case 2: //Dbb
note.tuning = centOffsets['d'][-5];
return;
case 3: //Abb
note.tuning = centOffsets['a'][-5];
return;
case 4: //Ebb
note.tuning = centOffsets['e'][-5];
return;
case 5: //Bbb
note.tuning = centOffsets['b'][-5];
return;
case 6: //Fb
note.tuning = centOffsets['f'][-2];
return;
case 7: //Cb
note.tuning = centOffsets['c'][-2];
return;
case 8: //Gb
note.tuning = centOffsets['g'][-2];
return;
case 9: //Db
note.tuning = centOffsets['d'][-2];
return;
case 10: //Ab
note.tuning = centOffsets['a'][-2];
return;
case 11: //Eb
note.tuning = centOffsets['e'][-2];
return;
case 12: //Bb
note.tuning = centOffsets['b'][-2];
return;
case 20: //F#
note.tuning = centOffsets['f'][2];
return;
case 21: //C#
note.tuning = centOffsets['c'][2];
return;
case 22: //G#
note.tuning = centOffsets['g'][2];
return;
case 23: //D#
note.tuning = centOffsets['d'][2];
return;
case 24: //A#
note.tuning = centOffsets['a'][2];
return;
case 25: //E#
note.tuning = centOffsets['e'][2];
return;
case 26: //B#
note.tuning = centOffsets['b'][2];
return;
case 27: //Fx
note.tuning = centOffsets['f'][5];
return;
case 28: //Cx
note.tuning = centOffsets['c'][5];
return;
case 29: //Gx
note.tuning = centOffsets['g'][5];
return;
case 30: //Dx
note.tuning = centOffsets['d'][5];
return;
case 31: //Ax
note.tuning = centOffsets['a'][5];
return;
case 32: //Ex
note.tuning = centOffsets['e'][5];
return;
case 33: //Bx
note.tuning = centOffsets['b'][5];
return;
}
// in the event that tpc is considered natural by
// MuseScore's playback, it would mean that it is
// either a natural note, or a microtonal accidental.
var baseNote;
switch(tpc) {
case 13:
baseNote = 'f';
break;
case 14:
baseNote = 'c';
break;
case 15:
baseNote = 'g';
break;
case 16:
baseNote = 'd';
break;
case 17:
baseNote = 'a';
break;
case 18:
baseNote = 'e';
break;
case 19:
baseNote = 'b';
break;
}
//NOTE: Only special accidentals need to be remembered.
if (note.accidental) {
console.log('Note: ' + baseNote + ', Line: ' + note.line + ', Special Accidental: ' + note.userAccidental);
if (note.accidental.accType == Accidental.MIRRORED_FLAT2)
parms.accidentals[note.line] = -4;
else if (note.accidental.accType == Accidental.FLAT_ARROW_DOWN)
parms.accidentals[note.line] = -3;
else if (note.accidental.accType == Accidental.NATURAL_ARROW_DOWN ||
note.accidental.accType == Accidental.MIRRORED_FLAT)
parms.accidentals[note.line] = -1;
else if (note.accidental.accType == Accidental.NATURAL)
parms.accidentals[note.line] = 0;
else if (note.accidental.accType == Accidental.NATURAL_ARROW_UP ||
note.accidental.accType == Accidental.SHARP_SLASH)
parms.accidentals[note.line] = 1;
else if (note.accidental.accType == Accidental.SHARP_ARROW_UP)
parms.accidentals[note.line] = 3;
else if (note.accidental.accType == Accidental.SHARP_SLASH4)
parms.accidentals[note.line] = 4;
}
// Check for prev accidentals first
var stepsFromBaseNote;
if (parms.accidentals[note.line] !== undefined)
stepsFromBaseNote = parms.accidentals[note.line];
else // No prev accidentals. Use key signature instead.
stepsFromBaseNote = parms.currKeySig[baseNote];
console.log("Base Note: " + baseNote + ", diesis: " + stepsFromBaseNote);
note.tuning = centOffsets[baseNote][stepsFromBaseNote];
return;
}
onRun: {
console.log("hello 31tet");
if (typeof curScore === 'undefined')
Qt.quit();
}
}