Reverse engineering VosimOsc

This oscillator, part of the ported plugins sounds really good.
How would you reverse engineering it ?
It says " two sinewaves multiplied by and sync’ed to a carrier "

Thanks :slight_smile:

Try:

I think I made this from looking at those two resources:

({
	//Vosim oscillator in SC
	var sig, mouse = MouseX.kr(1, 0.05);
	var freq = 100;
	var impulse = Impulse.ar(freq);
	var sweep = Sweep.ar(impulse, freq);
	var last = DC.ar(1).wrap(0,mouse).poll;

	//uncomment to limit to 3 bumps
	//sweep = Select.ar(sweep>(3*mouse), [sweep, DC.ar(0)]);
	sig = (sweep.wrap(0,mouse));
	sig = Select.ar(sweep>=(1.0-last), [sig/mouse,(sig).linlin(0,last,0,1)]);
	sig = (sig*pi).sin;
	sig = Select.ar(sweep>=(1.0-last), [sig, sig*last/mouse]);
	sig = sig*(1-(sweep*0.7));
	LPF.ar(sig**2, 10000).dup

}.scope
)

But I don’t remember. Maybe I got it from somewhere else.

Sam

Super that’s exactly what I was looking for, thanks a lot !

You’ve prompted me to dig out a version I’ve written. (A DC corrected version to follow)

// VOSIM(-like) Synchronos Granular Synthesis (SGS) (Classic VOSIM)
//
// GrainIn
//
// freq     --> f  : fundamental frequency
// formFreq --> ff : formant frequency
// k        --> k  : formant "quality"

SynthDef.new(\myVOSIM, {arg amp, freq = 440.0, formFreq = 1760.0, k = 0.707;
	var trigger;
	var grainDur;
	var grainFreq, envFreq;
	var numGrains;
	var phaseOffsets;
	var grainKs;
	var outSig;
	
	// must be hard set within the SynthDef
	numGrains = 3;
	
	// map / calculate SGS parameters
	grainFreq = freq;
	envFreq = formFreq;
	
	grainDur = envFreq.reciprocal;
	phaseOffsets = 1 - (Array.series(numGrains) * (freq / formFreq)); // multiple offsets for each pulse
	grainKs = Array.geom(numGrains, 1, k);                            // multiple gains for each pulse
	
	// granular (grain frequency) trigger
	// multi-channel expansion returns numGrains pulses!
	// this happens because phaseOffset is an Array of size = numGrains
	trigger = Impulse.ar(grainFreq, phaseOffsets);	

	// granular synthesis
	// multiple grains are generated here as trigger AND grainKs are arrays
	// we then need to sum the multiple grains (numGrains) together into a single audio
	// stream with the .sum method
	outSig = GrainIn.ar(numChannels: 1, trigger: trigger, dur: grainDur, in: DC.ar(1.0),
		mul: grainKs).sum;
	
	Out.ar(0,
		outSig.dup(2) * amp  // overall scaling here... could do so earlier
	)
)
});

And… a DC corrected version. (No need for HPF!)

// VOSIM(-like) Synchronos Granular Synthesis (SGS), DC corrected
//
// ... remove DC-bias
//
// GrainIn
//
// freq     --> f  : fundamental frequency
// formFreq --> ff : formant frequency
// k        --> k  : formant "quality"
SynthDef.new(\myVOSIMNoDC, {arg amp, freq = 440.0, formFreq = 1760.0, k = 0.707;
	var trigger;
	var grainDur;
	var grainFreq, envFreq;
	var numGrains;
	var phaseOffsets;
	var grainKs;
	var grainDcMul, grainDcAdd;
	var outSig;


	// must be hard set within the SynthDef
	numGrains = 3;

	// map / calculate SGS parameters
	grainFreq = freq;
	envFreq = formFreq;

	grainDcMul = (2 * formFreq) / (2 * formFreq - freq);      // DC correction parameters
	grainDcAdd = (-1 * freq) / (2 * formFreq - freq);


	grainDur = envFreq.reciprocal;
	phaseOffsets = 1 - (Array.series(numGrains) * (freq / formFreq)); // multiple offsets for each pulse
	grainKs = Array.geom(numGrains, 1, k);                            // multiple gains for each pulse

	// granular (grain frequency) trigger
	// multi-channel expansion returns numGrains pulses!
	// this happens because phaseOffset is an Array of size = numGrains
	trigger = Impulse.ar(grainFreq, phaseOffsets);	

	// granular synthesis
	// multiple grains are generated here as trigger AND grainKs are arrays
	// we then need to sum the multiple grains (numGrains) together into a single audio
	// stream with the .sum method
	outSig = GrainIn.ar(numChannels: 1, trigger: trigger, dur: grainDur, in: DC.ar(1.0),
		mul: grainDcMul * grainKs, add: grainDcAdd * grainKs).sum;

	Out.ar(0,
		outSig.dup(2) * amp  // overall scaling here... could do so earlier
	)
});
1 Like

I think a more general form for synchronous granular synthesis in the VOSIM style is pulsar synthesis with the difference that you can pick an arbitrary carrier and an arbitrary window function. The main point here is that the grain duration is binded to the grain frequency (in standard granular synthesis thats not the case), so it gets shorter for higher grain frequencies. You could either compensate for that with a multiplication of the window rate or multiply the pulsaret phase by a number of cycles param. But for really clean formant synthesis nothing beats the single sideband PM or modFM approach IMO which i have explained in detail here:

single sideband PM

(
var raisedCos = { |phase, index|
	var cosine = cos(phase * 2pi);
	exp(index.abs * (cosine - 1));
};

{
	var rate = 110;
	var modRatio = 2.5;
	var index = SinOsc.ar(0.3).linlin(-1, 1, 0, 30);

	var modPhase = Phasor.ar(DC.ar(0), rate * modRatio * SampleDur.ir);
	var mod = sin(modPhase * 2pi);
	var raisedCosWindow = raisedCos.(modPhase, index);

	var carrPhase = Phasor.ar(DC.ar(0), rate * SampleDur.ir);
	var carr = sin(carrPhase * 2pi + (mod * index));

	var sig = carr * raisedCosWindow;

	sig = LeakDC.ar(sig);

	sig!2 * 0.1;

}.play;
)

s.freqscope;

mod FM

(
var crossfade_formants = { |phase, harm|
	var harm_even = harm.round(2);
	var harm_odd = ((harm + 1).round(2) - 1);
	var sig_even = cos(phase * 2pi * harm_even);
	var sig_odd = cos(phase * 2pi * harm_odd);
	XFade2.ar(sig_even, sig_odd, harm.fold(0, 1) * 2 - 1);
};

var raisedCos = { |phase, index|
	var cosine = cos(phase * 2pi);
	exp(index.abs * (cosine - 1));
};

{
	var freq, phase, harmonic, raisedCosWindow, formants, sig;

	freq = \freq.kr(440);
	phase = Phasor.ar(DC.ar(0), freq * SampleDur.ir);

	harmonic = MouseX.kr(1, 10);

	raisedCosWindow = raisedCos.(phase, \index.kr(4));
	formants = crossfade_formants.(phase, harmonic);

	sig = formants * raisedCosWindow;

	sig = LeakDC.ar(sig);

	sig!2 * 0.1;

}.play;
)

s.freqscope;

Vector Phase Shaping

(
var vps = { |freq, width, harm|

	var harm_even = harm.round(2);
	var harm_odd = ((harm + 1).round(2) - 1);

	var phase = Phasor.ar(DC.ar(0), freq * SampleDur.ir, width.neg, 1 - width);

	var phasor_even = phase.bilin(0, width.neg, 1 - width, harm_even, 0, 1);
	var phasor_odd = phase.bilin(0, width.neg, 1 - width, harm_odd, 0, 1);

	var sig_even = cos(phasor_even + 0.5 * 2pi);
	var sig_odd = cos(phasor_odd + 0.5 * 2pi);

	var sig = XFade2.ar(sig_even, sig_odd, harm.fold(0, 1) * 2 - 1);
	LeakDC.ar(sig);
};

{
	var freq = 55;
	var width = MouseX.kr(0.01, 0.99);
	var harm = MouseY.kr(1.0, 10.0);
	var sig = vps.(freq, width, harm);
	sig !2 * 0.1;
}.play;
)

s.freqscope;