1#![forbid(unsafe_code)]
7#![allow(
8 clippy::erasing_op,
9 clippy::identity_op,
10 clippy::precedence,
11 clippy::int_plus_one
12)]
13
14macro_rules! debug_trace {
15 ($($arg:tt)*) => {};
16}
17
18mod celt;
19pub(crate) mod compare;
20mod entropy;
21mod error;
22mod multistream;
23mod packet;
24mod silk;
25use crate::entropy::EcDec;
26use core::sync::atomic::{AtomicUsize, Ordering};
27
28pub(crate) use error::Error;
29pub use multistream::OpusMultistreamDecoder;
30
31static TRACE_DECODE_PACKET_IDX: AtomicUsize = AtomicUsize::new(0);
32
33pub struct OpusDecoder {
38 decoder: Decoder,
39 float_scratch: Vec<i16>,
40 loss_count: u32,
41 last_packet_duration: usize,
42}
43
44impl OpusDecoder {
45 pub const MAX_FRAME_SIZE_48K: usize = Decoder::MAX_FRAME_SIZE_48K;
47
48 pub fn new(sample_rate: u32, channels: usize) -> Result<Self, OpusError> {
63 let channels =
64 u8::try_from(channels).map_err(|_| OpusError::InvalidArgument("channels"))?;
65 let decoder = Decoder::new(sample_rate, channels).map_err(OpusError::from)?;
66
67 Ok(Self {
68 decoder,
69 float_scratch: Vec::new(),
70 loss_count: 0,
71 last_packet_duration: 0,
72 })
73 }
74
75 pub fn max_frame_size_per_channel(&self) -> usize {
77 self.decoder.max_frame_size_per_channel()
78 }
79
80 fn sync_state_from_decoder(&mut self) {
85 self.loss_count = self.decoder.loss_count;
86 self.last_packet_duration = self.decoder.last_packet_duration;
87 }
88
89 pub fn decode(
109 &mut self,
110 packet: &[u8],
111 pcm: &mut [i16],
112 fec: bool,
113 ) -> Result<usize, OpusError> {
114 let samples_per_channel = if packet.is_empty() || fec {
115 self.decoder.decode(None, pcm).map_err(OpusError::from)?
116 } else {
117 self.decoder
118 .decode(Some(packet), pcm)
119 .map_err(OpusError::from)?
120 };
121 self.sync_state_from_decoder();
122 Ok(samples_per_channel)
123 }
124
125 pub fn decode_float(
145 &mut self,
146 packet: &[u8],
147 pcm: &mut [f32],
148 fec: bool,
149 ) -> Result<usize, OpusError> {
150 let samples_per_channel_hint = if packet.is_empty() || fec {
151 self.decoder.last_packet_duration
152 } else {
153 packet::parse_packet(packet)
154 .map_err(OpusError::from)?
155 .samples_per_channel(self.decoder.fs_hz())
156 };
157 let needed = samples_per_channel_hint * self.decoder.channels() as usize;
158 if pcm.len() < needed {
159 return Err(OpusError::BufferTooSmall);
160 }
161
162 if self.float_scratch.len() < needed {
163 self.float_scratch.resize(needed, 0);
164 }
165
166 let samples_per_channel = self
167 .decoder
168 .decode(
169 if packet.is_empty() || fec {
170 None
171 } else {
172 Some(packet)
173 },
174 &mut self.float_scratch[..needed],
175 )
176 .map_err(OpusError::from)?;
177 self.sync_state_from_decoder();
178 let written = samples_per_channel * self.decoder.channels() as usize;
179 for (dst, src) in pcm.iter_mut().zip(self.float_scratch[..written].iter()) {
180 *dst = f32::from(*src) / 32768.0;
181 }
182
183 Ok(samples_per_channel)
184 }
185
186 pub fn reset(&mut self) {
191 self.decoder.reset();
192 self.loss_count = 0;
193 self.last_packet_duration = 0;
194 }
195
196 pub fn final_range(&self) -> u32 {
198 self.decoder.final_range()
199 }
200
201 #[doc(hidden)]
203 pub fn last_split_count(&self) -> usize {
204 self.decoder.last_split_count()
205 }
206
207 #[doc(hidden)]
209 pub fn deemph_mem(&self) -> f32 {
210 self.decoder.deemph_mem()
211 }
212
213 #[doc(hidden)]
215 pub fn last_is_transient(&self) -> bool {
216 self.decoder.last_is_transient()
217 }
218
219 #[doc(hidden)]
221 pub fn last_had_redundancy(&self) -> bool {
222 self.decoder.last_had_redundancy()
223 }
224
225 #[doc(hidden)]
227 pub fn last_celt_to_silk(&self) -> bool {
228 self.decoder.last_celt_to_silk()
229 }
230}
231
232#[derive(Debug, thiserror::Error)]
234pub enum OpusError {
235 #[error("invalid packet")]
237 InvalidPacket,
238 #[error("internal error")]
240 InternalError,
241 #[error("buffer too small")]
243 BufferTooSmall,
244 #[error("invalid argument: {0}")]
246 InvalidArgument(&'static str),
247}
248
249impl From<Error> for OpusError {
250 fn from(value: Error) -> Self {
251 match value {
252 Error::InvalidSampleRate(_) => Self::InvalidArgument("sample_rate"),
253 Error::InvalidChannels(_) => Self::InvalidArgument("channels"),
254 Error::PacketTooLarge { .. } | Error::BadPacket => Self::InvalidPacket,
255 Error::OutputTooSmall { .. } => Self::BufferTooSmall,
256 Error::NotImplemented => Self::InternalError,
257 }
258 }
259}
260
261#[derive(Debug, Clone)]
262pub(crate) struct Decoder {
263 fs_hz: u32,
264 channels: u8,
265 celt: celt::CeltDecoder,
266 silk: silk::SilkDecoder,
267 last_packet_split_count: usize,
268 last_final_range: u32,
269 last_had_redundancy: bool,
270 last_celt_to_silk: bool,
271 prev_mode: Option<OpusMode>,
272 prev_redundancy: bool,
273 loss_count: u32,
274 last_packet_duration: usize,
275 last_output: Vec<i16>,
276}
277
278#[derive(Debug, Clone, Copy, PartialEq, Eq)]
279pub(crate) enum OpusMode {
280 SilkOnly,
281 Hybrid,
282 CeltOnly,
283}
284
285impl Decoder {
286 pub(crate) const MAX_FRAME_SIZE_48K: usize = 5760; pub(crate) fn new(fs_hz: u32, channels: u8) -> Result<Self, Error> {
289 if !matches!(fs_hz, 8000 | 12000 | 16000 | 24000 | 48000) {
290 return Err(Error::InvalidSampleRate(fs_hz));
291 }
292 if !matches!(channels, 1 | 2) {
293 return Err(Error::InvalidChannels(channels));
294 }
295 Ok(Self {
296 fs_hz,
297 channels,
298 celt: celt::CeltDecoder::new(fs_hz, channels),
299 silk: silk::SilkDecoder::new(fs_hz, channels),
300 last_packet_split_count: 0,
301 last_final_range: 0,
302 last_had_redundancy: false,
303 last_celt_to_silk: false,
304 prev_mode: None,
305 prev_redundancy: false,
306 loss_count: 0,
307 last_packet_duration: 0,
308 last_output: Vec::new(),
309 })
310 }
311
312 pub(crate) fn fs_hz(&self) -> u32 {
313 self.fs_hz
314 }
315
316 pub(crate) fn channels(&self) -> u8 {
317 self.channels
318 }
319
320 pub(crate) fn max_frame_size_per_channel(&self) -> usize {
321 match self.fs_hz {
324 8000 => 960,
325 12000 => 1440,
326 16000 => 1920,
327 24000 => 2880,
328 48000 => Self::MAX_FRAME_SIZE_48K,
329 _ => 0, }
331 }
332
333 fn conceal_with_fade(&self, frame_size: usize, loss_count: u32, out: &mut [i16]) {
338 let channels = self.channels as usize;
339 let needed = frame_size * channels;
340 if self.last_output.len() < needed {
341 out[..needed].fill(0);
342 return;
343 }
344
345 let fade = 0.9f32.powi((loss_count.min(10) + 1) as i32);
346 for (dst, src) in out[..needed]
347 .iter_mut()
348 .zip(self.last_output[..needed].iter())
349 {
350 let sample = f32::from(*src) * fade;
351 *dst = sample.round().clamp(i16::MIN as f32, i16::MAX as f32) as i16;
352 }
353 }
354
355 fn store_last_output(&mut self, out: &[i16], samples_per_channel: usize) {
360 let written = samples_per_channel * self.channels as usize;
361 self.last_output.clear();
362 self.last_output.extend_from_slice(&out[..written]);
363 }
364
365 fn decode_lost_packet(&mut self, out: &mut [i16]) -> Result<usize, Error> {
370 let samples_per_channel = self.last_packet_duration;
371 if samples_per_channel == 0 {
372 self.last_final_range = 0;
373 return Ok(0);
374 }
375
376 let needed = samples_per_channel * self.channels as usize;
377 if out.len() < needed {
378 return Err(Error::OutputTooSmall {
379 needed,
380 got: out.len(),
381 });
382 }
383
384 out[..needed].fill(0);
385 match self.prev_mode {
386 Some(OpusMode::CeltOnly) => {
387 let concealed = self
388 .celt
389 .decode_lost(samples_per_channel, self.channels as usize);
390 for (dst, src) in out[..needed].iter_mut().zip(concealed.iter()) {
391 *dst = src.round().clamp(i16::MIN as f32, i16::MAX as f32) as i16;
392 }
393 }
394 Some(OpusMode::SilkOnly | OpusMode::Hybrid) => {
395 self.silk
396 .decode_lost(samples_per_channel, out, self.loss_count)?;
397 }
398 _ => {
399 self.conceal_with_fade(samples_per_channel, self.loss_count, out);
400 }
401 }
402
403 self.loss_count = self.loss_count.saturating_add(1);
404 self.last_packet_split_count = 0;
405 self.last_final_range = 0;
406 self.last_had_redundancy = false;
407 self.last_celt_to_silk = false;
408 self.prev_redundancy = false;
409 self.store_last_output(out, samples_per_channel);
410 Ok(samples_per_channel)
411 }
412
413 pub(crate) fn reset(&mut self) {
414 self.celt.reset();
416 self.silk.reset();
417 self.last_packet_split_count = 0;
418 self.last_final_range = 0;
419 self.last_had_redundancy = false;
420 self.last_celt_to_silk = false;
421 self.prev_mode = None;
422 self.prev_redundancy = false;
423 self.loss_count = 0;
424 self.last_packet_duration = 0;
425 self.last_output.clear();
426 }
427
428 pub(crate) fn final_range(&self) -> u32 {
430 self.last_final_range
431 }
432
433 pub(crate) fn last_split_count(&self) -> usize {
437 self.last_packet_split_count
438 }
439
440 pub(crate) fn deemph_mem(&self) -> f32 {
444 self.celt.deemph_mem(0)
445 }
446
447 pub(crate) fn last_is_transient(&self) -> bool {
451 false
452 }
453
454 pub(crate) fn last_had_redundancy(&self) -> bool {
458 self.last_had_redundancy
459 }
460
461 pub(crate) fn last_celt_to_silk(&self) -> bool {
465 self.last_celt_to_silk
466 }
467
468 pub(crate) fn decode(
474 &mut self,
475 packet: Option<&[u8]>,
476 out: &mut [i16],
477 ) -> Result<usize, Error> {
478 let packet_idx = TRACE_DECODE_PACKET_IDX.fetch_add(1, Ordering::SeqCst);
479 let (toc, samples_per_channel_needed) = match packet {
480 Some(packet) => {
481 let pp = packet::parse_packet(packet)?;
482 (Some(pp.toc), pp.samples_per_channel(self.fs_hz))
483 }
484 None => (None, self.last_packet_duration),
485 };
486
487 let needed = samples_per_channel_needed * self.channels as usize;
488 if out.len() < needed {
489 return Err(Error::OutputTooSmall {
490 needed,
491 got: out.len(),
492 });
493 }
494
495 let Some(toc) = toc else {
496 return self.decode_lost_packet(out);
497 };
498
499 self.celt.reset_loss_count();
500 let config = (toc >> 3) & 0x1f;
501 let mode = match config {
502 0..=11 => OpusMode::SilkOnly,
503 12..=15 => OpusMode::Hybrid,
504 16..=31 => OpusMode::CeltOnly,
505 _ => unreachable!(),
506 };
507 let pp = packet::parse_packet(packet.unwrap())?;
509 self.last_packet_split_count = 0;
510 self.last_final_range = 0;
511
512 let transition = self.prev_mode.is_some()
513 && ((mode == OpusMode::CeltOnly
514 && self.prev_mode != Some(OpusMode::CeltOnly)
515 && !self.prev_redundancy)
516 || (mode != OpusMode::CeltOnly && self.prev_mode == Some(OpusMode::CeltOnly)));
517 let transition_samples = (self.fs_hz as usize) / 200;
518 let transition_overlap = (self.fs_hz as usize) / 400;
519 let channels = self.channels as usize;
520 let celt_transition = if transition && mode == OpusMode::CeltOnly {
521 vec![0i16; transition_samples * channels]
522 } else {
523 Vec::new()
524 };
525 let mut apply_celt_transition = transition && mode == OpusMode::CeltOnly;
526 let mut reset_silk = transition && self.prev_mode == Some(OpusMode::CeltOnly);
527 let mut had_redundancy = false;
528 let mut last_celt_to_silk = false;
529 let mut written_per_channel = 0usize;
530 for &frame in pp.frames().iter() {
531 let frame_samples_48k = pp.samples_per_frame_48k;
532 let out_frame = &mut out[written_per_channel * self.channels as usize..];
533 match mode {
534 OpusMode::CeltOnly => {
535 if apply_celt_transition {
536 self.celt.reset();
537 }
538 let mut ec = EcDec::new(frame);
541 let celt_frame = self.celt.decode_frame_with_ec(
542 frame,
543 &mut ec,
544 frame_samples_48k,
545 config,
546 pp.packet_channels,
547 packet_idx,
548 out_frame,
549 false,
550 )?;
551 if apply_celt_transition {
552 apply_transition_fade_i16(
553 &celt_transition,
554 &mut out_frame[..celt_frame.samples_per_channel * channels],
555 transition_overlap.min(celt_frame.samples_per_channel / 2),
556 channels,
557 self.celt.window(),
558 self.fs_hz,
559 );
560 apply_celt_transition = false;
561 }
562 self.last_packet_split_count += self.celt.last_split_count();
563 self.last_final_range = self.celt.final_range();
564 written_per_channel += celt_frame.samples_per_channel;
565 }
566 OpusMode::SilkOnly => {
567 if reset_silk {
568 self.silk.reset();
569 reset_silk = false;
570 }
571 let packet_frame = frame;
572 let silk_frame = self.silk.decode_frame(
573 packet_frame,
574 frame_samples_48k,
575 config,
576 pp.packet_channels,
577 packet_idx,
578 out_frame,
579 )?;
580 last_celt_to_silk = silk_frame.celt_to_silk;
581 let mut redundancy_rng = 0u32;
582 if silk_frame.consumed_redundancy {
583 let redundancy_data =
584 &packet_frame[packet_frame.len() - silk_frame.redundancy_bytes..];
585 let redundancy_frame_size_48k = 240usize;
586 let redundancy_samples = (self.fs_hz as usize) / 200;
587 let redundancy_end_band = silk_redundancy_end_band(config);
588 let mut redundancy_out =
589 vec![0i16; redundancy_samples * self.channels as usize];
590 if !silk_frame.celt_to_silk {
591 self.celt.reset();
592 }
593 self.celt.set_start_band(0);
594 self.celt.set_end_band(redundancy_end_band);
595 let redundancy_frame = self.celt.decode_frame(
596 redundancy_data,
597 redundancy_frame_size_48k,
598 config,
599 pp.packet_channels,
600 packet_idx,
601 &mut redundancy_out,
602 )?;
603 self.celt.clear_end_band();
604 redundancy_rng = self.celt.final_range();
605 if !silk_frame.celt_to_silk {
606 let overlap = (self.fs_hz as usize) / 400;
607 let channels = self.channels as usize;
608 let silk_tail_start =
609 (silk_frame.samples_per_channel - overlap) * channels;
610 let silk_tail_end = silk_frame.samples_per_channel * channels;
611 let redundancy_start = overlap * channels;
612 let redundancy_end = redundancy_start + overlap * channels;
613 let silk_tail = out_frame[silk_tail_start..silk_tail_end].to_vec();
614 smooth_fade_i16(
615 &silk_tail,
616 &redundancy_out[redundancy_start..redundancy_end],
617 &mut out_frame[silk_tail_start..silk_tail_end],
618 overlap,
619 channels,
620 self.celt.window(),
621 self.fs_hz,
622 );
623 } else {
624 let overlap = (self.fs_hz as usize) / 400;
625 let channels = self.channels as usize;
626 let frame_len = silk_frame.samples_per_channel * channels;
627 apply_transition_fade_i16(
628 &redundancy_out,
629 &mut out_frame[..frame_len],
630 overlap.min(redundancy_frame.samples_per_channel / 2),
631 channels,
632 self.celt.window(),
633 self.fs_hz,
634 );
635 }
636 }
637 had_redundancy = silk_frame.consumed_redundancy && !silk_frame.celt_to_silk;
638 self.last_final_range = self.silk.final_range() ^ redundancy_rng;
639 written_per_channel += silk_frame.samples_per_channel;
640 }
641 OpusMode::Hybrid => {
642 let mut ec = EcDec::new(frame);
643 let silk_frame = self.silk.decode_frame_with_ec(
644 frame,
645 &mut ec,
646 frame_samples_48k,
647 config,
648 pp.packet_channels,
649 true,
650 packet_idx,
651 out_frame,
652 )?;
653 let redundancy = if ec.tell() + 17 + 20 <= (frame.len() as i32) * 8 {
654 ec.dec_bit_logp(12)
655 } else {
656 false
657 };
658 let mut celt_to_silk = false;
659 let mut redundancy_bytes = 0usize;
660 if redundancy {
661 celt_to_silk = ec.dec_bit_logp(1);
662 redundancy_bytes = ec.dec_uint(256) as usize + 2;
663 ec.shrink_storage(redundancy_bytes);
664 }
665 let mut celt_to_silk_audio = Vec::new();
666 let mut celt_to_silk_samples = 0usize;
667 let mut redundant_rng = 0u32;
668 let apply_celt_to_silk_audio = redundancy
669 && celt_to_silk
670 && (self.prev_mode != Some(OpusMode::SilkOnly) || self.prev_redundancy);
671 let reset_main_celt = self.prev_mode.is_some()
672 && self.prev_mode != Some(mode)
673 && !self.prev_redundancy;
674 if redundancy && celt_to_silk {
675 let redundancy_samples = (self.fs_hz as usize) / 200;
676 let redundancy_data = &frame[frame.len() - redundancy_bytes..];
677 self.celt.set_start_band(0);
678 celt_to_silk_audio = vec![0i16; redundancy_samples * channels];
679 let redundancy_frame = self.celt.decode_frame(
680 redundancy_data,
681 240,
682 config,
683 pp.packet_channels,
684 packet_idx,
685 &mut celt_to_silk_audio,
686 )?;
687 self.last_packet_split_count += self.celt.last_split_count();
688 celt_to_silk_samples = redundancy_frame.samples_per_channel;
689 redundant_rng = self.celt.final_range();
690 }
691 if reset_main_celt {
692 self.celt.reset();
693 }
694 self.celt.set_start_band(17);
695 let celt_frame = match self.celt.decode_frame_with_ec(
696 frame,
697 &mut ec,
698 frame_samples_48k,
699 config,
700 pp.packet_channels,
701 packet_idx,
702 out_frame,
703 true,
704 ) {
705 Ok(frame) => frame,
706 Err(err) => {
707 self.celt.set_start_band(0);
708 return Err(err);
709 }
710 };
711 self.celt.set_start_band(0);
712 debug_assert_eq!(
713 silk_frame.samples_per_channel,
714 celt_frame.samples_per_channel
715 );
716 self.last_packet_split_count += self.celt.last_split_count();
717 let main_celt_rng = self.celt.final_range();
718 self.last_final_range = main_celt_rng;
719 if redundancy && !celt_to_silk {
720 let channels = self.channels as usize;
721 let redundancy_samples = (self.fs_hz as usize) / 200;
722 let overlap = (self.fs_hz as usize) / 400;
723 let frame_len = celt_frame.samples_per_channel * channels;
724 let redundancy_data = &frame[frame.len() - redundancy_bytes..];
725 let mut redundancy_out = vec![0i16; redundancy_samples * channels];
726 self.celt.reset();
727 self.celt.set_start_band(0);
728 let redundancy_frame = self.celt.decode_frame(
729 redundancy_data,
730 240,
731 config,
732 pp.packet_channels,
733 packet_idx,
734 &mut redundancy_out,
735 )?;
736 self.last_packet_split_count += self.celt.last_split_count();
737 let redundant_rng = self.celt.final_range();
738 let fade_len = overlap * channels;
739 let tail_start = frame_len.saturating_sub(fade_len);
740 let tail_end = tail_start + fade_len;
741 let redundancy_start = fade_len;
742 let redundancy_end = redundancy_start + fade_len;
743 if tail_end <= out_frame.len()
744 && redundancy_end <= redundancy_out.len()
745 && redundancy_frame.samples_per_channel == redundancy_samples
746 {
747 let celt_tail = out_frame[tail_start..tail_end].to_vec();
748 smooth_fade_i16(
749 &celt_tail,
750 &redundancy_out[redundancy_start..redundancy_end],
751 &mut out_frame[tail_start..tail_end],
752 overlap,
753 channels,
754 self.celt.window(),
755 self.fs_hz,
756 );
757 }
758 self.last_final_range ^= redundant_rng;
759 }
760 if redundancy && celt_to_silk {
761 if apply_celt_to_silk_audio {
762 let overlap = (self.fs_hz as usize) / 400;
763 let frame_len = celt_frame.samples_per_channel * channels;
764 apply_transition_fade_i16(
765 &celt_to_silk_audio,
766 &mut out_frame[..frame_len],
767 overlap.min(celt_to_silk_samples / 2),
768 channels,
769 self.celt.window(),
770 self.fs_hz,
771 );
772 }
773 self.last_final_range ^= redundant_rng;
774 }
775 last_celt_to_silk = celt_to_silk;
776 had_redundancy = redundancy && !celt_to_silk;
777 written_per_channel += silk_frame.samples_per_channel;
778 }
779 }
780 }
781
782 self.prev_mode = Some(mode);
783 self.last_had_redundancy = had_redundancy;
784 self.last_celt_to_silk = last_celt_to_silk;
785 self.prev_redundancy = had_redundancy;
786 self.loss_count = 0;
787 self.last_packet_duration = written_per_channel;
788 self.store_last_output(out, written_per_channel);
789 Ok(written_per_channel)
790 }
791}
792
793fn smooth_fade_i16(
799 in1: &[i16],
800 in2: &[i16],
801 out: &mut [i16],
802 overlap: usize,
803 channels: usize,
804 window: &[f32],
805 fs_hz: u32,
806) {
807 let inc = (48_000 / fs_hz) as usize;
808 for c in 0..channels {
809 for i in 0..overlap {
810 let w = window[i * inc] * window[i * inc];
811 let idx = i * channels + c;
812 let mixed = w * in2[idx] as f32 + (1.0 - w) * in1[idx] as f32;
813 out[idx] = mixed.round().clamp(i16::MIN as f32, i16::MAX as f32) as i16;
814 }
815 }
816}
817
818fn apply_transition_fade_i16(
824 transition: &[i16],
825 pcm: &mut [i16],
826 overlap: usize,
827 channels: usize,
828 window: &[f32],
829 fs_hz: u32,
830) {
831 if overlap == 0 || channels == 0 {
832 return;
833 }
834
835 let prefix_len = overlap * channels;
836 let copy_len = prefix_len.min(transition.len()).min(pcm.len());
837 pcm[..copy_len].copy_from_slice(&transition[..copy_len]);
838
839 let fade_available = (transition.len().saturating_sub(prefix_len))
840 .min(pcm.len().saturating_sub(prefix_len))
841 / channels;
842 if fade_available == 0 {
843 return;
844 }
845
846 let fade_samples = fade_available.min(overlap);
847 let fade_len = fade_samples * channels;
848 let fade_start = prefix_len;
849 let fade_end = fade_start + fade_len;
850 let incoming = pcm[fade_start..fade_end].to_vec();
851 smooth_fade_i16(
852 &transition[fade_start..fade_end],
853 &incoming,
854 &mut pcm[fade_start..fade_end],
855 fade_samples,
856 channels,
857 window,
858 fs_hz,
859 );
860}
861
862fn silk_redundancy_end_band(config: u8) -> usize {
867 match config {
868 0..=3 => 13,
869 4..=11 => 17,
870 12..=13 => 19,
871 14..=15 => 21,
872 _ => 21,
873 }
874}