# Sc Stream Enum

> Add a new streamable enum to shortcircuit-xt using the STREAM_ENUM pattern. Generates DECLARE_ENUM_STRING in the header, toString/fromString implementations in the .cpp, and STREAM_ENUM in the appropriate *_traits.h file.

- Skill: `baconpaul/sc-stream-enum` (Agent Skill)
- Install (CLI): `npx skillmds@latest add baconpaul/sc-stream-enum`
- Raw SKILL.md: https://api.skillmd.com/api/skills/baconpaul/sc-stream-enum/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: baconpaul (https://skillmd.com/u/baconpaul)
- Updated: 2026-09-21
- Page: https://skillmd.com/skills/baconpaul/sc-stream-enum

---


# sc-stream-enum: Streaming a New Enum in shortcircuit-xt

**Repo:** `surge-synthesizer/shortcircuit-xt`. Paths are relative to the repo root.

This skill implements the full pattern for making an enum JSON-streamable in shortcircuit-xt.
There are three touch points: the **header** (declaration), the **.cpp** (implementation),
and the **traits header** (tao::json wiring).

For the wider serialization picture see the `shortcircuit-streaming` skill.

---

## The Three-Part Pattern

### Part 1: Header declaration (`DECLARE_ENUM_STRING`)

Inside the class or struct that owns the enum, add the macro immediately after the enum definition:

```cpp
enum PlayMode
{
    NORMAL,
    ONE_SHOT,
    ON_RELEASE,
};
DECLARE_ENUM_STRING(PlayMode);
```

`DECLARE_ENUM_STRING(E)` expands to:
```cpp
static std::string toStringE(const E &);
static E fromStringE(const std::string &);
```

Both functions are `static` members of the enclosing class. The macro is defined in `src/scxt-core/utils.h`.

### Part 2: .cpp implementation

Add **two functions** to the owning class's `.cpp` file. Use short, lowercase JSON-friendly names
for the string values (no spaces, avoid underscores where possible):

```cpp
std::string OwnerClass::toStringPlayMode(const PlayMode &p)
{
    switch (p)
    {
    case NORMAL:
        return "normal";
    case ONE_SHOT:
        return "oneshot";
    case ON_RELEASE:
        return "onrelease";
    }
    return "normal";   // default = first/safest value
}

OwnerClass::PlayMode OwnerClass::fromStringPlayMode(const std::string &s)
{
    static auto inverse = makeEnumInverse<OwnerClass::PlayMode, OwnerClass::toStringPlayMode>(
        OwnerClass::PlayMode::NORMAL, OwnerClass::PlayMode::ON_RELEASE);
    auto p = inverse.find(s);
    if (p == inverse.end())
        return NORMAL;   // default = first/safest value
    return p->second;
}
```

**Key rules:**
- `toStringX` must handle every enumerator and have a fallback `return` after the switch.
- `fromStringX` uses `makeEnumInverse<EnumType, toStringFn>(firstValue, lastValue)` — the range
  must be contiguous integers (standard C++ enum without explicit values, or with consecutive values).
- The `static auto inverse` is constructed once (lazy, thread-safe in C++11+).
- If the enum is not in a class, drop the `OwnerClass::` prefix; both functions become free functions.

### Part 3: Traits wiring (`STREAM_ENUM` in `*_traits.h`)

Add `STREAM_ENUM(...)` to the appropriate traits header in `src/scxt-core/json/`:

| Data domain | Traits file |
|---|---|
| `engine::Zone`, `engine::Group`, `engine::Part`, `engine::Bus`, group triggers, clipboard | `engine_traits.h` |
| Modulation (`modulation::*`, LFO, step seq, etc.) | `modulation_traits.h` |
| Top-level / cross-cutting | `scxt_traits.h` |

In practice nearly every `STREAM_ENUM` is in `engine_traits.h`, with a handful in
`modulation_traits.h`. Confirm where a sibling type went before picking:

```bash
grep -rn 'STREAM_ENUM(' src/scxt-core/json/
```

```cpp
STREAM_ENUM(engine::Zone::PlayMode, engine::Zone::toStringPlayMode,
            engine::Zone::fromStringPlayMode);
```

The macro arguments are: **(1)** fully-qualified enum type, **(2)** fully-qualified toString fn,
**(3)** fully-qualified fromString fn.

**`STREAM_ENUM` vs `STREAM_ENUM_WITH_DEFAULT`:**
- `STREAM_ENUM` — standard; always writes `{"e": "value"}` to JSON.
- `STREAM_ENUM_WITH_DEFAULT` — if the value equals the specified default, writes `{}` instead.
  Use sparingly; only when the enum is used in a single, tightly-controlled context. The default
  value is the second argument: `STREAM_ENUM_WITH_DEFAULT(MyEnum, MyEnum::DEFAULT_VAL, toStr, fromStr)`.

---

## Checklist

- [ ] Enum defined in the header with contiguous integer values (no explicit gaps)
- [ ] `DECLARE_ENUM_STRING(EnumName)` added in the header, inside the owning class
- [ ] `toString` switch covers all enumerators; uses short lowercase strings; has a fallback return
- [ ] `fromString` uses `makeEnumInverse` with correct first/last enumerator range
- [ ] Default in `fromString` matches the first/safest enumerator
- [ ] `STREAM_ENUM(...)` added to the correct `*_traits.h` file with fully-qualified names
- [ ] Build passes (the macro expansion instantiates a `scxt_traits<E>` specialization — duplicate
  `STREAM_ENUM` for the same type will cause a compile error)

---

## Complete Example: Adding `Zone::PlayMode`

**`src/scxt-core/engine/zone.h`** (inside `class Zone`):
```cpp
enum PlayMode
{
    NORMAL,
    ONE_SHOT,
    ON_RELEASE,
};
DECLARE_ENUM_STRING(PlayMode);
```

**`src/scxt-core/engine/zone.cpp`**:
```cpp
std::string Zone::toStringPlayMode(const PlayMode &p)
{
    switch (p)
    {
    case NORMAL:
        return "normal";
    case ONE_SHOT:
        return "oneshot";
    case ON_RELEASE:
        return "onrelease";
    }
    return "normal";
}

Zone::PlayMode Zone::fromStringPlayMode(const std::string &s)
{
    static auto inverse = makeEnumInverse<Zone::PlayMode, Zone::toStringPlayMode>(
        Zone::PlayMode::NORMAL, Zone::PlayMode::ON_RELEASE);
    auto p = inverse.find(s);
    if (p == inverse.end())
        return NORMAL;
    return p->second;
}
```

**`src/scxt-core/json/engine_traits.h`** (near other Zone STREAM_ENUMs):
```cpp
STREAM_ENUM(engine::Zone::PlayMode, engine::Zone::toStringPlayMode,
            engine::Zone::fromStringPlayMode);
```

---

## Applying This Skill

When the user provides an enum, do the following:

1. **Read the header file** where the enum is declared to confirm its enumerators and owning class.
2. **Read the .cpp file** (same basename as the header) to see where other toString/fromString
   implementations live — add the new ones nearby for consistency.
3. **Identify the correct traits file** from the domain table above. If uncertain, search for
   existing `STREAM_ENUM` calls for sibling types and use the same file.
4. **Generate all three pieces** and insert them. Group `STREAM_ENUM` lines with related ones.
5. **Build to verify** — a missing `#include "utils.h"` or type mismatch in `makeEnumInverse`
   will produce a clear compile error.

If the enum is a free-standing enum (not inside a class), the `static` keyword is dropped from
the function declarations and the `OwnerClass::` prefix is absent everywhere.

---

## Backward compatibility

The string is the wire format, so **renaming one breaks every saved patch containing it.**
`fromString` returns the default for anything unrecognized, which means a rename does not
crash — it silently resets the value. If you must rename, keep the old string accepted in
`fromString` and only change what `toString` emits.

Adding a new enumerator is safe in the other direction too: older builds reading a newer
file fall back to the default rather than failing the load.
