Envelope implementation, help

DSP, Plugin and Host development discussion.
RELATED
PRODUCTS

Post

Hi Guys,

I have a university assignment that requires me to make a basic additive synth with ADSR envelope.

I have a good start and it works, that is until I add my envelope into it.

I have read lots, tried lots and been stuck lots and now I have to admit that I am stuck.

Presently the code compiles, it just doesn't make any sound. If I remove the line that is supposed to output the sample at the correct level for the stage in the envelope it works again. So I know it's something with the ADSR class.

I have attached the relevant stuff, and left my comments etc in, in case it helps you see what I was thinking.

if anyone can help at all I would be over the moon.

Thanks in advance

Ben


ADSR.h

Code: Select all

#ifndef __ADSR 
#define __ADSR

#include <math.h>

class ADSR
{
public:
	ADSR();
	~ADSR();

	//functions
	int setvalues();
	//int stage(int identify);
	float process(float* pCurrentLevel, int attack, int decay, float sustain, int release, int* stage, long* pTime, int* pKeyState);
	int internalTimer;
	/*float attack(float currentLevel);
	int decay(float currentLevel);
	int sustain(float currentLevel);//is this stage needed as only keeping level same while note is on??? * by 1???
	int release(float currentLevel);*/

};
#endif

ADSR.cpp

Code: Select all

#include "ADSR.h"

ADSR::ADSR()
{
	
}
ADSR::~ADSR()
{

}

float ADSR::process(float* pCurrentLevel, int attack, int decay, float sustain, int release, int* pstage, long* pTime, int* pKeyState)
{
	//float target = 1.f;
		
	while(*pTime < attack)
	{
		*pCurrentLevel = *pCurrentLevel + (1 / attack);
		//*pTime++;
		//*pstage = 2;
		//target = 0.5;//sustain level
		return *pCurrentLevel;
	}
		
	while(*pTime < (attack + decay))
	{
		*pCurrentLevel = *pCurrentLevel - (sustain / decay);
		//*pTime;
		//*pstage = 3;
		//target = 0;
		return *pCurrentLevel;
	}

	while(*pTime > (attack + decay))
	{
		*pCurrentLevel = *pCurrentLevel * 1;
		internalTimer++;
		return *pCurrentLevel;
	}
	
	while(*pTime < (attack + decay + internalTimer + release) && *pKeyState == 2)
	{
		*pCurrentLevel = *pCurrentLevel - (sustain / release);
		return *pCurrentLevel;

	}

}

VST_Plugin.h

Code: Select all

//-------------------------------------------------------------------------------------------------------
// This is the most basic plug in code there is!
// All this plug in will do is apply some gain to the input
// This is done in process replacing
//-------------------------------------------------------------------------------------------------------

#ifndef __VST_Plug_in__
#define __VST_Plug_in__

#include "audioeffectx.h"

#include <math.h>
#include "ADSR.h"



const int NUMBER_OF_INPUTS = 0;
const int NUMBER_OF_OUTPUTS = 2;
const int NUMBER_OF_PROGRAMS = 0;
const int NUMBER_OF_PARAMETERS = 5;


// Base frequency (A4- 440Hz) for use in generating frequency table
const float BASE_A4  = 440.0;
const float PI =  3.141592;


//-------------------------------------------------------------------------------------------------------
class VST_Plug_in : public AudioEffectX
{
public:
	VST_Plug_in (audioMasterCallback audioMaster);
	~VST_Plug_in ();
	virtual void processReplacing (float** inputs, float** outputs, VstInt32 sampleFrames);

	// synth functions
	virtual VstInt32 processEvents (VstEvents* events);
	virtual VstInt32 canDo (char* text);

	float leftSample;
	float* pLeftSample;
	float rightSample;
	float frequency;
	float sampleRate;
	long time;
	long* pTime;

	// MIDI stuff
	// MIDI data : holds data about current state of MIDI (note on/off, frequency, velocity)
	int keyDown; // true : key is down, false : no key down
	int* pkeyDown;

	long currentNote; // the MIDI note number of the last note on (key down)
	float currentVelocity; // current MIDI note velocity (0 -> 1)
	float *m_pfFrequencyTable; // will store a list of frequency values (for note->frequency conversion)
	
	void noteOff ();
	void noteOn (long liNote, long liVelocity);
	int getAttack();
	int getDecay();
	float getSustain();
	int getRelease();

	float partial1;
	float partial2;
	float partial3;
	float partial4;
	float partial5;
	float partial6;
	float partial7;
	float partial8;

	int adsrStage;
	int* padsrStage;

	ADSR env;
	
};

#endif

VST_Plugin.cpp

Code: Select all

//-------------------------------------------------------------------------------------------------------
// VST Plug-Ins SDK
// Version 2.4		$Date: 2005/11/15 15:14:03 $
// 
// Category     : VST 2.x SDK Samples
// Filename     : VST_Plug_in.cpp
// Created by   : Steinberg Media Technologies
// Description  : Stereo plugin which applies Gain [-oo, 0dB]
//
// © 2005, Steinberg Media Technologies, All Rights Reserved
//-------------------------------------------------------------------------------------------------------

#include "VST_Plug_in.h"

//-------------------------------------------------------------------------------------------------------
AudioEffect* createEffectInstance (audioMasterCallback audioMaster)
{
	return new VST_Plug_in (audioMaster);
}

//-------------------------------------------------------------------------------------------------------
VST_Plug_in::VST_Plug_in (audioMasterCallback audioMaster)
: AudioEffectX (audioMaster, NUMBER_OF_PROGRAMS, NUMBER_OF_PARAMETERS)	
{
	setNumInputs (NUMBER_OF_INPUTS);		// stereo in
	setNumOutputs (NUMBER_OF_OUTPUTS);		// stereo out
	setUniqueID ('Add1');	// identify
	canProcessReplacing ();	// supports replacing output
	leftSample  = 0.0;
	rightSample = 0.0;
	frequency = 0.0;

	currentVelocity = 0.0;
	currentNote = 0;
	keyDown = 2;
	pkeyDown = &keyDown;
	adsrStage = 4;
	padsrStage = &adsrStage;

	isSynth ();						// Informs host that this is a VSTi
	
	// initialise frequency table
	m_pfFrequencyTable = new float [128] ; // 128 Midi notes

	if (m_pfFrequencyTable)
	{
		for (int i = 0; i<  128; i++)
		{
			m_pfFrequencyTable[i] = BASE_A4 *powf(2.f,(i-57)/12.f) ;
		}
	}
	time = 0;

	partial1 = 0.f;
	partial2 = 0.f;
	partial3 = 0.f;
	partial4 = 0.f;
	partial5 = 0.f;
	partial6 = 0.f;
	partial7 = 0.f;
	partial8 = 0.f;

	//get sample rate from host
	sampleRate = getSampleRate();

	ADSR env;
		
}

//-------------------------------------------------------------------------------------------------------
VST_Plug_in::~VST_Plug_in ()
{
	// nothing to do here
}


//-----------------------------------------------------------------------------------------
// this is where the intresting stuff happens :0

void VST_Plug_in::processReplacing (float** inputs, float** outputs, VstInt32 sampleFrames)
{
    float* out1 = outputs[0];
    float* out2 = outputs[1];
	//ADSR env;

	for(int i = 0; i < sampleFrames; i++)
    {
		// NEW : only send out audio if there is a note on currently
		frequency = m_pfFrequencyTable[currentNote];
		
		partial1 = (float)sin(2.0*PI*time++*(frequency/sampleRate))* 0.125;
		partial2 = (float)sin(2.0*PI*time++*((2*frequency)/sampleRate))* 0.125;
		partial3 = (float)sin(2.0*PI*time++*((3*frequency)/sampleRate))* 0.125;
		partial4 = (float)sin(2.0*PI*time++*((4*frequency)/sampleRate))* 0.125;
		partial5 = (float)sin(2.0*PI*time++*((5*frequency)/sampleRate))* 0.125;
		partial6 = (float)sin(2.0*PI*time++*((6*frequency)/sampleRate))* 0.125;
		partial7 = (float)sin(2.0*PI*time++*((7*frequency)/sampleRate))* 0.125;
		partial8 = (float)sin(2.0*PI*time++*((8*frequency)/sampleRate))* 0.125;
		
		leftSample = partial1 + partial2 + partial3 + partial4 + partial5 + partial6 + partial7 + partial8;

		leftSample = (env.process(pLeftSample, getAttack(), getDecay(), getSustain(), getRelease(), padsrStage, pTime, pkeyDown)) * currentVelocity; 
		
		//leftSample = leftSample * currentVelocity;
		rightSample = leftSample;

		// write samples to output buffer 
        (*out1++) = leftSample;
        (*out2++) = rightSample;
    }
}


// NEW : overriden function, tells host what the plugin can do (see notes)

VstInt32 VST_Plug_in::canDo(char *text)
{
	if (!strcmp (text, "receiveVstEvents"))		// SimpleSynth can receive VST events
		return 1;
	if (!strcmp (text, "receiveVstMidiEvent"))	// SimpleSynth can receive VST MIDI events
		return 1;
	return -1;	// explicitly can't do; 0 => don't know	
}

// NEW : this process function is called to collect incoming VST events

VstInt32 VST_Plug_in::processEvents (VstEvents* events)
{
	// parse event list
	for (long i = 0; i < events->numEvents; i++)
	{
		if ((events->events[i])->type == kVstMidiType)
		{
			VstMidiEvent* event = (VstMidiEvent*)events->events[i];
			char* midiData = event->midiData;
			long status = midiData[0] & 0xf0;		// ignoring channel

			if (status == 0x90 || status == 0x80)	// we only look at notes
			{
				long note = midiData[1];
				long velocity = midiData[2];

				if (status == 0x80)
				{
					velocity = 0;	
					// set velocity to zero if it is a note off message
				}
				if (!velocity && (note == currentNote))
				{
					noteOff ();
				}
				else
				{
					noteOn (note, velocity);
				}
			}
		}
	}
	return 1; // indicate that we wish to receive more events
}

void VST_Plug_in::noteOn(long liNote, long liVelocity)
{
	keyDown = 1;
	time = 0;
	adsrStage = 1;
	currentNote = liNote;
	
	// velocity is liVelocity /127
	currentVelocity = liVelocity / 127.f;
	
}

void VST_Plug_in::noteOff()
{
	keyDown = 2;
	adsrStage = 4;
	currentVelocity = 0;
}

int VST_Plug_in::getAttack()
{
	int attack = 44100;
	//capture attack time in samples from slider
	return attack;
}

int VST_Plug_in::getDecay()
{
	int decay = 22050;
	//decay from slider
	return decay;
}

float VST_Plug_in::getSustain()
{
	float sustain = 0.75;
	//sustain from slider;
	return sustain;
}

int VST_Plug_in::getRelease()
{
	int release = 88200;
	//release from slider
	return release;
}

Post

In your processReplacing function, you've assigned the sum of the partials to "leftSample", then immediately overwrite that with the envelope process. Is the "pLeftSample" pointer argument to env.process supposed to be "leftSample" or at least point to it? It's unassigned as far as I can see. "pTime" hasn't been assigned to anything either, nor is it being incremented (commented out) so the envelope isn't moving anywhere.

And you should delete the allocation of m_pfFrequencyTable in your destructor. :)

Post

Thank you for your help, You are correct, it was supposed to send leftSample (or a pointer to it). The idea was that the partials are summed, and the envelope sets a level according to the current stage in the envelope. It just seems to be overwriting that with nothing.

Time is supposed to be incremented with the sine wave generation and had thought that would progress the envelope using the pointers. Is that wrong? I have since noticed that each partial was set to increment time, which essentially should have just sped up the envelope. I removed time incrimination from all but one partial, and still the same issue.

Code: Select all

partial1 = (float)sin(2.0*PI*time++*(frequency/sampleRate))* 0.125;
I then tried uncommenting the time increment lines in the adsr.cpp and still the same.

Obviously my thinking is wrong somewhere I just cannot see what is iffy. :cry:

In case you hadn't guessed I am far from an advanced programmer and are only part way through a year 2 module, and currently surrounded by 2 poorly kids, so you help is very much appreciated.

Cheers

Ben

Post

The 'time' element in calculating your partials should not be the same time element used in tracking where you are in the envelope/signal. It should just be an index, always incrementing from 0 to 7 (since you have 7 partials and one fundamental).

There's still an issue with your envelope process function. If the first argument is supposed to be the sample value, you just need to multiply that by the current envelope value -- don't add or subtract anything to it, that's mixing. Applying an envelope is just multiplication:

Code: Select all

sample = sample * envelopeValue;
Also, I would advise never to go straight into coding the plug-in. Especially if you're inexperienced as a programmer, always start off with a basic command-line program. They're much easier to debug, and once you have it working you can place the code within the plug-in framework.

Post

Dude thank you very much.

I have now had my envelope functioning perfectly.

Can see easily where I was going wrong now. duh * not + or -

Unfortunately the faders are giving me a new headache. I tweaked the same code as a different plugin I wrote for a previous assignment, just this time the faders will not update...? The envelope was working until I added user adjustment of the values of ADSR.

unsure now wether this is a continuing issue or requires a new post??

Once again thank you for your help.

Ben

Post

I think the envelope works but am not sure as I broke it making it pull its values based on the fader/slider position, and they just don't seem to want to play ball.

I am sure it is something to do with my value/range scaling as the sustain slider appears to work.

I have spent probably too long re-checking my code and just cannot what is awry.

Not sure exactly what bits of code will be useful now, so I have just attached the lot, updated.

Thanks again

Ben.

ADSR.h

Code: Select all

   
    #ifndef __ADSR 
    #define __ADSR
    
    #include <math.h>
    
    class ADSR
    {
    public:
    	ADSR();
    	~ADSR();
    
    	int time, internalTimer, stage;
    	float process(float CurrentLevel, int attack, int decay, float sustain, int release, int* pKeyState); 
    };
    #endif
ADSR.cpp

Code: Select all

    #include "ADSR.h"
    
    ADSR::ADSR()
    {
    	time = 0;
    	stage = 4;
    	internalTimer = 0;
    }
    ADSR::~ADSR()
    {
    
    }
    
    float ADSR::process(float CurrentLevel, int attack, int decay, float sustain, int release, int* pKeyState)
    {
    	if(stage = 1 && time >= (attack + decay + internalTimer + release))
    	{
    		time = 0;
    	}
    	while(time < attack)
    	{
    		CurrentLevel = CurrentLevel * ((1 / attack) + 1);
    		time++;
    		return CurrentLevel;
    	}
    		
    	while(time < (attack + decay))
    	{
    		CurrentLevel = CurrentLevel * (sustain / decay);
    		time++;
    		return CurrentLevel;
    	}
    
    	while(time > (attack + decay))
    	{
    		CurrentLevel = CurrentLevel * 1;
    		internalTimer++;
    		return CurrentLevel;
    	}
    	
    	while(time < (attack + decay + internalTimer + release) && *pKeyState == 2)
    	{
    		CurrentLevel = CurrentLevel * (sustain / release);
    		time++;
    		return CurrentLevel;
    
    	}
    	return CurrentLevel;
    }
VST_Plug_in.h

Code: Select all

    //-------------------------------------------------------------------------------------------------------
    // This is the most basic plug in code there is!
    // All this plug in will do is apply some gain to the input
    // This is done in process replacing
    //-------------------------------------------------------------------------------------------------------
    
    #ifndef __VST_Plug_in__
    #define __VST_Plug_in__
    
    #include "audioeffectx.h"
    
    #include <math.h>
    #include "ADSR.h"
    
    
    
    const int NUMBER_OF_INPUTS = 2;
    const int NUMBER_OF_OUTPUTS = 2;
    const int NUMBER_OF_PROGRAMS = 1;
    const int NUMBER_OF_PARAMETERS = 5;
    
    enum
    	{
    	kGain,
    	kAttack,
    	kDecay,
    	kSustain,
    	kRelease
    	};
    
    // Base frequency (A4- 440Hz) for use in generating frequency table
    	const float BASE_A4  = 440.0;
    	const double PI = 3.14159265358979323846;
    
    //-------------------------------------------------------------------------------------------------------
    class VST_Plug_in : public AudioEffectX
    {
    public:
    	VST_Plug_in (audioMasterCallback audioMaster);
    	~VST_Plug_in ();
    	
    	virtual void processReplacing (float** inputs, float** outputs, VstInt32 sampleFrames);
    
    	virtual VstInt32 processEvents (VstEvents* events);
    	virtual VstInt32 canDo (char* text);
    	
    	double leftSample;
    	double rightSample;
    	float frequency;
    	float sampleRate;
    	long time;
    	long* pTime;
    	float gain;
    
    	// MIDI stuff
    	// MIDI data : holds data about current state of MIDI (note on/off, frequency, velocity)
    	int keyDown; // true : key is down, false : no key down
    	int* pkeyDown;
    
    	long currentNote; // the MIDI note number of the last note on (key down)
    	float currentVelocity; // current MIDI note velocity (0 -> 1)
    	float *m_pfFrequencyTable; // will store a list of frequency values (for note->frequency conversion)
    	
    	void noteOff ();
    	void noteOn (long liNote, long liVelocity);
    	int getAttack(float value);
    	int sendAttack();
    	int getDecay(float value);
    	int sendDecay();
    	float getSustain(float value);
    	float sendSustain();
    	int getRelease(float value);
    	int sendRelease();
    
    	int maxAttack, minAttack, attack, maxDecay, minDecay, decay, minRelease, maxRelease, release;
    	float sustain;
    
    	double partial1, partial2, partial3, partial4, partial5, partial6, partial7, partial8;
    
    	ADSR env;
    
    	virtual void setParameter (VstInt32 index, float value);
    	virtual float getParameter (VstInt32 index);
    	virtual void getParameterLabel (VstInt32 index, char* label);
    	virtual void getParameterDisplay (VstInt32 index, char* text);
    	virtual void getParameterName (VstInt32 index, char* text);	
    };
    
    #endif
VST_Plug_in.cpp

Code: Select all

    //-------------------------------------------------------------------------------------------------------
    // VST Plug-Ins SDK
    // Version 2.4		$Date: 2005/11/15 15:14:03 $
    // 
    // Category     : VST 2.x SDK Samples
    // Filename     : VST_Plug_in.cpp
    // Created by   : Steinberg Media Technologies
    // Description  : a crap additive synth
    //
    // © 2005, Steinberg Media Technologies, All Rights Reserved
    //-------------------------------------------------------------------------------------------------------
    
    #include "VST_Plug_in.h"
    
    //-------------------------------------------------------------------------------------------------------
    AudioEffect* createEffectInstance (audioMasterCallback audioMaster)
    {
    	return new VST_Plug_in (audioMaster);
    }
    
    //-------------------------------------------------------------------------------------------------------
    VST_Plug_in::VST_Plug_in (audioMasterCallback audioMaster)
    : AudioEffectX (audioMaster, NUMBER_OF_PROGRAMS, NUMBER_OF_PARAMETERS)	
    {
    	setNumInputs (NUMBER_OF_INPUTS);		// stereo in
    	setNumOutputs (NUMBER_OF_OUTPUTS);		// stereo out
    	setUniqueID ('Add1');	// identify
    	canProcessReplacing ();	// supports replacing output
    	leftSample  = 0.0;
    	rightSample = 0.0;
    	frequency = 0.0;
    	gain = 1.f;
    	currentVelocity = 0.0;
    	currentNote = 0;
    	keyDown = 2;
    	pkeyDown = &keyDown;
    	time = 0;
    	pTime = &time;
    	partial1 = partial2 = partial3 = partial4 = partial5 = partial6 = partial7 = partial8 = 0.f;
    	maxAttack = 2 * 192000;
    	minAttack = 1;
    	attack = 44100;
    	maxDecay = 192000;
    	minDecay = 1;
    	decay = 22050;
    	maxRelease = 4 * 192000;
    	minRelease = 1;
    	release = 96000;
    	sustain = 1.f;
    	sampleRate = getSampleRate();//get sample rate from host
    	
    	ADSR env;
    
    	isSynth ();	// Informs host that this is a VSTi
    	
    	// initialise frequency table
    	m_pfFrequencyTable = new float [128] ; // 128 Midi notes
    	if (m_pfFrequencyTable)
    	{
    		for (int i = 0; i<  128; i++)
    		{
    			m_pfFrequencyTable[i] = BASE_A4 *powf(2.f,(i-57)/12.f) ;
    		}
    	}	
    }
    
    //-------------------------------------------------------------------------------------------------------
    VST_Plug_in::~VST_Plug_in ()
    {
    	// nothing to do here
    }
    
    
    //-----------------------------------------------------------------------------------------
    // this is where the intresting stuff happens :0
    
    void VST_Plug_in::processReplacing (float** inputs, float** outputs, VstInt32 sampleFrames)
    {
        float* out1 = outputs[0];
        float* out2 = outputs[1];
    	//ADSR env;
    
    	for(int i = 0; i < sampleFrames; i++)
        {
    		// NEW : only send out audio if there is a note on currently
    		frequency = m_pfFrequencyTable[currentNote];
    		
    		partial1 = (double)sin(2.0*PI*time++*(frequency/sampleRate))* 0.125;
    		partial2 = (double)sin(2.0*PI*time++*((2*frequency)/sampleRate))* 0.125;
    		partial3 = (double)sin(2.0*PI*time++*((3*frequency)/sampleRate))* 0.125;
    		partial4 = (double)sin(2.0*PI*time++*((4*frequency)/sampleRate))* 0.125;
    		partial5 = (double)sin(2.0*PI*time++*((5*frequency)/sampleRate))* 0.125;
    		partial6 = (double)sin(2.0*PI*time++*((6*frequency)/sampleRate))* 0.125;
    		partial7 = (double)sin(2.0*PI*time++*((7*frequency)/sampleRate))* 0.125;
    		partial8 = (double)sin(2.0*PI*time++*((8*frequency)/sampleRate))* 0.125;
    		
    		leftSample = partial1 + partial2 + partial3 + partial4 + partial5 + partial6 + partial7 + partial8;
    		
    		leftSample = leftSample * env.process(leftSample, attack, decay, sustain, release, pkeyDown); 
    		
    		leftSample = leftSample * currentVelocity;
    		leftSample = leftSample * gain;
    		
    		rightSample = leftSample;
    
    		// write samples to output buffer 
            (*out1++) = leftSample;
            (*out2++) = rightSample;
        }
    }
    
    
    // NEW : overriden function, tells host what the plugin can do (see notes)
    
    VstInt32 VST_Plug_in::canDo(char *text)
    {
    	if (!strcmp (text, "receiveVstEvents"))		// SimpleSynth can receive VST events
    		return 1;
    	if (!strcmp (text, "receiveVstMidiEvent"))	// SimpleSynth can receive VST MIDI events
    		return 1;
    	return -1;	// explicitly can't do; 0 => don't know	
    }
    
    // NEW : this process function is called to collect incoming VST events
    
    VstInt32 VST_Plug_in::processEvents (VstEvents* events)
    {
    	// parse event list
    	for (long i = 0; i < events->numEvents; i++)
    	{
    		if ((events->events[i])->type == kVstMidiType)
    		{
    			VstMidiEvent* event = (VstMidiEvent*)events->events[i];
    			char* midiData = event->midiData;
    			long status = midiData[0] & 0xf0;		// ignoring channel
    
    			if (status == 0x90 || status == 0x80)	// we only look at notes
    			{
    				long note = midiData[1];
    				long velocity = midiData[2];
    
    				if (status == 0x80)
    				{
    					velocity = 0;	
    					// set velocity to zero if it is a note off message
    				}
    				if (!velocity && (note == currentNote))
    				{
    					noteOff ();
    				}
    				else
    				{
    					noteOn (note, velocity);
    				}
    			}
    		}
    	}
    	return 1; // indicate that we wish to receive more events
    }
    
    void VST_Plug_in::noteOn(long liNote, long liVelocity)
    {
    	keyDown = 1;
    	time = 0;
    	currentNote = liNote;
    	
    	// velocity is liVelocity /127
    	currentVelocity = liVelocity / 127.f;
    	
    }
    
    void VST_Plug_in::noteOff()
    {
    	keyDown = 2;
    	currentVelocity = 0;
    }
    
    // this function is called whenever the fader is moved
    
    void VST_Plug_in::setParameter (VstInt32 index, float value)
    {
    	switch (index)
    	{
    		case kGain : gain = value;break;  
    		case kAttack : attack = getAttack(value); break; //need vale in samples for envelope here
    		case kDecay : decay = getDecay(value); break;
    		case kSustain : sustain = getSustain(value); break;
    		case kRelease : release = getRelease(value); break;
    	}
    }
    
    
    
    //-----------------------------------------------------------------------------------------
    
    // this function is called whenever the gui requires data
    
    // eg when it is displayed
    
    float VST_Plug_in::getParameter (VstInt32 index)
    
    {
    	switch (index)
    	{
    		case kGain : return gain;break;
    		case kAttack : return sendAttack(); break; //need 0 - 1 val here
    		case kDecay : return sendDecay();break;
    		case kSustain : return sendSustain();break;
    		case kRelease : return sendRelease();break;
    	}
    }
    
    
    
    //-----------------------------------------------------------------------------------------
    
    // getParameterName places the parameter name on the plug in
    
    void VST_Plug_in::getParameterName (VstInt32 index, char* label)
    
    {
    	switch (index)
    	{
    		case kGain : vst_strncpy (label, "Gain", kVstMaxParamStrLen);break;
    		case kAttack : vst_strncpy (label, "Attack", kVstMaxParamStrLen); break;
    		case kDecay : vst_strncpy (label, "Decay", kVstMaxParamStrLen); break;
    		case kSustain : vst_strncpy (label, "Sustain", kVstMaxParamStrLen); break;
    		case kRelease : vst_strncpy (label, "Release", kVstMaxParamStrLen); break;
    	}
    }
    
    
    
    //----------------------------------------------------------------------------------------
    
    // getParameterDisplay displays the parameter value on the plug-in
    
    void VST_Plug_in::getParameterDisplay (VstInt32 index, char* text)
    
    {
    	switch (index)
    	{
    		case kGain : dB2string(gain, text, kVstMaxParamStrLen);break;
    		case kAttack : float2string(attack, text, kVstMaxParamStrLen); break;
    		case kDecay : float2string(decay, text, kVstMaxParamStrLen); break;
    		case kSustain: dB2string(sustain, text, kVstMaxParamStrLen); break;
    		case kRelease : float2string(release, text, kVstMaxParamStrLen); break;
    	}
    	
    	//linear display
    	//float2string(gain, text, kVstMaxParamStrLen);
    
    	//dB display
    	//dB2string (gain, text, kVstMaxParamStrLen);
    }
    
    
    
    //-----------------------------------------------------------------------------------------
    
    void VST_Plug_in::getParameterLabel (VstInt32 index, char* label)
    
    {
    	switch (index)
    	{
    		case kGain : vst_strncpy (label, "dB", kVstMaxParamStrLen);break;
    		case kAttack : vst_strncpy (label, "Samples", kVstMaxParamStrLen); break;
    		case kDecay : vst_strncpy (label, "Samples", kVstMaxParamStrLen); break;
    		case kSustain : vst_strncpy (label, "dB", kVstMaxParamStrLen); break;
    		case kRelease : vst_strncpy (label, "Samples", kVstMaxParamStrLen); break;
    	}
    }
    
    int VST_Plug_in::getAttack(float value)
    {	
    	attack = minAttack + ((maxAttack-minAttack) * value);
    	return attack;
    }
    int VST_Plug_in::sendAttack()
    {	
    	int retAttVal;
    	retAttVal = (attack - minAttack) / (maxAttack-minAttack);
    	return retAttVal;
    }
    
    int VST_Plug_in::getDecay(float value)
    {
    	decay = minDecay + ((maxDecay-minDecay) * value);
    	return decay;
    }
    int VST_Plug_in::sendDecay()
    {
    	int retDecVal;
    	retDecVal = (decay - minDecay) / (maxDecay-minDecay);
    	return retDecVal;
    }
    
    
    float VST_Plug_in::getSustain(float value)
    {
    	sustain = value;
    	return sustain;
    }
    float VST_Plug_in::sendSustain()
    {
    	return sustain;
    }
    
    int VST_Plug_in::getRelease(float value)
    {
    	release = minRelease + ((maxRelease-minRelease) * value);
    	return release;
    }
    int VST_Plug_in::sendRelease()
    {
    	int retRelVal;
    	retRelVal = (release - minRelease) / (maxRelease-minRelease);
    	return retRelVal;
    }


:help:

Post

Code: Select all

int maxAttack, minAttack, attack, maxDecay, minDecay, ...

Code: Select all

retAttVal = (attack - minAttack) / (maxAttack-minAttack);
(attack - minAttack) is never greater than (maxAttack-minAttack)
and since all the variables are "int", retAttVal will be zero most
of the times.

retAttVal would be zero anyway, because it's "int":

Code: Select all

    
int VST_Plug_in::sendAttack()
{   
    int retAttVal;
    ...

Here's another case:

Code: Select all

CurrentLevel = CurrentLevel * ((1 / attack) + 1); 

Post

Hi thank you,

Yes since posting I realised what was wrong with the sliders. Turning all relevant functions and variables to float has sliders function as expected. I should have realised earlier having had a similar issue with Max MSP. :oops:

Amazing what some sleep and no kids can do for you!

Still struggling with the envelope progression though. Just cannot see why it won't step through the relevant stages. My logic must be wrong but where???

Trying a timer class using pointers... doubt it'll make any difference. :(

Post

try "stage == 1" instead of "stage = 1"

Post

larm wrote:try "stage == 1" instead of "stage = 1"
Nicely spotted thank you, unfortunately correcting it hasn't made the envelope function.

Having implemented a timer class that has 1 variable tracking time that is access via pointers, I have a new issue on top the fact that it isn't doing what I want.

If the time variable for the envelope is incremented within the processReplacing() loop the code complies but crashes the host.

When incrementing time within each adsr multiplication, it compiles and runs in a host, just doesn't work as wanted.

I would have thought that incrementing time in the processReplacing() loop was the was to go, apparently not.

I am very appreciative of everyones help here, I know its cheeky but have attached my current code to see if anyone can see whats actually wrong with my logic. I wish I were Vulcan sometimes.

Don't think the .h's are needed so just the 3 .cpp's, and I tidied it all up so it's much easier to read now.

Thank you again,

Ben


timer.cpp

Code: Select all

#include <math.h>
#include "timer.h"


timer::timer()
{
	time = 0;
	pTime = &time;
}
timer::~timer()
{
}

long* timer::timeCount()
{
	return pTime;
}
ADSR.cpp

Code: Select all

#include <math.h>
#include "ADSR.h"


ADSR::ADSR()
{
	stage = 1;
	susTimer = 0;
}
ADSR::~ADSR()
{
}

double ADSR::process(float currentSamp, int attack, int decay, float sustain, int release, int* pKeyState, long* pTime)
{
	if(stage == 1 && *pTime >= (attack + decay + susTimer + release))
	{
		*pTime = 0;
	}

	while(*pTime < attack)
	{
		currentSamp = currentSamp * ((1 / attack) + 1);
		*pTime++;//doesn't crash but doesnt do anything either
		stage = 1;
		return currentSamp;
	}
		
	while(*pTime < (attack + decay))
	{
		currentSamp = currentSamp * (sustain / decay);
		*pTime++;//doesn't crash but doesnt do anything either
		stage = 2;
		return currentSamp;
	}

	while(*pTime > (attack + decay))
	{
		currentSamp = currentSamp * 1;
		susTimer++;
		stage = 3;
		return currentSamp;
	}
	
	while(*pTime < (attack + decay + susTimer + release))
	{
		currentSamp = currentSamp * (sustain / release);
		*pTime++;//doesn't crash but doesnt do anything either
		stage = 4;
		return currentSamp;
	}
}
VST_Plug_in.cpp

Code: Select all

//-------------------------------------------------------------------------------------------------------
// VST Plug-Ins SDK
// Version 2.4		$Date: 2005/11/15 15:14:03 $
// 
// Category     : VST 2.x SDK Samples
// Filename     : VST_Plug_in.cpp
// Created by   : Steinberg Media Technologies
// Description  : a crap additive synth
//
// © 2005, Steinberg Media Technologies, All Rights Reserved
//-------------------------------------------------------------------------------------------------------

#include "VST_Plug_in.h"
#include "audioeffectx.h"
#include <math.h>
#include "ADSR.h"
#include "timer.h"

AudioEffect* createEffectInstance (audioMasterCallback audioMaster)
{
	return new VST_Plug_in (audioMaster);
}

VST_Plug_in::VST_Plug_in (audioMasterCallback audioMaster)
: AudioEffectX (audioMaster, NUMBER_OF_PROGRAMS, NUMBER_OF_PARAMETERS)	
{
	setNumInputs (NUMBER_OF_INPUTS);		// stereo in
	setNumOutputs (NUMBER_OF_OUTPUTS);		// stereo out
	setUniqueID ('Add1');	// identify
	canProcessReplacing ();	// supports replacing output
	isSynth ();	// Informs host that this is a VSTi
	sampleRate = getSampleRate();//get sample rate from host

	leftSample  = 0.0;
	rightSample = 0.0;
	frequency = 0.0;
	gain = 1.f;
	currentVelocity = 0.f;
	currentNote = 0;
	keyDown = 2;
	pkeyDown = &keyDown;
	partialTime = 0;
	partial1 = partial2 = partial3 = partial4 = partial5 = partial6 = partial7 = partial8 = 0.0;
	maxAttack = 384000;
	minAttack = 1;
	attack = 44100;
	maxDecay = 192000;
	minDecay = 1;
	decay = 22050;
	maxRelease = 768000;
	minRelease = 1;
	release = 96000;
	sustain = 1.f;

	ADSR env;
	timer universalTime;
	pTime = universalTime.timeCount();

	// initialise frequency table
	m_pfFrequencyTable = new float [128] ; // 128 Midi notes
	if (m_pfFrequencyTable)
	{
		for (int i = 0; i<  128; i++)
		{
			m_pfFrequencyTable[i] = BASE_A4 *powf(2.f,(i-57)/12.f) ;
		}
	}	
}

VST_Plug_in::~VST_Plug_in ()
{
	// nothing to do here
}

// this is where the intresting stuff happens :0
void VST_Plug_in::processReplacing (float** inputs, float** outputs, VstInt32 sampleFrames)
{
    float* out1 = outputs[0];
    float* out2 = outputs[1];

	for(int i = 0; i < sampleFrames; i++)
    {
		// NEW : only send out audio if there is a note on currently
		frequency = m_pfFrequencyTable[currentNote];

		partial1 = (double)sin(2.0*PI*partialTime++*(frequency/sampleRate))* 0.125;//Nyquist frequency issues.  
		partial2 = (double)sin(2.0*PI*partialTime*((2*frequency)/sampleRate))* 0.125;//Obvious looping/foldback of frequencies with higher notes.
		partial3 = (double)sin(2.0*PI*partialTime*((3*frequency)/sampleRate))* 0.125;//Appears mostly fixed when time is only 
		partial4 = (double)sin(2.0*PI*partialTime*((4*frequency)/sampleRate))* 0.125;//incremented with the fundamental.
		partial5 = (double)sin(2.0*PI*partialTime*((5*frequency)/sampleRate))* 0.125;
		partial6 = (double)sin(2.0*PI*partialTime*((6*frequency)/sampleRate))* 0.125;
		partial7 = (double)sin(2.0*PI*partialTime*((7*frequency)/sampleRate))* 0.125;
		partial8 = (double)sin(2.0*PI*partialTime*((8*frequency)/sampleRate))* 0.125; 
		leftSample = partial1 + partial2 + partial3 + partial4 + partial5 + partial6 + partial7 + partial8;
		
		leftSample = (env.process(leftSample, attack, decay, sustain, release, pkeyDown, pTime) * currentVelocity); //needs to be pointer as data is duplicated when sent.
		//*pTime++; //crashes host if uncommented
		leftSample = leftSample * gain;
		rightSample = leftSample;
		
		// write samples to output buffer 
        (*out1++) = leftSample;
        (*out2++) = rightSample;
    }
}

// NEW : overriden function, tells host what the plugin can do (see notes)
VstInt32 VST_Plug_in::canDo(char *text)
{
	if (!strcmp (text, "receiveVstEvents"))		// SimpleSynth can receive VST events
		return 1;
	if (!strcmp (text, "receiveVstMidiEvent"))	// SimpleSynth can receive VST MIDI events
		return 1;
	return -1;	// explicitly can't do; 0 => don't know	
}

// NEW : this process function is called to collect incoming VST events
VstInt32 VST_Plug_in::processEvents (VstEvents* events)
{
	// parse event list
	for (long i = 0; i < events->numEvents; i++)
	{
		if ((events->events[i])->type == kVstMidiType)
		{
			VstMidiEvent* event = (VstMidiEvent*)events->events[i];
			char* midiData = event->midiData;
			long status = midiData[0] & 0xf0;		// ignoring channel

			if (status == 0x90 || status == 0x80)	// we only look at notes
			{
				long note = midiData[1];
				long velocity = midiData[2];

				if (status == 0x80)
				{
					velocity = 0;	
					// set velocity to zero if it is a note off message
				}
				if (!velocity && (note == currentNote))
				{
					noteOff ();
				}
				else
				{
					noteOn (note, velocity);
				}
			}
		}
	}
	return 1; // indicate that we wish to receive more events
}

void VST_Plug_in::noteOn(long liNote, long liVelocity)
{
	keyDown = 1;
	partialTime = 0;
	currentNote = liNote;
	currentVelocity = liVelocity / 127.f;
}

void VST_Plug_in::noteOff()
{
	keyDown = 2;
	currentVelocity = 0;
}

// this function is called whenever the fader is moved
void VST_Plug_in::setParameter (VstInt32 index, float value)
{
	switch (index)
	{
		case kGain : gain = value;break;  
		case kAttack : attack = getAttack(value); break; //need vale in samples for envelope here
		case kDecay : decay = getDecay(value); break;
		case kSustain : sustain = getSustain(value); break;
		case kRelease : release = getRelease(value); break;
	}
}

// this function is called whenever the gui requires data
float VST_Plug_in::getParameter (VstInt32 index)

{
	switch (index)
	{
		case kGain : return gain;break;
		case kAttack : return sendAttack(); break; //need 0 - 1 val here
		case kDecay : return sendDecay();break;
		case kSustain : return sendSustain();break;
		case kRelease : return sendRelease();break;
	}
}

// getParameterName places the parameter name on the plug in
void VST_Plug_in::getParameterName (VstInt32 index, char* label)

{
	switch (index)
	{
		case kGain : vst_strncpy (label, "Gain", kVstMaxParamStrLen);break;
		case kAttack : vst_strncpy (label, "Attack", kVstMaxParamStrLen); break;
		case kDecay : vst_strncpy (label, "Decay", kVstMaxParamStrLen); break;
		case kSustain : vst_strncpy (label, "Sustain", kVstMaxParamStrLen); break;
		case kRelease : vst_strncpy (label, "Release", kVstMaxParamStrLen); break;
	}
}

// getParameterDisplay displays the parameter value on the plug-in
void VST_Plug_in::getParameterDisplay (VstInt32 index, char* text)

{
	switch (index)
	{
		case kGain : dB2string(gain, text, kVstMaxParamStrLen);break;
		case kAttack : float2string(attack, text, kVstMaxParamStrLen); break;
		case kDecay : float2string(decay, text, kVstMaxParamStrLen); break;
		case kSustain: dB2string(sustain, text, kVstMaxParamStrLen); break;
		case kRelease : float2string(release, text, kVstMaxParamStrLen); break;
	}
	/*linear display
	float2string(gain, text, kVstMaxParamStrLen);
	dB display
	dB2string (gain, text, kVstMaxParamStrLen);*/
}

void VST_Plug_in::getParameterLabel (VstInt32 index, char* label)

{
	switch (index)
	{
		case kGain : vst_strncpy (label, "dB", kVstMaxParamStrLen);break;
		case kAttack : vst_strncpy (label, "Samples", kVstMaxParamStrLen); break;
		case kDecay : vst_strncpy (label, "Samples", kVstMaxParamStrLen); break;
		case kSustain : vst_strncpy (label, "dB", kVstMaxParamStrLen); break;
		case kRelease : vst_strncpy (label, "Samples", kVstMaxParamStrLen); break;
	}
}

float VST_Plug_in::getAttack(float value)
{	
	attack = minAttack + ((maxAttack-minAttack) * value);
	return attack;
}
float VST_Plug_in::sendAttack()
{	
	float retAttVal;
	retAttVal = (attack - minAttack) / (maxAttack-minAttack);
	return retAttVal;
}

float VST_Plug_in::getDecay(float value)
{
	decay = minDecay + ((maxDecay-minDecay) * value);
	return decay;
}
float VST_Plug_in::sendDecay()
{
	float retDecVal;
	retDecVal = (decay - minDecay) / (maxDecay-minDecay);
	return retDecVal;
}

float VST_Plug_in::getSustain(float value)
{
	sustain = value;
	return sustain;
}
float VST_Plug_in::sendSustain()
{
	return sustain;
}

float VST_Plug_in::getRelease(float value)
{
	release = minRelease + ((maxRelease-minRelease) * value);
	return release;
}
float VST_Plug_in::sendRelease()
{
	float retRelVal;
	retRelVal = (release - minRelease) / (maxRelease-minRelease);
	return retRelVal;
}

Post

Try another change:

http://pw1.netcom.com/~tjensen/ptr/ch3x.htm

Code: Select all

Now, let's consider some of the things the above examples have shown us. First off, consider the fact that *ptr++ is to be interpreted as returning the value pointed to by ptr and then incrementing the pointer value. This has to do with the precedence of the operators. Were we to write (*ptr)++ we would increment, not the pointer, but that which the pointer points to! i.e. if used on the first character of the above example string the 'T' would be incremented to a 'U'. You can write some simple example code to illustrate this. 

Post

larm thank you.

So I wasn't incrementing the contents but the address. I wasn't sure until reading that rather handy link wether pointer contents where incremented the same way as assigned. Binky never covered that in "Fun with Pointers" So thank you for the extra learning. Alas the envelope still isn't working.

I don't get which bit of the decisions on adsr stage is iffy, and what it is returning, as without going through one of the while loops there is nothing to return. I even get a compiler warning to that effect:
e:\library\mobile documents\blah ... blah\adsr.cpp(52): warning C4715: 'ADSR::process' : not all control paths return a value
I have tried being more, less and non specific with the conditions of the if and while bits of ADSR::process() and still nothing actually happens, although something is returned??

I am sure that had I spent more time doing c++ and less time messing about with Max MSP I'd be able to see what's awry with the logic there.

any further input as ever is greatly appreciated.

Cheers

Post

Next thing to learn about is the difference between integer and floating point division. Since your parameters to ADSR process are integers (no decimals) your envelope multipliers/factors are either 0 or 1.

Rewrite

Code: Select all

int attack, int decay, float sustain, int release

to

Code: Select all

float attack, float decay, float sustain, float release
And you can add "f" to signal that arithmetics should be made in floating point.

Code: Select all

((1.0f / attack) + 1.0f)
The warning is because the compiler sees the potentiality of all of the while loops being skipped over. Add a final return and the warning disappears.

Try

Code: Select all

return -1.0;
for debugging, that way you would probably get a nasty click and a full on negative DC-offset, signalling that the ADSR probably didn't do as you thought..

Also I'm not sure about your time step delta, time++ will add "1" but are your ADSR values in seconds or samples? Could be good to have them run in a sample-rate independent format (seconds, milliseconds) and increase the time with 1.0/sample rate.

Post

Larm your help has got me far. Thank you very much.

I cannot believe that for the 2nd time today I have been got by the int/float thing. Ah well that's that bit sorted.

With your next bit of useful advice, return -1; I have managed to get the envelope to progress as far sustain. Capturing the release stage is proving tricky.

As I had expected now that A and D are actually doing something I have found that the are only holding the level constant, not ramping it.

I tried storing a previous sample like with filters and mixing the 2 however this just causes signal overload.

Code: Select all

while(*pTime <= attack)
	{
		currentSamp = prevSamp + (currentSamp * (1.0f / attack));
		(*pTime)++;//doesn't crash but doesnt do anything either
		stage = 1;
		prevSamp = currentSamp;
		return currentSamp;
	}
And yes, should I ever get the envelope working with time in samples, I was then going to make it sample rate independent with sensible time values. So again thank you for advice there, you've defiantly got my brain thinking in the right direction for that. For now at least I am finding debugging easier thinking in samples.

:)

Post

Why do you use while and then a return inside every while?
like this

while(*pTime < attack)
{......

In practice it will work like if you wrote "if" instead of "while" which is OK but not great for readability as you expect som kind of looping when reading a while.
David Guda gudaaudio.com

Post Reply

Return to “DSP and Plugin Development”