2
votes

I am attempting to use a ChannelSplitter node to send an audio signal into both a ChannelMerger node and to the destination, and then trying to use the ChannelMerger node to merge two different audio signals (one from the split source, one from the microphone using getUserMedia) into a recorder using Recorder.js.

I keep getting the following error: "Uncaught SyntaxError: An invalid or illegal string was specified."

The error is at the following line of code:

audioSource.splitter.connect(merger);

Where audioSource is an instance of ThreeAudio.Source from the library ThreeAudio.js, splitter is a channel splitter I instantiated myself by modifying the prototype, and merger is my merger node. The code that precedes it is:

merger = context.createChannelMerger(2);
userInput.connect(merger);

Where userInput is the stream from the user's microphone. That one connects without throwing an error. Sound is getting from the audioSource to the destination (I can hear it), so it doesn't seem like the splitter is necessarily wrong - I just can't seem to connect it.

Does anyone have any insight?

2
It would be a lot easier if you had the entire code sample. I suspect something else is wrong.cwilso
Unfortunately I think it's too much code to post on a question! I also have that feeling, I'm writing a version without using the ThreeAudio library right now so I can be certain that I'm connecting everything in the right way. If you've seen a link to a proper example using both splitter and merger that would be helpful as well.Morgan Codes

2 Answers

6
votes

I was struggling to understand the ChannelSplitterNode and ChannelMergerNode API. Finally I find the missing part, the 2nd and 3rd optional parameters of the connect() method - input and output channel.

connect(destinationNode: AudioNode, output?: number, input?: number): AudioNode;

When using the connect() method with Splitter or Merger nodes, spacify the input/output channel. This is how you split and Merge to audio data.

You can see in this example how I load audio data, split it into 2 channels, and control the left/right output. Notice the 2nd and 3rd parameter of the connect() method:

const audioElement = new Audio("https://file-examples-com.github.io/uploads/2017/11/file_example_MP3_700KB.mp3");
    audioElement.crossOrigin = "anonymous"; // cross-origin - if file is stored on remote server

    const audioContext = new AudioContext();

    const audioSource = audioContext.createMediaElementSource(audioElement);

    const volumeNodeL = new GainNode(audioContext);
    const volumeNodeR = new GainNode(audioContext);

    volumeNodeL.gain.value = 2;
    volumeNodeR.gain.value = 2;

    const channelsCount = 2; // or read from: 'audioSource.channelCount'

    const splitterNode = new ChannelSplitterNode(audioContext, { numberOfOutputs: channelsCount });
    const mergerNode = new ChannelMergerNode(audioContext, { numberOfInputs: channelsCount });

    audioSource.connect(splitterNode);

    splitterNode.connect(volumeNodeL, 0); // connect OUTPUT channel 0
    splitterNode.connect(volumeNodeR, 1); // connect OUTPUT channel 1

    volumeNodeL.connect(mergerNode, 0, 0); // connect INPUT channel 0
    volumeNodeR.connect(mergerNode, 0, 1); // connect INPUT channel 1

    mergerNode.connect(audioContext.destination);

    let isPlaying;
    function playPause() {
      // check if context is in suspended state (autoplay policy)
      if (audioContext.state === 'suspended') {
        audioContext.resume();
      }

      isPlaying = !isPlaying;
      if (isPlaying) {
        audioElement.play();
      } else {
        audioElement.pause();
      }
    }

    function setBalance(val) {
      volumeNodeL.gain.value = 1 - val;
      volumeNodeR.gain.value = 1 + val;
    }
  <h3>Try using headphones</h3>

  <button onclick="playPause()">play/pause</button>
  <br><br>
  <button onclick="setBalance(-1)">Left</button>
  <button onclick="setBalance(0)">Center</button>
  <button onclick="setBalance(+1)">Right</button>

P.S: The audio track isn't a real stereo track, but a left and right copy of the same Mono playback. You can try this example with a real stereo playback for a real balance effect.

5
votes

Here's some working splitter/merger code that creates a ping-pong delay - that is, it sets up separate delays on the L and R channels of a stereo signal, and crosses over the feedback. This is from my input effects demo on webaudiodemos.appspot.com (code on github).

var merger = context.createChannelMerger(2);
var leftDelay = context.createDelayNode();
var rightDelay = context.createDelayNode();
var leftFeedback = audioContext.createGainNode();
var rightFeedback = audioContext.createGainNode();
var splitter = context.createChannelSplitter(2);

// Split the stereo signal.
splitter.connect( leftDelay, 0 );

// If the signal is dual copies of a mono signal, we don't want the right channel - 
// it will just sound like a mono delay.  If it was a real stereo signal, we do want
// it to just mirror the channels.
if (isTrueStereo)
    splitter.connect( rightDelay, 1 );

leftDelay.delayTime.value = delayTime;
rightDelay.delayTime.value = delayTime;

leftFeedback.gain.value = feedback;
rightFeedback.gain.value = feedback;

// Connect the routing - left bounces to right, right bounces to left.
leftDelay.connect(leftFeedback);
leftFeedback.connect(rightDelay);

rightDelay.connect(rightFeedback);
rightFeedback.connect(leftDelay);

// Re-merge the two delay channels into stereo L/R
leftFeedback.connect(merger, 0, 0);
rightFeedback.connect(merger, 0, 1);

// Now connect your input to "splitter", and connect "merger" to your output destination.