AtlatestRepositorysigil-dsp

sigil-dsp / tree / src / cfm4.c

1/*
2 * fm4.c - Four-operator phase-modulation FM voice
3 *
4 * Operators are numbered 0..3 (op1..op4 in the user-facing table). Every
5 * algorithm routes modulation from a higher-numbered operator into a
6 * lower-numbered one, so computing operators from 3 down to 0 is a valid
7 * evaluation order for all of them; operator 3 is the feedback operator
8 * and modulates itself through the average of its last two outputs (the
9 * 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 12
18#define FM4_TABLE_SIZE (1 << FM4_TABLE_BITS)
19#define FM4_TABLE_MASK (FM4_TABLE_SIZE - 1)
20#define FM4_TWO_PI 6.283185307179586f
21#define FM4_FB_SCALE 3.141592653589793f /* feedback 1.0 = pi radians */
23static SPFLOAT fm4_sine[FM4_TABLE_SIZE + 1];
24static int fm4_table_ready = 0;
26static void fm4_build_table(void)
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;
36/* Algorithm table. Operators 0..3 = op1..op4. mod_mask[i] lists who
37 * modulates operator i; carrier_mask lists who reaches the output. */
38static 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};
49const sp_fm4_algorithm *sp_fm4_algorithm_table(void)
51 return fm4_algorithms;
54int sp_fm4_create(sp_fm4 **p)
56 *p = (sp_fm4 *)calloc(1, sizeof(sp_fm4));
57 return *p ? SP_OK : SP_NOT_OK;
60int sp_fm4_destroy(sp_fm4 **p)
62 if (!*p) return SP_OK;
63 free(*p);
64 *p = NULL;
65 return SP_OK;
68int sp_fm4_init(sp_data *sp, sp_fm4 *p)
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;
82int sp_fm4_set_algorithm(sp_fm4 *p, int algorithm)
84 if (algorithm < 0 || algorithm >= SP_FM4_ALGORITHMS) return SP_NOT_OK;
85 p->algorithm = algorithm;
86 return SP_OK;
89int sp_fm4_set_op(sp_fm4 *p, int op, SPFLOAT ratio, SPFLOAT level,
90 SPFLOAT attack, SPFLOAT decay, SPFLOAT sustain,
91 SPFLOAT release)
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;
104/* Linear ADSR, one step. `on` is the gate state this sample. */
105static inline SPFLOAT fm4_env_step(sp_fm4_op *o, SPFLOAT sr, int on)
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;
140static inline SPFLOAT fm4_sin(SPFLOAT phase01, SPFLOAT mod_rad)
142 SPFLOAT pos = phase01 * (SPFLOAT)FM4_TABLE_SIZE
143 + 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;
151int sp_fm4_compute(sp_data *sp, sp_fm4 *p, SPFLOAT freq, SPFLOAT gate,
152 SPFLOAT index, SPFLOAT velocity, SPFLOAT *out)
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;