UNPKG

@polygonjs/polygonjs

Version:

node-based WebGL 3D engine https://polygonjs.com

253 lines (236 loc) 8.44 kB
/** * Creates an audio source. * * @remarks * This node also has its own transforms. And if it is set as input of other nodes, their objects will be added as children to the object of this node. * */ import {TypedObjNode} from './_Base'; import {Group} from 'three'; import {TransformedParamConfig, TransformController} from './utils/TransformController'; import {FlagsControllerD} from '../utils/FlagsController'; import {NodeParamsConfig, ParamConfig} from '../utils/params/ParamsConfig'; import {HierarchyController} from './utils/HierarchyController'; import {ObjType} from '../../poly/registers/nodes/types/Obj'; import {NodeContext} from '../../poly/NodeContext'; import {isBooleanTrue} from '../../../core/BooleanValue'; import {CorePositionalAudio, DistanceModel, DISTANCE_MODELS} from '../../../core/audio/PositionalAudio'; import {CorePositionalAudioHelper} from '../../../core/audio/PositionalAudioHelper'; import {BaseNodeType} from '../_Base'; import {AudioNodeChildrenMap} from '../../poly/registers/nodes/Audio'; import {NodeCreateOptions} from '../utils/hierarchy/ChildrenController'; import {Constructor, valueof} from '../../../types/GlobalTypes'; import {BaseAudioNodeType} from '../audio/_Base'; const paramCallback = () => { return { cook: false, callback: (node: BaseNodeType) => { PositionalAudioObjNode.PARAM_CALLBACK_updatePositionalAudio(node as PositionalAudioObjNode); }, }; }; class PositionalAudioParamConfig extends TransformedParamConfig(NodeParamsConfig) { audio = ParamConfig.FOLDER(); /** @param audio node */ audioNode = ParamConfig.NODE_PATH('', { nodeSelection: { context: NodeContext.AUDIO, }, // dependentOnFoundNode: false, }); /** @param refDistance. See https://developer.mozilla.org/en-US/docs/Web/API/PannerNode/refDistance*/ refDistance = ParamConfig.FLOAT(10, { range: [0, 10], rangeLocked: [true, false], ...paramCallback(), }); /** @param rolloffFactor. See https://developer.mozilla.org/en-US/docs/Web/API/PannerNode/rolloffFactor */ rolloffFactor = ParamConfig.FLOAT(10, { range: [0, 10], rangeLocked: [true, false], ...paramCallback(), }); /** @param maxDistance. See https://developer.mozilla.org/en-US/docs/Web/API/PannerNode/maxDistance */ maxDistance = ParamConfig.FLOAT(100, { range: [0.001, 100], rangeLocked: [true, false], ...paramCallback(), }); /** @param distanceModel. See https://developer.mozilla.org/en-US/docs/Web/API/PannerNode/distanceModel */ distanceModel = ParamConfig.INTEGER(DISTANCE_MODELS.indexOf(DistanceModel.LINEAR), { menu: { entries: DISTANCE_MODELS.map((name, value) => { return {name, value}; }), }, ...paramCallback(), }); /** @param coneInnerAngle. See https://developer.mozilla.org/en-US/docs/Web/API/PannerNode */ coneInnerAngle = ParamConfig.FLOAT(360, { range: [0, 360], rangeLocked: [true, true], ...paramCallback(), }); /** @param coneOuterAngle. See https://developer.mozilla.org/en-US/docs/Web/API/PannerNode */ coneOuterAngle = ParamConfig.FLOAT(230, { range: [0, 360], rangeLocked: [true, true], ...paramCallback(), }); /** @param coneOuterGain. See https://developer.mozilla.org/en-US/docs/Web/API/PannerNode */ coneOuterGain = ParamConfig.FLOAT(0.1, { range: [0, 1], rangeLocked: [true, true], ...paramCallback(), }); /** @param ensures the transform of the audio listener is updated on very frame */ listenerTransformAutoUpdate = ParamConfig.BOOLEAN(1); /** @param show helper */ showHelper = ParamConfig.BOOLEAN(1); /** @param helper size */ helperSize = ParamConfig.FLOAT(1, { range: [0, 10], rangeLocked: [true, false], visibleIf: {showHelper: true}, ...paramCallback(), }); } const ParamsConfig = new PositionalAudioParamConfig(); function createPositionalAudio() { const positionalAudio = new CorePositionalAudio(); positionalAudio.matrixAutoUpdate = false; return positionalAudio; } export class PositionalAudioObjNode extends TypedObjNode<Group, PositionalAudioParamConfig> { override paramsConfig = ParamsConfig; static override type() { return ObjType.POSITIONAL_AUDIO; } override readonly hierarchyController: HierarchyController = new HierarchyController(this); override readonly transformController: TransformController = new TransformController(this); public override readonly flags: FlagsControllerD = new FlagsControllerD(this); private _positionalAudio = createPositionalAudio(); private _helper: CorePositionalAudioHelper | undefined; override createObject() { const group = new Group(); group.matrixAutoUpdate = false; group.add(this._positionalAudio); return group; } override initializeNode() { this.hierarchyController.initializeNode(); this.transformController.initializeNode(); this._updateHelperHierarchy(); this.flags.display.onUpdate(async () => { await this._updateToDestination(); await this._updateHelperHierarchy(); }); } private async _updateToDestination() { if (this.flags.display.active()) { const listener = this.root().audioController.audioListeners()[0]; if (!listener) { this.states.error.set('a listener is required in the scene'); return; } // this._positionalAudio.disconnect(); listener.object.addInput(this._positionalAudio); } else { this._positionalAudio.disconnect(); } } private _updateHelperHierarchy() { if (!this._helper) { return; } if (this.flags.display.active()) { this.object.add(this._helper); } else { this.object.remove(this._helper); } } override async cook() { this.transformController.update(); await this._updateToDestination(); this._updatePositionalAudio(); this._connectAudioNode(); this.cookController.endCook(); } private async _updatePositionalAudio() { this._positionalAudio.setRefDistance(this.pv.refDistance); this._positionalAudio.setRolloffFactor(this.pv.rolloffFactor); this._positionalAudio.setMaxDistance(this.pv.maxDistance); this._positionalAudio.setDistanceModel(DISTANCE_MODELS[this.pv.distanceModel]); this._positionalAudio.setDirectionalCone(this.pv.coneInnerAngle, this.pv.coneOuterAngle, this.pv.coneOuterGain); this._positionalAudio.listenerTransformAutoUpdate = isBooleanTrue(this.pv.listenerTransformAutoUpdate); if (isBooleanTrue(this.pv.showHelper)) { this._helper = this._helper || this._createHelper(this._positionalAudio); this.object.add(this._helper); } if (this._helper) { this._helper.visible = isBooleanTrue(this.pv.showHelper); this._helper.range = this.pv.helperSize; this._helper.update(); } } private _createHelper(positionalAudio: CorePositionalAudio) { const helper = new CorePositionalAudioHelper(positionalAudio); helper.matrixAutoUpdate = false; return helper; } private _resetAudioNode() { this._positionalAudio.setInput(undefined); } private async _connectAudioNode() { this._resetAudioNode(); const baseAudioNode = this.pv.audioNode.nodeWithContext(NodeContext.AUDIO); if (!baseAudioNode) { this.states.error.set('no audio node found'); this._resetAudioNode(); return; } const audioContainer = await baseAudioNode.compute(); const audioBuilder = audioContainer.coreContent(); if (!audioBuilder) { this.states.error.set('invalid audio node'); this._resetAudioNode(); return; } const toneAudioNode = audioBuilder.audioNode(); if (!toneAudioNode) { this.states.error.set('no valid audio node given'); this._resetAudioNode(); return; } this._positionalAudio.setInput(toneAudioNode); } static PARAM_CALLBACK_updatePositionalAudio(node: PositionalAudioObjNode) { node._updatePositionalAudio(); } /* * * CHILDREN * */ protected override _childrenControllerContext = NodeContext.AUDIO; override createNode<S extends keyof AudioNodeChildrenMap>( node_class: S, options?: NodeCreateOptions ): AudioNodeChildrenMap[S]; override createNode<K extends valueof<AudioNodeChildrenMap>>( node_class: Constructor<K>, options?: NodeCreateOptions ): K; override createNode<K extends valueof<AudioNodeChildrenMap>>( node_class: Constructor<K>, options?: NodeCreateOptions ): K { return super.createNode(node_class, options) as K; } override children() { return super.children() as BaseAudioNodeType[]; } override nodesByType<K extends keyof AudioNodeChildrenMap>(type: K): AudioNodeChildrenMap[K][] { return super.nodesByType(type) as AudioNodeChildrenMap[K][]; } }