How can I see additive filter frequency with two LFOs?

please format code with </> button * homework policy * asking questions

I have two LFOs (which are oscillators) running at 2.9 and 3.9 for their frequencies, set to 1500, and 1600 for their amplitude, and I’m setting a filter’s amplitude with both LFOs. By hearing the results, I know that the filter’s amplitude can be set by each LFO independently, as I hear both LFOs. If I try to get the current frequency of the filter with filter.freq(), it only shows the last frequency that it was set to, instead of it’s current frequency affected by both LFOs.

I also had the idea of returning the current amplitudes of the LFOs with getAmp(), but again this only shows the amplitude they were set to, and not the current ampitude at that very moment.

Is there a way to show both combined amplitudes of the LFOs in the current moment?

   // set each LFO (oscillator) amplitude
    lfo.amp(1500);
    lfo2.amp(1600);

    // connect the LFOs
    filter.freq(lfo);
    filter.freq(lfo2);

Hi @exactspace,

I assume you are using p5.sound right?

Because filter.freq() only accepts a Number as the first argument.

Since the previous function is setting the frequency of the filter, then it’s normal that the frequency is going to be the last value used.

Maybe you can use p5.Amplitude to get the amplitude of all the LFOs?

Thanks @josephh ,

Yes I am using p5.sound. Actually I am able to get concurrent oscillators into the filter. Give it a try. Meaning, if I set filter.freq(number) to one value and then another, I hear both oscillations in the filter at the same time. It seems that filter.freq() only returns the last value it was assigned to, even though you are able to assign a multitude of frequencies; all that run at the same time.

Maybe you can use p5.Amplitude to get the amplitude of all the LFOs?

If I can, I’m not able to get any values from it if I assign the source to an oscillator.

It would be helpful if we could see the entire code that you are having trouble with (not a snippet).

1 Like

Hi svan, here is the code. Thanks for taking the time to investigate this. I can’t get the combined frequencies of both LFOs at a given moment. Click and hold the gray canvas to play a random frequency. Keep clicking to get one where you can hear both filters simultaneously. (It will be an audible pattern of uneven intervals due to the difference in LFO frequencies.)

let amplitude, fft, peakDetect, fundamental, toneMachines, playing, freq, amp;

let useFundamentalForFreq = true
let randFundamentalFreqMin = 40
let randFundamentalFreqMax = 140

function setup() 
{
	amplitude = new p5.Amplitude();

  let cnv = createCanvas(300, 100);
  cnv.mousePressed(playOscillator);
  
  //set lowest fundamental 
  fundamental = random(randFundamentalFreqMin, randFundamentalFreqMax);
  toneMachines = []
  toneMachines.push(newToneMachine(true))
	amplitude.setInput(toneMachines[0].lfo);

  fft = new p5.FFT();
  peakDetect = new p5.PeakDetect(20,20000,0.02,20); 
  
   // chain
	toneMachines.forEach(function(toneMachine) 
	{
		toneMachine.lfo.disconnect();
		toneMachine.lfo2.disconnect();
		toneMachine.osc.disconnect();
		toneMachine.osc.connect(toneMachine.filter);
	});

}

function draw() 
{
  background(220)
  
  if(useFundamentalForFreq)
  {
  	freq = fundamental
  }
  else
  {
  	freq = constrain(map(mouseX, 0, width, 40, 140), 40, 140);
  }

  amp = .2

  if (playing) 
  {
    smoothOscStart()
    
		// connect the lfo
		toneMachines.forEach(function(toneMachine) 
		{
			toneMachine.filter.freq(toneMachine.lfo);
			toneMachine.filter.freq(toneMachine.lfo2);
		});
		
		drawSpectrum()

		drawWaveform()
		
  }
  
	drawInfoText()
  
}

function playOscillator() 
{
	if(useFundamentalForFreq)
	{
		fundamental = random(randFundamentalFreqMin, randFundamentalFreqMax);
	}

	toneMachines.forEach(function(toneMachine) 
	{
		if(!toneMachine.muted)
		{
			toneMachine.osc.start();
			toneMachine.lfo.start();
			toneMachine.lfo2.start();
 		}
	});
	
  playing = true;
}

function mouseReleased() 
{
 	toneMachines.forEach(function(toneMachine) 
	{
		toneMachine.osc.amp(0, 0.2);
	});
  playing = false;
}

function smoothOscStart()
{
	var freqMult = 1

	toneMachines.forEach(function(toneMachine) 
	{
		let toneMachineID = toneMachine.id
		// needs to reset the res to default first
		let resDefault = toneMachine.resDefault 
		let lfoCurrentAmp = toneMachine.lfo.getAmp()
				
		toneMachine.osc.freq(freq, 0.1);
		toneMachine.osc.amp(amp, 0.1);
		toneMachine.filter.res(resDefault, 0.1);
		
	});
}

function drawSpectrum()
{
	//note: spectrum stretched out, because highest freqs not likely to be used.
	let spectrum = fft.analyze();
	
	noStroke();
	fill(255, 0, 255);
	for (let i = 0; i<spectrum.length; i++)
	{
		let x = map(i, 0, spectrum.length, 0, width*5);
		let h = -height + map(spectrum[i], 0, 255, height, 0);
		rect(x, height, width / spectrum.length, h )
	}
}

function drawWaveform()
{
	let waveform = fft.waveform();
	noFill();
	beginShape();
	stroke(255, 100, 0);
	for (let i = 0; i < waveform.length; i++)
	{
		let x = map(i, 0, waveform.length, 0, width);
		let y = map( waveform[i], -1, 1, 0, height);
		vertex(x,y);
	}
	endShape();
}

function drawInfoText()
{
	fill(0, 0, 0);
	noStroke();
  text('tap to play', 20, 10);
  text('freq: ' + freq, 20, 20);
  text('amp: ' + amp, 20, 40); 
  text('lfo1 freq: ' + toneMachines[0].lfo.getFreq(), 20, 60); 
  text('lfo1 amp: ' + toneMachines[0].lfo.getAmp(), 20, 80); 
  text('lfo2 amp: ' + toneMachines[0].lfo2.getAmp(), 20, 100); 
}

function newToneMachine(shouldPlay)
{
	let osc = new p5.Oscillator('sine')
	
	let lfo = new p5.Oscillator('sine');
	let lfo2 = new p5.Oscillator('sine');
	
	let resDefault = 14
	
  lfo.freq(2.9);
  lfo2.freq(3.9);
  lfo.amp(1200);
  lfo2.amp(1300);
  
  let filter = new p5.Filter();
	filter.freq(1200);
	filter.res(resDefault);
  
	let obj = 
	{
	osc:osc,
  	lfo:lfo,
  	lfo2:lfo2,
  	filter:filter,
  	resDefault:resDefault,
  	id:toneMachines.length,
  	muted:!shouldPlay,
	}
	
	return obj
}

That’s an impressive little demo and the first time I’ve ever seen code to simulate a human heart beat. It even has a built-in arrhythmia! I’m not sure what your intended goal is but from a sound standpoint it sounds pretty good. Thanks for posting the code.

I got the following error in a Chrome browser and I would correct it first, ie change the variable ‘freq’ to something else such as ‘myFreq’. I’m not a js expert but if you want to use the variable globally you might want to use ‘var myFreq’ instead of ‘let myFreq’.

I really don’t understand the problem and need clarification on what you expect the code to do. I got the following output.
result

I see a tall spike on the left hand side that goes almost to the top of the box and a superimposed broad based, intermittently pulsating, graph at its base. There is also a continuous sine wave going right down the horizontal center. I presume that the pulsating base graph is from the second generator.
scrn

Are you looking for a lfo2 frequency (I don’t see it listed)? Are you looking for another curve on the graph or for the graph to look another way? Maybe comment your code to show where the problem is occurring or more clearly state the problem.

Yes, I agree I should change the name of freq there. It’s just been the bottom of what to tackle and fix at this time. Thanks for the appreciation of the code.

There’s a few major issues. One LFO’s amplitude is set to 1200. The second LFO is 1300. These two LFOs are applied to the filter’s frequency. The LFOs also run at different rates; determined by their low frequencies. This does not initially affect the filter’s wave amplitude, but instead the frequency of the filter.

You can imagine a raised amplitude on a parametric EQ oscillating back and forth, but you have an additional oscillation modifying that movement at a different rate. It may be confusing, because it’s the LFO’s amplitude affecting the filter’s frequency. So at LFO 1’s highest peak, it is offsetting the filter by 1200 Hz.

Now imagine both of those LFOs running at the same time for one filter, and you want to make sure the filter’s frequency is never under 60 Hz or above 1600 Hz. With their combined amplitudes, there are plenty of times it would drop below 60 Hz or above 1600 Hz. Sure, I could make both LFO’s amplitudes smaller, but lets just say I want it to often hit the bounds and be constrained to 60 Hz and 1600 Hz. So far, I don’t know any way to determine the filter’s frequency at any given moment, so I cannot check if it’s within those bounds and make corrections if needed. Not anything that’s build into p5.sound.js, at least. With the code I have given you, you’ll actually hear an audible click from that frequency going to extreme values that I want to avoid. You may even be able to see it on the waveform at times - part of the wave is no longer a nice sine wave, and instead is instantly squared off on the left or right.

My other problem is that I don’t know how to determine the amplitude of the resulting audio an oscillator makes at any given time. The oscillator is connected to the filter. The filter is affected by two LFOs. What’s the final amplitude of that one sound wave at any given time?

I’m not sure I can help you with this problem; there may be others in this forum with a lot more knowledge on this subject.

If I had to solve it, my approach would be to boil the problem down to the simplest code that I could write and then start experimenting, using lots of println statements so I can figure out what is going on behind the curtain (or under the hood). I’m not sure where you got this code, whether it is original or perhaps from some other source, but somewhere there is likely one of the base languages behind all of it, likely java or javascript since those are the predominant languages used in this forum. Wherever the original code came from there may be the opportunity to get more control than you’re able to get with p5.js. I’m amazed how much functionality the oscillator code provides, but in your case it sounds like you need more control over the model. You may have to look at the p5.js source code and see if you can trace it’s origins and go back further if you can. Alternatively, there’s a forum member who has in depth knowledge of what you are trying to accomplish and will come forward. Wish I could be of more help; I think it’s very interesting work.

It’s seems pretty ordinary to me to check the current amplitude of a wave at any given time.

Hi @exactspace. I think I understand conceptually what you want to do. But let me try to summarize.
You have two LFOs and you want to add their amplitude together, then use the sum to control the cutoff or center frequency of a filter. But you also want to set the range of each LFO, so the bottom would be 100 and top 2000 (for example). So in generic psuedo-code assuming the output of an LFO would be its amplitude value:

lfo1.amp = 1900; //assuming this is the same as (lfo1 + 1) * (1900/2)
lfo2.amp = 900;
lfo1.freq = 1;
lfo2.freq = 2;
output = lfo1 + lfo2 + 100;
filter.freq = output;

Is this what you are trying to do?
Now if P5.sound gave you access to the audio loop (like the draw() loop but at the audio rate) you could do this easily, but it doesn’t. So you have to use the access functions the library gives you. The only way I could see to create an amplitude range was to use the Oscillator.scale() function.
reference | scale() (p5js.org)
But I’ll admit, I really don’t understand how the function is working exactly. The documentation, as you’ll see, is vague and not set to a standard I’m aware of. However, below is an example that seems to achieve the above situation, somewhat. I can’t empirically verify the range, but it sounds within the limits of an octave or two…
I agree that being able to get empirical values of an audio signal chain is very helpful when trying to debug code or understand what is actually happening. P5.sound is not really designed for that. You could put in a feature request with the developers, but unless you can access the audio loop at the sample rate (not the frame rate like what exists currently) there would be no point. Even if the Oscillator.getAmp() function returned the actual amplitude, and not the initialized amplitude, it would still be at the frame rate which would not be suitable to detect the range of an audio signal.
Here is an example I modified from the P5.Filter() reference:

let fft, noise, filter, osc, osc2, myAmp;

function setup() {
  let cnv = createCanvas(100,100);
  cnv.mousePressed(makeNoise);
  fill(255, 0, 255);
  osc = new p5.Oscillator('sine');
  osc2 = new p5.Oscillator('sine');
  filter = new p5.BandPass();
  noise = new p5.Noise();
  noise.disconnect();
  noise.connect(filter);
  osc.disconnect();
  osc2.disconnect();
  osc.start();
  osc2.start();
  fft = new p5.FFT();
  osc.freq(1);
  osc2.freq(1.5);
  myAmp = 2000;
  osc.amp(myAmp);
  osc2.amp(myAmp*.5);
  osc.scale(1000, 200, 0, 400);
  osc2.scale(1000, 200, 0, 800);

}

function draw() {
  background(220);

  let freq = map(mouseX, 20, width, 1, 10);
  let freq2 = map(mouseY, 20, height, 10, 1);
  freq = constrain(freq, 1, 10);
  osc.freq(freq);
  osc2.freq(freq2);
  filter.freq(osc);
  filter.freq(osc2);
  // give the filter a narrow band (lower res = wider bandpass)
  filter.res(50);

  // draw filtered spectrum
  let spectrum = fft.analyze();
  noStroke();
  for (let i = 0; i < spectrum.length; i++) {
    let x = map(i, 0, spectrum.length, 0, width);
    let h = -height + map(spectrum[i], 0, 255, height, 0);
    rect(x, height, width/spectrum.length, h);
  }
  if (!noise.started) {
    text('tap here and drag to change frequency', 10, 20, width - 20);
  } else {
    text('x, freq: ' + nfc(freq,2),10, 20, width - 20);
    text('y, freq2: ' + nfc(freq2,2),10, 33, width - 20);
    
  }
}

function makeNoise() {
  // see also: `userStartAudio()`
  noise.start();
  noise.amp(0.5, 0.2);
}

function mouseReleased() {
  noise.amp(0, 0.2);
}