JUCE module for analogue modelling!

Hey Jucers!!

I’ve been working a JUCE module to allow easy integration of analogue modelling into plugin/apps. It uses a white-box analogue modelling theory called Nodal DK-Method (references in the repo).

The project is still in initial phase, but I have big plans for it! :slight_smile:

Feedback, requests and contributions are encouraged!!
Cheers!

13 Likes

Looks like a very interesting project! Nice repo structure as well :wink:

Forgive my DSP ignorance but what kind of audio effects can this be used to create?
Are parameters adjustable in real-time?
Have you got any performance benchmarks? (It’s my understanding that one of the most difficult aspects of analog modelling is getting it to run fast enough…)

Looking forward to giving it a run through!

1 Like

Nice inquiries!

The possibilities are endless, you could use it to emulate guitar stomp-boxes (diode clippers), tube pre-amps (check the examples), implement vintage filters, etc…
One thing that has been particularly fun to me, is to incorporate small pieces of analog circuits in your own DSP algorithms, to give it some analog feel.

When your emulating a analog circuit, the usual variable parameters are potentiometers, and at the moment of writing it doesn’t support that, but I’m working on it.

There’s no performance benchmarks, but according to my tests, it isn’t good enough for production yet. This will be solved using lookup tables for the non-linear system solver, which is also being taken care of at the moment.

As mentioned, the project is still in its infancy, but hold tight that cool things are coming! :slight_smile:

1 Like

Very cool :+1:

Very interesting, looks like something I need to have a more detailed read on when I have some time - also the theory behind it, never heard of Nodal DK.

But just for curiosity, I took a look at the example plugin code - is the idea of the steps done in the Processors constructor to programatically describe the netlist with the last two/three integer parameters passed to those makeComponent functions? So the idea is to describe your analogue netlist with the model and then let this model process samples?

Exactly!! You describe the netlist of the circuit, and the model will emulate the audio going thru it!

Nodal DK-Method allows you to extract a non-linear differential equations system right out of the circuit, then there’s some crazy algebra to solve it for the audio input supplied.

About the Nodal DK-Method, there’s David Yeh PhD thesis. There’s a chapter dedicated to it, a really nice read if you’re into that kind of stuff:

5 Likes

Interesting stuff!

1 Like

Analog modeling can do anything that has a schematic somewhere.
Seems like there are more an more projects on things like this (I also started a blog series on analog modeling, but for performance in the end, paper at ADC pending: http://blog.audio-tk.com/2018/05/01/analog-modelling-a-prototype-generic-modeller-in-python/)

3 Likes

Fantastic! I’ve just been playing about with this and it’s really great. Is it a direct C++/JUCE port of jardamacak’s MATLAB NodalDKFramework? Looks strikingly similar, but good job on the convert if so. One major problem so far for me though: It isn’t at all very friendly with sample rate changes! The performance isn’t too bad at all considering the Armadillo / BLAS matrix stuff, but anything higher than 48kHz and it outputs garbage. Windows 10 here, linking dynamically as an ASIO driven standalone app, and it’s all good other than that. Now, if we could get the sample rates working properly, a few more component models, and have a MIT styled license instead of GPL, this would soon become my favourite JUCE module of all time. Following and looking forward to seeing this develop further. :+1:

3 Likes

Hey man! Awesome blog series!! It’ll definitely be very useful for me, thanks for sharing!
Yeah, performance still an issue ATM, this should be solved when I finish implementing lookup tables for the non-linear solver.

Thanks a lot for the comments and support, this project will definitely go a lot further!
Yea, performance is still far from optimal, as mentioned, this will be addressed by implementing lookup tables.
More components are coming!! Diodes, transistors and OPA should be ready in the next two weeks!

I do not consider the code a direct port, although it is heavily based on Macak’s MATLAB code, it has a some key diferences, specially on the non-linear solver part. As new features get implemented, the differences should grow as well.

Regarding the licensing, I have to abide to Macak’s GPLv3 licence.
And more on that, I believe the most important purpose of an open-source project is to transmit knowledge, which is being done here.
But if someone is interested on using the code “as is” on their product, then I believe Macak and I deserve some rewarding for our work :wink:

1 Like

Par of the issue is that you have a dynamic code. Also you have lots of checks that could be done more efficiently (for instance use switch for populating components), or in a more automated part (I would do the bulk of the matrix building part via metatemplate multidispatches. But even then, you will be constrained by the dynamic part of the code :confused:

Armadillo is already heavily templated, I don’t want to ran the risk of incurring in obscure and bizarre preprocessor bugs. Also, I’m not that good with templates, please feel free to leave any notes :slight_smile:
Readability is also taken a lot into consideration, I will avoid all kinds of “gotcha” premature optimisations.
I’m pretty sure that with time I’ll be able to get pretty fast code and still keep it very understandable.

1 Like

Preprocessor and templated code have nothing in common, but I do agree about readability.

1 Like

You see what I mean, I’m not good with templates :joy:, lets leave it aside for now hehehe

Looks interesting! Just a stupid question, maybe: how do I specify the voltage of the power input in the example circuit? Couldn’t find that anywhere… or is it hidden in the triode?

Nope, power rails should be treated as regular inputs, then on the signal processing code you should supply them.

So, you just add the power supply with the ComponentFactory::makeInput() function, providing the appropriate nodes locations. Then, the processing function will take an dsp::AudioBlock with as many channels as inputs you added to the circuit. So if you added the signal input, and the the power rail input, it will take a buffer with 2 channels, in the declared order.

Check the example plugin in the repo :slight_smile:

1 Like

Ah, makes sense… thanks!

Hi
Have been experimenting with this library, is it possible to chain “models” rather than having one model fir a complete circuit. How would 2 models connect ( example would be the triode model chained to form complete preamp…
Thanks

1 Like

Hello,
More than one triode does not work in this module. This is due to a bug in the Model.cpp file.
Line 104:

    for (int nl = 0; nl < nonlinear.size();)
    {
        auto& comp = nonlinear.getReference (nl);
        auto ports = comp.numOfPorts;
        
        if (ports == 1)
        {
            comp.model (v.row (nl), iComp1, jComp1);
            i.submat (nl, 0, nl, 0) = iComp1;
            j.submat (nl, nl, nl, nl) = jComp1;
        }
        if (ports == 2)
        {
            comp.model (v.rows (nl, nl + 1), iComp2, jComp2);
            i.submat (nl, 0, nl + 1, 0) = iComp2;
            j.submat (nl, nl, nl + 1, nl + 1) = jComp2;
        }

        nl += ports;
    }

Need to be replaced with:

    int pi = 0;
    for (int nl = 0; nl < nonlinear.size(); nl++)
    {
        auto& comp = nonlinear.getReference (nl);
        auto ports = comp.numOfPorts;

        if (ports == 1)
        {
            comp.model (v.row (pi), iComp1, jComp1);
            i.submat (pi, 0, pi, 0) = iComp1;
            j.submat (pi, pi, pi, pi) = jComp1;
        }
        if (ports == 2)
        {
            comp.model (v.rows (pi, pi + 1), iComp2, jComp2);
            i.submat (pi, 0, pi + 1, 0) = iComp2;
            j.submat (pi, pi, pi + 1, pi + 1) = jComp2;
        }

        pi += ports;
    }

Line 215:

    for (int i = 0; i < nonlinear.size(); ++i)
    {
        Nn (i, nonlinear.getReference(i).nodes[0]) = 1.0;
        Nn (i, nonlinear.getReference(i).nodes[1]) = -1.0;

        if (nonlinear.getReference(i).numOfPorts == 2)
        {
            Nn (i + 1, nonlinear.getReference(i).nodes[2]) = 1.0;
            Nn (i + 1, nonlinear.getReference(i).nodes[3]) = -1.0;
        }
    }

Need to be replaced with:

    int pi = 0;
    for (int i = 0; i < nonlinear.size(); ++i)
    {
        Nn (pi, nonlinear.getReference(i).nodes[0]) = 1.0;
        Nn (pi, nonlinear.getReference(i).nodes[1]) = -1.0;

        if (nonlinear.getReference(i).numOfPorts == 2)
        {
            pi++;
            Nn (pi, nonlinear.getReference(i).nodes[2]) = 1.0;
            Nn (pi, nonlinear.getReference(i).nodes[3]) = -1.0;
        }

        pi++;
    }

Corrected version in the archive.
juce_dkmethod bug fixed by NaLex.zip (7.5 KB)

1 Like