Juce and file saving


#1

I’m a novice programmer, trying to build a piece of sequencing software and learning C++ and Juce as I go. Right now, I am trying to build a rudimentary system which will read and write the variables within a custom class to a file (hopefully one day this will become the saving and loading system for the program). I have a sense of how to do this using data streams in the STL library. On the other hand, I’m almost certainly going to do a bad job of it, and this is a problem that must have been solved thousands of times before.

Does Juce contain any tools which automate the file saving process? How is the problem usually solved? I’m not so much asking how exactly to do it, and more asking where I should invest time: into learning Juce modules or C++ and STL basics.

Sorry if this topic turns out to be less about Juce and more about C++.


#2

That is a really broad question with no yes/no answer. It really depends, what you want to save or load, and what structure you read it into (this process is called serialisation, btw.). So if you want to read something and construct a JUCE object from it, it makes sense to use the juce::File and juce::FileInputStream.
For Audio there are the AudioFormat classes available, and same for ImageFileFormat.
For hierarchical data that you produce yourself, the ValueTree is a good pick, since it allows also callbacks when it is manipulated.

If however the constructed object is completely independent from juce, and there is no ready reader/parser available, it would be considered good practice to avoid additional dependencies.

But another angle to look at this is, what technology you feel most comfortable with…


#3

Since C++ does not have reflection (the capability to know at runtime how the classes are structured, what data members they have etc), no automated solution as such is possible for arbitrary data. Raw streams are not a great solution because it becomes difficult to future proof that for extensibility. JUCE has the ValueTree class that can be helpful but still requires you to manually do the saving and loading code for your data, unless you structure your whole application to use the ValueTrees.


#4

…which is a good idea if you want undo/redo functionality for your data model… it is very tedious and error prone to do that by hand, the UndoManager is a great extension to the ValueTree


#5

Thanks for your responses. I am aware that my questions are broad, but the answers have already been very helpful. I’ve read up on ValueTree and it seems like it is exactly the right thing for me to be using, as it will help with file saving and so much more.

I have read through the tutorial on ValueTrees, but there are still some gaps in my understanding which are preventing me from being able to implement what I’ve learned. Am I right in saying that to utilize ValueTrees, I would start by creating my own custom class and then inheriting from the ValueTree class?


#6

No, inheriting ValueTrees is not useful. It’s in fact marked as “final” so you couldn’t inherit anyway.

There are many ways the ValueTrees can be used, but you will probably want some “master” ValueTree instance in some suitable class that always has all the state you need.


#7

So the ValueTree is a member of a custom class?


#8

Well, all your classes are “custom” classes, otherwise your code wouldn’t really be doing much. And yes, the ValueTree should be a member in some suitable class. What is “suitable” depends on your code structure.


#9

#10

My 2 cents, especially if you’re a novice: the setup/boilerplate code to get this working seems rather complex/large. You’re maybe not having the same problems you need to solve. If you’re just writing a simple plugin that e.g. represents a simple EQ, you might not need all this and can simply store your 10-20 parameters as an array of floats and be done with it.

If you have a complex data-model, with a deeply nested hierarchy etc. then it’s definitely a good way to go.

Learn to walk before you learn how to run.


#11

But even if the main data model is just plain variables and arrays, the ValueTrees can be used for profit during the serialization and deserialization steps…They are a very handy way to deal with data where future expansion may be needed.


#12

Horses for courses. If we’re talking, just as an example, about four EQ bands and an input/output gain, there is little that could be extended.

I would start out with a simple float array, don’t do anything special for serialization (just store them as is) and be done with it.

Then, when creating more complex plugins, add the ValueTree stuff to it. It’s a complex beast and can overwhelm any novice (and pro) easily. Especially when adding constrainers etc. it becomes complex quick.


#13

There’s not really anything complex about using the ValueTrees only during the serialization and deserialization steps. (Literally just using them as local variables when saving and loading the data.) However, if the whole application or plugin is designed around them, sure, it’s complete overkill for anything simple.


#14

I should have paid more attention to the OP needs. He mentioned a sequencer (so DAW?). That is a monumental undertaking and requires complex solutions. ValueTree seems perfect for that.


#15

@reFX and @Xenakios, your discussion reads like the one playing in my head right now. On the one hand, trying to run before you can walk is foolish. On the other hand, I’m determined to do things properly and make the most out of the existing tools and techniques (which is why I’m researching Juce).

I’m new to C++ but not to software design. I spent five years building this sequencer in PD, during which time I learned a lot of lessons about the importance of structure and long term thinking. But being a beginner, I had to scrap everything and start over on the project several times.

C++ is a lot harder than PD, but I’m still trying to make the most of the experience I have. My hope is that by investing my time into tools such as ValueTrees, it will help me write code that will hold its weight, so that I won’t have to scrap the whole thing and start again when it gets to a certain level of complexity. But maybe it’s necessary to write bad code and reinvent a thousand wheels before you can do something like this properly?

My questions are getting very broad again, but I’m writing them because your feedback has been genuinely helpful so far (and this place is a lot friendlier than stack exchange!)


#16

OT: take that, @juce team always saying that this forum is unwelcoming for new users :grin:


#17

The more I learn about ValueTrees, the more impressed I am with them, and the more convinced that they are perfectly suited for my needs. I have read the documentation through several times and I think I understand most of it, but I want to ask for a few points of clarification before I dive in.

  1. Should a ValueTree property replace standard member variables of a class, or should it duplicate them? Ie. if I had a getVolume() function in a class which stores the property volume in a ValueTree, should it use A: return myTree.getProperty(volume); or B: return volume; where float volume is defined as a private member variable? (I’m pretty sure that it’s A, and that’s what all the fuss about Identifiers being fast is about, but I want to be sure before I proceed).
  2. Are there cases where it would be acceptable / make sense to use ValueTrees instead of Array or std::vector, and then access it using Identifiers instead of int indexing?
  3. On the matter of indexing, when does it make sense to retrieve ValueTree properties via int rather than by Identifier? The documentation stresses ValueTree properties are stored in an arbitrary order, so I find it a bit confusing.
  4. I’d imagine that things can get quite confusing within complex ValueTree structures, with hundreds of grandchildren, great-grandchildren, etc. Are there any tools in JUCE that would help me to create a register to keep track of this? For instance, if one node wants to access a particular cousin node, how does it know which Identifier to pick? It is up to me to build my own directory structure, or has this problem been solved elsewhere in JUCE? (I’m sorry that this question is vague, but at this point I’m really only looking direction rather than solutions.)

#18

I think that was a misunderstanding: you are right in your understanding, that you cannot rely on the order of the properties. You can iterate over them, but you have to refer to the property name to make sense out of it.

The integer comparison probably refers to the Identifier: when an Identifier is constructed, it is stored as an index in a StringPool, where all Identifiers are stored. So once two Identifiers are constructed, they can be compared as a simple int comparison. However, to construct an Identifier from a literal, it needs to be looked up in the StringPool, which results in a string comparison.

I can’t see, how that would be an improvement. But on the other hand, ValueTree child nodes are simply stored in an Array, so on that level, there shouldn’t be much difference.

You shouldn’t mix accessing information directly in the tree or from the object it mirrors in your object hierarchy. It is best practice to not publish the Identifiers outside the object, that encapsulates the information of that leaf.
A good approach is, to have a namespace IDs containing the Identifiers in the cpp, so it’s only available in the implementation. It’s the up to you to have methods to access the value.

There is probably some personal preference involved, but take into account, that the ValueTree by itself is not thread safe. So my personal rule of thumb is, to keep the ValueTree in the message thread domain, and since that is not time critical, access the values in the ValueTree directly. That saves the overhead of synchronising members and ValueTree information.
On the audio thread, you should have measures in place for thread safety, so IMHO it makes sense to wrap a member in std::atomic, that you synchronise from the callbacks (ValueTree::Listener).

But I am interested in alternatives for that part as well…


#19

I was referring to ValueTree::getChild (int index). But I see now that this is for retrieving a child tree, not a property. I think I understand that this feature is basically used for introspection, whereas regular access should be done with getProperty() and getChild().

I’m finding it hard to express this question clearly, but really I’m just asking to make sure that I’m not holding the wrong end of the stick. My initial idea was that I would have to embed an Array of objects inside the ValueTree, but from what I can see now, this would be foolish/pointless. I can just add multiple children to the same ValueTree, and then access them using getChild(). Is this right?

Where can I learn about threading in JUCE? This is something I haven’t paid much attention to. My application is a sequencer and will contain no DSP at all–it will send MIDI / OSC / VST messages to other applications. Is it too naive to think that I can ignore the problem of threading altogether?


#20

Ah, I see. So I overshot with my answer… Yes indeed, children of a ValueTree are itself ValueTrees, and they are supposed to stay in order (but if a child is removed, the index can still change, lie in all arrays). And it makes sense to use that feature, and I am not aware of a penalty here.

Yes, see above.
I don’t claim to have the only truth, but that makes sense to me, to use simply children ValueTree nodes as array items.

The knowledge is spread unfortunately.
There are always at least 2 threads working in audio applications and in plugins.
For normal use cases in 90% you are fine considering that AudioProcessor::processBuffer(), AudioSource::getNextAudioBlock() and subsequently Synthesiser::renderNextBlock() are called from the audio thread and need special attention.
As soon as Thread, TimeSliceClient and ThreadPoolJob are involved, even more caution is needed.

Always checking “what is calling” is a good start.