### Setup Development Environment with uv Source: https://drumscript.github.io/DrumScript/development/contributor_guidance.html Create a virtual environment and install dependencies in editable mode using uv. ```bash uv venv source .venv/bin/activate # activate venv uv pip install -e . # install venv in 'editable' mode ``` -------------------------------- ### Provide FFmpeg installation instructions Source: https://drumscript.github.io/DrumScript/_modules/drumscript/utils/ffmpeg_installer.html Checks for FFmpeg and provides OS-specific installation guidance or attempts an automated install on Linux. ```python def install_ffmpeg() -> None: """ Checks for FFmpeg installation and provides OS-specific instructions if not found. For Linux/macOS, it can offer to attempt an installation via package manager (requires sudo). For Windows, it provides manual download instructions. This function is intended to be called by the user or a setup script, not as an automatic silent installer due to permission requirements. """ print("\n--- Checking for FFmpeg installation ---") if is_ffmpeg_installed(): print("FFmpeg is already installed and detected in your system's PATH.") print("You are ready to process all audio file types with DrumScript.") return print("\nFFmpeg is NOT detected in your system's PATH.") print("FFmpeg is required by DrumScript to process MP3 and other non-WAV audio files.") print("WAV files can usually be processed without FFmpeg.") system = platform.system() if system == "Linux": print("\n--- Installation instructions for Linux (Debian/Ubuntu-based) ---") print("1. Update package lists:") print(" sudo apt update") print("2. Install FFmpeg:") print(" sudo apt install ffmpeg") user_choice = input("Attempt to install FFmpeg using 'sudo apt install ffmpeg' now? (y/N): ").lower() if user_choice == "y": try: print("Attempting to run: sudo apt install ffmpeg...") # Use shell=True for sudo commands to work as expected, but be cautious subprocess.run(["sudo", "apt", "install", "-y", "ffmpeg"], check=True) print("\nFFmpeg installation attempt finished. Please re-run this check.") print("You may need to restart your terminal for PATH changes to take effect.") except subprocess.CalledProcessError as e: print(f"Error during automated FFmpeg installation: {e}") print("Please try installing manually using the commands above.") except FileNotFoundError: print("Apt command not found. Are you on a Debian/Ubuntu-based system?") print("Please install FFmpeg manually for your specific Linux distribution.") else: print("Please install FFmpeg manually using the instructions above.") ``` -------------------------------- ### Install development dependencies Source: https://drumscript.github.io/DrumScript/development/contributor_guidance.html Command to install all project dependencies including optional development tools. ```bash uv pip install -e ".[dev]" ``` -------------------------------- ### install_ffmpeg() Source: https://drumscript.github.io/DrumScript/_modules/drumscript/utils/ffmpeg_installer.html Checks for FFmpeg installation and provides OS-specific instructions or automated installation prompts if it is not found. ```APIDOC ## install_ffmpeg() ### Description Checks for FFmpeg installation and provides OS-specific instructions if not found. For Linux/macOS, it can offer to attempt an installation via package manager (requires sudo). For Windows, it provides manual download instructions. This function is intended to be called by the user or a setup script. ``` -------------------------------- ### Backing Track Output Example Source: https://drumscript.github.io/DrumScript/guide/interactive/create_backing_track.html Example output string indicating the path of the successfully created backing track. ```text Created backing track: backing_track/test_track_1/test_track_1_no_drums.wav ``` -------------------------------- ### Install FFmpeg via Homebrew Source: https://drumscript.github.io/DrumScript/guide/installation.html Installs FFmpeg on macOS or Linux systems using Homebrew. ```bash brew install ffmpeg ``` -------------------------------- ### FFmpeg Installation Logic for macOS and Windows Source: https://drumscript.github.io/DrumScript/_modules/drumscript/utils/ffmpeg_installer.html Handles platform-specific installation instructions and automated Homebrew execution for macOS, alongside manual configuration steps for Windows. ```python elif system == "Darwin": # macOS print("\n--- Installation instructions for macOS (using Homebrew) ---") print("1. Install Homebrew (if you don't have it):") print(' /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"') print("2. Install FFmpeg using Homebrew:") print(" brew install ffmpeg") user_choice = input("Attempt to install FFmpeg using 'brew install ffmpeg' now? (y/N): ").lower() if user_choice == "y": try: print("Attempting to run: brew install ffmpeg...") subprocess.run(["brew", "install", "ffmpeg"], check=True) print("\nFFmpeg installation attempt finished. Please re-run this check.") print("You may need to restart your terminal for PATH changes to take effect.") except subprocess.CalledProcessError as e: print(f"Error during automated FFmpeg installation: {e}") print("Please try installing manually using the commands above.") except FileNotFoundError: print("Homebrew command not found. Is Homebrew installed?") print("Please install FFmpeg manually using the instructions above.") else: print("Please install FFmpeg manually using the instructions above.") elif system == "Windows": print("\n--- Installation instructions for Windows ---") print("1. Download FFmpeg binaries from the official website:") print(" Go to https://ffmpeg.org/download.html") print(" Click on the Windows icon and then select a 'release build' provider (e.g., gyan.dev or BtbN).") print("2. Extract the downloaded ZIP file to a location like C:\\ffmpeg.") print(" You should have a folder structure like C:\\ffmpeg\\bin containing ffmpeg.exe, ffprobe.exe, etc.") print("3. Add the 'bin' folder (e.g., C:\\ffmpeg\\bin) to your System PATH environment variable.") print(" - Search for 'Environment Variables' in Windows search bar.") print(" - Click 'Edit the system environment variables'.") print(" - Click 'Environment Variables...' button.") print(" - Under 'System variables', find and select the 'Path' variable, then click 'Edit...'.") print(" - Click 'New' and paste the path to your FFmpeg 'bin' folder (e.g., C:\\ffmpeg\\bin).") print(" - Click 'OK' on all dialogs.") print("4. Restart your Command Prompt/PowerShell or IDE to ensure PATH changes take effect.") print("\nOnce installed, re-run 'drumscript.install_ffmpeg()' to verify.") else: print(f"\n--- Installation instructions for {system} ---") print("Please consult your operating system's documentation for installing FFmpeg.") print("You can typically find pre-built binaries on https://ffmpeg.org/download.html") print("Ensure FFmpeg executables (ffmpeg, ffprobe) are added to your system's PATH.") print("\n--- FFmpeg check complete ---") ``` -------------------------------- ### Verify environment setup Source: https://drumscript.github.io/DrumScript/development/contributor_guidance.html Command to run a local interface test to confirm the environment is configured correctly. ```bash uv run python local_tests/drumscript_lite/test_local_interface.py ``` -------------------------------- ### Set up development environment with uv Source: https://drumscript.github.io/DrumScript/development/contributor_guidance.html Commands to create a virtual environment, activate it, install dependencies in editable mode, and set up pre-commit hooks. ```bash # 1. Create a virtual environment uv venv # 2. Activate the virtual environment source .venv/bin/activate # macOS/Linux # .venv\Scripts\activate # Windows # 3. Install dependencies AND the package in editable mode uv pip install -e . # 4. Install pre-commit hooks (for automatic formatting) uv pip install pre-commit pre-commit install ``` -------------------------------- ### Verify FFmpeg installation Source: https://drumscript.github.io/DrumScript/guide/installation.html Checks if FFmpeg is correctly installed and accessible in the system path. ```bash ffmpeg -version ``` -------------------------------- ### Install DrumScript Source: https://drumscript.github.io/DrumScript/guide/installation.html Installs the DrumScript package using the uv package manager. ```bash uv pip install drumscript ``` -------------------------------- ### Load Audio File Examples Source: https://drumscript.github.io/DrumScript/_modules/drumscript/audio_processor/audio_loader.html Demonstrates loading audio at the native sample rate or resampling to a specific target rate. ```python import drumscript as ds audio, sr = ds.load_audio("my_drum_loop.wav") print(f"Native rate: {sr} Hz") ``` ```python from drumscript.notation_generator.constants import SAMPLE_RATE audio, sr = ds.load_audio("my_drum_loop.wav", sr=SAMPLE_RATE) ``` -------------------------------- ### Install uv package manager Source: https://drumscript.github.io/DrumScript/development/contributor_guidance.html Command to install the uv package manager on macOS or Linux systems. ```bash curl -LsSf https://astral.sh/uv/install.sh | sh ``` -------------------------------- ### Define n_fft parameter Source: https://drumscript.github.io/DrumScript/guide/glossary.html Example showing the configuration of the n_fft variable for the listening window size. ```text n_fft= 2048 ``` -------------------------------- ### Import DrumScript and IPython Audio Source: https://drumscript.github.io/DrumScript/guide/interactive/measure_tempo.html Initial setup for loading audio and displaying playback controls in an IPython environment. ```python from IPython.display import Audio, display import drumscript as ds ``` -------------------------------- ### Audio Loading Warnings Source: https://drumscript.github.io/DrumScript/guide/interactive/measure_tempo.html Example of library warnings encountered during the audio loading process. ```text /home/runner/work/DrumScript/DrumScript/drumscript/audio_processor/audio_loader.py:51: UserWarning: PySoundFile failed. Trying audioread instead. audio_data, sample_rate = librosa.load( /home/runner/work/DrumScript/DrumScript/.venv/lib/python3.12/site-packages/librosa/core/audio.py:184: FutureWarning: librosa.core.audio.__audioread_load Deprecated as of librosa version 0.10.0. It will be removed in librosa version 1.0. y, sr_native = __audioread_load(path, offset, duration, dtype) ``` -------------------------------- ### is_ffmpeg_installed() Source: https://drumscript.github.io/DrumScript/generated/drumscript.utils.ffmpeg_installer.is_ffmpeg_installed.html Checks if FFmpeg and ffprobe are installed and accessible in the system's PATH. ```APIDOC ## is_ffmpeg_installed() ### Description Checks if FFmpeg is installed and accessible in the system's PATH. It verifies the presence of both the `ffmpeg` and `ffprobe` executables. ### Signature `is_ffmpeg_installed() -> bool` ### Returns - **bool**: True if FFmpeg (ffmpeg and ffprobe) is found, False otherwise. ``` -------------------------------- ### Detecting Tempo with DrumScript Source: https://drumscript.github.io/DrumScript/_modules/drumscript.html Examples for estimating BPM from audio files or pre-loaded arrays. ```python import drumscript as ds bpm = ds.detect_tempo("drum_loop.wav") print(f"Tempo: {bpm} BPM") ``` ```python y, sr = ds.load_audio("drum_loop.wav") # stats = ds.detect_tempo(y, full=True) stats = ds.detect_tempo(y, verbose=True) print(stats['bpm']) ``` -------------------------------- ### is_ffmpeg_installed() Source: https://drumscript.github.io/DrumScript/_modules/drumscript/utils/ffmpeg_installer.html Checks if FFmpeg and ffprobe are installed and accessible in the system's PATH, and verifies that the FFmpeg executable is functional. ```APIDOC ## is_ffmpeg_installed() ### Description Checks if FFmpeg and ffprobe are installed and accessible in the system's PATH. It verifies the presence of the executables and attempts to run `ffmpeg -version` to ensure the installation is functional. ### Returns - **bool** - True if both ffmpeg and ffprobe are found and functional, False otherwise. ``` -------------------------------- ### Define hop_length parameter Source: https://drumscript.github.io/DrumScript/guide/glossary.html Example showing the configuration of the hop_length variable for audio analysis windows. ```text hop_length=512 ``` -------------------------------- ### Check for FFmpeg installation Source: https://drumscript.github.io/DrumScript/_modules/drumscript/utils/ffmpeg_installer.html Verifies if both ffmpeg and ffprobe are available in the system PATH and attempts to run a version check. ```python def is_ffmpeg_installed() -> bool: """ Checks if FFmpeg is installed and accessible in the system's PATH. :return: True if FFmpeg (ffmpeg and ffprobe) is found, False otherwise. :rtype: bool """ # Check for 'ffmpeg' executable ffmpeg_path = shutil.which("ffmpeg") if ffmpeg_path: print(f"FFmpeg executable found at: {ffmpeg_path}") else: print("FFmpeg executable not found in PATH.") return False # Also check for 'ffprobe' as it's often used alongside ffmpeg by librosa/pydub ffprobe_path = shutil.which("ffprobe") if ffprobe_path: print(f"FFprobe executable found at: {ffprobe_path}") else: print("FFprobe executable not found in PATH.") return False # Optional: Verify FFmpeg version by running it try: # Run ffmpeg -version and check for non-zero exit code subprocess.run(["ffmpeg", "-version"], check=True, capture_output=True) print("FFmpeg seems to be working correctly.") return True except (subprocess.CalledProcessError, FileNotFoundError): print("FFmpeg found but is not runnable or encountered an error. It might be corrupted or improperly installed.") return False ``` -------------------------------- ### Define sample_rate parameter Source: https://drumscript.github.io/DrumScript/guide/glossary.html Examples illustrating how sample_rate (sr) relates to audio measurements and signal length. ```text sr = 22050 (Hz) ``` ```text sr=22050 (Hz), and the input_signal of one of your drum notes is 1744 milliseconds in length, then your sample_rate = ~38450 samples ``` -------------------------------- ### Detect Tempo Source: https://drumscript.github.io/DrumScript/guide/interactive/measure_tempo.html Example of detecting the tempo of an audio file using the DrumScript library. ```python # 2. Detect tempo bpm = ds.detect_tempo(audio_file_path) print(f"Detected Tempo: {bpm:.2f} BPM") ``` -------------------------------- ### Implement Arrange-Act-Assert pattern in tests Source: https://drumscript.github.io/DrumScript/development/testing_guidance.html Structure tests by separating input setup, execution, and verification. Limit each test to a single action. ```python def test_normalises_to_unit_peak(): # Arrange — set up inputs audio = np.array([0.0, 0.5, -0.25, 0.1], dtype=np.float32) # Act — do the thing result = normalise_audio(audio) # Assert — verify the outcome assert np.isclose(np.max(np.abs(result)), 1.0) ``` -------------------------------- ### Extracting Stems with DrumScript Source: https://drumscript.github.io/DrumScript/_modules/drumscript.html Example usage of the extract_stems function to generate a backing track. ```python results = ds.extract_stems( "my_song.mp3", drumless=True, output_format="mp3", # full=True, # LEGACY CODE FROM V0.1.5 (--full flag replaced with deprecation shim, in place of --verbose) verbose=True ) print(f"Backing track saved to: {results['mix']}") ``` -------------------------------- ### Audio Loading Error Traceback Source: https://drumscript.github.io/DrumScript/guide/interactive/measure_tempo.html A traceback example showing a LibsndfileError and NoBackendError when attempting to load an audio file. ```python --------------------------------------------------------------------------- LibsndfileError Traceback (most recent call last) File ~/work/DrumScript/DrumScript/.venv/lib/python3.12/site-packages/librosa/core/audio.py:176, in load(path, sr, mono, offset, duration, dtype, res_type) 175 try: --> 176 y, sr_native = __soundfile_load(path, offset, duration, dtype) 178 except sf.SoundFileRuntimeError as exc: 179 # If soundfile failed, try audioread instead File ~/work/DrumScript/DrumScript/.venv/lib/python3.12/site-packages/librosa/core/audio.py:209, in __soundfile_load(path, offset, duration, dtype) 207 else: 208 # Otherwise, create the soundfile object --> 209 context = sf.SoundFile(path) 211 with context as sf_desc: File ~/work/DrumScript/DrumScript/.venv/lib/python3.12/site-packages/soundfile.py:690, in SoundFile.__init__(self, file, mode, samplerate, channels, subtype, endian, format, closefd, compression_level, bitrate_mode) 688 self._info = _create_info_struct(file, mode, samplerate, channels, 689 format, subtype, endian) --> 690 self._file = self._open(file, mode_int, closefd) 691 if set(mode).issuperset('r+') and self.seekable(): 692 # Move write position to 0 (like in Python file objects) File ~/work/DrumScript/DrumScript/.venv/lib/python3.12/site-packages/soundfile.py:1265, in SoundFile._open(self, file, mode_int, closefd) 1264 err = _snd.sf_error(file_ptr) -> 1265 raise LibsndfileError(err, prefix="Error opening {0!r}: ".format(self.name)) 1266 if mode_int == _snd.SFM_WRITE: 1267 # Due to a bug in libsndfile version <= 1.0.25, frames != 0 1268 # when opening a named pipe in SFM_WRITE mode. 1269 # See http://github.com/erikd/libsndfile/issues/77. LibsndfileError: Error opening 'audio/test_track_1.wav': Format not recognised. During handling of the above exception, another exception occurred: NoBackendError Traceback (most recent call last) Cell In[2], line 3 1 # 1. Load audio 2 audio_file_path = "audio/test_track_1.wav" ----> 3 audio_file = ds.load_audio(audio_file_path) 4 print("Audio loaded!") 5 display(Audio(audio_file_path)) File ~/work/DrumScript/DrumScript/drumscript/audio_processor/audio_loader.py:51, in load_audio(audio_path, sr) 49 # sample_rate = SAMPLE_RATE 50 try: ---> 51 audio_data, sample_rate = librosa.load( 52 audio_path, sr=sr 53 ) # The librosa.load_audio() fct handles wide variety of audio formats, including .mp3, .wav, .flac, .ogg, etc. 54 # return audio_data, sr 55 return audio_data, sample_rate File ~/work/DrumScript/DrumScript/.venv/lib/python3.12/site-packages/librosa/core/audio.py:184, in load(path, sr, mono, offset, duration, dtype, res_type) 180 if isinstance(path, (str, pathlib.PurePath)): 181 warnings.warn( 182 "PySoundFile failed. Trying audioread instead.", stacklevel=2 183 ) --> 184 y, sr_native = __audioread_load(path, offset, duration, dtype) 185 else: 186 raise exc File ~/work/DrumScript/DrumScript/.venv/lib/python3.12/site-packages/decorator/__init__.py:247, in decorate..fun(*args, **kw) 245 if not kwsyntax: 246 args, kw = fix(args, kw, sig) --> 247 return caller(func, *(extras + args), **kw) File ~/work/DrumScript/DrumScript/.venv/lib/python3.12/site-packages/librosa/util/decorators.py:63, in deprecated..__wrapper(func, *args, **kwargs) 54 """Warn the user, and then proceed.""" 55 warnings.warn( 56 "{:s}.{:s}\n\tDeprecated as of librosa version {:s}." 57 "\n\tIt will be removed in librosa version {:s}.".format( (...) 61 stacklevel=3, # Would be 2, but the decorator adds a level 62 ) ---> 63 return func(*args, **kwargs) File ~/work/DrumScript/DrumScript/.venv/lib/python3.12/site-packages/librosa/core/audio.py:240, in __audioread_load(path, offset, duration, dtype) 237 reader = path 238 else: 239 # If the input was not an audioread object, try to open it --> 240 reader = audioread.audio_open(path) 242 with reader as input_file: 243 sr_native = input_file.samplerate File ~/work/DrumScript/DrumScript/.venv/lib/python3.12/site-packages/audioread/__init__.py:131, in audio_open(path, backends) 128 pass 130 # All backends failed! --> 131 raise NoBackendError() NoBackendError: ``` -------------------------------- ### Run Audio Loader Module Source: https://drumscript.github.io/DrumScript/guide/cli_reference.html Verifies audio loading and peak normalization functionality. ```bash python -m drumscript.audio_processor.audio_loader ``` -------------------------------- ### Initialize PDF Canvas and Headers Source: https://drumscript.github.io/DrumScript/_modules/drumscript/notation_generator/pdf_exporter.html Sets up the initial PDF canvas state, including font settings and document title/tempo headers. ```python current_y = PAGE_HEIGHT - 150 c.setFont("Helvetica-Bold", 24) c.drawString(MARGIN_X, PAGE_HEIGHT - 50, "DrumScript Transcription") c.setFont("Helvetica", 12) c.drawString(MARGIN_X, PAGE_HEIGHT - 70, f"Tempo: {int(tempo)} BPM") ``` -------------------------------- ### Execute Onset Detection via CLI Source: https://drumscript.github.io/DrumScript/_modules/drumscript/audio_processor/onset_detector.html This script demonstrates how to load an audio file, normalize it, detect onsets, and estimate the tempo using the drumscript library. ```python if __name__ == "__main__": from drumscript.audio_processor.audio_loader import load_audio, normalise_audio from drumscript.audio_processor.tempo_detector import estimate_tempo # --------------------------------------------------------------------------uncomment during testing # from datetime import datetime # print("\n# ------------------------------------------------------------------------------------") # datetimestamp = datetime.now() # print(f'\ndate/time: {datetimestamp}') # -------------------------------------------------------------------------------------------------- print("Running onset_detector.py example with provided filepath...") # FUTURE: Find way to encode this so it prints the file path provided in CLI try: # Import necessary modules from package # Note: You might need 'from DrumScript.audio_processor.audio_loader import ...' # if running this script directly and 'audio_processor' is not in the Python path. # However, for 'python -m' style execution, 'from audio_processor.audio_loader import ...' is usually correct. # Required for CLI argparsing parser = argparse.ArgumentParser(description="Detect onsets in drum audio.") parser.add_argument("audio_path", help="Path to the input audio file") args = parser.parse_args() # sr = 44100 # Target sample rate for processing # sr = 44100*1.5 # Target sample rate for processing sr = SAMPLE_RATE print(f"sample_rate=sr={sr}") # Print current sample rate applied # --- Get path to audio (cli or import from sister module) current_script_dir = os.path.dirname(os.path.abspath(__file__)) print(f"current_script_dir: {current_script_dir}") # Go up one level from audio_processor/onset_detector.py to the outer DRUMSCRIPT/ folder project_root = os.path.abspath(os.path.join(current_script_dir, "..", "..")) print(f"project_root: {project_root}") audio_path = os.path.abspath(args.audio_path) print(f"audio_path: {audio_path}") print(f"Attempting to load: {audio_path}") # Load and normalise audio # audio_data, sample_rate = load_audio(audio_path, sr=sr) audio_data, sample_rate = load_audio(audio_path, sr=SAMPLE_RATE) normalised_audio = normalise_audio(audio_data) # Detect onsets print(f"Detecting onsets in : {audio_path}") onsets = detect_onsets(normalised_audio, SAMPLE_RATE) print(f"Detected {len(onsets)} onsets.") if onsets: # Print the first few detected onsets for verification # print("\nFirst 10 detected onsets (seconds):") # for i, onset_time in enumerate(onsets[:10]): # if len(onsets) > 10: # print(f" ...and {len(onsets) - 10} more onsets.") # Print * (ALL) detected onsets for now print(f"\n All {len(onsets)} detected onsets (seconds):") for i, onset_time in enumerate(onsets): print(f" Onset {i + 1}: {onset_time:.4f}s") # for i, onset_time in enumerate(onsets): # print(f" Onset {i+1}: {onsets:.4f}s") else: print(f"No onsets detected in audio_path: {audio_path}") # global_tempo = estimate_tempo(audio_data, SAMPLE_RATE, HOP_LENGTH) # tempo = estimate_tempo(audio_data, SAMPLE_RATE, HOP_LENGTH) tempo = estimate_tempo(audio_data, SAMPLE_RATE) # tempo = estimate_tempo(audio_data, SAMPLE_RATE)/2 # temporary fix # tempo = estimate_tempo(audio_data, SAMPLE_RATE)/4 # temporary fix # print(f"Loaded audio: Shape={normalised_audio.shape}, Sample Rate={sample_rate}, Duration={len(normalised_audio)/sample_rate:.2f} seconds, # Tempo={calculate_tempo_from_onsets(onsets, sr=SAMPLE_RATE):2f}") duration = len(normalised_audio) / sample_rate print( f"Loaded audio: Shape=[{normalised_audio.shape}, Sample Rate={sample_rate} (Hz), " f"Hop Length={HOP_LENGTH} (Hz), Duration={duration:.2f} seconds, Tempo={tempo:.2f} BPM]" ) except FileNotFoundError: print(f"\nERROR: The audio file '{audio_path}' was not found.") print(f"\nPlease ensure you have provided the correct path to audio file: {audio_path}") except ImportError as e: print(f"\nERROR: Required modules/libraries might be missing or imports are incorrect: {e}") print("Ensure 'soundfile', 'librosa', 'numpy', and DrumScript modules are correctly installed and structured.") print("For MP3, 'ffmpeg' must also be installed on system and accessible in PATH.") except Exception as e: print(f"\nAn unexpected error occurred during the example execution: {e}") import traceback traceback.print_exc() # Print full traceback for debugging ``` -------------------------------- ### Load and Process Audio File Source: https://drumscript.github.io/DrumScript/_modules/drumscript/audio_processor/audio_loader.html Demonstrates the standard workflow for loading an audio file, normalizing the signal, and estimating the tempo. ```python # Set up argument parser parser = argparse.ArgumentParser(description="Load and process an audio file for DrumScript.") parser.add_argument("audio_path", type=str, help="Path to the audio file to be processed.") # Parse the command-line arguments args = parser.parse_args() audio_path = args.audio_path try: print(f"Attempting to load: {audio_path}") audio, sr = load_audio(audio_path, sr=SAMPLE_RATE) normalised_audio = normalise_audio(audio) normalised_max = np.max(np.abs(normalised_audio)) assert np.isclose(normalised_max, 1.0) or np.isclose(normalised_max, 0.0), "Normalisation failed!" bpm = estimate_tempo(normalised_audio, sr) # print(f"Estimated Tempo (Tempogram-First): {int(round(bpm))} BPM") print(f"Loaded audio: Shape={audio.shape}, Sample Rate={sr}, Tempo={bpm:.2f}, Duration={len(audio) / sr:.2f} seconds") # if _prompt_user_for_playback(): # play_audio(normalised_audio, sr) except Exception as e: print(f"\nAn unexpected error occurred during the example execution: {e}") ``` -------------------------------- ### Demucs Separation Status Source: https://drumscript.github.io/DrumScript/guide/interactive/create_backing_track.html Console output indicating the start of the Demucs separation process. ```text Starting Demucs separation for: test_track_1.wav... ``` -------------------------------- ### Audio Loading Error Source: https://drumscript.github.io/DrumScript/guide/interactive/measure_tempo.html Example of an error message returned when an audio file fails to load. ```text Error loading audio file audio/test_track_1.wav: ``` -------------------------------- ### Load audio at native sample rate Source: https://drumscript.github.io/DrumScript/generated/drumscript.audio_processor.audio_loader.load_audio.html Use this approach for quick exploration or when working in notebooks where the original file sample rate is sufficient. ```python import drumscript as ds audio, sr = ds.load_audio("my_drum_loop.wav") print(f"Native rate: {sr} Hz") ``` -------------------------------- ### Run Tempo Estimation from CLI Source: https://drumscript.github.io/DrumScript/_modules/drumscript/audio_processor/tempo_detector.html A main block implementation for testing tempo estimation on an audio file provided via command line arguments. ```python if __name__ == "__main__": # uncomment during testing # from datetime import datetime # print("\n# ------------------------------------------------------------------------------------") # datetimestamp = datetime.now() # print(f'\ndate/time: {datetimestamp}') from drumscript.audio_processor.audio_loader import load_audio, normalise_audio from drumscript.notation_generator.constants import SAMPLE_RATE parser = argparse.ArgumentParser(description="Estimate the tempo of an audio file.") # parser.add_argument("audio_file_path", type=str, help="Path to the audio file to be processed.") parser.add_argument("audio_path", type=str, help="Path to the audio file to be processed.") args = parser.parse_args() # actual_drum_recording_path = args.audio_file_path # audio_file_path, relative to ROOT, not the path of this script audio_path = args.audio_path # audio_path, relative to ROOT, not the path of this script sr = SAMPLE_RATE try: # Load and normalise the audio # print(f"Attempting to load: {actual_drum_recording_path}") print(f"Attempting to load: {audio_path}") # audio, sr = load_audio(actual_drum_recording_path, sr=44100) # audio, sr = load_audio(actual_drum_recording_path, sr=sr) audio, sr = load_audio(audio_path, sr=sr) normalised_audio = normalise_audio(audio) # Estimate the tempo bpm = estimate_tempo(normalised_audio, sr) print(f"Estimated Tempo: {int(round(bpm))} BPM") except Exception as e: print(f"\nAn unexpected error occurred: {e}") # print("\n#==================================================================================================") ``` -------------------------------- ### Load Audio for Processing Source: https://drumscript.github.io/DrumScript/guide/interactive/extract_drums.html Initializes the audio file using the DrumScript library and displays it in an IPython environment. ```python from IPython.display import Audio, display import drumscript as ds # 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)) ``` -------------------------------- ### Transcribe drum audio Source: https://drumscript.github.io/DrumScript/_modules/drumscript.html Examples for performing drum transcription on isolated stems or full songs. ```python import drumscript as ds pdf = ds.transcribe("drum_loop.wav") ``` ```python result = ds.transcribe( "full_song.mp3", full_song=True, time_signature="6/8", output_dir="./my_transcriptions", # full=True, # LEGACY CODE FROM V0.1.5 (--full flag replaced with deprecation shim, in place of --verbose) verbose=True ) print(f"PDF: {result['pdf_path']}") print(f"Detected tempo: {result['tempo']:.1f} BPM") print(f"Onsets: {len(result['onsets'])}") ``` -------------------------------- ### get_audio_slice(audio_path, onset_time, sr) Source: https://drumscript.github.io/DrumScript/generated/drumscript.drum_classifier.classify.get_audio_slice.html Cuts a specific millisecond slice of audio starting exactly at the onset time. ```APIDOC ## get_audio_slice(audio_path, onset_time, sr) ### Description Cuts a specific millisecond slice of audio starting exactly at the onset time. ### Parameters - **audio_path** (ndarray) - Required - The input audio data array. - **onset_time** (float) - Required - The exact time in milliseconds to start the slice. - **sr** (int) - Required - The sampling rate of the audio. ### Returns - **ndarray** - The sliced audio segment. ``` -------------------------------- ### Run Tempo Detector Module Source: https://drumscript.github.io/DrumScript/guide/cli_reference.html Estimates the BPM of an audio file using the tempogram-first method. ```bash python -m drumscript.audio_processor.tempo_detector ``` -------------------------------- ### Slice Audio for Analysis Source: https://drumscript.github.io/DrumScript/_modules/drumscript/drum_classifier/classify.html Extracts a specific duration of audio starting at a given onset time, with zero-padding if the slice exceeds the file length. ```python def get_audio_slice(audio_path: np.ndarray, onset_time: float, sr: int) -> np.ndarray: """ Cuts a specific millisecond slice of audio starting exactly at the onset time. """ start_sample = int(onset_time * sr) # Convert duration from ms to seconds, then to samples duration_secs = ONSET_SLICE_DURATION_MS / 1000.0 end_sample = start_sample + int(duration_secs * sr) # Pad with zeros if the slice goes past the end of the audio file if end_sample > len(audio_path): slice_data = audio_path[start_sample:] pad_length = end_sample - len(audio_path) slice_data = np.pad(slice_data, (0, pad_length), mode="constant") return slice_data return audio_path[start_sample:end_sample] ``` -------------------------------- ### Write Audio Data to Disk Source: https://drumscript.github.io/DrumScript/_modules/drumscript/audio_processor/stem_splitter.html Writes audio data to disk, supporting WAV (via soundfile) and MP3 (via pydub). MP3 export requires FFmpeg to be installed on the system. ```python def _write_audio(audio_data, sample_rate, output_path, fmt="wav"): """ Write a numpy audio array to disk in the requested format. Routes WAV to soundfile (no ffmpeg) and MP3 to pydub (needs ffmpeg). :param audio_data: Audio samples, shape (n_samples, n_channels), float32 in [-1, 1]. :type audio_data: np.ndarray :param sample_rate: Sample rate in Hz. :type sample_rate: int :param output_path: Destination path. :type output_path: Path or str :param fmt: 'wav' or 'mp3'. :type fmt: str """ output_path = str(output_path) if fmt == "wav": # soundfile writes WAV losslessly with no ffmpeg dependency. # subtype='PCM_16' matches Demucs's default output bit depth and keeps # files a sensible size (float32 WAV would be 2x bigger with no benefit # for end-user playback). sf.write(output_path, audio_data, sample_rate, subtype="PCM_16") elif fmt == "mp3": # MP3 encoding requires an encoder (LAME). pydub shells out to ffmpeg, # which brokers this for us. Convert numpy array -> pydub AudioSegment # -> MP3 file. # pydub expects int16 samples interleaved as bytes. samples_int16 = (audio_data * 32767).astype(np.int16) n_channels = samples_int16.shape[1] if samples_int16.ndim > 1 else 1 segment = AudioSegment( samples_int16.tobytes(), frame_rate=sample_rate, sample_width=2, # 2 bytes = 16 bit channels=n_channels, ) # '-q:a 0' = best quality VBR for LAME. segment.export(output_path, format="mp3", parameters=["-q:a", "0"]) else: raise ValueError(f"Unsupported output format: {fmt!r}. Expected 'wav' or 'mp3'.") ``` -------------------------------- ### Run Onset Detector Module Source: https://drumscript.github.io/DrumScript/guide/cli_reference.html Verifies HPSS-based onset detection using internal hardcoded test paths. ```bash python -m drumscript.audio_processor.onset_detector ``` -------------------------------- ### load_audio(audio_path: str, sr: int | None = None) Source: https://drumscript.github.io/DrumScript/_modules/drumscript/audio_processor/audio_loader.html Loads an audio file from the specified path and optionally resamples it to a target sample rate. ```APIDOC ## load_audio(audio_path, sr=None) ### Description Loads an audio file (e.g., .wav, .mp3, .flac, .ogg) and optionally resamples it to a specific sample rate. ### Parameters - **audio_path** (str) - Required - Path to the audio file. - **sr** (int | None) - Optional - Target sample rate in Hz. If None, loads at the file's native sample rate. ### Returns - **tuple[np.ndarray, int]** - A tuple containing the audio data as a numpy array and the sample rate. ### Raises - **FileNotFoundError** - If the file does not exist. ``` -------------------------------- ### Clone Forked Repository Source: https://drumscript.github.io/DrumScript/development/contributor_guidance.html Clone the forked repository and initialize submodules. ```bash $ git clone --recursive git@github.com:YourLogin/DrumScript.git $ cd DrumScript $ git pull --recurse-submodules ``` -------------------------------- ### Clone the DrumScript repository Source: https://drumscript.github.io/DrumScript/development/contributor_guidance.html Commands to clone the repository and navigate into the project directory. ```bash git clone https://github.com/DrumScript/DrumScript.git cd DrumScript ``` -------------------------------- ### Generate Drumless Backing Track Source: https://drumscript.github.io/DrumScript/guide/cli_reference.html Creates a drumless version of the source audio in MP3 format. ```bash python drumscript/main.py "audio_path.wav" --drumless --format mp3 ``` -------------------------------- ### Create a Feature Branch Source: https://drumscript.github.io/DrumScript/development/contributor_guidance.html Create a new local branch for development using a descriptive naming convention. ```bash git checkout -b feature/improve-hihat-detection ``` -------------------------------- ### Locate and Play Backing Track Source: https://drumscript.github.io/DrumScript/guide/interactive/create_backing_track.html Uses the os module to filter for files containing 'no_drums' in the specified directory and plays the first match found. ```python # 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.") ``` -------------------------------- ### Split drum stems using StemSplitter Source: https://drumscript.github.io/DrumScript/guide/glossary.html Demonstrates initializing the StemSplitter class and invoking the split_drums method to isolate drum tracks from an audio file. ```python splitter = StemSplitter() drum_path = splitter.split_drums(*song.mp3*, *./stems*) ``` -------------------------------- ### mix_stems(stems_dict, stems_to_mix, output_path, fmt='wav') Source: https://drumscript.github.io/DrumScript/generated/drumscript.audio_processor.stem_splitter.mix_stems.html Combines specified audio stems into a single file. Uses soundfile and numpy for WAV output, and pydub/ffmpeg for MP3 output. ```APIDOC ## mix_stems(stems_dict, stems_to_mix, output_path, fmt='wav') ### Description Combines a subset of audio stems defined in a dictionary into a single output file. The function supports WAV (ffmpeg-free) and MP3 (via pydub/ffmpeg) formats. ### Parameters - **stems_dict** (dict) - Required - Dictionary mapping stem names to file paths. - **stems_to_mix** (list) - Required - List of stem names to combine. - **output_path** (Path) - Required - Destination path for the mixed audio file. - **fmt** (str) - Optional - Output format, either 'wav' or 'mp3'. Defaults to 'wav'. ``` -------------------------------- ### build_score(detected_events, tempo, output_path='outputs/score.pdf', quantization_subdivision=16, time_signature='4/4') Source: https://drumscript.github.io/DrumScript/_modules/drumscript/notation_generator/score_builder.html Builds a drum score by saving event data to JSON and rendering it to PDF and MIDI files. ```APIDOC ## build_score ### Description Builds a drum score by saving event data to JSON and rendering to PDF and MIDI. It performs quantization on the input events to ensure they align with a musical grid. ### Parameters - **detected_events** (list[dict[str, Any]]) - Required - List of classified drum events. - **tempo** (float) - Required - Tempo in BPM. - **output_path** (str) - Optional - Path to save the PDF (default: 'outputs/score.pdf'). - **quantization_subdivision** (int) - Optional - Grid for quantization (e.g., 16 for 16th notes) (default: 16). - **time_signature** (str) - Optional - Time signature string (e.g., '4/4') (default: '4/4'). ``` -------------------------------- ### Initialize and Use DrumScript Core Functions Source: https://drumscript.github.io/DrumScript/api.html Basic usage pattern for importing the library and accessing primary transcription and extraction functions. ```python import drumscript as ds ds.transcribe() ds.extract_drum_stem() ds.extract_stems() ds.detect_tempo() ``` -------------------------------- ### Load and Display Audio Source: https://drumscript.github.io/DrumScript/guide/interactive/measure_tempo.html Loads an audio file from a specified path and renders an interactive audio player. ```python # 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)) ``` -------------------------------- ### build_score(detected_events, tempo, output_path='outputs/score.pdf', quantization_subdivision=16, time_signature='4/4') Source: https://drumscript.github.io/DrumScript/generated/drumscript.notation_generator.score_builder.build_score.html Builds a drum score by saving event data to JSON and rendering it directly to a PDF file. ```APIDOC ## build_score ### Description Builds a drum score by saving event data to JSON and rendering to PDF. This process bypasses MusicXML to ensure WYSIWYG results. ### Parameters - **detected_events** (List[Dict[str, Any]]) - Required - List of classified drum events. - **tempo** (int) - Required - Tempo in BPM. - **output_path** (str) - Optional - Path to save the PDF. Defaults to 'outputs/score.pdf'. - **quantization_subdivision** (int) - Optional - Grid for quantization (e.g., 16 for 16th notes). Defaults to 16. - **time_signature** (str) - Optional - Time signature string (e.g., '4/4'). Defaults to '4/4'. ``` -------------------------------- ### Execute Tests with Markers Source: https://drumscript.github.io/DrumScript/development/testing_guidance.html Commands to run specific subsets of tests based on the defined slow and integration markers. ```bash # Fast loop (default) pytest -m "not slow" # Integration tests only pytest -m integration # Everything (e.g. before a release) pytest ``` -------------------------------- ### Extract All Stems Source: https://drumscript.github.io/DrumScript/guide/usage.html Splits a song into Drums, Bass, Vocals, and Other stems using Demucs. ```bash python -m drumscript.main "audio_path.mp3" --all-stems --format mp3 ``` -------------------------------- ### Export PDF Source: https://drumscript.github.io/DrumScript/_modules/drumscript/notation_generator/pdf_exporter.html Main entry point to generate a PDF drum score from detected events. ```python def export_pdf(detected_events, output_path, tempo, time_signature="4/4"): """ Generates a PDF drum score using ReportLab engine. """ if canvas is None: print("ReportLab missing.") return try: numerator, denominator = map(int, time_signature.split("/")) except ValueError: numerator, denominator = 4, 4 print(f"Generating PDF: {output_path} (Sig: {numerator}/{denominator}, {int(tempo)} BPM)") c = canvas.Canvas(output_path, pagesize=A4) c.setTitle("DrumScript Transcription") system_width = PAGE_WIDTH - (2 * MARGIN_X) music_width = system_width - CLEF_WIDTH BARS_PER_SYSTEM = 4 measure_width = music_width / BARS_PER_SYSTEM seconds_per_beat = 60.0 / tempo if denominator == 8: seconds_per_beat = seconds_per_beat / 2.0 sec_per_measure = seconds_per_beat * numerator ``` -------------------------------- ### Command-line interface for stem extraction Source: https://drumscript.github.io/DrumScript/_modules/drumscript/audio_processor/stem_splitter.html Provides an entry point for running stem extraction directly from the command line with configurable options. ```python if __name__ == "__main__": import argparse # --------------------------------------------------------------------------uncomment during testing # from datetime import datetime # print("\n# ------------------------------------------------------------------------------------") # datetimestamp = datetime.now() # print(f'\ndate/time: {datetimestamp}') # -------------------------------------------------------------------------------------------------- ## Banner / timestamp (moved here from module top-level so it only fires when this file is run ## DIRECTLY via `python3 -m drumscript.audio_processor.stem_splitter ...`, not on every `from ## drumscript ## audio_processor.stem_splitter import ...`) parser = argparse.ArgumentParser(description="Extract stems from an audio file.") parser.add_argument("input_file", help="Path to the input audio file.") parser.add_argument("-o", "--output_dir", default=None, help="Directory to save the stems.") parser.add_argument("--drumless", action="store_true", help="Generate a mix without drums.") parser.add_argument("--mp3", action="store_true", help="Output as MP3 instead of WAV.") parser.add_argument("--all", action="store_true", help="Export all individual stems.") args = parser.parse_args() if not Path(args.input_file).exists(): print(f"Error: File not found at {args.input_file}") sys.exit(1) fmt = "mp3" if args.mp3 else "wav" separate_audio(audio_path=args.input_file, output_format=fmt, drumless=args.drumless, all_stems=args.all, output_dir=args.output_dir) ```