MediaWiki:Gadget-TciTest.js: Difference between revisions
Appearance
No edit summary |
No edit summary Tag: Reverted |
||
Line 1: | Line 1: | ||
// Gadget-TciTest.js — TCI Calculator (LIVE | // Gadget-TciTest.js — TCI Calculator (LIVE DEBUG — logging ABC matches) | ||
(function ($, mw, OO) { | (function ($, mw, OO) { | ||
"use strict"; | "use strict"; | ||
Line 58: | Line 58: | ||
function parseABC(abcText, octaveMode) { | function parseABC(abcText, octaveMode) { | ||
console.log("📄 TEXT RECEIVED BY PARSER:\n", abcText); | |||
const lines = abcText.split(/\r?\n/); | const lines = abcText.split(/\r?\n/); | ||
const keyLine = lines.find(line => line.startsWith('K:')) || 'K:D'; | const keyLine = lines.find(line => line.startsWith('K:')) || 'K:D'; | ||
Line 71: | Line 73: | ||
const tokenRegex = /((=|\^|_)?[a-gA-GzZ][',/]*)(\d*\/?\d*)/g; | const tokenRegex = /((=|\^|_)?[a-gA-GzZ][',/]*)(\d*\/?\d*)/g; | ||
const allMatches = [...abcText.matchAll(tokenRegex)]; | |||
console.log("🔍 Raw matches from ABC:", allMatches.map(m => m[0])); | |||
let match; | let match; | ||
let beatAcc = 0, beatAlt = 0; | let beatAcc = 0, beatAlt = 0; | ||
Line 130: | Line 135: | ||
}; | }; | ||
} | } | ||
TCICalculatorTestDialog.prototype.getActionProcess = function (action) { | TCICalculatorTestDialog.prototype.getActionProcess = function (action) { |
Revision as of 18:07, 10 April 2025
// Gadget-TciTest.js — TCI Calculator (LIVE DEBUG — logging ABC matches)
(function ($, mw, OO) {
"use strict";
mw.loader.using(["oojs-ui-core", "oojs-ui-widgets", "oojs-ui-windows"]).done(function () {
function TCICalculatorTestDialog(config) {
TCICalculatorTestDialog.super.call(this, config);
}
OO.inheritClass(TCICalculatorTestDialog, OO.ui.ProcessDialog);
TCICalculatorTestDialog.static.name = 'tciCalculatorTestDialog';
TCICalculatorTestDialog.static.title = 'Theme Code Index Calculator — LIVE VERSION';
TCICalculatorTestDialog.static.actions = [
{ action: 'cancel', label: 'Cancel', flags: 'safe' },
{ action: 'calculate', label: 'Calculate TCI', flags: ['primary', 'progressive'] }
];
const majorScales = {
C: ['C', 'D', 'E', 'F', 'G', 'A', 'B'],
G: ['G', 'A', 'B', 'C', 'D', 'E', 'F#'],
D: ['D', 'E', 'F#', 'G', 'A', 'B', 'C#'],
A: ['A', 'B', 'C#', 'D', 'E', 'F#', 'G#'],
E: ['E', 'F#', 'G#', 'A', 'B', 'C#', 'D#'],
B: ['B', 'C#', 'D#', 'E', 'F#', 'G#', 'A#'],
F: ['F', 'G', 'A', 'Bb', 'C', 'D', 'E']
};
const minorScales = {
Am: ['A', 'B', 'C', 'D', 'E', 'F', 'G'],
Em: ['E', 'F#', 'G', 'A', 'B', 'C', 'D'],
Dm: ['D', 'E', 'F', 'G', 'A', 'Bb', 'C'],
Bm: ['B', 'C#', 'D', 'E', 'F#', 'G', 'A'],
Gm: ['G', 'A', 'Bb', 'C', 'D', 'Eb', 'F'],
Fsm: ['F#', 'G#', 'A', 'B', 'C#', 'D', 'E']
};
const parallelMajors = {
Em: 'E', Am: 'A', Dm: 'D', Bm: 'B', Gm: 'G', Fsm: 'F#'
};
function normalizeKey(key) {
return key.replace(/[^a-zA-Z#bm]/g, '').trim();
}
function getScaleForKey(keyRaw) {
const key = normalizeKey(keyRaw);
return majorScales[key] || minorScales[key] || majorScales['D'];
}
function getComparativeMajorScale(keyRaw) {
const key = normalizeKey(keyRaw);
return majorScales[parallelMajors[key]] || majorScales['D'];
}
function normalizeNote(note) {
return note.replace(/=/g, '').replace(/\^/g, '#').replace(/_/g, 'b');
}
function parseABC(abcText, octaveMode) {
console.log("📄 TEXT RECEIVED BY PARSER:\n", abcText);
const lines = abcText.split(/\r?\n/);
const keyLine = lines.find(line => line.startsWith('K:')) || 'K:D';
const lengthLine = lines.find(line => line.startsWith('L:')) || 'L:1/8';
const key = keyLine.split(':')[1].trim();
const [lNum, lDen] = lengthLine.split(':')[1].split('/').map(Number);
const beatDuration = lNum / lDen;
const scale = getScaleForKey(key);
const refScale = getComparativeMajorScale(key) || scale;
const beats4 = [], beats2 = [], debug4 = [], debug2 = [];
const tokenRegex = /((=|\^|_)?[a-gA-GzZ][',/]*)(\d*\/?\d*)/g;
const allMatches = [...abcText.matchAll(tokenRegex)];
console.log("🔍 Raw matches from ABC:", allMatches.map(m => m[0]));
let match;
let beatAcc = 0, beatAlt = 0;
function parseDuration(str) {
if (!str) return beatDuration;
if (str.includes('/')) {
const [n, d] = str.split('/').map(Number);
return n / d;
}
return parseFloat(str);
}
function code(note) {
const baseRaw = normalizeNote(note.replace(/[',0-9\/]/g, ''));
const base = baseRaw.toUpperCase();
const refIdx = refScale.findIndex(n => n.replace(/[#b]/g, '') === base);
const idxInMinor = scale.findIndex(n => n.replace(/[#b]/g, '') === base);
let c = refIdx !== -1 ? (refIdx + 1).toString() : '?';
if (refIdx !== -1 && (idxInMinor === -1 || scale[idxInMinor] !== refScale[refIdx])) {
if (refScale[refIdx].includes('#') && (!scale[idxInMinor] || !scale[idxInMinor].includes('#'))) c += 'b';
else if (refScale[refIdx].includes('b') && (!scale[idxInMinor] || !scale[idxInMinor].includes('b'))) c += '#';
}
const isHigh = /[a-g]/.test(note);
const isLow = /,/.test(note);
if (octaveMode === 'shiftUp') return c + 'H';
if (octaveMode === 'shiftDown') return c + 'L';
if (octaveMode === 'visual') return isHigh ? c + 'H' : isLow ? c + 'L' : c;
if (octaveMode === 'standard') return isHigh ? c + 'H' : c;
return c;
}
while ((match = tokenRegex.exec(abcText)) !== null && (beatAcc < 8 || beatAlt < 4)) {
const [full, note, , durStr] = match;
const dur = parseDuration(durStr || `${lNum}/${lDen}`);
const val = /z/i.test(note) ? '0' : code(note);
const plain = note.replace(/\d.*$/, '');
console.log(`NOTE: ${plain}, duration: ${dur}`);
if (beatAcc < 8) {
beats4.push(val);
debug4.push(`B${beatAcc + 1}: ${plain} → ${val}`);
beatAcc++;
}
if (beatAlt < 4) {
beats2.push(val);
debug2.push(`B${beatAlt + 1}: ${plain} → ${val}`);
beatAlt++;
}
}
return {
normal: beats4.join(' '),
dual: beats2.join(' '),
debug: '4-beat mode:\n' + debug4.join('\n') + '\n\n2-beat mode:\n' + debug2.join('\n')
};
}
TCICalculatorTestDialog.prototype.getActionProcess = function (action) {
if (action === 'calculate') {
return new OO.ui.Process(() => {
const abc = this.abcInput.getValue();
const mode = this.octaveChoice.getValue();
console.log('📥 ABC from textarea:\n', abc);
const result = parseABC(abc, mode);
this.resultOutput.setValue(
'Standard (4 beats): ' + result.normal +
'\nAlternate (2 beats): ' + result.dual +
'\n\n' + result.debug
);
});
} else if (action === 'cancel') {
return new OO.ui.Process(() => this.close({ action: action }));
}
return TCICalculatorTestDialog.super.prototype.getActionProcess.call(this, action);
};
function openTCICalculatorTestDialog() {
const windowManager = new OO.ui.WindowManager();
$(document.body).append(windowManager.$element);
const dialog = new TCICalculatorTestDialog();
windowManager.addWindows([dialog]);
windowManager.openWindow(dialog);
}
mw.util.addPortletLink('p-cactions', '#', '🎼 TCI Test', 'ca-tcitest', 'Open TCI test');
$(document).on('click', '#ca-tcitest', function (e) {
e.preventDefault();
openTCICalculatorTestDialog();
});
});
})(jQuery, mediaWiki, OO);