nx_voice_clone_synth.nx source
↩ module page · 78 lines · 2967 B
1// nx_voice_clone_synth.nx -- VOICE-CLONE-001 rung 2: CREATE new audio in a
2// captured voice. Given a voiceprint (the speaker's vocal-tract filter, as
3// averaged PARCOR) this drives that filter with a glottal source at ANY
4// requested pitch -> voiced speech in that speaker's voice, at that pitch.
5//
6// This is the "create voices" half of duplicate-and-create. Because pitch is
7// the SOURCE and identity is the FILTER, the same voiceprint can read at a low
8// pitch, a high pitch, or sing a melody (the singing-teacher tie-in) while
9// staying recognizably the same speaker.
10//
11// Pipeline (no reinvention): PARCOR k (Q30, from nx_voiceprint) --step-up
12// recursion--> LPC predictor a (Q30) ; glottal impulse train at fs/F0 as the
13// residual --> nx_lpc_synth(residual, a) --> i16 PCM.
14//
15// license_tier: ORIGINAL
16// module: nishi-core.audio.voice_clone_synth
17// depends: nishi-core.voice.lpc_synth, nishi-core.audio.voiceprint
18// capability: AUDIO_VOICE_SYNTH
19import "nx_syscalls_x86_64.nx"
20import "nx_lpc_synth.nx"
21import "nx_voiceprint.nx"
22
23const CS_AMP: i64 = 6000 // glottal impulse amplitude (excitation)
24
25// step-up recursion: PARCOR k[1..p] (in vp[2..]) -> LPC predictor a[1..p].
26// Both Q30. Writes a_out as p i64-LE words. Stable because |k|<1 is
27// preserved by averaging convex combinations of stable frames.
28func _cs_stepup(vp: *i64, p: i64, a_out: *u8) -> i64 {
29 let acur: *i64 = sys_mmap(p * 8) as *i64
30 let anew: *i64 = sys_mmap(p * 8) as *i64
31 acur[0] = vp[2] // a_1 = k_1
32 var i: i64 = 2
33 while i <= p {
34 let ki: i64 = vp[2 + (i - 1)] // k_i
35 var j: i64 = 1
36 while j <= i - 1 {
37 anew[j - 1] = acur[j - 1] + ((ki * acur[(i - j) - 1]) >> 30)
38 j = j + 1
39 }
40 anew[i - 1] = ki
41 var c: i64 = 0
42 while c < i { acur[c] = anew[c]; c = c + 1 }
43 i = i + 1
44 }
45 var w: i64 = 0
46 while w < p {
47 let v: i64 = acur[w]
48 var b: i64 = 0
49 while b < 8 {
50 a_out[w * 8 + b] = ((v >> (b * 8)) & 0xff) as u8
51 b = b + 1
52 }
53 w = w + 1
54 }
55 return 0
56}
57
58// nx_voice_clone_synth -- synthesize n_samples of voiced speech in the
59// voiceprint's vocal tract at f0_hz. out_pcm: i16-LE (caller mmaps, zeroed).
60func nx_voice_clone_synth(vp: *i64, f0_hz: i64, fs: i64,
61 n_samples: i64, out_pcm: *u8) -> i64 {
62 let a_buf: *u8 = sys_mmap(VP_ORDER * 8)
63 _cs_stepup(vp, VP_ORDER, a_buf)
64
65 // glottal impulse train residual at period = fs / f0
66 let e_pcm: *u8 = sys_mmap(n_samples * 2) // mmap zero-filled
67 var period: i64 = fs / f0_hz
68 if period < 1 { period = 1 }
69 var i: i64 = 0
70 while i < n_samples {
71 let x: i64 = CS_AMP
72 e_pcm[i * 2] = (x & 0xff) as u8
73 e_pcm[i * 2 + 1] = ((x >> 8) & 0xff) as u8
74 i = i + period
75 }
76 nx_lpc_synth(e_pcm, n_samples, a_buf, VP_ORDER, out_pcm)
77 return 0
78}