ver.90c7dbb
Tags: music hardware tools

Korg NTS-1

The Korg NTS-1 synthesizer

The Korg NTS-1 is a programmable digital monophonic synthesizer ("monosynth") and effects unit.

Custom oscillators and effects can be programmed in C (plus a subset of C++) using Korg's logue SDK.

I find the NTS-1 to be a really fun synth to mess around with, and really the only hardware synth outside of the [[deluge]] that I enjoy using. I think there are a few reasons for that, but a big one is the lack of presets. If I want to make a sound with the NTS-1, I need to engineer that sound more or less from scratch ([[sound-design]]), which leads to fun discoveries that I have to capture in the moment: once the synth is powered off, that patch is gone! This is a big part of the Eurorack ethos, except the NTS-1 costs an order of magnitude less than an equivalent Eurorack setup. The only comparable Eurorack modules I could find were the Electro-Smith Daisy Patch or Patch.Init(), and those cost at least double the price of the NTS-1 (and that's without a case, power supply, and other modules to play with). Also those only allow you to load one patch, although I suppose you could create a patch with a menu to switch between "sub-patches".

Since the NTS-1 is an external synth, I can run it through guitar pedals for more effects than those in its internal effects chain. Running it through a fuzz pedal and/or an amp sim pedal adds some nice warmth to the sound.

Most annoying thing has to be the build quality and connections. It's not poorly built or faulty, it's just a bit too delicate. All the audio/MIDI jacks are 3.5mm mini jacks, and the power connection is Mini-USB. Not the most durable connections. I'd like to build an enclosure that gives it 1/4" audio jacks, 5-pin DIN MIDI, a real power connector, and an internal battery, much like this project from Caspius Labs.

Custom oscillators/FX

Finding info about how to write your own oscillators and effects is tricky. Below is the bare-minimum that I could figure out to implement a basic sine oscillator:

// Based off https://github.com/damnatron/logue-sdk/blob/master/platform/prologue/contrib/sine/sine.cpp

#include "userosc.h"

typedef struct State {
    float phase;
} State;

static State s_state;

void OSC_INIT(uint32_t platform, uint32_t api)
{
    s_state.phase   = 0.f;
}

// Rendering function for each cycle.
// (frequency = number of cycles/second)
void OSC_CYCLE(
    const user_osc_param_t * const params,          // Current realtime parameter state. 
    int32_t *yn,                                    // Output buffer.
    const uint32_t frames                           // Size of output buffer. (1 sample per frame)
)
{
    // Notes:
    // Oscillators are NOT stereo, so the number of frames that need to get pushed to the output buffer
    //   is equal to the number of samples we need to generate.  For Mod FX (which are stereo), each
    //   frome needs 2 samples (L and R)

    // Get pointers to the start and end of the output buffer:
    q31_t * __restrict output_buffer_cursor = (q31_t *)yn;
    const q31_t * output_buffer_end = output_buffer_cursor + frames;

    // For the current note + mod, get the phase offset (w0) using osc_w0f_for_note.
    // The phase offset is how much we need to increment the phase every sample.
    uint8_t note = (params->pitch) >> 8;
    uint8_t mod = (params->pitch) & 0xFF;
    const float phase_offset = osc_w0f_for_note(note, mod);

    // phase ranges from 0 to 1 (we clamp it on line 48), representing 0 to 2PI
    float phase = s_state.phase;
    // Generate samples and push them to the output buffer until we reach
    //   the end of the output buffer:
    for (; output_buffer_cursor != output_buffer_end;)
    {
        const float sample = osc_softclipf(0.05f, osc_sinf(phase));
        *(output_buffer_cursor++) = f32_to_q31(sample);
        phase += phase_offset;
        phase -= (uint32_t)phase;   // clamp phase offset to 0-1 (discard whole part)
    }

    // we need to store the last phase for this cycle of audio so the
    //   next cycle can start on the same phase
    s_state.phase = phase;
}

void OSC_NOTEON(const user_osc_param_t * const params)
{

}

void OSC_NOTEOFF(const user_osc_param_t * const params)
{

}

void OSC_PARAM(uint16_t index, uint16_t value)
{

}

Shortcuts

The NTS-1 has a decent amount of features hidden behind shortcuts (pretty much all accessed by holding down a button and turning the A or B knobs).

Oscillator

The LFO can target 2 parameters: the oscillator pitch (P) or the waveshaping (or shape) amount (S). It cannot target both parameters at once, only one or the other.

While holding OSC... Function
A knob LFO frequency
B knob LFO target and depth (can target shape (S) or pitch (P))

Filter

While holding FILTER... Function
A knob Cutoff sweep frequency
B knob Cutoff sweep depth (can sweep up and down)

Envelope Generator

While holding EG... Function
A knob Tremolo frequency
B knob Tremolo depth

FX

While holding DELAY or REVERB... Function
A knob none
B knob Wet/dry balance

Arpeggiator

Press and hold the ARP button to latch the arpeggiator. Press it again to un-latch it.

Pressing any other buttons while holding the ARP button sets the intervals between notes of the pattern.

While holding ARP... Function
OSC Octave
FILTER Major triad
EG Major suspended
MOD Major augmented
DELAY Minor triad
REVERB Minor diminished
TYPE knob Arp pattern (Up, Down, Up-Down, Down-Up, Converge, Diverge, Converge-Diverge, Diverge-Converge, Random, Stochastic (Brownian?))
A knob Pattern length
B knob Step duration (if synced externally), tempo (if not synced externally)

Global Parameters

Hold down the REVERB button while powering on the NTS-1 to access the global parameter editor.

In the global parameter editor, use the TYPE knob to select a parameter and change its value with the B knob.

REVERB cancels parameter changes, while ARP saves parameter changes.

links