diff --git a/src/audio/Audio.ts b/src/audio/Audio.ts new file mode 100644 index 0000000..a161fef --- /dev/null +++ b/src/audio/Audio.ts @@ -0,0 +1,87 @@ +import { Audio as StaticAudio, PositionalAudio, AudioLoader } from 'three' +import { defineComponent, watch } from 'vue' +import Object3D from '../core/Object3D' + +type ConcreteAudio = StaticAudio | PositionalAudio + +export interface AudioSetupInterface { + audio?: ConcreteAudio + streamedAudio?: HTMLAudioElement +} + +export default defineComponent({ + extends: Object3D, + name: 'Audio', + props: { + src: { type: String, required: false }, + volume: { type: Number, default: 1.0 }, + isStreamed: { type: Boolean, default: true } + }, + setup (): AudioSetupInterface { + 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.loadAudioAndPlay() + }, + loadAudioAndPlay() { + if (!this.src) return undefined + + if (this.isStreamed) { + this.loadAudioFromStream() + } else { + this.loadAudioFromMemory() + } + }, + loadAudioFromMemory() { + const audioLoader = new AudioLoader(); + const instance = this + audioLoader.load(this.src!, function( buffer ) { + console.log('loaded audio from memory') + instance.audio?.setBuffer( buffer ); + instance.play() + }); + }, + loadAudioFromStream() { + this.streamedAudio = new Audio(this.src); + this.streamedAudio.loop = true; + this.streamedAudio.preload = 'metadata'; + this.streamedAudio.crossOrigin = 'anonymous'; + const instance = this + this.streamedAudio.addEventListener("loadedmetadata", function(_event) { + console.log('loaded audio from stream') + instance.play() + }); + this.audio?.setMediaElementSource(this.streamedAudio) + }, + play() { + if (this.isStreamed) { + this.streamedAudio?.play() + } else { + this.audio?.play() + } + }, + stop() { + if (this.isStreamed) { + if (!this.streamedAudio) { return } + this.streamedAudio.pause() + this.streamedAudio.currentTime = 0; + } else { + this.audio?.stop() + } + } + }, + __hmrId: 'Audio', +}) diff --git a/src/audio/AudioListener.ts b/src/audio/AudioListener.ts new file mode 100644 index 0000000..2e9e64e --- /dev/null +++ b/src/audio/AudioListener.ts @@ -0,0 +1,31 @@ +import { defineComponent, ComponentPublicInstance, InjectionKey, inject } from 'vue' +import { AudioListener } from 'three' +import Object3D from '../core/Object3D' + +//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... so might be better to refactor Camera first? + +export interface AudioListenerSetupInterface { + audioListener?: AudioListener + } + +export default defineComponent({ + extends: Object3D, + name: 'AudioListener', + setup():AudioListenerSetupInterface { + return {} + }, + created() { + this.audioListener = new AudioListener() + this.initObject3D(this.audioListener) + + if (!this.renderer) { + console.error('Renderer not found') + return + } + this.renderer.audioListener = this.audioListener + }, + __hmrId: 'AudioListener', +}) diff --git a/src/audio/PositionalAudio.ts b/src/audio/PositionalAudio.ts new file mode 100644 index 0000000..b63d0e5 --- /dev/null +++ b/src/audio/PositionalAudio.ts @@ -0,0 +1,40 @@ +import { defineComponent, inject } from 'vue' +import { PositionalAudio } from 'three' +import Audio from './Audio' +import { bindProp } from '../tools' + +export default defineComponent({ + extends: Audio, + name: 'PositionalAudio', + props: { + refDistance: { type: Number, default: 1.0 }, + maxDistance: { type: Number, default: 1.0 }, + rolloffFactor: { type: Number, default: 1.0 }, + distanceModel: { type: String, default: 'inverse' }, + }, + created() { + + if (!this.renderer) { + console.error('Renderer not found') + return + } + + if (!this.renderer.audioListener) { + console.error("No AudioListener component found in the Renderer's child components tree!") + return + } + + const positionalAudio = new PositionalAudio(this.renderer.audioListener) + this.initAudio(positionalAudio) + this.bindProps() + }, + methods: { + bindProps() { + ['refDistance', 'maxDistance', 'rolloffFactor', 'distanceModel'].forEach(p => { + console.log('initialised positionalAudio props') + bindProp(this.$props, p, (this.audio as PositionalAudio).panner) + }) + }, + }, + __hmrId: 'AudioTest', +}) diff --git a/src/audio/StaticAudio.ts b/src/audio/StaticAudio.ts new file mode 100644 index 0000000..d390717 --- /dev/null +++ b/src/audio/StaticAudio.ts @@ -0,0 +1,24 @@ +import { defineComponent, inject } from 'vue' +import { Audio as StaticAudio } from 'three' +import Audio from './Audio' + +export default defineComponent({ + extends: Audio, + name: 'StaticAudio', + created() { + + if (!this.renderer) { + console.error('Renderer not found') + return + } + + if (!this.renderer.audioListener) { + console.error("No AudioListener component found in the Renderer's child components tree!") + return + } + + const staticAudio = new StaticAudio(this.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 => {