UNPKG

@polygonjs/polygonjs

Version:

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

197 lines (186 loc) 5.5 kB
/** * creates a meter, which can read the raw value of the signal * * */ import {NodeParamsConfig, ParamConfig} from '../utils/params/ParamsConfig'; import {AudioBuilder} from '../../../core/audio/AudioBuilder'; import {Meter} from 'tone/build/esm/component/analysis/Meter'; import {AudioNodeAnalyserType} from '../../poly/NodeContext'; import {isNumber, isBooleanTrue} from '../../../core/Type'; import {effectParamsOptions} from './utils/EffectsController'; import {BaseNodeType} from '../_Base'; import {BaseAnalyserAudioNode} from './_BaseAnalyser'; import {Number2} from '../../../types/GlobalTypes'; const DEFAULTS = { smoothing: 0.8, }; //Meter.getDefaults(); const paramCallback = (node: BaseNodeType) => { MeterAudioNode.PARAM_CALLBACK_updateEffect(node as MeterAudioNode); }; const RANGE_DEFAULT: Number2 = [10000, -10000]; class MeterAudioParamsConfig extends NodeParamsConfig { /** @param a value from between 0 and 1 where 0 represents no time averaging with the last analysis frame */ smoothing = ParamConfig.FLOAT(DEFAULTS.smoothing, { range: [0, 1], rangeLocked: [true, true], ...effectParamsOptions(paramCallback), }); /** @param normalizes the output between 0 and 1. The value will be in decibel otherwise. */ normalRange = ParamConfig.BOOLEAN(1, effectParamsOptions(paramCallback)); /** @param display meter param */ updateValueParam = ParamConfig.BOOLEAN(1, { cook: false, callback: (node: BaseNodeType) => { MeterAudioNode.PARAM_CALLBACK_updateUpdateMeterParam(node as MeterAudioNode); }, }); /** @param meter value */ value = ParamConfig.FLOAT(0, { visibleIf: {updateValueParam: 1}, range: [-100, 100], editable: false, cook: false, }); /** @param display meter param */ updateRangeParam = ParamConfig.BOOLEAN(1, { cook: false, callback: (node: BaseNodeType) => { MeterAudioNode.PARAM_CALLBACK_updateUpdateMeterParam(node as MeterAudioNode); }, }); /** @param accumulated range */ maxRange = ParamConfig.VECTOR2([10000, -10000], { visibleIf: {updateRangeParam: 1}, editable: false, cook: false, }); /** @param resetMaxRange */ resetMaxRange = ParamConfig.BUTTON(null, { visibleIf: {updateRangeParam: 1}, callback: (node) => { MeterAudioNode.PARAM_CALLBACK_resetMaxRange(node as MeterAudioNode); }, }); } const ParamsConfig = new MeterAudioParamsConfig(); export class MeterAudioNode extends BaseAnalyserAudioNode<MeterAudioParamsConfig> { override paramsConfig = ParamsConfig; static override type() { return AudioNodeAnalyserType.METER; } override initializeNode() { this.io.inputs.setCount(1); } override cook(inputContents: AudioBuilder[]) { const audioBuilder = inputContents[0]; this._resetEffect(); const meter = this._effect(); this._updateOnTickHook(); const inputNode = audioBuilder.audioNode(); if (inputNode) { inputNode.connect(meter); } audioBuilder.setAudioNode(meter); this.setAudioBuilder(audioBuilder); } private _arrayValue = new Float32Array(1); getAnalyserValue() { if (this.__effect__) { const value = this.__effect__.getValue(); if (isNumber(value)) { this._arrayValue[0] = value; return this._arrayValue; } else { return value; } } } private __effect__: Meter | undefined; private _effect() { return (this.__effect__ = this.__effect__ || this._createEffect()); } private _createEffect() { return new Meter({ smoothing: this.pv.smoothing, normalRange: isBooleanTrue(this.pv.normalRange), }); } private _resetEffect() { if (this.__effect__) { this.__effect__.dispose(); this.__effect__ = undefined; } } static PARAM_CALLBACK_updateEffect(node: MeterAudioNode) { node._updateEffect(); } private _updateEffect() { const effect = this._effect(); effect.normalRange = isBooleanTrue(this.pv.normalRange); effect.smoothing = this.pv.smoothing; } /* * UPDATE METER PARAM */ static PARAM_CALLBACK_updateUpdateMeterParam(node: MeterAudioNode) { node._updateMeterParam(); node._updateOnTickHook(); } static PARAM_CALLBACK_resetMaxRange(node: MeterAudioNode) { node.p.maxRange.set(RANGE_DEFAULT); } private _updateMeterParam() { if (!this.__effect__) { return; } const value = this.getAnalyserValue(); if (!value) { return; } const valueN = value[0]; if (!isFinite(valueN)) { return; } // we check that we have a number again in case meter.getValue() // returns Infinity if (isBooleanTrue(this.pv.updateValueParam)) { if (isNumber(valueN)) { this.p.value.set(valueN); } } if (isBooleanTrue(this.pv.updateRangeParam)) { // update max range const newVal = valueN; if (newVal < this.pv.maxRange.x) { this.p.maxRange.x.set(newVal); } else { if (newVal > this.pv.maxRange.y) { this.p.maxRange.y.set(newVal); } } } } /* * REGISTER TICK CALLBACK */ private _updateOnTickHook() { if (isBooleanTrue(this.pv.updateValueParam) || isBooleanTrue(this.pv.updateRangeParam)) { this._registerOnTickHook(); } else { this._unRegisterOnTickHook(); } } private async _registerOnTickHook() { if (this.scene().registeredBeforeTickCallbacks().has(this._tickCallbackName())) { return; } this.scene().registerOnBeforeTick(this._tickCallbackName(), this._updateMeterParam.bind(this)); } private async _unRegisterOnTickHook() { this.scene().unRegisterOnBeforeTick(this._tickCallbackName()); } private _tickCallbackName() { return `audio/Meter-${this.graphNodeId()}`; } }