MediaWiki:Gadget-Tci.js
Appearance
Note: After publishing, you may have to bypass your browser's cache to see the changes.
- Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (â-R on a Mac)
- Google Chrome: Press Ctrl-Shift-R (â-Shift-R on a Mac)
- Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5.
(function ($, mw, OO) {
"use strict";
// Carica i moduli OOUI necessari
mw.loader.using(["oojs-ui-core", "oojs-ui-widgets", "oojs-ui-windows"]).done(function () {
// Mappa per conversione tra note e semitoni (usando notazione con #)
const noteSemitones = {
"C": 0, "C#": 1, "D": 2, "D#": 3, "E": 4, "F": 5,
"F#": 6, "G": 7, "G#": 8, "A": 9, "A#": 10, "B": 11
};
// Dato un valore intero (0-11), restituisce la nota in forma di stringa, sempre con # (no flat)
function semitoneToNote(s) {
const notesSharp = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"];
return notesSharp[s % 12];
}
// Costruisce la scala maggiore (Ionian) a partire da una nota di base.
function buildRelativeScale(manualKey) {
let key = manualKey.trim();
key = key.replace(/[,']/g, "");
let keyUpper = key.toUpperCase();
let baseSemitone = noteSemitones[keyUpper];
if (baseSemitone === undefined) {
console.log("buildRelativeScale - Key non riconosciuta:", manualKey);
return null;
}
let intervals = [0, 2, 4, 5, 7, 9, 11];
return intervals.map(i => semitoneToNote(baseSemitone + i));
}
const majorScales = { /* ... come prima ... */ };
const minorScales = { /* ... come prima ... */ };
const parallelMajors = { /* ... come prima ... */ };
const defaultOctaveMapping = { /* ... come prima ... */ };
function cleanABC(text) {
return text.replace(/\([^)]*\)/g, '')
.replace(/>/g, '')
.replace(/</g, '');
}
function normalizeKey(key) {
return key.replace(/[^A-Ga-g#bm]/g, '').trim();
}
function getScaleForKey(keyRaw) {
let key = normalizeKey(keyRaw);
if (majorScales[key]) return majorScales[key];
if (minorScales[key]) return minorScales[key];
return majorScales['D'];
}
function parseDuration(durStr, defaultDuration) {
if (!durStr) return defaultDuration;
if (durStr.includes('/')) {
let [n, d] = durStr.split('/').map(x => x === '' ? 1 : parseFloat(x));
return (n / d) * defaultDuration;
}
return parseFloat(durStr) * defaultDuration;
}
function convertNoteToTCICode(noteToken, scale, keyRaw, octaveMode, manualMode, manualKey, manualScale, isKeynoteToken) {
if (!manualMode) {
const regex = /^((?:=|\^|_)*)([a-gA-G])([',]*)$/;
let m = regex.exec(noteToken);
if (!m) return '?';
let [_, accidental, noteLetter, octaveMarkers] = m;
let acc = accidental.replace(/=/g, '').replace(/\^/g, '#').replace(/_/g, 'b');
let normalized = noteLetter.toUpperCase() + (acc || '');
let idx = scale.findIndex(n => n.toUpperCase() === normalized);
if (idx < 0) return '?';
let code = String(idx + 1);
if (octaveMode === 'shiftUp') code += 'H';
else if (octaveMode === 'shiftDown') code += 'L';
else if (octaveMode === 'visual') {
if (/[a-z]/.test(noteLetter)) code += 'H';
else if (octaveMarkers.includes(',')) code += 'L';
} else {
let keyNorm = normalizeKey(keyRaw);
let suffix = defaultOctaveMapping[keyNorm]?.[normalized] || '';
code += suffix;
}
return code;
} else {
const regex = /^((?:=|\^|_)*)([a-gA-G])([',]*)$/;
let m = regex.exec(noteToken);
if (!m) return '?';
let normalized = m[2].toUpperCase() + (m[1].replace(/=/g, '').replace(/\^/g, '#').replace(/_/g, 'b') || '');
let idx = manualScale.indexOf(normalized);
if (idx < 0) return '?';
let code = String(idx + 1);
if (isKeynoteToken) code += (manualKey === manualKey.toLowerCase() ? 'H' : 'L');
return code;
}
}
function parseABC_timeline(abcText, octaveMode, rhythmOption, manualSampling, manualKeyInput) {
let lines = cleanABC(abcText).split(/\r?\n/);
let hdr = { key:'D', meter:'4/4', length:1/8, beatsPerBar:4 };
lines.forEach(l => {
if (/^K:/.test(l)) hdr.key = l.slice(2).trim();
if (/^M:/.test(l)) {
hdr.meter = l.slice(2).trim();
hdr.beatsPerBar = parseInt(hdr.meter.split('/')[0], 10);
}
if (/^L:/.test(l)) {
let [n,d] = l.slice(2).trim().split('/').map(Number);
hdr.length = n/d;
}
});
let body = lines.filter(l => !/^[A-Za-z]:/.test(l)).join(' ');
let scale = manualKeyInput.trim() ?
buildRelativeScale(manualKeyInput) :
getScaleForKey(hdr.key);
let tokens = [];
let time = 0;
const regex = /((?:=|\^|_)?[a-gA-GzZ][',]*)(\d*\/?\d*)/g;
let match;
while ((match = regex.exec(body)) !== null) {
let note = match[1], dur = parseDuration(match[2], hdr.length);
let code = /^[zZ]/.test(note)? '0' :
convertNoteToTCICode(note, scale, hdr.key, octaveMode, !!manualKeyInput.trim(), manualKeyInput.trim(), scale, tokens.length===0);
tokens.push({ code, start: time, dur, note });
time += dur;
}
let units = hdr.length;
let beats = Array.from({length: hdr.beatsPerBar*2}, (_,i) => i*units);
let incipit = beats.map(t => {
let tok = tokens.find(x => t>=x.start && t<t+ x.dur);
return tok? tok.code : '?';
});
let groups = [];
for (let i=0; i<incipit.length; i+=hdr.beatsPerBar) {
groups.push(incipit.slice(i, i+hdr.beatsPerBar).join(''));
}
return groups.join(' ');
}
function TCICalculatorDialog(config) {
TCICalculatorDialog.super.call(this, config);
}
OO.inheritClass(TCICalculatorDialog, OO.ui.ProcessDialog);
TCICalculatorDialog.static.name = 'tciCalculatorDialog';
TCICalculatorDialog.static.title = 'Theme Code Index Calculator';
TCICalculatorDialog.static.actions = [
{ action: 'cancel', label: 'Annulla', flags: 'safe' },
{ action: 'calculate', label: 'Calcola TCI', flags: ['primary', 'progressive'] }
];
TCICalculatorDialog.prototype.initialize = function () {
TCICalculatorDialog.super.prototype.initialize.call(this);
this.content = new OO.ui.PanelLayout({ padded: true, expanded: false });
this.$body.append(this.content.$element);
this.abcInput = new OO.ui.MultilineTextInputWidget({ placeholder: 'Incolla qui la notazione ABC...', autosize: true, rows: 10 });
this.manualKeyInput = new OO.ui.TextInputWidget({ placeholder: 'Inserisci Keynote Manuale (es. d) (opzionale)' });
this.octaveChoice = new OO.ui.DropdownInputWidget({ options: [
{ data: 'standard', label: 'Standard (usa mappatura predefinita)' },
{ data: 'visual', label: 'Basato sulla notazione visiva ABC' },
{ data: 'shiftUp', label: 'Forza ottava alta (+1)' },
{ data: 'shiftDown', label: 'Forza ottava bassa (-1)' }
]});
this.rhythmChoice = new OO.ui.DropdownInputWidget({ options: [
{ data: '2beats', label: 'Standard 2/4 (2 battiti per misura)' },
{ data: '4beats', label: 'Ricalcola come cut time (4 battiti per misura)' }
]});
this.manualSamplingInput = new OO.ui.TextInputWidget({ placeholder: 'Es.: 0.8, 2.4, 4.0, ... (opzionale)' });
this.resultOutput = new OO.ui.MultilineTextInputWidget({ readOnly: true, autosize: true, rows: 4 });
this.debugOutput = new OO.ui.MultilineTextInputWidget({ readOnly: true, autosize: true, rows: 6 });
let fieldset = new OO.ui.FieldsetLayout({ label: 'Opzioni Calcolatore TCI', items: [
new OO.ui.FieldLayout(this.abcInput, { label: 'Notazione ABC', align: 'top' }),
new OO.ui.FieldLayout(this.manualKeyInput, { label: 'Keynote Manuale', align: 'top' }),
new OO.ui.FieldLayout(this.octaveChoice, { label: 'Interpretazione Ottava', align: 'top' }),
new OO.ui.FieldLayout(this.rhythmChoice, { label: 'Interpretazione Ritmica (per 2/4)', align: 'top' }),
new OO.ui.FieldLayout(this.manualSamplingInput, { label: 'Campionamento Manuale (unitĂ )', align: 'top' }),
new OO.ui.FieldLayout(this.resultOutput, { label: 'Theme Code Index (TCI)', align: 'top' }),
new OO.ui.FieldLayout(this.debugOutput, { label: 'Debug Info', align: 'top' })
]});
this.content.$element.append(fieldset.$element);
};
TCICalculatorDialog.prototype.getActionProcess = function (action) {
if (action === 'calculate') {
return new OO.ui.Process(() => {
try {
let tci = parseABC_timeline(
this.abcInput.getValue(),
this.octaveChoice.getValue(),
this.rhythmChoice.getValue(),
this.manualSamplingInput.getValue(),
this.manualKeyInput.getValue()
);
this.resultOutput.setValue(tci);
this.debugOutput.setValue('Controlla la console per dettagli.');
} catch (e) {
this.resultOutput.setValue('Errore: ' + e);
this.debugOutput.setValue('Verifica notazione ABC. Errore: ' + e);
}
});
} else if (action === 'cancel') {
return new OO.ui.Process(() => this.close({ action: action }));
}
return TCICalculatorDialog.super.prototype.getActionProcess.call(this, action);
};
function openTCICalculatorDialog() {
const wm = new OO.ui.WindowManager();
$(document.body).append(wm.$element);
const dlg = new TCICalculatorDialog();
wm.addWindows([dlg]);
wm.openWindow(dlg);
}
function addTCICalculatorLink() {
const ul = $('#p-cactions ul');
if (ul.length && !$('#tci-calculator').length) {
ul.append($('<li><a href="#" id="tci-calculator">đź Calcola TCI</a></li>'));
}
}
$(document).on('click', '#tci-calculator', function (e) {
e.preventDefault();
openTCICalculatorDialog();
});
$(addTCICalculatorLink);
});
})(jQuery, mediaWiki, OO);