Introduction
Greetings, I have looked at some similar questions that are on this platform and none of them seem to match my problem or maybe I missed something but I will try my best to give it a try because I am new in this platform.
OverView
This audio app maps the keyboard keys to audio samples.
Problem
There is a audio delay when pressing the keys rapidly and after each press the sample sound keeps playing for 4 to 6 second before it calls another sound and doesn't kill the sound immediately after the keyboard button is released.
Code
This is what I tried:
const dump = console.log.bind(console);
const sample = new Object
({
drum: "/samples/drums/trance01/BD_Trance.wav",
clap: "/samples/drums/trance01/Clap_trance.wav",
});
const keymap = new Object
({
"KeyD": "drum",
"KeyC": "clap",
});
Object.keys(sample).forEach((smpl)=>
{
let node = document.createElement("audio");
node.id = (smpl+"Sample");
node.className = "instrument";
node.src = sample[smpl];
document.body.appendChild(node);
});
document.body.addEventListener("keydown", function keyListener(event)
{
event.preventDefault(); // kill it
event.stopPropagation(); // seal it's ashes in a capsule
event.stopImmediatePropagation(); // and hurl it into the sun!
let key = event.code;
// console.log("pressed: "+key);
let tgt = keymap[key];
if (!tgt){ dump(key+" - is unused"); return };
var intervalID = setInterval(myCallback, 500, 'Parameter 1', 'Parameter 2');
function myCallback(a, b)
{
let nde = document.getElementById(tgt+"Sample");
nde.play();
dump("play: "+tgt);
}
});
Following what happens in the logic and how elements respond in their own way (and time) is important for analysing what the issue may be.
In this case I believe this can be solved by cloning the source-node, not expecting it to play multiple instances of itself by itself (or that is what I believe should happen) - but we can force it to:
const dump = console.log.bind(console);
const sample = new Object
({
drum: "/samples/drums/trance01/BD_Trance.wav",
clap: "/samples/drums/trance01/Clap_trance.wav",
});
const keymap = new Object
({
"KeyD": "drum",
});
(Object.keys(sample)).forEach((item,indx)=>
{
let node = document.createElement("audio");
node.id = (item+"Sample");
node.className = "instrument";
node.src = sample[item];
document.body.appendChild(node);
});
function keyHandler(event)
{
if (event.ctrlKey){ return }; // whew .. that was annoying as f*ck
event.preventDefault(); // kill it
event.stopPropagation(); // seal it's ashes in a capsule
event.stopImmediatePropagation(); // and hurl it into the sun!
let key = event.code;
let tag = keymap[key];
let nde,tgt;
tgt = document.getElementById(tag+"Sample");
if (!tgt){ dump(key+" - is unused"); return };
nde = tgt.cloneNode(true);
nde.id = (tgt.id+"Clone");
nde.addEventListener("ended",function()
{
this.parentNode.removeChild(this);
});
document.body.appendChild(nde);
nde.play();
dump("playing: "+tgt);
}
document.body.addEventListener("keydown", keyHandler);
Related
I have a couple issues - the app is a vanilla javascript app for school that maps audio to each alphabetical key. The issues are as follows:
It takes quite a while to load the audio - the app proceeds like normal but with no audio - it usually can run as intended after a few minutes or a few refreshes, but I do want to get rid of that period. I tried to fix that using (document.readyState === "interactive") and if (allAudio.entries(audio => (audio.readyState === 4)))
It doesn't take the first key down event to change the intro slides, it takes the second - but I'm also not sure how to fix this. The slides are in an array that goes through each item and then makes them display as none. I also tried to add the event listener for the keyboard earlier, but to no avail.
To look at the bugs yourself, live link is here: https://haeuncreative.github.io/mosatic/
relevant code:
if (document.readyState === "interactive") {
const allAudio = document.querySelectorAll("audio")
console.log(allAudio)
if (allAudio.entries(audio => (audio.readyState === 4)))
window.addEventListener('load', function() {
const keysDown = new KeyDownHandler()
const canvas = document.querySelector('canvas');
const context = canvas.getContext('2d');
context.fillStyle = '#967bb6'
context.fillRect(0, 0, 1000, 562.5)
const clickDown = new ClickHandler(keysDown)
}
);
}
export default class KeyDownHandler {
constructor() {
this.addPressListener()
// audio
this.soundBank = new AudioBank
this.soundBank.createBank(CONSTANTS.KEY_ALPHABET)
// visual // intro
this.intro1 = document.querySelector('#intro1')
this.intro2a = document.querySelector('#intro2a')
this.intro2b = document.querySelector('#intro2b')
this.intro2c = document.querySelector('#intro2c')
this.intro3 = document.querySelector('#intro3')
this.intro4 = document.querySelector('#intro4')
this.introBank = [
this.intro1,
this.intro2a,
this.intro2b,
this.intro2c,
this.intro3,
this.intro4
]
// visual // main
this.body = document.querySelector('body')
this.canvas = document.querySelector('canvas');
this.context = this.canvas.getContext('2d');
this.background_colors = ["#95c88c", "#967bb6", "#A7C7E7", "#FF6961"]
this.aniBank = new AniBank;
// recording/user interaction
this.keys = [];
this.durations = [];
this.recording = false;
}
introSwitch() {
this.soundBank.playSpace()
if (this.currentSlide) {
this.currentSlide.style.animation = "fadeOut 1s"
this.currentSlide.style.display = "none"
this.currentSlide = ""
}
if (!this.introBank.length) {
slide.style.display = "none"
this.introFinish = true
}
if (this.introBank.length) {
let slide = (this.introBank.shift())
slide.style.filter = "brightness(60%)"
console.log(slide)
if (slide === this.intro4 || !slide) {
this.canvas.style.display = "flex";
this.addKeyListeners()
}
if (slide.style.display = "none") {
slide.style.display = "flex"
slide.style.filter = "brightness(60%)"
}
this.currentSlide = slide;
slide.style.filter = "brightness(100%)"
}
}
addPressListener() {
window.addEventListener('keypress', e => {
e.preventDefault()
e.stopImmediatePropagation()
this.introSwitch()
if (this.currentSlide === this.intro4) {
this.currentSlide.style.display = "none"
window.removeEventListener("keypress", this.introSwitch)
this.addKeyListeners()
}
})
}`
It takes quite a while to load the audio - the app proceeds like normal but with no audio - it usually can run as intended after a few minutes or a few refreshes, but I do want to get rid of that period. I tried to fix that using (document.readyState === "interactive") and if (allAudio.entries(audio => (audio.readyState === 4)))
It doesn't take the first key down event to change the intro slides, it takes the second - but I'm also not sure how to fix this. The slides are in an array that goes through each item and then makes them display as none. I also tried to add the event listener for the keyboard earlier, but to no avail.
I have a problem with validation size of HTML after some interactions on the page complete in the loop.
After first iteration do{}while(), I try to validate size of plain text on the page (outerHTML), but it don't changed and loop stops activity.
If i try to execute command >document.documentElement.outerHTML.length out from loop: in browser console - i see that change size happend, and it don't work inside loop, but why?
Can somebody explain to me, please, how to correct loop behavior?
var controlpagesize_int = 0;
do{
controlpagesize_int = document.documentElement.outerHTML.length
element_dom.click()
sleep_fc(2000)
}while (controlpagesize_int != document.documentElement.outerHTML.length)
PS: sleep_fc() - is a timeout function
Thank You all:)
Your code is just doing an infinite loop. The loop is not going to allow the DOM to update. That sleep code is doing nothing
To match what you think you are doing would need to be done with a interval or timeout
var controlpagesize_int = document.documentElement.outerHTML.length;
function checkDOM () {
if (controlpagesize_int === document.documentElement.outerHTML.length) {
window.setTimeout(checkDOM, 1000);
} else {
console.log('Page Has Changed');
}
}
checkDOM();
A better approach is probably to just use MutationObserver
const callback = function(mutationsList, observer) {
// looking at all the changes and find hello
const newNode = mutationsList.find(mutation => [...mutation.addedNodes].some(node => node.textContent &&
node.textContent === 'hello'));
if (newNode) {
console.log("updated with hello");
// if you found the change you are looking for, remove the listener
observer.disconnect();
}
};
const targetNode = document.body;
const config = {
// attributes: true,
childList: true,
subtree: true
};
const observer = new MutationObserver(callback);
observer.observe(targetNode, config);
window.setTimeout(function() {
const temp = document.createElement("div");
temp.innerHTML = "<p>hello</p>";
document.body.appendChild(temp);
}, 5000);
I am in the process of replacing RecordRTC with the built in MediaRecorder for recording audio in Chrome. The recorded audio is then played in the program with audio api. I am having trouble getting the audio.duration property to work. It says
If the video (audio) is streamed and has no predefined length, "Inf" (Infinity) is returned.
With RecordRTC, I had to use ffmpeg_asm.js to convert the audio from wav to ogg. My guess is somewhere in the process RecordRTC sets the predefined audio length. Is there any way to set the predefined length using MediaRecorder?
This is a chrome bug.
FF does expose the duration of the recorded media, and if you do set the currentTimeof the recorded media to more than its actual duration, then the property is available in chrome...
var recorder,
chunks = [],
ctx = new AudioContext(),
aud = document.getElementById('aud');
function exportAudio() {
var blob = new Blob(chunks);
aud.src = URL.createObjectURL(new Blob(chunks));
aud.onloadedmetadata = function() {
// it should already be available here
log.textContent = ' duration: ' + aud.duration;
// handle chrome's bug
if (aud.duration === Infinity) {
// set it to bigger than the actual duration
aud.currentTime = 1e101;
aud.ontimeupdate = function() {
this.ontimeupdate = () => {
return;
}
log.textContent += ' after workaround: ' + aud.duration;
aud.currentTime = 0;
}
}
}
}
function getData() {
var request = new XMLHttpRequest();
request.open('GET', 'https://upload.wikimedia.org/wikipedia/commons/4/4b/011229beowulf_grendel.ogg', true);
request.responseType = 'arraybuffer';
request.onload = decodeAudio;
request.send();
}
function decodeAudio(evt) {
var audioData = this.response;
ctx.decodeAudioData(audioData, startRecording);
}
function startRecording(buffer) {
var source = ctx.createBufferSource();
source.buffer = buffer;
var dest = ctx.createMediaStreamDestination();
source.connect(dest);
recorder = new MediaRecorder(dest.stream);
recorder.ondataavailable = saveChunks;
recorder.onstop = exportAudio;
source.start(0);
recorder.start();
log.innerHTML = 'recording...'
// record only 5 seconds
setTimeout(function() {
recorder.stop();
}, 5000);
}
function saveChunks(evt) {
if (evt.data.size > 0) {
chunks.push(evt.data);
}
}
// we need user-activation
document.getElementById('button').onclick = function(evt){
getData();
this.remove();
}
<button id="button">start</button>
<audio id="aud" controls></audio><span id="log"></span>
So the advice here would be to star the bug report so that chromium's team takes some time to fix it, even if this workaround can do the trick...
Thanks to #Kaiido for identifying bug and offering the working fix.
I prepared an npm package called get-blob-duration that you can install to get a nice Promise-wrapped function to do the dirty work.
Usage is as follows:
// Returns Promise<Number>
getBlobDuration(blob).then(function(duration) {
console.log(duration + ' seconds');
});
Or ECMAScript 6:
// yada yada async
const duration = await getBlobDuration(blob)
console.log(duration + ' seconds')
A bug in Chrome, detected in 2016, but still open today (March 2019), is the root cause behind this behavior. Under certain scenarios audioElement.duration will return Infinity.
Chrome Bug information here and here
The following code provides a workaround to avoid the bug.
Usage : Create your audioElement, and call this function a single time, providing a reference of your audioElement. When the returned promise resolves, the audioElement.duration property should contain the right value. ( It also fixes the same problem with videoElements )
/**
* calculateMediaDuration()
* Force media element duration calculation.
* Returns a promise, that resolves when duration is calculated
**/
function calculateMediaDuration(media){
return new Promise( (resolve,reject)=>{
media.onloadedmetadata = function(){
// set the mediaElement.currentTime to a high value beyond its real duration
media.currentTime = Number.MAX_SAFE_INTEGER;
// listen to time position change
media.ontimeupdate = function(){
media.ontimeupdate = function(){};
// setting player currentTime back to 0 can be buggy too, set it first to .1 sec
media.currentTime = 0.1;
media.currentTime = 0;
// media.duration should now have its correct value, return it...
resolve(media.duration);
}
}
});
}
// USAGE EXAMPLE :
calculateMediaDuration( yourAudioElement ).then( ()=>{
console.log( yourAudioElement.duration )
});
Thanks #colxi for the actual solution, I've added some validation steps (As the solution was working fine but had problems with long audio files).
It took me like 4 hours to get it to work with long audio files turns out validation was the fix
function fixInfinity(media) {
return new Promise((resolve, reject) => {
//Wait for media to load metadata
media.onloadedmetadata = () => {
//Changes the current time to update ontimeupdate
media.currentTime = Number.MAX_SAFE_INTEGER;
//Check if its infinite NaN or undefined
if (ifNull(media)) {
media.ontimeupdate = () => {
//If it is not null resolve the promise and send the duration
if (!ifNull(media)) {
//If it is not null resolve the promise and send the duration
resolve(media.duration);
}
//Check if its infinite NaN or undefined //The second ontime update is a fallback if the first one fails
media.ontimeupdate = () => {
if (!ifNull(media)) {
resolve(media.duration);
}
};
};
} else {
//If media duration was never infinity return it
resolve(media.duration);
}
};
});
}
//Check if null
function ifNull(media) {
if (media.duration === Infinity || media.duration === NaN || media.duration === undefined) {
return true;
} else {
return false;
}
}
//USAGE EXAMPLE
//Get audio player on html
const AudioPlayer = document.getElementById('audio');
const getInfinity = async () => {
//Await for promise
await fixInfinity(AudioPlayer).then(val => {
//Reset audio current time
AudioPlayer.currentTime = 0;
//Log duration
console.log(val)
})
}
I wrapped the webm-duration-fix package to solve the webm length problem, which can be used in nodejs and web browsers to support video files over 2GB with not too much memory usage.
Usage is as follows:
import fixWebmDuration from 'webm-duration-fix';
const mimeType = 'video/webm\;codecs=vp9';
const blobSlice: BlobPart[] = [];
mediaRecorder = new MediaRecorder(stream, {
mimeType
});
mediaRecorder.ondataavailable = (event: BlobEvent) => {
blobSlice.push(event.data);
}
mediaRecorder.onstop = async () => {
// fix blob, support fix webm file larger than 2GB
const fixBlob = await fixWebmDuration(new Blob([...blobSlice], { type: mimeType }));
// to write locally, it is recommended to use fs.createWriteStream to reduce memory usage
const fileWriteStream = fs.createWriteStream(inputPath);
const blobReadstream = fixBlob.stream();
const blobReader = blobReadstream.getReader();
while (true) {
let { done, value } = await blobReader.read();
if (done) {
console.log('write done.');
fileWriteStream.close();
break;
}
fileWriteStream.write(value);
value = null;
}
blobSlice = [];
};
//If you want to modify the video file completely, you can use this package "webmFixDuration", Other methods are applied at the display level only on the video tag With this method, the complete video file is modified
webmFixDuration github example
mediaRecorder.onstop = async () => {
const duration = Date.now() - startTime;
const buggyBlob = new Blob(mediaParts, { type: 'video/webm' });
const fixedBlob = await webmFixDuration(buggyBlob, duration);
displayResult(fixedBlob);
};
$(document).on('keydown', function(event) {
const current = audios[Number(event.key) // audios is array of music files
current.currentTime = 0;
current.play();
});
I'm creating a drum application. If I press the number 2 with the keydown event, it will be activated. And while I hold down the number 2 and press 3, the number 2 will stop sounding. How can I make it happen? And Why do not this?
It looks like there's a syntax error in your code. const current = audios[Number(event.key) is missing a closing ].
Here is how I would approach it.
const pressedKeys = {};
$(document.body).keydown(function (evt) {
pressedKeys[Number(evt.key)] = true;
playSongs(pressedKeys);
});
$(document.body).keyup(function (evt) {
pressedKeys[Number(evt.key)] = false;
});
function playSongs(dict) {
for (let song in dict) {
if (dict[song] === true) {
audios[song].currentTime = 0;
audios[song].play();
}
}
}
This code keeps track of keys in a dictionary. Whenever a keydown is registered it adds it to the dictionary. Right afterwards, playSongs finds all keys that are true and plays that song.
I issued strange error(SYNTAX_ERR: DOM Exception 12) on Chrome with Audio API. I tried Audio Api first time and did tutorial(few times) of Kyle Nau(http://www.youtube.com/watch?v=1wYTkZVQKzs). When I run code with simple mp3 playing all sounds plays fine, but when I try to add volume control block from same tutorial plays only last sound in list of new object creation. Two first shows "SYNTAX_ERR: DOM Exception 12" on play. I checked mp3s and changing position on declaration = same bad effect. Remove volume control and all plays fine again. In this tutorial all fine too.
Tests show that problem apper when uncomment this part:
playSound.connect(this.gainNode);
this.gainNode.connect(audioContext.destination);
I can't understand why this error is appers.
Here code. This is fine working variant(i marked problem place with comment):
function Sound(source, level) {
if (!window.audioContex) {
audioContext = new webkitAudioContext;
};
var that = this;
that.source = source;
that.buffer = null;
that.isLoaded = false;
// that.gainNode = audioContext.createGain();
// if (!level) {
// that.gainNode.gain.value = 1;
// } else {
// that.gainNode.gain.value = level;
// };
var getSound = new XMLHttpRequest();
getSound.open("GET",that.source,true);
getSound.responseType = "arraybuffer";
getSound.onload = function() {
audioContext.decodeAudioData(getSound.response,function(buffer) {
that.buffer = buffer;
that.isLoaded = true;
});
};
getSound.send();
};
Sound.prototype.play = function(){
if(this.isLoaded === true) {
var playSound = audioContext.createBufferSource();
playSound.buffer = this.buffer;
// playSound.connect(this.gainNode);
// this.gainNode.connect(audioContext.destination);
playSound.connect(audioContext.destination);
playSound.noteOn(0);
};
};
// Sound.prototype.setVolume = function(level) {
// this.gainNode.gain.value = level;
// };
var laserSound = new Sound("sound/laser.mp3");
var dropSound = new Sound("sound/drop.mp3");
var pickupSound = new Sound("sound/pickup.mp3");
// laserSound.setVolume(.1);
window.addEventListener("keydown", onKeyDown);
function onKeyDown(event) {
switch (event.keyCode) {
//Z
case 90:
laserSound.play();
break;
//X
case 88:
dropSound.play();
break;
//C
case 67:
pickupSound.play();
break;
};
};
When you create your gain node in the first line you've commented out, it must be audioContext.createGainNode(); rather than audioContext.createGain();
It looks like you're missing the Node.
I hope that helps.
You have a syntax error somewhere. You don't need to put semi colons after function declarations. You would only use a semi colon in this case:
var myFunction = function(){
};