# 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 3 of 12 tracks have a riser — and all three do it differently.** Five more have
`d10` entirely free; four have `d10` occupied by non-riser material, so they'd need a
different stream. The inconsistency, not the absence, turned out to be the real finding.

| # | 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* | `live/techno/do_it_right.tidal` | riser | ✅ |
| 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.

Track 3 took a second pass to find — no slug I guessed matched. It's
`live/techno/do_it_right.tidal`, confirmed not by filename but by `setcps (89/60/4)` =
**89 BPM**, matching the backlog's `[89*]` exactly. Worth noting as a method: when the
title-to-slug guess fails, the tempo line is the reliable identifier.

## The three existing idioms (they all 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.

**C — Am i Doing it Right?, the clipped one:**

```haskell
d10
  $ mask "<f!4 t f!3>"
  $ loopAt 4 $ "risers:8" # gain 1
  # cut 10
```

An 8-cycle period firing on cycle 5, but `loopAt 4` stretches the sample over 4 cycles while
the mask only opens for 1 — so you hear a *slice* of a slowed riser, not a complete build.
Possibly deliberate (it's a 89 BPM track, a full 4-cycle build would be very long), possibly
a leftover. Uses the same `risers:8` sample as idiom B and the same `cut 10` as idiom A.

### Three inconsistencies worth fixing

1. **Idioms A and C are unwrapped.** Take Five's and Do It Right's `d10` have no `gF`/`gM` —
   the only streams in the set that the global filters and mutes cannot touch. Reach for gF1
   during that build and everything ducks except the riser. Only idiom B wraps correctly
   (`gF1 $ gM1`).
2. **Phase disagreement.** Fires-last-8 (A) vs fires-first-1 (B) vs fires-cycle-5-of-8 (C):
   "the riser" means three different things depending on which track you're in — exactly the
   muscle-memory hazard #33 is about.
3. **Gain disagreement.** `gain 0.8` (A) vs unset (B) vs `gain 1` (C). At three different
   levels across three tempos, the riser will not read consistently to the room.

## 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.
