Audio Plugin & Build Dynamic Lib


#1

Hey all,

I’m working on a simple audio plugin, and I’ve built a couple small DSP modules that I’d like to test.

I found a couple suggestions of testing simple modules like this with Python & Numpy.
https://stackoverflow.com/questions/145270/calling-c-c-from-python
https://docs.python.org/2/library/ctypes.html

To me that looks really straightforward and enables me to test with much more speed and flexibility than if I were to do it in C++ (I’m much more experienced with Python than C++).

So my question is, by default my project is set up to build a VST, AU, and Shared Code target in XCode. The Shared Code target produces a static library… is there a straightforward way of outputting a dynamic library instead, or alongside, that static library? I tried building it with g++ manually and got fairly far but got hung up linking against all of the juce modules that my code depends on. It seems like there should be an easy way to configure XCode to spit this out for me.

Has anyone done anything like this? Really appreciate your help.

Thanks!


#2

I am pretty sure that in order to interface with Python, you will need to make a plain C interface for your code first. It most likely is not going to be useful to just force XCode to spit out a generic dylib out of your plugin code. (Now of course for example VST2 plugins are already dylibs with a C interface…But you’d need to figure out how to utilize the AEffect struct in Python…)


#3

Right, I had intended to follow the example in that Stack Overflow post.

class Foo {
public:
    void bar(){
        std::cout << "Hello" << std::endl;
    }
};
#ifdef BUILD_DYLIB
extern "C" {
    Foo* Foo_new(){ return new Foo(); }
    void Foo_bar(Foo* foo){ foo->bar(); }
}
#endif

Where Foo is my DSP class, and the available methods are wrapped/exposed in the extern "C" block when building the dynamic library.


#4

Yeah, that looks like the way to go. Don’t forget a Foo_destroy function too! :wink:


#5

You shouldn’t do that, as calling the Python interpreter from the audio loop may take too long.
If you have a Python module that makes C/C++ calls like the ones from your link, just use these calls inside your plugin (with JUCE) instead of going through the interpreter.
The overhead of the interpreter is far too big compared to the DSP part (it’s several orders of magnitude). It’s fine for testing, not for production (at least that’s my position and what I’m doing for my plugins).


#6

Ah, good call! :smiley: Thanks.

I’m still confused though about how to get XCode to build the shared code as a dylib instead of as a static library. I configured the Mach-O type to Dynamic Library and now my build fails with a lot of undefined symbol errors.


#7

@Matthieu_Brucher I’m not trying to call the python interpreter from the audio loop, I want to build my few dsp classes into a dylib and then use Python’s FFI to test those dsp classes.

For example, from Python, instantiate a new MyDSP class instance, call its prepareToPlay, then push a few buffers into it and graph the output for testing purposes.

This is strictly for verifying and graphing the functionality of the DSP classes that I’m writing and building into my plugin.


#8

Oh OK, sorry. That’s what I’m also doing in ATK. But my DSP classes can be output as a static libraries (for JUCE) or shared libraries.
You shouldn’t change the shared code version static library. You would need to change the bundle building process, and it’s a pain. So just compile your DSP code as a shared library when you want it, and static library for a plugin.


#9

No problem! :slight_smile:

Ok, that sounds good. Do you have any tips on compiling as a shared library? I got this far:

clang++ -c -I/path/to/JUCE/modules -DRELEASE -std=c++14 delaytest.cpp -o delaytest.o

That worked fine, the next steps were failing trying to link to the JUCE code that my DSP depends upon.

32884  clang++ -shared -o delaytest.so delaytest.o 
32885  clang++ -Wl,undefined -shared -o delaytest.so delaytest.o 
32886  clang++ -undefined -shared -o delaytest.so delaytest.o 

Those are all failed attempts, I’m not quite sure what to go from there. The error output was:

Undefined symbols for architecture x86_64:
  "juce::logAssertion(char const*, int)", referenced from:
      ci::DelayLine::prepareToPlay(double, int) in delaytest.o
      juce::LeakedObjectDetector<juce::AudioBuffer<float> >::LeakCounter::~LeakCounter() in delaytest.o
      juce::LeakedObjectDetector<ci::DelayLine>::LeakCounter::~LeakCounter() in delaytest.o
      juce::AudioBuffer<float>::setSize(int, int, bool, bool, bool) in delaytest.o
  "juce::FloatVectorOperations::copy(float*, float const*, int)", referenced from:
      juce::AudioBuffer<float>::setSize(int, int, bool, bool, bool) in delaytest.o

etc…truncated for brevity.


#10

Maybe you could try creating a separate shared library project with Projucer?


#11

@Xenakios I did try that, though perhaps didn’t give it enough effort. But I was struggling to have two Projucer projects share the same directory without overwriting one another, or overwriting source & library code, on saves. I’d happily take advice on doing it that way too though!


#12

delaytest.o contains only the symbols that are defined in delaytest.cpp. You also need to link the symbols from juce_core.cpp, juce_events.cpp, juce_audio_basics.cpp, … This means that you have to compile these files to produce the object files (.o) and then link all the object files together in the shared library.


#13

That’s indeed a weakness of Projucer. I can offer to help you switch to using CMake instead of Projucer to create the Xcode project. With CMake it’s totally possible to build the C++ code only once and then produce a static library and a shared library. Don’t hesitate to PM me if you’re interested.


#14

This sounds like just a few cpp files - it seems silly to even consider complicating things with shared libraries or cmake when most likely all you need to do is throw all these DSP cpp files into your juce project and build it?


#15

@McMartin I might take you up on that CMake help! I’m currently trying my hand at a standard makefile because I’d expect this particular task to not be that hard… though it’s still giving me trouble.

@jules not sure I follow you; I don’t have any issue throwing these cpp files in my juce project and building it– I’m already doing that to build a VST & AU target. My goal is to build an additional dynamic library target for testing, and that’s where CMake might come into play.


#16

So I managed to get a working build with XCode by adding a new target (a duplicate of the Shared Code target) and tweaking it to output a .so. I even managed to import it with ctypes in Python and make a call.

I’d call this about 99% of the way there, but now I’m in a situation where any Projucer saves will overwrite the configuration I made for the new target. Is there any way around that?

Thanks!

EDIT: I tried the “Keep custom XCode schemes” option in the XCode exporter configuration (awesome option btw), but it did not preserve my custom target.


#17

How about a separate projucer project that produces a shared library (which I think is what Jules was suggesting), but instead of including the relevant cpp files to match the other project, just add a search path that will find the static library built by your existing projucer project, and add the library. You could have the existing project trigger a build of the shared library in the post build step of your current project.