# Riser audit — OPAL 2026 set (task #33)

**Date:** 2026-07-25 (J-10) · **Scope:** the 12 tracks named in `backlog.md` § OPAL 2026
· **Method:** static analysis of each track's `.tidal` (grep for `d10` + riser/sweep/impact
vocabulary), then reading every hit in context. No ear-validation — this is the *structural*
pass. Which risers actually sound good together is PLN's call.

## The headline

**Only 2 of 12 tracks have a riser.** Five more have `d10` entirely free; four have `d10`
occupied by non-riser material, so they'd need a different stream.

| # | Track | BPM | File | `d10` state | Riser? |
|---|---|---|---|---|---|
| 1 | Ceci n'est pas Une Bombe | 124 | `live/midi/nova/techno/bombe_dj.tidal` | free | ❌ |
| 2 | WAP | 133 | `live/midi/nova/dnb/wap.tidal` | free | ❌ |
| 3 | Am i Doing it Right? | 89* | **file not located** | — | ❓ |
| 4 | Take Five Drops | 124 | `live/midi/nova/lounge/take_5_drops.tidal` | riser | ✅ |
| 5 | Piment Brésilien | 124 | `live/collab/raph/piment_bresilien.tidal` | "Menace" | ❌ |
| 6 | Perfect <3 | 128 | `live/midi/nova/remix/perfect.tidal` | "Euclidian mid arps" | ❌ |
| 7 | Gimme Acid | 160 | `live/midi/nova/acid/gimme_acid.tidal` | "GIMME THAT" | ❌ |
| 8 | Vague de CRIME | 120 | `live/techno/vague_de_crime.tidal` | riser | ✅ |
| 9 | Mafia | 160 | `live/collab/raph/mafia_sans_serif.tidal` | free | ❌ |
| 10 | Sunshine | 166 | `live/midi/nova/dnb/liquid/you_my_sunshine.tidal` | free | ❌ |
| 11 | Desire (FINALE) | 129 | `live/collab/raph/desire.tidal` | free | ❌ |
| 12 | Revolution (encore) | — | `live/midi/nova/jazz/the_revolution_will_be_sampled.tidal` | "SUGAR KEY SWEET TRIGGER" | ❌ |

Bonus: `live/midi/nova/techno/electric_hammer.tidal` ("le shifteur marteau", floated as the
closer) also has no riser.

> **Open question for PLN:** track 3, *"Am i Doing it Right?"*, has no `.tidal` matching any
> obvious slug. Per the repo convention ("one track = one standalone `.tidal`, it always
> exists") it should be findable — it's probably filed under a name I didn't guess. Needed
> before the set is complete.

## The two existing idioms (they disagree)

**A — Take Five Drops, the "fair riser".** The considered one:

```haskell
d10  -- A FAIR RISER
  $ mask "<f!24 t!8>"     -- sleeps 24 cycles, fires on the last 8 → a 32-cycle phrase
  $ loopAt 8              -- stretches the sample to fill exactly that 8-cycle window
  $ "risers"
  # cut 10                -- own cut group: a new riser chokes the old one
  # room 0.4 # dry 1.4
  # octersubsub 0.5
  # gain 0.8
```

32 cycles = 8 bars, with the build over the final 2. `loopAt 8` is the load-bearing trick:
it makes the riser's *length* independent of the sample's natural length, so the same
sample lands correctly at 120 and at 166 BPM. That's what makes it reusable across the set.

**B — Vague de CRIME, the terse one:**

```haskell
d10 $ gF1 $ gM1 $ mask "<t f!7>" $ "risers:8"
```

Fires on cycle **1 of 8** and plays at the sample's natural length. Note it builds *out of*
the downbeat rather than *into* one — an accent, not a build-up. Different musical function
despite the same folder.

### Two inconsistencies worth fixing

1. **Idiom A is unwrapped.** Take Five's `d10` has no `gF`/`gM` — it is the only stream in
   the set that the global filters and mutes cannot touch. If you reach for gF1 during that
   build, everything ducks except the riser. Idiom B wraps correctly (`gF1 $ gM1`).
2. **Phase disagreement** (fires-last-8 vs fires-first-1) means "the riser" means two
   different things depending on which track you're in — exactly the muscle-memory hazard
   #33 is about.

## Proposal — one house helper

Idiom A, parameterised and wrapped, so a riser is a one-liner per track. Derived verbatim
from PLN's own working Take Five block, not invented:

```haskell
    -- RISER (#33) — house build-up. `gRiser period build` sleeps for
    -- (period-build) cycles then stretches a riser across the final `build`,
    -- so it lands identically at any BPM. Self-choking on cut group 10.
    gRiser period build =
        (# cut 10) . (# room 0.4) . (# dry 1.4) . (# octersubsub 0.5) . (# gain 0.8)
        . loopAt (pure $ fromIntegral build)
        . mask (slow (fromIntegral period) $ fastFromList
                 (replicate (period - build) False ++ replicate build True))
```

Per-track usage then collapses to:

```haskell
d10 $ gF1 $ gM1 $ gRiser 32 8 $ "risers:3"     -- 8-bar phrase, build over the last 2
d10 $ gF1 $ gM1 $ gRiser 16 4 $ "risers:8"     -- tighter, for the 160+ dnb tracks
```

**Status: type-checked and structurally verified, but NOT ear-validated, NOT committed.**

Verified two ways before proposing it, rather than asserted:

- Compiles clean against the real `tidal-1.9.5` install (`ghc -fno-code`, no stream started).
- The generated mask is **bit-identical to PLN's hand-written `<f!24 t!8>`** — `queryArc`
  over cycles 0–32 gives exactly 24 × `False` then 8 × `True` for both, compared elementwise:
  `mine == theirs → True`. So `gRiser 32 8` is a faithful refactor of the Take Five block,
  not a lookalike.

What's still unverified is everything musical: whether the stock `# gain 0.8 # room 0.4`
sits right under a 166 BPM liquid dnb track is a question for ears, not grep. Suggested
trial order:
Desire (the FINALE — the one place a missing build is most costly), then Mafia and Sunshine
(the two 160+ tracks that carry the set's peak).

`risers` folder has 24+ samples (`RPE - FX - Riser 1-12`, `RPE2 - FX - Riser 1-12`), several
BPM-tagged in the filename (`- 140`, `- 144`, `- 145`) — worth matching tag to track tempo
where the natural length matters, though `loopAt` largely removes the need.

## Recommendation

Risers are cheap insurance for the thing PLN actually flagged — transitions. With
default-xfade dN now baked in (#13, commit `98d073f`), every track *launch* already blends
over 4 cycles. A riser on `d10` is the **musical** counterpart to that **structural**
crossfade: the xfade stops the seam being abrupt, the riser tells the room a change is
coming. The two together are what turn "ctrl+enter and pray" into a transition.

Deferred to PLN: sign off `gRiser`, then it's ~10 one-line additions.
