AtlatestRepositorysigil-dsp
1
/*2
* fm4.c - Four-operator phase-modulation FM voice3
*4
* Operators are numbered 0..3 (op1..op4 in the user-facing table). Every5
* algorithm routes modulation from a higher-numbered operator into a6
* lower-numbered one, so computing operators from 3 down to 0 is a valid7
* evaluation order for all of them; operator 3 is the feedback operator8
* and modulates itself through the average of its last two outputs (the9
* DX/OPL form, which keeps the loop stable at high feedback).10
*/12
#include "fm4.h"13
#include <stdlib.h>14
#include <string.h>15
#include <math.h>17
#define FM4_TABLE_BITS 1218
#define FM4_TABLE_SIZE (1 << FM4_TABLE_BITS)19
#define FM4_TABLE_MASK (FM4_TABLE_SIZE - 1)20
#define FM4_TWO_PI 6.283185307179586f21
#define FM4_FB_SCALE 3.141592653589793f /* feedback 1.0 = pi radians */23
static SPFLOAT fm4_sine[FM4_TABLE_SIZE + 1];24
static int fm4_table_ready = 0;26
static void fm4_build_table(void)27
{28
if (fm4_table_ready) return;29
for (int i = 0; i <= FM4_TABLE_SIZE; i++) {30
double x = (double)i / (double)FM4_TABLE_SIZE;31
fm4_sine[i] = (SPFLOAT)sin(x * 6.283185307179586);32
}33
fm4_table_ready = 1;34
}36
/* Algorithm table. Operators 0..3 = op1..op4. mod_mask[i] lists who37
* modulates operator i; carrier_mask lists who reaches the output. */38
static const sp_fm4_algorithm fm4_algorithms[SP_FM4_ALGORITHMS] = {39
/* 0 */ { "stack", { 0x2, 0x4, 0x8, 0x0 }, 0x1 }, /* 4->3->2->1 */40
/* 1 */ { "two-into-one", { 0x2, 0xC, 0x0, 0x0 }, 0x1 }, /* 4->2, 3->2, 2->1 */41
/* 2 */ { "branch", { 0x6, 0x0, 0x8, 0x0 }, 0x1 }, /* 4->3->1, 2->1 */42
/* 3 */ { "three-into-one", { 0xE, 0x0, 0x0, 0x0 }, 0x1 }, /* 4,3,2 -> 1 */43
/* 4 */ { "pairs", { 0x2, 0x0, 0x8, 0x0 }, 0x5 }, /* 4->3, 2->1 */44
/* 5 */ { "stack-plus-one", { 0x0, 0x4, 0x8, 0x0 }, 0x3 }, /* 4->3->2, 1 */45
/* 6 */ { "one-into-three", { 0x8, 0x8, 0x8, 0x0 }, 0x7 }, /* 4 -> 1,2,3 */46
/* 7 */ { "additive", { 0x0, 0x0, 0x0, 0x0 }, 0xF }, /* all carriers */47
};49
const sp_fm4_algorithm *sp_fm4_algorithm_table(void)50
{51
return fm4_algorithms;52
}54
int sp_fm4_create(sp_fm4 **p)55
{56
*p = (sp_fm4 *)calloc(1, sizeof(sp_fm4));57
return *p ? SP_OK : SP_NOT_OK;58
}60
int sp_fm4_destroy(sp_fm4 **p)61
{62
if (!*p) return SP_OK;63
free(*p);64
*p = NULL;65
return SP_OK;66
}68
int sp_fm4_init(sp_data *sp, sp_fm4 *p)69
{70
fm4_build_table();71
memset(p, 0, sizeof(*p));72
p->sr = (SPFLOAT)sp->sr;73
p->algorithm = 0;74
p->feedback = 0.0f;75
p->vel_depth = 0.0f;76
p->vel_curve = 1.0f;77
for (int i = 0; i < SP_FM4_OPS; i++)78
sp_fm4_set_op(p, i, 1.0f, 1.0f, 0.005f, 0.1f, 1.0f, 0.1f);79
return SP_OK;80
}82
int sp_fm4_set_algorithm(sp_fm4 *p, int algorithm)83
{84
if (algorithm < 0 || algorithm >= SP_FM4_ALGORITHMS) return SP_NOT_OK;85
p->algorithm = algorithm;86
return SP_OK;87
}89
int sp_fm4_set_op(sp_fm4 *p, int op, SPFLOAT ratio, SPFLOAT level,90
SPFLOAT attack, SPFLOAT decay, SPFLOAT sustain,91
SPFLOAT release)92
{93
if (op < 0 || op >= SP_FM4_OPS) return SP_NOT_OK;94
sp_fm4_op *o = &p->op[op];95
o->ratio = ratio < 0.0f ? 0.0f : ratio;96
o->level = level;97
o->attack = attack < 0.0f ? 0.0f : attack;98
o->decay = decay < 0.0f ? 0.0f : decay;99
o->sustain = sustain < 0.0f ? 0.0f : (sustain > 1.0f ? 1.0f : sustain);100
o->release = release < 0.0f ? 0.0f : release;101
return SP_OK;102
}104
/* Linear ADSR, one step. `on` is the gate state this sample. */105
static inline SPFLOAT fm4_env_step(sp_fm4_op *o, SPFLOAT sr, int on)106
{107
switch (o->stage) {108
case 1: { /* attack */109
SPFLOAT samples = o->attack * sr;110
if (samples < 1.0f) { o->env = 1.0f; o->stage = 2; break; }111
o->env += 1.0f / samples;112
if (o->env >= 1.0f) { o->env = 1.0f; o->stage = 2; }113
break;114
}115
case 2: { /* decay */116
SPFLOAT samples = o->decay * sr;117
if (samples < 1.0f) { o->env = o->sustain; o->stage = 3; break; }118
o->env -= (1.0f - o->sustain) / samples;119
if (o->env <= o->sustain) { o->env = o->sustain; o->stage = 3; }120
break;121
}122
case 3: /* sustain */123
o->env = o->sustain;124
break;125
case 4: { /* release */126
SPFLOAT samples = o->release * sr;127
if (samples < 1.0f) { o->env = 0.0f; o->stage = 0; break; }128
o->env -= o->rel_rate;129
if (o->env <= 0.0f) { o->env = 0.0f; o->stage = 0; }130
break;131
}132
default: /* idle */133
o->env = 0.0f;134
break;135
}136
(void)on;137
return o->env;138
}140
static inline SPFLOAT fm4_sin(SPFLOAT phase01, SPFLOAT mod_rad)141
{142
SPFLOAT pos = phase01 * (SPFLOAT)FM4_TABLE_SIZE143
+ mod_rad * ((SPFLOAT)FM4_TABLE_SIZE / FM4_TWO_PI);144
int i = (int)pos;145
if ((SPFLOAT)i > pos) i--; /* floor for negative positions */146
SPFLOAT frac = pos - (SPFLOAT)i;147
i &= FM4_TABLE_MASK;148
return fm4_sine[i] + (fm4_sine[i + 1] - fm4_sine[i]) * frac;149
}151
int sp_fm4_compute(sp_data *sp, sp_fm4 *p, SPFLOAT freq, SPFLOAT gate,152
SPFLOAT index, SPFLOAT velocity, SPFLOAT *out)153
{154
const sp_fm4_algorithm *alg = &fm4_algorithms[p->algorithm];155
int on = gate > 0.5f;157
if (on && !p->gate_prev) {158
/* Note on: key sync + envelope restart from zero. */159
for (int i = 0; i < SP_FM4_OPS; i++) {160
sp_fm4_op *o = &p->op[i];161
o->phase = 0.0f;162
o->env = 0.0f;163
o->stage = 1;164
o->out = o->out_prev = o->out_prev2 = 0.0f;165
}166
} else if (!on && p->gate_prev) {167
for (int i = 0; i < SP_FM4_OPS; i++) {168
sp_fm4_op *o = &p->op[i];169
if (o->stage != 0) {170
SPFLOAT samples = o->release * p->sr;171
o->rel_rate = samples < 1.0f ? o->env : o->env / samples;172
o->stage = 4;173
}174
}175
}176
p->gate_prev = on;178
/* Velocity -> index. */179
SPFLOAT v = velocity < 0.0f ? 0.0f : (velocity > 1.0f ? 1.0f : velocity);180
SPFLOAT vshaped = (p->vel_curve == 1.0f) ? v : powf(v, p->vel_curve);181
SPFLOAT index_eff = index * (1.0f - p->vel_depth + p->vel_depth * vshaped);183
SPFLOAT sum = 0.0f;184
int ncar = 0;185
for (int i = SP_FM4_OPS - 1; i >= 0; i--) {186
sp_fm4_op *o = &p->op[i];187
SPFLOAT env = fm4_env_step(o, p->sr, on);189
SPFLOAT mod = 0.0f;190
unsigned char m = alg->mod_mask[i];191
for (int j = i + 1; j < SP_FM4_OPS; j++)192
if (m & (1u << j)) mod += p->op[j].out;193
mod *= index_eff;194
if (i == SP_FM4_OPS - 1 && p->feedback > 0.0f)195
mod += p->feedback * FM4_FB_SCALE * 0.5f * (o->out_prev + o->out_prev2);197
SPFLOAT s = fm4_sin(o->phase, mod) * o->level * env;198
if (i == SP_FM4_OPS - 1) {199
o->out_prev2 = o->out_prev;200
o->out_prev = s;201
}202
o->out = s;204
o->phase += freq * o->ratio / p->sr;205
while (o->phase >= 1.0f) o->phase -= 1.0f;206
while (o->phase < 0.0f) o->phase += 1.0f;208
if (alg->carrier_mask & (1u << i)) { sum += s; ncar++; }209
}210
*out = ncar > 0 ? sum / (SPFLOAT)ncar : 0.0f;211
return SP_OK;212
}