Skip to content
Snippets Groups Projects
Commit 90bf0a46 authored by moon2's avatar moon2 :speech_balloon:
Browse files

bl00mbox: plugin stuff

parent 25a28944
No related branches found
No related tags found
1 merge request!72bl00mbox: plugin stuff
Pipeline #6252 passed
Showing
with 628 additions and 130 deletions
......@@ -7,17 +7,21 @@ idf_component_register(
bl00mbox_user.c
bl00mbox_plugin_registry.c
bl00mbox_radspa_requirements.c
plugins/trad_synth/trad_synth.c
plugins/osc_fm.c
plugins/env_adsr.c
plugins/ampliverter.c
plugins/sampler.c
plugins/delay.c
plugins/flanger.c
plugins/sequencer.c
plugins/noise.c
radspa/radspa_helpers.c
extern/xoroshiro64star.c
INCLUDE_DIRS
include
plugins/trad_synth
plugins
radspa
extern
REQUIRES
st3m
)
## welcome to bl00mbox!
bl00mbox is a radspa plugin host that manages rendering as well as routing. it provides several high-level
features to reduce computational load of inactive sources in complex systems.
## naming conventions
bl00mbox_audio_* functions are to be called only from within the audio task. There are no thread safety
features.
bl00mbox_tween_* functions are to be called only when the audio task idles. Since the audio task can block
on them briefly they should not be run at high priority.
## rendering
The rendering engine produces samples as requested by a host task.
### channels
On the topmost layer, all plugins are assigned to a channel, representing different patches, applications and
similar. There is a limited amount of channels as defined by BL00MBOX_CHANNELS where channel 0 is reserved
for system sounds. The host UI provides applications with a channel number that they can use to freely
spawn plugin instances. This allows bl00mbox to not render a channel if the application is suspended, or
free it entirely if the application is closed while allowing for fast application switching without having
to rebuild plugins and routing structure.
The default policy for the time being is that only the last channel that has received data input as well as
the system channel 0 are rendered, but different policies such as individual background overrides may be
implemented.
### buds
All plugins are wrapped into buds which provide additional information, such as the assigned channel.
### radspa
Plugins are provided in the custom radspa format as defined in radspa.h. For the current scope of bl00mbox
all signals such as audio data, pitch or events is represented as int16_t values. While this allows for high
flexibility with signal routing, this means common signals such as note start/end are encoded. These encodings
are visible to bl00mbox via radspa signal hints. A basic radspa plugin descriptor with name, description and
an instance constructor function is provided to the host.
The specific number of signals is dynamic and thus is not part of the descriptor. Each plugin may take one
initialization variable (for example for numbers of channels in a polysynth or length of a delay buffer).
Support for multiple initialization variables is planned for the future.
### routing
Routing is generally slow and is performed in-between buffer generation.
### the render tree
Each channel has its own render tree. Each node on the tree is a bud. A single bud, often a mixer, is attached
to the tree and always rendered if the tree is active. Each bud may request signals from other buds during
rendering, in which case that bud is rendered. For example, the root bud could be an envelope generator that
receives audio from an oscillator child bud. If the envelope generator is in a "mute" state, the child bud
is never evaluated, thus providing an early return.
## modifying routing and parameters
bl00mbox provides a thread safe API for routing and parameter changes.
## known weaknesses
Due to the nested evaluation of the render tree the stack size may grow large.
### examples
run these in ur repl :D!
## bass line
```
import bl00mbox
c = bl00mbox.Channel()
c.volume = 10000
osc1 = c.new_bud(420)
env1 = c.new_bud(42)
env1.signals.output.value = c.output
env1.signals.input.value = osc1.signals.output
osc2 = c.new_bud(420)
env2 = c.new_bud(42)
env2.signals.input.value = osc2.signals.output
amp1 = c.new_bud(69)
amp1.signals.input.value = env2.signals.output
amp1.signals.output.value = osc1.signals.lin_fm
env1.signals.sustain.value = 0
env2.signals.sustain.value = 0
env1.signals.attack.value = 10
env2.signals.attack.value = 100
env1.signals.decay.value = 800
env2.signals.decay.value = 800
osc1.signals.pitch.tone = -12
osc2.signals.pitch.tone = -24
osc3 = c.new_bud(420)
osc3.signals.waveform.value = 0
osc3.signals.pitch.tone = -100
osc3.signals.output.value = env1.signals.trigger
osc3.signals.output.value = env2.signals.trigger
osc4 = c.new_bud(420)
osc4.signals.waveform.value = 32767
osc4.signals.pitch.tone = -124
amp2 = c.new_bud(69)
amp2.signals.input.value = osc4.signals.output
amp2.signals.bias.value = 18376 - 2400
amp2.signals.gain.value = 300
amp2.signals.output.value = osc1.signals.pitch
amp3 = c.new_bud(69)
amp3.signals.input.value = amp2.signals.output
amp3.signals.bias.value = - 2400
amp3.signals.gain.value = 31000
amp3.signals.output.value = osc2.signals.pitch
osc2.signals.output.value = c.output
```
......@@ -87,21 +87,25 @@ radspa_descriptor_t * bl00mbox_plugin_registry_get_id_from_index(uint32_t index)
* removing plugins from the registry at runtime is not intended.
*/
#include "trad_synth.h"
#include "osc_fm.h"
#include "env_adsr.h"
#include "ampliverter.h"
#include "delay.h"
//#include "filter.h"
//#include "sequence_timer.h"
#include "sequencer.h"
#include "sampler.h"
#include "flanger.h"
#include "noise.h"
void bl00mbox_plugin_registry_init(void){
if(bl00mbox_plugin_registry_is_initialized) return;
plugin_add(&trad_osc_desc);
plugin_add(&osc_fm_desc);
plugin_add(&ampliverter_desc);
plugin_add(&trad_env_desc);
plugin_add(&env_adsr_desc);
plugin_add(&delay_desc);
// plugin_add(&filter_desc);
// plugin_add(&sequence_timer_desc);
plugin_add(&sequencer_desc);
plugin_add(&sampler_desc);
plugin_add(&flanger_desc);
plugin_add(&noise_desc);
}
......@@ -93,3 +93,5 @@ int16_t radspa_trigger_get(int16_t trigger_signal, int16_t * hist){
(* hist) = trigger_signal;
return ret;
}
int16_t radspa_random(){ return xoroshiro64star()>>16; }
/* Written in 2016 by David Blackman and Sebastiano Vigna (vigna@acm.org)
To the extent possible under law, the author has dedicated all copyright
and related and neighboring rights to this software to the public domain
worldwide. This software is distributed without any warranty.
See <http://creativecommons.org/publicdomain/zero/1.0/>. */
#include <stdint.h>
/* This is xoroshiro64* 1.0, our best and fastest 32-bit small-state
generator for 32-bit floating-point numbers. We suggest to use its
upper bits for floating-point generation, as it is slightly faster than
xoroshiro64**. It passes all tests we are aware of except for linearity
tests, as the lowest six bits have low linear complexity, so if low
linear complexity is not considered an issue (as it is usually the
case) it can be used to generate 32-bit outputs, too.
We suggest to use a sign test to extract a random Boolean value, and
right shifts to extract subsets of bits.
The state must be seeded so that it is not everywhere zero. */
static inline uint32_t rotl(const uint32_t x, int k) {
return (x << k) | (x >> (32 - k));
}
// mod1: state seed
static uint32_t s[2] = {420, 69};
//uint32_t next(void) {
uint32_t xoroshiro64star(void) {
// mod2: changed name to avoid namespace collisions.
const uint32_t s0 = s[0];
uint32_t s1 = s[1];
const uint32_t result = s0 * 0x9E3779BB;
s1 ^= s0;
s[0] = rotl(s0, 26) ^ s1 ^ (s1 << 9); // a, b
s[1] = rotl(s1, 13); // c
return result;
}
#pragma once
uint32_t xoroshiro64star(void);
......@@ -3,4 +3,5 @@
#include "bl00mbox_audio.h"
#include "radspa.h"
#include "radspa_helpers.h"
#include "xoroshiro64star.h"
#include "trad_synth.h"
#include "env_adsr.h"
// plugin descriptions in trad_synth.h
static inline int16_t waveshaper(int16_t saw, int16_t shape);
// plugin: trad_osc
radspa_descriptor_t trad_osc_desc = {
.name = "osc_fm",
.id = 420,
.description = "simple audio band oscillator with classic waveforms",
.create_plugin_instance = trad_osc_create,
.destroy_plugin_instance = radspa_standard_plugin_destroy
};
#define TRAD_OSC_NUM_SIGNALS 4
#define TRAD_OSC_OUTPUT 0
#define TRAD_OSC_PITCH 1
#define TRAD_OSC_WAVEFORM 2
#define TRAD_OSC_LIN_FM 3
radspa_t * trad_osc_create(uint32_t init_var){
radspa_t * trad_osc = radspa_standard_plugin_create(&trad_osc_desc, TRAD_OSC_NUM_SIGNALS, sizeof(trad_osc_data_t), 0);
trad_osc->render = trad_osc_run;
radspa_signal_set(trad_osc, TRAD_OSC_OUTPUT, "output", RADSPA_SIGNAL_HINT_OUTPUT, 0);
radspa_signal_set(trad_osc, TRAD_OSC_PITCH, "pitch", RADSPA_SIGNAL_HINT_INPUT | RADSPA_SIGNAL_HINT_SCT, 18367);
radspa_signal_set(trad_osc, TRAD_OSC_WAVEFORM, "waveform", RADSPA_SIGNAL_HINT_INPUT, -16000);
radspa_signal_set(trad_osc, TRAD_OSC_LIN_FM, "lin_fm", RADSPA_SIGNAL_HINT_INPUT, 0);
return trad_osc;
}
void trad_osc_run(radspa_t * trad_osc, uint16_t num_samples, uint32_t render_pass_id){
trad_osc_data_t * plugin_data = trad_osc->plugin_data;
radspa_signal_t * output_sig = radspa_signal_get_by_index(trad_osc, TRAD_OSC_OUTPUT);
radspa_signal_t * pitch_sig = radspa_signal_get_by_index(trad_osc, TRAD_OSC_PITCH);
radspa_signal_t * waveform_sig = radspa_signal_get_by_index(trad_osc, TRAD_OSC_WAVEFORM);
radspa_signal_t * lin_fm_sig = radspa_signal_get_by_index(trad_osc, TRAD_OSC_LIN_FM);
if(output_sig->buffer == NULL) return;
int16_t ret = 0;
for(uint16_t i = 0; i < num_samples; i++){
int16_t pitch = pitch_sig->get_value(pitch_sig, i, num_samples, render_pass_id);
int16_t wave = waveform_sig->get_value(waveform_sig, i, num_samples, render_pass_id);
int32_t lin_fm = lin_fm_sig->get_value(lin_fm_sig, i, num_samples, render_pass_id);
if(pitch != plugin_data->prev_pitch){
plugin_data->incr = radspa_sct_to_rel_freq(pitch, 0);
plugin_data->prev_pitch = pitch;
}
plugin_data->counter += plugin_data->incr;
if(lin_fm){
plugin_data->counter += lin_fm * (plugin_data->incr >> 15);
}
int32_t tmp = (plugin_data->counter) >> 17;
tmp = (tmp*2) - 32767;
ret = waveshaper(tmp, wave);
(output_sig->buffer)[i] = ret;
}
output_sig->value = ret;
}
static inline int16_t triangle(int16_t saw){
int32_t tmp = saw;
tmp += 16384;
if(tmp > 32767) tmp -= 65535;
if(tmp > 0) tmp = -tmp;
tmp = (2 * tmp) + 32767;
return tmp;
}
inline int16_t waveshaper(int16_t saw, int16_t shape){
int32_t tmp = saw;
uint8_t sh = ((uint16_t) shape) >> 14;
sh = (sh + 2)%4;
switch(sh){
case 0: //sine
tmp = triangle(tmp);
if(tmp > 0){
tmp = 32767 - tmp;
tmp = (tmp*tmp)>>15;
tmp = 32767. - tmp;
} else {
tmp = 32767 + tmp;
tmp = (tmp*tmp)>>15;
tmp = tmp - 32767.;
}
break;
case 1: //tri
tmp = triangle(tmp);
break;
case 2: //square:
if(tmp > 0){
tmp = 32767;
} else {
tmp = -32767;
}
break;
default: //saw
break;
}
return tmp;
}
// plugin: trad_env
radspa_descriptor_t trad_env_desc = {
radspa_descriptor_t env_adsr_desc = {
.name = "env_adsr",
.id = 42,
.description = "simple ADSR envelope",
.create_plugin_instance = trad_env_create,
.create_plugin_instance = env_adsr_create,
.destroy_plugin_instance = radspa_standard_plugin_destroy
};
#define TRAD_ENV_NUM_SIGNALS 9
#define TRAD_ENV_OUTPUT 0
#define TRAD_ENV_PHASE 1
#define TRAD_ENV_INPUT 2
#define TRAD_ENV_TRIGGER 3
#define TRAD_ENV_ATTACK 4
#define TRAD_ENV_DECAY 5
#define TRAD_ENV_SUSTAIN 6
#define TRAD_ENV_RELEASE 7
#define TRAD_ENV_GATE 8
#define TRAD_ENV_PHASE_OFF 0
#define TRAD_ENV_PHASE_ATTACK 1
#define TRAD_ENV_PHASE_DECAY 2
#define TRAD_ENV_PHASE_SUSTAIN 3
#define TRAD_ENV_PHASE_RELEASE 4
radspa_t * trad_env_create(uint32_t init_var){
radspa_t * trad_env = radspa_standard_plugin_create(&trad_env_desc, TRAD_ENV_NUM_SIGNALS, sizeof(trad_env_data_t),0);
trad_env->render = trad_env_run;
radspa_signal_set(trad_env, TRAD_ENV_OUTPUT, "output", RADSPA_SIGNAL_HINT_OUTPUT, 0);
radspa_signal_set(trad_env, TRAD_ENV_PHASE, "phase", RADSPA_SIGNAL_HINT_OUTPUT, 0);
radspa_signal_set(trad_env, TRAD_ENV_INPUT, "input", RADSPA_SIGNAL_HINT_INPUT, 32767);
radspa_signal_set(trad_env, TRAD_ENV_TRIGGER, "trigger", RADSPA_SIGNAL_HINT_INPUT | RADSPA_SIGNAL_HINT_TRIGGER, 0);
radspa_signal_set(trad_env, TRAD_ENV_ATTACK, "attack", RADSPA_SIGNAL_HINT_INPUT, 100);
radspa_signal_set(trad_env, TRAD_ENV_DECAY, "decay", RADSPA_SIGNAL_HINT_INPUT, 250);
radspa_signal_set(trad_env, TRAD_ENV_SUSTAIN, "sustain", RADSPA_SIGNAL_HINT_INPUT, 16000);
radspa_signal_set(trad_env, TRAD_ENV_RELEASE, "release", RADSPA_SIGNAL_HINT_INPUT, 50);
radspa_signal_set(trad_env, TRAD_ENV_GATE, "gate", RADSPA_SIGNAL_HINT_INPUT,0);
radspa_signal_get_by_index(trad_env, TRAD_ENV_ATTACK)->unit = "ms";
radspa_signal_get_by_index(trad_env, TRAD_ENV_DECAY)->unit = "ms";
radspa_signal_get_by_index(trad_env, TRAD_ENV_SUSTAIN)->unit = "ms";
trad_env_data_t * data = trad_env->plugin_data;
#define ENV_ADSR_NUM_SIGNALS 9
#define ENV_ADSR_OUTPUT 0
#define ENV_ADSR_PHASE 1
#define ENV_ADSR_INPUT 2
#define ENV_ADSR_TRIGGER 3
#define ENV_ADSR_ATTACK 4
#define ENV_ADSR_DECAY 5
#define ENV_ADSR_SUSTAIN 6
#define ENV_ADSR_RELEASE 7
#define ENV_ADSR_GATE 8
#define ENV_ADSR_PHASE_OFF 0
#define ENV_ADSR_PHASE_ATTACK 1
#define ENV_ADSR_PHASE_DECAY 2
#define ENV_ADSR_PHASE_SUSTAIN 3
#define ENV_ADSR_PHASE_RELEASE 4
radspa_t * env_adsr_create(uint32_t init_var){
radspa_t * env_adsr = radspa_standard_plugin_create(&env_adsr_desc, ENV_ADSR_NUM_SIGNALS, sizeof(env_adsr_data_t),0);
env_adsr->render = env_adsr_run;
radspa_signal_set(env_adsr, ENV_ADSR_OUTPUT, "output", RADSPA_SIGNAL_HINT_OUTPUT, 0);
radspa_signal_set(env_adsr, ENV_ADSR_PHASE, "phase", RADSPA_SIGNAL_HINT_OUTPUT, 0);
radspa_signal_set(env_adsr, ENV_ADSR_INPUT, "input", RADSPA_SIGNAL_HINT_INPUT, 32767);
radspa_signal_set(env_adsr, ENV_ADSR_TRIGGER, "trigger", RADSPA_SIGNAL_HINT_INPUT | RADSPA_SIGNAL_HINT_TRIGGER, 0);
radspa_signal_set(env_adsr, ENV_ADSR_ATTACK, "attack", RADSPA_SIGNAL_HINT_INPUT, 100);
radspa_signal_set(env_adsr, ENV_ADSR_DECAY, "decay", RADSPA_SIGNAL_HINT_INPUT, 250);
radspa_signal_set(env_adsr, ENV_ADSR_SUSTAIN, "sustain", RADSPA_SIGNAL_HINT_INPUT, 16000);
radspa_signal_set(env_adsr, ENV_ADSR_RELEASE, "release", RADSPA_SIGNAL_HINT_INPUT, 50);
radspa_signal_set(env_adsr, ENV_ADSR_GATE, "gate", RADSPA_SIGNAL_HINT_INPUT,0);
radspa_signal_get_by_index(env_adsr, ENV_ADSR_ATTACK)->unit = "ms";
radspa_signal_get_by_index(env_adsr, ENV_ADSR_DECAY)->unit = "ms";
radspa_signal_get_by_index(env_adsr, ENV_ADSR_SUSTAIN)->unit = "ms";
env_adsr_data_t * data = env_adsr->plugin_data;
data->trigger_prev = 0;
data->env_phase = TRAD_ENV_PHASE_OFF;
data->env_phase = ENV_ADSR_PHASE_OFF;
return trad_env;
return env_adsr;
}
static int16_t trad_env_run_single(trad_env_data_t * env){
static int16_t env_adsr_run_single(env_adsr_data_t * env){
uint32_t tmp;
switch(env->env_phase){
case TRAD_ENV_PHASE_OFF:
case ENV_ADSR_PHASE_OFF:
env->env_counter = 0;;
break;
case TRAD_ENV_PHASE_ATTACK:
case ENV_ADSR_PHASE_ATTACK:
tmp = env->env_counter + env->attack;
if(tmp < env->env_counter){ // overflow
tmp = ~((uint32_t) 0); // max out
env->env_phase = TRAD_ENV_PHASE_DECAY;
env->env_phase = ENV_ADSR_PHASE_DECAY;
}
env->env_counter = tmp;
break;
case TRAD_ENV_PHASE_DECAY:
case ENV_ADSR_PHASE_DECAY:
tmp = env->env_counter - env->decay;
if(tmp > env->env_counter){ // underflow
tmp = 0; //bottom out
......@@ -174,27 +71,27 @@ static int16_t trad_env_run_single(trad_env_data_t * env){
if(env->env_counter <= env->sustain){
env->env_counter = env->sustain;
env->env_phase = TRAD_ENV_PHASE_SUSTAIN;
env->env_phase = ENV_ADSR_PHASE_SUSTAIN;
} else if(env->env_counter < env->gate){
env->env_counter = 0;
env->env_phase = TRAD_ENV_PHASE_OFF;
env->env_phase = ENV_ADSR_PHASE_OFF;
}
break;
case TRAD_ENV_PHASE_SUSTAIN:
if(env->sustain == 0) env->env_phase = TRAD_ENV_PHASE_OFF;
case ENV_ADSR_PHASE_SUSTAIN:
if(env->sustain == 0) env->env_phase = ENV_ADSR_PHASE_OFF;
env->env_counter = env->sustain;
break;
case TRAD_ENV_PHASE_RELEASE:
case ENV_ADSR_PHASE_RELEASE:
tmp = env->env_counter - env->release;
if(tmp > env->env_counter){ // underflow
tmp = 0; //bottom out
env->env_phase = TRAD_ENV_PHASE_OFF;
env->env_phase = ENV_ADSR_PHASE_OFF;
}
env->env_counter = tmp;
/*
if(env->env_counter < env->gate){
env->env_counter = 0;
env->env_phase = TRAD_ENV_PHASE_OFF;
env->env_phase = ENV_ADSR_PHASE_OFF;
}
*/
break;
......@@ -203,10 +100,10 @@ static int16_t trad_env_run_single(trad_env_data_t * env){
}
#define SAMPLE_RATE_SORRY 48000
#define TRAD_ENV_UNDERSAMPLING 5
#define ENV_ADSR_UNDERSAMPLING 5
static inline uint32_t trad_env_time_ms_to_val_rise(uint16_t time_ms, uint32_t val){
static inline uint32_t env_adsr_time_ms_to_val_rise(uint16_t time_ms, uint32_t val){
if(!time_ms) return UINT32_MAX;
uint32_t div = time_ms * ((SAMPLE_RATE_SORRY)/1000);
return val/div;
......@@ -219,13 +116,13 @@ static inline uint32_t uint32_sat_leftshift(uint32_t input, uint16_t left){
}
void trad_env_run(radspa_t * trad_env, uint16_t num_samples, uint32_t render_pass_id){
trad_env_data_t * plugin_data = trad_env->plugin_data;
radspa_signal_t * output_sig = radspa_signal_get_by_index(trad_env, TRAD_ENV_OUTPUT);
radspa_signal_t * phase_sig = radspa_signal_get_by_index(trad_env, TRAD_ENV_PHASE);
void env_adsr_run(radspa_t * env_adsr, uint16_t num_samples, uint32_t render_pass_id){
env_adsr_data_t * plugin_data = env_adsr->plugin_data;
radspa_signal_t * output_sig = radspa_signal_get_by_index(env_adsr, ENV_ADSR_OUTPUT);
radspa_signal_t * phase_sig = radspa_signal_get_by_index(env_adsr, ENV_ADSR_PHASE);
if((output_sig->buffer == NULL) && (phase_sig->buffer == NULL)) return;
radspa_signal_t * trigger_sig = radspa_signal_get_by_index(trad_env, TRAD_ENV_TRIGGER);
radspa_signal_t * input_sig = radspa_signal_get_by_index(trad_env, TRAD_ENV_INPUT);
radspa_signal_t * trigger_sig = radspa_signal_get_by_index(env_adsr, ENV_ADSR_TRIGGER);
radspa_signal_t * input_sig = radspa_signal_get_by_index(env_adsr, ENV_ADSR_INPUT);
radspa_signal_t * attack_sig = NULL;
radspa_signal_t * decay_sig = NULL;
radspa_signal_t * sustain_sig = NULL;
......@@ -241,78 +138,78 @@ void trad_env_run(radspa_t * trad_env, uint16_t num_samples, uint32_t render_pas
int16_t vel = radspa_trigger_get(trigger, &(plugin_data->trigger_prev));
if(vel < 0){ // stop
if(plugin_data->env_phase != TRAD_ENV_PHASE_OFF){
plugin_data->env_phase = TRAD_ENV_PHASE_RELEASE;
if(plugin_data->env_phase != ENV_ADSR_PHASE_OFF){
plugin_data->env_phase = ENV_ADSR_PHASE_RELEASE;
plugin_data->release_init_val = plugin_data->env_counter;
}
} else if(vel > 0 ){ // start
plugin_data->env_phase = TRAD_ENV_PHASE_ATTACK;
plugin_data->env_phase = ENV_ADSR_PHASE_ATTACK;
plugin_data->velocity = ((uint32_t) vel) << 17;
}
if(!(i%(1<<TRAD_ENV_UNDERSAMPLING))){
if(!(i%(1<<ENV_ADSR_UNDERSAMPLING))){
uint16_t time_ms;
uint32_t sus;
switch(plugin_data->env_phase){
case TRAD_ENV_PHASE_OFF:
case ENV_ADSR_PHASE_OFF:
break;
case TRAD_ENV_PHASE_ATTACK:
case ENV_ADSR_PHASE_ATTACK:
if(attack_sig == NULL){
attack_sig = radspa_signal_get_by_index(trad_env, TRAD_ENV_ATTACK);
attack_sig = radspa_signal_get_by_index(env_adsr, ENV_ADSR_ATTACK);
}
time_ms = attack_sig->get_value(attack_sig, i, num_samples, render_pass_id);
if(time_ms != plugin_data->attack_prev_ms){
plugin_data->attack = uint32_sat_leftshift(trad_env_time_ms_to_val_rise(time_ms, UINT32_MAX), TRAD_ENV_UNDERSAMPLING);
plugin_data->attack = uint32_sat_leftshift(env_adsr_time_ms_to_val_rise(time_ms, UINT32_MAX), ENV_ADSR_UNDERSAMPLING);
plugin_data->attack_prev_ms = time_ms;
}
break;
case TRAD_ENV_PHASE_DECAY:
case ENV_ADSR_PHASE_DECAY:
if(sustain_sig == NULL){
sustain_sig = radspa_signal_get_by_index(trad_env, TRAD_ENV_SUSTAIN);
sustain_sig = radspa_signal_get_by_index(env_adsr, ENV_ADSR_SUSTAIN);
}
sus = sustain_sig->get_value(sustain_sig, i, num_samples, render_pass_id);
plugin_data->sustain = sus<<17;
if(gate_sig == NULL){
gate_sig = radspa_signal_get_by_index(trad_env, TRAD_ENV_GATE);
gate_sig = radspa_signal_get_by_index(env_adsr, ENV_ADSR_GATE);
}
sus = gate_sig->get_value(gate_sig, i, num_samples, render_pass_id);
plugin_data->gate = sus<<17;
if(decay_sig == NULL){
decay_sig = radspa_signal_get_by_index(trad_env, TRAD_ENV_DECAY);
decay_sig = radspa_signal_get_by_index(env_adsr, ENV_ADSR_DECAY);
}
time_ms = decay_sig->get_value(decay_sig, i, num_samples, render_pass_id);
if(time_ms != plugin_data->decay_prev_ms){
plugin_data->decay = uint32_sat_leftshift(trad_env_time_ms_to_val_rise(time_ms, UINT32_MAX-plugin_data->sustain), TRAD_ENV_UNDERSAMPLING);
plugin_data->decay = uint32_sat_leftshift(env_adsr_time_ms_to_val_rise(time_ms, UINT32_MAX-plugin_data->sustain), ENV_ADSR_UNDERSAMPLING);
plugin_data->decay_prev_ms = time_ms;
}
break;
case TRAD_ENV_PHASE_SUSTAIN:
case ENV_ADSR_PHASE_SUSTAIN:
if(sustain_sig == NULL){
sustain_sig = radspa_signal_get_by_index(trad_env, TRAD_ENV_SUSTAIN);
sustain_sig = radspa_signal_get_by_index(env_adsr, ENV_ADSR_SUSTAIN);
}
sus = sustain_sig->get_value(sustain_sig, i, num_samples, render_pass_id);
plugin_data->sustain = sus<<17;
break;
case TRAD_ENV_PHASE_RELEASE:
case ENV_ADSR_PHASE_RELEASE:
if(gate_sig == NULL){
gate_sig = radspa_signal_get_by_index(trad_env, TRAD_ENV_GATE);
gate_sig = radspa_signal_get_by_index(env_adsr, ENV_ADSR_GATE);
}
sus = gate_sig->get_value(gate_sig, i, num_samples, render_pass_id);
plugin_data->gate = sus<<17;
if(release_sig == NULL){
release_sig = radspa_signal_get_by_index(trad_env, TRAD_ENV_RELEASE);
release_sig = radspa_signal_get_by_index(env_adsr, ENV_ADSR_RELEASE);
}
time_ms = release_sig->get_value(release_sig, i, num_samples, render_pass_id);
if(time_ms != plugin_data->release_prev_ms){
plugin_data->release = uint32_sat_leftshift(trad_env_time_ms_to_val_rise(time_ms, plugin_data->release_init_val), TRAD_ENV_UNDERSAMPLING);
plugin_data->release = uint32_sat_leftshift(env_adsr_time_ms_to_val_rise(time_ms, plugin_data->release_init_val), ENV_ADSR_UNDERSAMPLING);
plugin_data->release_prev_ms = time_ms;
}
break;
}
env = trad_env_run_single(plugin_data);
env = env_adsr_run_single(plugin_data);
}
if(env){
int16_t input = input_sig->get_value(input_sig, i, num_samples, render_pass_id);
......
#pragma once
#include <math.h>
#include "radspa.h"
#include "radspa_helpers.h"
/* provides traditional synthesizer functionality distributed over several plugins.
*/
/* plugin: trad_osc
* oscillator that can generate sine, square, saw and triangle waves in the audio band. uses trad_wave.
*/
typedef struct {
uint32_t counter;
int16_t prev_pitch;
int32_t incr;
} trad_osc_data_t;
extern radspa_descriptor_t trad_osc_desc;
radspa_t * trad_osc_create(uint32_t init_var);
void trad_osc_run(radspa_t * osc, uint16_t num_samples, uint32_t render_pass_id);
typedef struct {
uint32_t env_counter;
uint32_t attack;
......@@ -35,9 +17,9 @@ typedef struct {
uint8_t env_phase;
uint8_t skip_hold;
int16_t trigger_prev;
} trad_env_data_t;
} env_adsr_data_t;
extern radspa_descriptor_t trad_env_desc;
radspa_t * trad_env_create(uint32_t init_var);
void trad_env_run(radspa_t * osc, uint16_t num_samples, uint32_t render_pass_id);
extern radspa_descriptor_t env_adsr_desc;
radspa_t * env_adsr_create(uint32_t init_var);
void env_adsr_run(radspa_t * osc, uint16_t num_samples, uint32_t render_pass_id);
#include "flanger.h"
radspa_t * flanger_create(uint32_t init_var);
radspa_descriptor_t flanger_desc = {
.name = "flanger",
.id = 123,
.description = "flanger with subsample interpolation and negative mix/resonance capability.\
does not come with lfo.",
.create_plugin_instance = flanger_create,
.destroy_plugin_instance = radspa_standard_plugin_destroy
};
#define FLANGER_BUFFER_SIZE 4800
#define FIXED_POINT_DIGITS 4
#define VARIABLE_NAME ((FLANGER_BUFFER_SIZE)<<(FIXED_POINT_DIGITS))
#define FLANGER_NUM_SIGNALS 6
#define FLANGER_OUTPUT 0
#define FLANGER_INPUT 1
#define FLANGER_MANUAL 2
#define FLANGER_RESONANCE 3
#define FLANGER_LEVEL 4
#define FLANGER_MIX 5
static int16_t fixed_point_list_access(int32_t * buf, uint32_t fp_index, uint32_t buf_len){
uint32_t index = (fp_index) >> (FIXED_POINT_DIGITS);
while(index >= buf_len) index -= buf_len;
uint32_t next_index = index + 1;
while(next_index >= buf_len) next_index -= buf_len;
uint32_t subindex = (fp_index) & ((1<<(FIXED_POINT_DIGITS)) - 1);
int32_t ret = buf[index] * ((1<<(FIXED_POINT_DIGITS)) - subindex);
ret += (buf[next_index]) * subindex;
ret = ret >> (FIXED_POINT_DIGITS);
return radspa_clip(ret);
}
void flanger_run(radspa_t * flanger, uint16_t num_samples, uint32_t render_pass_id){
radspa_signal_t * output_sig = radspa_signal_get_by_index(flanger, FLANGER_OUTPUT);
if(output_sig->buffer == NULL) return;
flanger_data_t * data = flanger->plugin_data;
int32_t * buf = flanger->plugin_table;
radspa_signal_t * input_sig = radspa_signal_get_by_index(flanger, FLANGER_INPUT);
radspa_signal_t * manual_sig = radspa_signal_get_by_index(flanger, FLANGER_MANUAL);
radspa_signal_t * reso_sig = radspa_signal_get_by_index(flanger, FLANGER_RESONANCE);
radspa_signal_t * level_sig = radspa_signal_get_by_index(flanger, FLANGER_LEVEL);
radspa_signal_t * mix_sig = radspa_signal_get_by_index(flanger, FLANGER_MIX);
static int16_t ret = 0;
for(uint16_t i = 0; i < num_samples; i++){
int32_t manual = manual_sig->get_value(manual_sig, i, num_samples, render_pass_id);
if(manual != data->manual_prev){
// index propto 1/radspa_sct_to_rel_freq(manual) -> signflip faster
int32_t invert = ((2400*(FIXED_POINT_DIGITS)) - 7572) - manual;
uint32_t rho = radspa_sct_to_rel_freq(radspa_clip(invert), 0);
if(rho > VARIABLE_NAME) rho = VARIABLE_NAME;
data->read_head_offset = rho;
}
data->manual_prev = manual;
data->write_head_position++;
while(data->write_head_position >= FLANGER_BUFFER_SIZE) data->write_head_position -= FLANGER_BUFFER_SIZE;
data->read_head_position = ((data->write_head_position)<<(FIXED_POINT_DIGITS));
data->read_head_position -= data->read_head_offset;
while(data->read_head_position < 0) data->read_head_position += VARIABLE_NAME; //underflow
int32_t dry = input_sig->get_value(input_sig, i, num_samples, render_pass_id);
int16_t reso = reso_sig->get_value(reso_sig, i, num_samples, render_pass_id);
int16_t level = level_sig->get_value(level_sig, i, num_samples, render_pass_id);
int16_t mix = mix_sig->get_value(mix_sig, i, num_samples, render_pass_id);
buf[data->write_head_position] = dry;
int32_t wet = fixed_point_list_access(buf, data->read_head_position, FLANGER_BUFFER_SIZE);
buf[data->write_head_position] += radspa_mult_shift(wet, reso);
int16_t dry_vol = (mix>0) ? (32767-mix) : (32767+mix); //always pos polarity
ret = radspa_add_sat(radspa_mult_shift(dry, dry_vol), radspa_mult_shift(radspa_clip(wet), mix));
ret = radspa_clip(radspa_mult_shift(ret, level));
(output_sig->buffer)[i] = ret;
}
output_sig->value = ret;
}
radspa_t * flanger_create(uint32_t init_var){
radspa_t * flanger = radspa_standard_plugin_create(&flanger_desc, FLANGER_NUM_SIGNALS, sizeof(flanger_data_t),
2 * FLANGER_BUFFER_SIZE);
if(flanger == NULL) return NULL;
flanger_data_t * plugin_data = flanger->plugin_data;
plugin_data->manual_prev = 40000;
flanger->render = flanger_run;
radspa_signal_set(flanger, FLANGER_OUTPUT, "output", RADSPA_SIGNAL_HINT_OUTPUT, 0);
radspa_signal_set(flanger, FLANGER_INPUT, "input", RADSPA_SIGNAL_HINT_INPUT, 0);
radspa_signal_set(flanger, FLANGER_MANUAL, "manual", RADSPA_SIGNAL_HINT_INPUT | RADSPA_SIGNAL_HINT_SCT, 18367);
radspa_signal_set(flanger, FLANGER_RESONANCE, "resonance", RADSPA_SIGNAL_HINT_INPUT, 16000);
radspa_signal_set(flanger, FLANGER_LEVEL, "level", RADSPA_SIGNAL_HINT_INPUT, 16000);
radspa_signal_set(flanger, FLANGER_MIX, "mix", RADSPA_SIGNAL_HINT_INPUT, 1<<14);
return flanger;
}
#undef FLANGER_DLY_GAIN
#undef FLANGER_BUFFER_SIZE
#undef FLANGER_NUM_SIGNALS
#undef FLANGER_OUTPUT
#undef FLANGER_INPUT
#undef FLANGER_MANUAL
#undef FLANGER_RESONANCE
#undef FLANGER_LEVEL
#undef FLANGER_MIX
#undef FIXED_POINT_DIGITS
/* delay_time = 1/freq
* delay_samples = delay_time * SAMPLE_RATE
* freq(sct) = pow(2, (sct + 2708)/2400))
* freq = sct_to_rel_freq(sct) * SAMPLE_RATE / UINT32_MAX
* delay_samples = UINT32_MAX / sct_to_rel_freq(sct)
* delay_samples = sct_to_rel_freq(-7572-sct + 2400 * FIXED_POINT_DIGITS)
*/
#pragma once
#include <radspa.h>
#include <radspa_helpers.h>
typedef struct {
uint32_t write_head_position;
int32_t read_head_position;
int32_t read_head_offset;
int32_t manual_prev;
} flanger_data_t;
extern radspa_descriptor_t flanger_desc;
radspa_t * flanger_create(uint32_t init_var);
void flanger_run(radspa_t * osc, uint16_t num_samples, uint32_t render_pass_id);
#include "noise.h"
radspa_t * noise_create(uint32_t init_var);
radspa_descriptor_t noise_desc = {
.name = "noise",
.id = 0,
.description = "random data",
.create_plugin_instance = noise_create,
.destroy_plugin_instance = radspa_standard_plugin_destroy
};
#define NOISE_NUM_SIGNALS 1
#define NOISE_OUTPUT 0
void noise_run(radspa_t * noise, uint16_t num_samples, uint32_t render_pass_id){
radspa_signal_t * output_sig = radspa_signal_get_by_index(noise, NOISE_OUTPUT);
if(output_sig->buffer == NULL) return;
static int16_t ret = 0;
for(uint16_t i = 0; i < num_samples; i++){
(output_sig->buffer)[i] = radspa_random();
}
output_sig->value = ret;
}
radspa_t * noise_create(uint32_t init_var){
radspa_t * noise = radspa_standard_plugin_create(&noise_desc, NOISE_NUM_SIGNALS, sizeof(char), 0);
if(noise == NULL) return NULL;
noise->render = noise_run;
radspa_signal_set(noise, NOISE_OUTPUT, "output", RADSPA_SIGNAL_HINT_OUTPUT, 0);
return noise;
}
#undef NOISE_NUM_SIGNALS
#undef NOISE_OUTPUT
#pragma once
#include <radspa.h>
#include <radspa_helpers.h>
extern radspa_descriptor_t noise_desc;
radspa_t * noise_create(uint32_t init_var);
void noise_run(radspa_t * osc, uint16_t num_samples, uint32_t render_pass_id);
#include "osc_fm.h"
static inline int16_t waveshaper(int16_t saw, int16_t shape);
radspa_descriptor_t osc_fm_desc = {
.name = "osc_fm",
.id = 420,
.description = "simple audio band oscillator with classic waveforms and linear fm input",
.create_plugin_instance = osc_fm_create,
.destroy_plugin_instance = radspa_standard_plugin_destroy
};
#define OSC_FM_NUM_SIGNALS 4
#define OSC_FM_OUTPUT 0
#define OSC_FM_PITCH 1
#define OSC_FM_WAVEFORM 2
#define OSC_FM_LIN_FM 3
radspa_t * osc_fm_create(uint32_t init_var){
radspa_t * osc_fm = radspa_standard_plugin_create(&osc_fm_desc, OSC_FM_NUM_SIGNALS, sizeof(osc_fm_data_t), 0);
osc_fm->render = osc_fm_run;
radspa_signal_set(osc_fm, OSC_FM_OUTPUT, "output", RADSPA_SIGNAL_HINT_OUTPUT, 0);
radspa_signal_set(osc_fm, OSC_FM_PITCH, "pitch", RADSPA_SIGNAL_HINT_INPUT | RADSPA_SIGNAL_HINT_SCT, 18367);
radspa_signal_set(osc_fm, OSC_FM_WAVEFORM, "waveform", RADSPA_SIGNAL_HINT_INPUT, -16000);
radspa_signal_set(osc_fm, OSC_FM_LIN_FM, "lin_fm", RADSPA_SIGNAL_HINT_INPUT, 0);
return osc_fm;
}
void osc_fm_run(radspa_t * osc_fm, uint16_t num_samples, uint32_t render_pass_id){
osc_fm_data_t * plugin_data = osc_fm->plugin_data;
radspa_signal_t * output_sig = radspa_signal_get_by_index(osc_fm, OSC_FM_OUTPUT);
radspa_signal_t * pitch_sig = radspa_signal_get_by_index(osc_fm, OSC_FM_PITCH);
radspa_signal_t * waveform_sig = radspa_signal_get_by_index(osc_fm, OSC_FM_WAVEFORM);
radspa_signal_t * lin_fm_sig = radspa_signal_get_by_index(osc_fm, OSC_FM_LIN_FM);
if(output_sig->buffer == NULL) return;
int16_t ret = 0;
for(uint16_t i = 0; i < num_samples; i++){
int16_t pitch = pitch_sig->get_value(pitch_sig, i, num_samples, render_pass_id);
int16_t wave = waveform_sig->get_value(waveform_sig, i, num_samples, render_pass_id);
int32_t lin_fm = lin_fm_sig->get_value(lin_fm_sig, i, num_samples, render_pass_id);
if(pitch != plugin_data->prev_pitch){
plugin_data->incr = radspa_sct_to_rel_freq(pitch, 0);
plugin_data->prev_pitch = pitch;
}
plugin_data->counter += plugin_data->incr;
if(lin_fm){
plugin_data->counter += lin_fm * (plugin_data->incr >> 15);
}
int32_t tmp = (plugin_data->counter) >> 17;
tmp = (tmp*2) - 32767;
ret = waveshaper(tmp, wave);
(output_sig->buffer)[i] = ret;
}
output_sig->value = ret;
}
static inline int16_t triangle(int16_t saw){
int32_t tmp = saw;
tmp += 16384;
if(tmp > 32767) tmp -= 65535;
if(tmp > 0) tmp = -tmp;
tmp = (2 * tmp) + 32767;
return tmp;
}
inline int16_t waveshaper(int16_t saw, int16_t shape){
int32_t tmp = saw;
uint8_t sh = ((uint16_t) shape) >> 14;
sh = (sh + 2)%4;
switch(sh){
case 0: //sine
tmp = triangle(tmp);
if(tmp > 0){
tmp = 32767 - tmp;
tmp = (tmp*tmp)>>15;
tmp = 32767. - tmp;
} else {
tmp = 32767 + tmp;
tmp = (tmp*tmp)>>15;
tmp = tmp - 32767.;
}
break;
case 1: //tri
tmp = triangle(tmp);
break;
case 2: //square:
if(tmp > 0){
tmp = 32767;
} else {
tmp = -32767;
}
break;
default: //saw
break;
}
return tmp;
}
#pragma once
#include "radspa.h"
#include "radspa_helpers.h"
typedef struct {
uint32_t counter;
int16_t prev_pitch;
int32_t incr;
} osc_fm_data_t;
extern radspa_descriptor_t osc_fm_desc;
radspa_t * osc_fm_create(uint32_t init_var);
void osc_fm_run(radspa_t * osc, uint16_t num_samples, uint32_t render_pass_id);
//SPDX-License-Identifier: CC0-1.0
// Version 0.1.0
// Please do not define a new version number. If want to distribute a modified version of
// this file, kindly append "-modified" to the version string below so it is not mistaken
// for an official release.
// Version 0.1.0+
/* Realtime Audio Developer's Simple Plugin Api
*
* Written from scratch but largely inspired by faint memories of the excellent ladspa.h
*
* Plugins may only include this file and the corresponding "radspa_helpers.h" with
* the same version string. Specifically, do not include <math.h> no matter how tempting
* it may be - it's a notoriously slow library on most architectures and has no place
* in realtime audio synthesis.
*
* For a simple plugin implementation example check ampliverter.c/.h :D
*/
......@@ -28,11 +38,14 @@
// signal hints
#define RADSPA_SIGNAL_HINT_INPUT 1
#define RADSPA_SIGNAL_HINT_OUTPUT 2
#define RADSPA_SIGNAL_HINT_TRIGGER 4
#define RADSPA_SIGNAL_HINT_INPUT (1<<0)
#define RADSPA_SIGNAL_HINT_OUTPUT (1<<1)
#define RADSPA_SIGNAL_HINT_TRIGGER (1<<2)
#define RADSPA_SIGNAL_HINT_VOL (1<<3)
#define RADSPA_SIGNAL_HINT_SCT (1<<5)
#define RADSPA_SIGNAL_HINT_SCT 32
#define RADSPA_SIGNAL_VAL_SCT_A440 (INT16_MAX - 6*2400)
#define RADSPA_SIGNAL_VAL_UNITY_GAIN (1<<11)
struct _radspa_descriptor_t;
struct _radspa_signal_t;
......@@ -107,3 +120,5 @@ extern int16_t radspa_mult_shift(int32_t a, int32_t b);
extern int16_t radspa_trigger_start(int16_t velocity, int16_t * hist);
extern int16_t radspa_trigger_stop(int16_t * hist);
extern int16_t radspa_trigger_get(int16_t trigger_signal, int16_t * hist);
extern int16_t radspa_random();
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment