Spaces:
Running on Zero
Running on Zero
File size: 3,001 Bytes
5ed07ee | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 | """
Text tensor construction for SVS preprocessing.
Provides :func:`build_text_tensor` (used during preprocessing and inference)
and :func:`estimate_duration_from_notes` (used for sorting / duration estimation).
"""
from typing import Dict, List
import torch
def estimate_duration_from_notes(notes: List[str], bpm: int) -> float:
"""
Estimate audio duration from the raw note token list and BPM.
Each entry in `notes` corresponds to one pitch segment, so we just
sum up their durations directly — no need to go through syllables.
Args:
notes: Raw list of note token strings, e.g. ["<NOTE_4>", "<NOTE_8>", ...]
bpm: Beats per minute
Returns:
Estimated duration in seconds
"""
from vocalrender.model.svs_utils import get_svs_token_maps
_, _, _, dur_units, _ = get_svs_token_maps()
# Calculate seconds per quarter note
unit_duration = 60.0 / bpm
# Sum up all note durations
total_units = sum(dur_units.get(n, 1.0) for n in notes)
return total_units * unit_duration
def build_text_tensor(
syllables: List[Dict], bpm: int, tokenizer, has_score: bool = True,
) -> torch.Tensor:
"""Build text tensor from syllables.
For weak-label samples (has_score=False), syllables only have 'char' field.
pitch/note/bpm columns are filled with 0.
"""
from vocalrender.training.svs_data import expand_syllables
from vocalrender.model.svs_utils import get_svs_token_maps
_, note_tokens_list, _, _, _ = get_svs_token_maps()
if has_score:
expanded = expand_syllables(syllables)
else:
# Weak label: no melisma, just enumerate words
expanded = [{'char': s['char'], 'word_idx': i} for i, s in enumerate(syllables)]
if not expanded:
return torch.zeros((0, 5), dtype=torch.long)
# Tokenize all chars
tokenized = []
max_text_tokens = 1
for syl in expanded:
char = syl['char']
ids = tokenizer.encode(char, add_special_tokens=False)
tokenized.append(ids)
max_text_tokens = max(max_text_tokens, len(ids))
# Build rows
# Layout: [text_ids..., pitch, note_idx, bpm, word_idx]
rows = []
for i, syl in enumerate(expanded):
ids = tokenized[i]
padded_ids = ids + [0] * (max_text_tokens - len(ids))
word_idx = int(syl.get('word_idx', i))
if has_score:
pitch = int(syl['pitch'])
note_str = syl['note']
# Find note token index in the list
if isinstance(note_str, str) and note_str in note_tokens_list:
note_idx = note_tokens_list.index(note_str)
else:
note_idx = 6 # Default to <NOTE_4> which is index 6 in the list
else:
# Weak label: pitch/note are unknown, fill with 0
pitch = 0
note_idx = 0
row = padded_ids + [pitch, note_idx, bpm, word_idx]
rows.append(row)
return torch.tensor(rows, dtype=torch.long)
|