-
-
Notifications
You must be signed in to change notification settings - Fork 7
/
fm-operator.c
432 lines (362 loc) · 15.1 KB
/
fm-operator.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
/* Based on Nemesis's notes: http://gendev.spritesmind.net/forum/viewtopic.php?p=6114#p6114 */
/* http://gendev.spritesmind.net/forum/viewtopic.php?p=5716#p5716 */
/* http://gendev.spritesmind.net/forum/viewtopic.php?p=7967#p7967 */
#include "fm-operator.h"
#include <assert.h>
#include <math.h>
#include "clowncommon/clowncommon.h"
static cc_u16f GetSSGEGCorrectedAttenuation(const FM_Operator_State* const state, const cc_bool disable_inversion)
{
if (!disable_inversion && state->ssgeg.enabled && state->ssgeg.invert != state->ssgeg.attack)
return (0x200 - state->attenuation) & 0x3FF;
else
return state->attenuation;
}
static cc_u16f CalculateRate(const FM_Operator_State* const state)
{
if (state->rates[state->envelope_mode] == 0)
return 0;
return CC_MIN(0x3F, state->rates[state->envelope_mode] * 2 + (FM_Phase_GetKeyCode(&state->phase) / state->key_scale));
}
static void EnterAttackMode(FM_Operator_State* const state)
{
if (state->key_on)
{
state->envelope_mode = FM_OPERATOR_ENVELOPE_MODE_ATTACK;
if (CalculateRate(state) >= 0x1F * 2)
{
state->envelope_mode = FM_OPERATOR_ENVELOPE_MODE_DECAY;
state->attenuation = 0;
}
}
}
static cc_u16f InversePow2(const FM_Operator_Constant* const constant, const cc_u16f value)
{
/* TODO: Maybe replace this whole thing with a single lookup table? */
/* The attenuation is in 5.8 fixed point format. */
const cc_u16f whole = value >> 8;
const cc_u16f fraction = value & 0xFF;
return (constant->power_table[fraction] << 2) >> whole;
}
void FM_Operator_Constant_Initialise(FM_Operator_Constant* const constant)
{
const cc_u16f sine_table_length = CC_COUNT_OF(constant->logarithmic_attenuation_sine_table);
const cc_u16f pow_table_length = CC_COUNT_OF(constant->power_table);
const double log2 = log(2.0);
cc_u16f i;
/* Generate sine wave lookup table. */
for (i = 0; i < sine_table_length; ++i)
{
/* "Calculate the normalized phase value for the input into the sine table.Note
that this is calculated as a normalized result from 0.0-1.0 where 0 is not
reached, because the phase is calculated as if it was a 9-bit index with the
LSB fixed to 1. This was done so that the sine table would be more accurate
when it was "mirrored" to create the negative oscillation of the wave. It's
also convenient we don't have to worry about a phase of 0, because 0 is an
invalid input for a log function, which we need to use below." */
const double phase_normalised = (double)((i << 1) + 1) / (double)(sine_table_length << 1);
/* "Calculate the pure sine value for the input.Note that we only build a sine
table for a quarter of the full oscillation (0-PI/2), since the upper two bits
of the full phase are extracted by the external circuit." */
const double sin_result_normalized = sin(phase_normalised * (CC_PI / 2.0));
/* "Convert the sine result from a linear representation of volume, to a
logarithmic representation of attenuation. The YM2612 stores values in the sine
table in this form because logarithms simplify multiplication down to addition,
and this allowed them to attenuate the sine result by the envelope generator
output simply by adding the two numbers together." */
const double sin_result_as_attenuation = -log(sin_result_normalized) / log2;
/* "The division by log(2) is required because the log function is base 10, but the
YM2612 uses a base 2 logarithmic value. Dividing the base 10 log result by
log10(2) will convert the result to a base 2 logarithmic value, which can then
be converted back to a linear value by a pow2 function. In other words:
2^(log10(x)/log10(2)) = 2^log2(x) = x
If there was a native log2() function provided we could use that instead." */
/* "Convert the attenuation value to a rounded 12-bit result in 4.8 fixed point
format." */
const cc_u16l sinResult = (cc_u16l)((sin_result_as_attenuation * 256.0) + 0.5);
/* "Write the result to the table." */
constant->logarithmic_attenuation_sine_table[i] = sinResult;
}
/* Generate power lookup table. */
for (i = 0; i < pow_table_length; ++i)
{
/* "Normalize the current index to the range 0.0 - 1.0.Note that in this case, 0.0
is a value which is never actually reached, since we start from i+1. They only
did this to keep the result to an 11-bit output. It probably would have been
better to simply subtract 1 from every final number and have 1.0 as the input
limit instead when building the table, so an input of 0 would output 0x7FF,
but they didn't." */
const double entry_normalised = (double)(i + 1) / (double)pow_table_length;
/* "Calculate 2 ^ -entryNormalized." */
const double result_normalised = pow(2.0, -entry_normalised);
/* "Convert the normalized result to an 11-bit rounded result." */
const cc_u16l result = (cc_u16l)((result_normalised * 2048.0) + 0.5);
/* "Write the result to the table." */
constant->power_table[i] = result;
}
}
void FM_Operator_State_Initialise(FM_Operator_State* const state)
{
FM_Phase_State_Initialise(&state->phase);
/* Set envelope to update immediately. */
state->countdown = 1;
state->cycle_counter = 0;
state->delta_index = 0;
state->attenuation = 0x3FF;
FM_Operator_SetSSGEG(state, 0);
FM_Operator_SetTotalLevel(state, 0x7F); /* Silence channel. */
FM_Operator_SetKeyScaleAndAttackRate(state, 0, 0);
FM_Operator_SetDecayRate(state, 0);
FM_Operator_SetSustainRate(state, 0);
FM_Operator_SetSustainLevelAndReleaseRate(state, 0, 0);
state->envelope_mode = FM_OPERATOR_ENVELOPE_MODE_RELEASE;
state->key_on = cc_false;
}
void FM_Operator_SetFrequency(FM_Operator_State* const state, const cc_u16f f_number_and_block)
{
FM_Phase_SetFrequency(&state->phase, f_number_and_block);
}
void FM_Operator_SetKeyOn(FM_Operator_State* const state, const cc_bool key_on)
{
/* An envelope cannot be key-on'd if it isn't key-off'd, and vice versa. */
/* This is relied upon by Sonic's spring sound. */
/* TODO: http://gendev.spritesmind.net/forum/viewtopic.php?p=6179#p6179 */
/* Key-on/key-off operations should not occur until an envelope generator update. */
if (state->key_on != key_on)
{
state->key_on = key_on;
if (key_on)
{
EnterAttackMode(state);
FM_Phase_Reset(&state->phase);
}
else
{
state->envelope_mode = FM_OPERATOR_ENVELOPE_MODE_RELEASE;
/* SSG-EG attenuation inversion is not performed during key-off, so we have to manually invert the attenuation here. */
state->attenuation = GetSSGEGCorrectedAttenuation(state, cc_false);
/* This is always forced off as long as key-on is false. */
state->ssgeg.invert = cc_false;
}
}
}
void FM_Operator_SetSSGEG(FM_Operator_State* const state, const cc_u8f ssgeg)
{
state->ssgeg.enabled = (ssgeg & (1u << 3)) != 0;
state->ssgeg.attack = (ssgeg & (1u << 2)) != 0 && state->ssgeg.enabled;
state->ssgeg.alternate = (ssgeg & (1u << 1)) != 0 && state->ssgeg.enabled;
state->ssgeg.hold = (ssgeg & (1u << 0)) != 0 && state->ssgeg.enabled;
}
void FM_Operator_SetDetuneAndMultiplier(FM_Operator_State* const state, const cc_u16f detune, const cc_u16f multiplier)
{
FM_Phase_SetDetuneAndMultiplier(&state->phase, detune, multiplier);
}
void FM_Operator_SetTotalLevel(FM_Operator_State* const state, const cc_u16f total_level)
{
/* Convert from 7-bit to 10-bit. */
state->total_level = total_level << 3;
}
void FM_Operator_SetKeyScaleAndAttackRate(FM_Operator_State* const state, const cc_u16f key_scale, const cc_u16f attack_rate)
{
state->key_scale = 8 >> key_scale;
state->rates[FM_OPERATOR_ENVELOPE_MODE_ATTACK] = attack_rate;
}
void FM_Operator_SetDecayRate(FM_Operator_State* const state, const cc_u16f decay_rate)
{
state->rates[FM_OPERATOR_ENVELOPE_MODE_DECAY] = decay_rate;
}
void FM_Operator_SetSustainRate(FM_Operator_State* const state, const cc_u16f sustain_rate)
{
state->rates[FM_OPERATOR_ENVELOPE_MODE_SUSTAIN] = sustain_rate;
}
void FM_Operator_SetSustainLevelAndReleaseRate(FM_Operator_State* const state, const cc_u16f sustain_level, const cc_u16f release_rate)
{
state->sustain_level = sustain_level == 0xF ? 0x3E0 : sustain_level * 0x20;
/* Convert from 4-bit to 5-bit to match the others. */
state->rates[FM_OPERATOR_ENVELOPE_MODE_RELEASE] = (release_rate << 1) | 1;
}
static cc_u16f GetEnvelopeDelta(FM_Operator_State* const state)
{
if (--state->countdown == 0)
{
static const cc_u16f cycle_bitmasks[0x40 / 4] = {
#define GENERATE_BITMASK(x) ((1 << (x)) - 1)
GENERATE_BITMASK(11),
GENERATE_BITMASK(10),
GENERATE_BITMASK(9),
GENERATE_BITMASK(8),
GENERATE_BITMASK(7),
GENERATE_BITMASK(6),
GENERATE_BITMASK(5),
GENERATE_BITMASK(4),
GENERATE_BITMASK(3),
GENERATE_BITMASK(2),
GENERATE_BITMASK(1),
GENERATE_BITMASK(0),
GENERATE_BITMASK(0),
GENERATE_BITMASK(0),
GENERATE_BITMASK(0),
GENERATE_BITMASK(0)
#undef GENERATE_BITMASK
};
const cc_u16f rate = CalculateRate(state);
state->countdown = 3;
if ((state->cycle_counter++ & cycle_bitmasks[rate / 4]) == 0)
{
static const cc_u16f deltas[0x40][8] = {
{0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0},
{0, 1, 0, 1, 0, 1, 0, 1},
{0, 1, 0, 1, 0, 1, 0, 1},
{0, 1, 0, 1, 0, 1, 0, 1},
{0, 1, 0, 1, 0, 1, 0, 1},
{0, 1, 1, 1, 0, 1, 1, 1},
{0, 1, 1, 1, 0, 1, 1, 1},
{0, 1, 0, 1, 0, 1, 0, 1},
{0, 1, 0, 1, 1, 1, 0, 1},
{0, 1, 1, 1, 0, 1, 1, 1},
{0, 1, 1, 1, 1, 1, 1, 1},
{0, 1, 0, 1, 0, 1, 0, 1},
{0, 1, 0, 1, 1, 1, 0, 1},
{0, 1, 1, 1, 0, 1, 1, 1},
{0, 1, 1, 1, 1, 1, 1, 1},
{0, 1, 0, 1, 0, 1, 0, 1},
{0, 1, 0, 1, 1, 1, 0, 1},
{0, 1, 1, 1, 0, 1, 1, 1},
{0, 1, 1, 1, 1, 1, 1, 1},
{0, 1, 0, 1, 0, 1, 0, 1},
{0, 1, 0, 1, 1, 1, 0, 1},
{0, 1, 1, 1, 0, 1, 1, 1},
{0, 1, 1, 1, 1, 1, 1, 1},
{0, 1, 0, 1, 0, 1, 0, 1},
{0, 1, 0, 1, 1, 1, 0, 1},
{0, 1, 1, 1, 0, 1, 1, 1},
{0, 1, 1, 1, 1, 1, 1, 1},
{0, 1, 0, 1, 0, 1, 0, 1},
{0, 1, 0, 1, 1, 1, 0, 1},
{0, 1, 1, 1, 0, 1, 1, 1},
{0, 1, 1, 1, 1, 1, 1, 1},
{0, 1, 0, 1, 0, 1, 0, 1},
{0, 1, 0, 1, 1, 1, 0, 1},
{0, 1, 1, 1, 0, 1, 1, 1},
{0, 1, 1, 1, 1, 1, 1, 1},
{0, 1, 0, 1, 0, 1, 0, 1},
{0, 1, 0, 1, 1, 1, 0, 1},
{0, 1, 1, 1, 0, 1, 1, 1},
{0, 1, 1, 1, 1, 1, 1, 1},
{0, 1, 0, 1, 0, 1, 0, 1},
{0, 1, 0, 1, 1, 1, 0, 1},
{0, 1, 1, 1, 0, 1, 1, 1},
{0, 1, 1, 1, 1, 1, 1, 1},
{0, 1, 0, 1, 0, 1, 0, 1},
{0, 1, 0, 1, 1, 1, 0, 1},
{0, 1, 1, 1, 0, 1, 1, 1},
{0, 1, 1, 1, 1, 1, 1, 1},
{1, 1, 1, 1, 1, 1, 1, 1},
{1, 1, 1, 2, 1, 1, 1, 2},
{1, 2, 1, 2, 1, 2, 1, 2},
{1, 2, 2, 2, 1, 2, 2, 2},
{2, 2, 2, 2, 2, 2, 2, 2},
{2, 2, 2, 3, 2, 2, 2, 3},
{2, 3, 2, 3, 2, 3, 2, 3},
{2, 3, 3, 3, 2, 3, 3, 3},
{3, 3, 3, 3, 3, 3, 3, 3},
{3, 3, 3, 4, 3, 3, 3, 4},
{3, 4, 3, 4, 3, 4, 3, 4},
{3, 4, 4, 4, 3, 4, 4, 4},
{4, 4, 4, 4, 4, 4, 4, 4},
{4, 4, 4, 4, 4, 4, 4, 4},
{4, 4, 4, 4, 4, 4, 4, 4},
{4, 4, 4, 4, 4, 4, 4, 4}
};
return deltas[rate][state->delta_index++ % CC_COUNT_OF(deltas[rate])];
}
}
return 0;
}
static void UpdateEnvelopeSSGEG(FM_Operator_State* const state)
{
if (state->ssgeg.enabled && state->attenuation >= 0x200)
{
if (state->ssgeg.alternate)
state->ssgeg.invert = state->ssgeg.hold ? cc_true : !state->ssgeg.invert;
else if (!state->ssgeg.hold)
FM_Phase_Reset(&state->phase);
if (!state->ssgeg.hold)
EnterAttackMode(state);
}
}
static void UpdateEnvelopeADSR(FM_Operator_State* const state)
{
const cc_u16f delta = GetEnvelopeDelta(state);
const cc_bool end_envelope = state->attenuation >= (state->ssgeg.enabled ? 0x200 : 0x3F0);
switch (state->envelope_mode)
{
case FM_OPERATOR_ENVELOPE_MODE_ATTACK:
if (state->attenuation == 0)
{
state->envelope_mode = FM_OPERATOR_ENVELOPE_MODE_DECAY;
break;
}
if (delta != 0)
{
state->attenuation += (~(cc_u16f)state->attenuation << (delta - 1)) >> 4;
assert(state->attenuation <= 0x3FF);
}
break;
case FM_OPERATOR_ENVELOPE_MODE_DECAY:
if (state->attenuation >= state->sustain_level)
{
state->envelope_mode = FM_OPERATOR_ENVELOPE_MODE_SUSTAIN;
break;
}
/* Fallthrough */
case FM_OPERATOR_ENVELOPE_MODE_SUSTAIN:
case FM_OPERATOR_ENVELOPE_MODE_RELEASE:
if (delta != 0 && !end_envelope)
{
state->attenuation += 1u << ((delta - 1) + (state->ssgeg.enabled ? 2 : 0));
assert(state->attenuation <= 0x3FF);
}
if (end_envelope && !(state->key_on && state->ssgeg.hold && (state->ssgeg.alternate != state->ssgeg.attack)))
{
state->envelope_mode = FM_OPERATOR_ENVELOPE_MODE_RELEASE;
state->attenuation = 0x3FF;
}
break;
}
}
static cc_u16f UpdateEnvelope(FM_Operator_State* const state)
{
UpdateEnvelopeSSGEG(state);
UpdateEnvelopeADSR(state);
return CC_MIN(0x3FF, GetSSGEGCorrectedAttenuation(state, !state->key_on) + state->total_level);
}
cc_s16f FM_Operator_Process(const FM_Operator* const fm_operator, const cc_s16f phase_modulation)
{
/* TODO: https://gendev.spritesmind.net/forum/viewtopic.php?p=8908#p8908 */
/* Update and obtain phase and make it 10-bit (the upper bits are discarded later). */
const cc_u16f phase = FM_Phase_Increment(&fm_operator->state->phase) >> 10;
/* Update and obtain attenuation (10-bit). */
const cc_u16f attenuation = UpdateEnvelope(fm_operator->state);
/* Modulate the phase. */
/* The modulation is divided by two because up to two operators can provide modulation at once. */
const cc_u16f modulated_phase = (phase + phase_modulation / 2) & 0x3FF;
/* Reduce the phase down to a single quarter of the span of a sine wave, since the other three quarters
are just mirrored anyway. This allows us to use a much smaller sine wave lookup table. */
const cc_bool phase_is_in_negative_wave = (modulated_phase & 0x200) != 0;
const cc_bool phase_is_in_mirrored_half_of_wave = (modulated_phase & 0x100) != 0;
const cc_u16f quarter_phase = (modulated_phase & 0xFF) ^ (phase_is_in_mirrored_half_of_wave ? 0xFF : 0);
/* This table triples as a sine wave lookup table, a logarithm lookup table, and an attenuation lookup table.
The obtained attenuation is 12-bit. */
const cc_u16f phase_as_attenuation = fm_operator->constant->logarithmic_attenuation_sine_table[quarter_phase];
/* Both attenuations are logarithms (measurements of decibels), so we can attenuate them by each other by just adding
them together instead of multiplying them. The result is a 13-bit value. */
const cc_u16f combined_attenuation = phase_as_attenuation + (attenuation << 2);
/* Convert from logarithm (decibel) back to linear (sound pressure). */
const cc_s16f sample_absolute = InversePow2(fm_operator->constant, combined_attenuation);
/* Restore the sign bit that we extracted earlier. */
const cc_s16f sample = (phase_is_in_negative_wave ? -sample_absolute : sample_absolute);
/* Return the 14-bit sample. */
return sample;
}