Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
flow_pans.hpp
Go to the documentation of this file.
1#pragma once
2
3// Flow: per-room, per-block time-varying constant-power pans that turn a static
4// multi-room GPU reverb into a living, moving field.
5//
6// At depth == 0 each room sits at its base azimuth, so the pans equal the static
7// constant-power layout the engine bakes at prepare() — Flow off is bit-for-bit
8// the current sound. Above 0 each room's azimuth drifts on its OWN rate + phase,
9// so the N pan vectors are time-varying and full-rank: the irreducible regime
10// where the batched GPU beats any folding CPU. Constant-power is preserved every
11// instant (pan_l² + pan_r² == norm²), so loudness never pumps as the field moves.
12//
13// Header-only + allocation-free: fills caller-owned buffers, safe to call per
14// block from the GPU worker thread (read `depth` from an atomic on the audio side).
15
16#include <cmath>
17#include <cstdint>
18
19namespace pulp::gpu_audio {
20
21inline float flow_hash(std::uint32_t k) {
22 float x = std::sin(k * 127.1f + 0.7f) * 43758.5453f;
23 return x - std::floor(x);
24}
25
26// Base (depth==0) azimuth for room k of n: evenly spread across [0, π/2]. Matches
27// GpuMultiConvolver's prepared constant-power layout (t = k/(n-1), n==1 → π/4).
28inline float flow_base_azimuth(std::uint32_t k, std::uint32_t n) {
29 constexpr float kHalfPi = 1.5707963f;
30 if (n <= 1) return kHalfPi * 0.5f;
31 return static_cast<float>(k) / static_cast<float>(n - 1) * kHalfPi;
32}
33
34// One room's constant-power pan at time `t_seconds`. `room_index` keys the
35// distinct rate/phase (so the field stays full-rank); `depth`/`spread` shape the
36// drift; `norm` is the per-room gain. depth==0 → exactly (cos·norm, sin·norm) of
37// the base azimuth.
38inline void flow_pan_room(float base_theta, std::uint32_t room_index, float depth,
39 float spread, double t_seconds, float norm,
40 float& pan_l, float& pan_r) {
41 constexpr float kHalfPi = 1.5707963f;
42 constexpr float kTwoPi = 6.2831853f;
43 // Swing: radians of azimuth wander at full depth. ~1.2 rad (≈69°) sweeps a
44 // room most of the way across the field, so the per-room pan gains move by
45 // well over 6 dB — clearly audible motion, not a subtle wobble.
46 const float swing = depth * (0.6f + 0.6f * spread); // radians at full depth
47 // Rate: each room drifts on its own LFO. 0.15–0.5 Hz (2–6.7 s periods) is
48 // slow enough to read as an evolving field yet fast enough that the movement
49 // is obvious within a couple of seconds. The previous 0.05–0.14 Hz band gave
50 // 7–20 s periods — so slow that over a few seconds Flow was nearly inaudible.
51 const float rate = 0.15f + 0.35f * flow_hash(room_index * 5u + 2u); // Hz
52 const float phase = flow_hash(room_index * 11u + 4u) * kTwoPi;
53 float theta = base_theta + swing * std::sin(kTwoPi * rate *
54 static_cast<float>(t_seconds) + phase);
55 // Reflect into [0, π/2] so pans stay in range AND constant-power holds.
56 if (theta < 0.0f) theta = -theta;
57 theta = std::fmod(theta, 2.0f * kHalfPi);
58 if (theta > kHalfPi) theta = 2.0f * kHalfPi - theta;
59 pan_l = std::cos(theta) * norm;
60 pan_r = std::sin(theta) * norm;
61}
62
63// Fill pans for `n` rooms from each room's base azimuth (the engine passes its
64// prepared constant-power azimuths so depth==0 reproduces the baked layout).
65inline void flow_pans_from_base(const float* base_theta, std::uint32_t n, float depth,
66 float spread, double t_seconds, float norm,
67 float* pan_l, float* pan_r) {
68 for (std::uint32_t k = 0; k < n; ++k)
69 flow_pan_room(base_theta[k], k, depth, spread, t_seconds, norm, pan_l[k], pan_r[k]);
70}
71
72// Convenience: even-spread base azimuths (for tests and the still renderer).
73inline void fill_flow_pans(std::uint32_t n, double t_seconds, float depth,
74 float spread, float* pan_l, float* pan_r) {
75 const float norm = n > 0 ? 1.0f / std::sqrt(static_cast<float>(n)) : 1.0f;
76 for (std::uint32_t k = 0; k < n; ++k)
77 flow_pan_room(flow_base_azimuth(k, n), k, depth, spread, t_seconds, norm,
78 pan_l[k], pan_r[k]);
79}
80
81} // namespace pulp::gpu_audio
Definition flow_pans.hpp:19
float flow_hash(std::uint32_t k)
Definition flow_pans.hpp:21
void flow_pans_from_base(const float *base_theta, std::uint32_t n, float depth, float spread, double t_seconds, float norm, float *pan_l, float *pan_r)
Definition flow_pans.hpp:65
void flow_pan_room(float base_theta, std::uint32_t room_index, float depth, float spread, double t_seconds, float norm, float &pan_l, float &pan_r)
Definition flow_pans.hpp:38
float flow_base_azimuth(std::uint32_t k, std::uint32_t n)
Definition flow_pans.hpp:28
void fill_flow_pans(std::uint32_t n, double t_seconds, float depth, float spread, float *pan_l, float *pan_r)
Definition flow_pans.hpp:73