# Drums

A local, foreground Web MIDI rhythm partner. Chrome/Edge + HTTPS for a real kit;
**Try a groove** and the keyboard pads work without MIDI hardware.

- Public site: **https://a.skh.am/tools/drums/**
- Live development: **https://ms-mac.miku-owl.ts.net:8444/tools/drums/** (tailnet only).
- Local: `python3 scripts/serve-drums.py --port 4173` → `http://127.0.0.1:4173/tools/drums/`.
  Refresh to get edits; no service worker or browser asset cache.

## Connect and play

1. Open **Setup → MIDI**, connect MIDI, and choose your kit input and a **separate** output.
2. Load instruments in your DAW, monitoring channels **1 = bass, 2 = chords, 3 = pulse**.
   Channels, velocity scaling, octaves and mutes are independent; drum channel 10 is excluded.
3. Start. **Follow kit** waits for the first drum hit; **Fixed tempo** uses the target BPM.
4. In follow mode, set the pulse source and hits per beat to what you actually play.
   The default is eighth-note hats. Arbitrary syncopation is not enough to unambiguously
   infer a beat; use a steady anchor while confidence builds, or select Fixed tempo.

On Windows, create a `Drums Accompaniment` port in **loopMIDI** and select it as the
output here and the input in Ableton. loopMIDI has no sound of its own. Use instrument
tracks with matching MIDI channel filters and monitoring enabled. Do not send MIDI
back into the same port. Browser synth is optional; use an ASIO driver and a suitable
buffer in your DAW for low audio latency.

## Kit mapping, state and backups

Defaults cover common General MIDI kit notes. **Setup → Kit → Learn kit step by step**
captures one pad at a time, then waits for Next. MIDI bounce cannot advance the step.
The entire draft is saved only at **Finish & save**; Cancel preserves your old map.
Navigating away asks before discarding captured work. You can also Learn a single pad,
or enter comma-separated MIDI note numbers under Individual pads. Learning moves a
note out of its previous mapping. Note-off/zero-velocity events are not strikes.

All kit maps, channel assignments, musical settings, instrument profiles and custom
keyswitch maps save to **localStorage** after validation. No notes, audio or device data
are uploaded. Port preferences are remembered on this device. Enabled MIDI controls
reconnect on reload when the browser reports existing MIDI permission; otherwise
click Connect MIDI. Control enablement is restored, but musical output still waits
for an explicit Start. Reconnection never resumes playback by itself.

**Setup → Settings → Export settings / Import settings** move one JSON file between browsers or URLs.
Tailscale and GitHub Pages are different origins with separate localStorage. Export
from the old URL and import at the new one. Port IDs are local-machine preferences and
are deliberately not included in the portable export. Imports are limited to 128 KiB,
validated before mutation, and leave transport stopped. Older version-1 maps are
merged with new defaults rather than discarded. Reset settings is explicit.

## MIDI-map performance controls

Open **Setup → Controls**. Named actions include transport, Hold/Home, tap/target tempo,
tempo lock, clock modes, styles, pulse ratios, activity/swing, lane mutes/velocities,
melody/variation and arp direction/range, articulation, strum and note gates, Preview
and Click. All performance actions use the same registry as the controls and API.

### Quick learning

1. Select your kit input in **Setup → MIDI**. An **extra control input** is optional for
   a separate keyboard/foot controller; leave it empty when using spare kit notes.
2. In Controls, choose Note or CC, then **Pick a control on the page**. Click a highlighted
   button/fader and hit the spare note or move the controller. Or choose an action from
   the grouped Action menu and click **Learn trigger**.
3. Review the captured input/channel/number, then **Save binding**. Learning never runs
   the action. A hit that is already a musical pad gets an explicit conflict warning.
4. Click **Enable MIDI controls** once. The preference persists until you disable it
   or use Panic. The main view shows Controls on, off, or waiting for an input.

Bindings save automatically with your kit/instrument settings and are included in
export/import. Relearn, disable or remove individual bindings in the saved list.
Overlapping triggers (including any-channel overlap) are rejected rather than silently
choosing one. Existing kit maps are never erased to make room for a binding.

### Triggers and values

- Notes trigger on positive note-on; note-off/velocity-zero do nothing. A note-off is
  not required between hits. Default debounce is 200ms (80ms for tap); edit it if your
  pad produces doubles. Minimum velocity can require a deliberate stronger hit.
- CC buttons trigger on an upward threshold crossing, with release hysteresis. Held
  values do not repeat the action. Default press threshold is 64.
- Continuous CCs control target tempo, activity, swing and the three lane velocities.
  Soft pickup avoids jumps until the knob crosses the current value. Values coalesce;
  the final value is retained even when updates are throttled.
- A note mapped to a continuous action uses **note velocity** as the value. Up/down
  actions are also available when a fixed increment per hit is preferable.
- Target tempo drives the clock in **Fixed tempo**. Independent rhythm models share
  this clock; automatic multi-source tempo inference is not implemented.

### Musical notes and safety

By default an enabled binding reserves its note for controls, even while MIDI control
**actions** are switched off, so control hits do not distort the learned groove/tempo.
Check **Also play as a kit note** to pass it through the normal kit mapper/channel filter.
Then all matching musical hits still pass through, even if an individual control action
is debounced or below its velocity threshold. Disabling/removing the binding restores
ordinary kit handling. Unbound notes on the extra control port never enter the kit path.

Normal Stop, tempo/profile edits and silence leave controls enabled. Learning, hidden
tabs and unavailable inputs temporarily suspend actions, retaining your preference;
controls resume when the page/input is usable again. Other connected control inputs
can keep working when one port disappears. **Panic** and **Disable MIDI controls**
clear the enabled preference. Reset restores defaults. Output is stopped on hidden-tab,
disconnect and timing-stall paths; a fresh Start is required, with no stale command replay.
The page must prepare browser audio through a click; a MIDI note cannot grant autoplay
permission. If Preview/Click was never prepared, click it on the page before using an
audio-enabling MIDI binding. With Preview/Click off, MIDI-only control works without audio.
In Follow kit, mapped Start waits for the next musical hit, like the Start button.

Only the performance registry is bindable—no arbitrary commands, device changes,
imports, reset, permission grants or control arming. Maximum 64 bindings and 32 queued
commands; emergency stop bypasses the queue. Continuous changes and UI notifications
are coalesced; localStorage writes are rate-limited and flushed on shutdown. Old queued
commands expire rather than replaying a burst. A monotonic sent-event cursor prevents
explicit tempo changes from replaying already-scheduled notes; this does not solve the
separate multi-source tempo-inference problem.

If saved bindings become invalid, valid kit/instrument settings are preserved; a bounded
recovery copy is kept under `drums.controls.recovery.v1` in localStorage when possible.

## Instrument rack

**The band → + Add instrument** adds an independent entry. You can keep, mute,
rename or remove the original three entries; they are no longer mandatory roles.
Up to **15 entries** use distinct channels 1–16, excluding drum channel 10.
Each entry has its own kit/clock source, profile, channel, register, melody, gate,
voicing, rhythm and articulation settings. Saving/exporting includes the complete rack.

Entry types: bass line, chord layer, arpeggiator, melodic line, drone/pad,
kit-triggered instrument, riff/chug sequencer, and Euclidean rhythm. Templates include
**Kick-triggered guitar**, **7/8 chug guitar** and **Stutter guitar**.

### A percussive guitar setup

1. Add **7/8 chug guitar**. It starts on a free channel with the editable Argent
   profile. Route that channel to the corresponding Kontakt guitar track.
2. Keep its Rhythm source on **Clock**. **Cycle beats = 3.5** makes a seven-eighth
   cycle against the band's shared clock. Use 5 for 5/4 or 5.5 for 11/8.
3. Edit **Riff pattern**: `X` is an accented attack, `x` an ordinary attack, `.` or
   `-` a rest. Up to 32 cells share the chosen cycle. It does not restart every
   four-beat scheduling window.
4. Choose **Articulation phrase → Chugs + accents**, **Mute / dead stutter**, or
   **Custom keyswitch sequence**. A custom example is
   `powerMute mute mute dead mute sustain mute dead`. Sequence positions follow
   riff steps, including rests; live-triggered entries advance per strike.
   A fixed Articulation overrides the phrase. Unmapped power mutes/staccatos fall
   back to a mapped palm mute before sustain, never to a guessed MIDI note.
5. Add a second **Kick-triggered guitar**, on a different channel/profile. Each kick
   triggers it directly, without waiting for pattern inference or adding a loop.
   Choose snare, ride, hats or toms instead to make other responses.
6. Bring layers in/out with their mute buttons or MIDI bindings. The **▶** action
   triggers a fresh note/chord; after Start it can also launch a waiting Follow
   transport. It never arms stopped output. Looped entries resume on the next
   unscheduled bar; live entries respond on the next hit.

Guitar templates default to **Pitch follows → Home key / pedal**, so unrelated band
chord changes do not move the low riff. Choose **Band harmony** for layers that should
follow those changes. Register and the profile's playable range determine the low
notes; verify both against your actual Kontakt patch/tuning.

A kit source on a non-live entry uses its learned rhythm after lock, with a style or
entry-pattern fallback while learning. Choose **Clock** to retain your programmed
riff/rate. The inspector disables clock-pattern fields while locked kit timing owns
that entry. **Euclidean rhythm** exposes steps, hits, rotation and independent cycle
length. All entries have finite MIDI-learn actions for mute, trigger and musical changes;
removed entries also lose their bindings.

### Triggered bursts (0.7)

A **Kit-triggered instrument** can play 1–32 notes for each hit. **Notes per hit**
also accepts a CSV cycle: `1,2,3,2` generates 1, then 2, then 3, then 2 notes, and
repeats. Each entry advances independently, once per accepted hit. Ignored, muted,
zero-level or rejected triggers do not consume a step. New Start or editing the
count sequence resets its cursor; other edits and mute/unmute preserve it. Up to
32 counts are supported. The inspector and live view show the next count.

In its inspector:

- **Ride triplet:** Rhythm = Ride, Melody = Riff or Arpeggio, Notes per hit = 3,
  Burst timing = Eighth-note triplets.
- **Kick pair:** Rhythm = Kick, Notes per hit = 2, Burst timing = Sixteenth notes.
- **Meter-shaped group:** choose Fill one bar, then a time signature and note count.
  Seven notes in 7/8 at 120 BPM have 250 ms spacing.
- **Burst tempo:** Follow at trigger captures the current clock BPM for the whole
  group. Independent BPM uses that entry's own tempo. Later clock changes do not
  stretch an already-started group. Fixed burst BPM is not rounded by clock snapping.
- **Retrigger:** Restart cuts the unfinished group; Overlap lets groups coexist;
  Ignore skips hits while a group/tail is active. Restart is the default. Select
  Overlap if you want every ride hit to complete a group despite faster incoming hits.
- **Restart melody each hit** repeats the same motif opening; otherwise notes advance
  through the riff across hits. Explicit Trigger uses the same burst settings.

Mute, removal, source/profile/channel/keyswitch-mode changes, Stop and Panic cancel
unsent burst notes. Queues are bounded; excessive overlap is reported rather than
silently growing memory. Harmony and tempo are captured per group. Live articulation
can still follow sustained kit intensity.

### Odd bars, kick/snare beat placement, breakdowns, song sections, practice, MIDI clock (0.12)

**Bar meter (odd time for the whole band).** The clock block has a meter picker
(also Setup → Music → Bars & breakdowns): 4/4, 7/8, 5/4, 13/16, 15/8, 16/4 and more,
or **Auto meter**. Beat lights show the bar's units (seven eighths in 7/8, thirteen
sixteenths in 13/16); chords, phrases, style patterns, drones, Hold/Home and the
metronome (accented downbeat, eighth clicks in x/8) all follow the bar. Auto uses
the snare's locked repeating cycle (or the kick's) — so a 7/8 groove gets 7/8 bars
and a Meshuggah-style 7/8 riff over a 4/4 backbeat stays 4/4 — and aligns the
downbeat to your strongest kick, inserting one pickup bar if needed. Changes start
on the next unscheduled bar; nothing already sent is re-timed.

**Kick/snare beat placement.** In Groove grid detection, if kicks sit on one half of
the detected beat and snares on the other (kick on 1, snare on 2 read as one beat),
the tempo is corrected to double — so playing only 8ths with a 16th grid no longer
needs ½× / 2×. Half-time feels (snare on 3) are left alone.

**Breakdowns.** A breakdown is a half-time feel (one snare or fewer per four beats,
but present) played heavy (dense kicks or loud), detected over two bars; two bars of
normal backbeat end it. Choose what happens in Setup → Music:

- **Chug:** guitars and riffs pedal the home root with palm mutes (muted power chords
  on accents), melodies and arps rest, chords sustain a root power chord.
- **Switch to a profile:** load a chosen breakdown profile on the next bar (keeping
  tempo, kit map and controls) and return to your section when the backbeat returns
  — or when you Stop.

**Song sections.** Setup → Profiles → Song sections: list profiles in order (verse,
chorus, breakdown…), reorder with ↑ ↓. The ‹ section › strip next to the profile
pill (and **Next section** in the live view) switches **on the next bar**, keeping
tempo/clock detection, kit map and controls, and — with **Keep recorded riffs** —
the melodies you recorded this session. A section's meter applies on its first bar.
**Next / Previous song section** are MIDI-mappable.

**Timing practice.** Under Your groove → **Timing practice**: every hit is compared
with the nearest grid note (your pulse grid), per pad and note value (beat, 8ths,
16ths, triplets…): average (rushing / dragging in ms), spread, and % within ±10 ms.
Reset any time (also MIDI-mappable). In Follow mode the clock adapts to you, so
use Fixed tempo for the strictest score.

**MIDI clock out.** Setup → MIDI → **Send MIDI clock**: 24 ppq timing clock, Song
Position + Start when the band starts (Continue at the next 16th if enabled
mid-song), Stop on Stop/Panic. It follows the followed tempo continuously, pulses
are timestamped ~30 ms ahead like notes, and it can go to the accompaniment port or
a **separate clock port** (e.g. a loopMIDI port Ableton syncs to: enable Sync on that
input in Ableton's MIDI preferences). Device-local; never part of a profile.

### Tempo detection: grids, odd tuplets and combined pads (0.11)

**Setup → Kit → Follow your pulse** now has three settings:

- **Pulse source:** Hi-hats, Kick, Snare, Ride, **Hats + ride**, **Kick + snare** or
  **Every hit (all pads)**. Yes — combined sources merge every selected pad into one
  onset stream; hits within the grid's merge window (≈18–55 ms, so flams and kick+hat
  on the same note) count once, using the louder velocity.
- **Pulse grid · notes per beat:** straight ½, 1, 2, 4, **8 (32nds)**, 16; triplet 3, 6,
  12, 24; odd **5, 7, 9, 10, 20, 30**.
- **Detection:**
  - **Steady pulse** (the previous behaviour): every hit is the next grid step. Best
    for one timekeeper (hats/ride), and handles swing.
  - **Groove grid** (new): hits land _on_ the grid, with rests and mixed pads. It
    finds the coarsest step that explains nearly every gap between onsets, then reads
    that step as 1/N beat. Set the grid to the **finest note you play**.
  - **Auto** (default) uses Groove grid for Kick + snare and Every hit, Steady pulse
    for single sources — so existing hi-hat setups behave exactly as before.

Why Every hit + 16ths used to fail: Steady pulse assumed each combined hit was the
next 16th, so a groove with mostly 8th-note gaps read as half or double tempo, or as
irregular. Groove grid fixes that. Once a tempo is established, a stretch of only 8ths
(or a 32nd fill) is read as a subdivision change, not a tempo jump.

The readout under the settings shows the method, step (ms), how many gaps fit and the
BPM. If it locks at double or half speed, **Half time ½×** / **Double time 2×** fix it
in one click: in Follow they switch to the next finer/coarser grid (e.g. 16ths ↔ 32nds)
and rescale the clock immediately; in Fixed tempo they halve/double the BPM. Both are
MIDI-mappable (Clock → Half-time / Double-time fix).

Good playing tolerance: about ±10 ms timing spread at 16ths up to ~160 BPM. Very loose
fast 16ths (≈15 ms spread at 190 BPM) can settle a few BPM off; tempo lock or tap fixes it.

Per-drum **time signatures** (the Auto / meter selector on each groove row) gain
explicit odd and long cycles: 5/16, 7/16, 9/16, 11/16, 13/16, 15/16, 13/8, 15/8, 8/4,
10/4, 12/4, 16/4, plus 32nds, 16th quintuplets and 32nd triplets as explicit grids.
Auto cycle/grid search is unchanged (so it does not start guessing 32nds). Triggered
bursts gain 32nd notes and 16th quintuplets.

The shared clock, chords and phrases still count 4-beat bars; odd meters live per drum
and per riff/burst.

### Profiles: save and switch whole setups (0.10)

Use the **PROFILE** pill (next to Birthday) to switch modes in one click. It lists your
saved profiles, **Save changes** when the current one is edited (● dot), and **Save as
new**. **Setup → Profiles** manages them: rename, Load, **Save current here**, Delete,
and per-profile **Kit map** / **MIDI controls** inclusion. Export/Import moves every
profile between browsers (old single-settings exports import as one profile).

- A profile stores the instrument rack (entries, channels, keyswitch profiles,
  generators, riffs, recorded melodies, bursts, articulation), harmony/key/scale,
  tempo and clock mode, style/feel, per-drum rhythm settings and Birthday name/mode.
- **Kit pad map** and **MIDI controls** are optional per profile (off for new ones), so
  your physical kit and controller layout stay put unless you choose otherwise.
  Theme, MIDI device ports, control enablement and the Birthday photo never change.
- **Backwards compatible:** your existing browser settings are untouched and appear as
  **My setup** (including kit map and controls). The first time you change anything,
  that original setup is preserved in the profile store before the edit is saved.
- Switching never loses work: if the current setup has unsaved changes, it is kept and
  **Recover** in Setup → Profiles restores it (pressing it again swaps back).
- Switching keeps playback running unless the profile changes your kit map/channel.
  **Next / Previous saved profile** are MIDI-mappable in Setup → Controls.
- Unreadable saved profiles are kept (never silently deleted) and marked; a corrupt
  store is copied to a recovery key before a fresh My setup is shown.

### Heavy kit playing (0.9)

**+ Add instrument → + Heavy rig** adds four related parts in one step and switches
harmony to **Phrygian · Heavy shifts** (your root is kept):

- **Kick-locked chug** — every kick fires a gallop chug (`1,1,2,1,3` notes per hit);
  hard kicks add an octave stab, soft kicks become dead notes.
- **Polymeter riff** — a 13-sixteenth riff (3¼ beats) cycling against your 4/4, the
  displaced-riff feel of modern djent. Pedal stays home; ♭2 accents on capital X.
- **Snare tritone stab** — snare hits fire the root; hits above 60% add a tritone.
- **Crash sub drop** — crashes trigger a held low root that follows the harmony.

Individual templates are also in the Add menu (plus **Doom grind**, a chromatic
16th-note sequencer). Every entry gains:

- **Heavy melodies:** Chug, Gallop, Polymeter shift, Doom chromatic, Tritone and
  ♭2 cluster. These are semitone shapes, so ♭2/tritone stay dissonant in any scale.
- **Accent stab** (octave / power fifth / tritone / ♭2 cluster) above a velocity
  threshold, and **Soft hits → dead notes**. Only the first note of a burst stabs.
- **Power** chord voicing (root · fifth · octave).
- New scales: Phrygian, Locrian, Harmonic minor, Phrygian dominant. **Heavy shifts**
  harmony holds a pedal, lifts a ♭2 on fills and jumps a tritone in busy bars and at
  phrase ends.

All patterns are original; nothing transcribes an artist's riff.

### Melody input and recorded riffs (0.9)

Setup → MIDI → **Melody input** selects a keyboard/controller port (it cannot be the
kit or control port). Alternatively choose a **Melody channel** to split one port: that
channel never plays drums or MIDI controls. While playing, melody notes monitor on
the recording/recorded entry's channel and are released on key-up, Stop and Panic.

In **Melody & articulation**, choose an entry and press **● Record melody**, then play
notes in order (notes within 70 ms form a chord). **■ Save** stores up to 64 steps and
selects **Recorded from MIDI**; **Discard take** or Escape cancels. You can also type
steps (`E2 G2 B♭2+F3`, or MIDI numbers). **Recorded pitch** follows band chords, the
home key or plays exact notes. The entry's own rhythm (clock, riff pattern, locked
kit cycle or kit trigger) decides when each next step sounds.

### Birthday mode (0.9)

Press **🎂 Birthday**. The first time, Setup → Birthday opens: enter a **name**, choose
a **photo** (resized locally; stored only in this browser and never exported) and,
optionally, the **tune voice**. Balloons float behind the page and the live view
shows the photo, **HAPPY BIRTHDAY, NAME!** and the lyric line with the current syllable.

While on, the band loops _Happy Birthday_ every 24 beats in your home root (major):
one voice plays the melody; riff-type voices follow the tune pitch; chords, pedals,
arps and bass follow its I–V7–I–I7–IV–I–V7–I progression at the song position, including
changes inside a bar. Kit-triggered parts still fire from your drums. Hold/Home are
paused because the song owns the harmony. Press the button again to return.

### Fullscreen performance view (0.8)

Click **Live view** in the top transport bar. **Live view on Start** below the mixer
(or **On Start** inside the view) saves automatic entry for future Starts. It is off
by default and never reopens merely because you reload the page.

- The kit layout flashes the part you played. Learned onset marks and a moving
  cycle hand surround each drum; the detected meter/ratio, confidence and state
  stay overlaid. Uncertain/unplayed parts do not invent a time signature.
- The output panel shows the assigned kit source → instrument/channel, generated
  pitches and articulation/keyswitch, recent note activity and burst progress.
  CSV counts are displayed with the **next** step underlined, independently of
  the currently sounding burst's position.
- Tempo, shared four-beat clock, chord and transport state remain prominent.
  Input/output device names and setup forms are omitted for streaming.
- **Escape** or **Exit** leaves the view **without stopping music**. Stop and Panic
  remain explicit buttons. Outside this view, Escape retains its Panic behavior.
  Native browser fullscreen exit also returns to the regular page.
- Browser fullscreen needs permission/user activation. If denied or started from
  MIDI, a viewport-filling version still opens; click **Fullscreen** to retry with
  a gesture. This does not block playback. A cancelled/failed auto-Start closes its view.
- Keyboard kit pads and first-three-entry mute shortcuts still work. Kit visuals are
  clickable pads; output mute controls work normally. Focus stays inside the stage
  and returns to the prior control on exit. Narrow screens scroll; large racks have
  an output scroll region. Reduced-motion mode removes sweeping hands and scrolling
  note trails. No WebGL/GPU extension is required.

The view reports generated output, not an acknowledgement from a physical synth.
Patterns come from the same engine snapshots used by MIDI generation. Its recent
output history is bounded to 128 events and hidden-view rendering is suspended.

### MIDI bridge compatibility

The app **never calls MIDIOutput.clear()**, including when a bridge advertises a
wrapper that throws internally. Every future send stays in an owned, per-entry
software queue until due. Mute and Panic cancel those jobs locally; other entries are
not cleared/replayed. The snapshot reports `midi.scheduling = software` and any
release error in `midi.lastError`. A driver send failure stops output rather than
preventing the requested logical mute from being saved.

Scheduling follows the foreground 8ms timer; hardware/OS/Kontakt latency is external.
Keep keyswitch lead shorter than very dense attack spacing. Refresh and check the
**0.8** footer if an old open tab still reports an `output.clear()` error. If an older
session left physical keys held after losing ownership, **Reset keys** explicitly sends
note-offs for that entry's mapped keyswitches, even while stopped. It does not send note-ons
or a global all-notes-off command.

## Melody and expression

Open **Melody & articulation** below the mixer, then choose an entry.
Tabs also accept Left/Right, Home and End.

- **Melody:** pedal, root/fifth, stepwise, riff, question/answer, arpeggio or chord blocks.
- **Arp direction / range:** up, down, up/down, down/up, outside-in or seeded random,
  over one to three octaves. **Variation** rotates/develops the selected motif.
- **Voicing:** close, open or spread chord blocks. **Gate** chooses rhythmic attacks
  or held notes; **Note length** applies to rhythmic gates.
- **Articulation / Tracked intensity:** automatic contextual switching follows the
  selected drum part's smoothed hit rate and velocity (whole kit for Clock/Style sources),
  with hysteresis and **Intensity hold (beats)**
  (default 2). A ghost note, syncopation or one loud hit no longer selects a new key.
  Soft playing uses sustain, driving playing uses palm mute, and sustained heavy
  playing can use a mapped muted power chord. Profiles without it fall back to mute.
- **Articulation phrase:** choose Tracked intensity for that behavior. Chugs, stutters
  and Custom are deliberate step sequences, shown as **Phrase sequence** rather than
  contextual mode. Existing explicit phrases are preserved. Fixed Sustain/Palm mute/etc.
  override either automatic source.
- **Keyswitch → Held until changed**
  holds the physical MIDI switch until another articulation, mute or Stop; Latched
  sends a single short selection pulse. Repeated notes no longer retrigger the same
  switch, and replacement releases the old held switch first. Restarting a triggered
  group preserves an unchanged intensity-driven or fixed held/latched switch.

The panel reports the actual MIDI keyswitch selection. Presets are editable starting
points, not proof of your Kontakt snapshot: choose the correct instrument profile and
verify the MIDI note numbers and latch/held behavior in **Setup → Instruments**.
The keyswitch protocol is tested; a physical Shreddage/Djinn setup still needs an
operator listening test. No new preset map was guessed in this pass.

Melody, direction, range, voicing and variation changes apply at the next unscheduled
window. Articulation/gate changes affect the next attack. Learned rhythm timing stays
independent of pitch choices, and each lane can still follow a different drum part.

### Steady tempo and rests

**Lock tempo** freezes automatic following at the current BPM; tap/manual BPM still
work. **Auto** beside Tempo enables/disables automatic candidate acceptance (on by
default). With Auto off, detection continues but only **Use detected BPM** adopts a
candidate. Lock and Fixed mode take precedence.

**Quantize detected BPM** and **BPM increment** are below the performance inspector's
Tempo response control. Quantization defaults to **10 BPM** increments; choose another
positive increment or turn it off. Both automatic and button acceptance use the rounded
candidate. Manual BPM/taps remain exact. The target is quantized; Steady/Responsive
can glide the running clock toward a newly accepted target. Hover the reference status
for raw and accepted candidates. Coarse rounding deliberately pulls toward a tempo grid;
use a smaller increment or disable it for closer human-tempo following.

Steady response is the default. Initial acquisition uses the selected source's
configured pulse division across **40–240 BPM**, rather than restricting it to a
small band around the starting BPM. After acquisition, sustained changes are
confirmed and slewed (Steady: up to 1.5% per update; Responsive: 3.5%). Small jitter
is ignored. Auto-accept includes stable half/double/triplet candidates; use Auto off
or Lock when those are subdivisions rather than a desired tempo change. There is no
per-hit phase tugging.
Hover the reference-source status for the inferred target and reason for holding.
Tap tempo uses the recent five intervals and ignores bounce.

Choose a reliable reference source and set its pulse division correctly. An isolated
stream cannot uniquely establish both tempo and subdivision. Once a reference is
established, doubles, swing and missing hits are tested for stability; a truly new
reference can be accepted explicitly. Use another reference or Lock tempo when that
source itself switches to a cross-rhythm.

**Silence does not stop the band.** Resting parts keep their last usable pattern.
Only Stop/Panic or a real timing/device/visibility interruption stops musical output.

## Independent rhythms

1. Choose **Fixed tempo** and set your quarter-note BPM for the first test. In
   **Follow kit**, keep a straight pulse on the selected tempo source and set its
   pulse division correctly. Auto-accept cannot distinguish every tempo/subdivision alias.
2. Play repeating parts. Each row shows the best repeating cycle, automatic lock,
   fit strength and a proportional onset pattern. Grid phase is estimated across
   recent hits, not permanently fixed by the first strike. The inspector reports
   timing RMS in milliseconds and why a fit is pending. Learning normally takes at least
   three complete cycles plus sustained evidence, so longer meters take longer.
3. In each band lane, choose its **Rhythm** source. Defaults: bass follows kick,
   chords follow snare, pulse follows ride. Once a part locks, that lane replaces
   its style rhythm with the learned pattern, maintaining cycle phase across bars.
   Other lanes can follow other parts—or choose Style to ignore learned timing.
4. Open **Auto** on a part for cycle/grid overrides, **Hold**, and competing cycle
   fits. Hold freezes the current pattern; release it to adapt again. Select a meter
   when timing cannot disambiguate notation. Overrides still require repeated input;
   they constrain learning rather than inventing an unplayed pattern.

**5/4** means a five-quarter-note cycle. **5:4** means five evenly spaced attacks
across four quarter notes. They are different models. **3/4 · 6/8** is an honest
alias: timing alone cannot establish the musical grouping. Uniform quarters show
an N-beat motif rather than asserting a bar signature. Fit percentages describe
repeat/timing agreement, not certainty about musical notation or the global tempo.
The first onset seeds phase, refined from recent hits; this is not a claimed score downbeat.

Auto cycles: 1, 2, 2½, 3, 3½, 4, 4½, 5, 5½, 6, 7, 9 and 11 quarter notes.
Grids: quarters, eighths, sixteenths, triplets, sextuplets, quintuplets, septuplets and
3:2 / 5:2 / 3:4 / 5:4 / 7:4 / 11:4. An explicit cycle/grid combination must divide evenly.
The tracker compares onsets, rests and velocities over up to four completed cycles,
with a 256-hit history per mapped drum piece. Notes mapped to the same piece remain
grouped; separate raw-note/rimshot voices are not inferred yet.

Until a usable lock, the lane shows **Style · awaiting lock** (kick-routed bass:
**Style + live kick**, retaining its immediate response). Silence retains usable
patterns. Sustained contradictory playing can cause fallback; a fill does not
immediately discard the current model. Locked timing ignores style swing, activity
thinning, pulse ratio and chord phrase gating so it preserves your learned pattern.
Harmony, voicing, expression, mute and velocity controls still apply. The style
controls continue to affect fallback/Style lanes. Changes take effect at the next
unscheduled four-beat window; the shared harmony/metronome stays in four.

**16th activity · display only** is now a collapsible legacy meter. Its snapped
cells do not supply learned MIDI timing. `window.drums.snapshot()` exposes raw
beat positions, models, alternatives, lane choices and immutable `scheduledRhythms`
(event beats and provenance). Stop releases output; a new Start/reset clears learned
models. Route, cycle and grid settings persist, but no output re-arms on reload.

### Preview before Pages

Run `npm run serve:drums`, then open `http://localhost:4173/tools/drums/`.
A remote development host can be forwarded with
`ssh -N -L 4173:127.0.0.1:4173 <dev-host>`; localhost supports Web MIDI, unlike
plain remote HTTP. The preview is no-cache: refresh to load the latest files.
Commits tagged `[drums-preview]` land on main without publishing Pages. After
review, dispatch **Tools Pages** to promote the current main revision.

## Performance surface

The main view keeps the beat, current chord, groove and mixer together at 1280×800.
Setup is a bounded dialog with MIDI, Kit, Instruments, Music and Settings tabs; focus
returns to the control you opened it from. The current profile, channel and last-sent
articulation are visible beside each mixer voice, without opening a settings form.

- **Hold / H** retains the current harmony after already-scheduled notes. It does not
  freeze the rhythm or indefinitely latch note-ons. Press again to release the hold.
- **Home / R** cues the home key/tonic on the next unscheduled bar. The target bar is shown.
  Stop clears held/queued cues; these performance intents are never auto-restored.
- **1 / 2 / 3** mute the first three entries in rack order. Muting releases that lane, preserving other lanes'
  held notes and pending attacks. It does not send a global channel panic.
- Mixer **Velocity** scales new note attacks, not the DAW's volume or existing held notes.
- Musical form/profile changes are in Setup; commonly used feel/style controls stay on
  the performance surface. The pulse diagram shows actual base 3:4 / 5:4 / 7:4 positions.

## Harmony and expression

- **Follow phrases & fills**: a bounded harmonic grammar observes recent drum density,
  tom fills and crash accents. Busy bars open the harmony, sparse bars hold it, phrase
  endings lead back to the tonic. Minor dominant cadences use a leading tone.
- **Key movement**: a fill before a phrase boundary can visit the relative key or a
  fifth-related key. The following phrase resolves home. Select Home key only to keep
  the tonal center fixed. No arbitrary mid-beat modulations or cloud/AI inference.
- Four-chord cycle and root pedal remain available. Harmony changes are bar-quantized;
  current chord, phrase position and the reason for a change are shown under The band.
- **Setup → Instruments** expands per lane. Choose an instrument, contextual or forced
  articulation, channel, octave, velocity ceiling and note gate. Chords also expose strum
  direction/spacing. Advanced settings are under **Keyswitches & range · MIDI numbers**.
- **Contextual articulation** uses sustained phrase openings, muted syncopation/dense
  passages, and short fill pulses. Unsupported articulations fall back to sustain.
- **Strum** staggers chord notes, with down/up/contextual direction and bounded spacing.
  It emits the musical notes directly; no Kontakt strumming-mode toggle is sent.
- **Hold to next bar** maintains the chord until the next bar/replacement or Stop. It
  is a tool-owned note gate, not an indefinitely latched DAW device or sustain pedal.
  Disable additional Ableton latches/strums when testing to avoid duplicate behavior.

### Plugin presets

Keyswitch **MIDI numbers** are authoritative here; DAW octave labels vary. Both source
maps label MIDI 0 as C−2. Selecting a preset explicitly enables keyswitch transmission
on that lane's own channel. Generic MIDI emits no keyswitches. Your Kontakt snapshot
may have customized mappings: expand Keyswitch map and edit/clear entries as needed.

| Articulation               | DjinnBass 2 | Shreddage Argent |
| -------------------------- | ----------: | ---------------: |
| Sustain / main alternating |           0 |                0 |
| Palm mute                  |          17 |                1 |
| Staccato                   |           — |                2 |
| Dead / choke               |           3 |                7 |
| Harmonic                   |           8 |                9 |
| Sampled power chord        |           — |                3 |
| Sampled muted power chord  |           — |                4 |

- Argent uses the documented **Shreddage 3.5 default TACT latch map**. Sustain velocities
  are capped at **116** to avoid the default rake/pinch-harmonic velocity zones. Selecting
  its sampled power chord sends one root note, not a stacked chord of sampled fifths.
- DjinnBass uses the small common subset above, cross-checked against a community
  DjinnBass II note map. **An official public PDF was not available to verify the entire
  map.** Verify against your installed patch. Other products' thump/string-control notes
  are deliberately not borrowed or guessed.
- Keyswitches precede musical notes by **12ms** by default (0–40ms editable). Latched mode
  sends one short selection pulse; Held mode keeps the switch down until selection
  changes, mute or Stop. Both are tracked
  and released by Stop/Panic. Keyswitches never go to the browser synth.
- Playable MIDI ranges are conservative and editable. Pitches fold by octaves to retain
  their pitch class; switches cannot overlap the playable range. No bank/program changes,
  automatic slides/hammer-ons, pitch bends, or undocumented CCs are sent.

Sources:

- [Official Shreddage 3.5 Argent manual](https://impactsoundworks.com/manuals/Shreddage%203.5%20Argent%20Manual.pdf)
- [DjinnBass II community note map](https://github.com/DigitalInBlue/ReaperNoteNames/blob/main/submissionaudio_djinbass2.txt)
- [Submission Audio octave naming help](https://support.submissionaudio.com/help/i-am-in-reaper-and-can-not-access-c-2-keyswitch)

## Timing and safety

An 8ms foreground timer schedules timestamped MIDI within a 25ms horizon. Intentional
keyswitch lead and strum offsets extend the individual note timestamp. The browser
synth uses a Web Audio clock and small oscillators, not external samples. The kit's
MIDI callback does no layout or network I/O. The interface updates at about 30Hz.

Stop/Panic clears this output connection's scheduled messages and releases only notes
owned by this tool (including keyswitches). There is no global CC All Sound Off, MIDI
thru or sysex. Same-named input/output ports are refused as a feedback precaution.
Limits: 32 owned MIDI notes, 64 preview voices, 128 recent hits, 256 timing samples,
8–9 bars of harmonic context, 128 KiB imported settings. Nothing retries old notes.

Tab hiding, disconnect or a timer stall over 180ms stops output. Follow mode stops
after two silent bars. This is intentionally not a background DAW clock. Inspect
Timing & safety for local processing/timer measurements; these do not measure your
OS MIDI driver, virtual cable, plugin or audio-interface latency.

**Space** starts/stops, **Escape** panics, **1/2/3** mute lanes, **T** taps tempo,
**H/R** hold/return home, **S** opens Setup, and **Z X C V B N M ,** play the pads.
Shortcuts do not steal text editing, normal button activation, or Setup tab navigation.
Escape always releases notes, including when a dialog is open. Browser audio must be
unlocked by a user gesture.

## Development and deployment

`window.drums.snapshot()` is bounded state; `window.drums.dispatch(action,payload)`
uses the same intent path as controls. Actions: connect/input/output/start/demo/stop/
panic/settings/learn/strike/tap/reset/export/import; hold-harmony/cue-home; learn-kit/
learn-kit-next/learn-kit-skip/learn-kit-cancel/cancel-learning; setup/close-setup;
control-input/controls-arm/controls-pick/controls-learn/controls-save/controls-remove/
controls-cancel/prepare-audio/perform. MIDI never invokes this unrestricted dispatch
surface directly; it resolves a saved binding through the finite performance registry.
Snapshots also expose Setup tab/dialog state, the pending mapping draft and musical cues.
`close()` cleans up timers, listeners, MIDI ports and Web Audio. No separate command
server or native helper is needed.

```sh
node --test drums/test/*.test.js
npm ci
CHROMIUM_PATH=/path/to/Chrome-for-Testing node tests/drums-browser.mjs
CHROMIUM_PATH=/path/to/Chrome-for-Testing node tests/drums-controls-browser.mjs
python3 scripts/check-drums.py
bash scripts/publish-tools-site.sh --check
# Explicitly publish only allowlisted SPA assets:
bash scripts/publish-tools-site.sh
```

The Pages workflow uses the same branch publisher as local deployment, on self-hosted
Nix runners, with `gh-pages` as source. No Actions artifact upload, custom CNAME, or
repository visibility change. CLI/extension files, test fixtures and local artifacts
are **not** published. Runtime has no dependencies or build step.
