Écouter - Rejouer

Score général de la partie : 0
Tour 1 / 8

MODÈLE


IMITATEUR



Sélection du joueur courant :

Sélection du joueur modèle :













Connexion MIDI en attente...

Analyse des accords

Données enregistrées

document.getElementById("calculerScore").addEventListener("click", calculerScore); function calculerScore() { fetch("php/getTable.php?" + Date.now()) // Évite le cache .then(res => res.text()) .then(html => { const tempDiv = document.createElement("div"); tempDiv.innerHTML = html; const lignes = tempDiv.querySelectorAll("table tr"); lignes.forEach((ligne, index) => { if (index === 0) return; // Ignorer l'en-tête const cellules = ligne.querySelectorAll("td"); if (cellules.length < 8) return; const id = parseInt(cellules[0].textContent.trim()); const joueur = cellules[1].textContent.trim().toLowerCase(); const type = cellules[2].textContent.trim().toLowerCase(); const beat = parseFloat(cellules[7].textContent.trim()); const joueurCourant = window.joueur?.toLowerCase?.(); if ( type === "chord" && !isNaN(beat) && joueur === joueurCourant ) { const entierProche = Math.round(beat); const ecart = Math.abs(beat - entierProche); if (ecart < 0.1) { fetch("php/updateScore.php", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ id: id, points: 2 }) }); } } }); }) .catch(err => console.warn("Erreur dans calculerScore():", err)); } function synchroniserTempoDepuisServeur() { fetch("php/getDernierTempo.php?" + Date.now()) // éviter le cache navigateur .then(res => res.text()) .then(t => { const tempo = parseFloat(t.trim()); if (isNaN(tempo) || tempo <= 0) { console.warn("Tempo non disponible ou invalide."); return; } window.tempoBPM = tempo; }) .catch(err => { console.warn("Erreur lors de la lecture du tempo depuis le serveur :", err); }); } // Appel automatique toutes les secondes setInterval(synchroniserTempoDepuisServeur, 1000); window.jeuDemarre = false; window.tempoBPM = 60; document.getElementById("augmenterScore").addEventListener("click", () => { const id = prompt("Entrez l'identifiant (id) de la ligne à récompenser :"); if (!id || isNaN(id)) return alert("Identifiant invalide."); const points = prompt("Combien de points ajouter ?"); if (!points || isNaN(points)) return alert("Nombre de points invalide."); fetch("php/updateScore.php", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ id: parseInt(id), points: parseFloat(points) }) }).then(() => alert(`+${points} point(s) ajoutés à la ligne ${id}`)); }); document.addEventListener("DOMContentLoaded", () => { const bouton = document.getElementById("boutonCopierTable"); if (bouton) { bouton.addEventListener("click", copierTableAffichage); } const boutonImitateur = document.getElementById("boutonCopierTableImitateur"); if (boutonImitateur) { boutonImitateur.addEventListener("click", copierTableAffichageImitateur); } }); function pause(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } let nomServeur = null; let tableExists = false; function afficherMessageJeu() { const div = document.getElementById("messageJeu"); if (!div || !window.joueur || typeof window.modeleEstJoueur1 === "undefined") return; const estModele = (window.modeleEstJoueur1 && window.joueur === "joueur1") || (!window.modeleEstJoueur1 && window.joueur === "joueur2"); div.textContent = estModele ? "Votre rôle : 🎹 Enregistrer une séquence d'accords" : "Votre rôle : 🎧 Imiter la séquence de l'autre joueur"; } function afficherIdentiteJoueur() { const div = document.getElementById("identiteJoueur"); if (div && window.joueur) { div.textContent = `🎮 Vous êtes ${window.joueur}`; } } function traiterReponseCreateTable(result) { if (typeof result.tableExists === 'undefined') return; tableExists = result.tableExists; const msg = document.getElementById("message"); msg.textContent = `🧪 tableExists = ${tableExists}`; if (tableExists) { setJoueur('joueur2'); setModele('joueur1'); } else { setJoueur('joueur1'); setModele('joueur1'); } // ✅ Initialisation audio (t = 0) document.getElementById('btnInitAudio')?.click(); //setTimeout(inverserRole, 200); setTimeout(rafraichirRole, 300); } function demanderNomServeur() { synchroniserTempoDepuisServeur(); const saisie = prompt("Entrez le nom du serveur :"); if (!saisie) return; nomServeur = saisie; // mémoriser pour plus tard fetch('php/createTable.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ nomServeur }) }) .then(response => response.json()) .then(traiterReponseCreateTable) .catch(err => { console.error("❌ Erreur lors de la création ou vérification de la table :", err); }); window.jeuDemarre = true; configBoutonsModeleVsImitateur() //jouerBoucleAudio('sounds/ambiance/ambianceBase.mp3'); //jouerClochettesAleatoires(); } function nettoyerNomPourFichier(chaine) { return chaine .normalize("NFD") .replace(/[\u0300-\u036f]/g, "") // accents .replace(/[^a-zA-Z0-9_-]/g, "_"); // tout le reste } function jouerBoucleAudio(fichier) { const audioContext = new (window.AudioContext || window.webkitAudioContext)(); const gainNode = audioContext.createGain(); // Fixer le volume à 50 % gainNode.gain.value = 0.05; gainNode.connect(audioContext.destination); fetch(fichier) .then(response => response.arrayBuffer()) .then(data => audioContext.decodeAudioData(data)) .then(buffer => { const source = audioContext.createBufferSource(); source.buffer = buffer; source.loop = true; source.connect(gainNode); source.start(0); }) .catch(err => console.error("Erreur lecture boucle audio :", err)); } function jouerClochettesAleatoires() { const audioContext = new (window.AudioContext || window.webkitAudioContext)(); const gainNode = audioContext.createGain(); gainNode.gain.value = 0.3; gainNode.connect(audioContext.destination); const dossier = 'sounds/ambiance/bells/'; const fichiers = [ "bell_1.mp3", "bell_2.mp3", "bell_3.mp3", "bell_4.mp3", "bell_5.mp3", "bell_6.mp3", "bell_7.mp3", "bell_8.mp3", "bell_9.mp3", "bell_10.mp3", "bell_11.mp3", "bell_12.mp3", "bell_13.mp3", "bell_14.mp3", "bell_15.mp3", "bell_16.mp3", "bell_17.mp3", "bell_18.mp3", "bell_19.mp3", "bell_20.mp3", "bell_21.mp3", "bell_22.mp3", "bell_23.mp3", "bell_24.mp3", "bell_25.mp3", "bell_26.mp3", "bell_27.mp3", "bell_28.mp3", "bell_29.mp3", "bell_30.mp3", "bell_31.mp3", "bell_32.mp3", "bell_33.mp3", "bell_34.mp3", "bell_35.mp3", "bell_36.mp3", "bell_37.mp3", "bell_38.mp3", "bell_39.mp3", "bell_40.mp3" ]; function jouerSon() { const nom = fichiers[Math.floor(Math.random() * fichiers.length)]; const chemin = dossier + nom; fetch(chemin) .then(r => r.arrayBuffer()) .then(data => audioContext.decodeAudioData(data)) .then(buffer => { const source = audioContext.createBufferSource(); source.buffer = buffer; source.connect(gainNode); source.start(); }) .catch(err => console.error("Erreur lecture clochette :", err)); // Prochain son entre 5 et 15 secondes const prochainDelai = Math.random() * 10000 + 5000; setTimeout(jouerSon, prochainDelai); } // Lancer la première clochette jouerSon(); } function debutJeu() { setJoueur('joueur1'); const boutons = [ 'btnInitAudio', // t = 0 'btnSupprimer', // Supprimer table 'btnCreer', // Créer table 'btnJoueur1', // Je suis joueur 1 'btnModele1', // Modèle = joueur 1 'btnValiderTempo' // Valider le tempo ]; boutons.forEach((id, index) => { setTimeout(() => { const bouton = document.getElementById(id); if (bouton) { bouton.click(); } else { console.warn(`❌ Bouton ${id} introuvable`); } }, index * 200); // 200 ms entre chaque clic }); } function finDeTour() { jouerSonUnique('sounds/ambiance/finDeTour.mp3'); const btnFin = document.getElementById("btnFinTour"); fetch("php/nettoyerTable.php") .then(res => res.text()) .then(msg => { // Inverser le rôle directement via API const nouveauRole = window.modeleEstJoueur1 ? "joueur2:modèle" : "joueur1:modèle"; return fetch(`php/inverserRole.php?role=${nouveauRole}`); }) .then(res => res.text()) .then(msg => { window.modeleEstJoueur1 = !window.modeleEstJoueur1; afficherMessage(`🎭 Rôle mis à jour: ${window.modeleEstJoueur1 ? "modèle = joueur1" : "modèle = joueur2"}`); afficherMessageJeu(); rafraichirRole(); // Mise à jour de l’interface }) .catch(err => console.error("❌ Erreur en fin de tour :", err)); nettoyerTable(); } function inverserRole() { const nouveauRole = window.modeleEstJoueur1 ? "joueur2:modèle" : "joueur1:modèle"; fetch(`php/inverserRole.php?role=${nouveauRole}`) .then(res => res.text()) .then(msg => { afficherMessage(msg); window.modeleEstJoueur1 = !window.modeleEstJoueur1; afficherMessage(`Nouveau rôle : ${nouveauRole}`); afficherMessageJeu(); }); } function rafraichirRole() { if (!window.joueur || window.verrouRole) return; fetch("php/getDernierRole.php") .then(res => res.text()) .then(role => { const estJoueur1Modele = role.trim() === "joueur1:modèle"; window.modeleEstJoueur1 = estJoueur1Modele; afficherMessage(`↻ Rôle rafraîchi: modèle = ${estJoueur1Modele ? 'joueur1' : 'joueur2'}`); const estModele = (estJoueur1Modele && window.joueur === "joueur1") || (!estJoueur1Modele && window.joueur === "joueur2"); }) .catch(err => console.error("❌ Erreur rafraîchissement rôle :", err)); } function setModele(nom) { window.modeleEstJoueur1 = (nom === "joueur1"); // Envoi au serveur pour éviter que le rafraîchissement l’écrase const nouveauRole = window.modeleEstJoueur1 ? "joueur1:modèle" : "joueur2:modèle"; fetch(`php/inverserRole.php?role=${nouveauRole}`) .then(res => res.text()) .then(msg => { afficherMessage(`Modèle défini : ${nom}`); rafraichirRole(); // ✅ Maintenant qu’on est sûr que le rôle est bien enregistré window.verrouRole = true; // ← bloque les rafraîchissements temporaires setTimeout(() => window.verrouRole = false, 1500); }); // Mise à jour visuelle des boutons const btn1 = document.getElementById("btnModele1"); const btn2 = document.getElementById("btnModele2"); if (btn1 && btn2) { if (window.modeleEstJoueur1) { btn1.classList.add("bouton-modele-actif");//à effacer? btn2.classList.remove("bouton-modele-actif");//à effacer? } else { btn1.classList.remove("bouton-modele-actif");//à effacer? btn2.classList.add("bouton-modele-actif");//à effacer? } } } window.setModele = setModele; setTimeout(() => { if (!window.audioContext) { console.warn("⛔ audioContext non initialisé."); return; } const lignes = document.querySelectorAll("#tableAffichage table tr"); const now = window.audioContext.currentTime; for (let i = 1; i < lignes.length; i++) { const cellules = lignes[i].querySelectorAll("td"); const type = cellules[2]?.textContent.trim(); const note = cellules[3]?.textContent.trim(); const chord = cellules[4]?.textContent.trim(); const timestamp = parseFloat(cellules[6]?.textContent.trim()); if (isNaN(timestamp)) continue; const delay = (timestamp - (now - window.t0)) * 1000; if (type === "système" && note) { setTimeout(() => { const chemin = `sounds/drumSounds/${nettoyerNomPourFichier(note)}.mp3`; jouerMp3(chemin); }, Math.max(0, delay)); } } }, 1000); window.t0 = null; //window.joueur = "joueur1"; afficherIdentiteJoueur(); document.getElementById("status").textContent = "Audio activé, connexion MIDI..."; function afficherMessage(msg) { const el = document.getElementById('message'); el.textContent = msg; el.style.opacity = 1; setTimeout(() => { el.style.opacity = 0.3; }, 4000); } function createTable() { fetch('php/createTable.php') .then(res => res.text()) .then(msg => { afficherMessage(msg); setTimeout(loadTable, 500); // ✅ délai avant affichage }); } /* ------------------------------------------------------------------ * Lecture unique d’un MP3 * Appel : jouerSonUnique('sounds/drumSounds/monFichier.mp3'); * ------------------------------------------------------------------ */ let audioCtxUnique = null; // Contexte partagé let gainUnique = null; // Gain réglé à 1 (volume normal) function jouerSonUnique(chemin) { if (!audioCtxUnique) { // créer le contexte au 1ᵉʳ appel audioCtxUnique = new (window.AudioContext || window.webkitAudioContext)(); gainUnique = audioCtxUnique.createGain(); gainUnique.gain.value = 0.3; // volume à 100 % gainUnique.connect(audioCtxUnique.destination); } fetch(chemin) .then(r => r.arrayBuffer()) .then(buf => audioCtxUnique.decodeAudioData(buf)) .then(buffer => { const src = audioCtxUnique.createBufferSource(); src.buffer = buffer; src.connect(gainUnique); src.start(); // lecture immédiate }) .catch(err => console.error("Erreur lecture MP3 :", err)); } function dropTable() { fetch('php/dropTable.php') .then(res => res.text()) .then(msg => { afficherMessage(msg); loadTable(); }); } function loadTable() { fetch("php/getTable.php") .then(res => res.text()) .then(html => { if (html.startsWith("")) { console.warn("⚠️ Table probablement non créée (getTable.php)"); return; } document.getElementById('tableAffichage').innerHTML = html; }) .catch(err => { console.warn("⚠️ Erreur fetch table :", err.message); }); } function calculerScoreTour() { fetch("php/inscrireScoreTour.php") .then(res => res.text()) .then(msg => afficherMessage(msg)); } function incrementerScorePartie() { fetch("php/getAllData.php") .then(res => res.json()) .then(data => { const lignes = data.filter(d => d.type === "score tour"); if (lignes.length === 0) { afficherMessage("Aucun score tour trouvé."); return null; // ← renvoie null pour éviter les erreurs dans les .then suivants } const dernier = lignes[lignes.length - 1]; const scoreTour = parseFloat(dernier.timestamp); return fetch("php/insertEvent.php", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ joueur: "système", type: "score partie", note: "", chord: "", position: "", timestamp: scoreTour }) }); }) .then(res => { if (!res) return; // ← insertion non faite (score tour manquant) return res.text(); }) .then(msg => { if (!msg) return; // ← évite tout traitement inutile afficherMessage("Score partie mis à jour."); setTimeout(actualiserScorePartie, 150); }); } function nettoyerTable() { fetch("php/nettoyerTable.php") .then(res => res.text()) .then(msg => { afficherMessage(msg); loadTable(); passerAuTourSuivant(); }); } function actualiserScorePartie() { //if (!window.jeuDemarre || !window.nomTable) return; fetch("php/getAllData.php") .then(res => res.text()) .then(text => { try { const data = JSON.parse(text); const lignes = data.filter(l => l.type === "score partie"); const total = lignes.reduce((s, l) => s + parseFloat(l.timestamp), 0); document.getElementById("valeurScorePartie").textContent = total.toFixed(0); } catch (err) { console.warn("⚠️ Erreur JSON probable dans getAllData.php :", err.message); } }) .catch(err => { console.warn("⚠️ Erreur fetch getAllData :", err.message); }); } // Expositions à la fin window.validerTempo = validerTempo; window.createTable = createTable; window.dropTable = dropTable; window.loadTable = loadTable; window.demarrer = demarrer; window.calculerScoreTour = calculerScoreTour; window.incrementerScorePartie = incrementerScorePartie; window.actualiserScorePartie = actualiserScorePartie; window.nettoyerTable = nettoyerTable; setInterval(loadTable, 500); setInterval(actualiserScorePartie, 500); setInterval(rafraichirRole, 1000); setInterval(() => { const bouton = document.getElementById("boutonCopierTable"); if (bouton) bouton.click(); }, 500); setInterval(() => { const btnImit = document.getElementById("boutonCopierTableImitateur"); if (btnImit) btnImit.click(); // ⬅️ 1re ligne }, 500); window.comparerEtScorer = comparerEtScorer; window.addEventListener("pagehide", function () { if (!window.joueur) return; navigator.sendBeacon("php/dropTable.php"); }); function setJoueur(nom) { window.joueur = nom; afficherIdentiteJoueur(); // ✅ Enregistrement dans la table SQL fetch("php/setJoueurCourant.php", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ joueur: nom }) }); const select = document.getElementById("joueurSelect"); if (select) select.value = nom; afficherMessage(`Vous êtes maintenant ${nom}`); } window.setJoueur = setJoueur; setInterval(afficherMessageJeu, 1000); function detecterIdentite() { fetch("php/getDernierJoueurCourant.php") .then(res => res.text()) .then(joueurDefini => { if (joueurDefini === "joueur1") { window.joueur = "joueur2"; afficherMessage("🧍 Vous êtes automatiquement joueur2 (détecté)"); afficherIdentiteJoueur(); } else if (joueurDefini === "joueur2") { window.joueur = "joueur1"; afficherMessage("🧍 Vous êtes automatiquement joueur1 (détecté)"); afficherIdentiteJoueur(); } else { afficherMessage("⏳ En attente que joueur1 initialise le jeu..."); setTimeout(detecterIdentite, 2000); } }) .catch(err => { console.warn("Erreur serveur :", err); setTimeout(detecterIdentite, 2000); }); } function afficherTableHorizontale() { /* 1) point de départ : la table verticale déjà construite */ const sourceTable = document.querySelector("#copieTableAffichage table"); const dest = document.getElementById("tableAffichageHoriz"); if (!sourceTable || !dest) { console.warn("Table source ou destination manquante."); return; } /* 2) récupérer tous les accords (on ignore la 1re ligne d’en-tête) */ const accords = Array.from(sourceTable.querySelectorAll("tr")) .slice(1) // ⤷ saute l’en-tête .map(tr => tr.cells[0]?.textContent.trim()) .filter(txt => txt.length); // enlève les lignes vides if (accords.length === 0) { dest.innerHTML = "(Aucun accord à afficher)"; return; } /* 3) créer la nouvelle table horizontale */ const newTable = document.createElement("table"); const row = document.createElement("tr"); accords.forEach(ac => { const td = document.createElement("td"); td.textContent = ac; row.appendChild(td); }); newTable.appendChild(row); /* 4) injecter le résultat */ dest.innerHTML = ""; // nettoie l’ancienne version dest.appendChild(newTable); // affiche la nouvelle } function afficherTableHorizontaleImitateur() { /* 1) point de départ : la table verticale déjà construite */ const sourceTable = document.querySelector("#copieTableAffichageImitateur table"); const dest = document.getElementById("tableAffichageHorizImitateur"); if (!sourceTable || !dest) { console.warn("Table source ou destination manquante."); return; } /* 2) récupérer tous les accords (on ignore la 1re ligne d’en-tête) */ const accords = Array.from(sourceTable.querySelectorAll("tr")) .slice(1) .map(tr => tr.cells[0]?.textContent.trim()) .filter(txt => txt.length); if (accords.length === 0) { dest.innerHTML = "(Aucun accord à afficher)"; return; } /* 3) créer la nouvelle table horizontale */ const newTable = document.createElement("table"); const row = document.createElement("tr"); accords.forEach(ac => { const td = document.createElement("td"); td.textContent = ac; row.appendChild(td); }); newTable.appendChild(row); /* 4) injecter le résultat */ dest.innerHTML = ""; dest.appendChild(newTable); } //if (typeof preparerDesactivationAuto === "function") { //preparerDesactivationAuto(); //} function configBoutonsDebutJeu() { afficherBoutons(["btnDebutJeu"]); masquerBoutons([ "btnDemarrer", "btnEcouter", "imiterBtn", "btnFinTour", "btnValiderTempo", "tempoInput" ]); } function configBoutonsModeleVsImitateur() { setTimeout(() => { const estModele = (window.modeleEstJoueur1 && window.joueur === "joueur1") || (!window.modeleEstJoueur1 && window.joueur === "joueur2"); if (estModele) { afficherBoutons(["btnDemarrer", "btnValiderTempo", "tempoInput"]); masquerBoutons(["btnDebutJeu", "btnEcouter", "imiterBtn", "btnFinTour"]); } else { afficherBoutons(["btnEcouter", "imiterBtn", "btnFinTour"]); masquerBoutons(["btnDebutJeu", "btnDemarrer", "btnValiderTempo", "tempoInput"]); } }, 800); // 0,5 s de délai } configBoutonsDebutJeu() (async function () { const BASE_URL = "sounds/xylophoneNotes/"; const NOTES = ['C', 'Cd', 'D', 'Dd', 'E', 'F', 'Fd', 'G', 'Gd', 'A', 'Ad', 'B']; const audioCtx = new (window.AudioContext || window.webkitAudioContext)(); const cache = {}; function midiNoteToName(n) { const note = NOTES[n % 12]; const octave = Math.floor(n / 12) - 1; return `${note}${octave}`; } async function loadSample(name) { const url = `${BASE_URL}Xylo_${name}.wav`; try { const res = await fetch(url); if (!res.ok) throw new Error(`404: ${url}`); const buf = await res.arrayBuffer(); cache[name] = await audioCtx.decodeAudioData(buf); console.log("✔ Préchargé :", name); } catch (err) { console.warn("⚠️ Erreur de chargement :", err.message); } } async function preloadAllSamples() { const promises = []; for (let n = 60; n <= 108; n++) { // MIDI notes de C4 à C8 const name = midiNoteToName(n); promises.push(loadSample(name)); } await Promise.all(promises); console.log("✅ Tous les sons de xylophone ont été chargés."); } function playNote(name) { const buf = cache[name]; if (!buf) return console.warn("❌ Non chargé :", name); const src = audioCtx.createBufferSource(); src.buffer = buf; src.connect(audioCtx.destination); src.start(); } function handleMessage(e) { const [cmd, note, velocity] = e.data; if ((cmd & 0xf0) === 0x90 && velocity > 0) { const name = midiNoteToName(note); playNote(name); } } // Activation du contexte audio si suspendu window.addEventListener("click", () => { if (audioCtx.state === "suspended") audioCtx.resume(); }); // Initialisation MIDI if (navigator.requestMIDIAccess) { const midi = await navigator.requestMIDIAccess(); for (const input of midi.inputs.values()) { input.addEventListener("midimessage", handleMessage); } console.log("🎹 MIDI prêt"); } else { console.warn("❌ Web MIDI non supporté"); } // Lancer le préchargement après un clic utilisateur (obligatoire pour AudioContext) window.addEventListener("click", async () => { if (!window.xyloCharged) { window.xyloCharged = true; await preloadAllSamples(); } }); })(); const bouton2 = document.getElementById("btnConversionTimestamps"); if (bouton2) { bouton2.addEventListener("click", conversionTimestampsEnBeats); } function conversionTimestampsEnBeats() { const tempo = window.tempo || 120; const beatMap = {}; const table = document.querySelector("table"); if (!table) { alert("Aucune table trouvée dans la page."); return; } const lignes = [...table.querySelectorAll("tbody tr")]; const headerRow = table.querySelector("thead tr"); // Ajout de la colonne "Beat" si absente if (headerRow && ![...headerRow.children].some(cell => cell.textContent.trim() === "Beat")) { const th = document.createElement("th"); th.textContent = "Beat"; headerRow.appendChild(th); } lignes.forEach(ligne => { const cellules = [...ligne.querySelectorAll("td")]; const id = cellules[0]?.textContent?.trim(); const timestampStr = cellules[2]?.textContent?.trim(); const timestamp = parseFloat(timestampStr); let beatTime = ""; if (id && Number.isFinite(timestamp)) { beatTime = timestamp * tempo / 60; beatMap[id] = beatTime; } // Ajout ou mise à jour de la cellule "Beat" if (cellules.length >= 4) { cellules[3].textContent = Number.isFinite(beatTime) ? beatTime.toFixed(12) : ""; } else { const newCell = document.createElement("td"); newCell.textContent = Number.isFinite(beatTime) ? beatTime.toFixed(12) : ""; ligne.appendChild(newCell); } }); // Stockage global window.timestampBeats = beatMap; console.log("Conversion réussie. timestampBeats =", window.timestampBeats); } window.conversionTimestampsEnBeats = conversionTimestampsEnBeats; document.addEventListener("DOMContentLoaded", () => { const bouton = document.getElementById("btnConversionTimestamps"); if (bouton) { bouton.addEventListener("click", conversionTimestampsEnBeats); } }); document.addEventListener("DOMContentLoaded", () => { document.getElementById("btnExporterTables").addEventListener("click", () => { const tables = document.querySelectorAll("table"); const exportData = {}; tables.forEach((table, index) => { const tableName = table.getAttribute("id") || `table_${index + 1}`; const headers = Array.from(table.querySelectorAll("thead th")).map(th => th.textContent.trim()); const rows = Array.from(table.querySelectorAll("tbody tr")); const rowData = rows.map(tr => { const cells = Array.from(tr.querySelectorAll("td")); const rowObj = {}; cells.forEach((td, i) => { rowObj[headers[i] || `col${i + 1}`] = td.textContent.trim(); }); return rowObj; }); exportData[tableName] = rowData; }); const blob = new Blob([JSON.stringify(exportData, null, 2)], { type: "application/json" }); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = "export_tables.json"; document.body.appendChild(a); a.click(); a.remove(); URL.revokeObjectURL(url); }); });