Host SYNC (LFO/ADSR)

DSP, Plugin and Host development discussion.
Post Reply New Topic
RELATED
PRODUCTS

Post

I'm trying this for some time now. I got a free run LFO to work, but i have no idea how to get it to sync to the HOST tempo and exact sample position. I use JUCE, but that's not a problem or a solution. At any given moment i have access to all data that the host is passing (samplerate, sample position in bar, ppq and so on). But how do i calculate the frequency of the lfo, and how often to i call it's tick() method (the method that generates the next value of the lfo). Perhaps someone has a ready example class ? This is ofcourse VST subsystem.

Post

There are two steps to it.

A. Calculate samples per step. 120 BPM => 500 ms => 22050.0 samples for quarter notes.

B. Create a counter that you MOD by the bar position for every buffer, and wrap when it exceeds the sample count from A.

Then map the counter to a wave index or something.

Post

Here's a metronome class from a non-vst thing I did. It's a similar mechanism.

Code: Select all

#pragma once
#include "stereoBuffer.hpp"

class metronome
{
private:

	float samplesPerClick;
	int sampleCounter;

	int clicksPerBar;
	int clickCount;

	int sampleRate;
	float lfoS[2];
	float l_pitch[4];

public:

	metronome()
	{
		sampleCounter = 0;
		samplesPerClick = 0.0f;
		clicksPerBar = 4;
		
		clickCount = 0;
		
		lfoS[0] = 1.0f;
		lfoS[1] = 0.0f;
		
	}
	
	~metronome()
	{
	}
	
	void setClicksPerBar(int cpb)
	{
		clicksPerBar = cpb;
	}
	
	void setSamplesPerClick( ... )
	{
		samplesPerClick =
	}
	
	void setSampleRate(int s)
	{
		sampleRate = s;
		l_pitch[0] = 2.0f * (float)sin(M_PI * 1760.0f / sampleRate);
		l_pitch[1] = 2.0f * (float)sin(M_PI * 880.0f / sampleRate);
		l_pitch[2] = 2.0f * (float)sin(M_PI * 880.0f / sampleRate);
		l_pitch[3] = 2.0f * (float)sin(M_PI * 880.0f / sampleRate);
	}
	
	void doProcess(stereoBuffer* buffer, int numSamples, int samplesFromBarStart = -1)
	{
		float* ob1 = buffer->getDataPointer(0);
		float* ob2 = buffer->getDataPointer(1);

		for(int i = 0; i < numSamples; i++)
		{
			
			lfoS[0] = lfoS[0] - l_pitch[clickCount] * lfoS[1];
			lfoS[1] = lfoS[1] + l_pitch[clickCount] * lfoS[0];
			
			lfoS[0] *= 0.98f;
			
			ob1[i] += lfoS[0] * 0.7f;
			ob2[i] += lfoS[0] * 0.7f;
			
			sampleCounter++;
			if(sampleCounter > samplesPerClick) //Reset
			{
				clickCount++;
				clickCount %= clicksPerBar;
				sampleCounter = 0;
				lfoS[0] = 1.0f;
				lfoS[1] = 0.0f;
			}
		}
	
	}
	
};
Edited for clarity.

Post

atomix1040 wrote:I'm trying this for some time now. I got a free run LFO to work, but i have no idea how to get it to sync to the HOST tempo and exact sample position. I use JUCE, but that's not a problem or a solution. At any given moment i have access to all data that the host is passing (samplerate, sample position in bar, ppq and so on). But how do i calculate the frequency of the lfo, and how often to i call it's tick() method (the method that generates the next value of the lfo). Perhaps someone has a ready example class ? This is ofcourse VST subsystem.
First, get the tempo and convert it to frequency:

Code: Select all

frequency = tempo > 0.0 ? 60.0 / tempo : 0.0;
Second, we want to scale the frequency based on the synchronization settings, e.g. 1/4 triplet.

I use the following tables:

Code: Select all

enum NoteType
{
    Regular,
    Triplet,
    Dotted
};

enum NoteDivision
{
    Whole,
    Half,
    Quarter,
    Eighth,
    Sixteenth,
    Thirtysecond,
    Sixtyfourth,
};

const double NoteTypeScalers[] = { 1.0, 2.0 / 3.0, 1.5 };
const double NoteDivisionScalers[] = { 1.0 / 1.0 * 4.0,
                                       1.0 / 2.0 * 4.0,
                                       1.0 / 4.0 * 4.0,
                                       1.0 / 8.0 * 4.0,
                                       1.0 / 16.0 * 4.0,
                                       1.0 / 32.0 * 4.0,
                                       1.0 / 64.0 * 4.0 };
Then use the following formula:

Code: Select all

scaler = NoteTypeScalers[noteType] * 
         NoteDivisionScalers[noteDivision];

syncFrequency = frequency * scaler;
So we can now calculate our phase increment:

Code: Select all

phaseIncrement = syncFrequency / sampleRate;
And increment our LFO on a per-sample basis:

Code: Select all

// Sine output, could be some other waveform, e.g triangle.
output = sin(2 * pi * phaseAccumulator);

phaseAccumulator += phaseIncrement;

if(phaseAccumulator >= 1.0f)
{
    phaseAccumulator -= 1.0f;
}
We also want to synchronize the LFO's phase to the PPQN position:

Code: Select all

// Do this for every call to processReplacing.
p = ppqnPosition * scaler;
phaseAccumulator = p - Floor(p);
In theory, snapping the LFO's phase like this could cause a discontinuity. But in practise I've found this not to be a problem.

Post

@Leslie Sanford

well i got something running, but i'm stuck on types. witch variable is what type? is it all float ?

i have a running and working free-run LFO but there is a mix of unsigned int and float values, and the type casts are a key here.

Ofcourse all the values like tempo samplerate and so on are set and accurate at thhe moment when retrigger() is called. Also the inc parameter is the internal phase increment (it is uint32)

As for the ppqnPosition, i disabled this for testing (also type problems). I'm getting some results but i don't think it's all accurate.

Code: Select all

void LFO::retrigger()
{
	if (isSyncedToTempo)
	{
		frequency					= (float)(tempo > 0.0 ? 60.0 / tempo : 0.0);
		scaler						= noteTypeScalers[noteType] * noteDivisionScalers[noteDivision];
		syncFrequency				= (float)(frequency * scaler);
		inc							=  (uint32)((256.0f * syncFrequency / samplerate) * (float)(1<<24));

	}
	else
	{
		inc =  (uint32)((256.0f * rateInHz / samplerate) * (float)(1<<24));
	}
}

float LFO::tick()
{
	if (isSyncedToTempo)
	{
		const double p = ppqnPosition * scaler;
		inc = (uint32)(p - floor(p));
	}

	// the 8 MSB are the index in the table in the range 0-255 
	int i = phase >> 24; 
  
	// and the 24 LSB are the fractionnal part
	float frac = (phase & 0x00FFFFFF) * k1Div24lowerBits;

	// increment the phase for the next tick
	phase += inc; // the phase overflow itself

	return table[i]*(1.0f-frac) + table[i+1]*frac; // linear interpolation
}

When the LFO is free running the frequency is a large value, when it is in sync it is a small value (i guess your math assumes floating point math).

Could you clarify this for me please.

Post Reply

Return to “DSP and Plugin Development”