react decibel meter and use - javascript

i need to make a decibel meter
i try to use 2 libary but they wont work and they very old
i found a function that make this but it wont work as expected
first of all the issue is
inside this function i get the
" let decibelNumString = decibelNum.toString().substring(0, 2); "
which is get me the number of the decibel and make it to integear
but
i want to make the minNumber and MaxNumber as state because it should re render after the changes happen
the probme it make a lot of re render becuase every 100ms it change the numbers
i have also the current decibel so it need to be render every 100ms
and it not good for perfomance
// const [minNumber, setMinNumber] = useState(25);
// const [maxNumber, setMaxNumber] = useState(30);
let arr = [];
let volumeCallback = null;
let volumeInterval = null;
(async () => {
// Initialize
try {
const audioStream = await navigator.mediaDevices.getUserMedia({
audio: {
echoCancellation: true,
},
});
const audioContext = new AudioContext();
const audioSource = audioContext.createMediaStreamSource(audioStream);
const analyser = audioContext.createAnalyser();
analyser.fftSize = 512;
analyser.minDecibels = -127;
analyser.maxDecibels = 0;
analyser.smoothingTimeConstant = 0.4;
audioSource.connect(analyser);
const volumes = new Uint8Array(analyser.frequencyBinCount);
volumeCallback = () => {
analyser.getByteFrequencyData(volumes);
let volumeSum = 0;
for (const volume of volumes) volumeSum += volume;
const averageVolume = volumeSum / volumes.length;
// Value range: 127 = analyser.maxDecibels - analyser.minDecibels;
let decibelNum = (averageVolume * 100) / 127;
let decibelNumString = decibelNum.toString().substring(0, 2);
if (Number(decibelNumString) > maxNumber) {
// (maxNumber = Number(decibelNum))
// setMaxNumber((prev) => {
// return (prev = Number(decibelNumString));
// });
}
if (Number(decibelNumString) < minNumber) {
// setMinNumber((prev) => {
// return (prev = Number(decibelNumString));
// });
}
console.log(decibelNumString);
// console.log(minNumber + "min");
// console.log(maxNumber + "max");
arr.push(Number(decibelNumString));
// console.log(arr)
};
} catch (e) {
console.error("Failed to initialize volume visualizer, simulating instead...", e);
// Simulation
//TODO remove in production!
let lastVolume = 50;
volumeCallback = () => {
const volume = Math.min(Math.max(Math.random() * 100, 0.8 * lastVolume), 1.2 * lastVolume);
lastVolume = volume;
};
}
// Use
// start();
})();
const stop = () => {
clearInterval(volumeInterval);
// volumeInterval = null;
};
const start = () => {
// Updating every 100ms (should be same as CSS transition speed)
if (volumeCallback !== null && volumeInterval === null) volumeInterval = setInterval(volumeCallback, 100);
};

Related

Metronome JavaScript - function .pause() don't stop the Audio beep

I trying to do a metronome on Codepen.
But I have one problem because the function .pause() doesn't stop the beep(my sound).
This is the code that I wrote. I read about it but I don't find the problem.
The audio I found on Codepen. I don't think it's the problem. I think the problem is between my computer and my chair.
const beep = new Audio(
"data:audio/flac;base64,ZkxhQwAAACIQABAAAARBAARBAfQBcAAAAZLfwZb9QVlTtnnZLOsaWczxhAAAKCAAAAByZWZlcmVuY2UgbGliRkxBQyAxLjMuMCAyMDEzMDUyNgAAAAD/+HQMAAGRJhgKxHRX63x+6QxXqTpCYAANDJgITNAmIhAB/5BgFt4iAfgBZHY4A9UZAAZ65QManTnQUAKnx8A+ZcwFun6gX1gMVIgAfaXgEml9KIzgfUBYAnhLwJl/Oek+A5rZADhx2ApN3s198CTrfAPyHMGRV3DcKAYAAwBJ7uA8wysUF8HYV6AUyIIKv/ObD0B7BRAOf6QFVnlVIrg0zuwGYTqCTP9rJXAWm4AC1HQDxVMnllwVg94CGPDDq6XrJRgQ2zwHp6wGR5tQFGh9dygZtwIXeHNK+AHAw8BZ34BSgBUY44Ze3IEt2uEhBfQg3BcaTA+MSB/rIUTNIUTWYN9hMdT2M/TYEg+cDC6KGzFJQAzj8TBBUujjHFLnsqB07TAj7JBa4qTwQY15pQ9rKxSdhzrJwxaKA3o/BKIsTkDYtGCwxtDRCIqzegQo2pAr9KD0mKmwtRJeuhMscnKzjNLLCC4CCDoYNaPiZmNPNIsdzTJkF0zHIRyDGDZzCLozKYLfNVZUYt6pWUiy7/Bjf5CyRUJ95iW1TLkIET7MJJU5yxARVwzCNBsIZHCVflJ5ZE+0K30dXKabEjNYdP+TulglDBSFPJNlCtxqLycmj5OeMkURsMFSXgrtnXLllFm5cySbjiZQKoshh0ASOArXdBJuJ9c2syJBjM8II1VJYFtSEVbCw3f80qnABkAClxxw8mPk8NdHt4q9gNy0UpWEuO2UUx3jZSuRqVkcoWnWIWa1ahV3Y3K5KbKMH879vqjUtUZMatwgfZeOMvsQlyisbQ71rIxGX9FhUSYvRHvF3xeunSYfQ4iRItSvuvzR5iO3xDFaEdARSGOC1fGIpY/hll/Z8dBI+J6u22KvVuDK2IiXW9V8Htkhxa94kLDPz25hsMC8qImvymxd70W8D5iDfsU9TYMYt5EIfJnAWr0bS7NbeHbQu6ZsuZyvTuhwMbc9XFv3q4b4amazBWwDvKfuyGSBrwrbr8+kkvhfZKs7C2FwoVooWXOnr/sWmZ5hWFSSpEtq0O6bjQhPR6Eguo8PmOz4SoKeIgpRmJZ1CEWpm1U6F/2UKJhBBJi0KeJ6kgEoPCeWSBmv85AOeDhOk/mJgciON6g0DZHbeVa7jIhYMBSP4ukvNIr36CvTjhkZCkWJk5godIxpiOjqiEnoJQ2K3HjKtYcY2CFWiXpIrqaGEIgef4gs+JXnhRm4GziHB2h/OIRB6BgfhgH4ap2DiTgVwIUN6FjaguFoE0uENThJRYJMOBCwg3zIOyKB00gOl4LXWC79gW14DOaCQAglUoEKCApZgc1YHC2AxzgJBoFcSBWWgICoBp+BEXgO64BeGAXYgMGoCoCAOYgEk4CGWAb5gB54A2SAWGgEfoAHyAINgDuYAkeAAbgBfoAgyAEEgAC4ARGADPgAf4AFyABYgAeIACGAAMgAaYAECABJgAagfd0="
);
const bpm = document.getElementById('bpm-display');
const time = document.getElementById('time');
const timeSlice = document.getElementById('time-slice');
const rangeBpm = document.getElementById('bpm-range');
const btnStartStop = document.getElementById('start-stop');
const btnReset = document.getElementById('reset');
let intervalSound
// function to start and stop the metronome
const startMetronome = (() => {
btnStartStop.innerText==='Start'? btnStartStop.innerText= 'Stop': btnStartStop.innerText='Start';
if(btnStartStop.innerText === 'Stop'){
intervalSound = (60/bpm.value) * 1000;
console.log(intervalSound);
interval(intervalSound);
}
else{
stopBeat();
}
});
const stopBeat = (()=>{
beep.pause();
beep.currentTime = 0;
})
const playBeat = (() =>{
beep.play();
})
const interval = ((interval) => {
setInterval(playBeat, interval)
})
// function to reset all metronome
const resetMetronome = (()=>{
bpm.value = '120';
rangeBpm.value='120';
time.value='4';
timeSlice.value='4';
intervalSound = 0;
stopBeat();
});
btnReset.addEventListener('click', function(){resetMetronome()});
btnStartStop.addEventListener('click', function(){startMetronome()});
bpm.addEventListener('change', function(){
rangeBpm.value=bpm.value;
})
rangeBpm.addEventListener('click', function(){
bpm.value = rangeBpm.value;
})
console.log(bpm.value, time.value, timeSlice.value, rangeBpm.value, btnStartStop.innerText, btnReset.innerText);
Can you help me?
You need to clear your interval.
let intervalId = -1;
const stopBeat = (()=>{
beep.pause();
beep.currentTime = 0;
clearInterval(intervalId);
})
const interval = ((interval) => {
intervalId = setInterval(playBeat, interval)
})

Simple WebAudioWorklet Generating Choppy Audio

I'm on Firefox 84.0.1, Windows 10, x86_64. I have a very basic WebAudioWorklet synthesiser that maps keys to the frequencies of musical notes. It is generating very choppy audio when a key is held down. This makes me think that there is not enough audio samples being queued for the speaker to play, hence the audio dropping in and out. However, in audio processing terms, I'm performing a very low-intensive task. As a result, I feel like the default Worklet setup should be able to handle this. Here is my code:
syn.js
(async() => {
let a2_hertz = 110.0;
let twelfth_root_of_two = Math.pow(2.0, 1.0 / 12.0);
let audio_cxt = new AudioContext();
await audio_cxt.audioWorklet.addModule("syn-worklet.js", {credentials: "omit"});
let audio_worklet_options = {
numberOfInputs: 0,
numberOfOutputs: 1,
outputChannelCount: [audio_cxt.destination.channelCount]
};
let audio_worklet = new AudioWorkletNode(audio_cxt, "synthesiser", audio_worklet_options);
audio_worklet.connect(audio_cxt.destination);
document.addEventListener("keydown", (evt) => {
for (let key = 0; key < 12; ++key) {
if (evt.code == "Key" + "QWERTYUIOPAS"[key]) {
audio_worklet.port.postMessage(a2_hertz * Math.pow(twelfth_root_of_two, key));
}
}
});
document.addEventListener("keyup", (evt) => {
audio_worklet.port.postMessage(0.0);
});
})();
syn-worklet.js
function angular_frequency(hertz) {
return hertz * 2 * Math.PI;
}
let OSC_TYPES = {"sine": 0, "square": 1, "triangle": 2};
function oscillator(hertz, osc_type) {
switch (osc_type) {
case OSC_TYPES.sine: {
return Math.sin(angular_frequency(hertz) * currentTime);
} break;
case OSC_TYPES.square: {
return Math.sin(angular_frequency(hertz) * currentTime) > 0.0 ? 1.0 : -1.0;
} break;
case OSC_TYPES.triangle: {
return Math.asin(Math.sin(angular_frequency(hertz) * currentTime)) * (2.0 / Math.PI);
} break;
default: {
return 0.0;
}
}
}
class Synthesiser extends AudioWorkletProcessor {
constructor() {
super();
this.hertz = 0.0;
this.port.onmessage = (evt) => {
this.hertz = evt.data;
};
}
process(inputs, outputs) {
let channels = outputs[0];
let num_samples_per_channel = channels[0].length;
for (let pcm_i = 0; pcm_i < num_samples_per_channel; ++pcm_i) {
let volume = 0.1;
let pcm_value = volume * oscillator(this.hertz, OSC_TYPES.sine);
for (let channel_i = 0; channel_i < channels.length; ++channel_i) {
channels[channel_i][pcm_i] = pcm_value;
}
}
return true;
}
}
registerProcessor("synthesiser", Synthesiser);
I think the problem is that currentTime seems to be the only thing which influences the output of your oscillator() function. But currentTime doesn't change during the invocation of the process() function.
I would recommend using currentFrame instead. It will give you an integer value which represents the currentTime in frames. If you combine that with pcm_i you get the actual index of the sample that you're processing.
const currentSample = currentFrame + pcm_i;
const currentSampleInSeconds = (currentFrame + pcm_i) / sampleRate;

In stuck mode to try and create a loop for my products in HTML

I have nearly finished with my project but now stuck on the hardest challenge - create a loop. I am not sure maybe map is the right method to do this. Basically, I have created a 3D can that rotates clockwise that shows the user of the can in full detail. When the cursor hovers the can, the can doubles the size and displays in front of the screen, again to see it in much better detail. Because there are different colour cans for different types of flavours, I need to create a loop and avoid DRY. However, I am struggling to do this.
I am not asking for someone to do this for me but someone to say look at these methods or try this out and see if it works differently etc…
https://codepen.io/Rosstopherrr/pen/GVRvxJ
This is my code. Hopefully, when you look at it you understand what I am. I have a 3D can on display, in an active slider but the next number of slides are empty. This is where I want to fill in the gaps for different cans - that will slide to the left, and if the cursor hovers the can it will display front of the screen.
Just really asking for advice on what to do next, what problems I should be looking at, etc. I’ve been struggling for a week now trying to create a loop but finding it impossible
$("#products>article").on("click", function(){
$("#products>article").removeClass("active");
$(this).addClass("active");
animate();
});
function getActiveArticle(){
var x = 0;
$("#products>article").each(function(e){
if($("#products>article").eq(e).hasClass("active")){
x = e;
return false;
}
});
return x;
}
function gofwd(){
var activeIndex = getActiveArticle();
var minArticles = 0;
var maxArticles = $("#products>article").length - 1;
if(activeIndex >= maxArticles){
activeIndex = minArticles-1;
}
$("#products>article").removeClass("active");
$("#products>article").eq(activeIndex+1).addClass("active");
animate();
}
function gobwd(){
var activeIndex = getActiveArticle();
var minArticles = 1;
var maxArticles = $("#products>article").length;
$("#products>article").removeClass("active");
$("#products>article").eq(activeIndex-1).addClass("active");
animate();
}
$(document).ready(function(){
animate();
});
function animate(){
var articleIndex = getActiveArticle();
var totalMargin = 25 * (articleIndex+1) - (25*(articleIndex));
var articlePosition = Math.floor($("#products>article").eq(articleIndex).offset().left - $("#products").offset().left) - totalMargin;
var productsHalfWidth = $("#products").width()/2;
if(articleIndex == 0){
var halfWidth = 150;
}else{
var halfWidth = 100;
}
var finalPosition = productsHalfWidth - articlePosition - halfWidth;
$("#products").animate({
"left": finalPosition,
}, {
duration: 500,
easing: 'easeOutBack',
});
}
$(window).on("resize", function(){
animate();
});
var autoPlay = setInterval(function(){
gofwd();
}, 3500);
$("#slider").on("mouseenter", function(){
clearInterval(autoPlay);
});
$("#slider").on("mouseleave", function(){
autoPlay = setInterval(function(){
gofwd();
}, 4500);
});
const getElement = (selector) => document.querySelector(selector);
const createElement = (tag) => document.createElement(tag);
// const addBackground1 = document.style['background'] = 'url ("https://i.postimg.cc/BZ8rj2NM/sleve.png")';
const addSideStyle = ($side, i) => {
let deg = 3.75 * i;
let bgPosition = 972 - (i * 10.125);
$side.style['background-position'] = bgPosition + 'px 0';
$side.style['-webkit-transform'] = 'rotateY(' + deg + 'deg) translateZ(154px)';
$side.style['-moz-transform'] = 'rotateY(' + deg + 'deg) translateZ(154px)';
$side.style['transform'] = 'rotateY(' + deg + 'deg) translateZ(154px)';
};
const createBottle = () => {
const $bottle = createElement('div');
$bottle.classList.add('bottle');
const $bottleLabel = createBottleLabel();
for (let i = 0; i < 96; i = i + 1){
let $bottleSide = createBottleSide(i);
$bottleLabel.append($bottleSide);
}
$bottle.append($bottleLabel);
return $bottle;
};
const createBottleLabel = () => {
const $bottleLabel = createElement('div');
$bottleLabel.classList.add('label');
return $bottleLabel;
}
const createBottleSide = (i) => {
const $bottleSide = createElement('div');
$bottleSide.classList.add('side');
addSideStyle($bottleSide, i);
return $bottleSide;
};
const changeBottleSize = (clickFn) => {
const _bottle = createElement('div');
_bottle.classList.add('bottle');
_bottle.style['transform'] = 'scale(0.9)';
return _bottle;
}
const moveBottle = (moveFn) => {
const _moveBottle = createElement('div');
_moveBottle.classList.add('bottle');
_moveBottle.style['left'] = "-1000px";
return _moveBottle;
}
const clickFn = () => {
const $bottleSize = getElement('.container');
const $bottle1 = changeBottleSize();
const $bottle2 = changeBottleSize();
const $bottle3 = changeBottleSize();
$bottleSize.style['transform'] = 'scale(0.9)';
return $bottleSize;
}
const moveFn = () => {
const $bottleMove = getElement('.container');
const $move1 = moveBottle();
$bottleMove.style['left'] = "-1000px";
return $bottleMove;
}
const initApp = () => {
const $container = getElement('.container');
const $bottle1 = createBottle();
const $bottle2 = createBottle();
const $bottle3 = createBottle();
[$bottle1, $bottle2, $bottle3].forEach(($bottle, i) => {
$bottle.classList.add('bottle' + i);
});
$container.append($bottle1, $bottle2, $bottle3);
};
initApp();

Run function only once until onmouseout

I have an anchor tag that plays a sound everytime the link is hovered:
<a onmouseenter="playAudio();">LINK</a>
However, when hovered, the link creates some characters that, if I keep moving the mouse inside the element, it keeps playing the sound over and over again.
Is there a way to play the onmouseenter function only once until it has detected the event onmouseout?
I've tried adding:
<a onmouseenter="playAudio();this.onmouseenter = null;">LINK</a>
but then it only plays the sound once.
Here goes the TextScramble animation function:
// ——————————————————————————————————————————————————
// TextScramble
// ——————————————————————————————————————————————————
class TextScramble {
constructor(el) {
this.el = el
this.chars = '!<>-_\\/[]{}—=+*^?#________'
this.update = this.update.bind(this)
}
setText(newText) {
const oldText = this.el.innerText
const length = Math.max(oldText.length, newText.length)
const promise = new Promise((resolve) => this.resolve = resolve)
this.queue = []
for (let i = 0; i < length; i++) {
const from = oldText[i] || ''
const to = newText[i] || ''
const start = Math.floor(Math.random() * 40)
const end = start + Math.floor(Math.random() * 40)
this.queue.push({
from,
to,
start,
end
})
}
cancelAnimationFrame(this.frameRequest)
this.frame = 0
this.update()
return promise
}
update() {
let output = ''
let complete = 0
for (let i = 0, n = this.queue.length; i < n; i++) {
let {
from,
to,
start,
end,
char
} = this.queue[i]
if (this.frame >= end) {
complete++
output += to
} else if (this.frame >= start) {
if (!char || Math.random() < 0.28) {
char = this.randomChar()
this.queue[i].char = char
}
output += `<span class="dud">${char}</span>`
} else {
output += from
}
}
this.el.innerHTML = output
if (complete === this.queue.length) {
this.resolve()
} else {
this.frameRequest = requestAnimationFrame(this.update)
this.frame++
}
}
randomChar() {
return this.chars[Math.floor(Math.random() * this.chars.length)]
}
}
// ——————————————————————————————————————————————————
// Configuration
// ——————————————————————————————————————————————————
const phrases = [
'MAIN_HUB'
]
const el = document.querySelector('.link_mainhub')
const fx = new TextScramble(el)
let counter = 0
const next = () => {
fx.setText(phrases[counter]).then(() => {});
}
el.addEventListener('mouseenter', next);
<a class="link_mainhub" onmouseenter="playAudio();">LINK</a>
Thanks.
Set a variable when you start playing the audio, and check for this before playing it again. Have the animation unset the variable when it's done.
function audioPlaying = false;
function playAudio() {
if (!audioPlaying) {
audioPlaying = true;
audioEl.play();
}
}
const next = () => {
fx.setText(phrases[counter]).then(() => {
audioPlaying = false;
});
}

How to cache or pre-render canvas animation

I am in the process of doing some slight modification (color keying) to an inline html5 video using canvas. Now, this video is a loop and I'm thinking there are more optimal ways then having a looping video being evaluated and repainted every single time. What optimizations if any can I do to either a) pre-render the entire animation or b) cache the original loop so that no more processing/evaluation needs to occur. I find that I'm being totally inefficient in CPU / memory usage by constantly letting it run.
I am currently using the following code (note, im using Vue.js, so assume that all the current function and variable assignments work correctly already) :
loadVideo() {
this.video = document.getElementById('globe');
this.c1 = document.getElementById("c1");
this.ctx1 = this.c1.getContext("2d");
let that = this;
this.video.addEventListener("play", function() {
that.vWidth = that.video.videoWidth / 2;
that.vHeight = that.video.videoHeight / 2;
that.fpsInterval = 1000 / 120;
that.then = Date.now();
that.startTime = that.then;
that.computeFrame();
}, false);
}
computeFrame() {
requestAnimationFrame(this.computeFrame);
this.now = Date.now();
this.elapsed = this.now - this.then;
if (this.elapsed > this.fpsInterval) {
this.ctx1.canvas.width = this.video.offsetWidth;
this.ctx1.canvas.height = this.video.offsetHeight;
if (this.video.offsetWidth > 0 && this.video.offsetHeight > 0) {
this.then = this.now - (this.elapsed % this.fpsInterval);
this.ctx1.drawImage(this.video, 0, 0, this.ctx1.canvas.width, this.ctx1.canvas.height);
let frame = this.ctx1.getImageData(0, 0, this.ctx1.canvas.width, this.ctx1.canvas.height);
let l = frame.data.length / 4;
let primaryColor = this.ctx1.getImageData(0, 0, 8, 8).data;
let primaryR = primaryColor[60];
let primaryG = primaryColor[61];
let primaryB = primaryColor[62];
for (let i = 0; i < l; i++) {
let r = frame.data[i * 4 + 0];
let g = frame.data[i * 4 + 1];
let b = frame.data[i * 4 + 2];
if (r == primaryR && g == primaryG && b == primaryB) {
frame.data[i * 4 + 1] = 255;
frame.data[i * 4 + 2] = 0;
frame.data[i * 4 + 3] = 0;
}
}
this.ctx1.putImageData(frame, 0, 0);
}
}
}
loadVideo();
You can use a MediaRecorder to record the video stream returned by your cavnas captureStream() method to record the first pass, and then read the resulting recorded video directly in a looping <video>:
btn.onclick = e => {
// initialise the <video>
const vid = document.createElement('video');
vid.muted = true;
vid.crossOrigin = true;
vid.src = "https://upload.wikimedia.org/wikipedia/commons/transcoded/a/a4/BBH_gravitational_lensing_of_gw150914.webm/BBH_gravitational_lensing_of_gw150914.webm.480p.webm";
vid.playbackRate = 2;
vid.onplaying = startProcessing;
vid.play();
btn.remove();
log.textContent = 'fetching';
};
function startProcessing(evt) {
// when video is playing
const vid = evt.target;
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = vid.videoWidth;
canvas.height = vid.videoHeight;
// show the canvas for first round
document.body.appendChild(canvas);
// force first frame
anim();
const chunks = []; // we'll store our recorder's data here
const canvasStream = canvas.captureStream();
const vp = 'video/webm; codecs="vp',
vp8 = vp + '8"',
vp9 = vp + '9"';
const recorder = new MediaRecorder(canvasStream, {
mimeType: MediaRecorder.isTypeSupported(vp9) ? vp9:vp8,
videoBitsPerSeconds: 5000000
});
// every time new data is available
recorder.ondataavailable = evt => chunks.push(evt.data);
// record until the video ends
vid.onended = evt => {
recorder.stop();
};
recorder.onstop = exportVid;
recorder.start();
log.textContent = "recording";
function anim() {
if(vid.paused) {
console.log('stop drawing');
return;
}
ctx.drawImage(vid, 0, 0);
applyFilter(ctx);
requestAnimationFrame(anim);
}
function exportVid() {
// concatenate all our chunks in a single Blob
const blob = new Blob(chunks);
const url = URL.createObjectURL(blob);
// we reuse the same <video> (for Safari autoplay)
vid.onplaying = vid.onended = null;
vid.src = url;
vid.loop = true;
vid.playbackRate = 1;
log.textContent = "playing";
vid.play().then(()=> canvas.replaceWith(vid));
}
}
function applyFilter(ctx) {
const img = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);
const data = new Uint32Array(img.data.buffer);
for(let i=0; i<data.length; i++) {
if(data[i] < 0xFF111111) data[i] = ((Math.random()*0xFFFFFF) + 0xFF000000 | 0);
}
ctx.putImageData(img, 0, 0);
}
canvas,video{
max-height: 100vh;
max-width: 100vw;
}
<button id="btn">start</button><pre id="log"></pre>

Categories

Resources