i would like to achieve in the same way discord app is doing, calibrate the audio input to be triggered at a certain volume (Decibel, ...) and not bellow that volume.
I'm creating a video call app and i want before the call allow the user to setup his devices correctly. Actually the sensitivity is too high and there is a lot of undesirable noise (You can even hear a bug fly).
I did a lot of unsuccessful research about how to achieve this with the Web Audio API.
At the beginning i tried to use the GainNode but it's just a way to amplify/attenuate (+/-) the sound like a volume.
After i tried using the BiquadFilterNode but it's just some filters to attenuate/amplify the audio around a certain frequency.
I think there is maybe a way using AudioWorklet but i don't find any clear documentation about how to solve my problem.
I would like in the same way i'm modifying the gain create a function setVolumeThreshold that regarding a certain db (or other kind of value) cut the sound bellow that.
var start = () => navigator.mediaDevices.getUserMedia({audio: true})
.then(stream => audio.srcObject = modifyGain(stream, 2.5))
.catch(e => log(e));
var modifyGain = (stream, gainValue) => {
var ctx = new AudioContext();
var src = ctx.createMediaStreamSource(stream);
var dst = ctx.createMediaStreamDestination();
var gainNode = ctx.createGain();
gainNode.gain.value = gainValue;
[src, gainNode, dst].reduce((a, b) => a && a.connect(b));
return dst.stream;
};
Here a jsFiddle using that code snippet.
Thanks a lot to those will try to help me !
I did a similar project quite a while ago using ScriptProcessorNode - https://github.com/cwilso/volume-meter/. It should be easily portable to AudioWorklet.
Also of interest is the "noise gate" effect in https://github.com/cwilso/Audio-Input-Effects, which I think is closer to what you want?
Related
I'm trying to add filter effects to an audio stream I have playing on my website. I'm able to connect the Tone.js library to the audio stream but I'm not hearing any changes in the audio stream playing on the website. I'm not seeing any errors in the console and I've tried adjusting the filter from 50 to 5000 but nothing seems to have any effect on the audio. Do I need to set up the new Tone.Player() to actually hear the audio? If so, how do you go about setting up the Player if there is no src for the existing audio element.
$('#testBtn').click(async function () {
const audioElement = document.getElementById('theAudioStream');
const mediaElementSource = Tone.context.createMediaElementSource(audioElement);
const filter = new Tone.Filter(50, 'lowpass').toDestination();
Tone.connect(mediaElementSource, filter);
await Tone.start();
console.log("Started?");
});
The audio stream I'm trying to modify is set up from a JsSip call. The code to start the stream is as follows:
var audioStream = document.getElementById('theAudioStream')
//further down in code
currentSession.answer(options);
if (currentSession.connection) {
currentSession.connection.ontrack = function (e) {
audioStream.srcObject = e.streams[0];
audioStream.play();
}
}
I click the test button after the call has started so I know the audio stream is present before initializing the Tone.js Filters
Working solution:
Removing the audioStream.play() from where the JsSIP call is answered solves the issue.
I don't know the exact reason why this solves the issue (it might even be a workaround) but after much trial and error this way allows the audio to be available to ToneJS for effecting.
Any other solutions are welcome.
Safari on iOS puts a scrubber on its lock screen for simple HTMLAudioElements. For example:
const a = new Audio();
a.src = 'https://example.com/audio.m4a'
a.play();
JSFiddle: https://jsfiddle.net/0seckLfd/
The lock screen will allow me to choose a position in the currently playing audio file.
How can I disable the ability for the user to scrub the file on the lock screen? The metadata showing is fine, and being able to pause/play is also acceptable, but I'm also fine with disabling it all if I need to.
DISABLE Player on lock screen completely
if you want to completely remove the lock screen player you could do something like
const a = new Audio();
document.querySelector('button').addEventListener('click', (e) => {
a.src = 'http://sprott.physics.wisc.edu/wop/sounds/Bicycle%20Race-Full.m4a'
a.play();
});
document.addEventListener('visibilitychange', () => {
if (document.hidden) a.src = undefined
})
https://jsfiddle.net/5s8c9eL0/3/
that is stoping the player when changing tab or locking screen
(code to be cleaned improved depending on your needs)
From my understanding you can't block/hide the scrubbing commands unless you can tag the audio as a live stream. That being said, you can use js to refuse scrubbing server-side. Reference the answer here. Although that answer speaks of video, it also works with audio.
The lock screen / control center scrubber can also be avoided by using Web Audio API.
This is an example of preloading a sound and playing it, with commentary and error handling:
try {
// <audio> element is simpler for sound effects,
// but in iOS/iPad it shows up in the Control Center, as if it's music you'd want to play/pause/etc.
// Also, on subsequent plays, it only plays part of the sound.
// And Web Audio API is better for playing sound effects anyway because it can play a sound overlapping with itself, without maintaining a pool of <audio> elements.
window.audioContext = window.audioContext || new AudioContext(); // Interoperate with other things using Web Audio API, assuming they use the same global & pattern.
const audio_buffer_promise =
fetch("audio/sound.wav")
.then(response => response.arrayBuffer())
.then(array_buffer => audioContext.decodeAudioData(array_buffer))
var play_sound = async function () {
audioContext.resume(); // in case it was not allowed to start until a user interaction
// Note that this should be before waiting for the audio buffer,
// so that it works the first time (it would no longer be "within a user gesture")
// This only works if play_sound is called during a user gesture (at least once), otherwise audioContext.resume(); needs to be called externally.
const audio_buffer = await audio_buffer_promise; // Promises can be awaited any number of times. This waits for the fetch the first time, and is instant the next time.
// Note that if the fetch failed, it will not retry. One could instead rely on HTTP caching and just fetch() each time, but that would be a little less efficient as it would need to decode the audio file each time, so the best option might be custom caching with request error handling.
const source = audioContext.createBufferSource();
source.buffer = audio_buffer;
source.connect(audioContext.destination);
source.start();
};
} catch (error) {
console.log("AudioContext not supported", error);
play_sound = function() {
// no-op
// console.log("SFX disabled because AudioContext setup failed.");
};
}
I did a search, in search of a way to help you, but I did not find an effective way to disable the commands, however, I found a way to customize them, it may help you, follow the apple tutorial link
I think what's left to do now is wait, see if ios 13 will bring some option that will do what you want.
I've been trying to think on some ideas on what I could make with JavaScript using Web Audio API. I know that depending on the user's browser I know that sometimes it won't let you play audio without a user gesture of some sort. I been doing some research on how to do it and they are pretty useful ways but the problem is that some developers found different ways to do it. For example:
Using a audioContext.resume() and audioContext.suspend() methods to unlock web audio by changing it's state:
function unlockAudioContext(context) {
if (context.state !== "suspended") return;
const b = document.body;
const events = ["touchstart", "touchend", "mousedown", "keydown"];
events.forEach(e => b.addEventListener(e, unlock, false));
function unlock() {context.resume().then(clean);}
function clean() {events.forEach(e => b.removeEventListener(e, unlock));}
}
creating an empty buffer and play it to unlock web audio.
var unlocked = false;
var context = new (window.AudioContext || window.webkitAudioContext)();
function init(e) {
if (unlocked) return;
// create empty buffer and play it
var buffer = context.createBuffer(1, 1, 22050);
var source = context.createBufferSource();
source.buffer = buffer;
source.connect(context.destination);
/*
Phonograph.js use this method to start it
source.start(context.currentTime);
paulbakaus.com suggest to use this method to start it
source.noteOn(0);
*/
source.start(context.currentTime) || source.noteOn(0);
setTimeout(function() {
if (!unlocked) {
if (source.playbackState === source.PLAYING_STATE || source.playbackState === source.FINISHED_STATE) {
unlocked = true;
window.removeEventListener("touchend", init, false);
}
}
}, 0);
}
window.addEventListener("touchend", init, false);
I know mostly how both of these methods work but
my question is what is going on here, what is the difference and which method is better etc?
And can someone please explain to me about this source.playbackState from an AudioBufferSourceNode Please? I never heard about that property on there before. It even doesn't have an article or get mentioned in the Mozilla MDN Website.
Also as a bonus question (which you don't have to answer), If both of these methods are useful then could it be possible to put them together as one if you know what I mean?
Sorry if that is a lot to ask. Thanks :)
resources:
https://paulbakaus.com/tutorials/html5/web-audio-on-ios/
https://github.com/Rich-Harris/phonograph/blob/master/src/init.ts
https://www.mattmontag.com/web/unlock-web-audio-in-safari-for-ios-and-macos
Both methods work, but I find the first (resume context in a user gesture) to be cleaner. The AudioBufferSource method is a kind of gross hack for backward compatibility with old sites that started playing buffers in a user gesture. This method doesn't work if you don't start the buffer from a gesture. (I think.)
Which one you want to use is up to you.
Safari on iOS puts a scrubber on its lock screen for simple HTMLAudioElements. For example:
const a = new Audio();
a.src = 'https://example.com/audio.m4a'
a.play();
JSFiddle: https://jsfiddle.net/0seckLfd/
The lock screen will allow me to choose a position in the currently playing audio file.
How can I disable the ability for the user to scrub the file on the lock screen? The metadata showing is fine, and being able to pause/play is also acceptable, but I'm also fine with disabling it all if I need to.
DISABLE Player on lock screen completely
if you want to completely remove the lock screen player you could do something like
const a = new Audio();
document.querySelector('button').addEventListener('click', (e) => {
a.src = 'http://sprott.physics.wisc.edu/wop/sounds/Bicycle%20Race-Full.m4a'
a.play();
});
document.addEventListener('visibilitychange', () => {
if (document.hidden) a.src = undefined
})
https://jsfiddle.net/5s8c9eL0/3/
that is stoping the player when changing tab or locking screen
(code to be cleaned improved depending on your needs)
From my understanding you can't block/hide the scrubbing commands unless you can tag the audio as a live stream. That being said, you can use js to refuse scrubbing server-side. Reference the answer here. Although that answer speaks of video, it also works with audio.
The lock screen / control center scrubber can also be avoided by using Web Audio API.
This is an example of preloading a sound and playing it, with commentary and error handling:
try {
// <audio> element is simpler for sound effects,
// but in iOS/iPad it shows up in the Control Center, as if it's music you'd want to play/pause/etc.
// Also, on subsequent plays, it only plays part of the sound.
// And Web Audio API is better for playing sound effects anyway because it can play a sound overlapping with itself, without maintaining a pool of <audio> elements.
window.audioContext = window.audioContext || new AudioContext(); // Interoperate with other things using Web Audio API, assuming they use the same global & pattern.
const audio_buffer_promise =
fetch("audio/sound.wav")
.then(response => response.arrayBuffer())
.then(array_buffer => audioContext.decodeAudioData(array_buffer))
var play_sound = async function () {
audioContext.resume(); // in case it was not allowed to start until a user interaction
// Note that this should be before waiting for the audio buffer,
// so that it works the first time (it would no longer be "within a user gesture")
// This only works if play_sound is called during a user gesture (at least once), otherwise audioContext.resume(); needs to be called externally.
const audio_buffer = await audio_buffer_promise; // Promises can be awaited any number of times. This waits for the fetch the first time, and is instant the next time.
// Note that if the fetch failed, it will not retry. One could instead rely on HTTP caching and just fetch() each time, but that would be a little less efficient as it would need to decode the audio file each time, so the best option might be custom caching with request error handling.
const source = audioContext.createBufferSource();
source.buffer = audio_buffer;
source.connect(audioContext.destination);
source.start();
};
} catch (error) {
console.log("AudioContext not supported", error);
play_sound = function() {
// no-op
// console.log("SFX disabled because AudioContext setup failed.");
};
}
I did a search, in search of a way to help you, but I did not find an effective way to disable the commands, however, I found a way to customize them, it may help you, follow the apple tutorial link
I think what's left to do now is wait, see if ios 13 will bring some option that will do what you want.
So what I want is to have constant looping interchanging from different audio sources. For demo purpose I made a little puzzle game - you align numbers in order from 0 to 8 and depending on how you align them different loops are playing. I managed to get the result I want on Chrome Browser, but not on Safari or Firefox. I tried adding a different audio destination or multiple audio contexts but no matter what loop just stops after one iteration in Safari and other browsers except for Chrome.
Here is a link to the demo on code-pen Demo Puzzle with music
please turn down your sound as music might be a little too loud, I didn't master it. And here is basic code I have for Web Audio Api manipulation.
Thanks
*Also it does not work for mobile at all.
const AudioContext = window.AudioContext || window.webkitAudioContext;
var audioContext = new AudioContext();
const audio1 = document.getElementById("aud1");
const audio2 = document.getElementById("aud2");
const audio3 = document.getElementById("aud3");
const audio4 = document.getElementById("aud4");
var chosenTrack = audio2;
let gameStarted = false;
function startGame() {
document.getElementById("sHold").style.display = "none";
document.getElementById("container").style.display = "block";
gameStarted = true;
audioContext.resume();
audioContext = new AudioContext();
audio1.pause();
audio1.play();
audio1.currentTime = 0;
}
setInterval(function() {
if (gameStarted) {
//console.log(audioContext.currentTime );
if (audioContext.currentTime >= 6.4) {
audioContext = new AudioContext();
chosenTrack.pause();
chosenTrack.play();
chosenTrack.currentTime = 0;
}
}
}, 5);
Some thoughts:
You're not really using Web Audio this way, you're still using audio elements as the source which doesn't help if you want to be able to achieve precise timing. You should load them into AudioBuffers and play them using an AudioBufferSourceNode.
If you absolutely want to use audio elements (because the files you use are really massive and you want to stream them) you probably want to use the loop property on it although i doubt if that ends up being precise and gapless.
Never use setInterval to get a callback every frame, use requestAnimationFrame
Don't use setInterval OR requestAnimationFrame to be able to achieve precise audio looping, the javascript thread is not precise enough to do that AND can be held up when other things take a bit more time, too many enemies in screen for example. You should be scheduling ahead of time now and then: https://www.html5rocks.com/en/tutorials/audio/scheduling/
AudioBufferSourceNodes have a loop boolean property which will loop them as precise as possible
Do realise that different audio-decoders (so: different browsers) MIGHT decode audiofiles slightly differently: some may have a few more ms on the start for example. This might become an issue when using multiple looping AudioBufferSourceNodes, which may all be running out of sync after an x amount of time. I always reschedule something on the exact time needed instead of using the loop property.