Call Us: 01202 597180
We're open: Monday - Saturday 10am - 6pm
Sundays and Bank Holidays 10am - 4pm.

Creating Custom Oscillators & Effects for the Korg Prologue

Posted on May 29, 2018 by Richard Bottom There have been 0 comments

The Prologue is Korg’s latest polyphonic synth, available in 8- and 16-voice models. It has analogue oscillators and filters, and also a digital ‘multi-engine’ oscillator and digital effects.  It’s these last two aspects that Korg has made open-source, so programmers can get amongst the code and write their own. Sounds easy! It is… if you know C/C++ pretty well and are OK with having no technical support from Korg!

Korg’s decision to allow third party coding is a great move, and one that opens up some interesting possibilities to create some unique sounds. Admittedly, the likelihood of an individual having the skills to code for this kind of embedded system and also owning a Prologue is quite slim, but the thinking behind it must be that a handful of developers will make their oscillators available to the public, either free or as paid for add-ons. With this in mind, Korg have also announced a Prologue SDK Development board that is a PCB with one voice chip of the kind used in the Prologue itself and the necessary inputs/outputs.

The SDK (Software Development Kit) is free to download (https://github.com/korginc/logue-sdk) and is a collection of files that enable the coder to access the multi-engine and effects via an API (Application Programming Interface). Some additional files are required to get the system building correctly (depending on what OS platform is being used), the details of which are found in the various ReadMe’s in the SDK package. Once the build environment is up and running, creating the file that can be uploaded to the Prologue (or dev board) is pretty straight forward using the ‘make’ command in a terminal window. This command looks for a ‘makefile’ file in the current folder and uses that to produce the final zipped file (a .prlgunit file) that can be uploaded to the Prologue or dev board via the librarian software. The main limitation here is that the code must fit into 32kb – yes a whole 32kb!!

For the multi-engine oscillator, Korg have given access to 8 control parameters in total. Six of these are found in the edit menu, and the ‘Shape’ knob is the one control that is immediately available on the main synth panel. This control can also have a secondary function when holding the ‘shift’ key. There are 16 slots that can be filled with custom oscillators, and these can be mixed with the standard oscillators.

The digital effects section can be coded for other modulation and reverb/delay-based effects, and here you also have access to 2mb of sample memory. The Speed and Depth controls can be used to control the effects, and there are 16 user slots that can be filled with custom code.

 

So, we let our marketing man Rich loose with the SDK to see what he could make of it…

Getting the build environment up and running on a PC was relatively straight forward once I found the ReadMe’s that gave instructions for what additional software was required. This included a ZIP utility (that creates the final prlgunit file) that at first caused the build to fail – this was fixed by finding another version of the ZIP software.

The chip used in the Prologue is an ARM Cortex M-4 chip (with an FPU unit) and some extra files are needed to compile the code for it including CMSIS (Cortex Microcontroller Software Interface Standard) and the GNU Arm Embedded Toolchain.

Once the environment is set up correctly, editing the files can be done in any text editor (with C/C++ highlighting ideally!). I used Notepad++ to view and edit the files, and put them under source control using git/sourcetree/bitbucket – this is great as you can go back and forward in time and merge code branches back to the master branch until the project is done and dusted.

Now the fun bit – how does the API work?

The only way to find out what functions were available was to look through the source files. Some had notes to say what the function did, others didn’t so would have to be worked out. Of course, different C/C++ libraries could be used in addition to the API (assuming the end product compiles to 32kb or less).

I soon realised how the oscillator was structured, with the main sections to edit being the OSC_CYCLE function (where the waveform is generated) and the OSC_PARAM function that determines what the controls will do. OSC_INIT initialises the oscillator (sets the starting point of the waveform and resets any other values that you may want to reset).

C++ ‘classes’ are not used, instead the data structure is contained within a ‘struct’ which makes things a little simpler. The struct contains all the variables that are to be used for the oscillator.

So we're all dressed up, but right now there's no way to load up any custom oscillator directly to the Prologue - not until some time in June when the firmware is updated. The dev board works, but I don't have my hands on one! When the firmware is updated, we'll be uploading some custom oscillators and effects, so stay tuned!

Now, we did say you needed to know C/C++, here is the code for a basic sinewave oscillator..

 

/*

 * File: sine.cpp

 * Naive sine oscillator test

 */

#include "userosc.h"

typedef struct State {

  float w0;

  float phase;

  float drive;

  float dist;

  float lfo, lfoz;

  uint8_t flags;

} State;

static State s_state;

enum {

  k_flags_none = 0,

  k_flag_reset = 1<<0,

};

void OSC_INIT(uint32_t platform, uint32_t api)

{

  s_state.w0    = 0.f;

  s_state.phase = 0.f;

  s_state.drive = 1.f;

  s_state.dist  = 0.f;

  s_state.lfo = s_state.lfoz = 0.f;

  s_state.flags = k_flags_none;

}

void OSC_CYCLE(const user_osc_param_t * const params,

               int32_t *yn,

               const uint32_t frames)

{ 

  const uint8_t flags = s_state.flags;        

  s_state.flags = k_flags_none;                                   

  const float w0 = s_state.w0 = osc_w0f_for_note((params->pitch)>>8, params->pitch & 0xFF);

  float phase = (flags & k_flag_reset) ? 0.f : s_state.phase;

  const float drive = s_state.drive;

  const float dist  = s_state.dist;

  const float lfo = s_state.lfo = q31_to_f32(params->shape_lfo);

  float lfoz = (flags & k_flag_reset) ? lfo : s_state.lfoz;

  const float lfo_inc = (lfo - lfoz) / frames;

  q31_t * __restrict y = (q31_t *)yn;                        

  const q31_t * y_e = y + frames;                                              

  for (; y != y_e; ) {

    const float dist_mod = dist + lfoz * dist;                             

    float p = phase + linintf(dist_mod, 0.f, dist_mod * osc_sinf(phase));

    p = (p <= 0) ? 1.f - p : p - (uint32_t)p;                  

    const float sig  = osc_softclipf(0.05f, drive * osc_sinf(p));                          

    *(y++) = f32_to_q31(sig);

    phase += w0;                                                

    phase -= (uint32_t)phase;       

    lfoz += lfo_inc;             

  }

  s_state.phase = phase;

  s_state.lfoz = lfoz;

}

void OSC_NOTEON(const user_osc_param_t * const params)

{

  s_state.flags |= k_flag_reset;                                  

}

void OSC_NOTEOFF(const user_osc_param_t * const params)

{

  (void)params;

}

void OSC_PARAM(uint16_t index, uint16_t value)

{

  const float valf = param_val_to_f32(value);

  switch (index) {

  case k_osc_param_id1:

  case k_osc_param_id2:

  case k_osc_param_id3:

  case k_osc_param_id4:

  case k_osc_param_id5:

  case k_osc_param_id6:

    break;

               

  case k_osc_param_shape:

    s_state.dist = 0.3f * valf;

    break;

  case k_osc_param_shiftshape:

    s_state.drive = 1.f + valf;

    break;

  default:

    break;

  }

}

This post was posted in Blog entries, In-Depth Reviews, Keyboards & Synths

Comments