Using LevelMeterPlugin from tracktion AudioTrack

Hi there,

I’m building an application using JUCE + tracktion and first of all let me thank the people that have been developing these libraries because they are awesome. Would be nice to have more docs for tracktion but the examples are nice and well, I hope I’ll get answers in the forum :slight_smile:

Much like the playback example, my app has an audio track that loads a file to be played in loop. I’d like to display an audio meter for the track. I use the following code from examples to load the audio file and place it in track #0

auto f = File::createTempFile ("tambourine.wav");
f.replaceWithData (BinaryData::tambourine_wav, BinaryData::tambourine_wavSize);
auto clip = EngineHelpers::loadAudioFileAsClip (edit, f); 
EngineHelpers::loopAroundClip(*clip);

Then elsewhere in the application I set a timer at a rate of 15hz which runs a callback that will collect the level for the track and store it somewhere so I can show it in GUI. I saw the getLevelMeterPlugin method from AudioTrack, so I guessed all audio tracks are created with a number of basic plugins (including the level meter) to facilitate these things. I have the following code run in my timer callback:

track = EngineHelpers::getOrInsertAudioTrackAt (edit, 0);
std::cout << track->getLevelMeterPlugin()->measurer.getLevelCache() << std::endl;

But this is always printing -100 on the console, which seems to be the default value (even if can hear the audio is playing). I guessed the getLevelCache() would be the function to use but again there’s almost no documentation so I’m not sure. Maybe I need to initialise the plugin somehow or tell it to be “active” but I’m not sure how to continue.

Any help will be much appreciated. Thanks!

Yes, sorry, we’ll add some documentation to that soon.

What you actually need to do is create a subclass of LevelMeasurer::Client and then register that with the LevelMeasurer (LevelMeasurer::addClient).

Then on your timer you can call getAndClearAudioLevel (int chan) on your client.


The level cache is really just used for control surfaces.

Thanks for your quick answer :slight_smile:

I’ll try that, but I don’t understand the part of creating a subclass of LevelMeasurer::Client. This is a struct defined inside LevelMeasurer class so should I create an instance of client and pass it with LevelMeasurer::addClient or do I need to subclass to implement something? (sorry, I’ve using JUCE/C++ for some months now but I’m not super experienced C++ so subclassing the LevelMeasurer::Client struct is not obvious to me).

Can you clarify a bit more how should it be structured?

Thanks again!

It doesn’t seem that obvious to me either…The Client class (struct is the same as class) doesn’t have virtual methods, so subclassing it seems a bit odd. (Even though it would work of course, but couldn’t one just directly poke at the members of the original Client class to get the same results?)

Sorry, I was in a rush, that used to work via inheritance, it’s now much more cleanly composable.

Just add an instance as a member to your UI class, register it and then call the methods on it. (E.g.

struct MeterComp : public Component
{
    MeterComp (LevelMeasurer& lm)
        : levelMeasurer (lm)
    {
        levelMeasurer.addClient (levelClient);
    }

    ~MeterComp()
    {
        levelMeasurer.removeClient (levelClient);
    }

    // Draw by calling getAndClearAudioLevel etc.

    LevelMeasurer& levelMeasurer;
    LevelMeasurer::Client levelClient;
};

Oh, perfect, now I understand :slight_smile:
Works like charm, thanks!!

And to complement the question, I noticed that when changing the volume of a track via the default volume plugin the level measuer does not change output. I guess this is because measurer is pre fader (added before volume plugin). I imagine if I want to measure level post fader then I have to add a new level measurer plugin to the track right? (after volume plugin)

I was wrong in my last reply: the measured level does change if I set the volume using the volume plugin, so it is already post-fader.

Would you care to share what you have come up with? Good examples always help with the learning curve.

Thank you.

Sure! I basically followed @dave96’s advice. I hope the code is useful for others, but maybe it is not very good :S

In my app I have 4 audio tracks and I want to show meters for all of them. To do that I store an instance of LevelMeasurer::Client for each track in an array. I define an array as a member of an Engine class which manages all audio engine things:

std::array<te::LevelMeasurer::Client, N_AUDIO_TRACKS> trackLevelClients;

Then in my initialization code for the Engine class, after creating all audio tracks, I create the level meter clients and add to the LevelMeterPlugin of each track:

int index = 0;
for (auto track : te::getAudioTracks(edit)){
    trackLevelClients[index] = te::LevelMeasurer::Client();
    track->getLevelMeterPlugin()->measurer.addClient(trackLevelClients[index]);
    index ++;
}

My goal is to keep the measured levels for each track in a sort of “state” variable that will be read by another process (the GUI process) to print meters on screen. To do that I make my Engine class inherit from Timer and add a timerCallback function. In this function I read the measured levels and store them in my “state” variable. This is how I do it:

for (int index = 0; index<te::getAudioTracks(edit).size(); index++){
    float measuredLevel = trackLevelClients[index].getAndClearAudioLevel(0).dB;
    // Store measuredLevel in a variable
}

(the 0 in getAndClearAudioLevel is to indicate the first audio channel of the track)

The code for the GUI part is irrelevant here as it just reads from the “state” variable and therefore does not interact with the level measurers or the tracks themselves.

Thanks for the help and happy to contribute!

Thank you…interesting approach.

The thing is that I want to separate completely the UI* part from the rest of the app, that’s why I use the state (also it will be useful to, well, save and load state of the app). I’m at a very early stage of the app development, mostly trying to find the best technical setup that will allow me to build it so I’m mostly experimenting only now. Once I do a bit more stuff I might post again explaining the whole picture of what I’m doing and maybe I can get some feedback of whether I’m in the good direction or not. Also I’ll be at ADC this year so I might bother some of you :wink:

I’ll make this open source so it might also be useful as an example for using the tracktion engine. Thanks!

*I’m developing my app to be deployed in an embedded device which has a display but has no mouse/keyboard input so no standard UI interactions will be in place. I have a method to send content to that display using juce graphics functions, but I’m not planning to use JUCE Components because as I don’t need the standard interactions (mouse).

I thought I would flesh out the Level Meter example for the benefit of others.
In your track footer, create a std::unique_ptr<LevelMeterComp> levelMeterComp;. Then, in the track footer’s constructor,

auto audioTrackPtr{dynamic_cast<te::AudioTrack*>(track.get())};
levelMeterComp = std::make_unique<LevelMeterComp>(audioTrackPtr->getLevelMeterPlugin()->measurer);
addAndMakeVisible(levelMeterComp.get());

The dynamic_cast may not be needed in your implementation.

class LevelMeterComp : public Component, public Timer
{
public:
	LevelMeterComp(te::LevelMeasurer& lm) : levelMeasurer(lm)
	{
		setOpaque(true);
		levelMeasurer.addClient(levelClient);
		startTimerHz(30);
	}

	~LevelMeterComp()
	{
		levelMeasurer.removeClient(levelClient);
		stopTimer();
	}

	void paint(Graphics& g) override
	{
		g.fillAll(Colour(0xff000000));// fill the background black

		const double meterHeight{ double(getHeight()) };
		const double meterWidth{ double(getWidth()) };
		const double offSet{ fabs(RANGEMINdB) };
		const double scaleFactor{ meterHeight / (RANGEMAXdB + offSet) };

		// now we calculate and draw our 0dB line
		g.setColour(Colours::lightgrey);  // set line color						   
		g.fillRect(0.0f, float(meterHeight - (offSet * scaleFactor)), float(meterWidth), 1.0f);

		// draw meter Gain bar
		g.setColour(Colours::green);
		auto displayBarHeight = ((currentLeveldB + offSet) * scaleFactor);
		g.fillRect(0.0f, float(meterHeight - displayBarHeight), float(meterWidth), float(displayBarHeight));
	}

private:
	void timerCallback()
	{
		prevLeveldB = currentLeveldB;

		currentLeveldB = levelClient.getAndClearAudioLevel(0).dB;

		// Now we give the level bar fading charcteristics.
		// And, the below coversions, decibelsToGain and gainToDecibels,
		// take care of 0dB, which will never fade!...but a gain of 1.0 (0dB) will.

		const auto prevLevel{ Decibels::decibelsToGain(prevLeveldB) };

		if (prevLeveldB > currentLeveldB)
			currentLeveldB = Decibels::gainToDecibels(prevLevel * 0.94);

		// the test below may save some unnecessary paints
		if(currentLeveldB != prevLeveldB)
			repaint();
	}

	// set the range of the meter in dB 
	const double RANGEMAXdB{ 3.0 };//+3dB
	const double RANGEMINdB{ -30.0 };//-30dB

	double currentLeveldB{ 0.0 };
	double prevLeveldB{ 0.0 };

	te::LevelMeasurer& levelMeasurer;
	te::LevelMeasurer::Client levelClient;
};

You can customize the appearance of the level meter bar as per your preferences.

2 Likes

Thank you for providing such a complete example it was very helpful. You do hit an assertion when trying to draw the bar with negative height so I just added

if (displayBarHeight > 0)
    g.fillRect(0.0f, float(meterHeight - displayBarHeight), float(meterWidth),         float(displayBarHeight));

to prevent that.

Right you are. In my final code I used a jmax( 0.0, "your meter height" ) statement to put lower bounds on the bar, which does the same thing.

There are many very talented and helpful people here in the forum who I learn from every day. So, when I find a solution to something, I always try to post my code so others can benefit as I have.

Thank you.

It appears that the level meter doesn’t measure the levels of midi on the track. I’m aware of setShowMidi but that measures the velocity, not the audio levels once it’s processed.

Why does FourOscPlugin have a LevelMeter built into it? Is there no other way of measuring the output of a single plugin?

It depends where you put the level meter in the signal chain. 4OSC has its own level meter show it can show levels in its UI. If it relied on the track level meter, there is no guarantee that there wouldn’t be more plugins in the signal chain before the level meter, or the user could just remove the level meter.

Depends on the UI of your product, you could insert additional level meters where you need them, or add a level meter to your plugin like 4OSC.