Feed an Oscillator with FFT data

I have defined a FM-Synth and was thinking about using an audio buffer for the carrier frequency. My Idea is to modulate one audio file with the modulator, instead of another oscillator. I am very inexperienced with FFT, but thought using it to get the frequency information of the audio file, and to then use it to feed the carrier oscillator’s frequency might do what I want, but have no Idea where to start. Could some one give me some hints/examples of how to read the analysis data (frequency and time domain) of an FFT analysis? I would be very grateful! If you need more information, please ask!

To get the frequency of an audio file, you could use a pitch follower:

Hope that helps.
Best,
Paul

1 Like

I have created a basic FM synthesizer and tried to modulate the carrier with the frequencies read from an audio buffer with Pitch like this:

(
SynthDef(\klang, {
	arg freq, freqRatio, atk, modIdx=0, buf, relin=nil;
	var modFreq, hasFreq, car, mod, rel, env, idxEnv, amp=0.2;
	rel = BufDur.ir(buf) * 1.5;
	# modFreq, hasFreq = Pitch.kr(in: PlayBuf.ar(1, buf)).poll;
	env = EnvGen.kr(Env.new([0, amp, 0], [atk, rel]), doneAction: 2);
	idxEnv = EnvGen.kr(Env.new(
		[0, modIdx * modFreq, 0],
		[atk, rel]));
	mod = SinOsc.ar(
		modFreq,
		mul: idxEnv
	);
	car = SinOsc.ar(
		440 + mod,
		mul: env);
	Out.ar(0, car ! 2);
}
).add
)

and I play the synth this way:

(
Synth(\klang, [
	\buf, c,
	\freqRatio, 1,
	\modIdx, 10,
	\atk, 0.1
])
)

but the only thing I hear ist the 440 hz carrier frequency unmodulated. Can any one see what am I missing?

Right when you initialize the synth, the idxEnv looks at modFreq which at that point has a value of 0 and since the idxEnv is not re-triggered at any time, idxEnv puts out a value of 0 at all times, which means no modulation (try polling idxEnv). Also, you are not supplying a value for ‘rel’ - in general it is a good idea to specify default values for args in SynthDefs.

1 Like

Thank you! The release time rel is a variable, not an argument, it’s initialized to 1.5 times the buffer duration. I thought I can write it this way (modFreq) because the Pitch is sending out values all the time, so the modFreq will get updated! Why is this not working this way, and how should I get the freqs of Pitch into my modulator then?

Yes, you are right about rel, my bad.

I set the gate of the idxEnv generator to something like Impulse.kr(10), to trigger it, is that a reasonable thing to do?

I would probably just supply the modFreq directly in the car, possibly with a short lagtime and not use an envelope, but it depends on what exactly you are looking for:

// here using nameControl for lagtime, but use old style arg declaration if you prefer, so it is in line with the rest of the arg declaration.

car = SinOsc.ar(440 + modFreq.lag(\lagtime.kr(0.1)));
Out.ar(0, car ! 2);

Thanks for the code!
Only using the modFreq wouldn’t result in a FM-Synthesis, that’s my ultimate goal! :slight_smile:

Why wouldn’t it result in FM-synthesis? You are modulating one frequency with another frequency = FM-synthesis.

One frequency should be added to another oscillator, otherwise it would be a simple addition of two numbers, since modFreq is just a number. And is the modFreq.lag applied to a SimpleNumber in your code?

Well, modFreq is a rapidly changing number - in this sense it behaves like any signal although at a lower sampleRate since Pitch is only a control rate Ugen, so this is still FM. You could also simply plug the modFreq into the freq of a SinOsc and use this as the modulator and change + to *:

(
SynthDef(\klang, {
	arg freq, freqRatio, atk, modIdx=0, buf, relin=nil;
	var modFreq, hasFreq, car, mod, rel, env, amp=0.2;
	rel = BufDur.ir(buf) * 1.5;
	# modFreq, hasFreq = Pitch.kr(in: PlayBuf.ar(1, buf, loop:1));
	mod = SinOsc.ar(modFreq, mul: modIdx);
	car = SinOsc.ar(440 * mod);
	Out.ar(0, car ! 2);
}
).add
)

(
Synth(\klang, [
	\buf, c,
	\freqRatio, 1,
	\modIdx, 1,
	\atk, 0.8,
	\rel, 2
])
)
1 Like

The problem is you are using a modulating value in idxEnv. The way Env works is that when it reaches a target level, it essentially polls the value of the next target level and then goes to that value. This will be a static value, not modulating as you expect, so it is almost never a good idea to use modulating values within an Env. Instead, use static values in the Env, and then multiply the Env by the modulator. Take a look at these plots to see the difference:

(
{
    var mod, env1, env2;
    mod = SinOsc.ar(500).range(500, 800);
    env1 = Env([0, mod, mod, 0], [0.002, 0.006, 0.002]).ar; // bad
    env2 = Env([0, 1, 1, 0], [0.002, 0.006, 0.002]).ar * mod; // good
    [env1, env2];
}.plot;
)

So your SynthDef would look something like this:

(
SynthDef(\klang, {
	arg freq, freqRatio, atk, modIdx=0, buf, relin=nil;
	var modFreq, hasFreq, car, mod, rel, env, idxEnv, amp=0.2;
	rel = BufDur.ir(buf) * 1.5;
	# modFreq, hasFreq = Pitch.kr(in: PlayBuf.ar(1, buf)).poll;
	env = EnvGen.kr(Env.new([0, amp, 0], [atk, rel]), doneAction: 2);
	idxEnv = EnvGen.kr(Env.new(
		[0, 1, 0],
		[atk, rel]));
	mod = SinOsc.ar(
		modFreq,
		mul: idxEnv * modIdx * modFreq
	);
	car = SinOsc.ar(
		440 + mod,
		mul: env);
	Out.ar(0, car ! 2);
}
).add
)
1 Like