| """ |
| TokenInterpolator: Upsample low-rate (25/12 TPS) token embeddings back to 50 TPS. |
| |
| The original 50 TPS NeuCodec codebook is completely frozen and unchanged. |
| This module operates purely in embedding space (after quantizer lookup). |
| |
| Encode at 25 TPS: |
| audio -> NeuCodec.encode_code() -> 50 TPS codes [B, 1, T] |
| -> take every 2nd token -> 25 TPS codes [B, 1, T//2] |
| |
| Decode from 25 TPS: |
| 25 TPS codes -> quantizer.get_output_from_indices -> 25 TPS embeddings [B, T//2, 1024] |
| -> TokenInterpolator(factor=2) -> 50 TPS embeddings [B, T, 1024] |
| -> NeuCodec decoder backbone + ISTFT -> audio |
| |
| Training: |
| Freeze entire NeuCodec. Only train TokenInterpolator. |
| Loss: MSE on the predicted (odd-position) embeddings vs the true 50 TPS embeddings. |
| Optional: reconstruction loss via frozen decoder. |
| """ |
|
|
| import torch |
| import torch.nn as nn |
| from torchtune.modules import RotaryPositionalEmbeddings |
| from .bs_roformer5 import TransformerBlock |
|
|
|
|
| class TokenInterpolator(nn.Module): |
| """ |
| Upsamples from low-rate token embeddings to 50 TPS embeddings. |
| |
| Args: |
| dim: embedding dimension (1024, matching fc_post_a output) |
| factor: upsample factor β 2 for 25->50 TPS, 4 for 12->50 TPS |
| depth: number of transformer layers |
| heads: attention heads |
| """ |
|
|
| def __init__(self, dim: int = 1024, factor: int = 2, depth: int = 4, heads: int = 8): |
| super().__init__() |
| assert factor in (2, 4), "factor must be 2 (25 TPS) or 4 (12 TPS)" |
| self.factor = factor |
| self.dim = dim |
|
|
| |
| |
| self.sub_pos_embed = nn.Embedding(factor, dim) |
|
|
| rotary_embed = RotaryPositionalEmbeddings(dim=64) |
| self.transformer = nn.Sequential(*[ |
| TransformerBlock(dim=dim, n_heads=heads, rotary_embed=rotary_embed) |
| for _ in range(depth) |
| ]) |
| self.norm = nn.LayerNorm(dim) |
|
|
| def forward(self, x: torch.Tensor) -> torch.Tensor: |
| """ |
| Args: |
| x: [B, T_low, dim] β embeddings at 25 or 12 TPS |
| |
| Returns: |
| out: [B, T_low * factor, dim] β embeddings at 50 TPS |
| """ |
| B, T, D = x.shape |
|
|
| |
| |
| x = x.unsqueeze(2).expand(B, T, self.factor, D).reshape(B, T * self.factor, D) |
|
|
| |
| |
| sub_idx = torch.arange(self.factor, device=x.device).repeat(T) |
| x = x + self.sub_pos_embed(sub_idx) |
|
|
| x = self.transformer(x) |
| x = self.norm(x) |
| return x |
|
|
|
|
| def encode_low_rate(neucodec, audio, factor: int = 2) -> torch.Tensor: |
| """ |
| Encode audio to low-rate codes. |
| |
| Returns: |
| codes: [B, 1, T//factor] integer token indices |
| """ |
| codes = neucodec.encode_code(audio) |
| codes = codes[:, :, ::factor] |
| return codes |
|
|
|
|
| def decode_low_rate(neucodec, interpolator: TokenInterpolator, codes: torch.Tensor) -> torch.Tensor: |
| """ |
| Decode low-rate codes back to 48kHz audio via interpolation. |
| |
| Args: |
| codes: [B, 1, T_low] β 25 or 12 TPS codes |
| |
| Returns: |
| audio: [B, 1, T_audio] β 48kHz audio |
| """ |
| |
| emb = neucodec.generator.quantizer.get_output_from_indices(codes.transpose(1, 2)) |
| |
| emb = neucodec.fc_post_a(emb) |
|
|
| |
| emb = interpolator(emb) |
|
|
| |
| audio, _ = neucodec.generator(emb, vq=False) |
| return audio |
|
|