import { Client, BrowserSocketFactory } from "./blitz.js"; import { loadInstruments } from "./instruments.js"; import { sendMidiMessage } from "./midi.js"; document.addEventListener("DOMContentLoaded", async () => { let showing; let audioContext; let interacted = true; let gainNode; sendMidiMessage(); function startAudio() { audioContext = new (window.AudioContext || window.webkitAudioContext)(); gainNode = audioContext.createGain(); gainNode.gain.value = 0; gainNode.connect(audioContext.destination); } function muter() { fadeOutVolume(); } function unMute() { startAudio(); fadeInVolume(); } function padZero(num) { return num.toString().padStart(2, "0"); } function setTitle() { const now = new Date(); const future = new Date(now.getTime() + 59 * 1000); // 59 seconds later const formattedDate = `${padZero(now.getDate())}.${padZero( now.getMonth() + 1 )}.${now.getFullYear()}`; const formattedTime = `${padZero(now.getHours())}:${padZero( now.getMinutes() )}:${padZero(now.getSeconds())}`; const formattedFutureTime = `${padZero(future.getHours())}:${padZero( future.getMinutes() )}:${padZero(future.getSeconds())}`; title.innerHTML = `${formattedDate}${formattedTime} - ${formattedFutureTime} UTC`; } function fadeInVolume() { gainNode.gain.setValueAtTime(0, audioContext.currentTime); // Start at 0 gainNode.gain.linearRampToValueAtTime(1, audioContext.currentTime + 2); // Fade to full volume } function fadeOutVolume() { gainNode.gain.setValueAtTime(1, audioContext.currentTime); // Start at 1 gainNode.gain.linearRampToValueAtTime(0, audioContext.currentTime + 2); // Fade to 0 } function showMainContent() { setTitle(); const mute = document.getElementById("mute"); mute.addEventListener("click", () => { const mutedSrc = "/assets/svg/sound-on.svg"; const unMutedSrc = "/assets/svg/mute.svg"; interacted = !interacted; interacted ? unMute() : muter(); mute.src = interacted ? mutedSrc : unMutedSrc; startAudio(); fadeInVolume(); }); fadeAndReplaceADiv("main-content", "loading-screen"); fadeAndReplaceADiv("buttons", "buttons"); showing = "main-content"; document.getElementById("menu-button").onclick = function () { toggleInfo(); }; } function toggleInfo() { if (showing === "main-content") { fadeAndReplaceADiv("about-content", "main-content"); cleanUpAndRestart(); showing = "about-content"; } else { fadeAndReplaceADiv("main-content", "about-content"); showing = "main-content"; } } function fadeAndReplaceADiv(innyDivId, outtyDivId) { const outty = document.getElementById(outtyDivId); const inny = document.getElementById(innyDivId); outty.classList.remove("fade-in"); outty.classList.add("fade-out"); outty.style.display = "none"; inny.style.display = "flex"; inny.style.opacity = "0"; void inny.offsetWidth; inny.classList.add("fade-in"); inny.style.opacity = "1"; } const socketFactory = new BrowserSocketFactory(); let client = new Client(socketFactory); client.connect(); let connectionTimeout; // Timeout for detecting inactivity let lastReceived = Date.now(); // Tracks the last time data was received function resetTimeout() { clearTimeout(connectionTimeout); connectionTimeout = setTimeout(() => { // 15 seconds of inactivity console.log("No data received in 15 seconds, reconnecting..."); reconnectWebSocket(); }, 15000); } function reconnectWebSocket() { console.log("Reconnecting WebSocket..."); // Clean up the existing WebSocket connection if (client) { client.close(); // Ensure the current connection is closed } // Create a new client instance and re-establish the connection client = new Client(new BrowserSocketFactory()); setupWebSocketListeners(); // Set up listeners for the new WebSocket client.connect(); // Reconnect // Reset timeout after reconnecting resetTimeout(); // Ensure timeout is re-established after reconnection } function setupWebSocketListeners() { client.removeAllListeners("data"); client.removeAllListeners("error"); client.removeAllListeners("close"); client.on("data", (strike) => { lastReceived = Date.now(); // Update the last received timestamp resetTimeout(); // Reset the timeout on any data received // Process the strike (existing logic from your original code) strikeNumber++; if (strikeNumber == 1) { const randomInstrumentNumber = Math.floor( Math.random() * (Object.keys(instruments).length - 1) ); const selectedInstrumentName = Object.keys(instruments)[randomInstrumentNumber]; placeIcon(instruments[selectedInstrumentName]); playSound(instruments[selectedInstrumentName]); strikeNumber = 0; } }); client.on("error", (message) => { console.log("WebSocket error:", message); reconnectWebSocket(); }); client.on("close", () => { console.log("WebSocket closed, attempting to reconnect..."); reconnectWebSocket(); }); } let currStaveNumber = 1; const instruments = await loadInstruments(); const instNames = Object.keys(instruments); showMainContent(); /*** * Add the instrument images and names into the about section */ const instrumentsKey = document.getElementById("instrument-key-div"); for (const instrument of instNames) { const keyDiv = document.createElement("div"); const keySvg = document.createElement("object"); const keyName = document.createElement("div"); keyName.innerText = instrument.toUpperCase(); keySvg.type = "image/svg+xml"; keySvg.data = instruments[instrument].image; keyDiv.appendChild(keySvg); keyDiv.appendChild(keyName); keyDiv.classList.add("instrument-key"); instrumentsKey.appendChild(keyDiv); } const numStaves = 6; const sheetWindow = document.getElementById("music-window"); for (let i = 1; i <= numStaves; i++) { const staveWrapper = document.createElement("div"); staveWrapper.classList.add(`stave-wrapper`); staveWrapper.setAttribute("id", `stave-wrapper-${i}`); staveWrapper.style.position = "relative"; const staveObject = document.createElement("object"); staveObject.type = "image/svg+xml"; staveObject.data = "assets/svg/stave.svg"; staveObject.classList.add("stave-svg"); staveWrapper.appendChild(staveObject); sheetWindow.appendChild(staveWrapper); } const timeIndicator = document.querySelector("#time-indicator"); timeIndicator.addEventListener("animationiteration", () => { currStaveNumber++; if (currStaveNumber > numStaves) { cleanUpAndRestart(); } }); const cleanUpAndRestart = (reload = false) => { const icons = document.querySelectorAll(".event-icon"); const timeIndicator = document.getElementById("time-indicator"); icons.forEach((icon) => { icon.classList.add("fade-out"); icon.addEventListener("transitionend", () => { icon.remove(); }); }); currStaveNumber = 1; setTitle(); if (reload) location.reload(); }; const playSound = (instrument) => { if (interacted) { // const source = audioContext.createBufferSource(); const samples = instrument.samples; const sampleKeys = Object.keys(samples); const numSamples = sampleKeys.length; const randomSampleNumber = Math.floor(Math.random() * (numSamples - 1)); // const randomKey = // sampleKeys[randomSampleNumber]; sendMidiMessage(instrument.midiChannelName, randomSampleNumber); // source.buffer = samples[`${randomKey}`]; // source.connect(gainNode); // source.start(); } }; const placeIcon = (instrument) => { if (currStaveNumber > numStaves) currStaveNumber = 1; // Select the staveWrapper in which to place the div const staveWrapper = document.getElementById( `stave-wrapper-${currStaveNumber}` ); // Determine the position of the time indicator const rect = timeIndicator.getBoundingClientRect(); const sheetLeft = sheetWindow.getBoundingClientRect().left; const xPosition = rect.left + window.scrollX - sheetLeft; // Create the icon div and give it its location data const newObject = document.createElement("div"); newObject.classList.add("event-icon"); newObject.style.position = "absolute"; newObject.style.left = `${ xPosition - document.documentElement.clientWidth / 200 }px`; newObject.style.top = `${instrument.yPos}`; // Create the icon const eventIcon = document.createElement("object"); eventIcon.type = "image/svg+xml"; eventIcon.data = instrument.image; // Append icon to div newObject.appendChild(eventIcon); // Append the div to the staveWrapper staveWrapper.appendChild(newObject); }; let strikeNumber = 0; setupWebSocketListeners(); // Setup the WebSocket listeners resetTimeout(); // Start the timeout timer });