import * as lfo from 'waves-lfo/core';
import Ticker from '@ircam/ticker';
if (!Float32Array.prototype.fill) {
Float32Array.prototype.fill = function(val) {
for (let i = 0; i < this.length; i++) {
this[i] = val;
}
}
}
const parameters = {
frameRate: {
type: 'integer',
min: 1,
max: +Infinity,
default: 20,
constant: true,
metas: {
unit: 'Hz',
},
},
};
/**
* Module that naïvely resample an incomming vector frame at a given framerate.
* If 0 frame has been received since last tick, output last values.
* If more than 1 frame since last tick, output the mean of all the frames.
*
* @memberof operator
*
* @todo - add option for output type (i.e. mean, max, min, last, median, etc.)
*
* @param {Object} [options] - Override default options.
* @param {Number} [options.frameRate=20] - output sampling rate (in Hz)
*/
class Sampler extends lfo.BaseLfo {
constructor(options = {}) {
super(parameters, options);
this.ticker = null;
this.buffer = null;
this.bufferIndex = 0;
this.propagateFrame = this.propagateFrame.bind(this);
}
/** @private */
processStreamParams(prevStreamParams) {
this.prepareStreamParams(prevStreamParams);
const frameRate = this.params.get('frameRate'); // period is in Hz
this.streamParams.frameRate = frameRate;
// build buffer
const frameSize = this.streamParams.frameSize;
let sourceFrameRate = prevStreamParams.frameRate;
if (sourceFrameRate <= 0 || !isFinite(sourceFrameRate))
sourceFrameRate = 100; // arbitrary value hoping that we won't loose data
// max number of source frames to store
const bufferSize = Math.ceil(sourceFrameRate / frameRate);
this.maxBufferIndex = bufferSize;
this.buffer = new Float32Array(bufferSize * frameSize);
this.sums = new Float32Array(frameSize);
this.propagateStreamParams();
}
/** @private */
finalizeStream(endTime) {
// @todo - output current data, compute proper endTime
super.finalizeStream(endTime);
this.ticker.stop();
this.ticker = null;
}
/** @private */
processVector(frame) {
if (this.bufferIndex < this.maxBufferIndex) {
const data = frame.data;
const frameSize = this.streamParams.frameSize;
for (let i = 0; i < frameSize; i++)
this.buffer[this.bufferIndex * frameSize + i] = data[i];
this.bufferIndex += 1;
}
}
/** @private */
processScalar(value) {
if (this.bufferIndex < this.maxBufferIndex) {
const data = frame.data;
const frameSize = this.streamParams.frameSize;
this.buffer[this.bufferIndex * frameSize] = data[0];
this.bufferIndex += 1;
}
}
/** @private */
processFrame(frame) {
this.prepareFrame();
this.frame.metadata = frame.metadata;
this.processFunction(frame);
if (this.ticker === null) {
const period = 1000 / this.params.get('frameRate'); // in ms
this.ticker = new Ticker(period, this.propagateFrame);
this.ticker.start();
}
}
/** @private */
propagateFrame(logicalTime) {
this.frame.time = logicalTime / 1000;
if (this.bufferIndex > 0)
this._computeFrameData();
super.propagateFrame();
}
/** @private */
_computeFrameData() {
const numFrames = this.bufferIndex;
const frameSize = this.streamParams.frameSize;
const buffer = this.buffer;
const data = this.frame.data;
// get means for each vector index
const sums = this.sums;
sums.fill(0);
for (let frameIndex = 0; frameIndex < numFrames; frameIndex++) {
for (let i = 0; i < frameSize; i++)
sums[i] += buffer[frameSize * frameIndex + i];
}
for (let i = 0; i < frameSize; i++)
data[i] = sums[i] / numFrames;
this.bufferIndex = 0;
}
}
export default Sampler;