My goal is to be able to record the microphone audio stream as raw float PCM data so I can use it to fill an audio buffer and manipulate the sound programmatically and do visualizations.
I can create a array buffer containing the audio data then use audioCtx.decodeAudioData
and feed it to webAudio API and it plays back as it should, but I can't change the contents of the buffer so I'm confused at how that works.
I got it partly working by using a Analyser node in the getUserMedia promise.
It looks like this:
var audioCtx = new (window.AudioContext || window.webkitAudioContext)();
var audioBuffer = audioCtx.createBuffer(1, audioCtx.sampleRate * 3, audioCtx.sampleRate);
navigator.mediaDevices.getUserMedia({ audio: true })
.then(spectrum).catch(console.log);
function spectrum(stream) {
const audioCtx = new AudioContext();
const analyser = audioCtx.createAnalyser();
audioCtx.createMediaStreamSource(stream).connect(analyser);
setInterval(() => {
analyser.getFloatTimeDomainData(data);
if(isRecording){
for (let i = 0; i < data.length; i++) {
if(i < buffer.length)
buffer.push(data[i]);
else
break
}
}
}, 1000 / audioCtx.sampleRate);
};
Where the audio buffer is being filled up with the data from the analyser when isRecording is true.
Then I can manipulate and play the audio with something like:
function manipulateAndPlayAudioBuffer(){
let buf = buffer.getChannelData(0);
for (let i = 0; i < buf.length; i++) {
let t = (i / buf.length) * 3;
// Add a sine wave to the audio
let tone = Math.sin(3.1415*2*440*t);
buf[i] += tone * 0.4;
}
var source = audioCtx.createBufferSource();
source.buffer = buffer;
source.connect(audioCtx.destination);
source.start();
}
This almost works except the audio playback is poppy and sounds like data is missing.
There must be a correct solution to what I want to achieve but I have not found none.
AnalyserNode
is definitely not the way to go because it's not synchronized with the audio thread. Unless you're very, very luck, you'll get duplicated samples or gaps. And the way you're starting the source pretty much means there will be gaps becausestart()
means start now, but doesn't mean it will actually start "now". A jsfiddle or something as @Cole-Peterson mentions would be very, very helpful. – Raymond Toy