0123456789
list icon
list icon

NTS-1 digital kit NTS-1 digital kit

SoundCloud

Share

[DOTEC-AUDIO x Nu:Tekt] How to make Custom Effects for NTS-1

Creating custom plugins for the NTS-1 might seem pretty daunting, but fret not!
In this 5-part series of articles we will explore together with DOTEC-AUDIO how to create custom effects for NTS-1, step by step!

Article #1: Making Custom-Made Effects Work with the NTS-1

Hello, everyone! My name is Shinji Iijima from DOTEC-AUDIO, a plugin development company.

Up until now we have mostly developed audio plugins only for PC and iPhone / iPad.
https://dotec-audio.com/

The Nu:Tekt NTS-1 digital kit” (hereafter abbreviated as the “NTS-1) is an extremely interesting yet affordable build-it-yourself digital synthesizer kit. So, why is DOTEC-AUDIO, a PC plugin developer interested in this synthesizer? It's because this synth lets you write your own programs and then install the sounds (oscillators) and effects you've developed into the NTS-1.

Another interesting thing about the NTS-1 is that it has a stereo audio input (line input). What this means is that you can hook up a sound generator or a musical instrument, and use this unit not just as a synthesizer but as a compact standalone effects unit.

In other words, you get a synthesizer that you can freely program, as well as effects. When you hear that, it really makes you want to try it yourself, don’t you think?

OK, so let’s get started. Take a look at this video!

This is a “bit crusher,” an effect that gives you a coarse, low-res sound.

And...
This the vinyl stop effect, which gives you the sound of stopping a vinyl record with your hand.

Both of these effects contain part of the multi-effects functionality from DOTEC-AUDIO’s “DeeFX,” brought to the NTS-1.
As you can see, what’s so interesting about this is how you get not only the NTS-1 oscillators, but also effects that you can apply to the audio input! In this article series, I’d like to talk about developing effects for the NTS-1, focusing on these two effects.

In this series, we’ll be publishing and delving into the source code of these effects; but to start out, I’ll explain how to download these two effects and install them into the NTS-1.

If you’ve already purchased an NTS-1, definitely download these effects and play with them yourself!

How to install

  • 1/ Install the NTS-1 Sound Librarian

    First of all, install the NTS-1 Sound Librarian software on your computer (Win/Mac).
    Follow the instructions to install the software, and make sure to connect the NTS-1 to your computer.
  • 2/ Download the effect files

    Next, download these effect files developed by DOTEC-AUDIO.

    Download Bitcrusher here
    Download Stop FX here

    Decompressing this ZIP file will give you the “bitcrusher.ntkdigunit” and “stopfx.ntkdigunit” files. 
  • 3/ Load these files into Sound Librarian

    To load the files, go to the “File” menu, click “Import User Unit” and select the files you decompressed. Once you do this, the “bitcrusher” effect is added to the “USER MODULATION FX” category, and the “stopfx” effect is added to the “USER DELAY FX” category.

    Last, go the “Send/Recv.” menu and select “Send User Data” to transfer these two effects to the NTS-1.

    Once the effects are transferred, you’ll be able to select "bitcrusher" from “Mod”, and “stopfx” from "delay" on the NTS-1.
Enjoy experimenting with these effects.

See you next time!

Article #2: How to Prepare to Develop Effects for the NTS-1

Hello, everyone! I’m Shinji Iijima from DOTEC-AUDIO.

Last time we talked about the effects we actually made for the NTS-1. Have you tried them out yet? This time, I’ll explain the environment you’ll need to create your own original effects (and oscillators) for the NTS-1.
Actually, the basics you need to know are found in the “logue SDK” page, so this time, I’ll be giving you some added details. I should mention that everything I’m explaining is available free of charge. Also, one of the appealing things about this is that you can develop in Windows, macOS or Linux, so just about anyone can do this.
(Note that the installation steps differ with each OS.)

logue SDK
https://korginc.github.io/logue-sdk/

To begin, let’s talk about what an “SDK” is. SDK stands for “Software Development Kit,” which is a set of tools necessary to develop specific software.
For instance, you’ll use the Windows SDK to develop apps for Windows, or the iOS SDK to develop apps for the iPhone. When developing for the NTS-1, we use the “logue SDK.” https://github.com/korginc/logue-sdk

The SDK that’s released here is the logue SDK itself. The best thing to do is to use Git software to obtain the latest version of the SDK, but to do that you’ll need to know how to use Git.

For that reason, it’s fine just to go to the Web page listed above and click on the green “Clone or Download” button to download the files as a ZIP archive. Note that the SDK is sometimes updated, so it’s a better idea to use Git if you always want to obtain the latest version of the SDK.

MSYS2 (for Windows)
https://www.msys2.org/

The thorniest matter to deal with when you consider building an NTS-1 development environment for Windows is “MSYS2.” I’d like to explain about that now. All of the GNU commands used with the logue SDK execute commands with the Unix OS as a prerequisite.

For Linux, which is basically Unix, or macOS, which is based on Unix, you’ll be able to install using a bare minimum of tools. However, for Windows you’ll need an additional environment that can execute Unix commands. Simply put, MSYS2 is a platform for executing Unix commands on Windows. Because of this, if you’re developing with Windows and MSYS2, you’ll run your commands within the “MSYS2” window.

Run a Web search on “NTS-1 MSYS2,” and you’ll find articles that list the specific steps required. I definitely encourage you to use those sites for reference.

GNU Arm Embedded Toolchain
https://github.com/korginc/logue-sdk/tree/master/tools/gcc

To develop programs, you need a “compiler,” which is a software that converts programs written by humans into machine language that’s readable by a computer. With the logue SDK, we’ll use the “GNU Compiler Collection” (GCC for short).

The GCC is such a complicated topic in its own right that we could write a thick book about it, so I’ll skip that for now. Suffice to say that the “GNU Arm Embedded Toolchain” explained on this page is the name of a set of programming tools that includes the GCC.
The “Arm Embedded” refers to the “STM32F4” chip with an Arm CPU that’s used on the NTS-1. The toolchain we’re referring to is used to develop programs that run on this CPU.

As you can see in the Readme file, all you need to do to install is to run the prepared shell script.

GNU Make
https://github.com/korginc/logue-sdk/tree/master/tools/make

“Make” is a tool that lets you configure a single command to save you the trouble of executing many commands and compiling your files each time. (Make is another tool we could take up a whole book to write about.)

Make does not need any additional special installation on macOS, Linux and Windows (and MSYS2), but we will explain where it is otherwise necessary to install.

Info-ZIP
https://github.com/korginc/logue-sdk/tree/master/tools/zip

This is a tool used to create ZIP files, and as with GNU Make it doesn’t require special installation on virtually any platform.

logue-cli (optional)
https://github.com/korginc/logue-sdk/tree/master/tools/logue-cli

This is an “optional” component, and not everyone needs it. Think of this as the command-line variant of “Sound Librarian,” which we described last time. Note that there is a Windows and macOS version of Sound Librarian, but not a Linux version.
For that reason, if you’re planning to develop on Linux, you’ll need to install this tool. Once you’ve successfully installed these tools, let’s build (create the program for) the sample components included with the SDK.

We’ll use the steps in the “Demo Project Build (Waves)” ( https://korginc.github.io/logue-sdk/) to build.

If you’ve successfully executed the commands, a file named “waves.ntkdigunit” will be generated. This should be loaded into the “USER OSCILLATORS” of Sound Librarian as “waves.” If you’ve gotten this far, the next step is programming! It’s a bit of a chore to get things up and running.

The good thing is that there’s a lot of information on the Internet about these tools, so I encourage you to poke around on the Web to get your environment all set up!

See you next time!

Article #3: Creating an Original Effect, Part 1

Hello, my name is Frank Shigetora, and I’m a sound producer at DOTEC-AUDIO.
Since it’s time for us to create our effect, I’ll be in charge of this part, as the designer of the DSP.

Have you set up your development environment yet, as we explained last time?
I’ll be following up with those of you who have used the command line or who are creating a program environment for the first time.

With MSYS2, we’ll move to a different folder using the command “cd ” instead of the mouse. Now I’ll show you a bit of an easier way to do this.
For really long folder names, type a few of the initial characters, and then hit the TAB key to auto-complete with names that match, starting from the first character.
If you want to see a list of contents of the current folder, type the “ls” command and hit Enter.
So, you should just remember “cd” to move to a different folder, and “ls” to list its contents.
To move back up one level in the directory, type “cd ..” (two periods).
Actually, on UNIX-based systems, we use the term “directory” for a folder; but since we’re using Windows and the Mac as well, we’ll stick with the term “folder” for now. (This probably won’t ruffle any feathers, but just in case...)

If we install “logue SDK” in the user’s home folder of MSYS2, our work afterwards will be easier.
Example: when installing MSYS2 directly to the C: drive in Windows
C:\msys64\home\\korg

Okay, let’s move on to the good stuff. First, let’s download the file shown below,

NTS-1 Effect template

and decompress the files to this folder.
korg\v1.1\platform\nutekt-digital

Here we have the templates for creating the Mod (modulator), Delay, and Reverb effects. Actually, there’s already a template in logue SDK, but we’ll do this in a simple way so that our lesson this time goes smoothly. The fact is that if you’re not familiar with the folder structure, you might mistake the sample folder structures for one another and not know where to copy the files.

About the Template Folder Structure Used This Time

The templates created by DOTEC-AUDIO this time follow the structure shown below.
- ld (folder)
- tpl (folder)
- main.c
- Makefile
- manifest.json
- project.mk


The first “ld” folder contains the definition file for the effects we will create. Without going into too much detail, let’s just remember that the effects we are making are “Mod,” “Delay” and “Reverb,” and that their folder contents are different from this one.
In the same way, “tpl” contains the files that are the templates for each kind of effect. “ld” and “tpl” need to be selected correctly according to the type of effect you want to create, but you won’t need to edit them.
“Makefile”, “manifest.json” and “project.mk” are definition files. The files you’ll need to rewrite according to the effects you will create are “manifest.json” and “project.mk”.
We’ll explain where to edit at the end of this article.

The “main.c” file contains the effect program.

This time, we’ll create “bitcrusher”, which is in the demo. This effect is a simplified version of the bitcrusher effect found in DeeFX, made for us to study.

Setting this aside, let’s play around with the finished effect.
Download the bitcrusher files and unzip the files in this folder: korg\v1.1\platform\nutekt-digital

Using MSYS2, we’ll go (move) to “korg\v1.1\platform\nutekt-digital\bitcrusher”, which is easy to do if you use the TAB key trick I explained earlier.
Once you’ve entered the “bitcrusher” folder using the cd command, use “make” as we explained last time.
Do you remember that? Type “make” and hit the Enter key.
You’ll see a long block of characters flash by, and then finally you’ll see:
Packaging to ./bitcrusher.ntkdigunit

Done

...if all goes well. A file called “bitcrusher.ntkdigunit” should be generated in the “build” folder. As with last time, we’ll transfer that file to the NTS-1. This effect is strongly applied when you use knob A.

All right, so let’s have a look at the source code!
Using Notepad, Hidemaru or whatever text editor you prefer, open the “main.c” file located in the “bitcrusher” folder.

At the beginning of the file, you see:

#include "usermodfx.h"
#include "float_math.h"


We’ll need the first line to create modfx. The second line is required for floating point operations.

There are two methods used to show the sizes of numbers in digital audio data: fixed decimal and floating point. Floating point is commonly used in our ordinary lives, in the same way as when we say "50% more." It's easier for people to understand. However, the downside of floating point is that calculations get processed slower.

Fixed decimal gets processed faster, but it’s harder for people to understand. It’s also more difficult to handle, since there are different formats (like the Q format) for determining the number of digits for integers and decimals.

Still, take heart! The NTS-1 has a built-in processor that’s dedicated to handling floating point!
There’s no reason why we shouldn’t use it, so let’s do so.


Now, on to the next part.

static float rate, lastSampleL,lastSampleR;
static uint32_t count;


This is here to put a label on the “boxes” where we put data called “variables.”
This makes three float-type static variables called “rate”, “lastSampleL” and “lastSampleR”, and a uint32_t-type static variable called “count”.
These are called “(global) variable declarations,” and are a key part of the program... so you can do a search with a phrase like:

“C variable declarations” to learn more! By doing so, you’ll also learn about the meaning and types of “static”.
Simply put, if you don’t want the contents of the variable to be erased every time a process is executed, set it to “static”, and the contents will be maintained.
The “static” type has other uses. Try searching for “static variables”.

All right, so what’s next?

void MODFX_INIT(uint32_t platform, uint32_t api) { lastSampleL = 0.f; lastSampleR = 0.f; count = 0; }

The above is what we call a “function”.
A function is represented by a label for each program process by function, called a function name. You can call up functions by their names from all kinds of places within the program, and use them in combination to create a single chain of results—your software. Functions are much like the individual parts used in an automobile.

Value returned Function name (argument)
{
Processes to execute
}


The above is the format used for functions. The “value returned” means what kind of data is used to get the results of the process executed by the function, and the “argument” is the data and its type that are given to the function when it is called.

This time, we’re writing an initialization process. The contents of the variable we created are reset to “0”, but what does the “f” in “0.f” stand for? This simply means that we’re committing to a float type. Try doing a search for this.
So, how about the “void” in the returned value? This means that nothing is returned in the results. Since the first time we do the initialization process is also the last time, we get the “void” value.
That’s all this function has to do—nice!

Now we move on to the main processing. We’ll first skip to explain the function at the very bottom.

void MODFX_PARAM(uint8_t index, int32_t value)
{
const float valf = q31_to_f32(value);
switch (index) {
case k_user_modfx_param_time:
rate = valf;
break;
default:
break;
}
}


Wow, this already looks pretty tough!
All this is doing is getting data from the A and B knobs, and putting those values into our variable.
When you turn the knobs, the NTS-1 system calls up this function, and when this function is called up, the knob type “index” and its value, “value” are received as an argument.
This value comes in fixed decimal format as we covered earlier, so we need to first convert it to floating point. In this example, we convert fixed decimal Q31 format into 32-bit floating point, and store the result in a variable called “valf”. We use “const” when the data content should not be changed. Try searching for this if you need more details.

This is processed using the “switch case” statement. Explained simply, this shows the processing that occurs when the variables we specified using “switch” match these respective cases.
This time, we use just the knob A data, so the contents of “valf” are stored in the “rate” variable when k_user_modfx_param_time (index name of knob A) data is received.
The “rate” variable is used for the main processing, which is why I’ve explained this function first.


Now we’re on to the main part.

void MODFX_PROCESS(const float *main_xn, float *main_yn,
const float *sub_xn, float *sub_yn,
uint32_t frames)


With the function named MODFX_PROCESS, we can get these six arguments:

Main input: main_xn
Main output: main_yn
Sub input from the oscillator: sub_xn
Sub output from the oscillator: sub_yn
Total number of frames: frames


The variable names marked with an asterisk call by reference. They indicate that in this function, when a value is changed, the variable that calls it also changes. This basically means that the data received is the original, not a copy. You might want to study more about calling by reference and passing values!

Although you might have figured this out by looking at the argument, all you need to do is to do the various effect processing for the data equivalent to the number of frames in “main_xn”, and write this as the output to main_yn.
We won’t be using sub input/output this time.
Here’s the beginning part:

for(uint32_t i = 0; i < frames; i++){

This means that all we need to do is repeat what’s inside of the brackets { } the specified number of times. This is called a “for” statement, and we use this to repeat the processing for the number of frames.

// Prepare the L and R audio
const float inL = main_xn[i * 2];
const float inR = main_xn[i * 2 + 1];


The two slashes (//) at the beginning mark a comment. Use these to leave behind notes in your program, as they won’t have any effect on how the program runs.
Now, as for the input, “main_xn”, the stereo audio (channels) alternate between left and right.
We can process these alternately, but for the sake of clarity we transfer these into variables each time, “inL” and “inR”.
Going back to explain the structure of “main_xn”, we can see the text “LRLRLR” , which represents sets of “LR” according to the number of frames. The quantity of data equals twice the number of frames. Variables containing multiple sets of data are called “array variables.” You should be able to understand this term if you do a search for it. The quantity of this data is known as the number of elements. The number of elements in “main_xn” is twice that of the number of frames written in “frames”.

We specify which element number to use with square brackets []. The difficult part is that the numbers start with zero, so main_xn[0] indicates the first element number.
So in this “for” statement, the value of the variable named “i” increases by one with each loop, as long as the value is less than “frames”.
As “i” starts with zero, it will be the same in terms of number of times as the number of frames, but only as long as it ends one number lower than “frames”.
You might be thinking, “why don’t the numbers start with 1?”. In that case, the loop would end once the number reaches the same number as “frames”. That said, there’s only one chance for the numbers to be the same... so if you make a mistake in your programming and go over the number, you’ll end up with an endless loop. If this happens, the program may freeze, or perhaps the following process will start at zero, which could be convenient... That ties neatly in with the next topic.

You’ll see that the notation reads like“[i * 2]”, “[i * 2 + 1]” and so on. Since LR comes in alternately, number zero is “L”, and number 1 is “R”. It’s easy to understand when you think of it like this: when “i” is 0, [0 * 2][0 * 2 + 1] produces “0” and “1”; when “i” is 1,2... and so on.

So now that I’ve got a smug look on my face after explaining all this, we’ll get into the secrets of how the bitcrusher works.
To first explain how bitcrusher works, this effect gives you a “grittier” sampling rate, since higher sampling rates produce more detailed sound. What I mean by “grittier” is that the refresh rate of the sampling data is lower, or the steps (resolution) become less defined. This time, we’ll lower the refresh rate, and switch the data output once every certain number of times. If the data input is continuous like “12345678”, this effect gives you “11335577”. This gives you a sound like reducing the sampling rate, but you might argue that “this isn’t a bitcrusher effect (reducing the quantization)!” I totally understand that objection.
The fact is that most people will have trouble understanding if we suddenly talk about bit operations. Also, the bitcrusher effects out there now give you an in-your-face sound by lowering the sampling rate, not a mild effect by just dropping some bits. That’s why I gave you a more interesting explanation. All right, let’s move on!

// The larger the value, the grainier the sample
uint32_t skip = rate * 64;


This is where we use the data previously received from the knobs. At last, here it is! The data we received is from 0–1, so if we want to get a value of 64 from the maximum number (1), we need to multiply it by 64.
To make it easier to understand, we replace this with the variable “skip”.

// Update lastSample only when count is 0
if(count == 0){
lastSampleL = inL;
lastSampleR = inR;
}


You’ll understand this part more as we go forward. First, I’ll explain about the processing.
This updates the variable that maintains the output, but only when the “count” variable is “0”. In other words, when the value is not “0”, “lastSmpleL/R” is the same value as last time.
“count” begins with zero as it was initialized (mentioned previously), and suddenly gets updated to the “inL/R” value.

// Continue lastSample sound
main_yn[i * 2] = lastSampleL;
main_yn[i * 2 + 1] = lastSampleR;
count++;


This does the opposite of what we did by separating the input into L and R. The output is written to “main_yn” here for the L and R sounds. Basically, we write the same sound that we just put into “inL/R”. Nothing’s changed!
By the way, after this we see a “count++”. This means to increase “count” by one.
That’s why “0” becomes “1”. With this method, we can also write “i++” for the “for” statement, so that “i” gets incremented by one with each pass. Another way to write this is “count = count + 1”.
What happens when the count gets to “1” is that “lastSampleL/R” does not get updated at the beginning of the next pass. The same sample data keeps getting outputted. So, just when do we get back to zero?

// reset count to 0 if skip exceeded
if(count > (int)skip) count = 0;


Yes—right here.
Previously, we received a value from the knob, and if that value multiplied by 64 exceeded the “count”, it would be reset to zero.
What happens is that with larger “skip” values, the very same data is output without regard to the input, and thus we get a drop in refresh rate for the sampling data.
We’re done!

Finally, we have these:

}
}


Actually, these are important. The closed bracket on top is for the “for” statement, and the brackets on the bottom mark the end of the function. Basically, we use these brackets {} to enclose all kinds of processes.
The longer the process, the more likely it is we’ll forget to put in the closing bracket! Be careful about this, because just this omission alone will cause your build to fail.
This kind of mistake is also hard to find, and it’s common for programmers to waste a long time just hunting for it.
Of course, I’m no exception! When you find this kind of mistake, it really makes you want to toss the computer out the window and smack yourself in the head!

The program this time is “Modfx”, and we can create this one from the “tmpMod” template.
I encourage you to compare this with the “main.c” file, and see what’s been added.
Try replacing the values in “skip” with different values and test it out!

Finally, we’ll type name of the effect we created in to the “name” parameter in “manifest.json” and in the “PROJECT” parameter of “project.mk”. You can do this using a text editor.

Next up:StopFX
This will be quite a challenge, so I hope you’re looking forward to it!
Thanks very much for reading this long article!

Article #4: Creating an Original Effect, Part 2

Coming soon

Article #5: Building a Vocoder

Coming soon