🇬🇧: digital scale-aware oscillator for my korg minilogue xd and other logue-sdk instruments
🇮🇹: oscillatore digitale scale-aware per il mio korg minilogue xd e altri strumenti del logue-sdk
questa versione è datata e ne ho fatte altre che a un certo punto metterò su questo sito. se vuoi averla scrivimi per mail
v0.2-35
download: link
note
- parametro 5
"x/-5/-4/+5/+7/+9"è ancora buggato e fortissimo rispetto alle altre voci - il wave morph non fa niente. per il momento questo oscillatore fa solo onde sinusoidali
manifest.json
TODO: rinominare “root” -> “root (c = 0)”
{
"header" :
{
"platform" : "minilogue-xd",
"module" : "osc",
"api" : "1.1-0",
"dev_id" : 0,
"prg_id" : 0,
"version" : "0.2-35",
"name" : "offset",
"num_param" : 6,
"params" : [
["root", 0, 11, ""],
["major/minor", 0, 1, ""],
["offset/wave", 0, 1, ""],
["main note", 0, 100, "%"],
["x/-5/-4/+5/+7/+9", 0, 5, ""],
["lgb.26 :)", 0, 1, ""]
]
}
}
offset.cpp
#include "userosc.h"
static const uint8_t s_scales[8][7] = {
{0, 2, 4, 5, 7, 9, 11},
{0, 2, 3, 5, 7, 8, 10},
};
#define MODE_OFFSET 0
#define MODE_WVFORM 1
#define DEFAULT_GAIN 0.3
#define EXTRA_NONE 0
#define EXTRA_FIFTH 1
#define EXTRA_SEVENTH 2
#define EXTRA_NINTH 3
typedef struct State {
float phase[4];
uint8_t scale_idx;
uint8_t root;
uint8_t mode; // 0 = Offset, 1 = Waveform
uint8_t degree_offset;
float shape;
float main_note; // how much of the tonic
float wave_morph; // current shape
uint8_t extra;
} State;
static State s;
static inline q31_t q31_add(q31_t a, q31_t b);
uint8_t offset_note(const uint8_t* scale, const uint8_t scale_size, const uint8_t degree_offset, const uint8_t note, const uint8_t root);
void oscillate(
const uint8_t note,
const uint8_t voice,
const uint8_t mod,
const bool replace,
const float wave_morph,
const float gain,
q31_t* y,
const uint32_t frames
);
void OSC_INIT(uint32_t platform, uint32_t api) {
s.phase[0] = 0.0f;
s.phase[1] = 0.0f;
s.phase[2] = 0.0f;
s.phase[3] = 0.0f;
s.scale_idx = 0;
s.root = 0;
s.mode = MODE_OFFSET;
s.main_note = 1.0f;
s.wave_morph = 0.0f;
}
void OSC_CYCLE(const user_osc_param_t * const params, int32_t *yn, const uint32_t frames) {
switch (s.mode) {
case MODE_OFFSET:
s.degree_offset = (uint8_t)(s.shape * 10);
break;
case MODE_WVFORM:
s.wave_morph = param_val_to_f32(s.shape);
break;
}
// pitch logic
#define current_scale (s_scales[s.scale_idx])
uint8_t note = (params->pitch >> 8);
uint8_t moved_note = offset_note(current_scale, 7, s.degree_offset, note, s.root);
// audio generation
const float w0 = osc_w0f_for_note(moved_note, params->pitch & 0xFF);
q31_t * __restrict y = (q31_t *)yn;
for (int i = 0; i < frames; i++) {
yn[i] = f32_to_q31(0.0);
}
oscillate(note, 0, params->pitch & 0xFF, false, s.wave_morph, DEFAULT_GAIN * s.main_note, yn, frames);
oscillate(moved_note, 1, params->pitch & 0xFF, false, s.wave_morph, DEFAULT_GAIN, yn, frames);
uint8_t top_note = 0;
switch (s.extra) {
case EXTRA_NONE:
break;
case EXTRA_FIFTH:
top_note = offset_note(current_scale, 7, 4, note, s.root);
break;
case EXTRA_SEVENTH:
top_note = offset_note(current_scale, 7, 6, note, s.root);
break;
case EXTRA_NINTH:
top_note = offset_note(current_scale, 7, 8, note, s.root);
break;
}
if (top_note != 0) {
oscillate(top_note, 2, params->pitch & 0xFF, false, s.wave_morph, DEFAULT_GAIN, yn, frames);
}
}
uint8_t offset_note(
const uint8_t* scale,
const uint8_t scale_size,
const uint8_t degree_offset,
const uint8_t note,
const uint8_t root
) {
int8_t relative_note = (int8_t)note - s.root;
uint8_t closest_root = (relative_note - (relative_note % 12)) + s.root;
uint8_t semitone_offset = (note - closest_root) % 12;
uint8_t closest_scale_degree = 0;
for (int i = 0; i < scale_size; i++) {
if (current_scale[i] <= semitone_offset) {
closest_scale_degree = i;
}
}
uint8_t offset_scale_degree = (closest_scale_degree + degree_offset);
uint8_t octaves_above = offset_scale_degree / scale_size;
uint8_t final_note = closest_root + current_scale[offset_scale_degree % scale_size] + (12 * octaves_above);
return final_note;
}
void oscillate(
const uint8_t note,
const uint8_t voice,
const uint8_t mod,
const bool replace,
const float wave_morph,
const float gain,
q31_t* y,
const uint32_t frames
) {
float* phase = &s.phase[voice];
const float w0 = osc_w0f_for_note(note, mod);
for (uint32_t i = 0; i < frames; i++) {
float sin = osc_sinf(s.phase[voice]);
float saw = (s.phase[voice] * 2.0) - 1.0;
float sig = ((sin * s.wave_morph) + (saw * (1 - s.wave_morph))) * gain;
if (replace) {
// y[i] = 0;
y[i] = f32_to_q31(sig * gain);
} else {
y[i] = q31_add(y[i], f32_to_q31(sig));
}
*phase += w0;
if (*phase >= 1.0f) {
*phase -= 1.0f;
}
}
}
static inline q31_t q31_add(q31_t a, q31_t b) {
int64_t sum = (int64_t)a + (int64_t)b;
if (sum > INT32_MAX) return INT32_MAX;
if (sum < INT32_MIN) return INT32_MIN;
return (q31_t)sum;
}
void OSC_PARAM(uint16_t index, uint16_t value) {
switch (index) {
case k_user_osc_param_id1: s.root = value; break;
case k_user_osc_param_id2: s.scale_idx = value; break;
case k_user_osc_param_id3: s.mode = value; break; // 0: Offset, 1: Wave
case k_user_osc_param_id4: s.main_note = (float)value / 100.0; break;
case k_user_osc_param_id5: s.extra = value; break;
case k_user_osc_param_shape: s.shape = param_val_to_f32(value); break;
}
}
void OSC_NOTEON(const user_osc_param_t * const params) {
s.phase[0] = 0.0f;
s.phase[1] = 0.0f;
s.phase[2] = 0.0f;
s.phase[3] = 0.0f;
}
void OSC_NOTEOFF(const user_osc_param_t * const params) { }