UNPKG

@polygonjs/polygonjs

Version:

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

203 lines (202 loc) 6.14 kB
"use strict"; import { NodeParamsConfig, ParamConfig } from "../utils/params/ParamsConfig"; import { Waveform } from "tone/build/esm/component/analysis/Waveform"; import { AudioNodeAnalyserType } from "../../poly/NodeContext"; import { BaseAnalyserAudioNode } from "./_BaseAnalyser"; import { dbToGain } from "tone/build/esm/core/type/Conversions"; import { isNumber, isBooleanTrue } from "../../../core/Type"; const DEFAULTS = { // normalRange: false, size: 1024, smoothing: 0.8 }; const RANGE_DEFAULT = [1e4, -1e4]; class WaveformAudioParamsConfig extends NodeParamsConfig { constructor() { super(...arguments); /** @param array size will be 2**sizeExponent */ this.sizeExponent = ParamConfig.INTEGER(10, { range: [4, 14], rangeLocked: [true, true] }); /** @param array size */ this.arraySize = ParamConfig.INTEGER(`2**ch('sizeExponent')`, { editable: false }); /** @param a value from between 0 and 1 where 0 represents no time averaging with the last analysis frame */ this.smoothing = ParamConfig.FLOAT(DEFAULTS.smoothing, { range: [0, 1], rangeLocked: [true, true], cook: false // ...effectParamsOptions(paramCallback), }); /** @param normalizes the output between 0 and 1. The value will be in decibel otherwise. */ this.normalRange = ParamConfig.BOOLEAN(1, { cook: false // ...effectParamsOptions(paramCallback) }); /** @param display range param */ this.updateRangeParam = ParamConfig.BOOLEAN(1, { cook: false, callback: (node) => { WaveformAudioNode.PARAM_CALLBACK_updateUpdateRangeParam(node); } }); /** @param range value */ this.range = ParamConfig.VECTOR2([0, 0], { visibleIf: { updateRangeParam: 1 }, editable: false, cook: false }); /** @param accumulated range */ this.maxRange = ParamConfig.VECTOR2(RANGE_DEFAULT, { visibleIf: { updateRangeParam: 1 }, editable: false, cook: false }); /** @param resetMaxRange */ this.resetMaxRange = ParamConfig.BUTTON(null, { visibleIf: { updateRangeParam: 1 }, callback: (node) => { WaveformAudioNode.PARAM_CALLBACK_resetMaxRange(node); } }); } } const ParamsConfig = new WaveformAudioParamsConfig(); export class WaveformAudioNode extends BaseAnalyserAudioNode { constructor() { super(...arguments); this.paramsConfig = ParamsConfig; } static type() { return AudioNodeAnalyserType.WAVEFORM; } initializeNode() { this.io.inputs.setCount(1); } cook(inputContents) { const audioBuilder = inputContents[0]; this._resetEffect(); const waveform = this._effect(); this._updateOnTickHook(); const inputNode = audioBuilder.audioNode(); if (inputNode) { inputNode.connect(waveform); } audioBuilder.setAudioNode(waveform); this.setAudioBuilder(audioBuilder); } getAnalyserValue() { const currentValue = this._getWaveFormValue(); if (!currentValue) { return; } const blendedValue = this._previousValue != null && currentValue.length == this._previousValue.length ? this._blendValue(currentValue, this._previousValue) : currentValue; if (!this._previousValue || currentValue.length != this._previousValue.length) { this._previousValue = new Float32Array(currentValue.length); } this._previousValue.set(currentValue); return blendedValue; } _getWaveFormValue() { if (this.__effect__) { const values = this.__effect__.getValue(); if (isBooleanTrue(this.pv.normalRange)) { return values.map((v) => dbToGain(v)); } else { return values; } } } _blendValue(currentValue, previousValue) { const smoothing = this.pv.smoothing; if (smoothing == 0) { return currentValue; } if (smoothing == 1) { return previousValue; } const count = currentValue.length; for (let i = 0; i < count; i++) { currentValue[i] = smoothing * previousValue[i] + (1 - smoothing) * currentValue[i]; } return currentValue; } _effect() { return this.__effect__ = this.__effect__ || this._createEffect(); } _createEffect() { const effect = new Waveform({ size: this._effectSize() }); return effect; } _resetEffect() { if (this.__effect__) { this.__effect__.dispose(); this.__effect__ = void 0; } } // static PARAM_CALLBACK_updateEffect(node: WaveformAudioNode) { // node._updateEffect(); // } // private _updateEffect() { // // const effect = this._effect(); // // const analyser = effect.getAnalyser(); // // analyser.smoothing = this.pv.smoothing; // } _effectSize() { return 2 ** this.pv.sizeExponent; } /* * UPDATE RANGE PARAM */ static PARAM_CALLBACK_updateUpdateRangeParam(node) { node._updateRangeParam(); node._updateOnTickHook(); } static PARAM_CALLBACK_resetMaxRange(node) { node.p.maxRange.set(RANGE_DEFAULT); } _updateRangeParam() { if (!this.__effect__) { return; } const values = this.getAnalyserValue(); if (!values) { return; } const min = Math.min(...values); const max = Math.max(...values); this.p.range.set([min, max]); if (min < this.pv.maxRange.x && isNumber(min) && isFinite(min)) { this.p.maxRange.x.set(min); } if (max > this.pv.maxRange.y && isNumber(max) && isFinite(max)) { this.p.maxRange.y.set(max); } } /* * REGISTER TICK CALLBACK */ _updateOnTickHook() { if (isBooleanTrue(this.pv.updateRangeParam)) { this._registerOnTickHook(); } else { this._unRegisterOnTickHook(); } } async _registerOnTickHook() { if (this.scene().registeredBeforeTickCallbacks().has(this._tickCallbackName())) { return; } this.scene().registerOnBeforeTick(this._tickCallbackName(), this._updateRangeParam.bind(this)); } async _unRegisterOnTickHook() { this.scene().unRegisterOnBeforeTick(this._tickCallbackName()); } _tickCallbackName() { return `audio/FFT-${this.graphNodeId()}`; } }