From 7a3ab4dff741a9301cdc6717450e60ac326df29d Mon Sep 17 00:00:00 2001 From: skelbon Date: Wed, 17 Jul 2024 15:05:41 +0100 Subject: [PATCH] changes --- src/blitz.js | 222 +++++++++++++++++++++++---------------------- src/instruments.js | 67 ++++++++++++++ src/script.js | 118 +++++++++--------------- 3 files changed, 223 insertions(+), 184 deletions(-) create mode 100644 src/instruments.js diff --git a/src/blitz.js b/src/blitz.js index 4f45895..4727a89 100644 --- a/src/blitz.js +++ b/src/blitz.js @@ -1,142 +1,144 @@ class EventEmitter { - constructor() { - this.events = {}; - } + constructor() { + this.events = {}; + } - on(event, listener) { - if (!this.events[event]) { - this.events[event] = []; - } - this.events[event].push(listener); - } + on(event, listener) { + if (!this.events[event]) { + this.events[event] = []; + } + this.events[event].push(listener); + } - emit(event, ...args) { - if (this.events[event]) { - this.events[event].forEach(listener => listener(...args)); - } - } + emit(event, ...args) { + if (this.events[event]) { + this.events[event].forEach((listener) => listener(...args)); + } + } - removeAllListeners() { - this.events = {}; - } + removeAllListeners() { + this.events = {}; + } } // Convert Node.js module system to ES6 module system export class NotConnectedError extends Error { - constructor(client) { - super("Client not connected"); - this.client = client; - } + constructor(client) { + super("Client not connected"); + this.client = client; + } } export const Polarity = { - Negative: 0, - Positive: 1, + Negative: 0, + Positive: 1, }; export const Region = { - Unknown: 0, - Europe: 1, - Oceania: 2, - NorthAmerica: 3, - Asia: 4, - SouthAmerica: 5, + Unknown: 0, + Europe: 1, + Oceania: 2, + NorthAmerica: 3, + Asia: 4, + SouthAmerica: 5, }; export class Client extends EventEmitter { - constructor(socketFactory) { - super(); - this.socketFactory = socketFactory; - } + constructor(socketFactory) { + super(); + this.socketFactory = socketFactory; + } - getSocket() { - return this.socket; - } + getSocket() { + return this.socket; + } - connect(url = this.generateRandomConnectionUrl()) { - const socket = (this.socket = this.socketFactory.make(url)); - socket.onmessage = (event) => { - const rawData = this.decode(event.data); + connect(url = this.generateRandomConnectionUrl()) { + const socket = (this.socket = this.socketFactory.make(url)); + socket.onmessage = (event) => { + const rawData = this.decode(event.data); - this.emit("data", this.buildStrikeData(JSON.parse(rawData))); - }; - socket.onopen = () => { - this.sendJSON({ a: 111 }); - this.emit("connect", socket); - }; - socket.onerror = (err) => this.emit("error", err); - } + this.emit("data", this.buildStrikeData(JSON.parse(rawData))); + }; + socket.onopen = () => { + this.sendJSON({ a: 111 }); + this.emit("connect", socket); + }; + socket.onerror = (err) => this.emit("error", err); + } - close() { - if (!this.socket) { - throw new NotConnectedError(this); - } - this.socket.close(); - this.removeAllListeners(); - } + close() { + if (!this.socket) { + throw new NotConnectedError(this); + } + this.socket.close(); + this.removeAllListeners(); + } - processRawLocation(location) { - return { - latitude: location.lat, - longitude: location.lon, - altitude: location.alt, - }; - } + processRawLocation(location) { + return { + latitude: location.lat, + longitude: location.lon, + altitude: location.alt, + }; + } - buildStrikeData(strike) { - return { - location: this.processRawLocation(strike), - deviation: strike.mds, - delay: strike.delay, - time: new Date(Math.floor(strike.time / 1e6)), - detectors: strike.sig.map((rawDec) => ({ - id: rawDec.sta, - location: this.processRawLocation(rawDec), - status: rawDec.status, - delay: rawDec.time, - })), - polarity: strike.pol > 0 ? Polarity.Positive : Polarity.Negative, - maxDeviation: strike.mds, - maxCircularGap: strike.mcg, - region: 1 <= strike.region && strike.region <= 5 ? strike.region : Region.Unknown, - }; - } + buildStrikeData(strike) { + return { + location: this.processRawLocation(strike), + deviation: strike.mds, + delay: strike.delay, + time: new Date(Math.floor(strike.time / 1e6)), + detectors: strike.sig.map((rawDec) => ({ + id: rawDec.sta, + location: this.processRawLocation(rawDec), + status: rawDec.status, + delay: rawDec.time, + })), + polarity: strike.pol > 0 ? Polarity.Positive : Polarity.Negative, + maxDeviation: strike.mds, + maxCircularGap: strike.mcg, + region: strike.region, + }; + } - decode(b) { - let a, - e = {}, - d = b.split(""), - c = d[0], - f = c, - g = [c], - h = 256, - o = h; + decode(b) { + let a, + e = {}, + d = b.split(""), + c = d[0], + f = c, + g = [c], + h = 256, + o = h; - for (let i = 1; i < d.length; i++) { - let charCode = d[i].charCodeAt(0); - a = h > charCode ? d[i] : e[charCode] ? e[charCode] : f + c; - g.push(a); - c = a.charAt(0); - e[o] = f + c; - o++; - f = a; - } + for (let i = 1; i < d.length; i++) { + let charCode = d[i].charCodeAt(0); + a = h > charCode ? d[i] : e[charCode] ? e[charCode] : f + c; + g.push(a); + c = a.charAt(0); + e[o] = f + c; + o++; + f = a; + } - return g.join(""); - } + return g.join(""); + } - sendJSON(data) { - this.socket.send(JSON.stringify(data)); - } + sendJSON(data) { + this.socket.send(JSON.stringify(data)); + } - generateRandomConnectionUrl() { - const knownServerIds = [1, 7, 8]; - return `wss://ws${knownServerIds[Math.floor(Math.random() * knownServerIds.length)]}.blitzortung.org:443/`; - } + generateRandomConnectionUrl() { + const knownServerIds = [1, 7, 8]; + return `wss://ws${ + knownServerIds[Math.floor(Math.random() * knownServerIds.length)] + }.blitzortung.org:443/`; + } } export class BrowserSocketFactory { - make(url) { - return new WebSocket(url); - } + make(url) { + return new WebSocket(url); + } } diff --git a/src/instruments.js b/src/instruments.js new file mode 100644 index 0000000..39f25e8 --- /dev/null +++ b/src/instruments.js @@ -0,0 +1,67 @@ +async function loadInstrumentsConfig() { + const response = await fetch("/assets/instruments/instruments.json"); + return await response.json(); +} + +async function fetchAudioData(url) { + const response = await fetch(url); + const arrayBuffer = await response.arrayBuffer(); + return arrayBuffer; +} + +async function decodeAudioData(context, arrayBuffer) { + return new Promise((resolve, reject) => { + context.decodeAudioData(arrayBuffer, resolve, reject); + }); +} + +async function loadAudioSamples(context, directory) { + const response = await fetch(directory); + const text = await response.text(); + const parser = new DOMParser(); + const doc = parser.parseFromString(text, "text/html"); + const audioFiles = Array.from(doc.querySelectorAll("a")) // This may need changing depending on server + .map((link) => link.title) + .filter((href) => href.endsWith(".mp3") || href.endsWith(".wav")); + + const audioBuffers = {}; + for (const file of audioFiles) { + const arrayBuffer = await fetchAudioData(`${directory}/${file}`); + const audioBuffer = await decodeAudioData(context, arrayBuffer); + const fileName = file.split("/").pop(); // Extract filename without path for reference + audioBuffers[fileName] = audioBuffer; + } + + return audioBuffers; +} + +async function fetchImageData(url) { + const response = await fetch(url); + const blob = await response.blob(); + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onloadend = () => resolve(reader.result); + reader.onerror = reject; + reader.readAsDataURL(blob); // Convert to base64 + }); +} + +export async function loadInstruments() { + const instrumentsConfig = await loadInstrumentsConfig(); + const context = new (window.AudioContext || window.webkitAudioContext)(); + const instruments = {}; + + for (const [instrument, config] of Object.entries(instrumentsConfig)) { + const samples = await loadAudioSamples(context, config.directory); + const imageData = await fetchImageData( + `${config.directory}/${config.image.filename}` + ); + instruments[instrument] = { + samples: samples, + image: imageData, + yPos: config.image.yPos, // Store base64-encoded image data + }; + } + + return instruments; +} diff --git a/src/script.js b/src/script.js index f272cbd..5f6ed9a 100644 --- a/src/script.js +++ b/src/script.js @@ -1,68 +1,19 @@ import { Client, BrowserSocketFactory } from "./blitz.js"; +import { loadInstruments } from "./instruments.js"; -document.addEventListener("DOMContentLoaded", () => { +document.addEventListener("DOMContentLoaded", async () => { const audioContext = new (window.AudioContext || window.webkitAudioContext)(); const socketFactory = new BrowserSocketFactory(); const client = new Client(socketFactory); client.connect(); - const instruments = [ - { image: "Gong.svg", yPos: "20%", sample: "Gong.mp3" }, - { - image: "Bass.svg", - yPos: "10%", - sample: "Bass.mp3", - }, - { - image: "Snare.svg", - yPos: "20%", - sample: "Snare.mp3", - }, - { image: "Surdo Napa.svg", yPos: "30%", sample: "Surdo.mp3" }, - { - image: "Surdo.svg", - yPos: "25%", - sample: "Surdo.mp3", - }, - { - image: "Timpani Large.svg", - yPos: "40%", - sample: "Timpani.wav", - }, - { - image: "Timpani Small.svg", - yPos: "30%", - sample: "Timpani.wav", - }, - { - image: "Toms.svg", - yPos: "25%", - sample: "Toms.mp3", - }, - { - image: "Tubular-Bells.svg", - yPos: "0%", - sample: "Tubular-Bells.wav", - }, - ]; + let currStaveNumber = 1; - (async () => { - for (let i = 0; i < instruments.length; i++) { - try { - const response = await fetch(`/assets/sounds/${instruments[i].sample}`); - const arrayBuffer = await response.arrayBuffer(); - const audioBuffer = await audioContext.decodeAudioData(arrayBuffer); - instruments[i][`soundBuffer`] = audioBuffer; - console.log(`Loaded ${i}`); - console.log(instruments[i][`soundBuffer`]); - } catch (error) { - console.error(`Failed to load ${i}: ${error.message}`); - } - } - })(); + console.log(await loadInstruments()); + + const instruments = await loadInstruments(); const numStaves = 6; - const numFiles = instruments.length; const sheetWindow = document.getElementById("music-window"); for (let i = 1; i <= numStaves; i++) { @@ -82,29 +33,36 @@ document.addEventListener("DOMContentLoaded", () => { const timeIndicator = document.querySelector("#time-indicator"); timeIndicator.addEventListener("animationiteration", () => { - const icons = document.querySelectorAll(".event-icon"); + currStaveNumber++; + if (currStaveNumber > numStaves) { + const icons = document.querySelectorAll(".event-icon"); - icons.forEach((icon) => { - icon.classList.add("fade-out"); - icon.addEventListener("transitionend", () => { - icon.remove(); + icons.forEach((icon) => { + icon.classList.add("fade-out"); + icon.addEventListener("transitionend", () => { + icon.remove(); + }); }); - }); + } }); - const playSound = (num) => { + const playSound = (instrument) => { const source = audioContext.createBufferSource(); - const rando = Math.floor(Math.random() * instruments.length); - source.buffer = instruments[num].soundBuffer; // Example: Play the 'Gong' sound + const samples = instrument.samples; + // console.log(samples); + const sampleKeys = Object.keys(samples); + const numSamples = sampleKeys.length; + // console.log(numSamples); + const ramdomKey = sampleKeys[Math.floor(Math.random() * (numSamples - 1))]; + console.log(ramdomKey); + + source.buffer = samples[`${ramdomKey}`]; // Example: Play the 'Gong' sound source.connect(audioContext.destination); source.start(); }; - const placeIcon = (region) => { - // create random numbers - const randomStaveNumber = Math.floor((region % 6) + 1); - const randomFileNumber = Math.floor(region); - + const placeIcon = (instrument) => { + if (currStaveNumber > numStaves) currStaveNumber = 1; // Determine the position of the time indicator const rect = timeIndicator.getBoundingClientRect(); const sheetLeft = sheetWindow.getBoundingClientRect().left; @@ -115,12 +73,12 @@ document.addEventListener("DOMContentLoaded", () => { newObject.classList.add("event-icon"); newObject.style.position = "absolute"; newObject.style.left = `${xPosition}px`; - newObject.style.top = `${instruments[randomFileNumber].yPos}`; + newObject.style.top = `${instrument.yPos}`; // create the icon const eventIcon = document.createElement("object"); eventIcon.type = "image/svg+xml"; - eventIcon.data = `assets/svg/${instruments[randomFileNumber].image}`; + eventIcon.data = instrument.image; eventIcon.style.width = "35px"; eventIcon.style.height = "35px"; @@ -129,15 +87,27 @@ document.addEventListener("DOMContentLoaded", () => { // select the staveWrapper in which to place the div const staveWrapper = document.getElementById( - `stave-wrapper-${randomStaveNumber}` + `stave-wrapper-${currStaveNumber}` ); // append the div to the staveWrapper staveWrapper.appendChild(newObject); }; + let strikeNumber = 0; + client.on("data", (strike) => { - placeIcon(strike.region % 9); - playSound(strike.region % 9); + strikeNumber++; + if (strikeNumber == 3) { + 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; + } }); });