moar refactoring, fix some minor bugs with loading, update readme

This commit is contained in:
Silas 2022-03-18 17:34:01 -04:00
parent 53efb64c84
commit 154ca4f8f2
Signed by: silentsilas
GPG Key ID: 4199EFB7DAA34349
11 changed files with 121 additions and 147 deletions

View File

@ -1,6 +1,6 @@
## ThreeJS Audio Visualizer ## ThreeJS Audio Visualizer
Currently a work in progress. It will be able to take in an MP3 file, and distort the imported 3D hand model along to the highs and lows. A virtual spoken word exhibit. Objects animate along to the frequencies of the background music while a hand model distorts to the volume of the spoken word.
## Development ## Development

View File

@ -34,7 +34,7 @@
</head> </head>
<body style="margin: 0; background: black"> <body style="margin: 0; background: black">
<div id="container"> <div id="container">
<div id="loader">Loading model: 0%</div> <div id="loader"></div>
<div id="startButton" style="display: none">BEGIN</div> <div id="startButton" style="display: none">BEGIN</div>
<div <div

View File

@ -1,6 +1,18 @@
import { AudioAnalyser, AudioListener } from "three"; import { AudioAnalyser, AudioListener } from "three";
import { LoadAudio } from "../audio"; import { LoadAudio } from "../utils/loaders";
type SRTParser = {
(srt: string): SRT[];
};
type SRT = {
id: number;
start: number;
end: number;
text: string;
};
type ClickCallback = () => void;
export class Jukebox { export class Jukebox {
parser: SRTParser; parser: SRTParser;
listener: AudioListener; listener: AudioListener;
@ -26,14 +38,14 @@ export class Jukebox {
const music = await LoadAudio( const music = await LoadAudio(
this.listener, this.listener,
"/static/music/space_chillout.mp3", "/static/music/space_chillout.mp3",
1, 0.5,
"music", "music",
true false
); );
const poem = await LoadAudio( const poem = await LoadAudio(
this.listener, this.listener,
"/static/music/who_am_i.mp3", "/static/music/who_am_i.mp3",
1, 0.7,
"poem", "poem",
false false
); );
@ -43,13 +55,11 @@ export class Jukebox {
const poemAnalyser = new AudioAnalyser(poem, 512); const poemAnalyser = new AudioAnalyser(poem, 512);
const startButton = document.getElementById("startButton"); const startButton = document.getElementById("startButton");
startButton.style.display = "block";
startButton.addEventListener("click", () => { startButton.addEventListener("click", () => {
silence.play(); silence.play();
music.play(); music.play();
poem.play(); poem.play();
// CORE.playing = true;
startButton.remove(); startButton.remove();
clickCallback(); clickCallback();
}); });
@ -79,16 +89,3 @@ export class Jukebox {
} }
} }
} }
type SRTParser = {
(srt: string): SRT[];
};
type SRT = {
id: number;
start: number;
end: number;
text: string;
};
type ClickCallback = () => void;

View File

@ -6,8 +6,16 @@ import {
SphereBufferGeometry, SphereBufferGeometry,
Vector3, Vector3,
} from "three"; } from "three";
import { calculateSoundAverages } from "../audio"; import { calculateSoundAverages } from "../utils/audioUtils";
export interface Planet extends Mesh {
geometry: SphereBufferGeometry;
material: MeshToonMaterial;
originalPosition?: Vector3;
offsetY?: number;
angleSpeed?: number;
angle?: number;
}
export class Planets { export class Planets {
meshes: Array<Planet>; meshes: Array<Planet>;
analyser: AudioAnalyser; analyser: AudioAnalyser;
@ -56,12 +64,3 @@ export class Planets {
}); });
} }
} }
export interface Planet extends Mesh {
geometry: SphereBufferGeometry;
material: MeshToonMaterial;
originalPosition?: Vector3;
offsetY?: number;
angleSpeed?: number;
angle?: number;
}

View File

@ -1,56 +0,0 @@
import {
Audio,
AudioAnalyser,
AudioListener,
AudioLoader,
PositionalAudio,
} from "three";
import { avg, chunk } from "./utils";
const loader = new AudioLoader();
export function LoadAudio(
listener: AudioListener,
url: string,
volume: number,
type: string,
positional?: boolean
): Promise<PositionalAudio | Audio> {
const loadingDiv = document.getElementById("loader");
loadingDiv.innerHTML = `Loading ${type}: 0%`;
return new Promise((resolve, reject) => {
loader.load(
url,
(audio) => {
const sound = positional
? new PositionalAudio(listener)
: new Audio(listener);
sound.setBuffer(audio);
sound.setLoop(false);
sound.setVolume(volume);
loadingDiv.innerHTML = "";
return resolve(sound);
},
(progress) =>
(loadingDiv.innerHTML = `Loading audio: ${
(progress.loaded / progress.total) * 100
}%`),
(error: ErrorEvent) => {
console.log(error.target);
reject(error);
}
);
});
}
export function calculateSoundAverages(
audioAnalyser: AudioAnalyser,
chunkAmount: number
): number[] {
const soundArray = audioAnalyser.getFrequencyData();
const chunkedArray = chunk(soundArray, chunkAmount);
const averages = chunkedArray.map((arr) => avg(arr) / 255);
return averages;
}

View File

@ -12,7 +12,6 @@ import {
PerspectiveCamera, PerspectiveCamera,
} from "three"; } from "three";
import { GLTF } from "three/examples/jsm/loaders/GLTFLoader"; import { GLTF } from "three/examples/jsm/loaders/GLTFLoader";
import { LoadModel } from "./model";
import { prng_alea } from "esm-seedrandom"; import { prng_alea } from "esm-seedrandom";
import parseSRT from "parse-srt"; import parseSRT from "parse-srt";
import { Planets } from "./actors/Planets"; import { Planets } from "./actors/Planets";
@ -20,6 +19,20 @@ import { OrbitCamera } from "./actors/OrbitCamera";
import { Hand } from "./actors/Hand"; import { Hand } from "./actors/Hand";
import { OrbitLight } from "./actors/OrbitLight"; import { OrbitLight } from "./actors/OrbitLight";
import { Jukebox } from "./actors/Jukebox"; import { Jukebox } from "./actors/Jukebox";
import { LoadModel } from "./utils/loaders";
interface CORE {
renderer: WebGLRenderer;
scene: Scene;
clock: Clock;
rng: RandomNumberGenerator;
playing: boolean;
audioListener: AudioListener;
}
export type RandomNumberGenerator = {
(): number;
};
const CORE: CORE = { const CORE: CORE = {
renderer: null, renderer: null,
@ -38,7 +51,9 @@ let HAND: Hand;
let PLANETS: Planets; let PLANETS: Planets;
let JUKEBOX: Jukebox; let JUKEBOX: Jukebox;
init().then(() => animate()); init()
.then(() => animate())
.catch((err) => console.warn(err));
async function init() { async function init() {
const container = document.getElementById("container"); const container = document.getElementById("container");
@ -70,13 +85,15 @@ async function init() {
CORE.playing = true; CORE.playing = true;
}); });
CORE.scene.add(poem); CORE.scene.add(poem);
ORBIT_LIGHT.light.add(music); CORE.scene.add(music);
const model = await LoadModel(); const model = await LoadModel();
initializeModel(model, poemAnalyser); initializeModel(model, poemAnalyser);
initializeStars(); initializeStars();
ORBIT_CAMERA.update(0, HAND.mesh.position); ORBIT_CAMERA.update(0, HAND.mesh.position);
PLANETS = new Planets(musicAnalyser); PLANETS = new Planets(musicAnalyser);
PLANETS.meshes.forEach((planet) => CORE.scene.add(planet)); PLANETS.meshes.forEach((planet) => CORE.scene.add(planet));
document.getElementById("loader").innerHTML = "";
document.getElementById("startButton").style.display = "block";
} catch (err) { } catch (err) {
console.warn(err); console.warn(err);
} }
@ -133,16 +150,3 @@ function onWindowResize() {
CORE.renderer.setSize(window.innerWidth, window.innerHeight); CORE.renderer.setSize(window.innerWidth, window.innerHeight);
} }
interface CORE {
renderer: WebGLRenderer;
scene: Scene;
clock: Clock;
rng: RandomNumberGenerator;
playing: boolean;
audioListener: AudioListener;
}
export type RandomNumberGenerator = {
(): number;
};

View File

@ -1,44 +0,0 @@
import { Texture, TextureLoader } from "three";
import { GLTFLoader, GLTF } from "three/examples/jsm/loaders/GLTFLoader.js";
const gltfLoader = new GLTFLoader();
const textureLoader = new TextureLoader();
export function LoadModel(): Promise<GLTF> {
const loadingDiv = document.getElementById("loader");
return new Promise((resolve, reject) => {
gltfLoader.load(
"/static/models/scene.gltf",
(gltf: GLTF) => {
loadingDiv.innerHTML = "";
resolve(gltf);
},
(progress) =>
(loadingDiv.innerHTML = `Loading model: ${
(progress.loaded / progress.total) * 100
}%`),
(error: ErrorEvent) => {
console.log(error.target);
reject(error);
}
);
});
}
export function LoadTexture(url): Promise<Texture> {
const loadingDiv = document.getElementById("loader");
return new Promise((resolve, reject) => {
textureLoader.load(
url,
(texture: Texture) => resolve(texture),
(progress) =>
(loadingDiv.innerHTML = `Loading texture: ${
(progress.loaded / progress.total) * 100
}%`),
(error: ErrorEvent) => {
console.log(error.target);
reject(error);
}
);
});
}

13
src/utils/audioUtils.ts Normal file
View File

@ -0,0 +1,13 @@
import { AudioAnalyser } from "three";
import { avg, chunk } from "./arrayUtils";
export function calculateSoundAverages(
audioAnalyser: AudioAnalyser,
chunkAmount: number
): number[] {
const soundArray = audioAnalyser.getFrequencyData();
const chunkedArray = chunk(soundArray, chunkAmount);
const averages = chunkedArray.map((arr) => avg(arr) / 255);
return averages;
}

61
src/utils/loaders.ts Normal file
View File

@ -0,0 +1,61 @@
import { Audio, AudioListener, AudioLoader, PositionalAudio } from "three";
import { GLTFLoader, GLTF } from "three/examples/jsm/loaders/GLTFLoader.js";
const loader = new AudioLoader();
const gltfLoader = new GLTFLoader();
export function LoadAudio(
listener: AudioListener,
url: string,
volume: number,
type: string,
positional?: boolean
): Promise<PositionalAudio | Audio> {
const loadingDiv = document.getElementById("loader");
loadingDiv.innerHTML = `Loading ${type}: 0.00%`;
return new Promise((resolve, reject) => {
loader.load(
url,
(audio) => {
const sound = positional
? new PositionalAudio(listener)
: new Audio(listener);
sound.setBuffer(audio);
sound.setLoop(false);
sound.setVolume(volume);
return resolve(sound);
},
(progress) => {
const percent = (progress.loaded / progress.total) * 100;
loadingDiv.innerHTML = `Loading ${type}: ${percent.toFixed(2)}%`;
},
(error: ErrorEvent) => {
console.log(error.target);
reject(error);
}
);
});
}
export function LoadModel(): Promise<GLTF> {
const loadingDiv = document.getElementById("loader");
loadingDiv.innerHTML = `Loading model: 0.00%`;
return new Promise((resolve, reject) => {
gltfLoader.load(
"/static/models/scene.gltf",
(gltf: GLTF) => {
resolve(gltf);
},
(progress) => {
const percent = (progress.loaded / progress.total) * 100;
loadingDiv.innerHTML = `Loading model: ${percent.toFixed(2)}%`;
},
(error: ErrorEvent) => {
console.log(error.target);
reject(error);
}
);
});
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 122 KiB

After

Width:  |  Height:  |  Size: 226 KiB