Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
modal_spec.hpp File Reference

ModalSpec — a modal instrument as a data file rather than a C++ class. More...

#include <pulp/signal/modal_bank.hpp>
#include <algorithm>
#include <cmath>
#include <cstddef>
#include <optional>
#include <string>
#include <string_view>
#include <vector>

Go to the source code of this file.

Classes

struct  pulp::signal::ModalSpecExcitation
 How the resonator is struck. More...
struct  pulp::signal::ModalShapeMap
 A mode shape sampled on a uniform grid across the object's normalized extent [0, 1], mode-major. More...
struct  pulp::signal::ModalSpecTolerances
 The PASS criteria a render of this spec must meet. More...
struct  pulp::signal::ModalSpec
 A modal instrument, complete. More...
struct  pulp::signal::ModalSpecDiagnostics
 What a parse found beyond the spec itself. Always populated; read it. More...

Namespaces

namespace  pulp
namespace  pulp::signal

Functions

void pulp::signal::resolve_modes (const ModalSpec &spec, float strike_position, float pickup_position, std::vector< ModalMode > &out)
 Resolve the spec's modes at a strike/pickup position into out.
std::vector< ModalMode > pulp::signal::resolve_modes (const ModalSpec &spec, float strike_position=0.5f, float pickup_position=0.5f)
 Convenience overload; allocates.
template<typename SampleType>
void pulp::signal::load_modes (const ModalSpec &spec, ModalBankT< SampleType > &bank, std::vector< ModalMode > &scratch, float strike_position=0.5f, float pickup_position=0.5f)
 Resolve the spec at a strike/pickup position and load the result into an already-prepared bank.
template<typename SampleType>
void pulp::signal::prepare_and_load (const ModalSpec &spec, ModalBankT< SampleType > &bank, double sample_rate, float strike_position=0.5f, float pickup_position=0.5f)
 Prepare bank to hold this spec's modes at sample_rate and load them.
bool pulp::signal::validate_modal_spec (const ModalSpec &spec, std::string &error)
 Check a spec's internal consistency.
std::optional< ModalSpec > pulp::signal::parse_modal_spec (std::string_view json, ModalSpecDiagnostics *diagnostics=nullptr)
 Parse a ModalSpec from JSON.
std::string pulp::signal::to_json (const ModalSpec &spec, bool pretty=true)
 Serialize a spec to JSON.
bool pulp::signal::operator== (const ModalSpec &a, const ModalSpec &b)
 Exact equality over every known field.
bool pulp::signal::operator!= (const ModalSpec &a, const ModalSpec &b)

Variables

constexpr int pulp::signal::kModalSpecSchemaVersion = 1
 Schema version this build writes and is the highest it can read.

Detailed Description

ModalSpec — a modal instrument as a data file rather than a C++ class.

A ModalSpec is the complete description of a struck/plucked resonator: its modes, how it is excited, how strike and pickup position weight each mode, and — the part that makes it a contract rather than a blob — the tolerances within which a render of it must reproduce those modes. The spec is its own acceptance criterion: a harness can load a spec, render it, measure the render, and decide PASS/FAIL without a human in the loop and without a golden file to keep in sync.

The spec drives ModalBankT and does not modify it. Everything it can express is a mode set the bank already plays.

── The gain contract ───────────────────────────────────────────────────── ModalBankT scales its excitation by gain such that a mode's impulse response is exactly gain * r^n * sin((n+1) w). So the gain written in a spec is the amplitude a measurement of the rendered impulse response reads back: the number you author and the number the harness measures are the same number. resolve_modes() preserves this exactly when the spec carries no shape maps; when it does, the resolved gain is gain * strike_weight * pickup_weight and that product is what the measurement reads back. Any change here must keep that property — it is what allows tolerances.gain_rel to be checked against a render at all.

── Layering ────────────────────────────────────────────────────────────── This header is pure data + arithmetic: it depends on modal_bank.hpp and the standard library, and knows nothing about JSON. The serialization functions declared at the bottom are implemented in modal_spec.cpp, which is the only place choc's JSON parser is included. That firewall keeps pulp-signal — a header-only INTERFACE target that every DSP translation unit pulls in — free of a JSON dependency.

── Threading ───────────────────────────────────────────────────────────── Parsing, serializing and validating are loader-thread operations and allocate freely. resolve_modes() and load_modes() allocate only when the caller's scratch vector needs to grow, so a caller that pre-sizes its scratch can re-resolve (to move a strike position, say) without allocating. Nothing here is safe to call from process() regardless; ModalBankT::set_modes() evaluates transcendentals per mode.