11 constexpr double two_pi = 2.0 * std::numbers::pi;
13 [[nodiscard]]
double interpolate_table(std::span<const double> table,
double x)
noexcept
15 return sample_table(table, (std::clamp(x, -1.0, 1.0) + 1.0) * 0.5);
18 inline void wrap_revolution(
double& phase)
noexcept
20 if (phase >= two_pi) {
22 }
else if (phase < 0.0) {
27 [[nodiscard]]
double sum_of(std::span<const double>
values)
noexcept
29 const double* data =
values.data();
37 for (; k + 4 <=
count; k += 4) {
45 for (; k <
count; ++k)
47 return ((s0 + s1) + (s2 + s3)) + tail;
51 const double* __restrict cos_step,
const double* __restrict sin_step,
52 double* __restrict re,
double* __restrict im,
size_t n)
noexcept
54 for (
size_t i = 0; i < n; ++i) {
55 const double next_re = re[i] * cos_step[i] - im[i] * sin_step[i];
56 const double next_im = re[i] * sin_step[i] + im[i] * cos_step[i];
57 const double correction = 1.5 - 0.5 * (next_re * next_re + next_im * next_im);
59 re[i] = next_re * correction;
60 im[i] = next_im * correction;
65 const double* __restrict cells,
double* __restrict mapped,
double r,
size_t n)
noexcept
67 for (
size_t i = 0; i < n; ++i)
68 mapped[i] = r * cells[i] * (1.0 - cells[i]);
71 void blend_neighbours(
72 double* __restrict cells,
const double* __restrict mapped,
double epsilon,
size_t n)
noexcept
74 const double keep = 1.0 - epsilon;
75 const double share = 0.5 * epsilon;
78 cells[0] = keep * mapped[0] + share * (mapped[0] + mapped[0]);
82 cells[0] = keep * mapped[0] + share * (mapped[n - 1] + mapped[1]);
83 for (
size_t i = 1; i + 1 < n; ++i)
84 cells[i] = keep * mapped[i] + share * (mapped[i - 1] + mapped[i + 1]);
85 cells[n - 1] = keep * mapped[n - 1] + share * (mapped[n - 2] + mapped[0]);
90double horner(std::span<const double> coefficients,
double x)
noexcept
93 for (
const double c : coefficients)
94 result = result * x + c;
100 const size_t count = std::min(
window.size(), coefs.size());
101 const double*
a =
window.data();
102 const double*
b = coefs.data();
109 for (; k + 4 <=
count; k += 4) {
111 s1 +=
a[k + 1] *
b[k + 1];
112 s2 +=
a[k + 2] *
b[k + 2];
113 s3 +=
a[k + 3] *
b[k + 3];
117 for (; k <
count; ++k)
119 return ((s0 + s1) + (s2 + s3)) + tail;
127 const auto reach =
static_cast<double>(
window.size() - 1);
130 for (
size_t j = 0; j + 1 < coefs.size(); j += 2) {
131 const double lag = coefs[j];
132 if (!(lag >= 0.0) || lag > reach)
135 const auto index =
static_cast<size_t>(lag);
136 const double fraction = lag -
static_cast<double>(
index);
148 return interpolate_table(coefs,
window.empty() ? 0.0 :
window.front());
153 return interpolate_table(coefs,
window.size() > 1 ?
window[1] : 0.0);
161 const double x = std::clamp(
window.empty() ? 0.0 :
window.front(), -1.0, 1.0);
165 for (
size_t k = coefs.size() - 1; k >= 1; --k) {
166 const double b0 = coefs[k] + 2.0 * x * b1 - b2;
170 return coefs.front() + x * b1 - b2;
179 std::span<const double> increments,
180 std::span<const double> scales,
181 std::span<const double> am_increments,
182 std::span<const double> phases,
183 std::span<const double> am_phases)
185 const size_t n = increments.size();
186 std::vector<double> state(1 + 5 * n, 0.0);
187 state.front() =
static_cast<double>(n);
189 for (
size_t i = 0; i < n; ++i) {
190 state.at(1 + i) = increments[i];
191 state.at(1 + n + i) = i < scales.size() ? scales[i] : 1.0;
192 state.at(1 + 2 * n + i) = i < am_increments.size() ? am_increments[i] : 0.0;
193 state.at(1 + 3 * n + i) = i < phases.size() ? phases[i] : 0.0;
194 state.at(1 + 4 * n + i) = i < am_phases.size() ? am_phases[i] : 0.0;
200 std::span<const double> increments,
201 std::span<const double> scales,
202 std::span<const double> phases)
204 const size_t n = increments.size();
205 std::vector<double> state(1 + 5 * n, 0.0);
206 state.front() =
static_cast<double>(n);
208 for (
size_t i = 0; i < n; ++i) {
209 const double phase = i < phases.size() ? phases[i] : 0.0;
210 state.at(1 + i) = std::cos(increments[i]);
211 state.at(1 + n + i) = std::sin(increments[i]);
212 state.at(1 + 2 * n + i) = i < scales.size() ? scales[i] : 1.0;
213 state.at(1 + 3 * n + i) = std::cos(phase);
214 state.at(1 + 4 * n + i) = std::sin(phase);
219double rotor_bank(std::span<const double>, std::span<double> coefs)
noexcept
224 const auto n =
static_cast<size_t>(std::max(coefs.front(), 0.0));
225 if (n == 0 || coefs.size() < 1 + 5 * n)
228 const auto cos_step = coefs.subspan(1, n);
229 const auto sin_step = coefs.subspan(1 + n, n);
230 const auto scales = coefs.subspan(1 + 2 * n, n);
231 const auto re = coefs.subspan(1 + 3 * n, n);
232 const auto im = coefs.subspan(1 + 4 * n, n);
234 rotate_rotors(cos_step.data(), sin_step.data(), re.data(), im.data(), n);
238double phasor_bank(std::span<const double>, std::span<double> coefs)
noexcept
243 const auto n =
static_cast<size_t>(std::max(coefs.front(), 0.0));
244 if (n == 0 || coefs.size() < 1 + 5 * n)
247 const auto increments = coefs.subspan(1, n);
248 const auto scales = coefs.subspan(1 + n, n);
249 const auto am_increments = coefs.subspan(1 + 2 * n, n);
250 const auto phases = coefs.subspan(1 + 3 * n, n);
251 const auto am_phases = coefs.subspan(1 + 4 * n, n);
254 for (
size_t i = 0; i < n; ++i) {
255 phases[i] += increments[i];
256 wrap_revolution(phases[i]);
258 const double swell = 0.5 + 0.5 * std::sin(am_phases[i]);
260 am_phases[i] += am_increments[i];
261 wrap_revolution(am_phases[i]);
263 sum += scales[i] * swell * std::sin(phases[i]);
270 if (coefs.size() < 2)
273 const auto lags =
static_cast<size_t>(std::max(coefs[0], 0.0));
274 const auto powers =
static_cast<size_t>(std::max(coefs[1], 0.0)) + 1;
275 if (coefs.size() < 2 + lags * powers)
279 const size_t reach = std::min(lags,
window.size());
280 for (
size_t k = 0; k < reach; ++k) {
281 const double w =
window[k];
283 for (
size_t j = 0; j < powers; ++j) {
284 sum += coefs[2 + k * powers + j] *
power;
296 const double phase =
window.empty() ? 0.0 :
window.front();
297 const double c1 = std::cos(phase);
298 const double s1 = std::sin(phase);
302 double sum = coefs.front();
304 for (
size_t i = 1; i < coefs.size(); i += 2) {
305 sum += coefs[i] * ck;
306 if (i + 1 < coefs.size())
307 sum += coefs[i + 1] * sk;
309 const double next_c = ck * c1 - sk * s1;
310 sk = sk * c1 + ck * s1;
318 const size_t points = coefs.size() / 2;
325 if (x >= coefs[2 * (
points - 1)])
326 return coefs[2 * (
points - 1) + 1];
329 while (i + 2 <
points && x > coefs[2 * (i + 1)])
332 const double x0 = coefs[2 * i];
333 const double x1 = coefs[2 * (i + 1)];
334 const double y0 = coefs[2 * i + 1];
335 const double y1 = coefs[2 * (i + 1) + 1];
338 return y0 + (y1 - y0) * (x - x0) / (x1 - x0);
343 if (coefs.size() < 5 || !(coefs[3] > coefs[2]))
346 const auto levels =
static_cast<uint32_t
>(std::max(coefs[0], 1.0));
347 const auto count =
static_cast<size_t>(std::max(coefs[1], 1.0));
348 const auto table = coefs.subspan(4);
351 for (
size_t i = 0; i <
count; ++i) {
352 if (
total > table.size() / levels)
357 const Lattice1D lattice { .
resolution = levels, .bounds = { .min = coefs[2], .max = coefs[3] } };
361 for (
size_t i = 0; i <
count; ++i) {
363 index +=
static_cast<size_t>(lattice.cell_at(
value)) * stride;
369double dilate(std::span<const double>
window, std::span<const double> coefs)
noexcept
371 const size_t count = std::min(
window.size(), coefs.size());
375 double b0 =
window[0] + coefs[0];
380 for (; k + 4 <=
count; k += 4) {
381 b0 = std::max(b0,
window[k] + coefs[k]);
382 b1 = std::max(b1,
window[k + 1] + coefs[k + 1]);
383 b2 = std::max(b2,
window[k + 2] + coefs[k + 2]);
384 b3 = std::max(b3,
window[k + 3] + coefs[k + 3]);
386 for (; k <
count; ++k)
387 b0 = std::max(b0,
window[k] + coefs[k]);
388 return std::max(std::max(b0, b1), std::max(b2, b3));
391double erode(std::span<const double>
window, std::span<const double> coefs)
noexcept
393 const size_t count = std::min(
window.size(), coefs.size());
397 double b0 =
window[0] - coefs[0];
402 for (; k + 4 <=
count; k += 4) {
403 b0 = std::min(b0,
window[k] - coefs[k]);
404 b1 = std::min(b1,
window[k + 1] - coefs[k + 1]);
405 b2 = std::min(b2,
window[k + 2] - coefs[k + 2]);
406 b3 = std::min(b3,
window[k + 3] - coefs[k + 3]);
408 for (; k <
count; ++k)
409 b0 = std::min(b0,
window[k] - coefs[k]);
410 return std::min(std::min(b0, b1), std::min(b2, b3));
415 const size_t n =
window.size();
419 thread_local std::vector<double> scratch;
422 const double q = std::clamp(coefs.empty() ? 0.5 : coefs.front(), 0.0, 1.0);
423 const double position =
q *
static_cast<double>(n - 1);
424 const auto lower =
static_cast<size_t>(
position);
425 const double fraction =
position -
static_cast<double>(lower);
427 const auto nth = scratch.begin() +
static_cast<std::ptrdiff_t
>(lower);
428 std::nth_element(scratch.begin(), nth, scratch.end());
429 const double low = *nth;
431 if (fraction <= 0.0 || lower + 1 >= n)
434 const double high = *std::min_element(nth + 1, scratch.end());
435 return low + (high - low) * fraction;
438double zscore(std::span<const double>
window, std::span<const double> coefs)
noexcept
443 const auto past =
window.subspan(1);
444 const double mean = sum_of(past) /
static_cast<double>(past.size());
446 const double floor = coefs.empty() ? 1e-12 : std::max(coefs.front(), 1e-12);
451double goertzel(std::span<const double>
window, std::span<const double> coefs)
noexcept
453 const size_t n =
window.size();
457 const double scale = 4.0 / (
static_cast<double>(n) *
static_cast<double>(n));
460 for (
size_t j = 0; j + 1 < coefs.size(); j += 2) {
461 const double coeff = 2.0 * std::cos(two_pi * coefs[j]);
464 for (
const double x :
window) {
465 const double s = x + coeff * s1 - s2;
469 total += coefs[j + 1] *
scale * (s1 * s1 + s2 * s2 - coeff * s1 * s2);
480 const double modulus = coefs.front();
483 return wrap(
sum, 0.0, modulus);
487 std::span<const double> omegas,
double coupling, std::span<const double> phases)
489 const size_t n = omegas.size();
490 std::vector<double> state(2 + 2 * n, 0.0);
491 state.front() =
static_cast<double>(n);
492 state.at(1) = coupling;
494 for (
size_t i = 0; i < n; ++i) {
495 state.at(2 + i) = omegas[i];
496 state.at(2 + n + i) = i < phases.size() ? phases[i] : 0.0;
501double kuramoto(std::span<const double>, std::span<double> coefs)
noexcept
503 if (coefs.size() < 2)
506 const auto n =
static_cast<size_t>(std::max(coefs.front(), 0.0));
507 if (n == 0 || coefs.size() < 2 + 2 * n)
510 const double coupling = coefs[1];
511 const auto omegas = coefs.subspan(2, n);
512 const auto phases = coefs.subspan(2 + n, n);
513 const double inverse = 1.0 /
static_cast<double>(n);
515 constexpr size_t cached = 256;
516 std::array<double, cached> cosines;
517 std::array<double, cached> sines;
518 const bool reuse = n <= cached;
520 double field_cos = 0.0;
521 double field_sin = 0.0;
522 for (
size_t i = 0; i < n; ++i) {
523 const double c = std::cos(phases[i]);
524 const double s = std::sin(phases[i]);
532 field_cos *= inverse;
533 field_sin *= inverse;
536 for (
size_t i = 0; i < n; ++i) {
537 const double c = reuse ? cosines[i] : std::cos(phases[i]);
538 const double s = reuse ? sines[i] : std::sin(phases[i]);
539 const double pull = coupling * (field_sin * c - field_cos * s);
540 phases[i] += omegas[i] + pull;
541 wrap_revolution(phases[i]);
542 mix += std::sin(phases[i]);
544 return mix * inverse;
548 std::span<const double> cells,
double r,
double epsilon)
550 const size_t n = cells.size();
551 std::vector<double> state(3 + 2 * n, 0.0);
552 state.front() =
static_cast<double>(n);
554 state.at(2) = epsilon;
556 for (
size_t i = 0; i < n; ++i)
557 state.at(3 + i) = cells[i];
563 if (coefs.size() < 3)
566 const auto n =
static_cast<size_t>(std::max(coefs.front(), 0.0));
567 if (n == 0 || coefs.size() < 3 + 2 * n)
570 const double r = coefs[1];
571 const double epsilon = coefs[2];
572 const auto cells = coefs.subspan(3, n);
573 const auto mapped = coefs.subspan(3 + n, n);
575 logistic_map(cells.data(), mapped.data(), r, n);
576 blend_neighbours(cells.data(), mapped.data(), epsilon, n);
577 return sum_of(cells) /
static_cast<double>(n) - 0.5;
581 double dt,
double sigma,
double rho,
double beta,
double x,
double y,
double z)
583 return { sigma, rho, beta, dt, x, y, z };
588 if (coefs.size() < 7)
591 const double sigma = coefs[0];
592 const double rho = coefs[1];
593 const double beta = coefs[2];
594 const double dt = coefs[3];
596 using Vec = std::array<double, 3>;
597 const auto derivative = [sigma, rho, beta](
const Vec& p) -> Vec {
598 return { sigma * (p[1] - p[0]), p[0] * (rho - p[2]) - p[1], p[0] * p[1] - beta * p[2] };
600 const auto advanced = [](
const Vec& p,
const Vec& d,
double h) -> Vec {
601 return { p[0] +
h * d[0], p[1] +
h * d[1], p[2] +
h * d[2] };
604 const Vec start { coefs[4], coefs[5], coefs[6] };
605 const Vec k1 = derivative(start);
606 const Vec k2 = derivative(advanced(start, k1, 0.5 * dt));
607 const Vec k3 = derivative(advanced(start, k2, 0.5 * dt));
608 const Vec k4 = derivative(advanced(start, k3, dt));
610 for (
size_t i = 0; i < 3; ++i)
611 coefs[4 + i] = start[i] + dt / 6.0 * (k1[i] + 2.0 * k2[i] + 2.0 * k3[i] + k4[i]);
618 std::vector<double> state(4 +
values.size(), 0.0);
622 state.at(3) =
static_cast<double>(
values.size());
624 for (
size_t i = 0; i <
values.size(); ++i)
625 state.at(4 + i) =
values[i];
631 if (coefs.size() < 5)
634 const auto n =
static_cast<size_t>(std::max(coefs[3], 0.0));
635 if (n == 0 || coefs.size() < 4 + n)
638 const bool high = !
window.empty() &&
window.front() > coefs[0];
639 const bool latched = coefs[2] > 0.5;
640 auto cursor =
static_cast<size_t>(std::max(coefs[1], 0.0)) % n;
642 if (high && !latched)
643 cursor = (cursor + 1) % n;
645 coefs[1] =
static_cast<double>(cursor);
646 coefs[2] = high ? 1.0 : 0.0;
647 return coefs[4 + cursor];
std::optional< glm::vec3 > position
std::vector< glm::vec2 > * points
Pure functions of a newest-first window and an array of numbers.
std::weak_ptr< Core::Window > window
std::vector< std::byte > values
static double stddev(std::span< const double > samples) noexcept
Standard deviation of a span.
std::vector< double > rotor_bank_state(std::span< const double > increments, std::span< const double > scales, std::span< const double > phases)
Builds the array rotor_bank reads and writes.
double horner(std::span< const double > coefficients, double x) noexcept
Horner evaluation, coefficients highest power first.
std::vector< double > sum(std::span< const double > data, size_t n_windows, uint32_t hop_size, uint32_t window_size)
Sum per window.
double modular_recurrence(std::span< const double > window, std::span< const double > coefs) noexcept
Weighted sum wrapped into [0, modulus).
double phasor_bank(std::span< const double >, std::span< double > coefs) noexcept
Sum of N sinusoidal partials, each with its own slow amplitude swell.
double table_lookup(std::span< const double > window, std::span< const double > coefs) noexcept
Transfer table indexed by the current value.
std::vector< double > power(std::span< const double > data, size_t n_windows, uint32_t hop_size, uint32_t window_size)
Power (sum of squares) per window.
double rotor_bank(std::span< const double >, std::span< double > coefs) noexcept
Sum of N sinusoids advanced by complex rotation instead of a sine call.
double polynomial_lags(std::span< const double > window, std::span< const double > coefs) noexcept
Polynomial in several lags at once.
double weighted_sum(std::span< const double > window, std::span< const double > coefs) noexcept
Sum of window[k] * coefs[k] over the shorter of the two.
double kuramoto(std::span< const double >, std::span< double > coefs) noexcept
Kuramoto oscillators pulled toward their mean phase.
std::vector< double > kuramoto_state(std::span< const double > omegas, double coupling, std::span< const double > phases)
Builds the array kuramoto reads and writes.
double iterated_polynomial(std::span< const double > window, std::span< const double > coefs) noexcept
Polynomial of the previous value, coefs highest power first.
double goertzel(std::span< const double > window, std::span< const double > coefs) noexcept
Weighted energy of the window at chosen frequencies.
double pattern_lookup(std::span< const double > window, std::span< const double > coefs) noexcept
Table indexed by the recent shape of the window.
double erode(std::span< const double > window, std::span< const double > coefs) noexcept
Minimum over the window of window[k] - coefs[k], the grayscale erosion.
double dilate(std::span< const double > window, std::span< const double > coefs) noexcept
Maximum over the window of window[k] + coefs[k], the grayscale dilation.
double quantile(std::span< const double > window, std::span< const double > coefs)
Quantile of the whole window, interpolating between ranks.
double table_lookup_previous(std::span< const double > window, std::span< const double > coefs) noexcept
table_lookup indexed by window[1] instead of window[0].
std::vector< double > coupled_map_lattice_state(std::span< const double > cells, double r, double epsilon)
Builds the array coupled_map_lattice reads and writes.
std::vector< double > phasor_bank_state(std::span< const double > increments, std::span< const double > scales, std::span< const double > am_increments, std::span< const double > phases, std::span< const double > am_phases)
Builds the array phasor_bank reads and writes.
std::vector< double > step_sequence_state(std::span< const double > values, double threshold)
Builds the array step_sequence reads and writes.
double lorenz_attractor(std::span< const double >, std::span< double > coefs) noexcept
Lorenz system stepped once by fourth order Runge-Kutta, returning x.
double step_sequence(std::span< const double > window, std::span< double > coefs) noexcept
Table of values advanced by a trigger.
double breakpoint_curve(std::span< const double > window, std::span< const double > coefs) noexcept
Piecewise linear curve through (x, y) points.
double tapped_sum(std::span< const double > window, std::span< const double > coefs) noexcept
Sum of gain * window[lag] over (lag, gain) pairs.
std::vector< double > lorenz_state(double dt, double sigma, double rho, double beta, double x, double y, double z)
Builds the array lorenz_attractor reads and writes.
double coupled_map_lattice(std::span< const double >, std::span< double > coefs) noexcept
Ring of logistic maps with nearest neighbour coupling.
double fourier_series(std::span< const double > window, std::span< const double > coefs) noexcept
Fourier series of a phase.
std::vector< double > mean(std::span< const double > data, size_t n_windows, uint32_t hop_size, uint32_t window_size)
Arithmetic mean per window.
double chebyshev_series(std::span< const double > window, std::span< const double > coefs) noexcept
Sum of coefs[k] * T_k(window[0]) over Chebyshev polynomials.
double zscore(std::span< const double > window, std::span< const double > coefs) noexcept
How far the newest value sits from the rest of the window, in standard deviations.
double sample_table(std::span< const double > table, double t) noexcept
Read an evenly spaced table at a position in [0, 1], interpolating linearly.
Tendency< D, float > scale(const Tendency< D, float > &t, float factor)
Uniform scaling of a scalar-output tendency.
T wrap(T x, T lo, T hi) noexcept
Wrap x into [lo, hi) with modulo semantics.
uint32_t resolution
Cell count. Zero is invalid.
A regular subdivision of an AABB1D into a number of cells.