如何在使用网络音频和媒体录音机 api 播放时录制音符?

How do I record musical notes as they are played with the Web Audio and Media Recorder api's?

我正在尝试创建一个小应用程序,在使用网络音频 API 播放音频节点时记录声音,但 运行 遇到一些问题。

我有两个按钮('start recording' 和 'stop recording')一个媒体记录器和一个 keydown 事件监听器附加到 window。

我不想通过设备的扬声器录音,而是直接从 audioNodes

我想要发生的事情:

  1. 当我单击 'start recording' 按钮时,媒体播放器应该开始录制(确实如此)
  2. 当我按下“1”键时,函数 playNote 应该会播放声音。
  3. 当我点击 'stop recording' 按钮时,媒体播放器应该停止录制。
  4. 当我单击媒体播放器上的 'play' 按钮时,它应该会播放音符或已播放的音符。

到目前为止实际发生了什么:

场景一:

编码以便媒体播放器在单击 'start recording' 按钮时开始录制

  1. 我按下'start recording',媒体播放器开始录制。
  2. 我按下“1”键,playNote功能没有运行,我通过扬声器听不到任何声音。
  3. 我点击'stop recording',媒体播放器停止录制。
  4. 我单击媒体播放器上的播放按钮,媒体播放 无声 录音,持续时间为录制的持续时间会话。

结果:媒体录音机没有录下我的声音!

场景二:

编码以便媒体记录器在 playNote 函数 运行s:

时开始记录
  1. 我按下“1”键,便条开始播放,媒体录音机开始录音。
  2. 我单击 'stop recording' 按钮,媒体播放器停止录制。
  3. 我点击媒体播放器上的播放按钮,播放录音。

我对第二种情况的问题是,我想在整个录制期间通过连续击键播放多个 note/sound,因此,在我的 [=16] 中启动 startRecording 功能=] 函数无法实现这一点,因为一旦再次按下键“1”,就会开始新的声音和新的录音会话。

所以我真正想要发生的是:

场景三:

编码以便媒体记录器在单击 'start recording' 按钮时开始记录:

  1. 我单击 'start recording' 按钮,媒体播放器开始录制会话。
  2. 我按“1”键几次,弹奏和记录的音符
  3. 我单击 'stop recording' 按钮,媒体播放器停止录制。
  4. 我单击媒体播放器上的播放按钮,播放录音时显示所有音符

如果有人能帮我解决这个问题,我将不胜感激。目前我能想象得到的唯一方法是以某种方式为每个播放的声音创建一个 Blob,然后通过将它们推入一个数组来播放最终录音来将它们链接在一起。

我想知道的是,有没有一种方法可以将 audioNodes 的输出注入(需要另一个术语)到已经 运行ning 的媒体流中?它会让生活变得如此简单! :-)

我目前的代码如下。如果你注释掉这一行:

startRecording(); // If this isn't here, the note doesn't play when the media player has been told to start recording

您可以看到场景 1 的结果,如上所述。

感谢您抽出宝贵时间查看此内容。

// 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>

好的!经过几天的研究无果,脱发,我灵机一动!我偶尔有它们::oP

如果我们查看上面的代码,我们可以看到每次调用 startRecording 函数时我们都会创建一个新的 MediaRecorder


function startRecording() {
  recorder = new MediaRecorder(recordingStream.stream);
  recorder.start();
  started = true;
}

所以我想:

将记录器构造函数拉出到全局范围

在调用 stopRecording 时去掉 recorder = false,这样我们可以再次使用相同的 MediaRecorder 再次录制:

function stopRecording() {
  recorder.ondataavailable = function(e) {
    audioTag.src = URL.createObjectURL(e.data);

    //recorder = false;
    stopped = true;
  };
  recorder.stop();
}

然后,在我们的 playNote 函数中,添加一个条件语句以仅在 MediaRecorder 尚未录制时启动它:

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);
}

这个有效:-)

我还删除了 startBtn 及其事件侦听器,这样我们就不会不小心按下它并覆盖我们的录音。

为了好玩,在我们的 keyDownHandler

中添加了一条新注释
function keyDownHandler(e) {
  if (e.key === "1") {
    console.log(e.key, "pressed");
    playNote(440);
  }

  if (e.key === "2") {
    playNote(600);
  }
}

最终结果是我们现在可以重复播放音符,完成后单击 stopBtn 停止录音,然后单击媒体录音机上的播放按钮播放录音。

这是一个工作片段:

// 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>

所以最后,我们需要做的就是:

在全局范围内创建 MediaRecorder

仅当 MediaRecorder 尚未录制时才开始。