GithubHelp home page GithubHelp logo

qq-me / beat-manipulator Goto Github PK

View Code? Open in Web Editor NEW
8.0 1.0 2.0 2.09 MB

beat swapping powered by AI

License: MIT License

Jupyter Notebook 10.73% Python 89.27%
python music autoremix beat-edits beatswap remix beat-swap beatmap

beat-manipulator's Introduction

stunlocked's beat manipulator

Advanced beat swapping powered by madmom.

Installation

For most people I recommend using Hugging Face or Google Colab. However if you run it locally, you will have access to more advanced features that I haven't added to Hugging Face yet, like using samples, mixing multiple songs, presets.

First I recommend creating a new environment to avoid dependency issues. With conda, you can do that by running conda create --name beat_manipulator.

Then run those commands depending on what python you have:

conda 3.8, 3.9:

conda install pip cython mido numpy scipy pysoundfile librosa ffmpeg-python pytest pyaudio pyfftw

pip install madmom pedalboard

conda 3.10

conda install pip cython mido numpy scipy pysoundfile librosa ffmpeg-python pytest pyaudio pyfftw

pip install pedalboard

pip install git+https://github.com/CPJKU/madmom

pip 3.8, 3.9

pip install numpy cython soundfile ffmpeg-python pedalboard librosa

pip install madmom

pip 3.10

pip install numpy cython soundfile ffmpeg-python pedalboard librosa

pip install git+https://github.com/CPJKU/madmom

After installing all necessary libraries, to download beat manipulator, download and extract this repo using green "Code" button > Download ZIP, or run git clone https://github.com/stunlocked1/beat_manipulator. You can now open examples.py, jupiter.ipynb, or app.py for gradio interface.

Usage

First, import beat_manipulator and load a song

import beat_manipulator as bm
your_song = bm.song(audio = 'path or numpy array')

It accepts absolute or relative path to audio file, or you can directly load audio array into it. If you are loading audio array directly, make sure to also add sr=sample_rate argument. Array should be in -1 to 1 format, which is how most libraries load audio.

Now, generate the beatmap:

your_song.beatmap_generate()

Beatmap is generated using madmom library. When you generate it for the first time, it might take up to a minute. However all beatmaps are saved to beat_manipulator/beatmaps, so when you load the same file for the second time, it will be instant.

You can access beatmap in your_song.beatmap variable. It is a list of values that represent position of each beat in samples.

After generating the beatmap, you can do a bunch of stuff.

slicing

Song object supports slicing.

  • your_song[5] will return audio of the 5th beat, indexing starts from 1.
  • your_song[4:8.5] returns audio starting from 4th beat, ending halfway between 8th and 9th beat. your_song[0:1] is equivalent to your_song[1]
  • your_song['your_pattern'] returns a beatswapped audio using the pattern you provided. Beatswapping with patterns is the main feature if this app and you can do a whole bunch of stuff with them. There is a section below that explains how to write those patterns.

beatswapping

Another way to beatswap is:

your_song.beatswap(pattern = '1, 3, 2, 4', scale = 1, shift = 0, length = None)

This one doesn't return anything, instead it modifies the song in place.

You can also beatwap and write audio in one line:

bm.beatswap(song = 'path or numpy array', pattern = '1, 3, 2, 4', scale = 1, shift = 0, output = '')

scale

scale = 0.5 will insert a new beat position between every existing beat position in the beatmap. That allows you to make patterns on smaller intervals.

scale = 2, on the other hand, will merge every two beat positions in the beatmap. Useful, for example, when beat map detection puts sees BPM as two times faster than it actually is, and puts beats in between every actual beat.

To scale the beatmap, you can use your_song.beatmap_scale(0.5), or specify scale directly in your_song.beatswap(..., scale = float)

shift

Shifts the beatmap, in beats. For example, if you want to remove 4th beat every four beats, you can do it by writing 1, 2, 3, 4!. However sometimes it doesn't properly detect which beat is first, and for example remove 2nd beat every 4 beats instead. In that case, if you want 4th beat, use shift = 2. Also sometimes beats are detected right in between actual beats, so shift = 0.5 or -0.5 will fix it.

To shift the beatmap, you can use your_song.beatmap_shift(0.5), or specify shift directly in your_song.beatswap(..., shift = float)

When you specify shift in a beatswap function, it applies before scale for consistency.

saving scale and shift

If you run your_song.beatmap_save_settings(scale: float, shift: float), it will save a file in beat_manipulator/beatmaps with your scale and shift. That way, next time you load that song, it will automatically apply those scale and shift values.

writing audio

To write audio, use my_song.write(output = ''). If output is empty string, this will write the song next to your .py file, using the original filename.

pattern syntax

The pattern syntax is quite powerful and you can do a whole bunch of stuff with it. Basic syntax is - 1, 3, 2, 4 means every 4 beats, swap 2nd and 3rd beats, but you can do much more, like applying audio effects, shuffling beats, slicing them, mixing two songs, adding samples, sidechain.

You can use spaces freely in patterns for formatting. Most other symbols have some use though. Here is how to write patterns:

beats

  • 1 - 1st beat;
  • 1>0.5 - first half of first beat
  • `1<0.5 - second half of first beat
  • 0:0.5 - range of beats, this also means first half of first beat, but with this you can do complex stuff like 1.25:1.5. However this one is a bit more confusing because indexing starts from 0, so 1:2 is second beat, not first.
  • Also sometimes it is more convenient to use smaller scale, like 0.5 or 0.25, instead of slicing beats.

basic patterns

  • 1, 3, 2, 4 means 3rd and 2nds beats will be swapped every 4 beats. Happens every 4 beats because 4 is the biggest number in the pattern.
  • 1, 2, 3, 4! means every 4 beats, it will remove the 4th beat. ! allows you to skip a beat but it still counts for pattern size.
  • Specifying pattern length: pattern = '1,2,3', length = 4 is another way to remove 4th beat every 4 beats.
  • 1,4 skips 2nd and 3rd beat
  • 1; 2 plays 1st and 2nd beat same time. They will automatically be normalzied to avoid clipping. Length of the first beat will be preserved.

joining operators

, and ; are beat joining operators, that join beats together. Here are all available joiners:

  • , puts the beat next to previous beat
  • ; puts the beat on top of previous beat. Normalizes the volume to avoid clipping. If previous beat is shorter, your beat will be shortened to match it.
  • ^ multiplies previous beat by your beat. This can be used for fake sidechain.
  • $ adds the beat on top of previous beat + sidechains previous beat by your beat.

effects

beats can be followed by effects. For example 1s0.75 means take first beat and play it at 0.75x speed. Here are all available effects:

  • s - speed. 1s2 means first beat will be played at 2x speed.
  • r - reverse. 1r means first beat will have reversed audio.
  • v - volume. 1v0.5 means 1st beat will have 50% volume
  • d - downsample, or 8-bit sound. 1d10 will downsample the first beat so that it sounds 8-bit. Good values start above 7.
  • b - bitcrush. 1b4 will bitcrush it.
  • g - gradient, sounds like highpass. 1g1 is the recommended value
  • c - channel. If not followed by number, swaps channels. If followed by 0, plays only left channel. If 1, only right channel
  • mixing effects - 1s2rd8 - take first beat, play at 2x speed, reversed, and downsampled. You can also define your own effects. Check BM_EFFECTS dictionary from beat_manipulator/effects.py, this is where it reads effects from. You can add new stuff to that dictionary and use your own effects this way, or specify your own dictionary when using your_song.beatswap(..., effects: dict) with your effects argument. By default that argument points to BM_EFFECTS dictionary.

math

mathematical expressions with +, -, *, /, and ** are supported. For example, if you write 1/3 anywhere in the pattern, to slice beats or as effect value, it will be replaced by 0.3333...

using samples

To use samples, provide them in samples argument to your_song.beatswap(..., samples: dict). The dictionary should look like this:

{
  'sample name' : 'path to your sample or numpy array of your sample or bm.song object', 
  'sample name 2' : 'path or audio 2', 
  ...
}

It supports both loading audio files from a path, and directly loading arrays.

Then in pattern, you can use quotes (', ", or `) to access samples. For example: 1; "sample_name" will put that sample on top of 1st beat. Samples are treated just like beats, you can apply effects to them, use any joining operators.

You can also slice samples: "sample_name">0.5 means first half of the sample.

You can use list with samples instead of a dictionary. In that case, you can access them by their index in the list, for example "1" is the 1st sample.

mixing two songs

Add the second song that you want to mix to the samples argument dictionary or list, as described above. It can load path to a file, directly load a numpy array, or a bm.song object.

The difference is, instead of using quotes, for songs you use square brackets: [song_name]

[song_name]4 means fourth beat of that song. So you can do stuff like 1, [song_name]2, which will alternate beats between your two songs.

other stuff

  • i will be replaced by current position, e.g. i, i, i, i+1 is equvalent to 1, 2, 3, 4 + 1, or 1, 2, 3, 5.
  • # will add shuffle all beats with the same number after it. 1#1, 2#2, 3#1, 4#2, 5#1, 6#2, 7#1, 8#2 will shuffle 1st, 3rd, 5th and 7th beats (the are in 1st group), and 2nd, 4th, 6th and 8th beats - from 2nd shuffle group.
  • ! skips that beat. If you want to remove every 4th beat, you can't just do 1, 2, 3, because that would simply play every 3 beats. So to play 3 beats every 4 beats, you can write 1, 2, 3, 4!
  • ? makes that beat not count for pattern size. For example, 1, 2, 3, 8 will normally repeat every 8 beats because 8 is the highest number, but 1, 2, 3, 8? will repeat every 3 beats.
  • @ allows you to take a random beat with the following syntax: @start_stop_step. For example, @1_4_0.5 means it will take a random beat out of 1st, 1.5, 2nd, 2.5, 3rd, 3.5, and 4th. It will take whole beat, so you can also add >0.5 to take only first half.
  • % - for very advanced patterns you can create variables from various metrics. For example, %v will create a variable with average volume of that beat, and all following % will be replaced by that variable until you create a new one. Useful for applying different effects based on different song metrics. All metrics are in beat_manipulator/metrics.py.

special patterns

You can write special commands into the pattern argument instead of actual patterns.

  • reverse - plays all beats in reverse chronological order
  • shuffle - shuffles all beats
  • test - puts different pitched cowbells on each beat, useful for testing beat detection and adjusting it using scale and shift. Each cowbell is 1 beat, highest pitched cowbell is the 1st beat, lowest pitched - 4th.

complex patterns

You should be able to use all of the above operators in any combination, as complex as you want. Very low scales should also be fine, up to 0.001.

creating images

You can create cool images based on beat positions. Each song produces its own unique image. Write:

your_song.image_generate()

image will be saved as a numpy array to your_song.image variable. To export it to a file, use:

your_song.image_write()

The image will by default be resized to 4096x4096. It is also possible to export original image, which usually is too big for most image viewers to handle it. However the cool thing is that you can apply image effects to it, and then turn it back into audio. I will soon add info on how to do that.

quick functions

bm.beatswap(song = 'path or numpy array', pattern = '1,3,2,4', scale=1, shift=0, output='')

allows you to beatswap and write a song loaded from path or numpy array in one line. Returns path to the exported beatswapped song file.

bm.image(song = 'path or numpy array', max_size = 4096, scale=1, shift=0, output='')

creates an image and writes it in one line, returns path to exported image.

bm.osu.generate(song='path or numpy array', difficulties = [0.2, 0.1, 0.05, 0.025, 0.01, 0.0075, 0.005, 0.0025, 0.0001])

generates an osu! beatmap (uses madmom beat processor and peak detection). Writes an .osz file that you can install by opening it with osu! and returns path to it.

presets

there are some patterns in beat_manipulator/presets.yaml file. Those are supposed to be used on normalized beat maps, where kick + snare is two beats, so make sure to adjust beatmaps using scale and shift. To use one of the presets from that file, write:

bm.presets.use(song = song, preset = 'preset name', scale = 1, shift = 0)

Contributing

I will clean up the code and then it will be possible to actually understand what is going on. That will happen... At some point

beat-manipulator's People

Contributors

inikishev avatar penify-dev[bot] avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

Forkers

djwugee allanlaal

beat-manipulator's Issues

Outdated `numpy` dependency

A module that was compiled using NumPy 1.x cannot be run in
NumPy 2.0.0 as it may crash. To support both 1.x and 2.x
versions of NumPy, modules must be compiled with NumPy 2.0.
Some module may need to rebuild instead e.g. with 'pybind11>=2.12'.

If you are a user of the module, the easiest solution will be to
downgrade to 'numpy<2' or try to upgrade the affected module.
We expect that some modules will need time to support NumPy 2.

Traceback (most recent call last):  File "<frozen runpy>", line 198, in _run_module_as_main
  File "<frozen runpy>", line 88, in _run_code
  File "/home/theocgaming/Programs/beat-manipulator/beatman.py", line 43, in <module>
    bm.beatswap(audio=inp, output=output, pattern=pattern, scale = scale, shift = shift)
  File "/home/theocgaming/Programs/beat-manipulator/beat_manipulator/main.py", line 518, in beatswap
    if not isinstance(audio, song): audio = song(audio = audio, sr = sr, log = log)
  File "/home/theocgaming/Programs/beat-manipulator/beat_manipulator/main.py", line 16, in __init__
    self.audio, self.sr = io._load(audio=audio, sr=sr)
  File "/home/theocgaming/Programs/beat-manipulator/beat_manipulator/io.py", line 105, in _load
    if isinstance(audio, str): return(open_audio(path=audio, lib=lib))
  File "/home/theocgaming/Programs/beat-manipulator/beat_manipulator/io.py", line 55, in open_audio
    audio,sr=open_audio(path, i)
  File "/home/theocgaming/Programs/beat-manipulator/beat_manipulator/io.py", line 40, in open_audio
    import madmom
  File "/home/theocgaming/.local/lib/python3.12/site-packages/madmom/__init__.py", line 23, in <module>
    from . import audio, evaluation, features, io, ml, models, processors, utils
  File "/home/theocgaming/.local/lib/python3.12/site-packages/madmom/features/__init__.py", line 285, in <module>
    from . import beats, chords, downbeats, key, notes, onsets, tempo
  File "/home/theocgaming/.local/lib/python3.12/site-packages/madmom/features/downbeats.py", line 18, in <module>
    from .beats_hmm import (BarStateSpace, BarTransitionModel,
  File "/home/theocgaming/.local/lib/python3.12/site-packages/madmom/features/beats_hmm.py", line 19, in <module>
    from madmom.ml.hmm import ObservationModel, TransitionModel
open_audio with madmom: numpy.core.multiarray failed to import (auto-generated because you didn't call 'numpy.import_array()' after cimporting numpy; use '<void>numpy._import_array' to disable if you are certain you don't need it).
Analyzing beats using madmom.BeatDetectionProcessor; beatmap hasn't been generated yet. Generating...

A module that was compiled using NumPy 1.x cannot be run in
NumPy 2.0.0 as it may crash. To support both 1.x and 2.x
versions of NumPy, modules must be compiled with NumPy 2.0.
Some module may need to rebuild instead e.g. with 'pybind11>=2.12'.

If you are a user of the module, the easiest solution will be to
downgrade to 'numpy<2' or try to upgrade the affected module.
We expect that some modules will need time to support NumPy 2.

Traceback (most recent call last):  File "<frozen runpy>", line 198, in _run_module_as_main
  File "<frozen runpy>", line 88, in _run_code
  File "/home/theocgaming/Programs/beat-manipulator/beatman.py", line 43, in <module>
    bm.beatswap(audio=inp, output=output, pattern=pattern, scale = scale, shift = shift)
  File "/home/theocgaming/Programs/beat-manipulator/beat_manipulator/main.py", line 525, in beatswap
    audio.beatswap(pattern = pattern, scale = scale, shift = shift, length = length)
  File "/home/theocgaming/Programs/beat-manipulator/beat_manipulator/main.py", line 163, in beatswap
    if self.beatmap is None: self.beatmap_generate()
  File "/home/theocgaming/Programs/beat-manipulator/beat_manipulator/main.py", line 128, in beatmap_generate
    self.beatmap = beatmap.generate(audio = self.audio, sr = self.sr, lib=lib, caching=caching, filename = self.path, log = self.log, load_settings = load_settings)
  File "/home/theocgaming/Programs/beat-manipulator/beat_manipulator/beatmap.py", line 83, in generate
    import madmom
  File "/home/theocgaming/.local/lib/python3.12/site-packages/madmom/__init__.py", line 23, in <module>
    from . import audio, evaluation, features, io, ml, models, processors, utils
  File "/home/theocgaming/.local/lib/python3.12/site-packages/madmom/features/__init__.py", line 285, in <module>
    from . import beats, chords, downbeats, key, notes, onsets, tempo
  File "/home/theocgaming/.local/lib/python3.12/site-packages/madmom/features/downbeats.py", line 18, in <module>
    from .beats_hmm import (BarStateSpace, BarTransitionModel,
  File "/home/theocgaming/.local/lib/python3.12/site-packages/madmom/features/beats_hmm.py", line 19, in <module>
    from madmom.ml.hmm import ObservationModel, TransitionModel
Traceback (most recent call last):
  File "<frozen runpy>", line 198, in _run_module_as_main
  File "<frozen runpy>", line 88, in _run_code
  File "/home/theocgaming/Programs/beat-manipulator/beatman.py", line 43, in <module>
    bm.beatswap(audio=inp, output=output, pattern=pattern, scale = scale, shift = shift)
  File "/home/theocgaming/Programs/beat-manipulator/beat_manipulator/main.py", line 525, in beatswap
    audio.beatswap(pattern = pattern, scale = scale, shift = shift, length = length)
  File "/home/theocgaming/Programs/beat-manipulator/beat_manipulator/main.py", line 163, in beatswap
    if self.beatmap is None: self.beatmap_generate()
                             ^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/theocgaming/Programs/beat-manipulator/beat_manipulator/main.py", line 128, in beatmap_generate
    self.beatmap = beatmap.generate(audio = self.audio, sr = self.sr, lib=lib, caching=caching, filename = self.path, log = self.log, load_settings = load_settings)
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/theocgaming/Programs/beat-manipulator/beat_manipulator/beatmap.py", line 83, in generate
    import madmom
  File "/home/theocgaming/.local/lib/python3.12/site-packages/madmom/__init__.py", line 23, in <module>
    from . import audio, evaluation, features, io, ml, models, processors, utils
  File "/home/theocgaming/.local/lib/python3.12/site-packages/madmom/features/__init__.py", line 285, in <module>
    from . import beats, chords, downbeats, key, notes, onsets, tempo
  File "/home/theocgaming/.local/lib/python3.12/site-packages/madmom/features/downbeats.py", line 18, in <module>
    from .beats_hmm import (BarStateSpace, BarTransitionModel,
  File "/home/theocgaming/.local/lib/python3.12/site-packages/madmom/features/beats_hmm.py", line 19, in <module>
    from madmom.ml.hmm import ObservationModel, TransitionModel
  File "madmom/ml/hmm.pyx", line 1, in init madmom.ml.hmm
ImportError: numpy.core.multiarray failed to import (auto-generated because you didn't call 'numpy.import_array()' after cimporting numpy; use '<void>numpy._import_array' to disable if you are certain you don't need it).

gotta love the console spam

this is what happens when you use an old version of madmom

python3 -m beatman -i "/home/theocgaming/Music/dubioza kolektiv/3.mp3" -o "output/fukt.mp3" -p "1, 4, 3, 2" -s 1.5
spits out

open_audio with madmom: cannot import name 'MutableSequence' from 'collections' (/usr/lib/python3.12/collections/__init__.py)
Analyzing beats using madmom.BeatDetectionProcessor; beatmap hasn't been generated yet. Generating...
Traceback (most recent call last):
  File "<frozen runpy>", line 198, in _run_module_as_main
  File "<frozen runpy>", line 88, in _run_code
  File "/home/theocgaming/Programs/beat-manipulator/beatman.py", line 43, in <module>
    bm.beatswap(audio=inp, output=output, pattern=pattern, scale = scale, shift = shift)
  File "/home/theocgaming/Programs/beat-manipulator/beat_manipulator/main.py", line 525, in beatswap
    audio.beatswap(pattern = pattern, scale = scale, shift = shift, length = length)
  File "/home/theocgaming/Programs/beat-manipulator/beat_manipulator/main.py", line 163, in beatswap
    if self.beatmap is None: self.beatmap_generate()
                             ^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/theocgaming/Programs/beat-manipulator/beat_manipulator/main.py", line 128, in beatmap_generate
    self.beatmap = beatmap.generate(audio = self.audio, sr = self.sr, lib=lib, caching=caching, filename = self.path, log = self.log, load_settings = load_settings)
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/theocgaming/Programs/beat-manipulator/beat_manipulator/beatmap.py", line 83, in generate
    import madmom
  File "/home/theocgaming/.local/lib/python3.12/site-packages/madmom/__init__.py", line 24, in <module>
    from . import audio, evaluation, features, io, ml, models, processors, utils
  File "/home/theocgaming/.local/lib/python3.12/site-packages/madmom/audio/__init__.py", line 27, in <module>
    from . import comb_filters, filters, signal, spectrogram, stft
  File "madmom/audio/comb_filters.pyx", line 15, in init madmom.audio.comb_filters
  File "/home/theocgaming/.local/lib/python3.12/site-packages/madmom/processors.py", line 23, in <module>
    from collections import MutableSequence
ImportError: cannot import name 'MutableSequence' from 'collections' (/usr/lib/python3.12/collections/__init__.py)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.