Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,60 @@ All notable changes to **namz** are documented here. The format follows
[Semantic Versioning](https://semver.org/). The C++ reference versions independently; each language port
carries its own version.

## [2.0.0] — 2026-08-07 — the `.orbitrig` rig layer

The codec described below packs one model. This layer describes a DEVICE: `rig.json` plus the `.namz`
files it names, so a player can pick a model by a knob and apply the knobs that are not models.

An earlier draft of it went out under **v1.1.0 / v1.1.1** without a changelog entry — those tags carry
the rig layer with a fitted 1-pole where the curve now is, and `schema` still reading 1. They are
superseded, and this entry describes the layer as it actually stands. The codec itself
(`.namz`, wire format 2) is untouched by all of it.

### Added
- **`namz_rig.h` / `_load.h` / `_write.h`** — the manifest: a `chain` of stages, each with its `gear`,
its captured `controls` and the files that realise them.
- **`measured`** — a linear control shipped as a dB magnitude table per position, relative to a stated
`reference` the models were all captured at. The player interpolates and builds a filter; the knob it
draws is continuous.
- **`trusted`** — the band where that table was shown to BE a filter, over a stated drive range, with
the number of levels that showed it. Also as grid indices, so two readers cannot disagree about which
point an edge meant.
- **`blend`** — a dry/wet mix: the dry path's own response and level, per-position gains, a polarity
defined against the model's output, and whether those gains were measured or assumed. Costs no extra
captures, because the dry signal is the DI the model is already fed.
- **Per-value `labels`**, a per-position `level_db`, and a `default` position — three things a player
cannot derive and would otherwise invent.
- **Conformance** — `conformance/rig/` pins the selection policy on a sparse matrix;
`conformance/rig-measured/` pins the shape a client is written against.

### Removed
- **`lp1_hz` / `gain_db` / `residual_db`** — a fitted 1-pole beside the curve, so a simple player could
skip the table. Measured on real hardware the best fit missed a Big Muff tone control by 15 to 65 dB.
Removed rather than deprecated: everything parsed, nothing complained, and the result was confidently
wrong.
- **The clock vocabulary** (`12h`, `07h`) — degrees only. A knob's position is a number, and a filename
means nothing.

### Changed
- **`schema` 1 → 2.** None of the above is additive: a reader built for schema 1 would parse such a
manifest, recognise the keys it knows, drop the rest and be confidently wrong about every measured
knob. That is the failure the number exists to prevent, and it stayed at 1 through all of it. The
writer now spells it from the constant, so the two ends cannot drift apart again.
- **`input_db` on a file entry** — one setting playing another's model with less signal going *into*
it, which is how the bottom of a gain dial fades instead of becoming a model of hiss.
- **`trusted` ships only when levels were actually compared** — an untested claim now says so by being
absent, instead of by carrying a default that reads like a measurement.

### Fixed
- **`NAMZ_VERSION` told the truth for the first time since 1.0.0.** The macros in `namz.h` read
`"1.0.0"` while the tags walked to v1.1.1; a header that misreports itself is worse than one that
says nothing.

### Known consequence
A schema-1 pack that carried a `measured` control reads back with that knob silent — its old shape is
no longer parsed. No such pack was ever published, so this costs nothing today.

## [1.0.0] — 2026-07-05

First stable release of the C++ reference.
Expand All @@ -29,4 +83,5 @@ First stable release of the C++ reference.
- **Lossless** to float32, **deterministic** (byte-identical across runs, platforms, and — via the
conformance vectors — language ports), and **robust** (every malformed input is rejected cleanly).

[2.0.0]: https://github.com/darwinscat/namz/releases/tag/v2.0.0
[1.0.0]: https://github.com/darwinscat/namz/releases/tag/v1.0.0
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# SPDX-License-Identifier: MIT
cmake_minimum_required(VERSION 3.14)
project(namz VERSION 1.0.0 LANGUAGES CXX)
project(namz VERSION 2.0.0 LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
Expand Down
247 changes: 226 additions & 21 deletions NAMZ-FORMAT.md

Large diffs are not rendered by default.

34 changes: 34 additions & 0 deletions conformance/rig-measured/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<!-- SPDX-License-Identifier: MIT -->
# `rig-measured` — the minimal modern device

The second golden pack: **one gain dial + one measured knob**. It is the smallest device shape a
player must handle end to end, and it is deliberately the shape a real dirt pedal has.

```
Golden Drive pedal, tone_type "crunch"
gain a DIAL: sweep 300°, captured at 0 / 150 / 300 → three models
tone MEASURED: linear, never captured — six swept positions shipped as a filter
```

A player does two different things with those two knobs:

- **gain** is in `controls` — turning it SELECTS a file (`m01…m03`);
- **tone** is in `measured` — turning it changes nothing about which file plays; the player builds a
filter from `positions[]` and applies it after the stage. Interpolating between positions is what
makes it a continuous knob out of six measurements.

Everything the tone knob needs is the CURVE: `db[]`, one value per `grid` point, in physical
units on a stated frequency grid. An earlier draft of this fixture also carried a fitted 1-pole
corner (`lp1_hz`, `gain_db`) and a `residual_db` saying how far that fit lay from the truth, so
that a simple player could skip the curve. Measuring real pedals killed it: a Big Muff tone
control is a bass-cut/treble-boost blend of about 30 dB across the band, and the best 1-pole fit
missed it by 15 to 65 dB. Those fields are gone from the format and from this pack; a reader
written against them implements a dead design.

Why this fixture exists separately from `../rig/`: that one pins the SELECTION policy on a sparse
matrix, this one pins the shape a client is actually written against. See `../rig/README.md` for the
three duties, the naming rule (`m01…m03` numbering does not follow settings — names mean nothing),
and regeneration (`NAMZ_REGEN=1`).

The numbers here are hand-authored and plausible, not a real measurement — this is a contract
fixture, not a device model. What must be exact is their round-trip, not their physics.
66 changes: 66 additions & 0 deletions conformance/rig-measured/expected.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
{
"rig": {
"rig_id": "namz-golden-drive",
"name": "Golden Drive",
"modeled_by": "namz conformance"
},
"stage": {
"slot": "pedal",
"gear": { "make": "Darwin's Cat", "model": "Golden Drive", "type": "pedal" },
"tone_type": "crunch",
"controls": "gain:gain=0|150|300",
"sweep": { "gain": 300 },
"measured": [
{
"name": "tone",
"sweep": 300,
"placement": "post",
"reference": "300",
"operating_point": { "gain": "150" },
"grid": { "f_lo": 20.0, "f_hi": 20000.0, "points": 8 },
"trusted": { "lo_hz": 40.0, "hi_hz": 9000.0, "lo_index": 1, "hi_index": 6, "span_db": 24.0, "levels": 8 },
"default": "120",
"positions": [
{ "value": "0", "norm": 0.0, "level_db": -5.15, "db": [-0.2, -0.2, -0.4, -0.9, -3.2, -9.4, -16.8, -24.1] },
{ "value": "60", "norm": 0.2, "level_db": -3.9, "db": [-0.1, -0.2, -0.3, -0.6, -2.1, -6.8, -13.4, -20.2] },
{ "value": "120", "norm": 0.4, "level_db": -2.75, "db": [-0.1, -0.1, -0.2, -0.4, -1.3, -4.4, -10.1, -16.3] },
{ "value": "180", "norm": 0.6, "level_db": -1.78, "db": [0.0, -0.1, -0.1, -0.2, -0.7, -2.6, -7.0, -12.4] },
{ "value": "240", "norm": 0.8, "level_db": -0.93, "db": [0.0, 0.0, -0.1, -0.1, -0.3, -1.2, -3.9, -8.1] },
{ "value": "300", "norm": 1.0, "level_db": 0.0, "db": [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] }
]
}
],
"blend": [
{
"name": "blend",
"sweep": 300,
"reference": "300",
"dry_end": "0",
"default": "150",
"law": "linear",
"gains_measured": false,
"polarity": -1,
"grid": { "f_lo": 20.0, "f_hi": 20000.0, "points": 8 },
"trusted": { "lo_hz": 40.0, "hi_hz": 9000.0, "lo_index": 1, "hi_index": 6, "span_db": 24.0, "levels": 5 },
"dry": [2.1, 1.4, 0.3, -0.2, -0.6, -2.4, -6.1, -11.8],
"dry_level_db": -8.5,
"positions": [
{ "value": "0", "norm": 0.0, "dry_db": 0.0, "wet_db": -120.0 },
{ "value": "150", "norm": 0.5, "dry_db": -6.02, "wet_db": -6.02 },
{ "value": "300", "norm": 1.0, "dry_db": -120.0, "wet_db": 0.0 }
]
}
],
"files": {
"m01.namz": { "gain": "300" },
"m02.namz": { "gain": "0" },
"m03.namz": { "gain": "150" }
}
},
"selection": [
{ "from": "m02.namz", "turn": ["gain", "300"], "expect": "m01.namz",
"why": "every dial position of a one-knob device is captured — the turn is always exact" },
{ "from": "m01.namz", "turn": ["gain", "150"], "expect": "m03.namz",
"why": "and back" }
]
}
Binary file added conformance/rig-measured/pack/m01.namz
Binary file not shown.
Binary file added conformance/rig-measured/pack/m02.namz
Binary file not shown.
Binary file added conformance/rig-measured/pack/m03.namz
Binary file not shown.
Loading
Loading