@polygonjs/polygonjs
Version:
node-based WebGL 3D engine https://polygonjs.com
203 lines (202 loc) • 6.14 kB
JavaScript
"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()}`;
}
}