CoolFace
Modelpublic

scrapegoat/Neural-Audio-Codec

sourceHugging Faceupdated 2y agoView on Hugging Face
2likes
vocoder.py184 linesDownload Raw Back to root
1import argparse2import os3from pathlib import Path4import sys5import torchaudio6import numpy as np7from time import time8import torch9import typing as tp10from omegaconf import OmegaConf11from vocos import VocosDecoder12from models.soundstream_hubert_new import SoundStream13from tqdm import tqdm14 15def build_soundstream_model(config):16    model = eval(config.generator.name)(**config.generator.config)17    return model18 19def build_codec_model(config_path, vocal_decoder_path, inst_decoder_path):20    vocal_decoder = VocosDecoder.from_hparams(config_path=config_path)21    vocal_decoder.load_state_dict(torch.load(vocal_decoder_path))22    inst_decoder = VocosDecoder.from_hparams(config_path=config_path)23    inst_decoder.load_state_dict(torch.load(inst_decoder_path))24    return vocal_decoder, inst_decoder25 26def save_audio(wav: torch.Tensor, path: tp.Union[Path, str], sample_rate: int, rescale: bool = False):27    limit = 0.9928    mx = wav.abs().max()29    if rescale:30        wav = wav * min(limit / mx, 1)31    else:32        wav = wav.clamp(-limit, limit)33    34    path = str(Path(path).with_suffix('.mp3'))35    torchaudio.save(path, wav, sample_rate=sample_rate)36 37def process_audio(input_file, output_file, rescale, args, decoder, soundstream):38    compressed = np.load(input_file, allow_pickle=True).astype(np.int16)39    print(f"Processing {input_file}")40    print(f"Compressed shape: {compressed.shape}")41    42    args.bw = float(4)43    compressed = torch.as_tensor(compressed, dtype=torch.long).unsqueeze(1)44    compressed = soundstream.get_embed(compressed.to(f"cuda:{args.cuda_idx}"))45    compressed = torch.tensor(compressed).to(f"cuda:{args.cuda_idx}")46    47    start_time = time()48    with torch.no_grad():49        decoder.eval()50        decoder = decoder.to(f"cuda:{args.cuda_idx}")51        out = decoder(compressed)52        out = out.detach().cpu()53    duration = time() - start_time54    rtf = (out.shape[1] / 44100.0) / duration55    print(f"Decoded in {duration:.2f}s ({rtf:.2f}x RTF)")56    57    os.makedirs(os.path.dirname(output_file), exist_ok=True)58    save_audio(out, output_file, 44100, rescale=rescale)59    print(f"Saved: {output_file}")60    return out61 62def find_matching_pairs(input_folder):63    if str(input_folder).endswith('.lst'):  # Convert to string64        with open(input_folder, 'r') as file:65            files = [line.strip() for line in file if line.strip()]66    else:67        files = list(Path(input_folder).glob('*.npy'))68    print(f"found {len(files)} npy.")69    instrumental_files = {}70    vocal_files = {}71    72    for file in files:73        if not isinstance(file, Path):74            file = Path(file)75        name = file.stem76        if 'instrumental' in name.lower():77            base_name = name.lower().replace('instrumental', '')#.strip('_')78            instrumental_files[base_name] = file79        elif 'vocal' in name.lower():80            # base_name = name.lower().replace('vocal', '').strip('_')81            last_index = name.lower().rfind('vocal')82            if last_index != -1:83                # Create a new string with the last 'vocal' removed84                base_name = name.lower()[:last_index] + name.lower()[last_index + len('vocal'):]85            else:86                base_name = name.lower()87            vocal_files[base_name] = file88    89    # Find matching pairs90    pairs = []91    for base_name in instrumental_files.keys():92        if base_name in vocal_files:93            pairs.append((94                instrumental_files[base_name],95                vocal_files[base_name],96                base_name97            ))98    99    return pairs100 101def main():102    parser = argparse.ArgumentParser(description='High fidelity neural audio codec using Vocos decoder.')103    parser.add_argument('--input_folder', type=Path, required=True, help='Input folder containing NPY files.')104    parser.add_argument('--output_base', type=Path, required=True, help='Base output folder.')105    parser.add_argument('--resume_path', type=str, default='./final_ckpt/ckpt_00360000.pth', help='Path to model checkpoint.')106    parser.add_argument('--config_path', type=str, default='./config.yaml', help='Path to Vocos config file.')107    parser.add_argument('--vocal_decoder_path', type=str, default='/aifs4su/mmcode/codeclm/xcodec_mini_infer_newdecoder/decoders/decoder_131000.pth', help='Path to Vocos decoder weights.')108    parser.add_argument('--inst_decoder_path', type=str, default='/aifs4su/mmcode/codeclm/xcodec_mini_infer_newdecoder/decoders/decoder_151000.pth', help='Path to Vocos decoder weights.')109    parser.add_argument('-r', '--rescale', action='store_true', help='Rescale output to avoid clipping.')110    args = parser.parse_args()111 112    # Validate inputs113    if not args.input_folder.exists():114        sys.exit(f"Input folder {args.input_folder} does not exist.")115    if not os.path.isfile(args.config_path):116        sys.exit(f"{args.config_path} file does not exist.")117    # if not os.path.isfile(args.decoder_path):118    #     sys.exit(f"{args.decoder_path} file does not exist.")119 120    # Create output directories121    mix_dir = args.output_base / 'mix'122    stems_dir = args.output_base / 'stems'123    os.makedirs(mix_dir, exist_ok=True)124    os.makedirs(stems_dir, exist_ok=True)125 126    # Initialize models127    config_ss = OmegaConf.load("./final_ckpt/config.yaml")128    soundstream = build_soundstream_model(config_ss)129    parameter_dict = torch.load(args.resume_path)130    soundstream.load_state_dict(parameter_dict['codec_model'])131    soundstream.eval()132    133    vocal_decoder, inst_decoder = build_codec_model(args.config_path, args.vocal_decoder_path, args.inst_decoder_path)134    135    # Find and process matching pairs136    pairs = find_matching_pairs(args.input_folder)137    print(f"Found {len(pairs)} matching pairs")138    pairs = [p for p in pairs if not os.path.exists(mix_dir / f'{p[2]}.mp3')]139    print(f"{len(pairs)} to reconstruct...")140    141    for instrumental_file, vocal_file, base_name in tqdm(pairs):142        print(f"\nProcessing pair: {base_name}")143        # Create stems directory for this song144        song_stems_dir = stems_dir / base_name145        os.makedirs(song_stems_dir, exist_ok=True)146        147        try:148            # Process instrumental149            instrumental_output = process_audio(150                instrumental_file,151                song_stems_dir / 'instrumental.mp3',152                args.rescale,153                args,154                inst_decoder,155                soundstream156            )157            158            # Process vocal159            vocal_output = process_audio(160                vocal_file,161                song_stems_dir / 'vocal.mp3',162                args.rescale,163                args,164                vocal_decoder,165                soundstream166            )167        except IndexError as e:168            print(e)169            continue170        171        # Create and save mix172        try:173            mix_output = instrumental_output + vocal_output174            save_audio(mix_output, mix_dir / f'{base_name}.mp3', 44100, args.rescale)175            print(f"Created mix: {mix_dir / f'{base_name}.mp3'}")176        except RuntimeError as e:177            print(e)178            print(f"mix {base_name} failed! inst: {instrumental_output.shape}, vocal: {vocal_output.shape}")179 180if __name__ == '__main__':181    main()182 183    # Example Usage184    # python reconstruct_separately.py --input_folder test_samples --output_base test