Graphical mapper for velocity curves and similar


#1

Someone must have written a module for taking mouse dragging input on a graph and turning it into a lookup table for mapping an input value to an output value - but I can’t find an example.

It could be either columns for each value, or a line graph.

Has anyone got an example? I’m getting a bit stuck.

Redrawing 128 columns on every drag is inefficient, but making each column its own component is tricky when dragging as enter and exit calls don’t work on drag.

Calling repaint on the whole graph for every pixel mouse movement can’t be a good thing, but avoiding that seems to be working against the structures that are there.

There must be an easy way and I imagine it has been done loads of times for synths and midi stuff.

This is the first time I’ve tried to draw anything in Juce so I may be making total noob mistakes.


#2

Check out the dRowAudio classes… there may be something in the demo which may help you.

Rail


#3

Calling repaint is the only way you are going to get the whole thing to update if that’s what you need.

Are you making 128 independent bars, or a line graph?

Some options to think about:

You don’t have to use separate components if you don’t want to. There’s nothing to stop you detecting and handling the mouse within a component.

OR … If you were doing a bar, you could have them all owned by a parent object which managed sending the mouseDrag information out. Something like:

void Owner::mouseDrag(const MouseEvent & e) { … update bar under cursor … }

Which solves your problem about mouseEnter and Exit events not hitting the sibling components.

Anyway … maybe that helps … :slight_smile:


#4

Have written many such things over the years, and yes, keep it simple - just draw a path, repaint it all, don’t optimise prematurely because it’s really not very much work to draw a line!


#5

Maybe doing whole repaints of 128 columns is “inefficient” but it might still happen plenty fast enough. Why don’t you try it out? I would personally do it that way first. (I don’t completely buy into the JUCE/Jules philosophy of doing almost everything with components…)


#6

Thanks for the replies.

I want to make a line graph version and a column graph version to use in different situations. Lines are probably easier to draw fast, but columns reflect the underlying discrete data structure better. It is just a lookuptable array - each input value maps to an output value - converting to and from a graphical element involves lots of float/int conversion and coping with rounding errors.

I have now written a column graph version that works, and scales input and output correctly between pixels and actual array slots but it redraws all 210 columns on every pixel movement of the mouse, and when the app is running within xcode it isn’t really fast enough. That’s on a quadcore i5 so it may also not be fast enough on an old core2, which it also needs to work on,even when compiled as an app. There’s some nasty code surrounding rounding errors and the mouse skipping some columns sometimes.

I like the idea of using separate components for each column but triggering redraws from the parent - thanks.

I haven’t tried a line graph yet, but it almost seems like it will be easier.

I am only using one data structure - the array of actual lookup values, and writing and drawing to it - i have considered having a separate data model for the graph and then mapping that to whatever actual data structure is needed for the app. I’m keen to make redraws reflect the actual data the app will use for the mapping so the graph and the reality can’t diverge (something i think i’ve seen in commercial software and it is annoying)


#7

This is very crude, it’d be better rendering to a Path and then filling the path …
but the redraw is quick:

class MainContentComponent   : public Component
{
public:
    //==============================================================================
	MainContentComponent()
		:
		values(128, 0.5f)
	{
		setSize(400, 200);
	}

	void paint(Graphics&g) override
	{
		const bool paintAsPath{ false };

		g.fillAll(Colours::black);
		auto columnWidth = float(getWidth()) / 128.0f;
		auto h = float(getHeight());

		auto x = 0.0f;

		g.setColour(Colours::red);

		for (auto v : values)
		{
			auto y = getYForValue(v);

			g.fillRect(x, y, columnWidth, h - y);

			x += columnWidth;
		}
	}

	void mouseDown(const MouseEvent & e) override
	{
		setValueWithInterpolation(getValueIndexForX(float(e.x)), getValueForY(e.y), true);
		repaint();
	}

	void mouseDrag(const MouseEvent & e) override
	{
		setValueWithInterpolation(getValueIndexForX(float(e.x)), getValueForY(e.y), false);
		repaint();
	}

	void setValueWithInterpolation(int index, float newValue, bool startNewGesture)
	{
		if (startNewGesture)
		{
			lastIndex = index;
			lastValue = newValue;
		}

		auto distance = index - lastIndex;
		auto v = lastValue;

		if (distance == 0)
		{
			values[index] = newValue;
		}
		else
		{
			auto direction = (index - lastIndex > 0) ? 1 : -1;
			auto increment = float(direction) * (newValue - lastValue) / distance;
			auto i = lastIndex;

			do 
			{
				i += direction;
				v += increment;
				jassert(v >= 0.0f && v <= 1.0f);
				values[i] = v;
			} while (i != index);

			lastValue = v;
		}

		lastIndex = index;
	}

	int getValueIndexForX(float x) const
	{
		auto columnWidth = float(getWidth()) / 128.0f;

		return jlimit(0, 128, int(std::floor(x / columnWidth)));
	}

	float getYForValue(float value) const
	{
		auto h = float(getHeight());
		return h - h * value;
	}

	float getValueForY(int y) const
	{
		auto h = float(getHeight());
		return (h - float(y)) / h;
	}

private:
	std::vector<float> values;
	int lastIndex{ 0 };
	float lastValue{ 0.0f };
    //=============================================================================
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainContentComponent)
};

#8

wow thanks! There’s a lot of detail for me to learn from there. If I understand it correctly I think that’s conceptually the same as what I was struggling towards, but with things like the interpolation done properly. It works similarly just faster with better interpolation.

I can’t see yet why it runs faster than mine, it is still redrawing all he columns every drag isn’t it? I have 210 columns not 128 but I think this is faster by more than that.


#9

Are you sure you are deliberately calling repaint() in mouseDrag etc. If not it can look like it’s slow, because the updates are not happening all the time, even though the repaint is actually very fast?


#10

I’m back , sort of, after a dead motherboard and various other things keeping me from working on this. I’ve finished the graph with help from the above example, and also written a line graph version with draggable nodes.

I have one newbie question about the above example: I don’t understand the array declaration

values(128, 0.5f)

is this a juce array or a c++ array? because I don’t see how its valid for either - though obviously i’m missing something since it works…


#11

That’s a C++ standard library vector (heap allocated array with dynamic size) constructed to contain 128 elements each with the value of 0.5f with the values(128,0.5f) call.


#12

thanks, I’m new to c++ (obviously) and never thought of using a vector for that…
I was getting an error when trying to use that in another context, but I’m pretty sure now the error was spurious not the syntax.