My Profile Photo

Deviant Syndrome


coding, multimedia, gamedev, engineering


SuperColliderAU

Part 2. Containerizing in a JUCE-based plugin

Now, that we managed to build the thing. Let’s see how we can actually use it. Of course, you can follow official instructions and sorta “bake” SuperCollider synthdefs into the plugin. There is (was) even a script among the SuperCollider quarks for that. But this approach kinda fell flat for me. Guess why? Boooring GUI? Yes sure, and also no multichannel output, and MIDI support being rudimental as well.

The normal approach here would be just port the project to a modern framework like JUCE, because the there is not that much DSP-specific code in SCAU itself, it kinda just start the SC server, exposes a UDP port and routes the server’s output to plugin output. Unfortunately, this is still too much for my “a pointer to a pointer? WTF?” C++ skills.

So, my descioon was to start the DSP C++ journey of mine with something extremely noob-friendly and actively maintained as JUCE framework. Among the million other cool DSP-related features, it provides a suite to . So, you basically can hve a plugin audio-procesing chain inside a parent “container plugin”. Well, why not try it with our recently build SuperColliderAU then? Let’s see how it goes:

Starting from minimal JUCE template project
  • Create a new plugin using Projucer. It will lay out a C++ audio plugin project structure with PluginProcessor and PluginEditor classes.
  • Using JUCE audio format manager load an instance of SuperColliderAU
    • In processor’s header file we add the defintion of our plugin instance, plugin format manager and OSC sender.
      OSCSender oscSender;
      AudioPluginFormatManager pluginFormatManager;
      std::unique_ptr<AudioPluginInstance> scInstance;
      
    • When in PluginProcessors’ constructor we code something like this:
      pluginFormatManager.addDefaultFormats();
      
      String errorMessage;
      PluginDescription descr;
      
      descr.pluginFormatName = "AudioUnit";
      descr.fileOrIdentifier = "/Library/Audio/Plug-Ins/Components/SuperColliderAU.component";
          
      scInstance = pluginFormatManager.createPluginInstance(descr, 48000, 512, errorMessage);
      // you can then test if the pointer was set, or output and errorMessage otherwise
      
  • SuperColliderAU exposes it’s UDP server port number as a plugin parameter, so we can easily read it and setup an OSC connection using built-in JUCE OSC sender. Caveat: when you load SCAU as a plugin instance, it does not automatically start the server, you need to call prepareForPlay for that. So the following routine better be called in your PluginProcessor’s “prepareToPlay” method.
      scInstance->prepareToPlay(sampleRate, samplesPerBlock);
      scInstance->refreshParameterList();
      Array<AudioProcessorParameter*> params = scInstance->getParameters();
        
      // SCAU has only one parameter and it is the UDP port number
      scPort = std::stoi(params.getFirst()->getText(params.getFirst()->getValue(), 4).toStdString());
      oscSender.connect ("127.0.0.1", scPort);	
    
  • When the host plugin receives MIDI input, translate it to OSC messages
  • Pass audio buffer to be filled by SuperCollderAU during host plugin process loop
      MidiBuffer::Iterator it = MidiBuffer::Iterator(midiMessages);
      MidiMessage msg;
      int s;
      while(it.getNextEvent(msg, s)) {
          if(msg.isNoteOn() && msg.getNoteNumber() == 42) {
              oscSender.send(("/s_new", (String)"testsynth", (int)-1, (int)0, (int)0,
                         (String)"paramName1", (float)*paramValue1,
                         (String)"paramName2", (float)*paramValue2);
          }
      }
    

NOTE: it is implied that you have a SuperCollider Synthdef (testsynth.scsyndef) written to a file and placed it into SCAU resoucses dir, the synthdef is loaded at SCAU internal server startup.

 sudo cp ~/Library/Application\ Support/SuperCollider/synthdefs/testsynth.scsyndef /Library//Library/Audio/Plug-Ins/Components/SuperColliderAU.component/Contents/Resources/synthdefs

Also, if your synthdef is using SuperCollider extensions library, those complied SC-classes should be copied there as well:

wi11iew0nka@chocolatefactory > ls /Library/Audio/Plug-Ins/Components/SuperColliderAU.component/Contents/Resources

BinaryOpUGens.scx         FFT_UGens.scx             MembraneUGens             ReverbUGens.scx
ChaosUGens.scx            FilterUGens.scx           MulAddUGens.scx           TestUGens.scx
DWGUGens                  GendynUGens.scx           NoiseUGens.scx            TriggerUGens.scx
DelayUGens.scx            GrainUGens.scx            OscUGens.scx              UIUGens.scx
DemandUGens.scx           IOUGens.scx               PV_ThirdParty.scx         UnaryOpUGens.scx
DiskIO_UGens.scx          LFUGens.scx               PanUGens.scx              UnpackFFTUGens.scx
DynNoiseUGens.scx         ML_UGens.scx              PhysicalModelingUGens.scx