very nuclear, rendering engine gone
This commit is contained in:
parent
7f43f8987b
commit
28b6dcb1ef
|
@ -22,7 +22,6 @@
|
|||
#include "nekobee-src/nekobee_synth.c"
|
||||
#include "nekobee-src/nekobee_voice.c"
|
||||
#include "nekobee-src/nekobee_voice_render.c"
|
||||
#include "nekobee-src/minblep_tables.c"
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// nekobee_handle_raw_event
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,414 +1,20 @@
|
|||
/* nekobee DSSI software synthesizer plugin
|
||||
*/
|
||||
|
||||
#define _BSD_SOURCE 1
|
||||
#define _SVID_SOURCE 1
|
||||
#define _ISOC99_SOURCE 1
|
||||
|
||||
#include <math.h>
|
||||
|
||||
#include "nekobee.h"
|
||||
#include "nekobee_synth.h"
|
||||
#include "nekobee_voice.h"
|
||||
|
||||
#ifndef M_PI
|
||||
#define M_PI 3.14159265358979323846
|
||||
#endif
|
||||
|
||||
#define M_2PI_F (2.0f * (float)M_PI)
|
||||
#define M_PI_F (float)M_PI
|
||||
|
||||
#define VCF_FREQ_MAX (0.825f) /* original filters only stable to this frequency */
|
||||
|
||||
static int tables_initialized = 0;
|
||||
|
||||
float nekobee_pitch[128];
|
||||
|
||||
#define pitch_ref_note 69
|
||||
|
||||
#define volume_to_amplitude_scale 128
|
||||
|
||||
static float volume_to_amplitude_table[4 + volume_to_amplitude_scale + 2];
|
||||
|
||||
static float velocity_to_attenuation[128];
|
||||
|
||||
static float qdB_to_amplitude_table[4 + 256 + 0];
|
||||
|
||||
void
|
||||
nekobee_init_tables(void)
|
||||
{
|
||||
int i;
|
||||
float pexp;
|
||||
float volume, volume_exponent;
|
||||
float ol, amp;
|
||||
|
||||
if (tables_initialized)
|
||||
return;
|
||||
|
||||
/* MIDI note to pitch */
|
||||
for (i = 0; i < 128; ++i) {
|
||||
pexp = (float)(i - pitch_ref_note) / 12.0f;
|
||||
nekobee_pitch[i] = powf(2.0f, pexp);
|
||||
}
|
||||
|
||||
/* volume to amplitude
|
||||
*
|
||||
* This generates a curve which is:
|
||||
* volume_to_amplitude_table[128 + 4] = 0.25 * 3.16... ~= -2dB
|
||||
* volume_to_amplitude_table[64 + 4] = 0.25 * 1.0 ~= -12dB
|
||||
* volume_to_amplitude_table[32 + 4] = 0.25 * 0.316... ~= -22dB
|
||||
* volume_to_amplitude_table[16 + 4] = 0.25 * 0.1 ~= -32dB
|
||||
* etc.
|
||||
*/
|
||||
volume_exponent = 1.0f / (2.0f * log10f(2.0f));
|
||||
for (i = 0; i <= volume_to_amplitude_scale; i++) {
|
||||
volume = (float)i / (float)volume_to_amplitude_scale;
|
||||
volume_to_amplitude_table[i + 4] = powf(2.0f * volume, volume_exponent) / 4.0f;
|
||||
}
|
||||
volume_to_amplitude_table[ -1 + 4] = 0.0f;
|
||||
volume_to_amplitude_table[129 + 4] = volume_to_amplitude_table[128 + 4];
|
||||
|
||||
/* velocity to attenuation
|
||||
* Copyright (C) 2023 Gordonjcp, with attributions inline
|
||||
*
|
||||
* Creates the velocity to attenuation lookup table, for converting
|
||||
* velocities [1, 127] to full-velocity-sensitivity attenuation in
|
||||
* quarter decibels. Modeled after my TX-7's velocity response.*/
|
||||
velocity_to_attenuation[0] = 253.9999f;
|
||||
for (i = 1; i < 127; i++) {
|
||||
if (i >= 10) {
|
||||
ol = (powf(((float)i / 127.0f), 0.32f) - 1.0f) * 100.0f;
|
||||
amp = powf(2.0f, ol / 8.0f);
|
||||
} else {
|
||||
ol = (powf(((float)10 / 127.0f), 0.32f) - 1.0f) * 100.0f;
|
||||
amp = powf(2.0f, ol / 8.0f) * (float)i / 10.0f;
|
||||
}
|
||||
velocity_to_attenuation[i] = log10f(amp) * -80.0f;
|
||||
}
|
||||
velocity_to_attenuation[127] = 0.0f;
|
||||
|
||||
/* quarter-decibel attenuation to amplitude */
|
||||
qdB_to_amplitude_table[-1 + 4] = 1.0f;
|
||||
for (i = 0; i <= 255; i++) {
|
||||
qdB_to_amplitude_table[i + 4] = powf(10.0f, (float)i / -80.0f);
|
||||
}
|
||||
|
||||
tables_initialized = 1;
|
||||
}
|
||||
|
||||
static inline float
|
||||
volume(float level)
|
||||
{
|
||||
unsigned char segment;
|
||||
float fract;
|
||||
|
||||
level *= (float)volume_to_amplitude_scale;
|
||||
segment = lrintf(level - 0.5f);
|
||||
fract = level - (float)segment;
|
||||
|
||||
return volume_to_amplitude_table[segment + 4] + fract *
|
||||
(volume_to_amplitude_table[segment + 5] -
|
||||
volume_to_amplitude_table[segment + 4]);
|
||||
}
|
||||
|
||||
static inline float
|
||||
qdB_to_amplitude(float qdB)
|
||||
{
|
||||
int i = lrintf(qdB - 0.5f);
|
||||
float f = qdB - (float)i;
|
||||
return qdB_to_amplitude_table[i + 4] + f *
|
||||
(qdB_to_amplitude_table[i + 5] -
|
||||
qdB_to_amplitude_table[i + 4]);
|
||||
}
|
||||
|
||||
void blosc_place_step_dd(float *buffer, int index, float phase, float w, float scale){
|
||||
float r;
|
||||
int i;
|
||||
|
||||
r = MINBLEP_PHASES * phase / w;
|
||||
i = lrintf(r - 0.5f);
|
||||
r -= (float)i;
|
||||
i &= MINBLEP_PHASE_MASK; /* port changes can cause i to be out-of-range */
|
||||
/* This would be better than the above, but more expensive:
|
||||
* while (i < 0) {
|
||||
* i += MINBLEP_PHASES;
|
||||
* index++;
|
||||
* }
|
||||
*/
|
||||
|
||||
while (i < MINBLEP_PHASES * STEP_DD_PULSE_LENGTH) {
|
||||
buffer[index] += scale * (step_dd_table[i].value + r * step_dd_table[i].delta);
|
||||
i += MINBLEP_PHASES;
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void vco(unsigned long sample_count, nekobee_voice_t *voice, struct blosc *osc,
|
||||
int index, float w)
|
||||
|
||||
{
|
||||
unsigned long sample;
|
||||
float pos = osc->pos;
|
||||
float pw, gain, halfgain, out;
|
||||
pw=0.46f;
|
||||
gain=1.0f;
|
||||
halfgain=gain*0.5f;
|
||||
int bp_high = osc->bp_high;
|
||||
out=(bp_high ? halfgain : -halfgain);
|
||||
|
||||
switch (osc->waveform)
|
||||
{
|
||||
default:
|
||||
case 0: {
|
||||
|
||||
for (sample = 0; sample < sample_count; sample++) {
|
||||
pos += w;
|
||||
if (bp_high) {
|
||||
if (pos >= pw) {
|
||||
blosc_place_step_dd(voice->osc_audio, index, pos - pw, w, -gain);
|
||||
bp_high = 0;
|
||||
out = -halfgain;
|
||||
}
|
||||
if (pos >= 1.0f) {
|
||||
pos -= 1.0f;
|
||||
blosc_place_step_dd(voice->osc_audio, index, pos, w, gain);
|
||||
bp_high = 1;
|
||||
out = halfgain;
|
||||
}
|
||||
} else {
|
||||
if (pos >= 1.0f) {
|
||||
pos -= 1.0f;
|
||||
blosc_place_step_dd(voice->osc_audio, index, pos, w, gain);
|
||||
bp_high = 1;
|
||||
out = halfgain;
|
||||
}
|
||||
|
||||
if (bp_high && pos >= pw) {
|
||||
blosc_place_step_dd(voice->osc_audio, index, pos - pw, w, -gain);
|
||||
bp_high = 0;
|
||||
out = -halfgain;
|
||||
}
|
||||
}
|
||||
|
||||
voice->osc_audio[index + DD_SAMPLE_DELAY] += out;
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
osc->pos = pos;
|
||||
osc->bp_high = bp_high;
|
||||
break;
|
||||
}
|
||||
case 1: // sawtooth wave
|
||||
{
|
||||
for (sample=0; sample < sample_count; sample++) {
|
||||
pos += w;
|
||||
if (pos >= 1.0f) {
|
||||
pos -= 1.0f;
|
||||
blosc_place_step_dd(voice->osc_audio, index, pos, w, gain);
|
||||
}
|
||||
voice->osc_audio[index + DD_SAMPLE_DELAY] += gain * (0.5f - pos);
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
osc->pos=pos;
|
||||
}
|
||||
|
||||
static inline void
|
||||
vcf_4pole(nekobee_voice_t *voice, unsigned long sample_count,
|
||||
float *in, float *out, float *cutoff, float qres, float *amp)
|
||||
{
|
||||
unsigned long sample;
|
||||
float freqcut, freqcut2, highpass,
|
||||
delay1 = voice->delay1,
|
||||
delay2 = voice->delay2,
|
||||
delay3 = voice->delay3,
|
||||
delay4 = voice->delay4;
|
||||
|
||||
qres = 2.0f - qres * 1.995f;
|
||||
|
||||
for (sample = 0; sample < sample_count; sample++) {
|
||||
|
||||
/* Hal Chamberlin's state variable filter */
|
||||
|
||||
freqcut = cutoff[sample] * 2.0f;
|
||||
freqcut2 = cutoff[sample] * 4.0f;
|
||||
|
||||
|
||||
if (freqcut > VCF_FREQ_MAX) freqcut = VCF_FREQ_MAX;
|
||||
if (freqcut2 > VCF_FREQ_MAX) freqcut2 = VCF_FREQ_MAX;
|
||||
|
||||
delay2 = delay2 + freqcut * delay1; /* delay2/4 = lowpass output */
|
||||
highpass = in[sample] - delay2 - qres * delay1;
|
||||
delay1 = freqcut * highpass + delay1; /* delay1/3 = bandpass output */
|
||||
|
||||
delay4 = delay4 + freqcut2 * delay3;
|
||||
highpass = delay2 - delay4 - qres * delay3;
|
||||
delay3 = freqcut2 * highpass + delay3;
|
||||
|
||||
/* mix filter output into output buffer */
|
||||
out[sample] += 0.1*atan(3*delay4 * amp[sample]);
|
||||
}
|
||||
|
||||
voice->delay1 = delay1;
|
||||
voice->delay2 = delay2;
|
||||
voice->delay3 = delay3;
|
||||
voice->delay4 = delay4;
|
||||
voice->c5 = 0.0f;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* nekobee_voice_render
|
||||
* 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 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* generate the actual sound data for this voice
|
||||
* 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, write to the Free
|
||||
* Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
|
||||
* MA 02111-1307, USA.
|
||||
*/
|
||||
void
|
||||
nekobee_voice_render(nekobee_synth_t *synth, nekobee_voice_t *voice,
|
||||
float *out, unsigned long sample_count,
|
||||
int do_control_update)
|
||||
{
|
||||
unsigned long sample;
|
||||
|
||||
/* state variables saved in voice */
|
||||
float lfo_pos = voice->lfo_pos,
|
||||
vca_eg = voice->vca_eg,
|
||||
vcf_eg = voice->vcf_eg;
|
||||
unsigned char vca_eg_phase = voice->vca_eg_phase,
|
||||
vcf_eg_phase = voice->vcf_eg_phase;
|
||||
int osc_index = voice->osc_index;
|
||||
|
||||
/* temporary variables used in calculating voice */
|
||||
float fund_pitch;
|
||||
float deltat = synth->deltat;
|
||||
float freq, cutoff, vcf_amt;
|
||||
float vcf_acc_amt;
|
||||
|
||||
/* set up synthesis variables from patch */
|
||||
float omega;
|
||||
float vca_eg_amp = qdB_to_amplitude(velocity_to_attenuation[voice->velocity] * 0);
|
||||
|
||||
float vca_eg_rate_level[3], vca_eg_one_rate[3];
|
||||
float vcf_eg_amp = qdB_to_amplitude(velocity_to_attenuation[voice->velocity] * 0);
|
||||
|
||||
float vcf_eg_rate_level[3], vcf_eg_one_rate[3];
|
||||
float qres = synth->resonance;
|
||||
float vol_out = volume(synth->volume);
|
||||
|
||||
float velocity = (voice->velocity);
|
||||
|
||||
float vcf_egdecay = synth->decay;
|
||||
|
||||
fund_pitch = 0.1f*voice->target_pitch +0.9 * voice->prev_pitch; /* glide */
|
||||
|
||||
if (do_control_update) {
|
||||
voice->prev_pitch = fund_pitch; /* save pitch for next time */
|
||||
}
|
||||
|
||||
fund_pitch *= 440.0f;
|
||||
|
||||
omega = synth->tuning * fund_pitch;
|
||||
|
||||
// if we have triggered ACCENT
|
||||
// we need a shorter decay
|
||||
// we should probably have something like this in the note on code
|
||||
// that could trigger an ACCENT light
|
||||
if (velocity>90) {
|
||||
vcf_egdecay=.0005;
|
||||
}
|
||||
|
||||
// VCA - In a real 303, it is set for around 2 seconds
|
||||
vca_eg_rate_level[0] = 0.1f * vca_eg_amp; // instant on attack
|
||||
vca_eg_one_rate[0] = 0.9f; // very fast
|
||||
vca_eg_rate_level[1] = 0.0f; // sustain is zero
|
||||
vca_eg_one_rate[1] = 1.0f - 0.00001f; // decay time is very slow
|
||||
vca_eg_rate_level[2] = 0.0f; // decays to zero
|
||||
vca_eg_one_rate[2] = 0.975f; // very fast release
|
||||
|
||||
// VCF - funny things go on with the accent
|
||||
|
||||
vcf_eg_rate_level[0] = 0.1f * vcf_eg_amp;
|
||||
vcf_eg_one_rate[0] = 1-0.1f; //0.9f;
|
||||
vcf_eg_rate_level[1] = 0.0f; // vcf_egdecay * *(synth->vcf_eg_sustain_level) * vcf_eg_amp;
|
||||
vcf_eg_one_rate[1] = 1.0f - vcf_egdecay;
|
||||
vcf_eg_rate_level[2] = 0.0f;
|
||||
vcf_eg_one_rate[2] = 0.9995f; // 1.0f - *(synth->vcf_eg_release_time);
|
||||
|
||||
vca_eg_amp *= 0.99f;
|
||||
vcf_eg_amp *= 0.99f;
|
||||
|
||||
freq = M_PI_F * deltat * fund_pitch * synth->mod_wheel; /* now (0 to 1) * pi */
|
||||
|
||||
cutoff = 0.008f * synth->cutoff;
|
||||
|
||||
// 303 always has slight VCF mod
|
||||
vcf_amt = 0.05f+(synth->envmod*0.75);
|
||||
|
||||
/* copy some things so oscillator functions can see them */
|
||||
voice->osc1.waveform = lrintf(synth->waveform);
|
||||
|
||||
// work out how much the accent will affect the filter
|
||||
vcf_acc_amt=.333f+ (synth->resonance/1.5f);
|
||||
|
||||
for (sample = 0; sample < sample_count; sample++) {
|
||||
vca_eg = vca_eg_rate_level[vca_eg_phase] + vca_eg_one_rate[vca_eg_phase] * vca_eg;
|
||||
vcf_eg = vcf_eg_rate_level[vcf_eg_phase] + vcf_eg_one_rate[vcf_eg_phase] * vcf_eg;
|
||||
|
||||
voice->freqcut_buf[sample] = (cutoff + (vcf_amt * vcf_eg/2.0f) + (synth->vcf_accent * synth->accent*0.5f));
|
||||
|
||||
voice->vca_buf[sample] = vca_eg * vol_out*(1.0f + synth->accent*synth->vca_accent);
|
||||
|
||||
if (!vca_eg_phase && vca_eg > vca_eg_amp) vca_eg_phase = 1; /* flip from attack to decay */
|
||||
if (!vcf_eg_phase && vcf_eg > vcf_eg_amp) vcf_eg_phase = 1; /* flip from attack to decay */
|
||||
}
|
||||
|
||||
// oscillator
|
||||
vco(sample_count, voice, &voice->osc1, osc_index, deltat * omega);
|
||||
|
||||
// VCF and VCA
|
||||
vcf_4pole(voice, sample_count, voice->osc_audio + osc_index, out, voice->freqcut_buf, qres, voice->vca_buf);
|
||||
|
||||
osc_index += sample_count;
|
||||
|
||||
if (do_control_update || osc_index > MINBLEP_BUFFER_LENGTH - (XSYNTH_NUGGET_SIZE + LONGEST_DD_PULSE_LENGTH)) {
|
||||
/* do those things should be done only once per control-calculation
|
||||
* interval ("nugget"), such as voice check-for-dead, pitch envelope
|
||||
* calculations, volume envelope phase transition checks, etc. */
|
||||
/* check if we've decayed to nothing, turn off voice if so */
|
||||
if (vca_eg_phase == 2 && voice->vca_buf[sample_count - 1] < 6.26e-6f) {
|
||||
// sound has completed its release phase (>96dB below volume '5' max)
|
||||
XDB_MESSAGE(XDB_NOTE, " nekobee_voice_render check for dead: killing note id %d\n", voice->note_id);
|
||||
nekobee_voice_off(voice);
|
||||
return; // we're dead now, so return
|
||||
}
|
||||
|
||||
/* already saved prev_pitch above */
|
||||
|
||||
/* check oscillator audio buffer index, shift buffer if necessary */
|
||||
if (osc_index > MINBLEP_BUFFER_LENGTH - (XSYNTH_NUGGET_SIZE + LONGEST_DD_PULSE_LENGTH)) {
|
||||
memcpy(voice->osc_audio, voice->osc_audio + osc_index,
|
||||
LONGEST_DD_PULSE_LENGTH * sizeof (float));
|
||||
memset(voice->osc_audio + LONGEST_DD_PULSE_LENGTH, 0,
|
||||
(MINBLEP_BUFFER_LENGTH - LONGEST_DD_PULSE_LENGTH) * sizeof (float));
|
||||
osc_index = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* save things for next time around */
|
||||
voice->lfo_pos = lfo_pos;
|
||||
voice->vca_eg = vca_eg;
|
||||
voice->vca_eg_phase = vca_eg_phase;
|
||||
voice->vcf_eg = vcf_eg;
|
||||
voice->vcf_eg_phase = vcf_eg_phase;
|
||||
voice->osc_index = osc_index;
|
||||
|
||||
return;
|
||||
(void)freq;
|
||||
(void)vcf_acc_amt;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue