Frequency instead of phase

I still think my earlier suggestion is the best one worth not forgetting.

If you want the solution to be completely exact, you need somebody who is good in calculus. Otherwise you’re just stabbing randomly in the dark, and you will not accidentally find the solution that way.

Schottstaedt states “… there is no essential difference between frequency and phase modulation.”

Yes, because phase is the integral of frequency (and frequency is the derivative of phase). In classic FM, when you adding a sinusoid to a constant frequency, then the phase is the integral of these two terms.

  • a = integral of constant frequency = f * t – a line (ramp).
  • b = integral of sinusoid = phase-shifted sinusoid. (Integral of cosine = sine.)
  • FM’ed phase = a + b

And you can go the other way (PM synthesis). If you start with a linear ramp for phase, and add a sinusoid, the effect on frequency is:

  • a = derivative of linear ramp = constant frequency.
  • b = derivative of sinusoid = phase-shifted sinusoid.
  • Add those together, and it’s FM.

The catch is – and what often happens in FM – is that the modulator might have its own DC offset. (This happens easily, say, in a three-operator chain: A → B → C. A → B is likely to have DC offset.) Then the frequency is a (constant frequency) + b (modulator without DC offset) + c (modulator’s DC offset, constant value) = sum of two constant values (shifts the whole frequency!) + modulator b = not the frequency you started with.

In this case, you might think everything is keying off the same basic frequency, but you don’t get the nice clean tone color.

But PM, in the same situation, doesn’t have this effect, because the DC offset in the modulator only phase-shifts the wave in time, but does not skew frequency. This is why (I think) most FM synthesizers are really PM synthesizers.

@dietcv, your fmod does have a negative DC offset, skewing frequency downward. This might be the reason why it isn’t getting all the way back to 0 by the end of the grain. If I’m right, then your FM approach would have to control for DC offset. (If you’re not sure how to do that… again… take a mathematician out for a beer – or accept that PM handles DC offset better than FM and just use PM.)

hjh

thanks for your further explanations.

i think one solution then would be to stay with PM but exchange (1 - phase) ** 3 for a more general PM expression where pmod could either be an Oscillator or Playbuf.

(
{
	var tFreq = 100;
	var trig = Impulse.ar(tFreq);
	var grainFreq = 400;

	var phase = Sweep.ar(trig, grainFreq);
	
	//var pmod = ???
	//var sig = sin(phase * (1 + (pmod * index)) * 2pi);
	var sig = sin((1 - phase) ** 3 * 2pi);

	var grainWindow = phase < 1;

	sig = sig * grainWindow;

	sig;

}.plot(0.02);
)

Does somebody know how this would look like for this specific case?

Is this what you were looking for? Phase modulation with a waveform read from a buffer.

What I have not been able to figure out so far is how this would work if the carrier was also a BufRd, rather than sin( (phase * 2pi)). I’d be grateful for suggestions.

b=Buffer.loadCollection(s, Signal.sineFill(2048, [1,00], 0!2))

(
{
	arg phase_buf = 0, form = 1, mod_depth = 1;



	var tFreq = 100;
	var trig = Impulse.ar(tFreq);

	var phase = Sweep.ar(trig, tFreq*form.max(1));

	var phase_mod = BufRd.ar(1, phase_buf, phase*BufFrames.kr(phase_buf), 1, 2);

	var sig = sin( (phase * 2pi) + (phase_mod * mod_depth).mod(2pi) );
	
	var grainWindow = phase < 1;

	sig = sig * grainWindow;

	sig

}.plot(0.02)
)

thanks alot for your help :slight_smile:

in this thread are some hints for such an implementation, which i cannot make any sense of in my case.
The Sweep.ar rate cant be multiplied by BufFrames.kr(b) * bufRateScale.ir(b) because its phase is also used for the grain window (this is fixed) and phi is not a BufRd but a SinOsc.
Here is my extraction of the pm example from the thread:

(
{
	var carr = 100;
	var mod = 200;
	var index = 2;

	var bufFrames = BufFrames.ir(b);
	var phase = Sweep.ar(0, carr * bufFrames * BufRateScale.ir(b));
	var phi = ((1 - SinOsc.ar(mod, pi/2)) * index).mod(2pi);

	var sig = BufRd.ar(1, b, (phase + (phi * bufFrames * BufRateScale.ir(b) / 2pi)).mod(bufFrames));

	sig;
	
}.plot(0.02)
)

however none of these examples does give me the desired end result. the phase modulator isnt a plain sine wave.

(
{	
	var tFreq = 100;
	var trig = Impulse.ar(tFreq);
	var grainFreq = 400;
	
	var phase = Sweep.ar(trig, grainFreq);
	
	var grainWindow = phase < 1;
	
	var sig = sin((1 - phase) ** 3 * 2pi);
	
	sig = sig * grainWindow;

	sig;
}.plot(0.005);
)

Thanks @dietcv for pointing me to that other thread and for suggesting a way to adapt it.

This is still a little messy, but it works:

b=Buffer.loadCollection(s, Signal.sineFill(2048, {rrand(0.0,1.0)}!3, 0!3))

(
{
	var carr = 100;
	var mod = 10;
	var index = 0.2;

	var bufFrames = BufFrames.kr(b);
	var phase = Sweep.ar(0, carr * bufFrames * BufRateScale.kr(b));

	var phi = (( 1 - BufRd.ar(1, b, (mod* phase + (0.25 * bufFrames * BufRateScale.kr(b))), 1, 2)) * index ).mod(2pi);

	var sig = BufRd.ar(1, b, (phase + (phi * bufFrames * BufRateScale.kr(b) / 2pi)).mod(bufFrames));

	sig;

}.plot(0.02)
)

Sorry this is still not the solution you are looking for!

(post deleted by author - post will be hidden for 24 hours and then fully removed)

(post deleted by author - post will be hidden for 24 hours and then fully removed)

(post deleted by author - post will be hidden for 24 hours and then fully removed)

ok i think i got it!!!

this is an ordinary PM implementation, just with an added window and a flipped phase for better comparision:

// ordinary PM

(
{
	var freq = 400;
	var mod = SinOsc.ar(freq * \mRatio.kr(1));
	var sig = SinOsc.ar(freq * \cRatio.kr(1), (mod * \index.kr(5)).wrap(0, 4pi));
	sig.neg * (Sweep.ar(0, freq) < 1);
}.plot(0.005);
)

this BufRd pulsar implementation leads to the same result:

(
~freqBuf = Buffer.loadCollection(s, Signal.sineFill(2048, [1,0], 0!2));
~sndBuf = Buffer.loadCollection(s, Signal.sineFill(4096, [1,0], 0!2));
)

// pulsar BufRd PM with two sinusoids:

(
{
	var tFreq = \tFreq.kr(100);
	var trig = Impulse.ar(tFreq);
	var grainFreq = \freq.kr(400);
	var phase = Sweep.ar(trig, grainFreq);

	var phi = BufRd.ar(
		numChannels: 1,
		bufnum: ~freqBuf,
		phase: phase * BufFrames.kr(~freqBuf),
		loop: 0,
		interpolation: 4
	);

	var sig = BufRd.ar(
		numChannels: 1,
		bufnum: ~sndBuf,
		phase: 1 - (phase + ((phi * \index.kr(5)) / 2pi)) * BufFrames.kr(~sndBuf),
		loop: 1,
		interpolation: 4
	);

	sig * (phase < 1);

}.plot(0.005);
)

when instead using a welch window as the modulator and decreasing the modulation index from 5 to 2 you get the curved phase signal:

(
var welch = Env([0, 1, 0], [0.5, 0.5], \wel).discretize(4096);
~freqBuf = Buffer.sendCollection(s, welch, 1);
~sndBuf = Buffer.loadCollection(s, Signal.sineFill(4096, [1,0], 0!2));
)

// pulsar BufRd PM with welch window as a modulator

(
{
	var tFreq = \tFreq.kr(100);
	var trig = Impulse.ar(tFreq);
	var grainFreq = \freq.kr(400);
	var phase = Sweep.ar(trig, grainFreq);

	var phi = BufRd.ar(
		numChannels: 1,
		bufnum: ~freqBuf,
		phase: phase * BufFrames.kr(~freqBuf),
		loop: 0,
		interpolation: 4
	);

	var sig = BufRd.ar(
		numChannels: 1,
		bufnum: ~sndBuf,
		phase: 1 - (phase + ((phi * \index.kr(2)) / 2pi)) * BufFrames.kr(~sndBuf),
		loop: 1,
		interpolation: 4
	);

	sig * (phase < 1);

}.plot(0.005);
)

EDIT: i think the use of the welch window shows really nicely how frequency and phase are relatives of each other. Im wondering if i could use a ramp instead of a welch window when changing the interface.
thinking in terms of frequencies is a bit more straighforward then thinking of integrated frequency changes.
But i think the ordinary SinOsc as a modulator would not work anymore when changing the interface.

EDIT: using a curved ramp as the phase modulator and multiplying phi with the phase again does lead to something similiar, but then the SinOsc implementation doesnt work anymore, so ive introduced an index window:

(
var ramp = Env([1, 0], [1], [3.0]).discretize(4096);
~freqBuf = Buffer.sendCollection(s, ramp, 1);
~sndBuf = Buffer.loadCollection(s, Signal.sineFill(4096, [1,0], 0!2));
)

(
var statelessWindow = { |levels, times, curve, phase|
	var x = 0;
	var window = times.size.collect{ |i|
		var x2 = x + times[i];
		var result = (phase >= x) * (phase < x2) * phase.lincurve(x, x2, levels[i], levels[i+1], curve[i]);
		x = x2;
		result;
	}.sum;
	window * (phase < 1);
};

{
	var tFreq = \tFreq.kr(100);
	var trig = Impulse.ar(tFreq);
	var grainFreq = \freq.kr(400);
	var phase = Sweep.ar(trig, grainFreq);

	var phi = BufRd.ar(
		numChannels: 1,
		bufnum: ~freqBuf,
		phase: phase * BufFrames.kr(~freqBuf),
		loop: 0,
		interpolation: 4
	);

	var indexWindow = statelessWindow.(
		levels: [0, 1],
		times: [1],
		curve: [-8.0],
		phase: phase
	);
	var index = \index.kr(0) + indexWindow.linlin(0, 1, 0, \indexModAmount.kr(2));

	var sig = BufRd.ar(
		numChannels: 1,
		bufnum: ~sndBuf,
		phase: 1 - (phase + ((phi * index) / 2pi)) * BufFrames.kr(~sndBuf),
		loop: 1,
		interpolation: 4
	);

	sig * (phase < 1);

}.plot(0.005);
)

any suggestions?