### Instantiate GTZANBeatTrackingAudioTrain Dataset (Python) Source: https://github.com/a43992899/marble/blob/main/tests/test_GTZANBeatTracking_dataset.ipynb This code snippet demonstrates how to instantiate the GTZANBeatTrackingAudioTrain dataset from the marble.tasks.GTZANBeatTracking.datamodule. It configures parameters such as sample rate, number of channels, clip duration, and data source. ```python from marble.tasks.GTZANBeatTracking.datamodule import GTZANBeatTrackingAudioTrain # Instantiate the dataset (adjust paths as needed) dataset = GTZANBeatTrackingAudioTrain( sample_rate=22050, channels=1, clip_seconds=10.0, jsonl="data/GTZAN/GTZANBeatTracking.val.jsonl", label_freq=50, num_neighbors=2, channel_mode="mix", min_clip_ratio=0.5, ) # Call the function; in a Jupyter environment the Audio widget will display automatically audio_widget = visualize_and_play_with_onset_tone( dataset, beat_freq=440, downbeat_freq=880, tone_duration=0.05, tone_amplitude=0.3, ) audio_widget # Display the IPython Audio player in Jupyter ``` ```python from marble.tasks.GTZANBeatTracking.datamodule import GTZANBeatTrackingAudioTrain # Instantiate the dataset (adjust paths as needed) dataset = GTZANBeatTrackingAudioTrain( sample_rate=22050, channels=1, clip_seconds=10.0, jsonl="data/GTZAN/GTZANBeatTracking.val.jsonl", label_freq=75, num_neighbors=2, channel_mode="mix", min_clip_ratio=0.5, ) ``` -------------------------------- ### Visualize and Play Audio with Onset Pulses (Python) Source: https://github.com/a43992899/marble/blob/main/tests/test_GTZANBeatTracking_dataset.ipynb This code visualizes audio signals, including time-domain waveforms with onset pulses, onset masks using stem plots, and the frequency-domain magnitude spectrum. It also generates an audio widget for playback of the combined signal with synchronized square-wave pulses. ```python import matplotlib.pyplot as plt import numpy as np from IPython.display import Audio # Assuming axes, beat_times, db_times, beat_mask, db_mask, t_mask, combined, clip_len, fs, clip_seconds, audio_path, idx are defined elsewhere # ax1.set_title(f"Time-Domain Waveform with Onset Pulses\n{audio_path} (clip idx={idx})") # Draw vertical lines for beat times (red dashed) and downbeat times (blue dash-dot) if beat_times.size > 0: for i, bt in enumerate(beat_times): if i == 0: ax1.axvline(bt, color='r', linestyle='--', alpha=0.7, label='Beat Onset') else: ax1.axvline(bt, color='r', linestyle='--', alpha=0.7) if db_times.size > 0: for i, dt in enumerate(db_times): if i == 0: ax1.axvline(dt, color='b', linestyle='-.', alpha=0.8, label='Downbeat Onset') else: ax1.axvline(dt, color='b', linestyle='-.', alpha=0.8) ax1.legend(loc='upper right') # 7.2 Onset masks (stem plots) ax2 = axes[1] markerline1, stemlines1, baseline1 = ax2.stem( t_mask, beat_mask, linefmt='r-', markerfmt='ro', basefmt='k-', label='Beat Mask' ) plt.setp(markerline1, markersize=4) if db_mask is not None: markerline2, stemlines2, baseline2 = ax2.stem( t_mask, db_mask, linefmt='b-', markerfmt='bs', basefmt='k-', label='Downbeat Mask' ) plt.setp(markerline2, markersize=4) ax2.set_xlim(0, clip_seconds) ax2.set_ylim(-0.1, 1.1) ax2.set_xlabel("Time (s)") ax2.set_ylabel("Mask Value") ax2.set_title("Beat & Downbeat Onset Masks") ax2.legend(loc='upper right') # 7.3 Frequency-domain: magnitude spectrum of combined signal ax3 = axes[2] # Compute real FFT of the combined signal fft_vals = np.fft.rfft(combined) fft_freq = np.fft.rfftfreq(clip_len, d=1.0 / fs) magnitude = np.abs(fft_vals) # Plot magnitude spectrum (in linear scale) ax3.plot(fft_freq, magnitude, color='purple', linewidth=0.8) ax3.set_xlim(0, fs / 2) ax3.set_xlabel("Frequency (Hz)") ax3.set_ylabel("Magnitude") ax3.set_title("Frequency-Domain (Magnitude Spectrum) of Combined Signal") plt.show() # 8. Return an Audio widget for playback of the combined signal print("▶️ Playing the combined audio with onset-synchronized square-wave pulses:") # return Audio(combined, rate=fs) ``` -------------------------------- ### Visualize and Play Onset Tones in Audio Source: https://github.com/a43992899/marble/blob/main/tests/test_GTZANBeatTracking_dataset.ipynb This function takes an audio dataset and generates a modified audio clip with square-wave pulses added at beat and downbeat times. It visualizes the original and combined waveforms, onset masks, and frequency spectrum, returning an audio widget for playback. Dependencies include numpy, torch, and matplotlib. ```python import os import random import numpy as np import torch import matplotlib.pyplot as plt def visualize_and_play_with_onset_tone( dataset, beat_freq=440, downbeat_freq=880, tone_duration=0.05, tone_amplitude=0.3, ): """ Randomly select one clip from the dataset, visualize the original waveform and onset masks, generate separate square-wave pulses for beats and downbeats at different frequencies, overlay them onto the original audio, visualize the combined waveform and onset masks, and display a frequency-domain plot of the combined signal. Finally, return an Audio widget to play back the combined signal. Args: dataset: an instance of a GTZANBeatTracking dataset (inherited from _GTZANBeatTrackingAudioBase) beat_freq: frequency (Hz) of the square-wave pulse for beats (default 440 Hz) downbeat_freq: frequency (Hz) of the square-wave pulse for downbeats (default 880 Hz) tone_duration: duration (seconds) of each square-wave pulse (default 0.05 s) tone_amplitude: amplitude of each square-wave pulse (0.0 < amplitude <= 1.0) """ # 1. Randomly pick one clip index idx = random.randrange(len(dataset)) waveform, label_dict, audio_path = dataset[idx] # waveform: Tensor(shape=(channels, clip_len_samples)) # label_dict["beat"]: Tensor(shape=(label_len,)) # label_dict.get("downbeat"): Tensor(shape=(label_len,)) (if present) # 2. Convert waveform to numpy and select the first channel if multi-channel wav_np = waveform.cpu().numpy() if wav_np.ndim > 1: wav_np = wav_np[0, :] clip_len = wav_np.shape[0] fs = dataset.sample_rate # sample rate (samples per second) clip_seconds = dataset.clip_seconds # clip duration in seconds # Build time axis for waveform t_wav = np.linspace(0, clip_seconds, num=clip_len, endpoint=False) # 3. Extract beat and downbeat masks and compute their time arrays beat_mask = label_dict["beat"].cpu().numpy() # shape = (label_len,) db_mask = label_dict.get("downbeat", None) if db_mask is not None: db_mask = db_mask.cpu().numpy() label_len = beat_mask.shape[0] label_freq = dataset.label_freq # Time axis for masks: each frame i corresponds to time i / label_freq t_mask = np.arange(label_len) / label_freq # Compute beat times and downbeat times in seconds beat_times = t_mask[beat_mask.astype(bool)] if db_mask is not None: db_times = t_mask[db_mask.astype(bool)] else: db_times = np.array([], dtype=float) # 4. Prepare square-wave pulses for beat and downbeat separately tone_len = int(round(tone_duration * fs)) if tone_len < 1: tone_len = 1 # Time axis for a single pulse t_tone = np.linspace(0, tone_duration, num=tone_len, endpoint=False) # Square-wave pulse for beats at beat_freq beat_pulse = tone_amplitude * np.sign(np.sin(2 * np.pi * beat_freq * t_tone)) beat_pulse = beat_pulse.astype(np.float32) # Square-wave pulse for downbeats at downbeat_freq db_pulse = tone_amplitude * np.sign(np.sin(2 * np.pi * downbeat_freq * t_tone)) db_pulse = db_pulse.astype(np.float32) # 5. Create a combined signal by copying the original waveform combined = wav_np.copy().astype(np.float32) # Overlay beat pulses at each beat time for bt in beat_times: start_idx = int(round(bt * fs)) end_idx = start_idx + tone_len if start_idx >= clip_len: continue if end_idx <= clip_len: combined[start_idx:end_idx] += beat_pulse else: valid_len = clip_len - start_idx combined[start_idx:clip_len] += beat_pulse[:valid_len] # Overlay downbeat pulses at each downbeat time for dt in db_times: start_idx = int(round(dt * fs)) end_idx = start_idx + tone_len if start_idx >= clip_len: continue if end_idx <= clip_len: combined[start_idx:end_idx] += db_pulse else: valid_len = clip_len - start_idx combined[start_idx:clip_len] += db_pulse[:valid_len] # 6. Clip the combined signal to [-1.0, +1.0] to avoid distortion combined = np.clip(combined, -1.0, +1.0) # 7. Visualization # Create a figure with three subplots: # 1) Time-domain: original vs combined waveform with onset vertical lines # 2) Time-domain: beat and downbeat masks (stem plots) # 3) Frequency-domain: magnitude spectrum of the combined signal fig, axes = plt.subplots(nrows=3, ncols=1, figsize=(12, 10), constrained_layout=True) # 7.1 Time-domain waveforms ax1 = axes[0] ax1.plot(t_wav, wav_np, color='gray', linewidth=1, label='Original Waveform') ax1.plot(t_wav, combined, color='orange', linewidth=1, alpha=0.6, label='Combined Waveform') ax1.set_xlim(0, clip_seconds) ax1.set_ylabel("Amplitude") # Add vertical lines for beats and downbeats for bt in beat_times: ax1.axvline(bt, color='red', linestyle='--', linewidth=0.5, alpha=0.7) for dt in db_times: ax1.axvline(dt, color='blue', linestyle='--', linewidth=0.5, alpha=0.7) ax1.legend() ax1.set_title('Original vs. Combined Waveform with Onsets') # 7.2 Beat and downbeat masks ax2 = axes[1] markerline, stemlines, baseline = ax2.stem(t_mask, beat_mask, linefmt='r-', markerfmt='ro', basefmt=' ', label='Beat Mask') plt.setp(stemlines, linewidth=1) plt.setp(markerline, markersize=3) if db_mask is not None: markerline, stemlines, baseline = ax2.stem(t_mask, db_mask, linefmt='b-', markerfmt='bo', basefmt=' ', label='Downbeat Mask') plt.setp(stemlines, linewidth=1) plt.setp(markerline, markersize=3) ax2.set_xlim(0, clip_seconds) ax2.set_ylabel("Presence (1) / Absence (0)") ax2.legend() ax2.set_title('Beat and Downbeat Masks') # 7.3 Frequency-domain plot ax3 = axes[2] # Compute FFT of the combined signal yf = torch.fft.fft(torch.tensor(combined).float()) xf = torch.fft.fftfreq(len(combined), 1 / fs) # Plot the magnitude spectrum ax3.plot(xf[:len(xf)//2], torch.abs(yf[:len(yf)//2]) * 2.0 / len(combined)) ax3.set_xlabel("Frequency (Hz)") ax3.set_ylabel("Magnitude") ax3.set_title('Frequency Spectrum of Combined Signal') plt.show() # Return an Audio widget to play back the combined signal # Note: This part requires ipywidgets and IPython.display to be installed and used in a compatible environment (e.g., Jupyter Notebook) try: from IPython.display import Audio return Audio(combined, rate=fs) except ImportError: print("IPython.display.Audio not available. Cannot return playback widget.") return None ``` -------------------------------- ### Import Core Libraries (Python) Source: https://github.com/a43992899/marble/blob/main/tests/test_GTZANBeatTracking_dataset.ipynb Imports essential Python libraries for the Marble project. This includes modules for random number generation, PyTorch for deep learning, NumPy for numerical operations, Matplotlib for plotting, IPython.display for interactive outputs like audio, and os for operating system interactions. ```python import random import torch import numpy as np import matplotlib.pyplot as plt from IPython.display import Audio import os ``` -------------------------------- ### Install MARBLE and Dependencies (Bash) Source: https://context7.com/a43992899/marble/llms.txt Installs MARBLE and its dependencies using conda and pip. This includes setting up a Python 3.10 environment, installing ffmpeg for audio processing, and installing the MARBLE package itself. Optional dependencies for fairseq-based encoders can also be installed. ```bash # Create and activate conda environment conda create -n marble python=3.10 -y conda activate marble # Install ffmpeg for audio processing conda install -c conda-forge ffmpeg -y # Install MARBLE package pip install -e . # Optional: For fairseq-based encoders pip install pip==24.0 pip install fairseq ``` -------------------------------- ### Install Marble Dependencies (Bash) Source: https://github.com/a43992899/marble/blob/main/README.md Installs Marble and its dependencies using Conda and pip. This includes creating a new Conda environment, installing ffmpeg, and then installing Marble itself. An optional step to downgrade pip and install fairseq is also provided. ```bash conda create -n marble python=3.10 -y conda activate marble conda install -c conda-forge ffmpeg -y pip install -e . # pip install pip==24.0 # pip install fairseq ``` -------------------------------- ### Create Custom Audio Dataset with BaseAudioDataset (Python) Source: https://context7.com/a43992899/marble/llms.txt Demonstrates how to extend the `BaseAudioDataset` class in Python to create a custom audio dataset. This involves defining the `__init__` method to call the superclass constructor with dataset parameters and implementing the `get_targets` method to extract task-specific labels from the metadata. The example assumes a JSONL file with 'audio_path', 'sample_rate', 'num_samples', and 'label' fields. ```python import torch from marble.core.base_datamodule import BaseAudioDataset class CustomAudioDataset(BaseAudioDataset): """Custom dataset for genre classification.""" def __init__( self, jsonl: str, # Path to JSONL metadata file sample_rate: int, # Target sample rate (e.g., 24000) channels: int, # Output channels (1 for mono) clip_seconds: float, # Duration of each clip channel_mode: str = "first", # "first", "mix", or "random" min_clip_ratio: float = 1.0 # Minimum partial clip ratio ): super().__init__( jsonl=jsonl, sample_rate=sample_rate, channels=channels, clip_seconds=clip_seconds, channel_mode=channel_mode, min_clip_ratio=min_clip_ratio ) def get_targets(self, file_idx, slice_idx, orig_sr, orig_clip_frames): """Return label for the audio clip.""" info = self.meta[file_idx] label = info["label"] # Expects "label" field in JSONL return torch.tensor(label, dtype=torch.long) # JSONL file format (one JSON object per line): # {"audio_path": "/path/to/audio.wav", "sample_rate": 22050, "num_samples": 660000, "label": 0} # {"audio_path": "/path/to/audio2.wav", "sample_rate": 44100, "num_samples": 1323000, "label": 5} # Usage dataset = CustomAudioDataset( jsonl="data/GTZAN/GTZANGenre.train.jsonl", sample_rate=24000, channels=1, clip_seconds=30.0, channel_mode="first", min_clip_ratio=0.8 ) waveform, label, audio_path = dataset[0] print(waveform.shape) # (1, 720000) - mono, 30 sec at 24kHz print(label) # tensor(5) ``` -------------------------------- ### Set Current Directory for Tests Source: https://github.com/a43992899/marble/blob/main/tests/test_GTZANBeatTracking_dataset.ipynb This code snippet checks if the script is running within a 'tests' directory and, if so, navigates to the parent directory. This is useful for ensuring correct file path resolution during testing. ```python import os pwd = os.getcwd() if pwd.endswith("tests"): os.chdir(os.path.dirname(pwd)) ``` -------------------------------- ### Calculate Loss Weights for Beat and Downbeat (Python) Source: https://github.com/a43992899/marble/blob/main/tests/test_GTZANBeatTracking_dataset.ipynb This script calculates the loss weights for beat and downbeat events based on the total number of positive and negative instances in the dataset. It iterates through the dataset, sums the positive and negative occurrences, and then computes the ratio for each. ```python import torch total_num_pos_beat = 0 total_num_neg_beat = 0 total_num_pos_downbeat = 0 total_num_neg_downbeat = 0 # Assuming 'dataset' is an instance of GTZANBeatTrackingAudioTrain for idx in range(len(dataset)): beat = dataset[idx][1]['beat'] downbeat = dataset[idx][1]['downbeat'] total_num_pos_beat += torch.sum(beat).item() total_num_neg_beat += beat.shape[0] - torch.sum(beat).item() total_num_pos_downbeat += torch.sum(downbeat).item() total_num_neg_downbeat += downbeat.shape[0] - torch.sum(downbeat).item() loss_weight_beat = total_num_neg_beat / total_num_pos_beat loss_weight_downbeat = total_num_neg_downbeat / total_num_pos_downbeat print(f"pos loss weight for beat: {loss_weight_beat:.2f}") print(f"pos loss weight for downbeat: {loss_weight_downbeat:.2f}") ``` -------------------------------- ### Custom Music Task Implementation using BaseTask API Source: https://context7.com/a43992899/marble/llms.txt Illustrates how to create a custom music task by subclassing the BaseTask from marble.core.base_task. This example shows defining a CustomMusicTask with an encoder, transforms, decoders, losses, and metrics. It also demonstrates a typical usage pattern with configuration, including initializing an MERT encoder, transforms, a decoder, a loss function, and metrics, then performing a forward pass. ```python import torch import torch.nn as nn from marble.core.base_task import BaseTask from torchmetrics import Accuracy # Define a custom task by subclassing BaseTask class CustomMusicTask(BaseTask): def __init__( self, encoder: nn.Module, emb_transforms: list, decoders: list, losses: list, metrics: dict, sample_rate: int = 24000, use_ema: bool = False ): super().__init__( encoder=encoder, emb_transforms=emb_transforms, decoders=decoders, losses=losses, metrics=metrics, sample_rate=sample_rate, use_ema=use_ema ) # Usage with configuration (typically via YAML) from marble.encoders.MERT.model import MERT_v1_95M_Encoder from marble.modules.transforms import LayerSelector, TimeAvgPool from marble.modules.decoders import MLPDecoder encoder = MERT_v1_95M_Encoder(train_mode="freeze") transforms = [LayerSelector(layers=[7]), TimeAvgPool()] decoder = MLPDecoder(in_dim=768, out_dim=10) loss_fn = nn.CrossEntropyLoss() metrics = { "train": {"acc": Accuracy(num_classes=10, task="multiclass")}, "val": {"acc": Accuracy(num_classes=10, task="multiclass")}, "test": {"acc": Accuracy(num_classes=10, task="multiclass")} } task = CustomMusicTask( encoder=encoder, emb_transforms=transforms, decoders=[decoder], losses=[loss_fn], metrics=metrics, use_ema=False ) # Forward pass audio = torch.randn(4, 24000 * 5) logits = task(audio) print(logits.shape) # (4, 10) ``` -------------------------------- ### Download MARBLE Datasets (Bash) Source: https://context7.com/a43992899/marble/llms.txt Downloads benchmark datasets for MARBLE from Hugging Face Hub using the `download.py` script. Datasets can be downloaded individually or all at once, and saved to a specified directory. Authentication is required for gated datasets. ```bash # Download all supported datasets python download.py all --save-dir ./data # Download a specific dataset python download.py GTZAN --save-dir ./data # Download gated datasets (requires Hugging Face authentication) # First visit https://huggingface.co/m-a-p/Chords1217 to request access python download.py Chords1217 --save-dir ./data ``` -------------------------------- ### Project Structure Source: https://github.com/a43992899/marble/blob/main/README.md The project is organized into several directories for core code, encoders, modules, tasks, utilities, and configuration files. It also includes entry points for experiments, SOTA models, scripts, and tests. ```bash .\n├── marble/ # Core code package\n│ ├── core/ # Base classes (BaseTask, BaseEncoder, BaseTransform)\n│ ├── encoders/ # Wrapper classes for various SSL encoders\n│ ├── modules/ # Shared transforms, callbacks, losses, decoders\n│ ├── tasks/ # Downstream tasks (probe, few-shot, datamodules)\n│ └── utils/ # IO utilities, instantiation helpers\n├── cli.py # Entry-point for launching experiments\n├── sota/ # Scripts for state-of-the-art models and inference\n├── configs/ # Experiment configs (YAML)\n├── data/ # Datasets and metadata files\n├── scripts/ # Run scripts & utilities\n├── tests/ # Unit tests for transforms & datasets\n├── pyproject.toml # Python project metadata\n└── README.md # This file\n ``` -------------------------------- ### Run MARBLE Experiments via CLI (Bash) Source: https://context7.com/a43992899/marble/llms.txt Executes MARBLE training, testing, and validation workflows using the `cli.py` script and PyTorch Lightning's CLI. Experiments are controlled by YAML configuration files, and parameters can be overridden from the command line. ```bash # Train a model using a configuration file python cli.py fit --config configs/probe.MERT-v1-95M.GTZANGenre.yaml # Test a trained model python cli.py test --config configs/probe.MERT-v1-95M.GTZANGenre.yaml # Validate a model python cli.py validate --config configs/probe.MERT-v1-95M.GTZANGenre.yaml # Override configuration parameters from command line python cli.py fit --config configs/probe.MERT-v1-95M.GTZANGenre.yaml \ --trainer.max_epochs 50 \ --trainer.devices [0,1] \ --data.batch_size 16 ``` -------------------------------- ### Run Marble Training and Testing (Bash) Source: https://github.com/a43992899/marble/blob/main/README.md Executes Marble's training and testing routines using a specified configuration file. The `fit` command trains the model, while the `test` command evaluates it. Results are saved in the 'output/' directory. ```bash python cli.py fit --config configs/probe.MERT-v1-95M.GTZANGenre.yaml python cli.py test --config configs/probe.MERT-v1-95M.GTZANGenre.yaml ``` -------------------------------- ### Reproduce Key Prediction SOTA Training/Testing Source: https://github.com/a43992899/marble/blob/main/README.md This shell script allows for the reproduction of the training and testing procedures for state-of-the-art key prediction models, provided access to the corresponding datasets is available. It is designed to be run from the command line. ```bash bash sota/reproduce_key_sota_20250618.sh ``` -------------------------------- ### Initialize and Use MERT Encoder (Python) Source: https://context7.com/a43992899/marble/llms.txt Demonstrates how to initialize and use the MERT (Music Understanding via Large-Scale Self-Supervised Training) encoder in Python. It shows how to load different model sizes (95M, 330M) and process audio data, with options for different training modes and precision. ```python import torch from marble.encoders.MERT.model import MERT_v1_95M_Encoder, MERT_v1_330M_Encoder # Initialize encoder in frozen mode (for linear probing) encoder = MERT_v1_95M_Encoder( pre_trained_folder=None, # Uses HuggingFace "m-a-p/MERT-v1-95M" train_mode="freeze", # Options: "freeze", "lora", "full" force_half=False, # Cast to fp16 for memory savings preprocess_in_forward=False ) # Process audio (batch_size, num_samples) - audio at 24kHz audio = torch.randn(4, 24000 * 5) # 4 clips of 5 seconds outputs = encoder(audio, output_hidden_states=True) print(outputs.last_hidden_state.shape) # (4, 375, 768) - 75 frames/sec * 5 sec print(len(outputs.hidden_states)) # 13 layers (embedding + 12 transformer) ``` -------------------------------- ### Predict Audio Key with Pretrained SSL Model Source: https://github.com/a43992899/marble/blob/main/README.md This script predicts the musical key of audio files using a pretrained Self-Supervised Learning (SSL) model. It handles automatic model downloading from Hugging Face, batch processing of audio, and saving predictions to a JSONL file. Dependencies include libraries for audio processing and Hugging Face Transformers. ```bash python sota/predict_key.py --filelist_path --output_path --batch_size 16 --download_dir ``` -------------------------------- ### MuQ Encoder API for Music Representation Source: https://context7.com/a43992899/marble/llms.txt Illustrates the usage of the MuQ (Music understanding via Mel Residual Vector Quantization) encoder for generating music representations. It covers initialization, setting the training mode, and processing audio inputs to obtain hidden states. ```python import torch from marble.encoders.MuQ.model import MuQ_Encoder # Initialize MuQ encoder muq = MuQ_Encoder( pre_trained_folder=None, # Uses HuggingFace "OpenMuQ/MuQ-large-msd-iter" train_mode="freeze" # Options: "freeze", "lora", "full" ) muq = muq.to("cuda").eval() # Process audio (batch_size, num_samples) audio = torch.randn(4, 24000 * 10).to("cuda") # 10 seconds at 24kHz with torch.no_grad(): hidden_states = muq(audio, output_hidden_states=True) print(f"Total layers: {len(hidden_states)}") # 13 layers print(f"Output shape per layer: {hidden_states[-1].shape}") # (4, 250, 1024) # 25 frames/sec * 10 sec = 250 frames ``` -------------------------------- ### Visualize Audio Sample and Chord Labels with Playback (Python) Source: https://github.com/a43992899/marble/blob/main/tests/test_Chords1217_dataset.ipynb This Python function visualizes an audio sample's waveform and its associated chord labels. It merges consecutive identical chord labels for clarity and displays them on the waveform plot. Additionally, it provides audio playback functionality suitable for Jupyter Notebooks. Dependencies include `torch`, `torchaudio`, `matplotlib`, `numpy`, and `IPython.display.Audio`. ```python import os # If running inside "tests" folder, move up one level pwd = os.getcwd() if pwd.endswith("tests"): os.chdir(os.path.dirname(pwd)) import torch import torchaudio import matplotlib.pyplot as plt import numpy as np from IPython.display import Audio from marble.utils.utils import id2chord_str def display_sample_audio_and_labels(dataset: torch.utils.data.Dataset, sample_idx: int): """ Visualizes an audio sample and its corresponding chord labels from the dataset. Also supports audio playback in Jupyter Notebooks. Args: dataset (torch.utils.data.Dataset): The dataset to extract the sample from. sample_idx (int): The index of the sample to visualize. """ waveform, targets, audio_path = dataset[sample_idx] # Plot the waveform with reduced opacity for clearer visibility of labels plt.figure(figsize=(12, 6)) plt.plot(np.linspace(0, len(waveform[0]) / dataset.sample_rate, len(waveform[0])), waveform[0].numpy(), color='gray', alpha=0.5) plt.title(f"Waveform of {audio_path}") plt.xlabel("Time (s)") plt.ylabel("Amplitude") # Plot the chord labels over the waveform label_seq = targets.numpy() time_axis = np.linspace(0, len(label_seq) / dataset.label_freq, len(label_seq)) # Merge consecutive segments with the same chord label merged_segments = [] current_label = label_seq[0] segment_start_time = time_axis[0] for i in range(1, len(label_seq)): if label_seq[i] != current_label: segment_end_time = time_axis[i] merged_segments.append((segment_start_time, segment_end_time, current_label)) current_label = label_seq[i] segment_start_time = time_axis[i] # Add the last segment merged_segments.append((segment_start_time, time_axis[-1], current_label)) # Display chord labels in the center of each segment for start_time, end_time, chord_label in merged_segments: # Assign color based on chord label (e.g., H chord gets a distinct color) chord_color = "blue" # Changed to blue for a neutral tone # Display the chord label in the center of the segment plt.text((start_time + end_time) / 2, 0.5, id2chord_str(chord_label), color=chord_color, fontsize=12, ha='center', va='center') # Draw a vertical line at the segment boundary plt.axvline(x=start_time, color='black', linestyle='-', linewidth=1) plt.axvline(x=end_time, color='black', linestyle='-', linewidth=1) # Tight layout to avoid label clipping plt.tight_layout() plt.show() # Playback the audio in the notebook audio_data = waveform.squeeze().numpy() # Remove channel dimension if mono return Audio(audio_data, rate=dataset.sample_rate) from marble.tasks.Chords1217.datamodule import Chords1217AudioTrain # Example usage: dataset = Chords1217AudioTrain(sample_rate=44100, channels=1, clip_seconds=5.0, jsonl="data/Chords1217/Chords1217.train.jsonl", label_freq=10) sample_idx = 100 # Any index you'd like to inspect display_sample_audio_and_labels(dataset, sample_idx) ``` -------------------------------- ### LoRA Fine-tuning for MERT Encoder Source: https://context7.com/a43992899/marble/llms.txt Demonstrates LoRA fine-tuning for the MERT encoder, allowing for efficient adaptation of pre-trained models. It shows how to configure LoRA parameters like rank (r), alpha, dropout, and target modules. ```python encoder_lora = MERT_v1_95M_Encoder( train_mode="lora", lora_r=8, lora_alpha=16, lora_dropout=0.1, lora_target_modules=["q_proj", "v_proj"] ) # Larger 330M variant encoder_large = MERT_v1_330M_Encoder(train_mode="freeze") # Output shape: (batch, seq_len, 1024) with 24 transformer layers ``` -------------------------------- ### Configure External Encoder (YAML) Source: https://github.com/a43992899/marble/blob/main/README.md For external encoders, place your encoder file (e.g., `my_encoder.py`) anywhere in your project and reference it using its full import path in the YAML configuration. ```yaml model:\n encoder:\n class_path: my_project.my_encoder.MyEncoder\n init_args:\n arg1: 123\n ``` -------------------------------- ### Configure Internal Encoder (YAML) Source: https://github.com/a43992899/marble/blob/main/README.md Reference your custom internal encoder in the YAML configuration file by specifying the `class_path` and any necessary `init_args` for the encoder. ```yaml model:\n encoder:\n class_path: marble.encoders.my_encoder.MyEncoder\n init_args:\n arg1: 123\n arg2: 456\n ``` -------------------------------- ### Key Prediction Inference API (Bash) Source: https://context7.com/a43992899/marble/llms.txt Command-line interface for performing key prediction on audio files using a pretrained MuQ-based model. It handles batch processing, slice aggregation, and outputs predictions in JSONL format. ```bash # Create a file list with audio paths cat > filelist.txt << EOF /path/to/song1.mp3 /path/to/song2.wav /path/to/song3.flac EOF # Run key prediction python sota/predict_key.py \ --filelist_path filelist.txt \ --output_path predictions.jsonl \ --batch_size 16 \ --download_dir output/key_sota_20250618 \ --min_clip_ratio 0.2 # Output format (predictions.jsonl): # {"audio_path": "/path/to/song1.mp3", "prediction": "C major", "confidence": 0.92} # {"audio_path": "/path/to/song2.wav", "prediction": "A minor", "confidence": 0.87} # Reproduce training/testing (requires dataset access) bash sota/reproduce_key_sota_20250618.sh ``` -------------------------------- ### Configure Internal Task (YAML) Source: https://github.com/a43992899/marble/blob/main/README.md Reference your custom internal task classes in the YAML configuration by specifying the `class_path` for both the task and the data module, along with any required `init_args`. ```yaml task:\n class_path: marble.tasks.YourTask.probe.YourTask\n init_args:\n sample_rate: 22050\n use_ema: false\n\ndata:\n class_path: marble.tasks.YourTask.datamodule.YourDataModule\n ``` -------------------------------- ### Configure External Task (YAML) Source: https://github.com/a43992899/marble/blob/main/README.md For external tasks, place your task and datamodule code files anywhere in your project and reference them using their full import paths in the YAML configuration. ```yaml model:\n class_path: my_project.probe.CustomTask\n\ndata:\n class_path: my_project.datamodule.CustomDataModule\n ``` -------------------------------- ### Audio Genre Classification Configuration (YAML) Source: https://context7.com/a43992899/marble/llms.txt Configuration file for training an audio genre classification model using MERT-v1-95M encoder and PyTorch Lightning. It specifies model architecture, data loading, training parameters, and evaluation metrics. ```yaml seed_everything: 1234 trainer: accelerator: gpu devices: [0] precision: bf16 max_epochs: 100 accumulate_grad_batches: 8 callbacks: - class_path: lightning.pytorch.callbacks.ModelCheckpoint init_args: dirpath: "./output/checkpoints/" filename: "best" save_top_k: 1 monitor: "val/acc" mode: "max" - class_path: lightning.pytorch.callbacks.EarlyStopping init_args: monitor: "val/acc" patience: 20 mode: "max" logger: class_path: lightning.pytorch.loggers.WandbLogger init_args: project: "marble" name: "probe.GTZANGenre.MERT-v1-95M" model: class_path: marble.tasks.GTZANGenre.probe.ProbeAudioTask init_args: sample_rate: 24000 use_ema: false encoder: class_path: marble.encoders.MERT.model.MERT_v1_95M_Encoder init_args: train_mode: freeze # freeze, lora, or full emb_transforms: - class_path: marble.modules.transforms.LayerSelector init_args: layers: [7] - class_path: marble.modules.transforms.TimeAvgPool decoders: - class_path: marble.modules.decoders.MLPDecoder init_args: in_dim: 768 out_dim: 10 hidden_layers: [512] activation_fn: class_path: torch.nn.ReLU dropout: 0.2 losses: - class_path: torch.nn.CrossEntropyLoss metrics: train: acc: class_path: torchmetrics.Accuracy init_args: num_classes: 10 task: multiclass val: acc: class_path: torchmetrics.Accuracy init_args: num_classes: 10 task: multiclass test: acc: class_path: torchmetrics.Accuracy init_args: num_classes: 10 task: multiclass data: class_path: marble.tasks.GTZANGenre.datamodule.GTZANGenreDataModule init_args: batch_size: 8 num_workers: 8 audio_transforms: train: - class_path: marble.encoders.MERT.model.MERT_v1_95M_FeatureExtractor val: - class_path: marble.encoders.MERT.model.MERT_v1_95M_FeatureExtractor test: - class_path: marble.encoders.MERT.model.MERT_v1_95M_FeatureExtractor train: class_path: marble.tasks.GTZANGenre.datamodule.GTZANGenreAudioTrain init_args: sample_rate: 24000 channels: 1 clip_seconds: 30.2 jsonl: data/GTZAN/GTZANGenre.train.jsonl val: class_path: marble.tasks.GTZANGenre.datamodule.GTZANGenreAudioVal init_args: sample_rate: 24000 channels: 1 clip_seconds: 30.2 jsonl: data/GTZAN/GTZANGenre.val.jsonl test: class_path: marble.tasks.GTZANGenre.datamodule.GTZANGenreAudioTest init_args: sample_rate: 24000 channels: 1 clip_seconds: 30.2 jsonl: data/GTZAN/GTZANGenre.test.jsonl optimizer: class_path: torch.optim.Adam init_args: lr: 5e-3 lr_scheduler: class_path: lightning.pytorch.cli.ReduceLROnPlateau init_args: mode: "max" factor: 0.5 patience: 6 monitor: "val/acc" ``` -------------------------------- ### BaseTask API Source: https://context7.com/a43992899/marble/llms.txt The BaseTask API allows users to create custom machine learning tasks by extending the `BaseTask` class. It orchestrates the encoder-decoder pipeline, supports multiple loss functions, per-split metrics, and optional EMA on encoder weights. ```APIDOC ## BaseTask API Create custom tasks by extending `BaseTask` with encoder, transforms, decoders, and metrics. `BaseTask` is the core Lightning module that orchestrates the encoder-decoder pipeline with support for multiple loss functions, per-split metrics, and optional EMA on encoder weights. It handles training, validation, and test steps with automatic logging. ### Class Definition `class BaseTask(pytorch_lightning.LightningModule)` ### `__init__` Method **Description**: Initializes the `BaseTask`. **Parameters**: - `encoder` (nn.Module) - Required - The encoder module. - `emb_transforms` (list) - Required - A list of embedding transformation modules. - `decoders` (list) - Required - A list of decoder modules. - `losses` (list) - Required - A list of loss functions. - `metrics` (dict) - Required - A dictionary mapping split names (e.g., 'train', 'val', 'test') to dictionaries of metrics. - `sample_rate` (int) - Optional - The sample rate of the audio data (default: 24000). - `use_ema` (bool) - Optional - Whether to use Exponential Moving Average on encoder weights (default: False). ### Request Example (Custom Task Creation) ```python import torch import torch.nn as nn import pytorch_lightning from marble.core.base_task import BaseTask from torchmetrics import Accuracy from marble.encoders.MERT.model import MERT_v1_95M_Encoder from marble.modules.transforms import LayerSelector, TimeAvgPool from marble.modules.decoders import MLPDecoder # Define a custom task by subclassing BaseTask class CustomMusicTask(BaseTask): def __init__( self, encoder: nn.Module, emb_transforms: list, decoders: list, losses: list, metrics: dict, sample_rate: int = 24000, use_ema: bool = False ): super().__init__( encoder=encoder, emb_transforms=emb_transforms, decoders=decoders, losses=losses, metrics=metrics, sample_rate=sample_rate, use_ema=use_ema ) # Usage with configuration (typically via YAML) encoder = MERT_v1_95M_Encoder(train_mode="freeze") transforms = [LayerSelector(layers=[7]), TimeAvgPool()] decoder = MLPDecoder(in_dim=768, out_dim=10) loss_fn = nn.CrossEntropyLoss() metrics = { "train": {"acc": Accuracy(num_classes=10, task="multiclass")}, "val": {"acc": Accuracy(num_classes=10, task="multiclass")}, "test": {"acc": Accuracy(num_classes=10, task="multiclass")} } task = CustomMusicTask( encoder=encoder, emb_transforms=transforms, decoders=[decoder], losses=[loss_fn], metrics=metrics, use_ema=False ) # Forward pass audio = torch.randn(4, 24000 * 5) # Batch of 4, 5 seconds of audio logits = task(audio) print(logits.shape) # Expected output shape: (4, 10) ``` ### Response Example (Forward Pass) ```json { "output_shape": "[4, 10]" } ``` ``` -------------------------------- ### Implement Internal Task (Python) Source: https://github.com/a43992899/marble/blob/main/README.md To add a new task internally, create a new package under `marble/tasks/YourTask/` containing `datamodule.py` and `probe.py`. Implement subclasses for `pl.LightningDataModule` and `BaseTask` respectively. ```python # datamodule.py\nimport pytorch_lightning as pl\n\nclass YourDataModule(pl.LightningDataModule):\n def setup(self, stage=None):\n ...\n def train_dataloader(self):\n ...\n # val_dataloader, test_dataloader, etc.\n\n# probe.py\nfrom marble.core.base_task import BaseTask\n\nclass YourTask(BaseTask):\n def __init__(self, encoder, emb_transforms, decoders, losses, metrics, sample_rate, use_ema):\n super().__init__(...)\n # custom behavior here\n ```