The Numanoid Channel

Play Music Play Gary NUMAN

The website dedicated to the Pop Star Gary NUMAN.



ABOUT ME

My name is : Jean-Guy
SOCIAL MEDIA
© 2024 Copyright: https://www.numanoid.be
/* * Karaoké : colore les paroles de la page, mot par mot, pendant la lecture du mp3. * * Charge Titre.karaoke.json (situé à côté du Titre.mp3 en cours), retrouve tout seul le bloc de * paroles dans la page, puis remplit chaque mot au rythme du chant. * N'agit pas sur l'audio : sans conflit avec le VU-mètre ni avec subtitles.js. */ (function () { // ---------- réglages ---------- var SELECTOR = ''; // ex. '#paroles' pour désigner le bloc a la main ; vide = détection automatique var SUNG_COLOR = '#ffd400'; // couleur des mots chantés (les mots a venir gardent la couleur de la page) var PAST_COLOR = ''; // couleur des lignes déjà chantées ('' = la meme que SUNG_COLOR), ex. '#ff9f1c' var LINE_COLOR = '#ff6a1a'; // couleur des mots pas encore chantés de la ligne en cours ('' = couleur de la page), ex. '#ffffff' var ACTIVE_SCALE = 1.22; // agrandissement de la ligne chantée (1 = aucun ; 1.2 = nettement plus grand) var LEAD = 0.5; // la ligne suivante se colore / s'agrandit ce nombre de secondes AVANT son premier mot (0 = désactivé) // cherche le lecteur dans cette page, puis dans la page parente (si les paroles sont dans un cadre) function findAudio() { var wins = [window], w = window; for (var i = 0; i <3; i++)="" { ="" try="" { ="" if="" (w.parent="" &&="" w.parent="" !="=" w)="" {="" w="w.parent;" wins.push(w);="" }="" else="" {="" break;="" } ="" }="" catch="" (e)="" {="" break;="" } ="" } ="" for="" (var="" k="0;" k="" <="" wins.length;="" k++)="" { ="" try="" { ="" var="" a="wins[k].document.querySelector('audio'); " if="" (a)="" return="" a; ="" }="" catch="" (e)="" page="" d'un="" autre="" domaine="" :="" inaccessible ="" } ="" return="" null; ="" le="" lecteur="" peut="" apparaître="" après="" le="" chargement="" de="" la="" page="" :="" on="" attend="" jusqu'à="" 30="" secondes ="" var="" waitTries="0; " function="" waitForAudio()="" { ="" var="" a="findAudio(); " if="" (a)="" {="" console.info('karaoke.js="" :="" lecteur="" audio="" trouvé');="" start(a);="" return;="" } ="" if="" (waitTries++="" <="" 60)="" setTimeout(waitForAudio,="" 500); ="" else="" console.warn('karaoke.js="" :="" aucun="" lecteur="" trouvé'); } function start(audio) { var words = []; // [{s, e, el}] mots reliés a un élément de la page var cur = -1; // index du dernier mot commencé var curLine = -1; // ligne actuellement agrandie var curMain = -2; // ligne du dernier mot commencé (pour les lignes « déjà chantées ») var lineEls = {}; // numéro de ligne -> éléments de cette ligne var lineStart = {}; // numéro de ligne -> instant de son premier mot var lineOrder = []; // numéros de ligne, dans l'ordre var lastP = -1; var wanted = ''; var tries = 0; var raf = 0; // ---------- styles ---------- var css = document.createElement('style'); css.textContent = // chaque mot est une « boîte » : cela permet d'agrandir la ligne chantée sans déplacer les autres lignes '.kw{display:inline-block;transition:transform .25s ease;transform-origin:50% 65%;}' + '.kw-line{transform:scale(' + ACTIVE_SCALE + ');}' + '.kw-line:not(.kw-done):not(.kw-act){color:var(--kc-line);}' + '.kw-done{color:var(--kc-sung) !important;}' + '.kw-done.kw-past{color:var(--kc-past) !important;}' + '.kw-act{color:transparent !important;-webkit-text-fill-color:transparent;text-shadow:none !important;' + 'background-image:linear-gradient(90deg,var(--kc-sung) 0,var(--kc-sung) var(--p,0%),' + 'var(--kc-base) var(--p,0%),var(--kc-base) 100%);' + '-webkit-background-clip:text;background-clip:text;}'; document.head.appendChild(css); // ---------- outils ---------- function norm(t) { // mot normalisé pour la comparaison : lettres et chiffres seulement // apostrophes et guillemets de toute sorte (droits, courbes, accents, ʼ) sont ignorés des deux côtés return t.toLowerCase().replace(/[ʻʼʽˈ`´]/g, '').replace(/[^\p{L}\p{N}]+/gu, ''); } function squash(t) { // texte sans espaces ni ponctuation return t.toLowerCase().replace(/[^\p{L}\p{N}]+/gu, ''); } function base() { return (audio.currentSrc || audio.src || '').split('?')[0].replace(/\.[a-z0-9]+$/i, ''); } // ---------- 1. retrouver le bloc de paroles ---------- function findContainer(lines) { if (SELECTOR) return document.querySelector(SELECTOR); var n = lines.length; // début et fin : les deux premières / deux dernières lignes DIFFÉRENTES (un refrain répété // à l'identique n'est pas forcément répété sur la page) var distinct = []; lines.forEach(function (l) { if (!distinct.length || distinct[distinct.length - 1] !== l) distinct.push(l); }); var head = squash(distinct.slice(0, Math.min(2, distinct.length)).join(' ')); var tail = squash(distinct.slice(Math.max(0, distinct.length - 2)).join(' ')); var all = document.body.querySelectorAll('*'); var best = null, bestLen = Infinity; for (var i = 0; i -1 && t.indexOf(tail) > -1) { best = el; bestLen = t.length; } } return best; } // ---------- 2. découper le texte en mots ---------- function unwrap(container) { Array.prototype.forEach.call(container.querySelectorAll('span.kw'), function (sp) { sp.replaceWith(document.createTextNode(sp.textContent)); }); container.normalize(); } function wrap(container) { var walker = document.createTreeWalker(container, NodeFilter.SHOW_TEXT, { acceptNode: function (node) { var p = node.parentNode && node.parentNode.tagName; return /^(SCRIPT|STYLE)$/.test(p) ? NodeFilter.FILTER_REJECT : NodeFilter.FILTER_ACCEPT; } }); var nodes = [], node; while ((node = walker.nextNode())) nodes.push(node); var spans = []; nodes.forEach(function (tn) { var text = tn.nodeValue; if (!/\S/.test(text)) return; var frag = document.createDocumentFragment(); text.split(/(\s+)/).forEach(function (part) { if (!part) return; if (/^\s+$/.test(part)) { frag.appendChild(document.createTextNode(part)); } else { var sp = document.createElement('span'); sp.className = 'kw'; sp.textContent = part; frag.appendChild(sp); spans.push(sp); } }); tn.parentNode.replaceChild(frag, tn); }); return spans; } // ---------- 3. relier les mots de la page aux minutages ---------- function match(allSpans, allFlat) { // les « mots » faits uniquement de ponctuation (tiret isolé, ...) sont laissés de côté des deux côtés var spans = allSpans.filter(function (sp) { return norm(sp.textContent) !== ''; }); var flat = allFlat.filter(function (w) { return norm(w[0]) !== ''; }); var jn = flat.map(function (w) { return norm(w[0]); }); var pn = spans.map(function (sp) { return norm(sp.textContent); }); // début : premier endroit de la page ou les 3 premiers mots chantés se suivent var k = Math.min(3, jn.length), start = -1, i, m; for (i = 0; i + k < ="pn.length" &&="" start="" <="" 0;="" i++)="" { ="" var="" ok="true; " for="" (m="0;" m="" <="" k;="" m++)="" { ="" if="" (pn[i="" +="" m]="" !="=" jn[m])="" {="" ok="false;" break;="" } ="" } ="" if="" (ok)="" start="i; " } ="" if="" (start="" <="" 0)="" return="" null; ="" var="" j="0," out="[]," n; ="" for="" (i="start;" i="" <="" pn.length="" &&="" j="" <="" jn.length;="" i++)="" { ="" if="" (pn[i]="==" jn[j])="" { ="" out.push({="" s:="" flat[j][1],="" e:="" flat[j][2],="" el:="" spans[i],="" line:="" flat[j][3]="" }); ="" j++; ="" continue; ="" un="" mot="" de="" la="" page="" coupé="" en="" plusieurs="" morceaux="" (balises="" de="" mise="" en="" forme)="" :="" «="" Aren="" »="" +="" «="" 't="" » ="" var="" joined="false; " for="" (n="2;" n="" <="3" &&="" i="" +="" n="" <="pn.length" &&="" !joined;="" n++)="" { ="" if="" (pn.slice(i,="" i="" +="" n).join('')="==" jn[j])="" { ="" for="" (m="0;" m="" <="" n;="" m++)="" out.push({="" s:="" flat[j][1],="" e:="" flat[j][2],="" el:="" spans[i="" +="" m],="" line:="" flat[j][3]="" }); ="" i="" +="n" -="" 1; ="" j++; ="" joined="true; " } ="" } ="" if="" (joined)="" un="" mot="" de="" la="" page="" pour="" deux="" mots="" chantés="" :="" «="" low-line="" »="" pour="" «="" low="" line="" » ="" for="" (n="2;" n="" <="3" &&="" j="" +="" n="" <="jn.length" &&="" !joined;="" n++)="" { ="" if="" (jn.slice(j,="" j="" +="" n).join('')="==" pn[i])="" { ="" out.push({="" s:="" flat[j][1],="" e:="" flat[j="" +="" n="" -="" 1][2],="" el:="" spans[i],="" line:="" flat[j][3]="" }); ="" j="" +="n; " joined="true; " } ="" } ="" if="" (joined)="" mot="" différent="" :="" on="" regarde="" les="" 3="" mots="" chantés="" suivants="" (mot="" oublié="" dans="" le="" texte="" de="" la="" page) ="" var="" found="-1; " for="" (var="" d="1;" d="" <="3" &&="" j="" +="" d="" <="" jn.length;="" d++)="" { ="" if="" (pn[i]="==" jn[j="" +="" d])="" {="" found="j" +="" d;="" break;="" } ="" plus="" loin="" (jusqu'à="" 40="" mots)="" :="" refrain="" chanté="" plusieurs="" fois="" mais="" écrit="" une="" seule="" fois="" sur="" la="" On="" n'accepte="" que="" si="" les="" mots="" suivants="" de="" la="" page="" suivent="" aussi,="" pour="" ne="" pas="" se="" tromper="" d'endroit. ="" if="" (found="" <="" 0)="" { ="" for="" (d="4;" d="" <="40" &&="" j="" +="" d="" <="" jn.length="" &&="" found="" <="" 0;="" d++)="" { ="" if="" (pn[i]="==" jn[j="" +="" d]="" &&="" pn[i="" +="" 1]="==" jn[j="" +="" d="" +="" 1]="" &&="" pn[i="" +="" 2]="==" jn[j="" +="" d="" +="" 2])="" found="j" +="" d; ="" } ="" } ="" if="" (found=""> -1) { out.push({ s: flat[found][1], e: flat[found][2], el: spans[i], line: flat[found][3] }); j = found + 1; } // sinon : mot en trop sur la page (titre, copyright...), ignoré } return out; } // ---------- 4. affichage ---------- function setDone(i) { var el = words[i].el; el.classList.remove('kw-act'); el.classList.add('kw-done'); el.style.removeProperty('--p'); } function setIdle(i) { var el = words[i].el; el.classList.remove('kw-act', 'kw-done'); el.style.removeProperty('--p'); } function setActive(i, p) { var el = words[i].el; el.classList.remove('kw-done'); el.classList.add('kw-act'); el.style.setProperty('--p', p + '%'); } function update() { if (!words.length) return; var t = audio.currentTime, lo = 0, hi = words.length - 1, idx = -1, i; while (lo < ="hi)" { ="" var="" mid="(lo" +="" hi)="">> 1; if (words[mid].s < ="t)" {="" idx="mid;" lo="mid" +="" 1;="" }="" else="" {="" hi="mid" -="" 1;="" } ="" } ="" if="" (idx="" !="=" cur)="" { ="" if="" (idx=""> cur) { for (i = Math.max(cur, 0); i = 0) { var w = words[idx]; var p = Math.max(0, Math.min(100, Math.round((t - w.s) / Math.max(w.e - w.s, 0.05) * 100))); if (p !== lastP) { setActive(idx, p); lastP = p; } } var main = idx >= 0 ? words[idx].line : -1; if (main !== curMain) { updatePast(main); curMain = main; } // ligne agrandie : celle du mot en cours, sauf pendant un silence (passage instrumental) var L = (idx >= 0 && t < ="words[idx].e" +="" 0 && (idx < 0 || t >= words[idx].e)) { var cl = idx >= 0 ? words[idx].line : -1; for (var q = 0; q < lineOrder.length; q++) { if (lineOrder[q] > cl) { var ns = lineStart[lineOrder[q]]; if (ns > t && ns - t <= LEAD) L = lineOrder[q]; break; } } } if (L !== curLine) { markLine(curLine, false); markLine(L, true); curLine = L; } } function markLine(n, on) { var els = lineEls[n] || []; if (on) { // on agrandit chaque rangée de la ligne autour de SON centre, comme un seul bloc : // les mots s'écartent au lieu de se coller les uns aux autres var rows = []; els.forEach(function (el) { var row = rows.filter(function (r) { return Math.abs(r.top - el.offsetTop) < 6; })[0]; if (!row) { row = { top: el.offsetTop, els: [] }; rows.push(row); } row.els.push(el); }); rows.forEach(function (r) { var left = Infinity, right = -Infinity; r.els.forEach(function (el) { left = Math.min(left, el.offsetLeft); right = Math.max(right, el.offsetLeft + el.offsetWidth); }); var cx = (left + right) / 2; r.els.forEach(function (el) { el.style.transformOrigin = (cx - el.offsetLeft) + 'px 65%'; }); }); } els.forEach(function (el) { el.classList.toggle('kw-line', on); }); } // lignes déjà chantées (avant la ligne en cours) : classe kw-past, pour leur donner une autre couleur function updatePast(b) { for (var n in lineEls) { var on = Number(n) < b; lineEls[n].forEach(function (el) { el.classList.toggle('kw-past', on); }); } } function loop() { update(); raf = (!audio.paused && !audio.ended) ? requestAnimationFrame(loop) : 0; } // ---------- mise en route ---------- function attach(data) { if (!data.lines.length || !data.lines[0] || !data.lines[0].w) { console.warn('karaoke.js : ce fichier n\'est pas un fichier .karaoke.json valide (il ne contient pas ' + 'les mots) : vérifiez que c\'est bien « ....karaoke.json » et non « ....timing.json »'); return; } var flat = [], lines = []; data.lines.forEach(function (l, li) { lines.push(l.t); l.w.forEach(function (w) { flat.push([w[0], w[1], w[2], li]); }); }); if (!flat.length) return; var container = findContainer(lines); if (!container) { if (tries++ < 10) setTimeout(function () { attach(data); }, 500); // paroles peut-être pas encore affichées else console.warn('karaoke.js : bloc de paroles introuvable dans la page (le début et la fin du texte ' + 'ne correspondent pas au fichier .karaoke.json)'); return; } unwrap(container); var spans = wrap(container); var matched = match(spans, flat); if (!matched || matched.length < flat.length * 0.7) { console.warn('karaoke.js : les paroles de la page ne correspondent pas assez au fichier .karaoke.json'); unwrap(container); return; } container.style.setProperty('--kc-sung', SUNG_COLOR); container.style.setProperty('--kc-past', PAST_COLOR || SUNG_COLOR); if (LINE_COLOR) container.style.setProperty('--kc-line', LINE_COLOR); container.style.setProperty('--kc-base', LINE_COLOR || getComputedStyle(container).color); words = matched; lineEls = {}; matched.forEach(function (w) { (lineEls[w.line] = lineEls[w.line] || []).push(w.el); }); lineStart = {}; matched.forEach(function (w) { if (!(w.line in lineStart)) lineStart[w.line] = w.s; }); lineOrder = Object.keys(lineStart).map(Number).sort(function (a, b) { return a - b; }); curLine = -1; curMain = -2; cur = -1; lastP = -1; update(); if (!audio.paused) loop(); } function init() { words = []; cur = -1; tries = 0; if (!base()) return; var url = base() + '.karaoke.json'; wanted = url; console.info('karaoke.js : chargement de ' + url); fetch(url) .then(function (r) { if (!r.ok) console.warn('karaoke.js : fichier introuvable (code ' + r.status + ') : ' + url); return r.ok ? r.json() : null; }) .then(function (data) { if (data && data.lines && wanted === url) attach(data); }) .catch(function (e) { console.warn('karaoke.js : lecture impossible : ' + url, e); }); } audio.addEventListener('loadedmetadata', init); audio.addEventListener('play', function () { if (!raf) loop(); }); audio.addEventListener('pause', update); audio.addEventListener('seeked', update); audio.addEventListener('timeupdate', function () { if (!raf) update(); }); if (audio.readyState >= 1) init(); } waitForAudio(); })(); ?>="">