Skip to content

Instantly share code, notes, and snippets.

@joaoreboucas1
Last active May 31, 2024 19:43
Show Gist options
  • Save joaoreboucas1/f66ef205825c97b5e82d432a514e5534 to your computer and use it in GitHub Desktop.
Save joaoreboucas1/f66ef205825c97b5e82d432a514e5534 to your computer and use it in GitHub Desktop.
Command-line tool in Python that synchronizes a funk beat into a song (must be .wav format)
from math import floor
from pathlib import Path
import sys
import os
import numpy as np
import librosa
import librosa.beat
import soundfile as sf
def synchronize(file_path_1, file_path_2, output_path="mashup.wav", second_iteration=False):
"""
Synchronizes the audio in `file_path_2` to the tempo of `file_path_1` and also does phase alignment. Saves the result to `output_path`.
"""
file_path_1 = Path(file_path_1)
file_path_2 = Path(file_path_2)
print(f"Synchronizing {file_path_2} into {file_path_1}")
# Load the audio file
audio_1, sr_1 = librosa.load(file_path_1)
audio_2, sr_2 = librosa.load(file_path_2)
# Estimate the tempo (BPM) of the audio file
onset_env_1 = librosa.onset.onset_strength(y=audio_1, sr=sr_1)
bpm_1 = librosa.beat.tempo(onset_envelope=onset_env_1, sr=sr_1)[0]
print(f"{file_path_1} has {bpm_1} BPM")
onset_env_2 = librosa.onset.onset_strength(y=audio_2, sr=sr_2)
bpm_2 = librosa.beat.tempo(onset_envelope=onset_env_2, sr=sr_2)[0]
print(f"{file_path_2} has {bpm_2} BPM")
stretch_factor = bpm_1/bpm_2
stretched_audio_2 = librosa.effects.time_stretch(audio_2, rate=stretch_factor)
onset_env_2 = librosa.onset.onset_strength(y=stretched_audio_2, sr=sr_2)
beat_index_1 = np.argmax(onset_env_1)
beat_index_2 = np.argmax(onset_env_2)
time_shift_samples = beat_index_1 - beat_index_2
aligned_audio_2 = np.roll(stretched_audio_2, -time_shift_samples)
if len(audio_1) > len(aligned_audio_2):
mashup = 0.5 * audio_1[:len(aligned_audio_2)] + 0.5 * aligned_audio_2
else:
mashup = 0.5 * audio_1 + 0.5 * aligned_audio_2[:len(audio_1)]
if not second_iteration:
sf.write("temp.wav", aligned_audio_2, sr_2)
synchronize(file_path_1, "temp.wav", output_path, second_iteration=True)
else:
os.remove("temp.wav")
print(f"Saving mashup to {output_path}")
sf.write(output_path, mashup, sr_1)
if len(sys.argv) != 4:
print(f"Usage: python {sys.argv[0]} TARGET_FILE BEAT_FILE OUTPUT_FILE")
exit(1)
song = sys.argv[1]
beat = sys.argv[2]
output = sys.argv[3]
synchronize(song, beat, output)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment