Source code for medleydb.mix

"""Functions for creating new mixes from medleydb multitracks.
"""
import shutil
import sox


VOCALS = ["male singer", "female singer", "male speaker", "female speaker",
          "male rapper", "female rapper", "beatboxing", "vocalists"]


[docs]def mix_multitrack(mtrack, output_path, stem_indices=None, alternate_weights=None, alternate_files=None, additional_files=None): """Mix the stems of a multitrack to create a new mix. Can optionally adjust the volume of stems and replace, remove, or add stems. Parameters ---------- mtrack : Multitrack Multitrack object output_path : str Path to save output file. stem_indices : list or None, default=None stem indices to include in mix. If None, mixes all stems alternate_weights : dict or None, default=None Dictionary with stem indices as keys and mixing coefficients as values. Stem indices present that are not in this dictionary will use the default estimated mixing coefficient. alternate_files : dict or None, default=None Dictionary with stem indices as keys and filepaths as values. Audio file to use in place of original stem. Stem indices present that are not in this dictionary will use the original stems. additional_files : list of tuple or None, default=None List of tuples of (filepath, mixing_coefficient) pairs to additionally add to final mix. Returns ------- filepaths : list List of filepaths used in the mix weights : list List of weights used to mix filepaths """ filepaths, weights = _build_mix_args( mtrack, stem_indices, alternate_weights, alternate_files, additional_files ) if len(filepaths) == 1: shutil.copyfile(filepaths[0], output_path) else: cbn = sox.Combiner() cbn.build( filepaths, output_path, 'mix', input_volumes=weights ) return filepaths, weights
def _build_mix_args(mtrack, stem_indices, alternate_weights, alternate_files, additional_files): """Create lists of filepaths and weights to use in final mix. Parameters ---------- mtrack : Multitrack Multitrack object stem_indices : list stem indices to include in mix. If None, mixes all stems alternate_weights : dict Dictionary with stem indices as keys and mixing coefficients as values. Stem indices present that are not in this dictionary will use the default estimated mixing coefficient. alternate_files : dict Dictionary with stem indices as keys and filepaths as values. Audio file to use in place of original stem. Stem indices present that are not in this dictionary will use the original stems. additional_files : list of tuples List of tuples of (filepath, mixing_coefficient) pairs to additionally add to final mix. Returns ------- filepaths : list List of filepaths that were included in mix. weights : list List of weights that were used in mix """ if stem_indices is None: stem_indices = list(mtrack.stems.keys()) if alternate_files is None: alternate_files = {} if alternate_weights is None: alternate_weights = {} weights = [] filepaths = [] for index in stem_indices: if index in alternate_files.keys(): filepaths.append(alternate_files[index]) else: filepaths.append(mtrack.stems[index].audio_path) if index in alternate_weights.keys(): weights.append(alternate_weights[index]) else: if mtrack.stems[index].mixing_coefficient is not None: mix_coeff = mtrack.stems[index].mixing_coefficient else: print( '[Warning] Multitrack does not have mixing coefficients. ' 'Using uniform stem weights.' ) mix_coeff = 1 weights.append(mix_coeff) if additional_files is not None: for fpath, weight in additional_files: filepaths.append(fpath) weights.append(weight) return filepaths, weights
[docs]def mix_melody_stems(mtrack, output_path, max_melody_stems=None, include_percussion=False, require_mono=False): """Creates a mix using only the stems labeled as melody. Parameters ---------- mtrack : Multitrack Multitrack object output_path : str Path to save output wav file. max_melody_stems : int or None, default=None The maximum number of melody stems to mix. If None, uses the number of melody stems in the mix. include_percussion : bool, default=False If true, adds percussion stems to the mix. require_mono : bool, default=False If true, only includes melody stems that are monophonic instruments. Returns ------- melody_indices : list List of selected melody indices. stem_indices : list List of stem indices used in mix. """ if max_melody_stems is None: max_melody_stems = 100 melody_rankings = mtrack.melody_rankings inverse_ranking = {v: k for k, v in melody_rankings.items()} n_melody_stems = len(list(melody_rankings.keys())) stem_indices = [] melody_indices = [] n_chosen = 0 for i in range(1, n_melody_stems + 1): if n_chosen >= max_melody_stems: break this_stem_index = inverse_ranking[i] if require_mono: mono = all([ f0_type == 'm' for f0_type in mtrack.stems[this_stem_index].f0_type ]) if mono: stem_indices.append(this_stem_index) melody_indices.append(this_stem_index) n_chosen += 1 else: stem_indices.append(this_stem_index) melody_indices.append(this_stem_index) n_chosen += 1 if include_percussion: percussive_indices = [ i for i, s in mtrack.stems.items() if s.f0_type == 'u' ] for i in percussive_indices: stem_indices.append(i) mix_multitrack(mtrack, output_path, stem_indices=stem_indices) return melody_indices, stem_indices
[docs]def mix_mono_stems(mtrack, output_path, include_percussion=False): """Creates a mix using only the stems that are monophonic. For example, in mix with piano, voice, and clarinet, the resulting mix would include only voice and clarinet. Parameters ---------- mtrack : Multitrack Multitrack object output_path : str Path to save output wav file. include_percussion : bool, default=False If true, percussive instruments are included in the mix. If false, they are excluded. Returns ------- mono_indices : list List of stem indices containing monophonic instruments. stem_indices : list List of stem indices used in mix. """ stems = mtrack.stems stem_indices = [] mono_indices = [] for i in stems.keys(): mono = all([ f0_type == 'm' for f0_type in mtrack.stems[i].f0_type ]) unvoiced = all([ f0_type == 'u' for f0_type in mtrack.stems[i].f0_type ]) if mono: stem_indices.append(i) mono_indices.append(i) elif include_percussion and unvoiced: stem_indices.append(i) mix_multitrack(mtrack, output_path, stem_indices=stem_indices) return mono_indices, stem_indices
[docs]def mix_no_vocals(mtrack, output_path): """Remixes a multitrack with anything type of vocals removed. If no vocals are present, the mix will be a simple weighted linear remix. Parameters ---------- mtrack : Multitrack Multitrack object output_path : str Path to save output file. Returns ------- stem_indices : list List of stem indices used in mix. """ stems = mtrack.stems stem_indices = [] for i in stems.keys(): not_vocal = all([inst not in VOCALS for inst in stems[i].instrument]) if not_vocal: stem_indices.append(i) mix_multitrack(mtrack, output_path, stem_indices=stem_indices) return stem_indices
[docs]def remix_vocals(mtrack, output_path, vocals_scale): """Remixes a multitrack, changing the volume of the vocals. Parameters ---------- mtrack : Multitrack Multitrack object output_path : str Path to save output wav file. vocals_scale : float The target scale factor for vocals. A value of 1 keeps the volume the same. Values above 1 increase the volume and below 1 decrease it. Returns ------- alternate_weights : dict Dictionary of vocal weights keyed by vocal stem index. """ stems = mtrack.stems alternate_weights = {} for i in stems.keys(): vocal = any([inst in VOCALS for inst in stems[i].instrument]) if vocal: vocal_weight = stems[i].mixing_coefficient * vocals_scale alternate_weights[i] = vocal_weight mix_multitrack( mtrack, output_path, alternate_weights=alternate_weights ) return alternate_weights