Reputation: 37
I have a class I am attempting to write that looks like the below.
When I run the class, I get an error:
Audio.js:53 Uncaught ReferenceError: bufferLength is not defined
I believe this is because bufferLength
is not available in the drawCanvas
function.
I have tried adding the this
keyword, however this did not work.
How can I make these variables available to functions within a method of this class?
export const LINE_COLORS = ['rgba(255, 23, 204, 0.5)', 'rgba(130, 23, 255, 0.5)'];
// The dimensions of the current viewport
// - Used to set canvas width & height
export const PAGE_DIMENSIONS = {
width: window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth,
height: window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight,
};
class AudioEngine {
constructor(params) {
this.params = params;
this.audio = new Audio();
this.ctx = new (window.AudioContext || window.webkitAudioContext)();
this.analyser = this.ctx.createAnalyser();
this.source = this.ctx.createMediaElementSource(this.audio);
this.dataArray = new Uint8Array(this.analyser.frequencyBinCount);
this.bufferLength = this.analyser.frequencyBinCount;
this.canvas = document.getElementById(params.waveform);
this.onInit();
}
onInit() {
this.ConfigAudio();
this.DrawAudioWave();
}
ConfigAudio = () => {
this.audio.src = this.params.stream;
this.audio.controls = false;
this.audio.autoplay = false;
this.audio.crossOrigin = 'anonymous';
this.analyser.smoothingTimeConstant = 0.6;
this.source.connect(this.ctx.destination);
this.source.connect(this.analyser);
this.analyser.fftSize = 2048;
this.analyser.getByteFrequencyData(this.dataArray);
document.body.appendChild(this.audio);
};
DrawAudioWave = () => {
// Bind to the context
const canvasCtx = this.canvas.getContext('2d');
function drawCanvas() {
// We always start the drawing function by clearing the canvas. Otherwise
// we will be drawing over the previous frames, which gets messy real quick
canvasCtx.clearRect(0, 0, PAGE_DIMENSIONS.width, PAGE_DIMENSIONS.height);
requestAnimationFrame(drawCanvas);
const sliceWidth = (PAGE_DIMENSIONS.width * 1.0) / bufferLength;
// Create a new bounding rectangle to act as our backdrop, and set the
// fill color to black.
canvasCtx.fillStyle = '#000';
canvasCtx.fillRect(0, 0, PAGE_DIMENSIONS.width, PAGE_DIMENSIONS.height);
// Loop over our line colors. This allows us to draw two lines with
// different colors.
LINE_COLORS.forEach((color, index) => {
let x = 0;
// Some basic line width/color config
canvasCtx.lineWidth = 2;
canvasCtx.strokeStyle = color;
// Start drawing the path
canvasCtx.beginPath();
for (let i = 0; i < bufferLength; i++) {
// We offset using the loops index (stops both colored lines
// from overlapping one another)
const v = dataArray[i] / 120 + index / 20;
// Set the Y point to be half of the screen size (the middle)
const y = (v * PAGE_DIMENSIONS.height) / 2;
if (i === 0) {
canvasCtx.moveTo(x, y);
} else {
canvasCtx.lineTo(x, y);
}
x += sliceWidth;
}
canvasCtx.lineTo(canvas.width, canvas.height / 2);
canvasCtx.stroke();
});
}
drawCanvas();
};
audioToggle = () => (this.audio.paused ? this.audio.play() : this.audio.pause());
}
export default AudioEngine;
Upvotes: 1
Views: 2505
Reputation: 3574
requestAnimationFrame will call drawCanvas with the global context bound, so this.bufferLength
will not be defined. Easiest solution is to take advantage of lexical scoping, which arrow functions make easy.
If you look at how you're defining your class's methods, they're using arrow functions. This is precisely to avoid the issue with rebinding this
that you're currently having. You should read up on this
and scoping in JavaScript to better understand why your code isn't working as you'd expect.
Couple solutions, both require drawCanvas -> this.drawCanvas
:
(A) Bind the context of drawCanvas before passing it to requestAnimationFrame:
requestAnimationFrame(drawCanvas.bind(this))
Or (B) take advantange of lexical scoping:
requestAnimationFrame(() => drawCanvas())
"Lexical" scope is scope derived from the "text", i.e. where your arrow function is defined relative to other scopes. Non-lexical scoping uses the caller function to determine bound context.
You can also change the drawCanvas
function itself to be bound to the appropriate scope using .bind
or changing it to an arrow function.
Further Reading:
setTimeout
with requestAnimationFrame
in their examples)Upvotes: 3