Problem with automation in Live


#1

Hi,

just noticed that my plugin cannot be automated in Ableton Live
if it is vst. AU is fine.

No problems in Cubase or Reaper.

Any idea where to look at?

Thanks
Joerg


#2

No idea! Anyone else seen this?


#3

Automation seems to work here, also with OSX + VST + Ableton 9. I had some problems in ableton live too, because i forgot to add dontSendNotification after the JUCE 2.0 migration to the components setValue calls. This produced a feedback loop.

EDIT: I used abletons automation to assign a MIDI knob to a slider.


#4

Silly question, but did you make sure you marked your parameters as automatable? (Could be that some hosts ignore that flag and automate them anyway)


#5

Just wondering: Where can i flag parameters for automation? Is this a new feature? I only see isParameterAutomatable in the wrapper.


#6

Well yes, that’s what I meant. You just make isParameterAutomatable return true for the ones that are automatable.


#7

I decide by case what controls are automatable depending on their index.

They strange think to me is that the same code works well as AU and,
vst doesn’t work on Windows either.

This is the method being called when any vst parameter changes:

void JK_AudioProcessor::performAndNotifyHostAboutParamChange(int vstIndex, int value)
{	
            beginParameterChangeGesture(vstIndex); 
            setParameterNotifyingHost(vstIndex, appManager->midiToVstValue(vstIndex, value));
            endParameterChangeGesture(vstIndex);
}

Would it be helpful to re-build the project in Introducer from scratch?


#8

Found the culprit. The Audioprocessor method setParameterNotifyingHost MUST be called by the message thread.
Doing this from a different thread causes the issue.

Did I oversee a warning here to only use the message threat?


#9

I think the threading requirements probably depend on the host, but yes, I should certainly add an assertion there, as it’d certainly be dangerous to call it from other threads.


#10

Okay, I can certainly deal with this from the UI but how would you do this
if there is no message thread e.g. the plugin has no editor or the editor
is closed while you want to write automation from the Midi input?

Any idea would be much appreciated!

Thanks


#11

There’s always a message thread, regardless of whether or not you have a UI open.


#12

mmh, I was referring to a post from somebody else saying

http://www.rawmaterialsoftware.com/viewtopic.php?f=2&t=9481

But I may mess things up here…


#13

I had the same kind of problem that forced me to create a dedicated thread that is pulling messages sent by the audio thread with my own FIFO when a parameter changes and then using the MessageListener API to call setParameterNotifyingHost.
There must be a smarter way to do that but this works.


#14

I also queue host param calls in a FIFO and let a thread deal with it but the same thread
calls setParameterNotifyingHost if the param change came from somewhere else than
the host e.g. the UI.
I’ll try to post a message to the processor instead thought I am not really convinced
because it introduces more delay in the host notification process.

Thank you!


#15

I finally added host automation support for midi controllers too and implemented the idea ke20 described above and it works. I currently do not like that i have a circular dependency between my AudioProcessor and the AutomationNotifier thread. I’m not sure if the Queue handling is done the right way and thread save.

I posted this as a working solution and hope that i get some feedback for improvements.

#include "AutomationNotifier.h"
#include "TalCore.h"

AutomationNotifier::AutomationNotifier(TalCore *audioProcessor) : Thread("Midi Controller Notifications")
{
	this->audioProcessor = audioProcessor;
}

AutomationNotifier::~AutomationNotifier()
{
}

void AutomationNotifier::pushMessage(MidiMessage midiMessage)
{
	const ScopedLock myScopedLock (myCriticalSectionBuffer);
	this->midiMessageQueue.push(midiMessage);
}

void AutomationNotifier::run()
{
	while (!this->threadShouldExit())
	{
		while (!this->midiMessageQueue.empty())
		{
			this->process();
		}

		this->sleep(20);
	}
}

void AutomationNotifier::process()
{
	const ScopedLock myScopedLock (myCriticalSectionBuffer);
	MidiMessage message = this->midiMessageQueue.front();
	audioProcessor->postMessage(new ParameterChangedMessage(message));
	this->midiMessageQueue.pop();
}
#ifndef __AutomationNotifier_h
#define __AutomationNotifier_h

class TalCore;
#include "JuceHeader.h"
#include "ParameterChangedMessage.h"
#include <queue>

class AutomationNotifier : public Thread
{
private:
	TalCore *audioProcessor;

	std::queue<MidiMessage> midiMessageQueue;

	CriticalSection myCriticalSectionBuffer;

public:
    AutomationNotifier(TalCore *audioProcessor);

	~AutomationNotifier();

	void pushMessage(MidiMessage midiMessage);

	void run();
	void process();
};

#endif

In the AudioProcessor:

void TalCore::handleMessage (const Message& message)
{
	const ParameterChangedMessage& parameterChangedMessage =  dynamic_cast <const ParameterChangedMessage&>(message);
	MidiMessage midi = parameterChangedMessage.midiMessage;
	int controllerNumber = midi.getControllerNumber();
	this->beginParameterChangeGesture(controllerNumber); 
	this->setParameterNotifyingHost(controllerNumber, midi.getControllerValue() / 127.0f);
 	this->endParameterChangeGesture(controllerNumber);
}

#16

I imagine that pushMessage is called from the MIDI callback so even it is less time critical than the audio thread I would rather use a lock free system than a CriticalSection (but I could be wrong).
Another thing is that I don’t think your AutomationNotifier need to inherit from MessageListener. It seems that is your Processor who is doing the job.

Kevin


#17

[quote=“ke20”]I imagine that pushMessage is called from the MIDI callback so even it is less time critical than the audio thread I would rather use a lock free system than a CriticalSection (but I could be wrong).
Another thing is that I don’t think your AutomationNotifier need to inherit from MessageListener. It seems that is your Processor who is doing the job.

Kevin[/quote]
Hi Kevin,

thanks a lot for that feedback. I removed the MessageListener from the thread.

I get the midi messages by the processBlock function in the audio processor. I’m really not sure about the lock… i had a concurrency exception while i did some debugging.


#18

If you are in the audio thread you should really take care about that because the lock could be a problem in some situation.
I would even not use the std::queue because it might use dynamic allocation (something that is not good in the audio thread).


#19

Thanks for the info. You are right. Its not a good idea to block the audio processing with this lock. I will also have a look at other options for the standard queue.

e: Is it possible to use a queue in a thread loop without a lock?
e2: Is it better to use filter->getCallbackLock().enter(); in the thread loop and remove the lock in the push method?

Something like this?

void AutomationNotifier::pushMessage(MidiMessage midiMessage)
{
	this->midiMessageQueue.push(midiMessage);
}

void AutomationNotifier::run()
{
	while (!this->threadShouldExit())
	{
		while (!this->midiMessageQueue.empty())
		{
			this->audioProcessor->getCallbackLock().enter();
			this->process();
			this->audioProcessor->getCallbackLock().exit();
		}

		this->sleep(20);
	}
}

#20

Thanks again for the help. I finally ended in following solution with a simple array. Maybe its save to remove the entire lock, but i think its possible to loose data without it. This way no memory will be allocated and there is no lock in the push method called from processBlock.

#include "AutomationNotifier.h"
#include "TalCore.h"

AutomationNotifier::AutomationNotifier(TalCore *audioProcessor) : Thread("Midi Controller Notifications")
{
    this->audioProcessor = audioProcessor;
    this->numberOfMessages = 0;
}

AutomationNotifier::~AutomationNotifier()
{
}

void AutomationNotifier::pushMessage(MidiMessage& midiMessage)
{
	if (this->numberOfMessages < 200)
	{
		this->midiMessageQueue[this->numberOfMessages++] = midiMessage;
	}
}

void AutomationNotifier::run()
{
    while (!this->threadShouldExit())
    {
		if (this->numberOfMessages > 0)
		{
			this->process();
		}

        this->sleep(20);
    }
}

void AutomationNotifier::process()
{
	for (int i = 0; i < this->numberOfMessages; i++)
	{
		this->audioProcessor->getCallbackLock().enter();
		MidiMessage message = this->midiMessageQueue[i];
		audioProcessor->postMessage(new ParameterChangedMessage(message));
		this->audioProcessor->getCallbackLock().exit();
	}

	this->numberOfMessages = 0;
}
#ifndef __AutomationNotifier_h
#define __AutomationNotifier_h

class TalCore;
#include "JuceHeader.h"
#include "ParameterChangedMessage.h"

class AutomationNotifier : public Thread
{
private:
	TalCore *audioProcessor;

	MidiMessage midiMessageQueue[200];
	int numberOfMessages;

public:
    AutomationNotifier(TalCore *audioProcessor);

	~AutomationNotifier();

	void pushMessage(MidiMessage& midiMessage);

	void run();
	void process();
};

#endif