-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
16 changed files
with
722 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
build* | ||
*unit |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
[submodule "logue-sdk"] | ||
path = logue-sdk | ||
url = https://github.com/korginc/logue-sdk.git |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,40 @@ | ||
# FunFM-logue | ||
exploration oscillator for KORG Logue platform | ||
|
||
![](blocks.png "structure") | ||
|
||
Consists of 2 extended range alias supressed FM operators with variable waveshape. | ||
|
||
Each operator is capable of generating different waveforms, from pulse to sine to sawtooth. Unlike all other oscillators, the wave transition algorithm is not a crossfade, but is a bit like low-pass filtering. A pulse (bw=-100) can be “filtered” to a sine (bw=0), and then “unfiltered” to a saw (bw=100). | ||
The modulation depth is bipolar: positive is the normal depth, negative is the absolute input value, sometimes giving a different result with more odd harmonics. | ||
the output is an x-fade between op1 and op2, the value is then used as feedback modulation for op2. | ||
|
||
### Parameters: | ||
|
||
- Shape - high resolution op1 pitch offset | ||
- Alt - high resolution op2 pitch offset | ||
- 1bw - op1 bandwidth: -100 - pulse, 0 - sine (parabolic approximation), +100 - saw | ||
- 1pm - op1 by op2 phase modulation depth: -100 - absolute "odd", +100 - bipolar "even" | ||
- 2bw - op2 bandwidth | ||
- 2fb - op2 by feedback modulation depth | ||
- mix - OP1 | ||
|
||
LFO shape is routed to 1pm | ||
|
||
### History: | ||
|
||
The idea was born after meeting KORG Opsix. Despite the relatively convenient control, the synthesizer was disappointing with its low sampling rate and aliasing noises when deviating from the classic sinusoidal fm. The first proof-of-concept was created in 2021 with the support of the [Cultural Transit Foundation Yekaterinburg](http://www.cultt.ru/) as part of the laboratory of electronics in art. Thanks to all participants and artists for the inspiration! | ||
|
||
### Build and load example | ||
|
||
git submodule update --init --recursive | ||
cd funfm-src | ||
make install PLATFORMDIR=../logue-sdk/platform/minilogue-xd/ | ||
./../logue-sdk/tools/logue-cli/get_logue_cli_msys64.sh | ||
./../logue-sdk/tools/logue-cli/logue-cli-win64-0.07-2b/logue-cli.exe load -i 0 -o 0 -u FunFM.mnlgxdunit | ||
|
||
### Note | ||
|
||
The NTS-1 initializes the parameters to -100, so the first sound you hear will be the harsh noise resulting from maximum PM and feedback. I recommend starting by setting the depths and pitches to the middle. The pitch range is intentionally wide, and the extremes are quite random. Please use your ears to adjust the settings. In general, the oscillator is capable of producing classic VA tones (detuned SAW, PWM, hardsync), but the main goal is to search for something new. Stay open to new things. | ||
|
||
Peace! |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
|
||
ifndef PLATFORMDIR | ||
$(warning PLATFORMDIR not defined, building for nts-1) | ||
PLATFORMDIR := $(abspath ../logue-sdk/platform/nutekt-digital) | ||
endif | ||
|
||
GCC_BIN_PATH ?= /bin | ||
LDDIR := $(PLATFORMDIR)/dummy-osc/ld | ||
include $(PLATFORMDIR)/dummy-osc/Makefile |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
// TEST WAVE | ||
// Copyright (C) Eugene Chernyh man125403@gmail.com | ||
|
||
// This program is free software: you can redistribute it and/or modify | ||
// it under the terms of the GNU General Public License as published by | ||
// the Free Software Foundation, either version 3 of the License, or | ||
// (at your option) any later version. | ||
|
||
// This program is distributed in the hope that it will be useful, | ||
// but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
// GNU General Public License for more details. | ||
|
||
// You should have received a copy of the GNU General Public License | ||
// along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
|
||
#ifndef _BASIC_OSC_H | ||
#define _BASIC_OSC_H | ||
|
||
#include <stdint.h> | ||
|
||
// low accuracy for testing only | ||
static inline int32_t boscInc(const int32_t freq, const uint32_t sample_rate) | ||
{ | ||
return (0x100000000LL / sample_rate) * freq; | ||
} | ||
|
||
static inline int32_t boscSaw(int32_t* const acc, const int32_t inc) | ||
{ | ||
*acc += inc; | ||
return *acc; | ||
} | ||
|
||
static inline int32_t boscTriangle(const int32_t saw) | ||
{ | ||
int32_t tri = saw; | ||
if (tri < 0) | ||
tri = ~tri; | ||
tri = tri * 2; | ||
tri -= 0x80000000; | ||
return tri; | ||
} | ||
|
||
static inline int32_t boscParabolicSine(const int32_t tri) | ||
{ | ||
volatile int32_t ps = tri; | ||
if (ps < 0) { | ||
ps = ps + 0x80000000; | ||
ps = (ps / 65536) * (ps / 32768); | ||
ps = ps - 0x80000000; | ||
} else { | ||
ps = 0x7FFFFFFF - ps; | ||
ps = (ps / 65536) * (ps / 32768); | ||
ps = 0x7FFFFFFF - ps; | ||
} | ||
return ps; | ||
} | ||
|
||
#endif // _BASIC_OSC_H |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,180 @@ | ||
// FunFM oscillator port for KORG Logue platform | ||
// Copyright (C) 2024 Eugene Chernyh man125403@gmail.com | ||
|
||
// This program is free software: you can redistribute it and/or modify | ||
// it under the terms of the GNU General Public License as published by | ||
// the Free Software Foundation, either version 3 of the License, or | ||
// (at your option) any later version. | ||
|
||
// This program is distributed in the hope that it will be useful, | ||
// but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
// GNU General Public License for more details. | ||
|
||
// You should have received a copy of the GNU General Public License | ||
// along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
|
||
#include "userosc.h" | ||
#include "osc_api.h" | ||
#include "operator.h" | ||
#include "scale12.h" | ||
|
||
uint8_t osc_notecounter; // wtf, i don't have keyboard to test this! | ||
float note; | ||
float patch_glide; | ||
float patch_op1pitch; | ||
float patch_op1bw; | ||
float patch_op1pm; | ||
float patch_op2pitch; | ||
float patch_op2bw; | ||
float patch_op2pm; | ||
float patch_mix; | ||
|
||
#define RANGE_O1PITCH_HIGH 48.1f | ||
#define RANGE_O1PITCH_LOW -47.4f | ||
#define RANGE_O2PITCH_HIGH (39.3f) | ||
#define RANGE_O2PITCH_LOW (-52.7f) | ||
#define RANGE_BW 1.f | ||
#define RANGE_O1PM 5.f | ||
#define RANGE_O1PM_MAX_FBK 0.5f | ||
#define RANGE_O2PM 1.1f | ||
#define RANGE_MIX 1.f | ||
#define RANGE_GLIDE 1.f | ||
|
||
void OSC_INIT(uint32_t platform, uint32_t api) | ||
{ | ||
(void)platform; | ||
(void)api; | ||
// FPU->FPDSCR |= FPU_FPDSCR_FZ_Msk; | ||
// FPU->FPDSCR |= FPU_FPDSCR_DN_Msk; | ||
// FPU->FPDSCR &= ~(1UL << 12); | ||
// FPU->FPDSCR &= ~(1UL << 10); | ||
// FPU->FPDSCR &= ~(1UL << 8); | ||
osc_notecounter = 0; | ||
} | ||
|
||
// instance | ||
static int32_t in_o1acc; | ||
static int32_t in_o1pm_prev; | ||
static int32_t in_o2acc; | ||
static int32_t in_o2pm_prev; | ||
static float in_feedback; | ||
|
||
// blocksize 64 | ||
void OSC_CYCLE(const user_osc_param_t* const params, int32_t* yn, const uint32_t frames) | ||
{ | ||
float pitch = (float)(params->pitch) * (1.f / 256.f); | ||
float lfo = q31_to_f32(params->shape_lfo); | ||
// pitch filter coefficient for 750Hz CR | ||
// const float glide_cf = osc_notecounter > 1 ? 1.f : 0.f; | ||
// | ||
float op1speed = scale12EdoGetFreqHz(pitch + patch_op1pitch) * k_samplerate_recipf; | ||
float op2speed = scale12EdoGetFreqHz(pitch + patch_op2pitch) * k_samplerate_recipf; | ||
int32_t op1inc = (int32_t)(op1speed * 2147483648.f); | ||
int32_t op2inc = (int32_t)(op2speed * 2147483648.f); | ||
float op2pm = patch_op2pm * patch_op2pm; | ||
|
||
// limitation of phasemod on high feedback | ||
float op2pm_normalized = fabsf(patch_op2pm) / RANGE_O2PM; | ||
float op1pm_mul = 1.f - ((RANGE_O1PM - RANGE_O1PM_MAX_FBK) / RANGE_O1PM) * op2pm_normalized; | ||
float op1pm = (patch_op1pm + RANGE_O1PM * lfo) * op1pm_mul; | ||
op1pm = op1pm * op1pm; | ||
|
||
float op1bw; | ||
float op1amp_comp1; | ||
float op1amp_comp2; | ||
float op2bw; | ||
float op2amp_comp1; | ||
float op2amp_comp2; | ||
if (patch_op1bw < 0) { | ||
op1bw = -patch_op1bw; | ||
op1amp_comp1 = bwAmpComp(op1bw) / 2; | ||
op1amp_comp2 = -op1amp_comp1; | ||
} else { | ||
op1bw = patch_op1bw; | ||
op1amp_comp1 = bwAmpComp(op1bw); | ||
op1amp_comp2 = 0; | ||
} | ||
if (patch_op2bw < 0) { | ||
op2bw = -patch_op2bw; | ||
op2amp_comp1 = bwAmpComp(op2bw) / 2; | ||
op2amp_comp2 = -op2amp_comp1; | ||
} else { | ||
op2bw = patch_op2bw; | ||
op2amp_comp1 = bwAmpComp(op2bw); | ||
op2amp_comp2 = 0; | ||
} | ||
|
||
float op1amp_mix = patch_mix; | ||
float op2amp_mix = 1.f - op1amp_mix; | ||
|
||
float fb = in_feedback; | ||
|
||
q31_t* __restrict y = (q31_t*)yn; | ||
const q31_t* y_e = y + frames; | ||
for (; y != y_e;) { | ||
// comparison on each sample is not required, but we still have cycles and let's keep this code simple. | ||
float o2pm = (patch_op2pm < 0 ? fabsf(fb) : fb) * op2pm; | ||
float o2 = operatorDoubleSmpl(&in_o2acc, &in_o2pm_prev, o2pm, op2inc, op2speed, op2bw, 0x80000000, op2amp_comp1, op2amp_comp2); | ||
float o1pm = (patch_op1pm < 0 ? fabsf(o2) : o2) * op1pm; | ||
float o1 = operatorDoubleSmpl(&in_o1acc, &in_o1pm_prev, o1pm, op1inc, op1speed, op1bw, 0x80000000, op1amp_comp1, op1amp_comp2); | ||
float out = o1 * op1amp_mix + o2 * op2amp_mix; | ||
fb = out; | ||
*(y++) = f32_to_q31(out); | ||
} | ||
|
||
in_feedback = fb; | ||
} | ||
|
||
void OSC_NOTEON(const user_osc_param_t* const params) | ||
{ | ||
(void)params; | ||
osc_notecounter++; | ||
} | ||
void OSC_NOTEOFF(const user_osc_param_t* const params) | ||
{ | ||
(void)params; | ||
if (osc_notecounter) | ||
osc_notecounter--; | ||
} | ||
|
||
void OSC_PARAM(uint16_t index, uint16_t value) | ||
{ | ||
switch (index) { | ||
case k_user_osc_param_shape: | ||
// o1pitch | ||
patch_op1pitch = (float)value * ((RANGE_O1PITCH_HIGH - RANGE_O1PITCH_LOW) / 1024.f) - RANGE_O1PITCH_HIGH; | ||
break; | ||
case k_user_osc_param_shiftshape: | ||
// o2pitch | ||
patch_op2pitch = (float)value * ((RANGE_O2PITCH_HIGH - RANGE_O2PITCH_LOW) / 1024.f) - RANGE_O2PITCH_HIGH; | ||
break; | ||
case k_user_osc_param_id1: | ||
// 1bw | ||
patch_op1bw = (float)value * (RANGE_BW * 2.f / 200.f) - RANGE_BW; | ||
break; | ||
case k_user_osc_param_id2: | ||
// 1pm | ||
patch_op1pm = (float)value * (RANGE_O1PM * 2.f / 200.f) - RANGE_O1PM; | ||
break; | ||
case k_user_osc_param_id3: | ||
// 2bw | ||
patch_op2bw = (float)value * (RANGE_BW * 2.f / 200.f) - RANGE_BW; | ||
break; | ||
case k_user_osc_param_id4: | ||
// 2pm | ||
patch_op2pm = (float)value * (RANGE_O2PM * 2.f / 200.f) - RANGE_O2PM; | ||
break; | ||
case k_user_osc_param_id5: | ||
// mix | ||
patch_mix = (float)value * (RANGE_MIX / 200.f); | ||
break; | ||
case k_user_osc_param_id6: | ||
// glide | ||
patch_glide = (float)value * (RANGE_GLIDE / 100.f); | ||
break; | ||
default: | ||
break; | ||
} | ||
(void)value; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
./../logue-sdk/tools/logue-cli/logue-cli-win64-0.07-2b/logue-cli.exe load -i 0 -o 1 -u FunFM.ntkdigunit -s $1 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
{ | ||
"header" : | ||
{ | ||
"platform" : "nutekt-digital", | ||
"module" : "osc", | ||
"api" : "1.1-0", | ||
"dev_id" : 1836213874, | ||
"prg_id" : 2, | ||
"version" : "1.0-0", | ||
"name" : "ffm", | ||
"num_param" : 5, | ||
"params" : [ | ||
["1bw", -100, 100, "%"], | ||
["1pm", -100, 100, "%"], | ||
["2bw", -100, 100, "%"], | ||
["2fb", -100, 100, "%"], | ||
["mix", -100, 100, "%"] | ||
] | ||
} | ||
} |
Oops, something went wrong.