From ed143b7af434005d1a51c819351a357eeb711525 Mon Sep 17 00:00:00 2001 From: JujieX Date: Sat, 3 Jun 2023 19:59:16 +0800 Subject: [PATCH 1/5] feat: setup audio playground --- playground/audio-basic.ts | 243 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 243 insertions(+) create mode 100644 playground/audio-basic.ts diff --git a/playground/audio-basic.ts b/playground/audio-basic.ts new file mode 100644 index 000000000..72cd7eb99 --- /dev/null +++ b/playground/audio-basic.ts @@ -0,0 +1,243 @@ +/** + * @title Audio Basic + * @category Basic + */ +import { OrbitControl } from "@galacean/engine-toolkit-controls"; +import { + AssetType, + AudioClip, + AudioListener, + AudioSource, + BoxColliderShape, + Camera, + Entity, + GLTFResource, + Pointer, + Script, + SpotLight, + StaticCollider, + TextRenderer, + Vector3, + WebGLEngine, +} from "@galacean/engine"; +import { LitePhysics } from "@galacean/engine-physics-lite"; +class RecorderScript extends Script { + public onClick: () => any; + onPointerDown(pointer: Pointer): void { + this.onClick && this.onClick(); + } +} + +class LightScript extends Script { + public zDir: boolean = true; + + private _startX: number = -4; + private _highY: number = 16; + private _x: number = -4; + private _y: number = 0; + private _spotLight: SpotLight; + // from left to right + private _originDir: boolean = true; + private _currDir: boolean = true; + + set startX(value: number) { + this._startX = value; + this._x = value; + this._highY = value * value; + + if (value <= 0) { + this._originDir = true; + } else { + this._originDir = false; + } + } + + get startX(): number { + return this._startX; + } + + constructor(entity: Entity) { + super(entity); + this._spotLight = entity.getComponent(SpotLight); + } + + onUpdate(deltaTime: number): void { + if (this._originDir) { + // 开始时候是从左向右 + if (this._x >= -this.startX) { + this._currDir = false; + } + if (this._x <= this.startX) { + this._currDir = true; + } + } else { + // 开始的时候是从右向左 + if (this._x >= this.startX) { + this._currDir = false; + } + if (this._x <= -this.startX) { + this._currDir = true; + } + } + + // 根据current direction计算 + if (this._currDir) { + this._x = this._x + 4 * deltaTime; + } else { + this._x = this._x - 4 * deltaTime; + } + + this._y = this._highY - this._x * this._x; + if (this.zDir) { + this.entity.transform.setPosition(this._x, this._y, this._x); + this._spotLight.direction.set(-this._x, -this._y, -this._x); + } else { + this.entity.transform.setPosition(this._x, this._y, -this._x); + this._spotLight.direction.set(-this._x, -this._y, this._x); + } + } +} + +class BarScript extends Script { + private _audioSource: AudioSource; + private _barRenderer: TextRenderer; + + constructor(entity: Entity) { + super(entity); + this._audioSource = entity.getComponent(AudioSource); + this._barRenderer = entity.children[0].getComponent(TextRenderer); + } + + onUpdate(deltaTime: number): void { + if (this._audioSource.isPlaying) { + const percent = + this._audioSource.position / this._audioSource.clip.duration; + this._barRenderer.text = `${Math.floor(percent * 100)}` + "%"; + } + } +} + +function addBtn( + rootEntity: Entity, + position: Vector3, + name: string, + clickHandler: () => any +) { + const playEntity = rootEntity.createChild(name); + playEntity.transform.setPosition(position.x, position.y, position.z); + + const playCollider = playEntity.addComponent(StaticCollider); + const playShape = new BoxColliderShape(); + playShape.size = new Vector3(1.4, 0.9, 1); + playCollider.addShape(playShape); + + const playBtnEntity = playEntity.createChild("playbtn"); + playBtnEntity.transform.setPosition(0, 1, 0); + const playText = playBtnEntity.addComponent(TextRenderer); + playText.text = name; + + const playScript = playEntity.addComponent(RecorderScript); + playScript.onClick = clickHandler; + + return playEntity; +} + +// Create engine +WebGLEngine.create({ canvas: "canvas", physics: new LitePhysics() }).then( + (engine) => { + engine.canvas.resizeByClientSize(); + // Create root entity + const rootEntity = engine.sceneManager.activeScene.createRootEntity(); + + // Create camera + const cameraEntity = rootEntity.createChild("Camera"); + cameraEntity.transform.setPosition(12, 16, 16); + cameraEntity.addComponent(Camera); + cameraEntity.addComponent(OrbitControl); + + // create light + const lightEntity = rootEntity.createChild(); + + const lightAEntity = lightEntity.createChild(); + const lightA = lightAEntity.addComponent(SpotLight); + lightA.angle = 30; + lightA.penumbra = 15; + lightA.color.set(1, 0, 0, 1); + const lightAHandler = lightAEntity.addComponent(LightScript); + lightAHandler.startX = -4; + + const lightBEntity = lightEntity.createChild(); + const lightB = lightBEntity.addComponent(SpotLight); + lightB.angle = 30; + lightB.penumbra = 15; + lightB.color.set(0, 1, 0, 1); + const lightBHandler = lightBEntity.addComponent(LightScript); + lightBHandler.startX = -4; + lightBHandler.zDir = false; + + lightA.enabled = false; + lightB.enabled = false; + + engine.resourceManager + .load({ + url: "https://gw.alipayobjects.com/mdn/oasis_be/afts/file/A*96_aSZfBP7wAAAAAAAAAAAAADkp5AQ/tape_recorder.glb", + type: AssetType.GLTF, + }) + .then((asset) => { + const { defaultSceneRoot } = asset; + const recorderEntity = defaultSceneRoot; + rootEntity.addChild(recorderEntity); + recorderEntity.transform.setScale(10, 10, 10); + }); + + engine.resourceManager + .load({ + url: "https://mass-office.alipay.com/huamei_koqzbu/afts/file/JLvfSZkPfIoAAAAAAAAAABAADnV5AQBr", + type: AssetType.Audio, + }) + .then((res) => { + const clip = new AudioClip("new"); + clip.setData(res); + const listenEntity = rootEntity.createChild("listen"); + const listener = listenEntity.addComponent(AudioListener); + + const playerEntity = rootEntity.createChild("player"); + + const audioSource = playerEntity.addComponent(AudioSource); + audioSource.clip = clip; + + audioSource.onPlayEnd = () => { + lightA.enabled = false; + lightB.enabled = false; + }; + + const percentEntity = playerEntity.createChild(); + percentEntity.transform.setPosition(0, 8, 0); + const test = percentEntity.addComponent(TextRenderer); + test.fontSize = 80; + playerEntity.addComponent(BarScript); + + // play btn + addBtn(rootEntity, new Vector3(3.3, 4, 0), "PLAY", () => { + audioSource.play(); + + lightA.enabled = true; + lightB.enabled = true; + }); + // stop btn + addBtn(rootEntity, new Vector3(-2.3, 4, 0), "STOP", () => { + audioSource.stop(); + }); + + addBtn(rootEntity, new Vector3(0.5, 4, 0), "PAUSE", () => { + audioSource.pause(); + }); + + addBtn(rootEntity, new Vector3(1.9, 4, 0), "RESUME", () => { + audioSource.resume(); + }); + }); + + engine.run(); + } +); From 8b74f2979d415a8de38b0368ac539fb40d737e2b Mon Sep 17 00:00:00 2001 From: JujieX Date: Mon, 12 Jun 2023 15:21:01 +0800 Subject: [PATCH 2/5] fix: update api --- playground/audio-basic.ts | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/playground/audio-basic.ts b/playground/audio-basic.ts index 72cd7eb99..de6f5e378 100644 --- a/playground/audio-basic.ts +++ b/playground/audio-basic.ts @@ -1,6 +1,6 @@ /** * @title Audio Basic - * @category Basic + * @category Audio */ import { OrbitControl } from "@galacean/engine-toolkit-controls"; import { @@ -110,8 +110,7 @@ class BarScript extends Script { onUpdate(deltaTime: number): void { if (this._audioSource.isPlaying) { - const percent = - this._audioSource.position / this._audioSource.clip.duration; + const percent = this._audioSource.time / this._audioSource.clip.duration; this._barRenderer.text = `${Math.floor(percent * 100)}` + "%"; } } @@ -196,21 +195,16 @@ WebGLEngine.create({ canvas: "canvas", physics: new LitePhysics() }).then( type: AssetType.Audio, }) .then((res) => { - const clip = new AudioClip("new"); + const clip = new AudioClip(engine); clip.setData(res); const listenEntity = rootEntity.createChild("listen"); - const listener = listenEntity.addComponent(AudioListener); + listenEntity.addComponent(AudioListener); const playerEntity = rootEntity.createChild("player"); const audioSource = playerEntity.addComponent(AudioSource); audioSource.clip = clip; - audioSource.onPlayEnd = () => { - lightA.enabled = false; - lightB.enabled = false; - }; - const percentEntity = playerEntity.createChild(); percentEntity.transform.setPosition(0, 8, 0); const test = percentEntity.addComponent(TextRenderer); @@ -227,14 +221,16 @@ WebGLEngine.create({ canvas: "canvas", physics: new LitePhysics() }).then( // stop btn addBtn(rootEntity, new Vector3(-2.3, 4, 0), "STOP", () => { audioSource.stop(); + lightA.enabled = false; + lightB.enabled = false; }); addBtn(rootEntity, new Vector3(0.5, 4, 0), "PAUSE", () => { audioSource.pause(); }); - addBtn(rootEntity, new Vector3(1.9, 4, 0), "RESUME", () => { - audioSource.resume(); + addBtn(rootEntity, new Vector3(1.9, 4, 0), "UNPAUSE", () => { + audioSource.unPause(); }); }); From b1d62669eeebfca98b94858246e6fe3238c4ee69 Mon Sep 17 00:00:00 2001 From: JujieX Date: Mon, 12 Jun 2023 19:11:36 +0800 Subject: [PATCH 3/5] feat: setup positional audio playground --- playground/audio-positional.ts | 163 +++++++++++++++++++++++++++++++++ 1 file changed, 163 insertions(+) create mode 100644 playground/audio-positional.ts diff --git a/playground/audio-positional.ts b/playground/audio-positional.ts new file mode 100644 index 000000000..6624223ea --- /dev/null +++ b/playground/audio-positional.ts @@ -0,0 +1,163 @@ +/** + * @title Audio Positional + * @category Audio + */ +import { + AssetType, + AudioClip, + AudioListener, + PositionalAudioSource, + Camera, + MeshRenderer, + PrimitiveMesh, + Vector3, + WebGLEngine, + PBRMaterial, + DirectLight, + AmbientLight, +} from "@galacean/engine"; + +import { LitePhysics } from "@galacean/engine-physics-lite"; +import { FreeControl } from "@galacean/engine-toolkit-controls"; +import { GridControl } from "@galacean/engine-toolkit-custom-material"; + +import * as dat from "dat.gui"; + +const gui = new dat.GUI(); +function addGUI(audioSource: PositionalAudioSource) { + const state = { + volume: 1, + playbackRate: 1, + loop: true, + panningMode: ["equalpower"], + minDistance: 0, + maxDistance: 64, + distanceModel: ["inverse"], + innerAngle: 0, + outerAngle: 360, + outerVolume: 0, + }; + + // basic setup + gui.add(state, "volume", 0, 10).onChange((v: number) => { + audioSource.volume = v; + }); + + gui.add(state, "playbackRate", 0, 2).onChange((v: number) => { + audioSource.playbackRate = v; + }); + + gui.add(state, "loop").onChange((v: boolean) => { + audioSource.loop = v; + }); + // distance model + const panningModeConfig = ["HRTF", "equalpower"]; + const distanceModelConfig = ["exponential", "inverse", "linear"]; + + gui + .add(state, "panningMode", panningModeConfig) + .onChange((v: PanningModelType) => { + audioSource.PanningMode = v; + }); + + gui.add(state, "minDistance", 0, 20).onChange((v: number) => { + audioSource.minDistance = v; + }); + + gui.add(state, "maxDistance", 20, 100).onChange((v: number) => { + audioSource.maxDistance = v; + }); + + gui + .add(state, "distanceModel", distanceModelConfig) + .onChange((v: DistanceModelType) => { + audioSource.distanceModel = v; + }); + + // direction model + gui.add(state, "innerAngle", 0, 360).onChange((v: number) => { + audioSource.innerAngle = v; + }); + + gui.add(state, "outerAngle", 0, 360).onChange((v: number) => { + audioSource.outerAngle = v; + }); + + gui.add(state, "outerVolume", 0, 1).onChange((v: number) => { + audioSource.outerVolume = v; + }); +} + +// Create engine +WebGLEngine.create({ canvas: "canvas", physics: new LitePhysics() }).then( + (engine) => { + engine.canvas.resizeByClientSize(); + // Scene + const rootEntity = engine.sceneManager.activeScene.createRootEntity(); + const scene = engine.sceneManager.activeScene; + scene.background.solidColor.set(0, 0, 0, 0); + + scene.ambientLight.diffuseSolidColor.set(1, 0, 0, 1); + scene.ambientLight.diffuseIntensity = 1.2; + + // Camera + const cameraEntity = rootEntity.createChild("Camera"); + cameraEntity.transform.setPosition(0, 2, 8); + cameraEntity.transform.lookAt(new Vector3(0, 2, 0), new Vector3(0, 1, 0)); + + const camera = cameraEntity.addComponent(Camera); + const controller = cameraEntity.addComponent(FreeControl); + controller.floorMock = false; + controller.movementSpeed = 10; + + cameraEntity.addComponent(AudioListener); + + // light + const light = rootEntity.createChild("light"); + light.transform.setPosition(-140, 1000, -1020); + light.transform.lookAt(new Vector3(30, 0, 300)); + const directLight = light.addComponent(DirectLight); + + // Grid + const grid = rootEntity.addComponent(GridControl); + grid.camera = camera; + + // Sphere + const sphereEntity = rootEntity.createChild("sphere"); + sphereEntity.transform.setPosition(0, 2, 0); + const sphereRender = sphereEntity.addComponent(MeshRenderer); + sphereRender.mesh = PrimitiveMesh.createSphere(engine, 1); + const material = new PBRMaterial(engine); + material.baseColor.set(1, 0.4, 0.2, 1); + sphereRender.setMaterial(material); + + engine.resourceManager + .load({ + type: AssetType.Env, + url: "https://gw.alipayobjects.com/os/bmw-prod/89c54544-1184-45a1-b0f5-c0b17e5c3e68.bin", + }) + .then((ambientLight) => { + scene.ambientLight = ambientLight; + }); + + engine.resourceManager + .load({ + url: "https://mass-office.alipay.com/huamei_koqzbu/afts/file/JLvfSZkPfIoAAAAAAAAAABAADnV5AQBr", + type: AssetType.Audio, + }) + .then((audioBuffer) => { + const clip = new AudioClip(engine); + clip.setData(audioBuffer); + + const audioSource = sphereEntity.addComponent(PositionalAudioSource); + audioSource.clip = clip; + audioSource.loop = true; + audioSource.playOnAwake = true; + addGUI(audioSource); + + audioSource.play(); + }); + + engine.run(); + } +); From a3b4a94f80b2a6c42f740783d682a1e89601653a Mon Sep 17 00:00:00 2001 From: JujieX Date: Tue, 13 Jun 2023 14:44:57 +0800 Subject: [PATCH 4/5] feat: add auxiliary line for distance model --- playground/audio-positional.ts | 73 +++++++++++++++++++++++++++++----- 1 file changed, 63 insertions(+), 10 deletions(-) diff --git a/playground/audio-positional.ts b/playground/audio-positional.ts index 6624223ea..666db4384 100644 --- a/playground/audio-positional.ts +++ b/playground/audio-positional.ts @@ -15,11 +15,17 @@ import { PBRMaterial, DirectLight, AmbientLight, + Entity, + Script, + Pointer, + StaticCollider, + SphereColliderShape, } from "@galacean/engine"; import { LitePhysics } from "@galacean/engine-physics-lite"; import { FreeControl } from "@galacean/engine-toolkit-controls"; import { GridControl } from "@galacean/engine-toolkit-custom-material"; +import { LineDrawer } from "@galacean/engine-toolkit-auxiliary-lines"; import * as dat from "dat.gui"; @@ -27,13 +33,14 @@ const gui = new dat.GUI(); function addGUI(audioSource: PositionalAudioSource) { const state = { volume: 1, + mute: false, playbackRate: 1, loop: true, panningMode: ["equalpower"], - minDistance: 0, - maxDistance: 64, - distanceModel: ["inverse"], - innerAngle: 0, + minDistance: 1, + maxDistance: 10, + distanceModel: ["linear"], + innerAngle: 360, outerAngle: 360, outerVolume: 0, }; @@ -43,6 +50,10 @@ function addGUI(audioSource: PositionalAudioSource) { audioSource.volume = v; }); + gui.add(state, "mute").onChange((v: boolean) => { + audioSource.mute = v; + }); + gui.add(state, "playbackRate", 0, 2).onChange((v: number) => { audioSource.playbackRate = v; }); @@ -60,11 +71,11 @@ function addGUI(audioSource: PositionalAudioSource) { audioSource.PanningMode = v; }); - gui.add(state, "minDistance", 0, 20).onChange((v: number) => { + gui.add(state, "minDistance", 0, 5).onChange((v: number) => { audioSource.minDistance = v; }); - gui.add(state, "maxDistance", 20, 100).onChange((v: number) => { + gui.add(state, "maxDistance", 5, 100).onChange((v: number) => { audioSource.maxDistance = v; }); @@ -87,6 +98,27 @@ function addGUI(audioSource: PositionalAudioSource) { audioSource.outerVolume = v; }); } +class HitPlayScript extends Script { + public onClick: () => any; + onPointerDown(pointer: Pointer): void { + this.onClick && this.onClick(); + } +} +class AudioScript extends Script { + private _audioSource: PositionalAudioSource; + constructor(entity: Entity) { + super(entity); + this._audioSource = this.entity.getComponent(PositionalAudioSource); + } + + onUpdate() { + const maxRadius = this._audioSource.maxDistance; + const minRadius = this._audioSource.minDistance; + const pos = this.entity.transform.position; + LineDrawer.drawSphere(maxRadius, pos); + LineDrawer.drawSphere(minRadius, pos); + } +} // Create engine WebGLEngine.create({ canvas: "canvas", physics: new LitePhysics() }).then( @@ -95,14 +127,14 @@ WebGLEngine.create({ canvas: "canvas", physics: new LitePhysics() }).then( // Scene const rootEntity = engine.sceneManager.activeScene.createRootEntity(); const scene = engine.sceneManager.activeScene; - scene.background.solidColor.set(0, 0, 0, 0); + scene.background.solidColor.set(0, 0, 0, 1); scene.ambientLight.diffuseSolidColor.set(1, 0, 0, 1); scene.ambientLight.diffuseIntensity = 1.2; // Camera const cameraEntity = rootEntity.createChild("Camera"); - cameraEntity.transform.setPosition(0, 2, 8); + cameraEntity.transform.setPosition(0, 4, 25); cameraEntity.transform.lookAt(new Vector3(0, 2, 0), new Vector3(0, 1, 0)); const camera = cameraEntity.addComponent(Camera); @@ -117,10 +149,13 @@ WebGLEngine.create({ canvas: "canvas", physics: new LitePhysics() }).then( light.transform.setPosition(-140, 1000, -1020); light.transform.lookAt(new Vector3(30, 0, 300)); const directLight = light.addComponent(DirectLight); + directLight.color.set(0, 0.6, 0.8, 1); // Grid const grid = rootEntity.addComponent(GridControl); grid.camera = camera; + rootEntity.addComponent(MeshRenderer); + rootEntity.addComponent(LineDrawer); // Sphere const sphereEntity = rootEntity.createChild("sphere"); @@ -131,6 +166,13 @@ WebGLEngine.create({ canvas: "canvas", physics: new LitePhysics() }).then( material.baseColor.set(1, 0.4, 0.2, 1); sphereRender.setMaterial(material); + const playCollider = sphereEntity.addComponent(StaticCollider); + const playShape = new SphereColliderShape(); + playShape.radius = 1; + playCollider.addShape(playShape); + + const playHandler = sphereEntity.addComponent(HitPlayScript); + engine.resourceManager .load({ type: AssetType.Env, @@ -152,10 +194,21 @@ WebGLEngine.create({ canvas: "canvas", physics: new LitePhysics() }).then( const audioSource = sphereEntity.addComponent(PositionalAudioSource); audioSource.clip = clip; audioSource.loop = true; - audioSource.playOnAwake = true; + audioSource.maxDistance = 10; + audioSource.distanceModel = "linear"; addGUI(audioSource); - audioSource.play(); + sphereEntity.addComponent(AudioScript); + + playHandler.onClick = () => { + audioSource.play(); + material.baseColor.set( + Math.random(), + Math.random(), + Math.random(), + 1 + ); + }; }); engine.run(); From 2c3b33747d4aab6a7a99e6f0a5a5569a021737ed Mon Sep 17 00:00:00 2001 From: JujieX Date: Fri, 15 Dec 2023 00:11:41 +0800 Subject: [PATCH 5/5] fix: opt codes --- playground/audio-basic.ts | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/playground/audio-basic.ts b/playground/audio-basic.ts index de6f5e378..26b619ec8 100644 --- a/playground/audio-basic.ts +++ b/playground/audio-basic.ts @@ -6,7 +6,6 @@ import { OrbitControl } from "@galacean/engine-toolkit-controls"; import { AssetType, AudioClip, - AudioListener, AudioSource, BoxColliderShape, Camera, @@ -190,16 +189,11 @@ WebGLEngine.create({ canvas: "canvas", physics: new LitePhysics() }).then( }); engine.resourceManager - .load({ + .load({ url: "https://mass-office.alipay.com/huamei_koqzbu/afts/file/JLvfSZkPfIoAAAAAAAAAABAADnV5AQBr", type: AssetType.Audio, }) - .then((res) => { - const clip = new AudioClip(engine); - clip.setData(res); - const listenEntity = rootEntity.createChild("listen"); - listenEntity.addComponent(AudioListener); - + .then((clip: AudioClip) => { const playerEntity = rootEntity.createChild("player"); const audioSource = playerEntity.addComponent(AudioSource); @@ -228,10 +222,6 @@ WebGLEngine.create({ canvas: "canvas", physics: new LitePhysics() }).then( addBtn(rootEntity, new Vector3(0.5, 4, 0), "PAUSE", () => { audioSource.pause(); }); - - addBtn(rootEntity, new Vector3(1.9, 4, 0), "UNPAUSE", () => { - audioSource.unPause(); - }); }); engine.run();