diff --git a/README.md b/README.md index 2b1a567..2e5ffc3 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ ## 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 diff --git a/index.html b/index.html index 67015d3..cdc6c9c 100644 --- a/index.html +++ b/index.html @@ -34,7 +34,7 @@
-
Loading model: 0%
+
void; export class Jukebox { parser: SRTParser; listener: AudioListener; @@ -26,14 +38,14 @@ export class Jukebox { const music = await LoadAudio( this.listener, "/static/music/space_chillout.mp3", - 1, + 0.5, "music", - true + false ); const poem = await LoadAudio( this.listener, "/static/music/who_am_i.mp3", - 1, + 0.7, "poem", false ); @@ -43,13 +55,11 @@ export class Jukebox { const poemAnalyser = new AudioAnalyser(poem, 512); const startButton = document.getElementById("startButton"); - startButton.style.display = "block"; startButton.addEventListener("click", () => { silence.play(); music.play(); poem.play(); - // CORE.playing = true; startButton.remove(); 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; diff --git a/src/actors/Planets.ts b/src/actors/Planets.ts index 8c6b3d9..fbfcc86 100644 --- a/src/actors/Planets.ts +++ b/src/actors/Planets.ts @@ -6,8 +6,16 @@ import { SphereBufferGeometry, Vector3, } 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 { meshes: Array; 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; -} diff --git a/src/audio.ts b/src/audio.ts deleted file mode 100644 index 01abdec..0000000 --- a/src/audio.ts +++ /dev/null @@ -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 { - 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; -} diff --git a/src/index.ts b/src/index.ts index 51183aa..1f1d184 100644 --- a/src/index.ts +++ b/src/index.ts @@ -12,7 +12,6 @@ import { PerspectiveCamera, } from "three"; import { GLTF } from "three/examples/jsm/loaders/GLTFLoader"; -import { LoadModel } from "./model"; import { prng_alea } from "esm-seedrandom"; import parseSRT from "parse-srt"; import { Planets } from "./actors/Planets"; @@ -20,6 +19,20 @@ import { OrbitCamera } from "./actors/OrbitCamera"; import { Hand } from "./actors/Hand"; import { OrbitLight } from "./actors/OrbitLight"; 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 = { renderer: null, @@ -38,7 +51,9 @@ let HAND: Hand; let PLANETS: Planets; let JUKEBOX: Jukebox; -init().then(() => animate()); +init() + .then(() => animate()) + .catch((err) => console.warn(err)); async function init() { const container = document.getElementById("container"); @@ -70,13 +85,15 @@ async function init() { CORE.playing = true; }); CORE.scene.add(poem); - ORBIT_LIGHT.light.add(music); + CORE.scene.add(music); const model = await LoadModel(); initializeModel(model, poemAnalyser); initializeStars(); ORBIT_CAMERA.update(0, HAND.mesh.position); PLANETS = new Planets(musicAnalyser); PLANETS.meshes.forEach((planet) => CORE.scene.add(planet)); + document.getElementById("loader").innerHTML = ""; + document.getElementById("startButton").style.display = "block"; } catch (err) { console.warn(err); } @@ -133,16 +150,3 @@ function onWindowResize() { 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; -}; diff --git a/src/model.ts b/src/model.ts deleted file mode 100644 index 5d38abd..0000000 --- a/src/model.ts +++ /dev/null @@ -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 { - 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 { - 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); - } - ); - }); -} diff --git a/src/utils.ts b/src/utils/arrayUtils.ts similarity index 100% rename from src/utils.ts rename to src/utils/arrayUtils.ts diff --git a/src/utils/audioUtils.ts b/src/utils/audioUtils.ts new file mode 100644 index 0000000..8597439 --- /dev/null +++ b/src/utils/audioUtils.ts @@ -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; +} diff --git a/src/utils/loaders.ts b/src/utils/loaders.ts new file mode 100644 index 0000000..022a5ba --- /dev/null +++ b/src/utils/loaders.ts @@ -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 { + 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 { + 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); + } + ); + }); +} diff --git a/static/og.png b/static/og.png index d16f13d..618748c 100644 Binary files a/static/og.png and b/static/og.png differ