A small (682K param) convolutional model that predicts pitch (F0) and volume (RMS) contours from text at 100ms resolution.
Text -> CharEncoder (4x Conv1d) -> DurationPredictor (2x Conv1d, detached)
-> LengthRegulator (repeat by durations)
-> FrameDecoder (3x Conv1d) -> [F0, RMS]
1 import torch
2 from model_prosody import ProsodyPredictor
3 from infer_prosody import predict_prosody
4
5 ckpt = torch . load ( "final_model.pt" , map_location = "cpu" , weights_only = False )
6 model = ProsodyPredictor ( vocab_size = ckpt [ "vocab_size" ] , d_model = 128 , dropout = 0.0 )
7 model . load_state_dict ( ckpt [ "model" ] )
8 model . eval ( )
9
10 result = predict_prosody ( "Hello, I am Kobi AI" , model , ckpt [ "norm_stats" ] )
11 # result["f0_hz"] - pitch in Hz per 100ms frame
12 # result["rms"] - volume per 100ms frame
13 # result["duration_s"] - total duration in seconds
1 import numpy as np
2 import soundfile as sf
3 from scipy . interpolate import CubicSpline
4
5 f0 = result [ "f0_hz" ]
6 rms = result [ "rms" ]
7 sr = 24000
8 frame_dur = 0.1
9 n_frames = len ( f0 )
10 total_samples = int ( n_frames * frame_dur * sr )
11
12 # Smooth interpolation between frames
13 frame_times = ( np . arange ( n_frames ) + 0.5 ) * frame_dur
14 sample_times = np . arange ( total_samples ) / sr
15 f0_smooth = np . clip ( CubicSpline ( frame_times , f0 , bc_type = 'clamped' ) ( sample_times ) , 50 , 300 )
16 rms_smooth = np . clip ( CubicSpline ( frame_times , rms , bc_type = 'clamped' ) ( sample_times ) , 0 , None )
17
18 # Generate with continuous phase
19 phase = np . cumsum ( 2 * np . pi * f0_smooth / sr )
20 audio = ( rms_smooth * np . sin ( phase ) ) . astype ( np . float32 )
21 audio = audio / ( np . abs ( audio ) . max ( ) + 1e-8 ) * 0.8
22 sf . write ( "output.wav" , audio , sr )