Slurm at Home

2026-04-19 | [misc] [music]

As a child who spent way too much time listening to sped up cassette tapes, it is always interesting how very simple effects can change how music sounds. Apparently, I'm not the only one, because on the internet, these sorts of things can crystalize into entire "genres":

And one kind that caught my attention this weekend:

So in the old days of CDs and MP3s, the physical players of these would not only let you skip to the next song, but you could usually fast forward or rewind the audio itself. Unlike a tape, this didn't speed up the audio, instead it would play at normal speed, while the player would "skip" a few 10s of milliseconds as long as you held down the button.

Somehow, somebody thought of lining up these "skips" with the beat of the song, creating something that seems sped up but also not really. They also pitched down the initial audio...

...and the result is now something the internet has named "Slurmcore". No relation to Futurama, at least I don't think so.

There seemed to be some confusion about how this effect was achieved, but someone found out how to do it in Ableton Live using a built-in feature. But if you listen closely, you can tell what's going on; it's basically this:

diagram of slurmcore

If you don't have or know how to use Abelton Live (which I don't), how can I do this?

part 1 - finding the BPM

Looking at the image above, it doesn't look that complicated, but getting the beat exactly right isn't trivial. Since I don't have any better idea, I'm going to have to one-shot it using a fixed rate beat at the beginning of the song. So you need to know the BPM of the song exactly. If a song is between 123 and 124 beats per minute, you will be able to tell it is losing sync within 10 or so seconds. Those "tap for BPM" tools online that report in whole numbers aren't going to cut it; you need to know it is exactly 123.43 BPM. To that end, I make my own version.

It is only then that you can realize the next problem: a lot of music does not have a long-term stable BPM! Unless preformed or remixed on a computer, a human drummer has no real reason to make sure beat 200 is exactly in phase and lines up at 97221 miliseconds after beat 1 for a 123.43 BPM song.

That's probably why we haven't seen these techniques until now - the commercial software has evolved enough to have features that track the beat in the song. So my versions only work on songs with very stable BPM.

part 2 - python

Moving and selecting portions of lists is easy in python, but the first version out of the AI tool worked really slowly. That version worked by coming up with the timestamps for all the sections of audio, and passing that list to ffmpeg which would do the editing.

In my eye, a much more sensible version would convert to an uncompressed "raw" format first, do the swaps and cutting in python, and then export that to ffmpeg to be re-rendered into a mp3 or whatever.

I do feel that you should be able to do this with the little known and cryptic Nyquist prompt in Audacity, but neither I or ChatGPT know enough about it to get it correct.

slurm

Where BPM

import subprocess

## ********** User Inputs **********
BPM = 124.15
SCALING = 2 #multiply BPM period by this to get the interval on which to clip the audio
KEEP = 0.66 #proportion of audio to keep. 1 is all; 0.5 is 50%
PHASE = 0.66 #range 0 to 1. Within Keep, value of 0 keeps the audio at the beginning of the clip, and 1 keeps the audio at the end of the clip. Values in between select the middle of the clip.
## ******** end User Inputs ********


FRAME_JUMP = 1 / (BPM * SCALING / 60)

input_file = "input.mp3"
output_file = "slurm.mp3"

# Step 1: Decode MP3 -> raw PCM (16-bit signed, stereo, 44.1kHz)
pcm = subprocess.check_output(
    [
        "ffmpeg",
        "-i",
        input_file,
        "-f",
        "s16le",  # raw 16-bit PCM
        "-acodec",
        "pcm_s16le",
        "-ac",
        "2",  # stereo
        "-ar",
        "44100",  # sample rate
        "-",
    ]
)

sample_rate = 44100
channels = 2
bytes_per_sample = 2  # s16le = 2 bytes

frame_size = channels * bytes_per_sample  # bytes per frame


# Convert ms to frames
def s_to_frames(t):
    return int(sample_rate * t)


sframe_jump = s_to_frames(FRAME_JUMP)
sframe_width = int(sframe_jump*KEEP)
sframe_remainder = sframe_jump - sframe_width
sframe_offset = int(sframe_remainder*PHASE)

total_frames = len(pcm) // frame_size

# Step 2: Slice and rebuild buffer
output_pcm = bytearray()

pos = 0
while pos < total_frames:
    start_byte = max((pos + sframe_offset) * frame_size, 0)
    end_frame = min(pos + sframe_width + sframe_offset, total_frames)
    end_byte = end_frame * frame_size

    output_pcm.extend(pcm[start_byte:end_byte])

    pos += sframe_jump

# Step 3: Encode back to MP3
proc = subprocess.Popen(
    [
        "ffmpeg",
        "-y",
        "-f",
        "s16le",
        "-ac",
        str(channels),
        "-ar",
        str(sample_rate),
        "-i",
        "-",
        output_file,
    ],
    stdin=subprocess.PIPE,
)

proc.communicate(output_pcm)

Cut in half

Keeps discards every other beat.

import subprocess

## ********** User Inputs **********
BPM = 122 
SCALING = 1 #range 1 to 10
PHASE = 1.70 #offset the interval. a phase of 1 offsets by 1 beat. 
## ******** end User Inputs ********


FRAME_JUMP = 1 / (BPM * SCALING / 60)

input_file = "input.mp3"
output_file = "cut_half.mp3"

# Step 1: Decode MP3 -> raw PCM (16-bit signed, stereo, 44.1kHz)
pcm = subprocess.check_output(
    [
        "ffmpeg",
        "-i",
        input_file,
        "-f",
        "s16le",  # raw 16-bit PCM
        "-acodec",
        "pcm_s16le",
        "-ac",
        "2",  # stereo
        "-ar",
        "44100",  # sample rate
        "-",
    ]
)

sample_rate = 44100
channels = 2
bytes_per_sample = 2  # s16le = 2 bytes

frame_size = channels * bytes_per_sample  # bytes per frame


# Convert ms to frames
def s_to_frames(t):
    return int(sample_rate * t)


sframe_jump = s_to_frames(FRAME_JUMP)
sframe_offset = int(sframe_jump*PHASE)

total_frames = len(pcm) // frame_size

# Step 2: Slice and rebuild buffer
output_pcm = bytearray()

pos = 0


while pos < total_frames:
    
    part1 = max((pos + sframe_offset) * frame_size, 0)
    part2 = min(pos + sframe_offset + sframe_jump, total_frames) * frame_size

    output_pcm.extend(pcm[part1:part2])

    pos += sframe_jump*2

# Step 3: Encode back to MP3
proc = subprocess.Popen(
    [
        "ffmpeg",
        "-y",
        "-f",
        "s16le",
        "-ac",
        str(channels),
        "-ar",
        str(sample_rate),
        "-i",
        "-",
        output_file,
    ],
    stdin=subprocess.PIPE,
)

proc.communicate(output_pcm)

swap beat 2 and 4

import subprocess
import random

## ********** User Inputs **********
BPM = 124
SCALING = 2 
PHASE = 0.7 
## ******** end User Inputs ********


FRAME_JUMP = 1 / (BPM * SCALING / 60)

input_file = "input.mp3"
output_file = "beat24.mp3"

# Step 1: Decode MP3 -> raw PCM (16-bit signed, stereo, 44.1kHz)
pcm = subprocess.check_output(
    [
        "ffmpeg",
        "-i",
        input_file,
        "-f",
        "s16le",  # raw 16-bit PCM
        "-acodec",
        "pcm_s16le",
        "-ac",
        "2",  # stereo
        "-ar",
        "44100",  # sample rate
        "-",
    ]
)

sample_rate = 44100
channels = 2
bytes_per_sample = 2  # s16le = 2 bytes

frame_size = channels * bytes_per_sample  # bytes per frame


# Convert ms to frames
def s_to_frames(t):
    return int(sample_rate * t)


sframe_jump = s_to_frames(FRAME_JUMP)
sframe_offset = int(sframe_jump*PHASE)

total_frames = len(pcm) // frame_size

# Step 2: Slice and rebuild buffer
output_pcm = bytearray()

pos = 0
while pos < total_frames:
    
    part1 = max((pos + sframe_offset) * frame_size, 0)
    part2 = min(pos + sframe_offset + sframe_jump, total_frames) * frame_size
    part3 = min(pos + sframe_offset + sframe_jump*2, total_frames) * frame_size
    part4 = min(pos + sframe_offset + sframe_jump*3, total_frames) * frame_size
    part5 = min(pos + sframe_offset + sframe_jump*4, total_frames) * frame_size

    # #click for aligning
    # output_pcm.extend([random.randint(0, 96) for _ in range(128*frame_size)])
    # output_pcm.extend(pcm[part1+128*frame_size:part5])


    output_pcm.extend(pcm[part1:part2])
    output_pcm.extend(pcm[part4:part5])
    output_pcm.extend(pcm[part3:part4])
    output_pcm.extend(pcm[part2:part3])
    

    pos += sframe_jump*4

# Step 3: Encode back to MP3
proc = subprocess.Popen(
    [
        "ffmpeg",
        "-y",
        "-f",
        "s16le",
        "-ac",
        str(channels),
        "-ar",
        str(sample_rate),
        "-i",
        "-",
        output_file,
    ],
    stdin=subprocess.PIPE,
)

proc.communicate(output_pcm)
comments | patronage | Alnwlsn 2026