🍑 nectarine
Audio synthesis tools for C23
Loading...
Searching...
No Matches
wavetable.h
1#include <stdlib.h>
2#include "vendor/prelude/hint.h"
3
4// TODO: Anti-aliasing filter. Should probably be in its own header
5// since it's not strictly for use in the wavetables and should also be
6// useful for real-time signals. Putting the reading links here first
7// though since it will be used to filter the mipmaps.
8// https://ldesoras.fr/doc/articles/resampler-en.pdf
9// https://www.kvraudio.com/forum/viewtopic.php?t=458831
10// https://www.earlevel.com/main/2020/01/04/further-thoughts-on-wave-table-oscillators
11// https://community.native-instruments.com/discussion/411/anti-aliasing-strategies-for-a-phase-distortion-oscillator
12
13typedef struct nec_wavetable {
14 size_t mipmap_count; // Spectral axis (a mipmap of variants)
15 size_t variant_count; // Feature axis (a variant of cycles)
16 size_t sample_count; // Temporal axis (a cycle of frames)
17 float data[]; // float[mipmap_count][variant_count][sample_count]
18} nec_wt_t;
19
20// TODO: Why isn't this autovectorized?
21[[nodiscard, gnu::const]]
22static inline float nec_trilerp(
23 float c000,
24 float c001,
25 float c010,
26 float c011,
27 float c100,
28 float c101,
29 float c110,
30 float c111,
31 float x, float y, float z
32) [[unsequenced]] {
33 assume(0 <= x && x <= 1);
34 assume(0 <= y && y <= 1);
35 assume(0 <= z && z <= 1);
36 const auto z1 = 1.f - z;
37 const auto c00 = c000 * z1 + c001 * z;
38 const auto c01 = c010 * z1 + c011 * z;
39 const auto c10 = c100 * z1 + c101 * z;
40 const auto c11 = c110 * z1 + c111 * z;
41 const auto y1 = 1.f - y;
42 const auto c0 = c00 * y1 + c01 * y;
43 const auto c1 = c10 * y1 + c11 * y;
44 return c0 * (1.f - x) + c1 * x;
45}
46
47[[nodiscard]]
48nec_wt_t *nec_make_wt(
49 size_t mipmap_count,
50 size_t variant_count,
51 size_t sample_count
52) [[clang::allocating]] {
53 if (!mipmap_count) { mipmap_count = 16; }
54 if (!variant_count) { variant_count = 1; }
55 if (!sample_count) { sample_count = 2048; }
56 const size_t data_len = mipmap_count * variant_count * sample_count;
57 return calloc(1, sizeof(nec_wt_t) + sizeof(float) * data_len);
58}
59
60[[nodiscard, gnu::const, gnu::nonnull]]
61static inline float nec_wt_get(
62 nec_wt_t wt[const restrict static 1],
63 float normalized_frequency,
64 float variant,
65 float phase
66) [[unsequenced]] {
67 assume(0 <= normalized_frequency && normalized_frequency < 1);
68 assume(0 <= variant && variant <= 1);
69 assume(0 <= phase && phase < 1);
70
71 const auto samples_per_mipmap = wt->sample_count * wt->variant_count;
72 const auto mipmap_idxr = normalized_frequency * (wt->mipmap_count - 1);
73 const auto mipmap_idxi = ceilf(mipmap_idxr);
74 const auto mipmap_idxf = mipmap_idxr - mipmap_idxi;
75 const auto mipmap_idx1 = (size_t)(mipmap_idxi);
76 const auto mipmap_idx0 = mipmap_idx1 - 1;
77 const auto m0 = mipmap_idx0 * samples_per_mipmap;
78 const auto m1 = mipmap_idx1 * samples_per_mipmap;
79
80 const auto variant_idxr = variant * (wt->variant_count - 1);
81 const auto variant_idxi = floorf(variant_idxr);
82 const auto variant_idxf = variant_idxr - variant_idxi;
83 const auto variant_idx0 = (size_t)(variant_idxi);
84 const auto variant_idx1 = 1 + variant_idx0;
85 const auto v0 = variant_idx0 * wt->sample_count;
86 const auto v1 = variant_idx1 * wt->sample_count;
87
88 const auto sample_idxr = phase * wt->sample_count;
89 const auto sample_idxi = floorf(sample_idxr);
90 const auto sample_idxf = sample_idxr - sample_idxi;
91 const auto sample_idx0 = (size_t)(sample_idxi);
92 const auto sample_idx1 = (1 + sample_idx0) % wt->sample_count;
93
94 return nec_trilerp(
95 wt->data[m0 + v0 + sample_idx0],
96 wt->data[m0 + v0 + sample_idx1],
97 wt->data[m0 + v1 + sample_idx0],
98 wt->data[m0 + v1 + sample_idx1],
99 wt->data[m1 + v0 + sample_idx0],
100 wt->data[m1 + v0 + sample_idx1],
101 wt->data[m1 + v1 + sample_idx0],
102 wt->data[m1 + v1 + sample_idx1]
103 );
104}
Definition wavetable.h:13