APNext H3 Sound Events (bass hits / drops / stops)
APNext H3 Sound Events
- audio
- audio
- events
- events_json
- summary
- count
If you've ever made a music video in ComfyUI, you know the problem: your prompt says "cut on the beat," and the model nods along while the picture does whatever it wants. This node is the fix - it hands the writer a list of where the hits actually are, so the video gets staged on the music instead of near it.
H3SoundEvents finds and labels every moment worth cutting on - with the second each one lands. It lives in the dagthomas/comfyui_dagthomas pack (the "SDXL Auto Prompter" family, which has grown far beyond SDXL into a full MiniMax-H3 video pipeline). Wire it between Load Audio and the APNext H3 Music Video Writer, connect its events output to the writer's sound_events socket, and every scene's brief then carries only the hits inside that clip, timed from the clip's own start - [+2.10s] BASS HIT (heavy) - plus a directive to land a cut, a camera move, or a light change on them. That's something a director can stage. [1:47.3] is not.
How it works
The detectors are ported from graphgen's AudioEngine, converted from real-time Web Audio into a whole-song torch pass - pure torch, no librosa, and it takes about half a second to chew through a 200-second song.
- BASS HIT - kicks and low hits, found via band-limited spectral flux (positive per-bin rises over 20–250 Hz) against an adaptive median floor. A kick lifts every low bin at once; a bassline note change only moves a few, which is why flux beats a plain level threshold on a compressed master.
- IMPACT - slams and crashes, via time-domain RMS rise. A body hit is a broadband transient that the FFT smears out, so it needs the waveform, not the spectrum.
- DROP / STOP - signed loudness novelty: mean level of the 1.5 s after a point minus the 1.5 s before it. Strongly positive is the beat arriving, strongly negative is the music cutting out.
- BUILD - a sustained loudness ramp that lands on a drop.
- SECTION - verse/chorus turns from the same novelty over a 4 s window.
Running offline helps twice over: the median window is centered on each frame instead of trailing it, and every threshold is relative to the whole track, so the first bar is scored as accurately as the last.
The inputs that matter
Three widgets cover 95% of cases:
sensitivity- 2.0 is a hair-trigger (every sixteenth), 0.5 is strict (only the big ones). Raise it for a sparse, quiet track; lower it for a wall of sound where everything clears the floor.min_gap_seconds- refractory gap between hits of the same kind. The 0.18 s default keeps a kick and its trailing snare apart at most tempos; raise it to thin a busy track down to the downbeats.max_events- cap on the whole list (default 120) so a four-minute track can't bury the prompt. Drops, stops, sections and builds are always kept; only the hit stream gets thinned.
There's also a per-detector toggle for each kind; accents (hats, snares, cymbals) is off by default because on most tracks it's hundreds of events that crowd everything else out. min_strength / max_strength form a strength band (0–1, 1 = loudest hit in the track) so 0.35–0.45 keeps only the hits around 0.4. Hand edits go in rejected (times you struck out) and edits (JSON from the preview editor). time_offset_ms nudges everything globally if the whole list reads early or late.
The audio output passes the song through unchanged - wire the writer from there. events is the readable table, events_json the same data for scripting, summary gives you one line with measured BPM and character, and count tells you how many events survived.
Install
comfyui_dagthomas installs like any custom node - ComfyUI Manager → search "comfyui_dagthomas" → Install, or:
cd ComfyUI/custom_nodes
git clone https://github.com/dagthomas/comfyui_dagthomas
cd comfyui_dagthomas
pip install -r requirements.txt
Then restart ComfyUI. The requirements are light (the OpenAI/Anthropic/Gemini SDKs, httpx, huggingface_hub, chardet) - nothing here pulls in librosa or a heavy audio stack, and no models are downloaded for this node.
Troubleshooting
The 🎚 Preview events button on the node is worth actually using: it opens a full-screen editor with the waveform and every event as a draggable block (wind-up → landing → settle). That's how you tune the detector or strike out events that don't matter. Where people get burned: leaving accents on (it floods the list), and feeding the node an instrumental when the vocals carry the cuts - the detector only knows what's in the audio you give it.
Inputs (15)
| Name | Type | Default | Description |
|---|---|---|---|
| audio | AUDIO | The song (Load Audio). Passed through unchanged on the audio output. | |
| sensitivity | FLOAT | 1.000.25–2 | How easily an event triggers. Every detector scores a peak against the median of the surrounding audio, and this divides that factor: 2.0 is a hair-trigger (every sixteenth), 0.5 is strict (only the big ones). Raise it for a sparse, quiet track; lower it for a wall of sound where everything clears the floor. |
| min_gap_seconds | FLOAT | 0.180.05–2 | Refractory gap: the shortest time between two hits of the same kind. 0.18 s keeps kicks and their trailing snare apart at most tempos. Raise it to thin a busy track down to the downbeats. |
| max_events | INT | 1204–2000 | Cap on the whole list, so a 4-minute track cannot bury the prompt. Drops, stops, sections and builds are always kept; only the hit stream is thinned, strongest first. |
| min_strengthopt | FLOAT | 0.000–1 | Drop hits weaker than this (0-1, 1 = the loudest hit in this track). With max_strength it is a band: 0.35-0.45 keeps only the hits around 0.4. Structural events (drops, stops, sections, builds) are never dropped. |
| bass_hitsopt | BOOLEAN | true | Kicks and low hits: 20-250 Hz spectral flux over an adaptive floor. |
| impactsopt | BOOLEAN | true | Slams, crashes, body hits: broadband RMS rise on the waveform. |
| drops_and_stopsopt | BOOLEAN | true | The beat arriving (DROP) or cutting out (STOP), from signed loudness novelty. |
| buildsopt | BOOLEAN | true | Risers: a sustained loudness ramp that lands on a drop. |
| sectionsopt | BOOLEAN | true | Verse/chorus turns, from the same novelty over a 4 s window. |
| accentsopt | BOOLEAN | false | Hats, snares and cymbals (2-12 kHz flux). Off by default: on most tracks this alone is hundreds of events and it crowds out the rest. |
| rejectedopt | STRING | Hits you have struck out on the preview timeline (click a tick in 🎚 Preview events), as seconds separated by spaces. Anything within 50 ms of a listed time is dropped from the output. Clear to keep everything. | |
| max_strengthopt | FLOAT | 1.000–1 | Drop hits STRONGER than this (0-1). Together with min_strength it is a band: 0.35-0.45 targets the hits around 0.4 and leaves both the soft kicks and the big slams out. 1.0 = no ceiling. Structural events are never dropped. |
| editsopt | STRING | Hand edits from the 🎚 editor, as JSON: events moved, stretched (wind-up / settle handles), retyped, re-weighted, and events you added. Each edit names the time the event was DETECTED at, so it survives a re-detection with the same settings. Clear to go back to the detector's list. | |
| time_offset_msopt | INT | 0-250–250 | Global nudge for every event, in milliseconds - positive = later. Events are already snapped onto the waveform's own peaks, so 0 is where the sound is; use this if the whole list still reads early or late against the editor's waveform (check with the playhead), or to pre-empt a render that lands its moves late. |
Outputs (5)
| Name | Type | Description |
|---|---|---|
| audio | AUDIO | The same audio, passed through - wire the writer from here. |
| events | STRING | The readable table, one event per line. Wire into the Music Video Writer's `sound_events` socket; it slices the events per clip automatically. |
| events_json | STRING | The same events as JSON, for other nodes or your own scripting. |
| summary | STRING | One line: how many of each kind, plus the measured BPM and character. |
| count | INT | How many events were found. |