Reputation: 316
I'm trying to create a little app that records sounds from audio nodes as they are played using the Web Audio API, but have run into a few problems.
I have two buttons ('start recording' & 'stop recording') a media recorder and a keydown
event listener attached to the window.
I don't want to record through the device's speaker but directly from audioNodes
What I want to happen:
playNote
should play a sound.What's actually happening so far:
Scenario 1:
Coded so the media player starts to record when the 'start recording' button is clicked
playNote
function does not run, I can hear nothing through the speakers.Result: The media recorder didn't record my sound!
Scenario 2:
Coded so the media recorder starts recording when the playNote
function runs:
My problem with this second scenario is that I want to play more than one note/sound with successive keystrokes throughout the duration of the recording period so, starting the startRecording
function in my playNote
function does not achieve this, because as soon as key "1" is pressed again, a new sound and new recording session are started.
So what I actually want to happen is this:
Scenario 3:
Coded so the media recorder starts recording when the 'start recording' button is clicked:
If anyone can help me solve this problem, I would be very grateful. The only way I can imagine getting this to work at the moment is to somehow create a Blob
of each sound played and then chain them all together by pushing them into an array to play back the final recording.
What I'm wondering is, is there a way to inject (for want of another term) outputs from audioNodes
into an already running media stream? It would make life so much simpler! :-)
The code I have so far is below. If you comment out this line:
startRecording(); // If this isn't here, the note doesn't play when the media player has been told to start recording
You can see how Scenario 1 pans out, as described above.
Thanks for taking the time to look at this.
// Variables
let audioTag = document.getElementById("audioTag"),
started = false,
stopped,
startBtn = document.getElementById("startBtn"),
stopBtn = document.getElementById("stopBtn"),
actx,
recorder = false,
recordingStream = false,
mainVol;
// Define the global context, recording stream & gain nodes
actx = new AudioContext();
recordingStream = actx.createMediaStreamDestination();
mainVol = actx.createGain();
mainVol.gain.value = 0.1;
// Connect the main gain node to the recording stream and speakers
mainVol.connect(recordingStream);
mainVol.connect(actx.destination);
// Function to run when we want to start recording
function startRecording() {
recorder = new MediaRecorder(recordingStream.stream);
recorder.start();
started = true;
}
// Function to run when we want to terminate the recording
function stopRecording() {
recorder.ondataavailable = function(e) {
audioTag.src = URL.createObjectURL(e.data);
recorder = false;
stopped = true;
};
recorder.stop();
}
// Event listeners attached to the start and stop buttons
startBtn.addEventListener(
"click",
(e) => {
e.target.disabled = true;
console.log("start button clicked");
startRecording();
},
false
);
stopBtn.addEventListener("click", (e) => {
console.log("stop button clicked");
startBtn.disabled = false;
stopRecording();
});
// A function to play a note
function playNote(freq, decay = 1, type = "sine") {
let osc = actx.createOscillator();
osc.frequency.value = freq;
osc.connect(mainVol); // connect to stream destination via main gain node
startRecording(); // // If this isn't here, the note doesn't play when the media player has been told to start recording
console.log(mainVol);
osc.start(actx.currentTime);
osc.stop(actx.currentTime + 1);
}
// keydown evennt listener attached to the window
window.addEventListener("keydown", keyDownHandler, false);
// The keydown handler
function keyDownHandler(e) {
if (e.key === "1") {
console.log(e.key, "pressed");
playNote(440);
}
}
<p>
<button id="startBtn">Start Recording</button>
<button id="stopBtn">Stop Recording</button>
</p>
<audio id="audioTag" controls="true"></audio>
Upvotes: 0
Views: 231
Reputation: 316
OK! After a few days of unsuccessful research and hair loss, I had a thought! I have them occasionally ::oP
If we look at the code above we can see that we are creating a new MediaRecorder
every time the startRecording
function is called:
function startRecording() {
recorder = new MediaRecorder(recordingStream.stream);
recorder.start();
started = true;
}
So I thought:
pull the recorder constructor out into the global scope
get rid of the recorder = false
when stopRecording
is called, so we can record again using the same MediaRecorder
again if we wish:
function stopRecording() {
recorder.ondataavailable = function(e) {
audioTag.src = URL.createObjectURL(e.data);
//recorder = false;
stopped = true;
};
recorder.stop();
}
Then, in our playNote
function, add a conditional statement to only start the MediaRecorder
if it is not already recording:
function playNote(freq, decay = 1, type = "sine") {
let osc = actx.createOscillator();
osc.frequency.value = freq;
osc.connect(mainVol); // connect to stream destination via main gain node
// Only start the media recorder if it is not already recording
if (recorder.state !== "recording") {
startRecording();
} // If this isn't here, the note doesn't play
console.log(mainVol);
osc.start(actx.currentTime);
osc.stop(actx.currentTime + decay);
}
This works :-)
I also removed the startBtn
and its event listener so that we don't accidentally press it and overwrite our recording.
And just for fun, added a new note to our keyDownHandler
function keyDownHandler(e) {
if (e.key === "1") {
console.log(e.key, "pressed");
playNote(440);
}
if (e.key === "2") {
playNote(600);
}
}
The final result is that we can now play notes repeatedly, stop recording when we are finished by clicking the stopBtn
and then play back the recording by clicking the play button on the media recorder.
Here's a working snippet:
// Variables
let audioTag = document.getElementById("audioTag"),
started = false,
stopped,
// startBtn = document.getElementById("startBtn"),
stopBtn = document.getElementById("stopBtn"),
actx,
recorder = false,
streamDest = false,
mainVol;
// Define the global context, recording stream & gain nodes
actx = new AudioContext();
streamDest = actx.createMediaStreamDestination();
mainVol = actx.createGain();
mainVol.gain.value = 1;
// Create a new MediaRecorder and attached it to our stream
recorder = new MediaRecorder(streamDest.stream);
// Connect the main gain node to the recording stream and speakers
mainVol.connect(streamDest);
mainVol.connect(actx.destination);
// Function to run when we want to start recording
function startRecording() {
recorder.start();
started = true;
console.log(recorder.state);
}
// Function to run when we want to terminate the recording
function stopRecording() {
recorder.ondataavailable = function(e) {
audioTag.src = URL.createObjectURL(e.data);
// recorder = false;
stopped = true;
};
recorder.stop();
}
// Event listeners attached to the start and stop buttons
// startBtn.addEventListener(
// "click",
// (e) => {
// e.target.disabled = true;
// console.log("start button clicked");
// startRecording();
// },
// false
// );
stopBtn.addEventListener("click", (e) => {
console.log("stop button clicked");
// startBtn.disabled = false;
stopRecording();
});
// A function to play a note
function playNote(freq, decay = 1, type = "sine") {
let osc = actx.createOscillator();
osc.frequency.value = freq;
osc.connect(mainVol); // connect to stream destination via main gain node
// Only start the media recorder if it is not already recording
if (recorder.state !== "recording") {
startRecording();
}
osc.start(actx.currentTime);
osc.stop(actx.currentTime + decay);
}
// keydown evennt listener attached to the window
window.addEventListener("keydown", keyDownHandler, false);
// The keydown handler
function keyDownHandler(e) {
if (e.key === "1") {
console.log(e.key, "pressed");
playNote(440);
}
if (e.key === "2") {
playNote(600);
}
}
* {
margin: 0;
padding: 4px;
}
<h6>Keys #1 & #2 play sounds</h6>
<p>Recording starts when the first key is pressed</p>
<h6>Press the 'Stop Recording' button when finished</h6>
<h6>
Click the play button on the media recorder to play back the recording
</h6>
<p>
<!-- <button id="startBtn">Start Recording</button> -->
<button id="stopBtn">Stop Recording</button>
</p>
<audio id="audioTag" controls="true"></audio>
So in the end, all we needed to do was:
create the MediaRecorder
in the global scope
Start the MediaRecorder
only if it is not already recording.
Upvotes: 0