| 175 | } |
| 176 | |
| 177 | float ClientTube::shape(float x) const noexcept |
| 178 | { |
| 179 | const float bias = m_cached.bias; |
| 180 | // Asymmetric term — always non-negative, produces DC offset + |
| 181 | // even harmonics. tanh-bounded so it never blows up at high |
| 182 | // drive; peak contribution capped at ±bias. |
| 183 | const float asym = bias * std::tanh(x * x); |
| 184 | switch (m_cached.model) { |
| 185 | case static_cast<uint8_t>(Model::A): // soft tanh |
| 186 | return std::tanh(x) + asym; |
| 187 | case static_cast<uint8_t>(Model::B): { // hard clip + tanh hybrid |
| 188 | // Hard clip at ±1 followed by tanh softening — gives a |
| 189 | // stronger odd-harmonic character than pure tanh. |
| 190 | const float h = std::clamp(x, -1.0f, 1.0f); |
| 191 | return std::tanh(h * 1.3f) * 0.85f + asym; |
| 192 | } |
| 193 | case static_cast<uint8_t>(Model::C): { // asymmetric-dominant |
| 194 | // tanh-bounded asymmetric shaper — stronger even-harmonic |
| 195 | // content than A/B without the unbounded growth of raw |
| 196 | // x² terms. |
| 197 | const float t = std::tanh(x); |
| 198 | const float a2 = std::tanh(x * x); |
| 199 | return 0.75f * t + (0.35f + 0.65f * bias) * a2; |
| 200 | } |
| 201 | } |
| 202 | return std::tanh(x); |
| 203 | } |
| 204 | |
| 205 | void ClientTube::process(float* interleaved, int frames, int channels) noexcept |
| 206 | { |
no outgoing calls