Based on this java code:

package com.github.natanbc.lavadsp.vibrato;

import com.github.natanbc.lavadsp.Converter;

public class VibratoConverter implements Converter {
    private static final int ADDITIONAL_DELAY = 3;
    private static final float BASE_DELAY_SEC = 0.002f; // 2 ms
    private static final float VIBRATO_FREQUENCY_DEFAULT_HZ = 2;
    private static final float VIBRATO_FREQUENCY_MAX_HZ = 14;
    private static final float VIBRATO_DEPTH_DEFAULT_PERCENT = 50;
    
    private final float sampleRate;
    private final Lfo lfo;
    private final RingBuffer buffer;
    private float depth = VIBRATO_DEPTH_DEFAULT_PERCENT / 100;
    
    public VibratoConverter(int sampleRate) {
        if(sampleRate < 1) {
            throw new IllegalArgumentException("Sample rate < 1");
        }
        this.sampleRate = sampleRate;
        this.lfo = new Lfo(sampleRate);
        this.buffer = new RingBuffer((int)(BASE_DELAY_SEC * sampleRate * 2));
    }
    
    public void setDepth(float depth) {
        if(depth <= 0) {
            throw new IllegalArgumentException("Depth <= 0");
        }
        if(depth > 1) {
            throw new IllegalArgumentException("Depth > 1");
        }
        this.depth = depth;
    }
    
    public void setFrequency(float frequency) {
        if(frequency <= 0) {
            throw new IllegalArgumentException("Frequency <= 0");
        }
        if(frequency > VIBRATO_FREQUENCY_MAX_HZ) {
            throw new IllegalArgumentException("Frequency > max (" + VIBRATO_FREQUENCY_MAX_HZ + ")");
        }
        lfo.frequency = frequency;
    }
    
    @Override
    public void process(float[] input, int inputOffset, float[] output, int outputOffset, int samples) {
        for(int i = 0; i < samples; i++) {
            float lfoValue = lfo.getValue();
            int maxDelay = (int)(BASE_DELAY_SEC * sampleRate);
    
            float delay = lfoValue * depth * maxDelay + ADDITIONAL_DELAY;
    
            output[outputOffset + i] = buffer.getHermiteAt(delay);
    
            buffer.writeMargined(input[inputOffset + i]);
        }
    }
    
    private static class Lfo {
        private final float sampleRate;
        float frequency = VIBRATO_FREQUENCY_DEFAULT_HZ;
        float phase;
    
        Lfo(float sampleRate) {
            this.sampleRate = sampleRate;
        }
        
        float getValue() {
            float dp = 2 * (float)Math.PI * frequency / sampleRate;
            float value = (float)((Math.sin(phase) + 1) * 0.5);
            phase += dp;
            while(phase > 2 * Math.PI) {
                phase -= 2 * Math.PI;
            }
            return value;
        }
    }
    
    private static class RingBuffer {
        private static final int INTERPOLATOR_MARGIN = 3;
        
        private final float[] buffer;
        private final int size;
        private int writeIndex;
        
        RingBuffer(int size) {
            this.buffer = new float[size + INTERPOLATOR_MARGIN];
            this.size = size;
        }
        
        void writeMargined(float sample) {
            buffer[writeIndex] = sample;
    
            if(writeIndex < INTERPOLATOR_MARGIN) {
                buffer[size + writeIndex] = sample;
            }
    
            writeIndex++;
            if(writeIndex == size){
                writeIndex = 0;
            }
        }
    
        float getHermiteAt(float delay) {
            float fReadIndex = writeIndex - 1 - delay;
            while(fReadIndex < 0) {
                fReadIndex += size;
            }
            while(fReadIndex >= size) {
                fReadIndex -= size;
            }
        
            int iPart = (int)fReadIndex; // integer part of the delay
            float fPart = fReadIndex - iPart; // fractional part of the delay
    
            return getSampleHermite4p3o(fPart, buffer, iPart);
        }
        
        // Hermite polynomial interpolation
        // 4-point, 3rd-order Hermite (x-form)
        private static float getSampleHermite4p3o(float x, float[] buffer, int offset) {
            float y0 = buffer[offset];
            float y1 = buffer[offset + 1];
            float y2 = buffer[offset + 2];
            float y3 = buffer[offset + 3];
        
            //c0 = y[1];
            //c1 = (1.0/2.0)*(y[2]-y[0]);
            float c1 = (1f / 2f) * (y2 - y0);
            //c2 = (y[0] - (5.0/2.0)*y[1]) + (2.0*y[2] - (1.0/2.0)*y[3]);
            float c2 = (y0 - (5f / 2f) * y1) + (2f * y2 - (1f / 2f) * y3);
            //c3 = (1.0/2.0)*(y[3]-y[0]) + (3.0/2.0)*(y[1]-y[2]);
            float c3 = (1f / 2f) * (y3 - y0) + (3f / 2f) * (y1 - y2);
            return ((c3 * x + c2) * x + c1) * x + y1;
        }
    }
}

Please add the "vibrato" filter to this nodejs code, and follow the syntax of the nodejs code:

class ChannelProcessor {
  constructor(data, type) {
    this.type = type

    switch (type) {
      case constants.filtering.types.equalizer: {
        this.history = new Array(constants.filtering.equalizerBands * 6).fill(0)
        this.bandMultipliers = data
        this.current = 0
        this.minus1 = 2
        this.minus2 = 1

        break
      }
      case constants.filtering.types.tremolo: {
        this.frequency = data.frequency
        this.depth = data.depth
        this.phase = 0

        break
      }
      case constants.filtering.types.rotationHz: {
        this.phase = 0
        this.rotationStep = (constants.circunferece.diameter * data.rotationHz) / constants.opus.samplingRate

        break
      }
    }
  }

  processEqualizer(band) {
    let processedBand = band * 0.25

    for (let bandIndex = 0; bandIndex < constants.filtering.equalizerBands; bandIndex++) {
      const coefficient = constants.sampleRate.coefficients[bandIndex]

      const x = bandIndex * 6
      const y = x + 3

      const bandResult = coefficient.alpha * (band - this.history[x + this.minus2]) + coefficient.gamma * this.history[y + this.minus1] - coefficient.beta * this.history[y + this.minus2]

      this.history[x + this.current] = band
      this.history[y + this.current] = bandResult

      processedBand += bandResult * this.bandMultipliers[bandIndex]
    }

    return processedBand * 4
  }

  processTremolo(sample) {
    const lfo = Math.sin(constants.circunferece.diameter * this.frequency * this.phase / constants.opus.samplingRate)
    const newAmplitude = sample * ((1 - this.depth) + this.depth * lfo)

    if (this.phase >= constants.opus.samplingRate / this.frequency) this.phase = 0
    else this.phase += 1

    return newAmplitude
  }

  processRotationHz(leftSample, rightSample) {
    const panning = Math.sin(this.phase)
  
    const leftMultiplier = panning <= 0 ? 1 : 1 - panning
    const rightMultiplier = panning >= 0 ? 1 : 1 + panning
  
    this.phase += this.rotationStep
    if (this.phase > constants.circunferece.diameter)
      this.phase -= constants.circunferece.diameter
  
    return {
      left: leftSample * leftMultiplier,
      right: rightSample * rightMultiplier
    }
  }

  process(samples) {
    const bytes = constants.pcm.bytes

    for (let i = 0; i < samples.length - bytes; i += bytes * 2) {
      const sample = samples.readInt16LE(i)
      let result = null
      
      switch (this.type) {
        case constants.filtering.types.equalizer: {
          result = this.processEqualizer(sample)

          if (this.current++ == 3) this.current = 0
          if (this.minus1++ == 3) this.minus1 = 0
          if (this.minus2++ == 3) this.minus2 = 0

          break
        }
        case constants.filtering.types.tremolo: {
          result = this.processTremolo(sample)

          break
        }
        case constants.filtering.types.rotationHz: {
          const rightSample = samples.readInt16LE(i + 2)
          const { left, right } = this.processRotationHz(sample, rightSample)

          samples.writeInt16LE(clamp16Bit(left), i)
          samples.writeInt16LE(clamp16Bit(right), i + 2)

          break
        }
      }

      if (this.type != constants.filtering.types.rotationHz)
        samples.writeInt16LE(clamp16Bit(result), i)
    }

    return samples
  }
}

class Filtering extends Transform {
  constructor(data, type) {
    super()

    this.type = type
    this.channel = new ChannelProcessor(data, type)
  }

  process(input) {
    this.channel.process(input)
  }

  _transform(data, _encoding, callback) {
    this.process(data)

    return callback(null, data)
  }
}