Hi Jules
I like many others am building software controlled dynamically by ValueTrees. I assume it will not be long before AI is integrated into audio software. One day I would like my software to set up an audio mix based on track names and FFT content.
It appears like you are keeping AI in mind as you write Soul. I hope Soul will eventually replace C++. It does not appear like C++ will hold up under the AI revolution. Most of my programming is trying to break outside of the safeguards of C++ scope protection. It’s like, I am going to lock you all in prison and those of you are innocent can dig escape tunnels, here is a spoon or a lambda. If a parent creates a child in it’s constructor and it only controls this child, then the child should really have full access to the parent scope upwards, until this is no longer the case. In this case, the child really is the parent, but if they were all in one class, that would be one crazy big class.
Love what I am hearing about Soul. Very exciting.
Any advice on how should we be writing our plug-ins to easily migrate to Soul. I am currently using AudioProcessorGraphs only connected to the UI by parameters.


lol what?

        class A
                One should be able to access B here if all constructing and deconstructing of A happens only in the constructor and deconstructor of B and this pattern should repeat itself, to all depth, while all the constructors and deconstructors are happening simultaneous with each other.
        class B
            B():a() { };
            class A a;


you’re talking about this:

and what you’re proposing does not sound like a good idea. it defeats the whole idea of limiting access to member variables which Object-Oriented Programming centers around.


Yes, I am using friend classes, but I am suggesting sometimes a is inherently linked to B. I made the example more specific. It would be great if the compiler could make an exception for this sort of relationship, where there are no pointers shared, etc.
Anyway sorry about the complaint.
Thank you


Try to stay on topic, folks!

Using graphs and avoiding dependencies between the processing code and the rest of your app are both very good disciplines. And maybe writing the processing nodes as sample-by-sample rather than in terms of buffers (the way much of the juce::dsp classes are designed) is another good practice for the long-term.




I guess programs distributed as SOUL IR will be later on usable with other programs distributed as SOUL IR. That is to be combinable with other already compiled ones, depending of the new connection topology of the resulting super program.

How is the SOUL IR format going to represent this ? Will the DAG composed of the processors be clearly exposed and accessible?

Assuming a DAG is exposed, does each processor exposes any notion of vector size? Or is this kind of thing to be decided later on in the compilation and deployment steps?

This kind of questions may be of great importance for other front-end languages (beside the SOUL language itself) going to produce SOUL IR.


Yes - it preserves the DAG. We’re holding back on releasing more info about the IR while we’re still working on it, but will make it available when we can!


The point is that if you want other front-end languages to be highly operable with the SOUL IR and runtime, they may have to change a bit the way they decide to generate output code. Like in Faust we currently decide at the compilation stage (giving appropriate compilation parameters) if the output DSP is going to be a big scalar loop, or a DAG of simpler connected sub loops with a given vector size.
If I understand correctly the SOUL runtime model, then this kind of decision is possible better taken in the runtime itself, and the front-end language would better have to generate the DAG is a more abstract form.


Yes, that’s a good point. Ideally we’d like our back-end to have as much DAG info as possible so it can make those choices itself, but if a front-end language just emits fewer big nodes then it should still work, it might just not be optimal.


Yep, non optimal is the point, so if the internal format is (or can be…) more abstract in some sense, it may help in this issue.
For instance in Faust we could decide to generate the DAG (which basically describe the topology and data flow), but do not decide vector size of each processor, and let the runtime choose later on, when other SOUL processors coming from somewhere else are combined with the Faust generated ones.
So less decisions to take at the front-end level, versus more control in the runtime which has more knowledge.


Or even find out a way to generate a SOUL graph (instead of a big single SOUL processor) by plugin SOUL IR code generation at a earlier stage in the Faust compiler pipeline, so keeping more structure in some sense.


How does mixing between boolean and int types actually works ? This is not so clear in the syntax guide:
In C/C++, mixing expressions with actual “bool” or “int” types in logical or bitwise constructs can be directly written without having to explicitly cast the sub-expressions.
Is this possible also in SOUL ? Code examples in the syntax guide would help :wink:


A task on our to-do-list is to write a proper language definition document which contains all the detailed rules about this kind of thing - the guide is just a friendly high-level introduction.

But I think our general policy when it comes to mixing types and silent casting is to be stricter than C++ but not make it so fussy that it becomes distracting to write.


OK :laughing:, distracting to write may not be a problem for an automatic code generator, but without more proper language definition or concrete code examples, it is difficult right now to move on in the Faust => SOUL backend on this specific issue.


Separating the processing is not only a crucial part of AAX, but has also been available in VST3 from start. If JUCE had a proper abstraction for UI+controller vs. processing communication which can be channeled through the host, this can be used for SOUL, AAX and VST3 alike.
If the host is not involved (e.g. Audio Unit or VST3 when limited to SingleComponentEffect) the abstraction can be implemented by directly writing to memory, keeping the performance unaffected.
As is the case both with AAX and VST3, I suggest the abstraction should allow for both sending small packets of primitive data with timestamp to/from the realtime thread (in VST3, this would need to be mapped to ProcessData::input/outputParameterChanges), and for sending arbitrary larger chunks of data to/from non-realtime threads (VST3 offers IConnectionPoint with IMessage for this).
Not yet having seen the stream API Jules mentiones, I don’t know if the abstraction can map to that API as well, but I would assume so since both are common use cases. Executed properly, such an abstraction would greatly improve JUCE’s usability with both existing and upcoming technology. :slight_smile:


Is SOUL advance() mechanism actually using or linked in some sense whit the LLVM coroutine model ?


No, we implemented our own way of achieving the same thing. It didn’t really make sense in our case to rely on something else to handle the stack saving/loading as we already have a mechanism and a place to put variables that need to persist over calls. And we also wanted to avoid a dependency on LLVM for this


What? I don’t want AI in my DSP code path, I don’t think you know what the generic “AI” means anyway, especially with regards to audio.