Create a Drumless Backing Track

Want to practise along to a song without the original drums getting in the way?

DrumScript can create a drumless backing track from any audio file. It uses Demucs to separate the song into four stems (drums, bass, vocals, other), then re-mixes everything except the drums into a single file.

The result is a clean play-along track with bass, vocals, and instruments intact — and no drums.

Under the hood, ds.extract_stems(drumless=True) calls Demucs for source separation, then uses numpy-based element-wise summation to mix the non-drum stems back together. WAV output is ffmpeg-free; MP3 output requires ffmpeg.

(Note: We are using a copyright-free synthetic track for this demonstration.)


from IPython.display import Audio, display

import drumscript as ds
# 1. Load audio
audio_file_path = "audio/test_track_1.wav"
audio_file = ds.load_audio(audio_file_path)
print("Audio loaded!")
display(Audio(audio_file_path))
Audio loaded!
# 2. Create a drumless backing track
# This isolates all stems except the drums and mixes them together.
backing_track = ds.extract_stems(audio_path=audio_file_path, output_dir="backing_track/", output_format="wav", drumless=True)
Starting Demucs separation for: test_track_1.wav...
---------------------------------------------------------------------------
KeyboardInterrupt                         Traceback (most recent call last)
Cell In[3], line 3
      1 # 2. Create a drumless backing track
      2 # This isolates all stems except the drums and mixes them together.
----> 3 backing_track = ds.extract_stems(audio_path=audio_file_path, output_dir="backing_track/", output_format="wav", drumless=True)

File ~/work/DrumScript/DrumScript/drumscript/__init__.py:90, in extract_stems(audio_path, output_dir, output_format, drumless, mute, all_stems, full)
     83 output_dir.mkdir(parents=True, exist_ok=True)
     85 # result_path = extract_drum_stem(audio_path, output_dir=str(output_dir))
     86 
     87 # results = separate_audio(
     88 #   audio_path=audio_path, output_format=output_format, drumless=drumless, mute=mute, all_stems=all_stems, output_dir=str(output_dir)
---> 90 results = separate_audio(
     91     audio_path=audio_path,
     92     output_format=output_format,
     93     drumless=drumless,
     94     mute=mute,
     95     all_stems=all_stems,
     96     output_dir=str(output_dir),
     97 )
     99 # drum_path = results.get("drums") or results.get("drums_stem")
    100 
    101 # if full:
    102 #   return {"status": "success", "drum_stem_path": result_path, "original_file": audio_path,
    103 # "output_directory": str(output_dir)}
    105 drum_path = results.get("drums") or results.get("drums_stem")

File ~/work/DrumScript/DrumScript/drumscript/audio_processor/stem_splitter.py:194, in separate_audio(audio_path, output_format, drumless, mute, all_stems, output_dir)
    192 command = ["demucs", "-o", str(temp_demucs_dir), "-n", DEMUCS_MODEL, str(input_path)]
    193 try:
--> 194     subprocess.run(command, check=True, capture_output=True, text=True)
    195 except subprocess.CalledProcessError as e:
    196     shutil.rmtree(temp_demucs_dir, ignore_errors=True)

File /opt/hostedtoolcache/Python/3.12.13/x64/lib/python3.12/subprocess.py:550, in run(input, capture_output, timeout, check, *popenargs, **kwargs)
    548 with Popen(*popenargs, **kwargs) as process:
    549     try:
--> 550         stdout, stderr = process.communicate(input, timeout=timeout)
    551     except TimeoutExpired as exc:
    552         process.kill()

File /opt/hostedtoolcache/Python/3.12.13/x64/lib/python3.12/subprocess.py:1209, in Popen.communicate(self, input, timeout)
   1206     endtime = None
   1208 try:
-> 1209     stdout, stderr = self._communicate(input, endtime, timeout)
   1210 except KeyboardInterrupt:
   1211     # https://bugs.python.org/issue25942
   1212     # See the detailed comment in .wait().
   1213     if timeout is not None:

File /opt/hostedtoolcache/Python/3.12.13/x64/lib/python3.12/subprocess.py:2115, in Popen._communicate(self, input, endtime, orig_timeout)
   2108     self._check_timeout(endtime, orig_timeout,
   2109                         stdout, stderr,
   2110                         skip_check_and_raise=True)
   2111     raise RuntimeError(  # Impossible :)
   2112         '_check_timeout(..., skip_check_and_raise=True) '
   2113         'failed to raise TimeoutExpired.')
-> 2115 ready = selector.select(timeout)
   2116 self._check_timeout(endtime, orig_timeout, stdout, stderr)
   2118 # XXX Rewrite these to use non-blocking I/O on the file
   2119 # objects; they are no longer using C stdio!

File /opt/hostedtoolcache/Python/3.12.13/x64/lib/python3.12/selectors.py:415, in _PollLikeSelector.select(self, timeout)
    413 ready = []
    414 try:
--> 415     fd_event_list = self._selector.poll(timeout)
    416 except InterruptedError:
    417     return ready

KeyboardInterrupt: 
# 3. Playback!
# Workaround: extract_stems currently returns the drum path
# even with drumless=True. Manually find the backing track.
import os

backing_dir = os.path.join("backing_track", "test_track_1")
backing_files = [f for f in os.listdir(backing_dir) if "no_drums" in f]

if backing_files:
    backing_path = os.path.join(backing_dir, backing_files[0])
    print("Created backing track:", backing_path)
    display(Audio(backing_path))
else:
    print("No backing track found — check the output directory.")
Created backing track: backing_track/test_track_1/test_track_1_no_drums.wav