1use anyhow::{Context, Result, bail, ensure};
9use siglus_assets::vorbis::{Pcm16, pcm16_to_wav_bytes};
10
11const JITAN_RATE_NORMAL: i32 = 100;
12const JITAN_RATE_CONVERTER_MAX: i32 = 400;
13const JITAN_CLAMP_SAMPLE: i32 = 32_760;
14const JITAN_STEREO_SUM_CLAMP: i32 = 30_000;
15
16pub fn convert_koe_wav(wav: Vec<u8>, percent: u16) -> Result<Vec<u8>> {
23 if percent == JITAN_RATE_NORMAL as u16 {
24 return Ok(wav);
25 }
26
27 let pcm = parse_pcm16_wav(&wav)?;
28 let mono = match pcm.channels {
29 1 => pcm.samples,
30 2 => stereo_to_mono_original(&pcm.samples),
31 channels => {
32 bail!(
33 "Siglus JITAN only handles mono/stereo KOE before the mono converter; got {channels} channels"
34 )
35 }
36 };
37
38 let converted = convert_pcm16_original(&mono, pcm.sample_rate, percent as i32)
39 .unwrap_or_else(|| vec![0; mono.len()]);
43
44 Ok(pcm16_to_wav_bytes(&Pcm16 {
45 channels: 1,
46 sample_rate: pcm.sample_rate,
47 samples: converted,
48 }))
49}
50
51fn parse_pcm16_wav(wav: &[u8]) -> Result<Pcm16> {
52 ensure!(
53 wav.len() >= 12 && &wav[..4] == b"RIFF" && &wav[8..12] == b"WAVE",
54 "KOE JITAN requires RIFF/WAVE"
55 );
56
57 let mut pos = 12usize;
58 let mut format: Option<(u16, u16, u32, u16)> = None;
59 let mut data: Option<&[u8]> = None;
60 while pos + 8 <= wav.len() {
61 let tag = &wav[pos..pos + 4];
62 let size = u32::from_le_bytes(
63 wav[pos + 4..pos + 8]
64 .try_into()
65 .context("read WAV chunk size")?,
66 ) as usize;
67 pos += 8;
68 let end = pos.checked_add(size).context("WAV chunk size overflow")?;
69 ensure!(end <= wav.len(), "truncated WAV chunk");
70
71 if tag == b"fmt " {
72 ensure!(size >= 16, "short WAV fmt chunk");
73 format = Some((
74 u16::from_le_bytes(wav[pos..pos + 2].try_into()?),
75 u16::from_le_bytes(wav[pos + 2..pos + 4].try_into()?),
76 u32::from_le_bytes(wav[pos + 4..pos + 8].try_into()?),
77 u16::from_le_bytes(wav[pos + 14..pos + 16].try_into()?),
78 ));
79 } else if tag == b"data" && data.is_none() {
80 data = Some(&wav[pos..end]);
81 }
82
83 pos = end.saturating_add(size & 1);
84 }
85
86 let (format_tag, channels, sample_rate, bits) = format.context("missing WAV fmt chunk")?;
87 ensure!(
88 format_tag == 1,
89 "JITAN requires PCM WAV, got format {format_tag}"
90 );
91 ensure!(bits == 16, "JITAN requires 16-bit PCM, got {bits}-bit");
92 ensure!(channels != 0, "JITAN WAV has zero channels");
93 ensure!(sample_rate != 0, "JITAN WAV has zero sample rate");
94 let data = data.context("missing WAV data chunk")?;
95 let frame_bytes = channels as usize * 2;
96 ensure!(
97 data.len() % frame_bytes == 0,
98 "unaligned PCM16 KOE payload: {} bytes / {} channels",
99 data.len(),
100 channels
101 );
102
103 let samples = data
104 .as_chunks::<2>()
105 .0
106 .iter()
107 .map(|sample| i16::from_le_bytes([sample[0], sample[1]]))
108 .collect();
109 Ok(Pcm16 {
110 channels,
111 sample_rate,
112 samples,
113 })
114}
115
116fn stereo_to_mono_original(input: &[i16]) -> Vec<i16> {
117 input
118 .as_chunks::<2>()
119 .0
120 .iter()
121 .map(|pair| {
122 (pair[0] as i32 + pair[1] as i32).clamp(-JITAN_STEREO_SUM_CLAMP, JITAN_STEREO_SUM_CLAMP)
123 as i16
124 })
125 .collect()
126}
127
128fn convert_pcm16_original(input: &[i16], sample_rate: u32, percent: i32) -> Option<Vec<i16>> {
131 if input.is_empty() {
132 return None;
133 }
134
135 let percent = percent.clamp(JITAN_RATE_NORMAL, JITAN_RATE_CONVERTER_MAX);
136 let extra_percent = percent - JITAN_RATE_NORMAL;
137 let rate = usize::try_from(sample_rate).ok()?;
138 let play_block_smp = rate / 80;
139 let before_smp = rate / 110;
140 let after_smp = rate / 110;
141 let delete_block_smp = play_block_smp.saturating_mul(extra_percent as usize) / 100;
142
143 convert_func_original(
144 input,
145 rate,
146 play_block_smp,
147 delete_block_smp,
148 before_smp,
149 after_smp,
150 true,
151 true,
152 )
153}
154
155fn convert_func_original(
156 input: &[i16],
157 rate: usize,
158 play_block_smp: usize,
159 delete_block_smp: usize,
160 before_smp: usize,
161 after_smp: usize,
162 zero_flag: bool,
163 smooth_flag: bool,
164) -> Option<Vec<i16>> {
165 if input.len() < rate / 10 || play_block_smp == 0 {
167 return None;
168 }
169
170 let work_base_smp = play_block_smp;
171 let jump_smp = delete_block_smp;
172
173 let dst_bound = input.len();
176 let mut output = vec![0i16; input.len().saturating_add(rate)];
177 let center = before_smp;
178 let mut splice = vec![0i16; before_smp.saturating_add(after_smp)];
179 let mut splice_before_count = 0usize;
180 let mut splice_after_count = 0usize;
181
182 let mut dst = 0usize;
183 let mut src = 0usize;
184 let mut new_sample_count = 0usize;
185 let mut before_copy_smp = 0usize;
186
187 loop {
188 if src >= input.len() {
189 break;
190 }
191
192 if dst != 0 && zero_flag {
193 src = convert_func_16bit_rep(&output, dst - 1, input, src, work_base_smp, dst_bound);
194 }
195 if src >= input.len() {
196 break;
197 }
198
199 let mut copy_smp = if zero_flag {
200 convert_func_16bit_copy_size(input, src, work_base_smp)
201 } else {
202 work_base_smp
203 };
204 copy_smp = copy_smp.min(input.len() - src);
205
206 splice_before_count = before_smp.min(src);
207 if splice_before_count != 0 {
208 let from = src - splice_before_count;
209 let to = center;
210 splice[to - splice_before_count..to].copy_from_slice(&input[from..src]);
211 }
212
213 if output.len() < dst.saturating_add(copy_smp) {
214 output.resize(dst.saturating_add(copy_smp), 0);
215 }
216 if copy_smp != 0 {
217 output[dst..dst + copy_smp].copy_from_slice(&input[src..src + copy_smp]);
218 }
219
220 let mut smooth_len = 0i32;
221 if dst != 0 && smooth_flag {
222 smooth_len =
223 convert_func_smooth(&mut output, dst, before_copy_smp, copy_smp, dst_bound);
224 }
225 before_copy_smp = copy_smp;
226
227 if splice_before_count != 0 || splice_after_count != 0 {
228 convert_func_gousei(
229 &mut output,
230 dst,
231 dst_bound,
232 &mut splice,
233 center,
234 splice_before_count,
235 splice_after_count,
236 smooth_len,
237 );
238 }
239
240 splice_after_count = after_smp.min(input.len().saturating_sub(src + copy_smp));
241 if splice_after_count != 0 {
242 splice[center..center + splice_after_count]
243 .copy_from_slice(&input[src + copy_smp..src + copy_smp + splice_after_count]);
244 }
245
246 dst = dst.saturating_add(copy_smp);
247 src = src.saturating_add(copy_smp);
248 new_sample_count = new_sample_count.saturating_add(copy_smp);
249 src = src.saturating_add(jump_smp);
250 }
251
252 if new_sample_count >= 100 {
253 let start = new_sample_count - 100;
254 for offset in 0..100usize {
255 let multiplier = 100i32 - offset as i32;
256 output[start + offset] = ((output[start + offset] as i32 * multiplier) / 100) as i16;
257 }
258 }
259
260 output.truncate(new_sample_count);
261 Some(output)
262}
263
264fn convert_func_16bit_rep(
265 dst: &[i16],
266 dst_last: usize,
267 src: &[i16],
268 src_index: usize,
269 work_base_smp: usize,
270 dst_bound: usize,
271) -> usize {
272 let limit_smp_cnt1 = work_base_smp >> 2;
273 let limit_smp_cnt2 = work_base_smp >> 2;
274 if limit_smp_cnt1 == 0 || limit_smp_cnt2 == 0 {
275 return src_index;
276 }
277
278 let (vector_flag, target_smp) = if dst_last < dst_bound && dst_last < dst.len() {
279 (
280 convert_func_16bit_vector(dst, dst_last, dst_bound),
281 dst[dst_last] as i32,
282 )
283 } else {
284 (0, 0)
285 };
286
287 if vector_flag != 0 {
288 if let Some(distance) = convert_func_16bit_rep_func(
289 src,
290 src_index,
291 limit_smp_cnt1,
292 limit_smp_cnt2,
293 vector_flag,
294 target_smp,
295 -1,
296 ) {
297 return src_index.saturating_sub(distance);
298 }
299 if let Some(distance) = convert_func_16bit_rep_func(
300 src,
301 src_index,
302 limit_smp_cnt1,
303 limit_smp_cnt2,
304 vector_flag,
305 target_smp,
306 1,
307 ) {
308 return src_index.saturating_add(distance).min(src.len());
309 }
310 }
311
312 if let Some(distance) = convert_func_16bit_rep_func(
313 src,
314 src_index,
315 limit_smp_cnt1,
316 limit_smp_cnt2,
317 0,
318 target_smp,
319 -1,
320 ) {
321 return src_index.saturating_sub(distance);
322 }
323 if let Some(distance) = convert_func_16bit_rep_func(
324 src,
325 src_index,
326 limit_smp_cnt1,
327 limit_smp_cnt2,
328 0,
329 target_smp,
330 1,
331 ) {
332 return src_index.saturating_add(distance).min(src.len());
333 }
334 src_index
335}
336
337fn convert_func_16bit_rep_func(
338 src: &[i16],
339 start: usize,
340 limit_smp_cnt1: usize,
341 limit_smp_cnt2: usize,
342 vector_flag: i32,
343 target_smp: i32,
344 add: isize,
345) -> Option<usize> {
346 let mut index = start as isize;
347 let mut best_count: Option<usize> = None;
348 let mut test_smp_cnt = 0usize;
349 let mut get_flag = false;
350 let mut min_len = 999_999i32;
351
352 loop {
353 if index < 0 || index as usize >= src.len() {
354 break;
355 }
356 let idx = index as usize;
357 let sample = src[idx] as i32;
358
359 if vector_flag == 0 {
360 let distance = (target_smp - sample).abs();
361 if distance < min_len {
362 min_len = distance;
363 best_count = Some(test_smp_cnt);
364 }
365 } else {
366 let candidate_vector = convert_func_16bit_vector(src, idx, src.len());
367 if candidate_vector == vector_flag {
368 if vector_flag == 1 {
369 if sample <= target_smp {
370 let distance = target_smp - sample;
371 if !get_flag || distance < min_len {
372 min_len = distance;
373 best_count = Some(test_smp_cnt);
374 get_flag = true;
375 }
376 } else if !get_flag {
377 let distance = sample - target_smp;
378 if distance < min_len {
379 min_len = distance;
380 best_count = Some(test_smp_cnt);
381 }
382 }
383 } else if sample >= target_smp {
384 let distance = sample - target_smp;
385 if !get_flag || distance < min_len {
386 min_len = distance;
387 best_count = Some(test_smp_cnt);
388 get_flag = true;
389 }
390 } else if !get_flag {
391 let distance = target_smp - sample;
392 if distance < min_len {
393 min_len = distance;
394 best_count = Some(test_smp_cnt);
395 }
396 }
397 }
398 }
399
400 if best_count.is_some() && min_len <= 100 {
401 break;
402 }
403 test_smp_cnt += 1;
404 index += add;
405 if test_smp_cnt >= limit_smp_cnt1 {
406 break;
407 }
408 if test_smp_cnt >= limit_smp_cnt2
409 && if vector_flag == 0 {
410 best_count.is_some()
411 } else {
412 get_flag
413 }
414 {
415 break;
416 }
417 }
418
419 best_count
420}
421
422fn convert_func_16bit_vector(src: &[i16], index: usize, end: usize) -> i32 {
423 if index >= end || index >= src.len() {
424 return 0;
425 }
426 let base_smp = src[index] as i32;
427 let mut vector_flag = 0i32;
428 let mut wp = index as isize - 1;
429 for _ in 0..10 {
430 if wp < 0 || wp as usize >= end || wp as usize >= src.len() {
431 break;
432 }
433 let sample = src[wp as usize] as i32;
434 if sample < base_smp {
435 vector_flag -= 1;
436 if vector_flag <= -3 {
437 break;
438 }
439 } else if sample > base_smp {
440 vector_flag += 1;
441 if vector_flag >= 3 {
442 break;
443 }
444 }
445 wp -= 1;
446 }
447
448 if vector_flag >= 3 {
449 1
450 } else if vector_flag <= -3 {
451 -1
452 } else {
453 0
454 }
455}
456
457fn convert_func_16bit_copy_size(src: &[i16], src_index: usize, work_base_smp: usize) -> usize {
458 let remaining = src.len().saturating_sub(src_index);
459 if remaining <= work_base_smp {
460 return remaining;
461 }
462
463 let mut copy_smp = work_base_smp;
464 let mut wp = src_index + copy_smp - 1;
465 let mut min_smp = (src[wp] as i32).abs();
466 wp = wp.saturating_sub(1);
467 let mut min_i = 0usize;
468 for i in 0..10usize {
469 if wp < src_index || wp >= src.len() {
470 break;
471 }
472 let test_smp = (src[wp] as i32).abs();
473 if test_smp < min_smp {
474 min_smp = test_smp;
475 min_i = i + 1;
476 }
477 if wp == 0 {
478 break;
479 }
480 wp -= 1;
481 }
482 if min_i != 0 {
483 copy_smp = copy_smp.saturating_sub(min_i);
484 }
485
486 let boundary = src_index + copy_smp - 1;
487 min_smp = (src[boundary] as i32).abs();
488 let sign_negative = src[boundary] < 0;
489
490 if min_smp == 0 {
491 return copy_smp;
492 }
493
494 let limit_smp_cnt = copy_smp >> 2;
495
496 let mut min_smp_a = min_smp;
497 let mut copy_smp_cnt_a = 0usize;
498 let mut test_smp_cnt = 1usize;
499 let mut scan = src_index + copy_smp;
500 loop {
501 if scan >= src.len() {
502 break;
503 }
504 let raw = src[scan];
505 if (raw < 0) != sign_negative {
506 break;
507 }
508 let test_smp = (raw as i32).abs();
509 if test_smp < min_smp_a {
510 copy_smp_cnt_a = test_smp_cnt;
511 min_smp_a = test_smp;
512 if min_smp_a == 0 {
513 break;
514 }
515 }
516 test_smp_cnt += 1;
517 if test_smp_cnt >= limit_smp_cnt {
518 break;
519 }
520 scan += 1;
521 }
522
523 let mut min_smp_b = min_smp;
524 let mut copy_smp_cnt_b = 0usize;
525 test_smp_cnt = 1;
526 let mut scan = src_index as isize + copy_smp as isize - 2;
527 loop {
528 if scan < src_index as isize || scan < 0 || scan as usize >= src.len() {
529 break;
530 }
531 let raw = src[scan as usize];
532 if (raw < 0) != sign_negative {
533 break;
534 }
535 let test_smp = (raw as i32).abs();
536 if test_smp < min_smp_b {
537 copy_smp_cnt_b = test_smp_cnt;
538 min_smp_b = test_smp;
539 if min_smp_b == 0 {
540 break;
541 }
542 }
543 test_smp_cnt += 1;
544 if test_smp_cnt >= limit_smp_cnt {
545 break;
546 }
547 scan -= 1;
548 }
549
550 if min_smp_a <= min_smp_b {
551 copy_smp.saturating_add(copy_smp_cnt_a).min(remaining)
552 } else {
553 copy_smp.saturating_sub(copy_smp_cnt_b)
554 }
555}
556
557fn convert_func_smooth(
558 dst: &mut [i16],
559 boundary: usize,
560 before_smp_cnt: usize,
561 after_smp_cnt: usize,
562 end: usize,
563) -> i32 {
564 if boundary == 0 || boundary >= end || boundary >= dst.len() {
565 return 0;
566 }
567
568 let smooth_len = (dst[boundary] as i32 - dst[boundary - 1] as i32) >> 1;
569
570 if before_smp_cnt != 0 {
571 let mut wp = boundary as isize - 1;
572 for i in (1..=before_smp_cnt).rev() {
573 if wp < 0 || wp as usize >= end || wp as usize >= dst.len() {
574 break;
575 }
576 let correction = ((smooth_len as f64 / before_smp_cnt as f64) * i as f64) as i32;
577 if correction == 0 {
578 break;
579 }
580 let value = (dst[wp as usize] as i32 + correction)
581 .clamp(-JITAN_CLAMP_SAMPLE, JITAN_CLAMP_SAMPLE);
582 dst[wp as usize] = value as i16;
583 wp -= 1;
584 }
585 }
586
587 if after_smp_cnt != 0 {
588 let mut wp = boundary;
589 for i in (1..=after_smp_cnt).rev() {
590 if wp >= end || wp >= dst.len() {
591 break;
592 }
593 let correction = ((smooth_len as f64 / after_smp_cnt as f64) * i as f64) as i32;
594 if correction == 0 {
595 break;
596 }
597 let value =
598 (dst[wp] as i32 - correction).clamp(-JITAN_CLAMP_SAMPLE, JITAN_CLAMP_SAMPLE);
599 dst[wp] = value as i16;
600 wp += 1;
601 }
602 }
603
604 smooth_len
605}
606
607#[allow(clippy::too_many_arguments)]
608fn convert_func_gousei(
609 dst: &mut [i16],
610 boundary: usize,
611 end: usize,
612 splice: &mut [i16],
613 center: usize,
614 mut before_count: usize,
615 mut after_count: usize,
616 smooth_len: i32,
617) {
618 if before_count != 0 {
619 if smooth_len != 0 {
620 let mut dp = center as isize - 1;
621 for _ in 0..before_count {
622 if dp < 0 || dp as usize >= splice.len() {
623 break;
624 }
625 let value = (splice[dp as usize] as i32 - smooth_len)
626 .clamp(-JITAN_CLAMP_SAMPLE, JITAN_CLAMP_SAMPLE);
627 splice[dp as usize] = value as i16;
628 dp -= 1;
629 }
630 }
631 before_count = before_count.min(boundary);
632 }
633
634 if after_count != 0 {
635 if smooth_len != 0 {
636 let mut dp = center;
637 for _ in 0..after_count {
638 if dp >= splice.len() {
639 break;
640 }
641 let value =
642 (splice[dp] as i32 + smooth_len).clamp(-JITAN_CLAMP_SAMPLE, JITAN_CLAMP_SAMPLE);
643 splice[dp] = value as i16;
644 dp += 1;
645 }
646 }
647 after_count = if boundary >= end {
648 0
649 } else {
650 after_count.min(end - boundary)
651 };
652 }
653
654 if after_count != 0 {
655 for i in 0..after_count {
656 if boundary + i >= dst.len() || center + i >= splice.len() {
657 break;
658 }
659 std::mem::swap(&mut dst[boundary + i], &mut splice[center + i]);
660 }
661 }
662
663 let proc_count = before_count.saturating_add(after_count);
664 if proc_count <= 1 {
665 return;
666 }
667
668 let aaa = (proc_count - 1) as f64;
669 let splice_start = center.saturating_sub(before_count);
670 let dst_start = boundary.saturating_sub(before_count);
671 for i in 0..proc_count {
672 if splice_start + i >= splice.len() || dst_start + i >= dst.len() {
673 break;
674 }
675 let value = ((splice[splice_start + i] as f64 / aaa) * i as f64
676 + (dst[dst_start + i] as f64 / aaa) * ((proc_count - 1 - i) as f64))
677 as i32;
678 dst[dst_start + i] = value.clamp(-JITAN_CLAMP_SAMPLE, JITAN_CLAMP_SAMPLE) as i16;
679 }
680}
681
682#[cfg(test)]
683mod tests {
684 use super::*;
685
686 fn wav(channels: u16, sample_rate: u32, samples: Vec<i16>) -> Vec<u8> {
687 pcm16_to_wav_bytes(&Pcm16 {
688 channels,
689 sample_rate,
690 samples,
691 })
692 }
693
694 #[test]
695 fn normal_rate_is_byte_identical() {
696 let input = wav(2, 8_000, vec![100, -100, 200, -200]);
697 assert_eq!(convert_koe_wav(input.clone(), 100).unwrap(), input);
698 }
699
700 #[test]
701 fn stereo_jitan_uses_original_sum_to_mono_rule() {
702 let frames = 8_000usize * 80 / 1000;
706 let mut samples = Vec::with_capacity(frames * 2);
707 for _ in 0..frames {
708 samples.push(20_000);
709 samples.push(20_000);
710 }
711 let out = convert_koe_wav(wav(2, 8_000, samples), 200).unwrap();
712 let pcm = parse_pcm16_wav(&out).unwrap();
713 assert_eq!(pcm.channels, 1);
714 assert_eq!(pcm.samples.len(), frames);
715 assert!(pcm.samples.iter().all(|&sample| sample == 0));
716 }
717
718 fn fnv1a_pcm16(samples: &[i16]) -> u64 {
719 let mut hash = 0xcbf29ce484222325u64;
720 for sample in samples {
721 for byte in sample.to_le_bytes() {
722 hash ^= byte as u64;
723 hash = hash.wrapping_mul(0x100000001b3);
724 }
725 }
726 hash
727 }
728
729 #[test]
730 fn converter_matches_original_cpp_golden_vectors() {
731 let input = (0..8_000usize)
735 .map(|i| (((i * 7_919 + 12_345) % 60_001) as i32 - 30_000) as i16)
736 .collect::<Vec<_>>();
737 for (rate, expected_len, expected_hash) in [
738 (125, 6_526, 0x32ed7b7901ceab1a),
739 (150, 5_754, 0xcb55646dcdd69270),
740 (200, 4_130, 0x7422c527b9b4de3d),
741 (300, 2_599, 0x88033d2b78a75beb),
742 ] {
743 let out = convert_pcm16_original(&input, 8_000, rate).unwrap();
744 assert_eq!(out.len(), expected_len, "rate={rate}");
745 assert_eq!(fnv1a_pcm16(&out), expected_hash, "rate={rate}");
746 }
747 }
748
749 #[test]
750 fn original_converter_shortens_long_voice_and_fades_tail() {
751 let rate = 8_000u32;
752 let frames = rate as usize;
753 let samples = (0..frames)
754 .map(|i| {
755 ((i as f64 * 440.0 * std::f64::consts::TAU / rate as f64).sin() * 12_000.0) as i16
756 })
757 .collect::<Vec<_>>();
758 let out = convert_koe_wav(wav(1, rate, samples), 200).unwrap();
759 let pcm = parse_pcm16_wav(&out).unwrap();
760 assert_eq!(pcm.channels, 1);
761 assert!(pcm.samples.len() < frames);
762 assert!(pcm.samples.len() > frames / 3);
763 assert!(pcm.samples.last().copied().unwrap_or(0).abs() < 500);
764 }
765}