From 62b0db0cd5adf293c469e29d7bee2e2c1d92250f Mon Sep 17 00:00:00 2001 From: dataexcess Date: Mon, 28 Nov 2022 20:22:22 +0100 Subject: [PATCH 1/3] wip integrating audio components to the project --- src/audio/Audio.ts | 83 ++++++++++++++++++++++++++++++++++++ src/audio/AudioListener.ts | 32 ++++++++++++++ src/audio/PositionalAudio.ts | 39 +++++++++++++++++ src/audio/StaticAudio.ts | 26 +++++++++++ src/audio/index.ts | 3 ++ src/core/Renderer.ts | 7 ++- src/core/useThree.ts | 3 +- src/index.ts | 1 + src/plugin.ts | 4 ++ 9 files changed, 196 insertions(+), 2 deletions(-) create mode 100644 src/audio/Audio.ts create mode 100644 src/audio/AudioListener.ts create mode 100644 src/audio/PositionalAudio.ts create mode 100644 src/audio/StaticAudio.ts create mode 100644 src/audio/index.ts diff --git a/src/audio/Audio.ts b/src/audio/Audio.ts new file mode 100644 index 0000000..f66443e --- /dev/null +++ b/src/audio/Audio.ts @@ -0,0 +1,83 @@ +import { Audio as StaticAudio, PositionalAudio, AudioLoader } from 'three' +import { defineComponent } from 'vue' +import Object3D from '../core/Object3D' +import { bindProp } from '../tools' + +export interface AudioSetupInterface { + streamMediaElement?: HTMLAudioElement +} + +type ConcreteAudio = StaticAudio | PositionalAudio + +export default defineComponent({ + extends: Object3D, + name: 'Audio', + props: { + src: String, + volume: { type: Number, default: 1.0 }, + isStreamed: { type: Boolean, default: true } + }, + setup (): AudioSetupInterface { + let streamMediaElement = new Audio(); + return { streamMediaElement } + }, + methods: { + initAudio(audio: ConcreteAudio) { + this.initObject3D(audio) + this.bindProps(audio) + this.loadAudioAndPlay(audio) + }, + bindProps(audio: ConcreteAudio) { + ['volume', 'isStreamed'].forEach(p => { + bindProp(this.$props, p, audio) + }) + }, + loadAudioAndPlay(audio: ConcreteAudio) { + if (!this.src) return undefined + + if (this.isStreamed) { + this.loadAudioFromStream(audio, this.src) + } else { + this.loadAudioFromMemory(audio, this.src) + } + }, + loadAudioFromMemory(audio:ConcreteAudio, src:string) { + const audioLoader = new AudioLoader(); + const instance = this + audioLoader.load( src, function( buffer ) { + audio.setBuffer( buffer ); + instance.play(audio) + }); + }, + loadAudioFromStream(audio:ConcreteAudio, src:string) { + this.streamMediaElement = new Audio(src); + this.streamMediaElement.loop = true; + this.streamMediaElement.preload = 'metadata'; + this.streamMediaElement.crossOrigin = 'anonymous'; + const instance = this + this.streamMediaElement.addEventListener("loadedmetadata", function(_event) { + console.log('loaded the stream') + instance.play(audio) + }); + audio.setMediaElementSource(this.streamMediaElement) + }, + play(audio:ConcreteAudio) { + if (this.isStreamed) { + if (!this.streamMediaElement) { return } + this.streamMediaElement.play() + } else { + audio.play() + } + }, + stop(audio:ConcreteAudio) { + if (this.isStreamed) { + if (!this.streamMediaElement) { return } + this.streamMediaElement.pause() + this.streamMediaElement.currentTime = 0; + } else { + audio.stop() + } + } + }, + __hmrId: 'Audio', +}) diff --git a/src/audio/AudioListener.ts b/src/audio/AudioListener.ts new file mode 100644 index 0000000..1da10b8 --- /dev/null +++ b/src/audio/AudioListener.ts @@ -0,0 +1,32 @@ +import { defineComponent, ComponentPublicInstance, InjectionKey, inject } from 'vue' +import { AudioListener } from 'three' +import Object3D from '../core/Object3D' +import { RendererInjectionKey } from './../core/Renderer' + +//TODO: currently the AudioListener is inheriting Object3D so it must be a +// child of Scene. However the AudioListener should actually be possible to be +// a child of Camera... Then again Camera is said to extend Object3D in the +// future... + +export interface AudioListenerSetupInterface { + audioListener?: AudioListener + } + +export default defineComponent({ + extends: Object3D, + name: 'AudioListener', + setup():AudioListenerSetupInterface { + return {} + }, + created() { + this.audioListener = new AudioListener() + this.initObject3D(this.audioListener) + const renderer = inject(RendererInjectionKey) + if (!renderer) { + console.error('Renderer not found') + return + } + renderer.audioListener = this.audioListener + }, + __hmrId: 'AudioListener', +}) diff --git a/src/audio/PositionalAudio.ts b/src/audio/PositionalAudio.ts new file mode 100644 index 0000000..3e274f6 --- /dev/null +++ b/src/audio/PositionalAudio.ts @@ -0,0 +1,39 @@ +import { defineComponent, inject } from 'vue' +import { PositionalAudio } from 'three' +import { RendererInjectionKey } from './../core/Renderer' +import Audio from './Audio' +import { bindProp } from '../tools' + +export default defineComponent({ + extends: Audio, + name: 'PositionalAudio', + props: { + refDistance: { type: Number, default: 1.0 }, + rolloffFactor: { type: Number, default: 1.0 } + }, + created() { + const renderer = inject(RendererInjectionKey) + + if (!renderer) { + console.error('Renderer not found') + return + } + + if (!renderer.audioListener) { + console.error("No AudioListener component found in the Renderer's child components tree!") + return + } + + const positionalAudio = new PositionalAudio(renderer.audioListener) + this.initAudio(positionalAudio) + this.bindProps(positionalAudio) + }, + methods: { + bindProps(audio: PositionalAudio) { + ['refDistance', 'rolloffFactor'].forEach(p => { + bindProp(this.$props, p, audio) + }) + }, + }, + __hmrId: 'AudioTest', +}) diff --git a/src/audio/StaticAudio.ts b/src/audio/StaticAudio.ts new file mode 100644 index 0000000..e6d3ab2 --- /dev/null +++ b/src/audio/StaticAudio.ts @@ -0,0 +1,26 @@ +import { defineComponent, inject } from 'vue' +import { Audio as StaticAudio } from 'three' +import { RendererInjectionKey } from './../core/Renderer' +import Audio from './Audio' + +export default defineComponent({ + extends: Audio, + name: 'StaticAudio', + created() { + const renderer = inject(RendererInjectionKey) + + if (!renderer) { + console.error('Renderer not found') + return + } + + if (!renderer.audioListener) { + console.error("No AudioListener component found in the Renderer's child components tree!") + return + } + + const staticAudio = new StaticAudio(renderer.audioListener) + this.initAudio( staticAudio ) + }, + __hmrId: 'StaticAudio', +}) diff --git a/src/audio/index.ts b/src/audio/index.ts new file mode 100644 index 0000000..0bbc0fa --- /dev/null +++ b/src/audio/index.ts @@ -0,0 +1,3 @@ +export { default as AudioListener } from './AudioListener' +export { default as PositionalAudio } from './PositionalAudio' +export { default as StaticAudio } from './StaticAudio' diff --git a/src/core/Renderer.ts b/src/core/Renderer.ts index d741d5c..2d34c08 100644 --- a/src/core/Renderer.ts +++ b/src/core/Renderer.ts @@ -1,5 +1,5 @@ /* eslint-disable no-use-before-define */ -import { Camera, Scene, WebGLRenderer, WebGLRendererParameters } from 'three' +import { Camera, Scene, WebGLRenderer, WebGLRendererParameters, AudioListener } from 'three' import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer' import { ComponentPublicInstance, defineComponent, InjectionKey, PropType, watchEffect } from 'vue' import { bindObjectProp } from '../tools' @@ -75,6 +75,7 @@ export interface RendererInterface extends RendererSetupInterface { scene?: Scene camera?: Camera composer?: EffectComposer + audioListener?: AudioListener onInit(cb: InitCallbackType): void onMounted(cb: MountedCallbackType): void @@ -181,6 +182,10 @@ export default defineComponent({ get: function(): EffectComposer | undefined { return this.three.composer }, set: function(composer: EffectComposer): void { this.three.composer = composer }, }, + audioListener: { + get: function(): AudioListener | undefined { return this.three.audioListener }, + set: function(audioListener: AudioListener): void { this.three.audioListener = audioListener }, + } }, provide() { return { diff --git a/src/core/useThree.ts b/src/core/useThree.ts index 61761f6..a076e43 100644 --- a/src/core/useThree.ts +++ b/src/core/useThree.ts @@ -1,4 +1,4 @@ -import { Camera, Object3D, OrthographicCamera, PerspectiveCamera, Scene, WebGLRenderer, WebGLRendererParameters } from 'three' +import { Camera, Object3D, OrthographicCamera, PerspectiveCamera, Scene, WebGLRenderer, WebGLRendererParameters, AudioListener } from 'three' import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js' import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js' import usePointer, { PointerConfigInterface, PointerPublicConfigInterface, PointerInterface } from './usePointer' @@ -32,6 +32,7 @@ export interface ThreeInterface { composer?: EffectComposer camera?: Camera cameraCtrl?: OrbitControls + audioListener?: AudioListener scene?: Scene pointer?: PointerInterface size: SizeInterface diff --git a/src/index.ts b/src/index.ts index 7087925..23ddc4b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,6 +5,7 @@ export * from './materials/index' export * from './meshes/index' export * from './models/index' export * from './effects/index' +export * from './audio/index' // export * from './components/index' diff --git a/src/plugin.ts b/src/plugin.ts index 5c3fbac..16a2251 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -77,6 +77,10 @@ export const TroisJSVuePlugin = { 'TiltShiftPass', 'UnrealBloomPass', 'ZoomBlurPass', + + 'AudioListener', + 'PositionalAudio', + 'StaticAudio' ] comps.forEach(comp => { From c75964d599df76186c9f5d0bcce30b2ea10cb8ea Mon Sep 17 00:00:00 2001 From: dataexcess Date: Tue, 29 Nov 2022 12:30:42 +0100 Subject: [PATCH 2/3] refactor using audio instance on Audio component + no need to inject render --- src/audio/Audio.ts | 74 ++++++++++++++++++------------------ src/audio/AudioListener.ts | 9 ++--- src/audio/PositionalAudio.ts | 12 +++--- src/audio/StaticAudio.ts | 8 ++-- 4 files changed, 50 insertions(+), 53 deletions(-) diff --git a/src/audio/Audio.ts b/src/audio/Audio.ts index f66443e..0b870ab 100644 --- a/src/audio/Audio.ts +++ b/src/audio/Audio.ts @@ -3,12 +3,13 @@ import { defineComponent } from 'vue' import Object3D from '../core/Object3D' import { bindProp } from '../tools' -export interface AudioSetupInterface { - streamMediaElement?: HTMLAudioElement -} - type ConcreteAudio = StaticAudio | PositionalAudio +export interface AudioSetupInterface { + audio?: ConcreteAudio + streamedAudio?: HTMLAudioElement +} + export default defineComponent({ extends: Object3D, name: 'Audio', @@ -18,64 +19,65 @@ export default defineComponent({ isStreamed: { type: Boolean, default: true } }, setup (): AudioSetupInterface { - let streamMediaElement = new Audio(); - return { streamMediaElement } + let streamedAudio = new Audio(); + return { streamedAudio } }, methods: { initAudio(audio: ConcreteAudio) { - this.initObject3D(audio) - this.bindProps(audio) - this.loadAudioAndPlay(audio) + this.audio = audio + this.initObject3D(this.audio) + this.bindProps() + this.loadAudioAndPlay() }, - bindProps(audio: ConcreteAudio) { + bindProps() { ['volume', 'isStreamed'].forEach(p => { - bindProp(this.$props, p, audio) + bindProp(this, p, this.audio) }) }, - loadAudioAndPlay(audio: ConcreteAudio) { + loadAudioAndPlay() { if (!this.src) return undefined if (this.isStreamed) { - this.loadAudioFromStream(audio, this.src) + this.loadAudioFromStream() } else { - this.loadAudioFromMemory(audio, this.src) + this.loadAudioFromMemory() } }, - loadAudioFromMemory(audio:ConcreteAudio, src:string) { + loadAudioFromMemory() { const audioLoader = new AudioLoader(); const instance = this - audioLoader.load( src, function( buffer ) { - audio.setBuffer( buffer ); - instance.play(audio) + audioLoader.load(this.src!, function( buffer ) { + console.log('loaded audio from memory') + instance.audio?.setBuffer( buffer ); + instance.play() }); }, - loadAudioFromStream(audio:ConcreteAudio, src:string) { - this.streamMediaElement = new Audio(src); - this.streamMediaElement.loop = true; - this.streamMediaElement.preload = 'metadata'; - this.streamMediaElement.crossOrigin = 'anonymous'; + loadAudioFromStream() { + this.streamedAudio = new Audio(this.src); + this.streamedAudio.loop = true; + this.streamedAudio.preload = 'metadata'; + this.streamedAudio.crossOrigin = 'anonymous'; const instance = this - this.streamMediaElement.addEventListener("loadedmetadata", function(_event) { - console.log('loaded the stream') - instance.play(audio) + this.streamedAudio.addEventListener("loadedmetadata", function(_event) { + console.log('loaded audio from stream') + instance.play() }); - audio.setMediaElementSource(this.streamMediaElement) + this.audio?.setMediaElementSource(this.streamedAudio) }, - play(audio:ConcreteAudio) { + play() { if (this.isStreamed) { - if (!this.streamMediaElement) { return } - this.streamMediaElement.play() + this.streamedAudio?.play() } else { - audio.play() + this.audio?.play() } }, - stop(audio:ConcreteAudio) { + stop() { if (this.isStreamed) { - if (!this.streamMediaElement) { return } - this.streamMediaElement.pause() - this.streamMediaElement.currentTime = 0; + if (!this.streamedAudio) { return } + this.streamedAudio.pause() + this.streamedAudio.currentTime = 0; } else { - audio.stop() + this.audio?.stop() } } }, diff --git a/src/audio/AudioListener.ts b/src/audio/AudioListener.ts index 1da10b8..2e9e64e 100644 --- a/src/audio/AudioListener.ts +++ b/src/audio/AudioListener.ts @@ -1,12 +1,11 @@ import { defineComponent, ComponentPublicInstance, InjectionKey, inject } from 'vue' import { AudioListener } from 'three' import Object3D from '../core/Object3D' -import { RendererInjectionKey } from './../core/Renderer' //TODO: currently the AudioListener is inheriting Object3D so it must be a // child of Scene. However the AudioListener should actually be possible to be // a child of Camera... Then again Camera is said to extend Object3D in the -// future... +// future... so might be better to refactor Camera first? export interface AudioListenerSetupInterface { audioListener?: AudioListener @@ -21,12 +20,12 @@ export default defineComponent({ created() { this.audioListener = new AudioListener() this.initObject3D(this.audioListener) - const renderer = inject(RendererInjectionKey) - if (!renderer) { + + if (!this.renderer) { console.error('Renderer not found') return } - renderer.audioListener = this.audioListener + this.renderer.audioListener = this.audioListener }, __hmrId: 'AudioListener', }) diff --git a/src/audio/PositionalAudio.ts b/src/audio/PositionalAudio.ts index 3e274f6..c6aae53 100644 --- a/src/audio/PositionalAudio.ts +++ b/src/audio/PositionalAudio.ts @@ -1,6 +1,5 @@ import { defineComponent, inject } from 'vue' import { PositionalAudio } from 'three' -import { RendererInjectionKey } from './../core/Renderer' import Audio from './Audio' import { bindProp } from '../tools' @@ -12,26 +11,25 @@ export default defineComponent({ rolloffFactor: { type: Number, default: 1.0 } }, created() { - const renderer = inject(RendererInjectionKey) - if (!renderer) { + if (!this.renderer) { console.error('Renderer not found') return } - if (!renderer.audioListener) { + if (!this.renderer.audioListener) { console.error("No AudioListener component found in the Renderer's child components tree!") return } - const positionalAudio = new PositionalAudio(renderer.audioListener) + const positionalAudio = new PositionalAudio(this.renderer.audioListener) this.initAudio(positionalAudio) - this.bindProps(positionalAudio) + this.bindProps() }, methods: { bindProps(audio: PositionalAudio) { ['refDistance', 'rolloffFactor'].forEach(p => { - bindProp(this.$props, p, audio) + bindProp(this.$props, p, this.audio) }) }, }, diff --git a/src/audio/StaticAudio.ts b/src/audio/StaticAudio.ts index e6d3ab2..d390717 100644 --- a/src/audio/StaticAudio.ts +++ b/src/audio/StaticAudio.ts @@ -1,25 +1,23 @@ import { defineComponent, inject } from 'vue' import { Audio as StaticAudio } from 'three' -import { RendererInjectionKey } from './../core/Renderer' import Audio from './Audio' export default defineComponent({ extends: Audio, name: 'StaticAudio', created() { - const renderer = inject(RendererInjectionKey) - if (!renderer) { + if (!this.renderer) { console.error('Renderer not found') return } - if (!renderer.audioListener) { + if (!this.renderer.audioListener) { console.error("No AudioListener component found in the Renderer's child components tree!") return } - const staticAudio = new StaticAudio(renderer.audioListener) + const staticAudio = new StaticAudio(this.renderer.audioListener) this.initAudio( staticAudio ) }, __hmrId: 'StaticAudio', From b3a1977875251156754511283d7899d2775d323f Mon Sep 17 00:00:00 2001 From: dataexcess Date: Thu, 8 Dec 2022 22:12:51 +0100 Subject: [PATCH 3/3] added more properties with appropriate watcher/binder --- src/audio/Audio.ts | 20 +++++++++++--------- src/audio/PositionalAudio.ts | 11 +++++++---- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/src/audio/Audio.ts b/src/audio/Audio.ts index 0b870ab..a161fef 100644 --- a/src/audio/Audio.ts +++ b/src/audio/Audio.ts @@ -1,7 +1,6 @@ import { Audio as StaticAudio, PositionalAudio, AudioLoader } from 'three' -import { defineComponent } from 'vue' +import { defineComponent, watch } from 'vue' import Object3D from '../core/Object3D' -import { bindProp } from '../tools' type ConcreteAudio = StaticAudio | PositionalAudio @@ -14,7 +13,7 @@ export default defineComponent({ extends: Object3D, name: 'Audio', props: { - src: String, + src: { type: String, required: false }, volume: { type: Number, default: 1.0 }, isStreamed: { type: Boolean, default: true } }, @@ -22,18 +21,21 @@ export default defineComponent({ let streamedAudio = new Audio(); return { streamedAudio } }, + watch: { + volume: function(value) { + this.audio?.setVolume(value) + }, + src: function(value) { + this.stop() + this.loadAudioAndPlay() + } + }, methods: { initAudio(audio: ConcreteAudio) { this.audio = audio this.initObject3D(this.audio) - this.bindProps() this.loadAudioAndPlay() }, - bindProps() { - ['volume', 'isStreamed'].forEach(p => { - bindProp(this, p, this.audio) - }) - }, loadAudioAndPlay() { if (!this.src) return undefined diff --git a/src/audio/PositionalAudio.ts b/src/audio/PositionalAudio.ts index c6aae53..b63d0e5 100644 --- a/src/audio/PositionalAudio.ts +++ b/src/audio/PositionalAudio.ts @@ -8,7 +8,9 @@ export default defineComponent({ name: 'PositionalAudio', props: { refDistance: { type: Number, default: 1.0 }, - rolloffFactor: { type: Number, default: 1.0 } + maxDistance: { type: Number, default: 1.0 }, + rolloffFactor: { type: Number, default: 1.0 }, + distanceModel: { type: String, default: 'inverse' }, }, created() { @@ -27,9 +29,10 @@ export default defineComponent({ this.bindProps() }, methods: { - bindProps(audio: PositionalAudio) { - ['refDistance', 'rolloffFactor'].forEach(p => { - bindProp(this.$props, p, this.audio) + bindProps() { + ['refDistance', 'maxDistance', 'rolloffFactor', 'distanceModel'].forEach(p => { + console.log('initialised positionalAudio props') + bindProp(this.$props, p, (this.audio as PositionalAudio).panner) }) }, },