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. | |
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.