changes
This commit is contained in:
parent
eced53d390
commit
7a3ab4dff7
222
src/blitz.js
222
src/blitz.js
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
118
src/script.js
118
src/script.js
|
|
@ -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;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue