Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dividing one audio signal by another one

Short version

I need to divide an audio signal by another one (amplitude-wise). How could I accomplish this in the Web Audio API, without using ScriptProcessorNode? (with ScriptProcessorNode the task is trivial, but it is completely unusable for production due to the inherent performance issues)

Long version

Consider two audio sources, two OscillatorNodes for example, oscA and oscB:

var oscA = audioCtx.createOscillator();
var oscB = audioCtx.createOscillator();

Now, consider that these oscillators are LFOs, both with low (i.e. <20Hz) frequencies, and that their signals are used to control a single destination AudioParam, for example, the gain of a GainNode. Through various routing setups, we can define mathematical operations between these two signals.

Addition

If oscA and oscB are both directly connected to the destination AudioParam, their outputs are added together:

var dest = audioCtx.createGain();

oscA.connect(dest.gain);
oscB.connect(dest.gain);

Subtraction

If the output of oscB is first routed through another GainNode with a gain of -1, which is then connected to the destination AudioParam, then the output of oscB is effectively subtracted from that of oscA, because we are effectively doing an oscA + -oscB op. Using this trick we can subtract one signal from another one:

var dest = audioCtx.createGain();
var inverter = audioCtx.createGain();

oscA.connect(dest.gain);

oscB.connect(inverter);
inverter.gain = -1;
inverter.connect(dest.gain);

Multiplication

Similarly, if the output of oscA is connected to another GainNode, and the output of oscB is connected to the gain AudioParam of that GainNode, then oscB is multiplying the signal of oscA:

var dest = audioCtx.createGain();
var multiplier = audioCtx.createGain();

oscA.connect(multiplier);
oscB.connect(multiplier.gain);

multiplier.connect(dest.gain);

Division (?)

Now, I want the output of oscB to divide the output of oscA. How do I do this, without using ScriptProcessorNode?


Edit

My earlier, absolutely ridiculous attempts at solving this problem were:

  • Using a PannerNode to control the positionZ param, which did yield a result that decreased as signal B (oscB) increased, but it was completely off (e.g. it yielded 12/1 = 8.5 and 12/2 = 4.2) -- now this value can be compensated for by using a GainNode with its gain set to 12 / 8.48528099060058593750 (approximation), but it only supports values >=1
  • Using an AnalyserNode to rapidly sample the audio signal and then use that value (LOL)

Edit 2

The reason why the ScriptProcessorNode is essentially useless for applications more complex than a tech demo is that:

  1. it executes audio processing on the main thread (!), and heavy UI work will introduce audio glitches
  2. a single, dead simple ScriptProcessorNode will take 5% CPU power on a modern device, as it performs processing with JavaScript and requires data to be passed between the audio thread (or rendering thread) and the main thread (or UI thread)
like image 765
John Weisz Avatar asked Feb 09 '17 18:02

John Weisz


1 Answers

It should be noted, that ScriptProcessorNode is deprecated.

If you need A/B, therefore you need 1/B, inverted signal. You can use WaveShaperNode to make the inversion. This node needs an array of corresponding values. Inversion means that -1 becomes -1, -0.5 becomes -2 etc.

In addition, make sure that you are aware of division by zero. You have to handle it. In the following code I just take the next value after zero and double it.

function makeInverseCurve( ) {
  var n_samples = 44100,
      curve = new Float32Array(n_samples),
      x;
  for (var i = 0 ; i < n_samples; i++ ) {
    x = i * 2 / n_samples - 1;
    // if x = 0, let reverse value be twice the previous 
    curve[i] = (i * 2 == n_samples) ? n_samples : 1 / x;
  }
  return curve;
};

Working fiddle is here. If you remove .connect(distortion) out of the audio chain, you see a simple sine wave. Visualization code got from sonoport.

like image 137
shukshin.ivan Avatar answered Sep 23 '22 20:09

shukshin.ivan