This commit is contained in:
skelbon 2024-07-17 15:05:41 +01:00
parent eced53d390
commit 7a3ab4dff7
3 changed files with 223 additions and 184 deletions

View File

@ -1,142 +1,144 @@
class EventEmitter { class EventEmitter {
constructor() { constructor() {
this.events = {}; this.events = {};
} }
on(event, listener) { on(event, listener) {
if (!this.events[event]) { if (!this.events[event]) {
this.events[event] = []; this.events[event] = [];
} }
this.events[event].push(listener); this.events[event].push(listener);
} }
emit(event, ...args) { emit(event, ...args) {
if (this.events[event]) { if (this.events[event]) {
this.events[event].forEach(listener => listener(...args)); this.events[event].forEach((listener) => listener(...args));
} }
} }
removeAllListeners() { removeAllListeners() {
this.events = {}; this.events = {};
} }
} }
// Convert Node.js module system to ES6 module system // Convert Node.js module system to ES6 module system
export class NotConnectedError extends Error { export class NotConnectedError extends Error {
constructor(client) { constructor(client) {
super("Client not connected"); super("Client not connected");
this.client = client; this.client = client;
} }
} }
export const Polarity = { export const Polarity = {
Negative: 0, Negative: 0,
Positive: 1, Positive: 1,
}; };
export const Region = { export const Region = {
Unknown: 0, Unknown: 0,
Europe: 1, Europe: 1,
Oceania: 2, Oceania: 2,
NorthAmerica: 3, NorthAmerica: 3,
Asia: 4, Asia: 4,
SouthAmerica: 5, SouthAmerica: 5,
}; };
export class Client extends EventEmitter { export class Client extends EventEmitter {
constructor(socketFactory) { constructor(socketFactory) {
super(); super();
this.socketFactory = socketFactory; this.socketFactory = socketFactory;
} }
getSocket() { getSocket() {
return this.socket; return this.socket;
} }
connect(url = this.generateRandomConnectionUrl()) { connect(url = this.generateRandomConnectionUrl()) {
const socket = (this.socket = this.socketFactory.make(url)); const socket = (this.socket = this.socketFactory.make(url));
socket.onmessage = (event) => { socket.onmessage = (event) => {
const rawData = this.decode(event.data); const rawData = this.decode(event.data);
this.emit("data", this.buildStrikeData(JSON.parse(rawData))); this.emit("data", this.buildStrikeData(JSON.parse(rawData)));
}; };
socket.onopen = () => { socket.onopen = () => {
this.sendJSON({ a: 111 }); this.sendJSON({ a: 111 });
this.emit("connect", socket); this.emit("connect", socket);
}; };
socket.onerror = (err) => this.emit("error", err); socket.onerror = (err) => this.emit("error", err);
} }
close() { close() {
if (!this.socket) { if (!this.socket) {
throw new NotConnectedError(this); throw new NotConnectedError(this);
} }
this.socket.close(); this.socket.close();
this.removeAllListeners(); this.removeAllListeners();
} }
processRawLocation(location) { processRawLocation(location) {
return { return {
latitude: location.lat, latitude: location.lat,
longitude: location.lon, longitude: location.lon,
altitude: location.alt, altitude: location.alt,
}; };
} }
buildStrikeData(strike) { buildStrikeData(strike) {
return { return {
location: this.processRawLocation(strike), location: this.processRawLocation(strike),
deviation: strike.mds, deviation: strike.mds,
delay: strike.delay, delay: strike.delay,
time: new Date(Math.floor(strike.time / 1e6)), time: new Date(Math.floor(strike.time / 1e6)),
detectors: strike.sig.map((rawDec) => ({ detectors: strike.sig.map((rawDec) => ({
id: rawDec.sta, id: rawDec.sta,
location: this.processRawLocation(rawDec), location: this.processRawLocation(rawDec),
status: rawDec.status, status: rawDec.status,
delay: rawDec.time, delay: rawDec.time,
})), })),
polarity: strike.pol > 0 ? Polarity.Positive : Polarity.Negative, polarity: strike.pol > 0 ? Polarity.Positive : Polarity.Negative,
maxDeviation: strike.mds, maxDeviation: strike.mds,
maxCircularGap: strike.mcg, maxCircularGap: strike.mcg,
region: 1 <= strike.region && strike.region <= 5 ? strike.region : Region.Unknown, region: strike.region,
}; };
} }
decode(b) { decode(b) {
let a, let a,
e = {}, e = {},
d = b.split(""), d = b.split(""),
c = d[0], c = d[0],
f = c, f = c,
g = [c], g = [c],
h = 256, h = 256,
o = h; o = h;
for (let i = 1; i < d.length; i++) { for (let i = 1; i < d.length; i++) {
let charCode = d[i].charCodeAt(0); let charCode = d[i].charCodeAt(0);
a = h > charCode ? d[i] : e[charCode] ? e[charCode] : f + c; a = h > charCode ? d[i] : e[charCode] ? e[charCode] : f + c;
g.push(a); g.push(a);
c = a.charAt(0); c = a.charAt(0);
e[o] = f + c; e[o] = f + c;
o++; o++;
f = a; f = a;
} }
return g.join(""); return g.join("");
} }
sendJSON(data) { sendJSON(data) {
this.socket.send(JSON.stringify(data)); this.socket.send(JSON.stringify(data));
} }
generateRandomConnectionUrl() { generateRandomConnectionUrl() {
const knownServerIds = [1, 7, 8]; const knownServerIds = [1, 7, 8];
return `wss://ws${knownServerIds[Math.floor(Math.random() * knownServerIds.length)]}.blitzortung.org:443/`; return `wss://ws${
} knownServerIds[Math.floor(Math.random() * knownServerIds.length)]
}.blitzortung.org:443/`;
}
} }
export class BrowserSocketFactory { export class BrowserSocketFactory {
make(url) { make(url) {
return new WebSocket(url); return new WebSocket(url);
} }
} }

67
src/instruments.js Normal file
View File

@ -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;
}

View File

@ -1,68 +1,19 @@
import { Client, BrowserSocketFactory } from "./blitz.js"; 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 audioContext = new (window.AudioContext || window.webkitAudioContext)();
const socketFactory = new BrowserSocketFactory(); const socketFactory = new BrowserSocketFactory();
const client = new Client(socketFactory); const client = new Client(socketFactory);
client.connect(); client.connect();
const instruments = [ let currStaveNumber = 1;
{ 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",
},
];
(async () => { console.log(await loadInstruments());
for (let i = 0; i < instruments.length; i++) {
try { const instruments = await loadInstruments();
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}`);
}
}
})();
const numStaves = 6; const numStaves = 6;
const numFiles = instruments.length;
const sheetWindow = document.getElementById("music-window"); const sheetWindow = document.getElementById("music-window");
for (let i = 1; i <= numStaves; i++) { for (let i = 1; i <= numStaves; i++) {
@ -82,29 +33,36 @@ document.addEventListener("DOMContentLoaded", () => {
const timeIndicator = document.querySelector("#time-indicator"); const timeIndicator = document.querySelector("#time-indicator");
timeIndicator.addEventListener("animationiteration", () => { timeIndicator.addEventListener("animationiteration", () => {
const icons = document.querySelectorAll(".event-icon"); currStaveNumber++;
if (currStaveNumber > numStaves) {
const icons = document.querySelectorAll(".event-icon");
icons.forEach((icon) => { icons.forEach((icon) => {
icon.classList.add("fade-out"); icon.classList.add("fade-out");
icon.addEventListener("transitionend", () => { icon.addEventListener("transitionend", () => {
icon.remove(); icon.remove();
});
}); });
}); }
}); });
const playSound = (num) => { const playSound = (instrument) => {
const source = audioContext.createBufferSource(); const source = audioContext.createBufferSource();
const rando = Math.floor(Math.random() * instruments.length); const samples = instrument.samples;
source.buffer = instruments[num].soundBuffer; // Example: Play the 'Gong' sound // 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.connect(audioContext.destination);
source.start(); source.start();
}; };
const placeIcon = (region) => { const placeIcon = (instrument) => {
// create random numbers if (currStaveNumber > numStaves) currStaveNumber = 1;
const randomStaveNumber = Math.floor((region % 6) + 1);
const randomFileNumber = Math.floor(region);
// Determine the position of the time indicator // Determine the position of the time indicator
const rect = timeIndicator.getBoundingClientRect(); const rect = timeIndicator.getBoundingClientRect();
const sheetLeft = sheetWindow.getBoundingClientRect().left; const sheetLeft = sheetWindow.getBoundingClientRect().left;
@ -115,12 +73,12 @@ document.addEventListener("DOMContentLoaded", () => {
newObject.classList.add("event-icon"); newObject.classList.add("event-icon");
newObject.style.position = "absolute"; newObject.style.position = "absolute";
newObject.style.left = `${xPosition}px`; newObject.style.left = `${xPosition}px`;
newObject.style.top = `${instruments[randomFileNumber].yPos}`; newObject.style.top = `${instrument.yPos}`;
// create the icon // create the icon
const eventIcon = document.createElement("object"); const eventIcon = document.createElement("object");
eventIcon.type = "image/svg+xml"; eventIcon.type = "image/svg+xml";
eventIcon.data = `assets/svg/${instruments[randomFileNumber].image}`; eventIcon.data = instrument.image;
eventIcon.style.width = "35px"; eventIcon.style.width = "35px";
eventIcon.style.height = "35px"; eventIcon.style.height = "35px";
@ -129,15 +87,27 @@ document.addEventListener("DOMContentLoaded", () => {
// select the staveWrapper in which to place the div // select the staveWrapper in which to place the div
const staveWrapper = document.getElementById( const staveWrapper = document.getElementById(
`stave-wrapper-${randomStaveNumber}` `stave-wrapper-${currStaveNumber}`
); );
// append the div to the staveWrapper // append the div to the staveWrapper
staveWrapper.appendChild(newObject); staveWrapper.appendChild(newObject);
}; };
let strikeNumber = 0;
client.on("data", (strike) => { client.on("data", (strike) => {
placeIcon(strike.region % 9); strikeNumber++;
playSound(strike.region % 9); 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;
}
}); });
}); });