Skip to content

File wfm_surface.h

File List > doppler > wfm > wfm_surface.h

Go to the documentation of this file

/*
 * wfm_surface.h — GENERATED by scripts/gen_wfm_defaults.py
 *
 * Do not edit. One row per wfm source or segment field that a
 * face other than Python exposes, generated from `just-makeit.toml`
 * (the rows that declare `cli`) and the headers it names. Every
 * face that reads a field by name reads it from here, so a field
 * cannot be spelled, typed or bounded two ways
 * (docs/design/wfmgen.md, "One surface table"; doppler#853).
 */
#ifndef WFM_SURFACE_H
#define WFM_SURFACE_H

#include <stddef.h>

#include "doppler/wfm/wfm_compose.h"
#include "doppler/wfm/wfm_names.h"

/* Which struct a row's offsets are into. */
typedef enum
{
  WFM_SURF_SOURCE,  /* wfm_source_t */
  WFM_SURF_SEGMENT, /* wfm_segment_t */
} wfm_surf_owner_t;

/* What kind of value a row holds. */
typedef enum
{
  WFM_SV_DOUBLE,
  WFM_SV_INT,
  WFM_SV_SIZE,
  WFM_SV_U32,
  WFM_SV_U64,
  WFM_SV_CHOICE,  /* an int index into `choices` */
  WFM_SV_SYMBOLS, /* float _Complex *, its count at `len_off` */
  WFM_SV_FIELD,   /* a wfm_seq_t, as a Field (wfm_frame.h) */
  WFM_SV_BESPOKE, /* a pointer each face reads with its own code;
                     the generic readers and writers skip it */
} wfm_sv_kind_t;

typedef struct
{
  const char        *name;  /* the struct member */
  const char        *cli;   /* the flag; NULL = not on the CLI */
  wfm_surf_owner_t   owner;
  wfm_sv_kind_t      kind;
  size_t             off;       /* offsetof the member */
  unsigned           range_bit; /* WFM_RANGE_*; 0 = not ranged */
  size_t             hi_off;    /* `<name>_hi`, if ranged */
  size_t             len_off;   /* WFM_SV_SYMBOLS: the count */
  size_t             reps_off;  /* WFM_SV_FIELD: where *REPS goes;
                                    0 = the field does not repeat */
  const char *const *choices;   /* WFM_SV_CHOICE: the names */
  int                n_choices;
  int                unit_interval; /* require 0 < v <= 1 */
  const char        *json;          /* the scene/record key */
  int                json_required; /* absent/unknown: refuse */
  int                json_omit;     /* not written at its default */
  int                json_bool;     /* written as true/false */
  int                has_when;      /* written only when ... */
  int                when_row;      /* ... this row's choice ... */
  int                when_value;    /* ... is this index */
} wfm_surface_row_t;

/* A row's index: WFM_SURFACE_<owner>_<name>. */
enum
{
  WFM_SURFACE_source_type,
  WFM_SURFACE_source_freq,
  WFM_SURFACE_source_snr,
  WFM_SURFACE_source_snr_mode,
  WFM_SURFACE_source_seed,
  WFM_SURFACE_source_sps,
  WFM_SURFACE_source_pn_length,
  WFM_SURFACE_source_pn_poly,
  WFM_SURFACE_source_lfsr,
  WFM_SURFACE_source_level,
  WFM_SURFACE_source_background,
  WFM_SURFACE_source_f_end,
  WFM_SURFACE_source_span,
  WFM_SURFACE_source_doppler,
  WFM_SURFACE_source_doppler_rate,
  WFM_SURFACE_source_carrier_hz,
  WFM_SURFACE_source_doppler_lifetime,
  WFM_SURFACE_source_modulation,
  WFM_SURFACE_source_pulse,
  WFM_SURFACE_source_rrc_beta,
  WFM_SURFACE_source_rrc_span,
  WFM_SURFACE_source_symbols,
  WFM_SURFACE_source_acq_code,
  WFM_SURFACE_source_data_code,
  WFM_SURFACE_source_symbol_rate,
  WFM_SURFACE_source_dsss_code_only,
  WFM_SURFACE_source_frame,
  WFM_SURFACE_source_data_from_file,
  WFM_SURFACE_source_data,
  WFM_SURFACE_source_data_len,
  WFM_SURFACE_source_fill,
  WFM_SURFACE_source_code_only_symbols,
  WFM_SURFACE_source_frame_symbols,
  WFM_SURFACE_segment_fs,
  WFM_SURFACE_segment_num_samples,
  WFM_SURFACE_segment_off_samples,
  WFM_SURFACE_segment_repeats,
  WFM_SURFACE_segment_delay_samples,
  WFM_SURFACE_segment_gap_noise,
  WFM_SURFACE_N
};

static const wfm_surface_row_t WFM_SURFACE[WFM_SURFACE_N] = {
  [WFM_SURFACE_source_type] = {
    .name = "type",
    .cli = "--type",
    .owner = WFM_SURF_SOURCE,
    .kind = WFM_SV_CHOICE,
    .off = offsetof (wfm_source_t, type),
    .choices = TYPE_NAMES,
    .n_choices = (int)(sizeof TYPE_NAMES / sizeof *TYPE_NAMES),
    .json = "type",
    .json_required = 1,
  },
  [WFM_SURFACE_source_freq] = {
    .name = "freq",
    .cli = "--freq",
    .owner = WFM_SURF_SOURCE,
    .kind = WFM_SV_DOUBLE,
    .off = offsetof (wfm_source_t, freq),
    .range_bit = WFM_RANGE_FREQ,
    .hi_off = offsetof (wfm_source_t, freq_hi),
    .json = "freq",
  },
  [WFM_SURFACE_source_snr] = {
    .name = "snr",
    .cli = "--snr",
    .owner = WFM_SURF_SOURCE,
    .kind = WFM_SV_DOUBLE,
    .off = offsetof (wfm_source_t, snr),
    .range_bit = WFM_RANGE_SNR,
    .hi_off = offsetof (wfm_source_t, snr_hi),
    .json = "snr",
  },
  [WFM_SURFACE_source_snr_mode] = {
    .name = "snr_mode",
    .cli = "--snr-mode",
    .owner = WFM_SURF_SOURCE,
    .kind = WFM_SV_CHOICE,
    .off = offsetof (wfm_source_t, snr_mode),
    .choices = MODE_NAMES,
    .n_choices = (int)(sizeof MODE_NAMES / sizeof *MODE_NAMES),
    .json = "snr_mode",
  },
  [WFM_SURFACE_source_seed] = {
    .name = "seed",
    .cli = "--seed",
    .owner = WFM_SURF_SOURCE,
    .kind = WFM_SV_U32,
    .off = offsetof (wfm_source_t, seed),
    .json = "seed",
  },
  [WFM_SURFACE_source_sps] = {
    .name = "sps",
    .cli = "--sps",
    .owner = WFM_SURF_SOURCE,
    .kind = WFM_SV_INT,
    .off = offsetof (wfm_source_t, sps),
    .json = "sps",
  },
  [WFM_SURFACE_source_pn_length] = {
    .name = "pn_length",
    .cli = "--pn-length",
    .owner = WFM_SURF_SOURCE,
    .kind = WFM_SV_INT,
    .off = offsetof (wfm_source_t, pn_length),
    .json = "pn_length",
  },
  [WFM_SURFACE_source_pn_poly] = {
    .name = "pn_poly",
    .cli = "--pn-poly",
    .owner = WFM_SURF_SOURCE,
    .kind = WFM_SV_U64,
    .off = offsetof (wfm_source_t, pn_poly),
    .json = "pn_poly",
  },
  [WFM_SURFACE_source_lfsr] = {
    .name = "lfsr",
    .cli = "--lfsr",
    .owner = WFM_SURF_SOURCE,
    .kind = WFM_SV_CHOICE,
    .off = offsetof (wfm_source_t, lfsr),
    .choices = LFSR_NAMES,
    .n_choices = (int)(sizeof LFSR_NAMES / sizeof *LFSR_NAMES),
    .json = "lfsr",
  },
  [WFM_SURFACE_source_level] = {
    .name = "level",
    .cli = "--level",
    .owner = WFM_SURF_SOURCE,
    .kind = WFM_SV_DOUBLE,
    .off = offsetof (wfm_source_t, level),
    .range_bit = WFM_RANGE_LEVEL,
    .hi_off = offsetof (wfm_source_t, level_hi),
    .json = "level",
    .json_omit = 1,
  },
  [WFM_SURFACE_source_background] = {
    .name = "background",
    .owner = WFM_SURF_SOURCE,
    .kind = WFM_SV_INT,
    .off = offsetof (wfm_source_t, background),
    .json = "background",
    .json_omit = 1,
    .json_bool = 1,
  },
  [WFM_SURFACE_source_f_end] = {
    .name = "f_end",
    .cli = "--f-end",
    .owner = WFM_SURF_SOURCE,
    .kind = WFM_SV_DOUBLE,
    .off = offsetof (wfm_source_t, f_end),
    .range_bit = WFM_RANGE_FEND,
    .hi_off = offsetof (wfm_source_t, f_end_hi),
    .json = "f_end",
    .has_when = 1,
    .when_row = WFM_SURFACE_source_type,
    .when_value = 5,
  },
  [WFM_SURFACE_source_span] = {
    .name = "span",
    .owner = WFM_SURF_SOURCE,
    .kind = WFM_SV_SIZE,
    .off = offsetof (wfm_source_t, span),
    .json = "span",
    .json_omit = 1,
    .has_when = 1,
    .when_row = WFM_SURFACE_source_type,
    .when_value = 5,
  },
  [WFM_SURFACE_source_doppler] = {
    .name = "doppler",
    .cli = "--doppler",
    .owner = WFM_SURF_SOURCE,
    .kind = WFM_SV_DOUBLE,
    .off = offsetof (wfm_source_t, doppler),
    .range_bit = WFM_RANGE_DOPPLER,
    .hi_off = offsetof (wfm_source_t, doppler_hi),
    .json = "doppler",
    .json_omit = 1,
  },
  [WFM_SURFACE_source_doppler_rate] = {
    .name = "doppler_rate",
    .cli = "--doppler-rate",
    .owner = WFM_SURF_SOURCE,
    .kind = WFM_SV_DOUBLE,
    .off = offsetof (wfm_source_t, doppler_rate),
    .range_bit = WFM_RANGE_DOPPLER_RATE,
    .hi_off = offsetof (wfm_source_t, doppler_rate_hi),
    .json = "doppler_rate",
    .json_omit = 1,
  },
  [WFM_SURFACE_source_carrier_hz] = {
    .name = "carrier_hz",
    .cli = "--carrier-hz",
    .owner = WFM_SURF_SOURCE,
    .kind = WFM_SV_DOUBLE,
    .off = offsetof (wfm_source_t, carrier_hz),
    .json = "carrier_hz",
    .json_omit = 1,
  },
  [WFM_SURFACE_source_doppler_lifetime] = {
    .name = "doppler_lifetime",
    .cli = "--doppler-lifetime",
    .owner = WFM_SURF_SOURCE,
    .kind = WFM_SV_CHOICE,
    .off = offsetof (wfm_source_t, doppler_lifetime),
    .choices = DOPPLER_LIFETIME_NAMES,
    .n_choices = (int)(sizeof DOPPLER_LIFETIME_NAMES / sizeof *DOPPLER_LIFETIME_NAMES),
    .json = "doppler_lifetime",
    .json_omit = 1,
  },
  [WFM_SURFACE_source_modulation] = {
    .name = "modulation",
    .cli = "--modulation",
    .owner = WFM_SURF_SOURCE,
    .kind = WFM_SV_CHOICE,
    .off = offsetof (wfm_source_t, modulation),
    .choices = BITMOD_NAMES,
    .n_choices = (int)(sizeof BITMOD_NAMES / sizeof *BITMOD_NAMES),
    .json = "modulation",
    .has_when = 1,
    .when_row = WFM_SURFACE_source_type,
    .when_value = 6,
  },
  [WFM_SURFACE_source_pulse] = {
    .name = "pulse",
    .cli = "--pulse",
    .owner = WFM_SURF_SOURCE,
    .kind = WFM_SV_CHOICE,
    .off = offsetof (wfm_source_t, pulse),
    .choices = PULSE_NAMES,
    .n_choices = (int)(sizeof PULSE_NAMES / sizeof *PULSE_NAMES),
    .json = "pulse",
    .json_omit = 1,
  },
  [WFM_SURFACE_source_rrc_beta] = {
    .name = "rrc_beta",
    .cli = "--rrc-beta",
    .owner = WFM_SURF_SOURCE,
    .kind = WFM_SV_DOUBLE,
    .off = offsetof (wfm_source_t, rrc_beta),
    .unit_interval = 1,
    .json = "rrc_beta",
    .has_when = 1,
    .when_row = WFM_SURFACE_source_pulse,
    .when_value = 1,
  },
  [WFM_SURFACE_source_rrc_span] = {
    .name = "rrc_span",
    .cli = "--rrc-span",
    .owner = WFM_SURF_SOURCE,
    .kind = WFM_SV_INT,
    .off = offsetof (wfm_source_t, rrc_span),
    .json = "rrc_span",
    .has_when = 1,
    .when_row = WFM_SURFACE_source_pulse,
    .when_value = 1,
  },
  [WFM_SURFACE_source_symbols] = {
    .name = "symbols",
    .cli = "--symbols-file",
    .owner = WFM_SURF_SOURCE,
    .kind = WFM_SV_SYMBOLS,
    .off = offsetof (wfm_source_t, symbols),
    .len_off = offsetof (wfm_source_t, n_symbols),
  },
  [WFM_SURFACE_source_acq_code] = {
    .name = "acq_code",
    .cli = "--acq-code",
    .owner = WFM_SURF_SOURCE,
    .kind = WFM_SV_FIELD,
    .off = offsetof (wfm_source_t, acq_code),
    .reps_off = offsetof (wfm_source_t, acq_reps),
    .json = "acq_code",
  },
  [WFM_SURFACE_source_data_code] = {
    .name = "data_code",
    .cli = "--data-code",
    .owner = WFM_SURF_SOURCE,
    .kind = WFM_SV_FIELD,
    .off = offsetof (wfm_source_t, data_code),
    .json = "data_code",
  },
  [WFM_SURFACE_source_symbol_rate] = {
    .name = "symbol_rate",
    .cli = "--symbol-rate",
    .owner = WFM_SURF_SOURCE,
    .kind = WFM_SV_DOUBLE,
    .off = offsetof (wfm_source_t, symbol_rate),
    .json = "symbol_rate",
    .json_omit = 1,
    .has_when = 1,
    .when_row = WFM_SURFACE_source_type,
    .when_value = 8,
  },
  [WFM_SURFACE_source_dsss_code_only] = {
    .name = "dsss_code_only",
    .cli = "--code-only",
    .owner = WFM_SURF_SOURCE,
    .kind = WFM_SV_INT,
    .off = offsetof (wfm_source_t, dsss_code_only),
    .json = "code_only",
    .json_omit = 1,
    .json_bool = 1,
  },
  [WFM_SURFACE_source_frame] = {
    .name = "frame",
    .cli = "--frame",
    .owner = WFM_SURF_SOURCE,
    .kind = WFM_SV_BESPOKE,
    .off = offsetof (wfm_source_t, frame),
    .json = "frame",
  },
  [WFM_SURFACE_source_data_from_file] = {
    .name = "data_from_file",
    .cli = "--data-from-file",
    .owner = WFM_SURF_SOURCE,
    .kind = WFM_SV_BESPOKE,
    .off = offsetof (wfm_source_t, data_from_file),
    .json = "data_from_file",
  },
  [WFM_SURFACE_source_data] = {
    .name = "data",
    .cli = "--data",
    .owner = WFM_SURF_SOURCE,
    .kind = WFM_SV_FIELD,
    .off = offsetof (wfm_source_t, data),
    .json = "data",
  },
  [WFM_SURFACE_source_data_len] = {
    .name = "data_len",
    .cli = "--data-len",
    .owner = WFM_SURF_SOURCE,
    .kind = WFM_SV_SIZE,
    .off = offsetof (wfm_source_t, data_len),
    .json = "data_len",
    .json_omit = 1,
  },
  [WFM_SURFACE_source_fill] = {
    .name = "fill",
    .cli = "--fill",
    .owner = WFM_SURF_SOURCE,
    .kind = WFM_SV_FIELD,
    .off = offsetof (wfm_source_t, fill),
    .json = "fill",
  },
  [WFM_SURFACE_source_code_only_symbols] = {
    .name = "code_only_symbols",
    .cli = "--code-only-symbols",
    .owner = WFM_SURF_SOURCE,
    .kind = WFM_SV_SIZE,
    .off = offsetof (wfm_source_t, code_only_symbols),
    .json = "code_only_symbols",
    .json_omit = 1,
    .has_when = 1,
    .when_row = WFM_SURFACE_source_type,
    .when_value = 8,
  },
  [WFM_SURFACE_source_frame_symbols] = {
    .name = "frame_symbols",
    .cli = "--frame-symbols",
    .owner = WFM_SURF_SOURCE,
    .kind = WFM_SV_SIZE,
    .off = offsetof (wfm_source_t, frame_symbols),
    .json = "frame_symbols",
    .json_omit = 1,
    .has_when = 1,
    .when_row = WFM_SURFACE_source_type,
    .when_value = 8,
  },
  [WFM_SURFACE_segment_fs] = {
    .name = "fs",
    .cli = "--fs",
    .owner = WFM_SURF_SEGMENT,
    .kind = WFM_SV_DOUBLE,
    .off = offsetof (wfm_segment_t, fs),
    .json = "fs",
  },
  [WFM_SURFACE_segment_num_samples] = {
    .name = "num_samples",
    .cli = "--count",
    .owner = WFM_SURF_SEGMENT,
    .kind = WFM_SV_SIZE,
    .off = offsetof (wfm_segment_t, num_samples),
    .range_bit = WFM_RANGE_NUM_SAMPLES,
    .hi_off = offsetof (wfm_segment_t, num_samples_hi),
    .json = "num_samples",
  },
  [WFM_SURFACE_segment_off_samples] = {
    .name = "off_samples",
    .cli = "--off",
    .owner = WFM_SURF_SEGMENT,
    .kind = WFM_SV_SIZE,
    .off = offsetof (wfm_segment_t, off_samples),
    .range_bit = WFM_RANGE_OFF_SAMPLES,
    .hi_off = offsetof (wfm_segment_t, off_samples_hi),
    .json = "off_samples",
  },
  [WFM_SURFACE_segment_repeats] = {
    .name = "repeats",
    .cli = "--repeats",
    .owner = WFM_SURF_SEGMENT,
    .kind = WFM_SV_SIZE,
    .off = offsetof (wfm_segment_t, repeats),
    .json = "repeats",
    .json_omit = 1,
  },
  [WFM_SURFACE_segment_delay_samples] = {
    .name = "delay_samples",
    .cli = "--delay",
    .owner = WFM_SURF_SEGMENT,
    .kind = WFM_SV_SIZE,
    .off = offsetof (wfm_segment_t, delay_samples),
    .range_bit = WFM_RANGE_DELAY_SAMPLES,
    .hi_off = offsetof (wfm_segment_t, delay_samples_hi),
    .json = "delay_samples",
    .json_omit = 1,
  },
  [WFM_SURFACE_segment_gap_noise] = {
    .name = "gap_noise",
    .cli = "--gap-noise",
    .owner = WFM_SURF_SEGMENT,
    .kind = WFM_SV_CHOICE,
    .off = offsetof (wfm_segment_t, gap_noise),
    .choices = GAP_NOISE_NAMES,
    .n_choices = (int)(sizeof GAP_NOISE_NAMES / sizeof *GAP_NOISE_NAMES),
    .json = "gap_noise",
    .json_omit = 1,
  },
};

/* Two rows no face takes together, and the reason, spelled for each
   face that can be given both: the CLI (both flags), a scene (both
   keys), the object (both members set). NULL: that face cannot
   carry one of the two, so it has nothing to refuse. */
typedef struct
{
  int         a, b; /* WFM_SURFACE_<owner>_<name> */
  const char *cli_why;
  const char *json_why;
  const char *obj_why;
} wfm_surface_exclusive_t;

#define WFM_SURFACE_N_EXCLUSIVE 1
static const wfm_surface_exclusive_t
    WFM_SURFACE_EXCLUSIVE[1] = {
  {
    .a = WFM_SURFACE_source_data,
    .b = WFM_SURFACE_source_data_from_file,
    .cli_why = "--data and --data-from-file cannot both be given: a "
        "payload has one data source",
    .json_why = "\"data\" and \"data_from_file\" cannot both be given: a "
        "payload has one data source",
    .obj_why = "a source's data and data_from_file cannot both be set:"
        " a payload has one data source",
  },
};

/* Whether a row's member holds a value: the object face's
   "given". Only the kinds an exclusion may name (the generator
   refuses any other) have an unset value to test for. */
static inline int
wfm_surface_row_is_set (const wfm_surface_row_t *r,
                        const void              *base)
{
  const char *m = (const char *)base + r->off;
  switch (r->kind)
    {
    case WFM_SV_FIELD:
      return ((const wfm_seq_t *)m)->len != 0;
    case WFM_SV_BESPOKE:
      return *(const void *const *)m != NULL;
    case WFM_SV_SYMBOLS:
      return *(const size_t *)((const char *)base + r->len_off)
             != 0;
    default:
      return 0;
    }
}

/* A Field row with no repetition count refuses *REPS: only the
   rows with `field_reps` repeat (a preamble). One reason per face.
*/
#define WFM_SURFACE_REPS_WHY_CLI \
  "only --acq-code repeats (a preamble): drop the *REPS"
#define WFM_SURFACE_REPS_WHY_JSON \
  "only \"acq_code\" repeats (a preamble): drop the *REPS"

/* --help option lines, one string per USAGE section. */
#define WFM_SURFACE_HELP_SIGNAL \
  "  --fs HZ         Sample rate in Hz, one per segment and shared by all its\n" \
  "                  sources. (default 1.0)\n" \
  "  --count N[:N]   Segment on-time in samples, before the trailing gap: 0\n" \
  "                  derives it from the sources, or 1024 when they set none.\n" \
  "                  (default 0)\n" \
  "  --off N[:N]     Trailing gap after the on-time, in samples. (default 0)\n" \
  "  --repeats N     Play the segment this many times back-to-back (each instance\n" \
  "                  = delay + on-time + trailing gap) before advancing.\n" \
  "                  (default 1)\n" \
  "  --delay N[:N]   Leading gap before the on-time, in samples: the burst arrives\n" \
  "                  after this delay, and the gap carries the noise floor like\n" \
  "                  off_samples. (default 0)\n" \
  "  --gap-noise M   Gap policy for this segment's delay and trailing gap. One of:\n" \
  "                  auto | off. (default auto)\n" \
  "  --freq HZ[:HZ]  Carrier or offset frequency in Hz; for chirp, the sweep\n" \
  "                  start. (default 0.0)\n" \
  "  --seed N        PRNG and LFSR seed for the noise and PN streams. (default 0)\n" \
  "  --sps N         Samples per symbol (PSK) or per chip (PN): the oversampling\n" \
  "                  factor. (default 1)\n" \
  "  --f-end HZ[:HZ] Chirp end frequency in Hz; ignored by other types.\n" \
  "                  (default 0.0)\n"

#define WFM_SURFACE_HELP_TYPE \
  "  --type TYPE     Waveform type. One of: tone | noise | pn | bpsk | qpsk |\n" \
  "                  chirp | bits | symbols | dsss. (default tone)\n"

#define WFM_SURFACE_HELP_NOISE \
  "  --snr DB[:DB]   Signal-to-noise ratio in dB, interpreted per snr_mode.\n" \
  "                  (default 100.0)\n" \
  "  --snr-mode MODE How snr is interpreted. One of: auto | fs | ebno | esno.\n" \
  "                  (default auto)\n"

#define WFM_SURFACE_HELP_PN \
  "  --pn-length N   PN LFSR register length in bits, 2 to 64 for the PN-bearing\n" \
  "                  types; the sequence period is 2^pn_length - 1. (default 15)\n" \
  "  --pn-poly N     PN generator polynomial, in the Galois bit-vector convention;\n" \
  "                  0 selects a maximal-length (MLS) polynomial for pn_length.\n" \
  "                  (default 0)\n" \
  "  --lfsr TYPE     PN LFSR realisation. One of: galois | fibonacci.\n" \
  "                  (default galois)\n"

#define WFM_SURFACE_HELP_AMPLITUDE \
  "  --level DB[:DB] Source power in dBFS (<= 0; 0 is unit power). (default 0.0)\n"

#define WFM_SURFACE_HELP_DOPPLER \
  "  --doppler PPM[:PPM]\n" \
  "                  Clock Doppler in ppm: the received time base is rescaled by 1\n" \
  "                  + doppler*1e-6, so the symbol and chip rates move with the\n" \
  "                  carrier and a timing loop sees the error a carrier-only\n" \
  "                  `freq` offset hides. (default 0.0)\n" \
  "  --doppler-rate PPM_S[:PPM_S]\n" \
  "                  Linear ramp on `doppler`, in ppm per second of elapsed stream\n" \
  "                  time. (default 0.0)\n" \
  "  --carrier-hz HZ RF carrier in Hz that the ppm figures are referred to.\n" \
  "                  (default 0.0)\n" \
  "  --doppler-lifetime L\n" \
  "                  How long this source's Doppler channel lives. One of:\n" \
  "                  per_instance | persist. (default per_instance)\n"

#define WFM_SURFACE_HELP_BITS \
  "  --modulation M  Symbol mapping of a bits pattern. One of: none | bpsk | qpsk.\n" \
  "                  (default bpsk)\n" \
  "  --data-from-file PATH\n" \
  "                  A data source read from a file of packed octets, MSB first,\n" \
  "                  or `-` for stdin, instead of data. Not with --data.\n" \
  "  --data FIELD    A frame's payload drawn from a data source: a Field on the\n" \
  "                  command line and in a scene, a bit array in Python. Not with\n" \
  "                  --data-from-file.\n" \
  "  --data-len BITS Bits of the data source per frame: the data:LEN of the common\n" \
  "                  frame [preamble x reps | sync | data:LEN | crc]. (default 0)\n" \
  "  --fill FIELD    The bits that pad a data source's last frame when it does not\n" \
  "                  divide into data_len-bit frames, tiled from their first bit;\n" \
  "                  stdin on a framed source always needs them.\n"

#define WFM_SURFACE_HELP_PULSE \
  "  --pulse SHAPE   Pulse shape per symbol or chip, for\n" \
  "                  pn/bpsk/qpsk/bits/symbols/dsss. One of: rect | rrc.\n" \
  "                  (default rect)\n" \
  "  --rrc-beta R    RRC roll-off factor, in (0, 1], when pulse=rrc.\n" \
  "                  (default 0.35)\n" \
  "  --rrc-span N    RRC filter support in symbols when pulse=rrc, ONE-SIDED: the\n" \
  "                  filter has 2*rrc_span*sps + 1 taps, unit energy (sum of h^2 =\n" \
  "                  1). (default 8)\n"

#define WFM_SURFACE_HELP_SYMBOLS \
  "  --symbols-file F\n" \
  "                  For type=symbols: a complex constellation stream.\n"

#define WFM_SURFACE_HELP_DSSS_BURST \
  "  --acq-code FIELD\n" \
  "                  The preamble code, sent acq_reps times at the head of the\n" \
  "                  frame (a Field's *REPS on the command line and in a scene) --\n" \
  "                  the coherent pull-in target BurstDespreader.set_acq and\n" \
  "                  BurstDemod.set_preamble lock to.\n" \
  "  --data-code FIELD\n" \
  "                  For type=dsss: the payload spreading code, a second code\n" \
  "                  distinct from acq_code.\n"

#define WFM_SURFACE_HELP_DSSS_CONT \
  "  --symbol-rate HZ\n" \
  "                  For type=dsss: > 0 selects CONTINUOUS asynchronous mode.\n" \
  "                  (default 0.0)\n" \
  "  --code-only     Continuous dsss data source: 1 = code-only (the pure\n" \
  "                  spreading code, no data modulation); 0 = data-modulated (the\n" \
  "                  payload when supplied, else the seeded PN). (default 0)\n" \
  "  --code-only-symbols N\n" \
  "                  Continuous dsss: pure-code symbols opening each frame of\n" \
  "                  frame_symbols symbols. (default 0)\n" \
  "  --frame-symbols N\n" \
  "                  Continuous dsss: the frame, in symbols, that\n" \
  "                  code_only_symbols opens; give it with code_only_symbols, or\n" \
  "                  both 0 for no window. (default 0)\n"

#define WFM_SURFACE_HELP_CODED \
  "  --frame FILE    A frame DESCRIPTION, the whole frame: fields in wire order,\n" \
  "                  and stages that each name the span they cover (crc16, rs,\n" \
  "                  randomise, interleave, conv, or a kind of your own).\n"

/* The keys each object of a scene takes, generated from the schema's
   `properties` (docs/schema/wfmgen.schema.json). The reader refuses any
   other key by name: the schema and the reader are one table (#1153). */
typedef enum
{
  WFM_JSON_ROOT,
  WFM_JSON_INLINE_SEGMENT,
  WFM_JSON_SUM_SEGMENT,
  WFM_JSON_SOURCE,
  WFM_JSON_FRAME,
  WFM_JSON_FIELD,
  WFM_JSON_STAGE,
  WFM_JSON_N_LEVELS
} wfm_json_level_t;

static const char *const WFM_JSON_KEYS_ROOT[] = {
  "continuous",
  "headroom",
  "repeat",
  "seed_advance",
  "segments",
  "version",
  NULL
};

static const char *const WFM_JSON_KEYS_INLINE_SEGMENT[] = {
  "acq_code",
  "background",
  "carrier_hz",
  "code_only",
  "code_only_symbols",
  "data",
  "data_code",
  "data_from_file",
  "data_len",
  "data_sent",
  "delay_samples",
  "doppler",
  "doppler_lifetime",
  "doppler_rate",
  "f_end",
  "fill",
  "frame",
  "frame_symbols",
  "freq",
  "fs",
  "gap_noise",
  "level",
  "lfsr",
  "modulation",
  "num_samples",
  "off_samples",
  "pn_length",
  "pn_poly",
  "pulse",
  "repeats",
  "rrc_beta",
  "rrc_span",
  "seed",
  "snr",
  "snr_mode",
  "span",
  "sps",
  "symbol_rate",
  "symbols",
  "type",
  NULL
};

static const char *const WFM_JSON_KEYS_SUM_SEGMENT[] = {
  "delay_samples",
  "fs",
  "gap_noise",
  "num_samples",
  "off_samples",
  "repeats",
  "sum",
  NULL
};

static const char *const WFM_JSON_KEYS_SOURCE[] = {
  "acq_code",
  "background",
  "carrier_hz",
  "code_only",
  "code_only_symbols",
  "data",
  "data_code",
  "data_from_file",
  "data_len",
  "data_sent",
  "doppler",
  "doppler_lifetime",
  "doppler_rate",
  "f_end",
  "fill",
  "frame",
  "frame_symbols",
  "freq",
  "level",
  "lfsr",
  "modulation",
  "pn_length",
  "pn_poly",
  "pulse",
  "rrc_beta",
  "rrc_span",
  "seed",
  "snr",
  "snr_mode",
  "span",
  "sps",
  "symbol_rate",
  "symbols",
  "type",
  NULL
};

static const char *const WFM_JSON_KEYS_FRAME[] = {
  "fields",
  "stages",
  NULL
};

static const char *const WFM_JSON_KEYS_FIELD[] = {
  "bits",
  "derived_by",
  "name",
  "spec",
  NULL
};

static const char *const WFM_JSON_KEYS_STAGE[] = {
  "depth",
  "emit_den",
  "emit_num",
  "first_field",
  "kind",
  "n_fields",
  "unit_bits",
  NULL
};

static const char *const *const WFM_JSON_KEYS[WFM_JSON_N_LEVELS] = {
  WFM_JSON_KEYS_ROOT,
  WFM_JSON_KEYS_INLINE_SEGMENT,
  WFM_JSON_KEYS_SUM_SEGMENT,
  WFM_JSON_KEYS_SOURCE,
  WFM_JSON_KEYS_FRAME,
  WFM_JSON_KEYS_FIELD,
  WFM_JSON_KEYS_STAGE,
};

#endif /* WFM_SURFACE_H */