# Crash BASIC 1.36.3 Release Notes

*~written by Crash-tan~*

## THE "STOP MAKING ME SET LOOP POINTS BY HAND" PATCH

Sampled instruments grew up this release. Three shifts to how a `.wav`
becomes a playable instrument: the engine now finds sustain loops on its
own, there's a proper pitch-shift mode that preserves a sample's natural
decay envelope instead of speeding it up with the pitch, and the data
shapes that carry all of this through the engine are cleaner.

---

## Auto-loop detection

Drop a WAV into an instrument layer and the engine looks at it at
decode time. If it finds something loopable, it picks the loop points
for you. Two paths:

**Envelope-based** — for samples with a clear sustain plateau (organs,
pads, sustained synth notes). The detector finds where the attack ends
and where the release begins, and loops a region inside the sustain.
For pitched material it loops an integer number of cycles so the loop
seam stays musically coherent.

**Short-pitched fallback** — for samples that are too short or too
envelope-shaped to have a "sustain plateau" but are still pitched
(guitar stabs, synth blips, the short-and-pitchy stuff). When the
envelope path can't find a sustain region, but autocorrelation
confirms the sample is pitched, the engine loops the entire sample
end-to-start with zero-crossing snap on both endpoints to kill the
loop-seam click.

What stays one-shot: truly transient material with no detectable
pitch (snare hits, blip stabs, percussion). The pitch confidence
threshold is intentionally strict for the short-pitched path — a noise
burst won't get auto-looped just because it has spurious
correlation. Worst case the engine punts and you play one-shot,
which is what you'd have gotten before.

The fallback is gated to samples shorter than ~2 seconds so a long
envelope-shaped sample (a 5-second piano hit) doesn't accidentally
machine-gun itself looking for a loop. Long samples keep the
conservative behavior — envelope-detect or nothing.

## LoopBehavior: explicit when you want it, magic when you don't

Both the `.cbi` `LayerSample` field and the runtime `SampleBinding`
grew an explicit loop mode:

- **Auto** (default) — what's described above. Pre-`LoopMode` `.cbi`
  files with explicit `loop_points` still play exactly the same
  (legacy fallback in the deserializer maps that case to Manual).
- **One-shot** — force no loop, regardless of how loopable the sample
  looks. For sounds you really want to play exactly once.
- **Manual** — use the explicit `loop_start` / `loop_end` frames you
  specified.

The protobuf carries this as a `LoopMode` enum on `SetInstrumentWavCmd`
alongside the legacy `has_loop` / `loop_start` / `loop_end` fields
(unchanged so old serialized commands still decode the same way).

## PitchMode::Stretch — pitched without speeding up the envelope

Here's the one for the sampled-piano people.

The historical sample oscillator couples pitch and duration: play a
sample at twice its base pitch and you read the PCM twice as fast,
which means the sample takes half as long to finish. For a pure
oscillator-style sample that's fine — there's no envelope to compress.
For a sampled piano, it's wrong: the piano's natural attack-decay
envelope shouldn't speed up just because you played a higher note.

New `PitchMode::Stretch` decouples the two. At voice-trigger time the
engine runs the sample through a WSOLA + phase-vocoder pitch shifter
(via the `timestretch` crate — pure Rust, all platforms, including
the WASM build). The output is the same sample at the played pitch,
**same natural duration as the source**. A 2-second piano hit stays
2 seconds long whether you play it at C4, C5, or C2.

Set it on a per-layer basis in `.cbi`:

```json
{
  "wavPath": "piano.wav",
  "baseNote": 60,
  "pitchMode": "stretch"
}
```

Or on a runtime binding via the `pitch_mode` field on `SampleBinding`.
Default is still `Resample` so existing instruments sound exactly
the same as before — Stretch is opt-in per layer.

**Quality is best on tonal material within about ±1 octave of the
base note.** Phase vocoder gets crunchy at extreme pitch shifts and
on transient-rich content — which is fine, because the things you'd
*want* to Stretch (pianos, plucks, sustained vocal samples) are the
things it handles well.

**Pre-stretching low-rate sources:** for samples below 44.1 kHz
(SNES-era game assets, retro packs), the engine upsamples to at least
44.1 kHz before pitch-shifting so the pitch-shift algorithm has
enough FFT windows of context to work with. Costs a one-shot linear
resample per voice trigger; pays for itself in audible quality on the
short low-rate sources where the algorithm would otherwise starve.

**Stretch is heavier than Resample** (an FFT per voice trigger), which
is fine if you're pre-rendering music. For live note-by-note keyboard
performance with a lot of polyphony, you'll want Resample.

## Not in this patch

`SETINSTRUMENTWAV` from BASIC still only takes `LOOP start, end` — no
`PITCH` clause yet. So pitch-mode opt-in lives in `.cbi` files and
runtime bindings for now. Grammar clause coming in a later release —
the engine plumbing is all there, just waiting on parser work.

---

*A sampled piano should sound like a piano at any note. Now it does.*

*— Crash-tan*
