Thanks in advance for any ideas/comments. I have a little timecode library/module not written in JUCE. If I want to use it in a JUCE project which stores its data in Value Trees, it seems I either convert every timecode instance to and from a Value Tree when I want to use functions from my timecode module, or I rewrite the module to use Value Trees.
Imagining I would do the latter, it seems I would have to write a load of free functions that take Value Trees as arguments, rather than class methods. Am I about to learn a strange form of C?
I am a big advocate of VT’s and I would love to offer advice, but I don’t understand your question. Having said that, let me describe my usage of VT’s. I create wrapper classes that hide the ValueTree behind setters/getters/callbacks. Those ValueTree’s are passed around the code, and clients of them use an instance of the wrapper to make use of them. If you created something similar your library/module can use it to set data, and your client can receive the callbacks and use getters. I hope this helps conceptualy.
Here is a stripped down example of how that class might look:
class MyThing : public ValueTreeWrapper
MyThing (juce::ValueTree vt);
void wrap (juce::ValueTree vt);
void setAThing (int thingValue);
void setAnotherThing (bool anotherThingValue);
int getAThing ();
bool getAnotherThing ();
std::function<void(int thingValue)> onAThingChanged;
std::function<void(bool anotherThingValue)> onAnotherThingChanged;
Thank you. So to make use of this wrapping technique, I would build a ‘bridge’ class, which contains a Value Tree and contains ‘mirror’ functions which pass on the calls from other classes which understand the MyThing/ValueTreeWrapper interface. In this case, I think that would require having two versions of each timecode - one as a TimeCode object (from the module) and one as a MyThing. Either that, or MyThing would create temporary TimeCode objects to do calculations and store the answer as a MyThing.
When I have used Value Trees in the past, I have only had one ‘ValueTreeWrapper’, which contains one large tree with many subtrees. This might contain ‘getAThing()s’ which can dig down into the tree and a few different 'onAThingChanged’s. Do you find it is better to use multiple small wrappers?
Value Trees are very versatile and it seems it could be useful to have a few ‘in this situation, do this, but in this situation, consider that’ guidelines somewhere.
Thanks for the chance for a bit of brainstorming…
Yes, how you describe the bridge object is correct. In another project of mine where I have a node system, I use the VT data to instantiate the actual back end nodes themselve. ie. the UI adds/removes nodes from the VT, and the backend configures itself accordingly.
Yes, multiple small wrappers. while it’s not the right term, I often say the wrappers are ‘domain specific’. In my applications I pass a single root ValueTree object around. At startup the ‘owners’ of the specific ValueTree’s create and attach themselves to the Root, and subsequent clients pick what they need from the Root. The Root actually has two inital branches, one for ‘persistent data’ and one for ‘runtime data’. The persistent branch is connected to my ValueTreeFile class, which will schedule a write to the backing file whenever anything about the VT changes. The scheduling of the write is to help coalesce rapidly changing data, for example if you have data that is changed by a slider. If you want any data to persist, you simply have to put it’s VT on the persistent tree.
While I have been talking about the idea, and using it in my personal and professional work, I have only recently published code with the Wrapper base class and real world usage. As well, I am going to give a talk about it on an upcoming Audio Programmer podcast. Here is the published code base.
Thank you! I’ll give it a look.