D1D-Turbo: 3-step drum one-shot generator (kick, snare, hi-hat)

Live Demo: try it in your browser   GitHub: inference code

D1D-Turbo, the fast version of D1D ("Drum 1D"), generates drum one-shots as raw waveform: 16,384 samples at 32 kHz (0.512 s), mono, class-conditional on kick, snare or hi-hat. It needs only 3 network passes per sample, about 0.35 s on a 6-core CPU.

It is a reflow distillation of the D1D teacher model (50 steps + CFG = 100 passes, not released), with classifier-free guidance baked in. On top of that it has a learned output gate, so it produces exact digital silence before the hit and after the decay with no post-processing.

Inference code (C++ header-only library, C++ command line, Python/ONNX): github.com/sixf0ur/D1D-Turbo. Everything is CPU-only and needs no PyTorch.

file
model.onnx fp32 ONNX (opset 20), for ONNX Runtime and any other ONNX runtime
model.bin the same weights in a simple binary format, for the C++ library d1d.hpp
test_io.bin PyTorch reference outputs: drumgen --test test_io.bin checks a C++ build
samples/ the audio samples below

Samples

These samples were generated with the C++ code (drumgen --seed 2026, 3 steps). They are peak-normalised and have no post-processing.

0 1 2 3
kick
snare
hat

Usage

The full tools are on GitHub.

C++ (header-only, standard library only):

#include "d1d.hpp"

d1d::Model model = d1d::Model::load("model.bin");        // or load(bytes, size)
d1d::GenParams p;
p.seed = 42;                                              // same seed -> same sound
std::vector<float> kick = model.generate(d1d::Drum::Kick, p);   // 16384 floats @ 32 kHz
d1d::save_wav("kick.wav", kick);

Python without any extra code: pip install numpy onnxruntime soundfile, then

import numpy as np, onnxruntime as ort, soundfile as sf
from huggingface_hub import hf_hub_download

sess = ort.InferenceSession(hf_hub_download("sixf0ur/D1D-Turbo", "model.onnx"))
cls = 0                                                   # 0 kick, 1 snare, 2 hat
x = np.random.randn(1, 1, 16384).astype(np.float32)       # noise
for i in range(3):                                        # 3 Euler steps, t = 0, 1/3, 2/3
    t = np.array([i / 3], np.float32)
    x = x + sess.run(None, {"x": x, "t": t, "y": np.array([cls], np.int64)})[0] / 3
sf.write("kick.wav", x[0, 0] / np.abs(x).max() * 0.95, 32000)   # output level is arbitrary: peak-normalise

Steps. 3 steps is the setting chosen by listening. 4 and 8 sound practically identical, and with 1 step the pre-roll gets noisier. CFG is already baked into the weights, so keep the CFG weight at 1.0: one pass per step.

Speed

Measured on an AMD Ryzen 5 5500 (6 cores / 12 threads, AVX2), batch 1:

per network pass per sample (3 passes)
C++ (d1d.hpp), 12 threads 114 ms 0.34 s
C++ (d1d.hpp), 1 thread 481 ms 1.4 s
Python, ONNX Runtime fp32 ~118 ms 0.35 to 0.38 s
teacher, for comparison 11.7 s

Model details

type rectified flow, reflow-distilled, velocity prediction, Euler sampler
network 1D U-Net (same as the teacher) + learned output gate
parameters 4,872,770 (fp32, 19.5 MB); the gate adds 225
output 16,384 samples, mono, 32 kHz (0.512 s); the hit starts at sample 256 (8 ms pre-roll)
classes 0 kick, 1 snare, 2 hat (3 = null, unused: CFG is baked in)
sampler 3 Euler steps, CFG weight 1.0 (3 passes per sample)

Architecture. It is the same network as D1D:

  • Encoder: a stem conv (k=7) feeds 5 resolution levels with channels 32 / 64 / 96 / 128 / 192. Each level has 2 FiLM residual blocks (GroupNorm, SiLU, conv k=3, FiLM scale/shift from the conditioning vector) and a strided conv downsample.
  • Bottleneck: at length 1024 it is residual block, attention, residual block.
  • Decoder: it mirrors the encoder with U-Net skip concatenation and uses nearest-neighbour Γ—2 upsampling followed by a conv.
  • Attention: self-attention with 4 heads runs in the bottleneck and in the first decoder level, both at length 1024, with a gated sin-cos position table.
  • Conditioning: a sinusoidal timestep embedding goes through a 2-layer MLP; a class embedding is added to the result.

Output gate.

  • What it computes: from the final head features h (after GroupNorm and SiLU, 32 channels), the network computes the velocity v = conv7(h) and a gate g = clamp(1 + conv7_gate(h), 0, 1). The predicted sound x1_hat = x_t + (1βˆ’t)Β·v is multiplied by g. The network returns that as a velocity:

    v_out = gΒ·v + (g βˆ’ 1)Β·x_t / max(1 βˆ’ t, 1e-3)
    
  • Why it exists: with plain velocity prediction, digital silence requires v = βˆ’x_t/(1βˆ’t) to about 0.03 % accuracy. In practice that left a βˆ’55 to βˆ’67 dB noise floor that was audible on headphones, and loss tweaks alone plateaued around βˆ’57 dB. With the gate, silence only needs gate ≀ βˆ’1, a margin rather than a precision.

  • Initialisation: the gate conv is zero-initialised, so g = 1 and training started as an exact copy of the ungated model.

  • In ONNX: all of this is inside model.onnx. The sampler is a plain Euler loop.

ONNX interface (model.onnx, opset 20, dynamic batch):

name type shape
x float32 (B, 1, 16384) current noisy waveform
t float32 (B,) time in [0, 1): 0 = noise, 1 = sound
y int64 (B,) class 0..2
v float32 (B, 1, 16384) velocity (output, gate already applied)

Sampler. This is all of the inference logic:

x = np.random.randn(1, 1, 16384).astype(np.float32)   # noise
for i in range(3):                                      # t = 0, 1/3, 2/3
    t = np.array([i / 3], np.float32)
    x = x + run(x, t, cls) / 3
audio = x[0, 0] / np.abs(x).max() * 0.95                # output level is arbitrary: peak-normalise

model.bin uses a trivial binary format, so it is easy to read from any language:

  • the header is char[4] "DDW1", then u32 count;
  • then come count records, each u32 name_len, the name, u32 ndim, u32 dims[ndim] and float32 data;
  • all values are little-endian;
  • names are the PyTorch state_dict keys, plus meta.steps and meta.cfg_w.

Training procedure

1. Pair generation.

  • The teacher (D1D: 50 Euler steps, CFG 2.0) turned 60,000 noise vectors into sounds, classes balanced. This took 5 h on an RTX 3090.
  • The noise of each pair is regenerated from its seed, so only the sounds were stored.
  • 240 pairs (80 per class) are held out for evaluation.

2. Reflow.

  • The student was initialised from the teacher.
  • It was trained with the flow-matching loss on the straight line between each pair: x_t = (1βˆ’t)Β·z0 + tΒ·teacher(z0), target v = teacher(z0) βˆ’ z0, t ~ U(0, 1). Straight paths can be integrated in very few Euler steps. Because the pairs were made with CFG 2.0, the guidance is baked in.
  • Settings: 30,000 steps, batch 32, AdamW lr 1e-4 (500 warm-up steps), EMA 0.9995, about 2.5 h.

3. Gate fine-tune.

  • The output gate was added (zero-init).
  • The model was trained 12,000 more steps (lr 5e-5, EMA 0.999, about 1 h) towards silence-cleaned teacher targets. In these targets the pre-roll is zeroed (fade-in from sample 160 to 224), and the tail is faded to zero once its envelope reaches the teacher's noise floor.
  • The loss had three terms:
    • the velocity MSE;
    • the MSE of the predicted sound x1_hat;
    • a small (0.02) log-energy penalty on x1_hat where the target is exactly 0.

Evaluation

The table uses 64 samples per class from fresh noise that was never used in training. Every row uses the same noise for the same index.

  • Bands: energy share in %.
  • pair-cos: diversity (lower = more varied).
  • decay: time from the envelope peak to βˆ’20 dB.
  • pre-roll / tail: level of samples 0–200 / the last 2,048 samples, relative to the peak. βˆ’180 means digital silence.
  • SNR: waveform SNR against the teacher's output for the same noise.
0–250 250–2k 2k–8k 8k–16k pair-cos decay pre-roll tail SNR
kick, real 95.8 3.7 0.4 0.1 0.713 126 ms βˆ’180 βˆ’180
kick, teacher (100 passes) 97.4 2.5 0.1 0.0 0.595 122 ms βˆ’59 βˆ’57
kick, teacher, 4 steps, no distillation 83.9 10.9 4.6 0.6 0.606 258 ms βˆ’54 βˆ’26 7.3 dB
kick, this model (3 passes) 97.6 2.4 0.1 0.0 0.607 132 ms βˆ’161 βˆ’160 17.1 dB
snare, real 22.9 41.8 29.4 5.8 0.722 86 ms βˆ’180 βˆ’180
snare, teacher 16.5 47.6 33.7 2.3 0.769 74 ms βˆ’66 βˆ’67
snare, teacher, 4 steps, no distillation 10.4 31.6 48.1 9.9 0.662 322 ms βˆ’56 βˆ’34 5.5 dB
snare, this model 16.0 47.3 34.2 2.4 0.854 76 ms βˆ’168 βˆ’169 18.0 dB
hat, real 2.9 3.7 58.2 35.3 0.640 52 ms βˆ’180 βˆ’180
hat, teacher 0.0 0.7 55.7 43.4 0.703 39 ms βˆ’70 βˆ’73
hat, teacher, 4 steps, no distillation 0.7 4.4 59.0 35.9 0.576 468 ms βˆ’57 βˆ’29 3.8 dB
hat, this model 0.0 0.6 55.9 43.4 0.751 44 ms βˆ’173 βˆ’173 18.8 dB

The distilled model matches the teacher's spectrum, attack and decay. Running the undistilled teacher with only 4 steps gives smeared noise tails instead (the "no distillation" rows). Pair-cos is higher than the teacher's, partly because the teacher's random noise floor makes any two of its samples less similar. Applying the same silence cleanup to the ungated student raises its snare pair-cos from 0.78 to 0.84 as well.

Limitations

  • Noise under the sound. The gate only silences the regions that should be silent. While the sound plays, a faint noise floor remains underneath it, masked by the sound and quieter than the teacher's floor in the silent regions.
  • Not the teacher. It is not a bit-exact copy of the teacher: about 17–19 dB SNR against the teacher's output for the same noise. It is the same kind of sound, not the identical waveform.
  • Inherited from the teacher. The weak snare top octave (8–16 kHz 2.4 % vs 5.8 % real), slightly lower diversity than real one-shots, a fixed 0.512 s mono 32 kHz format, and three classes with no further control.
  • Fixed sampler. CFG is baked in. A CFG weight other than 1.0 is not meaningful for this model.

License

CC BY-NC 4.0: free for non-commercial use with attribution (see License.txt). For commercial licensing contact info@sixf0ur.com.

Downloads last month

-

Downloads are not tracked for this model. How to track
Inference Providers NEW
This model isn't deployed by any Inference Provider. πŸ™‹ Ask for provider support