Loading...
Searching...
No Matches
Wavetable.hpp
1#pragma once
2
3#include <ossia/network/value/value.hpp>
4#include <ossia/math/safe_math.hpp>
5
6#include <algorithm>
7#include <array>
8#include <cassert>
9#include <cmath>
10#include <cstddef>
11#include <cstdint>
12#include <limits>
13#include <optional>
14#include <span>
15#include <stdexcept>
16#include <type_traits>
17#include <utility>
18#include <vector>
19
20namespace Process
21{
29{
30 static constexpr std::size_t maxFrameSize = 65536;
31 static constexpr std::size_t maxFrames = 256;
32 static constexpr std::size_t maxSamples = 1048576;
33
34 std::size_t frameSize{};
35 std::vector<float> samples;
36
37 std::size_t frameCount() const noexcept
38 {
39 return frameSize ? samples.size() / frameSize : 0;
40 }
41
42 std::span<const float> frame(std::size_t index) const noexcept
43 {
44 assert(index < frameCount());
45 return {samples.data() + index * frameSize, frameSize};
46 }
47
48 bool valid() const noexcept
49 {
50 return frameSize >= 2 && frameSize <= maxFrameSize
51 && !samples.empty() && samples.size() <= maxSamples
52 && samples.size() % frameSize == 0 && frameCount() <= maxFrames
53 && std::all_of(samples.begin(), samples.end(), [](float value) {
54 return ossia::safe_isfinite(value) && value >= -1.f && value <= 1.f;
55 });
56 }
57
58 bool operator==(const WavetableData&) const = default;
59};
60
63inline std::optional<float> waveformNumber(const ossia::value& value) noexcept
64{
65 float number;
66 if(const auto* real = value.target<float>())
67 number = *real;
68 else if(const auto* integer = value.target<int>())
69 number = float(*integer);
70 else if(const auto* flag = value.target<bool>())
71 number = *flag ? 1.f : 0.f;
72 else
73 return std::nullopt;
74 if(!ossia::safe_isfinite(number))
75 return std::nullopt;
76 return number;
77}
78
80inline std::optional<float> waveformSample(const ossia::value& value) noexcept
81{
82 if(const auto number = waveformNumber(value))
83 return std::clamp(*number, -1.f, 1.f);
84 return std::nullopt;
85}
86
90inline std::optional<WavetableData> wavetableFromValue(const ossia::value& value)
91{
92 const auto* frames = value.target<std::vector<ossia::value>>();
93 if(!frames || frames->empty() || frames->size() > WavetableData::maxFrames)
94 return std::nullopt;
95 const auto* first = frames->front().target<std::vector<ossia::value>>();
96 if(!first || first->size() < 2 || first->size() > WavetableData::maxFrameSize
97 || frames->size() > WavetableData::maxSamples / first->size())
98 return std::nullopt;
99
100 WavetableData result;
101 result.frameSize = first->size();
102 result.samples.reserve(result.frameSize * frames->size());
103 for(const auto& frameValue : *frames)
104 {
105 const auto* frame = frameValue.target<std::vector<ossia::value>>();
106 if(!frame || frame->size() != result.frameSize)
107 return std::nullopt;
108 for(const auto& sample : *frame)
109 {
110 const auto amplitude = waveformNumber(sample);
111 if(!amplitude || *amplitude < -1.f || *amplitude > 1.f)
112 return std::nullopt;
113 result.samples.push_back(*amplitude);
114 }
115 }
116 return result;
117}
118
119inline ossia::value wavetableToValue(const WavetableData& table)
120{
121 if(!table.valid())
122 throw std::invalid_argument("Invalid periodic sample table");
123 std::vector<ossia::value> frames;
124 frames.reserve(table.frameCount());
125 for(std::size_t index = 0; index < table.frameCount(); ++index)
126 {
127 std::vector<ossia::value> samples;
128 samples.reserve(table.frameSize);
129 for(float sample : table.frame(index))
130 samples.emplace_back(sample);
131 frames.emplace_back(std::move(samples));
132 }
133 return frames;
134}
135
136namespace detail
137{
138inline bool isWaveformFrame(std::span<const ossia::value> samples) noexcept
139{
140 return samples.size() >= 2 && samples.size() <= WavetableData::maxFrameSize
141 && std::all_of(samples.begin(), samples.end(), [](const ossia::value& v) {
142 return bool(waveformSample(v));
143 });
144}
145}
146
153inline std::optional<std::span<const ossia::value>>
154waveformFrame(const ossia::value& value, std::size_t frame = 0) noexcept
155{
156 const auto* list = value.target<std::vector<ossia::value>>();
157 if(!list || list->empty())
158 return std::nullopt;
159 std::span<const ossia::value> samples;
160 if(const auto* nested = list->front().target<std::vector<ossia::value>>(); nested)
161 {
162 if(frame >= list->size())
163 return std::nullopt;
164 const auto* selected = (*list)[frame].target<std::vector<ossia::value>>();
165 if(!selected)
166 return std::nullopt;
167 samples = *selected;
168 }
169 else
170 {
171 if(frame != 0)
172 return std::nullopt;
173 samples = *list;
174 }
175 if(!detail::isWaveformFrame(samples))
176 return std::nullopt;
177 return samples;
178}
179
183inline std::optional<WavetableData> waveformFromValue(const ossia::value& value)
184{
185 const auto* list = value.target<std::vector<ossia::value>>();
186 if(!list || list->empty())
187 return std::nullopt;
188 const bool bank = bool(list->front().target<std::vector<ossia::value>>());
189 const std::size_t frames = bank ? list->size() : 1;
190 if(frames > WavetableData::maxFrames)
191 return std::nullopt;
192 WavetableData result;
193 for(std::size_t index = 0; index < frames; ++index)
194 {
195 const auto samples = waveformFrame(value, index);
196 if(!samples || (index > 0 && samples->size() != result.frameSize)
197 || frames > WavetableData::maxSamples / samples->size())
198 return std::nullopt;
199 result.frameSize = samples->size();
200 for(const auto& sample : *samples)
201 result.samples.push_back(*waveformSample(sample));
202 }
203 return result;
204}
205
206inline WavetableData makeTriangleWavetable(std::size_t frameSize = 32)
207{
208 if(frameSize < 2 || frameSize > WavetableData::maxFrameSize)
209 throw std::invalid_argument("Invalid waveform resolution");
210 WavetableData table{frameSize, std::vector<float>(frameSize)};
211 for(std::size_t sample = 0; sample < frameSize; ++sample)
212 {
213 const double phase = double(sample) / frameSize;
214 table.samples[sample] = float(1. - 4. * std::abs(phase - .5));
215 }
216 return table;
217}
218
221inline float quantizeSample(double value, int levels) noexcept
222{
223 value = ossia::safe_isfinite(value) ? std::clamp(value, -1., 1.) : 0.;
224 if(levels >= 2)
225 value = 2. * std::round((value + 1.) * .5 * (levels - 1)) / (levels - 1) - 1.;
226 return std::clamp(float(value), -1.f, 1.f);
227}
228
231inline int sampleLevel(float value, int levels) noexcept
232{
233 assert(levels >= 2);
234 return int(std::lround((std::clamp(value, -1.f, 1.f) + 1.) * .5 * (levels - 1)));
235}
236
237enum class WaveformShape
238{
239 Sine,
240 Triangle,
241 SawUp,
242 SawDown,
243 Square,
244 Pulse25,
245 Pulse12,
246 Noise
247};
248
252 std::span<float> frame, WaveformShape shape, int levels, std::uint32_t seed = 1) noexcept
253{
254 const std::size_t size = frame.size();
255 if(size == 0)
256 return;
257 std::uint32_t state = seed ? seed : 0x9E3779B9u;
258 for(std::size_t i = 0; i < size; ++i)
259 {
260 const double phase = double(i) / size;
261 const double ramp = size > 1 ? double(i) / (size - 1) : 0.;
262 double value{};
263 switch(shape)
264 {
265 case WaveformShape::Sine:
266 value = std::sin(2. * 3.14159265358979323846 * phase);
267 break;
268 case WaveformShape::Triangle:
269 value = 1. - 4. * std::abs(phase - .5);
270 break;
271 case WaveformShape::SawUp:
272 value = 2. * ramp - 1.;
273 break;
274 case WaveformShape::SawDown:
275 value = 1. - 2. * ramp;
276 break;
277 case WaveformShape::Square:
278 value = phase < .5 ? 1. : -1.;
279 break;
280 case WaveformShape::Pulse25:
281 value = phase < .25 ? 1. : -1.;
282 break;
283 case WaveformShape::Pulse12:
284 value = phase < .125 ? 1. : -1.;
285 break;
286 case WaveformShape::Noise:
287 // xorshift32: the same seed always draws the same frame.
288 state ^= state << 13;
289 state ^= state >> 17;
290 state ^= state << 5;
291 value = 2. * (double(state) / 4294967295.) - 1.;
292 break;
293 }
294 frame[i] = quantizeSample(value, levels);
295 }
296}
297
300inline void normalizeWaveform(std::span<float> frame, int levels) noexcept
301{
302 float peak{};
303 for(float sample : frame)
304 peak = std::max(peak, std::abs(sample));
305 if(peak <= 0.f)
306 return;
307 for(float& sample : frame)
308 sample = quantizeSample(double(sample) / peak, levels);
309}
310
311inline void invertWaveform(std::span<float> frame, int levels) noexcept
312{
313 for(float& sample : frame)
314 sample = quantizeSample(-double(sample), levels);
315}
316
317inline void reverseWaveform(std::span<float> frame) noexcept
318{
319 std::reverse(frame.begin(), frame.end());
320}
321
323inline void rotateWaveform(std::span<float> frame, bool left) noexcept
324{
325 if(frame.size() < 2)
326 return;
327 if(left)
328 std::rotate(frame.begin(), frame.begin() + 1, frame.end());
329 else
330 std::rotate(frame.rbegin(), frame.rbegin() + 1, frame.rend());
331}
332
334inline void smoothWaveform(std::span<float> frame, int levels)
335{
336 const std::size_t size = frame.size();
337 if(size < 3)
338 return;
339 const std::vector<float> source(frame.begin(), frame.end());
340 for(std::size_t i = 0; i < size; ++i)
341 {
342 const double previous = source[(i + size - 1) % size];
343 const double next = source[(i + 1) % size];
344 frame[i] = quantizeSample((previous + 2. * source[i] + next) * .25, levels);
345 }
346}
347
349inline void clearWaveform(std::span<float> frame, int levels) noexcept
350{
351 std::fill(frame.begin(), frame.end(), quantizeSample(0., levels));
352}
353
356inline float sampleWavetable(
357 const WavetableData& table, std::size_t frame, double phase) noexcept
358{
359 assert(table.frameSize >= 2 && frame < table.frameCount() && ossia::safe_isfinite(phase));
360 phase -= std::floor(phase);
361 const double position = phase * table.frameSize;
362 const auto left = std::min(std::size_t(position), table.frameSize - 1);
363 const auto right = (left + 1) % table.frameSize;
364 const auto samples = table.frame(frame);
365 return float(samples[left] + (samples[right] - samples[left]) * (position - left));
366}
367
370inline WavetableData resampleWavetable(const WavetableData& source, std::size_t size)
371{
372 WavetableData result{size, std::vector<float>(source.frameCount() * size)};
373 for(std::size_t frame = 0; frame < source.frameCount(); ++frame)
374 for(std::size_t sample = 0; sample < size; ++sample)
375 result.samples[frame * size + sample]
376 = sampleWavetable(source, frame, double(sample) / size);
377 return result;
378}
379
380namespace detail
381{
382template <typename T, std::size_t N, typename Sample>
383bool resampleWaveform(
384 std::size_t size, Sample&& sample, std::array<T, N>& destination, double low,
385 double high) noexcept
386{
387 static_assert(N > 0 && std::is_arithmetic_v<T>);
388 if(size < 2 || !ossia::safe_isfinite(low) || !ossia::safe_isfinite(high) || low >= high
389 || low < double(std::numeric_limits<T>::lowest())
390 || high > double(std::numeric_limits<T>::max()))
391 return false;
392 std::array<T, N> prepared;
393 for(std::size_t index = 0; index < N; ++index)
394 {
395 const double position = (double(index) / N) * size;
396 const auto left = std::min(std::size_t(position), size - 1);
397 const auto right = (left + 1) % size;
398 const float a = sample(left), b = sample(right);
399 const double interpolated = float(a + (b - a) * (position - left));
400 const double mapped = low + (high - low) * ((interpolated + 1.) * .5);
401 if constexpr(std::is_integral_v<T>)
402 prepared[index] = T(std::round(mapped));
403 else
404 prepared[index] = T(mapped);
405 }
406 destination = prepared;
407 return true;
408}
409}
410
415template <typename T, std::size_t N>
417 const WavetableData& table, std::array<T, N>& destination,
418 double low, double high, std::size_t frame = 0) noexcept
419{
420 if(!table.valid() || frame >= table.frameCount())
421 return false;
422 const auto samples = table.frame(frame);
423 return detail::resampleWaveform(
424 samples.size(), [samples](std::size_t i) { return samples[i]; }, destination, low,
425 high);
426}
427
430template <typename T, std::size_t N>
432 const ossia::value& value, std::array<T, N>& destination, double low,
433 double high, std::size_t frame = 0) noexcept
434{
435 const auto samples = waveformFrame(value, frame);
436 if(!samples)
437 return false;
438 return detail::resampleWaveform(
439 samples->size(),
440 [samples](std::size_t i) { return *waveformSample((*samples)[i]); },
441 destination, low, high);
442}
443}
Base classes and tools to implement processes and layers.
Definition JSONVisitor.hpp:1115
std::optional< WavetableData > wavetableFromValue(const ossia::value &value)
Definition Wavetable.hpp:90
WavetableData resampleWavetable(const WavetableData &source, std::size_t size)
Definition Wavetable.hpp:370
void clearWaveform(std::span< float > frame, int levels) noexcept
Sets every sample to the level nearest to zero.
Definition Wavetable.hpp:349
void rotateWaveform(std::span< float > frame, bool left) noexcept
Moves every sample one position earlier (left) or later, wrapping.
Definition Wavetable.hpp:323
std::optional< WavetableData > waveformFromValue(const ossia::value &value)
Definition Wavetable.hpp:183
std::optional< std::span< const ossia::value > > waveformFrame(const ossia::value &value, std::size_t frame=0) noexcept
Definition Wavetable.hpp:154
void generateWaveform(std::span< float > frame, WaveformShape shape, int levels, std::uint32_t seed=1) noexcept
Definition Wavetable.hpp:251
void smoothWaveform(std::span< float > frame, int levels)
One pass of a periodic [1 2 1] / 4 low-pass.
Definition Wavetable.hpp:334
void normalizeWaveform(std::span< float > frame, int levels) noexcept
Definition Wavetable.hpp:300
std::optional< float > waveformSample(const ossia::value &value) noexcept
A sample of a waveform value: a waveformNumber clamped to [-1, 1].
Definition Wavetable.hpp:80
float quantizeSample(double value, int levels) noexcept
Definition Wavetable.hpp:221
float sampleWavetable(const WavetableData &table, std::size_t frame, double phase) noexcept
Definition Wavetable.hpp:356
int sampleLevel(float value, int levels) noexcept
Definition Wavetable.hpp:231
std::optional< float > waveformNumber(const ossia::value &value) noexcept
Definition Wavetable.hpp:63
bool prepareWaveform(const WavetableData &table, std::array< T, N > &destination, double low, double high, std::size_t frame=0) noexcept
Definition Wavetable.hpp:416
Definition Wavetable.hpp:29