Skip to main content

siglus_assets/
nwa.rs

1//! NWA audio container support.
2//!
3//! Decoder implementation for the NWA container format.
4//!
5//! Notes:
6//! - `set_read_sample_pos` and `read_samples` treat one *frame* as one sample
7//!   even for stereo (i.e. a frame contains L+R).
8//! - For compressed NWA (`pack_mod != -1`), the current implementation supports
9//!   16-bit PCM output, matching the original decoder.
10
11use anyhow::{bail, Context, Result};
12use std::fs::File;
13use std::io::{Cursor, Read, Seek, SeekFrom};
14use std::path::Path;
15
16#[derive(Debug, Clone)]
17pub struct NwaHeader {
18    pub channels: u16,
19    pub bits_per_sample: u16,
20    pub samples_per_sec: u32,
21    pub pack_mod: i32,
22    pub zero_mod: i32,
23    pub unit_cnt: u32,
24    pub original_size: u32,
25    pub pack_size: u32,
26    pub sample_cnt: u32,
27    pub unit_sample_cnt: u32,
28    pub last_sample_cnt: u32,
29    pub last_sample_pack_size: u32,
30}
31
32impl NwaHeader {
33    pub fn is_uncompressed(&self) -> bool {
34        self.pack_mod == -1
35    }
36
37    pub fn frame_count(&self) -> u32 {
38        if self.channels == 0 {
39            return 0;
40        }
41        self.sample_cnt / (self.channels as u32)
42    }
43}
44
45#[derive(Debug)]
46struct UnitCache {
47    unit_no: i32,
48    unit_sample_cnt: u32,
49    buf: Vec<u8>,
50}
51
52impl UnitCache {
53    fn new() -> Self {
54        Self {
55            unit_no: -1,
56            unit_sample_cnt: 0,
57            buf: Vec::new(),
58        }
59    }
60}
61
62trait ReadSeek: Read + Seek {}
63impl<T: Read + Seek> ReadSeek for T {}
64
65/// NWA reader with random access by frame index.
66pub struct NwaReader {
67    file: Box<dyn ReadSeek>,
68    base_offset: u64,
69    header: NwaHeader,
70    unit_offsets: Vec<u32>,
71    one_sample_byte_size: u32,
72    read_sample_pos: u32,
73    cache: UnitCache,
74}
75
76
77impl std::fmt::Debug for NwaReader {
78    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
79        f.debug_struct("NwaReader")
80            .field("base_offset", &self.base_offset)
81            .field("header", &self.header)
82            .field("unit_offsets_len", &self.unit_offsets.len())
83            .field("one_sample_byte_size", &self.one_sample_byte_size)
84            .field("read_sample_pos", &self.read_sample_pos)
85            .finish()
86    }
87}
88
89impl NwaReader {
90    pub fn open(path: impl AsRef<Path>) -> Result<Self> {
91        Self::open_with_offset(path, 0)
92    }
93
94    pub fn open_with_offset(path: impl AsRef<Path>, base_offset: u64) -> Result<Self> {
95        let mut file =
96            File::open(&path).with_context(|| format!("open NWA: {}", path.as_ref().display()))?;
97        if base_offset != 0 {
98            file.seek(SeekFrom::Start(base_offset))?;
99        }
100        let header = read_header(&mut file)?;
101
102        let mut unit_offsets = Vec::new();
103        if header.pack_mod != -1 {
104            unit_offsets.resize(header.unit_cnt as usize, 0);
105            let mut tmp = vec![0u8; header.unit_cnt as usize * 4];
106            file.read_exact(&mut tmp)?;
107            for i in 0..header.unit_cnt as usize {
108                let o = u32::from_le_bytes([
109                    tmp[i * 4],
110                    tmp[i * 4 + 1],
111                    tmp[i * 4 + 2],
112                    tmp[i * 4 + 3],
113                ]);
114                unit_offsets[i] = o;
115            }
116        }
117
118        let one_sample_byte_size = (header.bits_per_sample as u32) / 8;
119
120        Ok(Self {
121            file: Box::new(file),
122            base_offset,
123            header,
124            unit_offsets,
125            one_sample_byte_size,
126            read_sample_pos: 0,
127            cache: UnitCache::new(),
128        })
129    }
130
131    pub fn open_from_bytes(bytes: Vec<u8>) -> Result<Self> {
132        let mut file = Cursor::new(bytes);
133        let header = read_header_from_reader(&mut file)?;
134
135        let mut unit_offsets = Vec::new();
136        if header.pack_mod != -1 {
137            unit_offsets.resize(header.unit_cnt as usize, 0);
138            let mut tmp = vec![0u8; header.unit_cnt as usize * 4];
139            file.read_exact(&mut tmp)?;
140            for i in 0..header.unit_cnt as usize {
141                let o = u32::from_le_bytes([
142                    tmp[i * 4],
143                    tmp[i * 4 + 1],
144                    tmp[i * 4 + 2],
145                    tmp[i * 4 + 3],
146                ]);
147                unit_offsets[i] = o;
148            }
149        }
150
151        let one_sample_byte_size = (header.bits_per_sample as u32) / 8;
152
153        Ok(Self {
154            file: Box::new(file),
155            base_offset: 0,
156            header,
157            unit_offsets,
158            one_sample_byte_size,
159            read_sample_pos: 0,
160            cache: UnitCache::new(),
161        })
162    }
163
164    pub fn header(&self) -> &NwaHeader {
165        &self.header
166    }
167
168    /// Set current read position in frames.
169    pub fn set_read_sample_pos(&mut self, frame_pos: u32) {
170        self.read_sample_pos = frame_pos.saturating_mul(self.header.channels as u32);
171    }
172
173    /// Get current read position in frames.
174    pub fn get_read_sample_pos(&self) -> u32 {
175        let ch = self.header.channels as u32;
176        if ch == 0 {
177            0
178        } else {
179            self.read_sample_pos / ch
180        }
181    }
182
183    /// Read `frame_cnt` frames into an interleaved PCM byte buffer.
184    ///
185    /// Returns exactly `frames_read` frames worth of data.
186    pub fn read_samples(&mut self, frame_cnt: u32) -> Result<Vec<u8>> {
187        if self.header.channels != 1 && self.header.channels != 2 {
188            bail!("unsupported channels: {}", self.header.channels);
189        }
190        if self.header.bits_per_sample != 8 && self.header.bits_per_sample != 16 {
191            bail!(
192                "unsupported bits_per_sample: {}",
193                self.header.bits_per_sample
194            );
195        }
196
197        let need_byte_size =
198            (frame_cnt as u64) * (self.header.channels as u64) * (self.one_sample_byte_size as u64);
199        let mut out = vec![0u8; need_byte_size as usize];
200        let mut dp = 0usize;
201        let mut need = need_byte_size as i64;
202
203        while need > 0 {
204            if self.read_sample_pos >= self.header.sample_cnt {
205                break;
206            }
207
208            let copy_byte_size: usize;
209            if self.header.pack_mod == -1 {
210                // Uncompressed: data starts immediately after the header.
211                copy_byte_size = self.read_no_pack_data(need as u64, &mut out[dp..])?;
212            } else {
213                // Compressed: decode the unit if needed, then copy from cached buffer.
214                let unit_no = (self.read_sample_pos / self.header.unit_sample_cnt) as u32;
215                self.read_unit(unit_no)?;
216
217                let ofs = ((self.read_sample_pos % self.header.unit_sample_cnt) as usize)
218                    * (self.one_sample_byte_size as usize);
219                let mut cb = (self.cache.unit_sample_cnt as usize
220                    * self.one_sample_byte_size as usize)
221                    .saturating_sub(ofs);
222                if cb > need as usize {
223                    cb = need as usize;
224                }
225                if cb == 0 {
226                    break;
227                }
228                out[dp..dp + cb].copy_from_slice(&self.cache.buf[ofs..ofs + cb]);
229                copy_byte_size = cb;
230            }
231
232            if copy_byte_size == 0 {
233                break;
234            }
235            dp += copy_byte_size;
236            need -= copy_byte_size as i64;
237            self.read_sample_pos += (copy_byte_size as u32) / self.one_sample_byte_size;
238        }
239
240        out.truncate(dp);
241        Ok(out)
242    }
243
244    /// Convert the entire NWA stream into a WAV (RIFF PCM) byte vector.
245    pub fn to_wav_bytes(&mut self) -> Result<Vec<u8>> {
246        let total_frames = self.header.frame_count();
247        self.set_read_sample_pos(0);
248        let pcm = self.read_samples(total_frames)?;
249
250        let fmt_tag: u16 = 1; // PCM
251        let channels = self.header.channels;
252        let sample_rate = self.header.samples_per_sec;
253        let bits = self.header.bits_per_sample;
254        let block_align = (channels as u32 * (bits as u32 / 8)) as u16;
255        let byte_rate = sample_rate * (block_align as u32);
256        let data_len = pcm.len() as u32;
257
258        let mut wav = Vec::with_capacity(44 + pcm.len());
259        wav.extend_from_slice(b"RIFF");
260        wav.extend_from_slice(&(36u32 + data_len).to_le_bytes());
261        wav.extend_from_slice(b"WAVE");
262
263        wav.extend_from_slice(b"fmt ");
264        wav.extend_from_slice(&16u32.to_le_bytes());
265        wav.extend_from_slice(&fmt_tag.to_le_bytes());
266        wav.extend_from_slice(&channels.to_le_bytes());
267        wav.extend_from_slice(&sample_rate.to_le_bytes());
268        wav.extend_from_slice(&byte_rate.to_le_bytes());
269        wav.extend_from_slice(&block_align.to_le_bytes());
270        wav.extend_from_slice(&bits.to_le_bytes());
271
272        wav.extend_from_slice(b"data");
273        wav.extend_from_slice(&data_len.to_le_bytes());
274        wav.extend_from_slice(&pcm);
275        Ok(wav)
276    }
277
278    fn read_no_pack_data(&mut self, need_byte_size: u64, out: &mut [u8]) -> Result<usize> {
279        let data_ofs = (self.read_sample_pos as u64) * (self.one_sample_byte_size as u64);
280        let file_ofs = self.base_offset + 44 + data_ofs; // sizeof(NWA_HEADER_STRUCT)
281
282        self.file.seek(SeekFrom::Start(file_ofs))?;
283
284        // Clamp at end.
285        let max_data_len = (self.header.sample_cnt as u64) * (self.one_sample_byte_size as u64);
286        let remain = max_data_len.saturating_sub(data_ofs);
287        let to_read = std::cmp::min(need_byte_size, std::cmp::min(remain, out.len() as u64));
288        let mut buf = &mut out[..to_read as usize];
289        self.file.read_exact(&mut buf)?;
290        Ok(to_read as usize)
291    }
292
293    fn read_unit(&mut self, unit_no: u32) -> Result<()> {
294        if self.cache.unit_no == unit_no as i32 {
295            return Ok(());
296        }
297
298        if unit_no >= self.header.unit_cnt {
299            bail!(
300                "unit_no out of range: {} >= {}",
301                unit_no,
302                self.header.unit_cnt
303            );
304        }
305
306        if self.header.pack_mod == -1 {
307            bail!("read_unit called for uncompressed NWA");
308        }
309        if self.header.bits_per_sample != 16 {
310            bail!(
311                "compressed NWA currently requires 16-bit PCM (got {})",
312                self.header.bits_per_sample
313            );
314        }
315
316        let (unit_sample_cnt, src_size) = if unit_no == self.header.unit_cnt - 1 {
317            (
318                self.header.last_sample_cnt,
319                self.header.last_sample_pack_size,
320            )
321        } else {
322            let a = self.unit_offsets[unit_no as usize] as u64;
323            let b = self.unit_offsets[unit_no as usize + 1] as u64;
324            (self.header.unit_sample_cnt, (b.saturating_sub(a)) as u32)
325        };
326
327        let src_ofs = self.base_offset + (self.unit_offsets[unit_no as usize] as u64);
328        self.file.seek(SeekFrom::Start(src_ofs))?;
329        let mut src = vec![0u8; src_size as usize];
330        self.file.read_exact(&mut src)?;
331
332        self.cache.buf.resize(unit_sample_cnt as usize * 2, 0);
333        nwa_unpack_unit(
334            &src,
335            self.header.pack_mod,
336            self.header.zero_mod != 0,
337            unit_sample_cnt,
338            &mut self.cache.buf,
339        )?;
340
341        self.cache.unit_no = unit_no as i32;
342        self.cache.unit_sample_cnt = unit_sample_cnt;
343        Ok(())
344    }
345}
346
347fn read_header(file: &mut File) -> Result<NwaHeader> {
348    read_header_from_reader(file)
349}
350
351fn read_header_from_reader<R: Read + ?Sized>(file: &mut R) -> Result<NwaHeader> {
352    let mut buf = [0u8; 44];
353    file.read_exact(&mut buf)?;
354    let mut o = 0usize;
355
356    let channels = u16::from_le_bytes([buf[o], buf[o + 1]]);
357    o += 2;
358    let bits_per_sample = u16::from_le_bytes([buf[o], buf[o + 1]]);
359    o += 2;
360    let samples_per_sec = u32::from_le_bytes([buf[o], buf[o + 1], buf[o + 2], buf[o + 3]]);
361    o += 4;
362    let pack_mod = i32::from_le_bytes([buf[o], buf[o + 1], buf[o + 2], buf[o + 3]]);
363    o += 4;
364    let zero_mod = i32::from_le_bytes([buf[o], buf[o + 1], buf[o + 2], buf[o + 3]]);
365    o += 4;
366    let unit_cnt = u32::from_le_bytes([buf[o], buf[o + 1], buf[o + 2], buf[o + 3]]);
367    o += 4;
368    let original_size = u32::from_le_bytes([buf[o], buf[o + 1], buf[o + 2], buf[o + 3]]);
369    o += 4;
370    let pack_size = u32::from_le_bytes([buf[o], buf[o + 1], buf[o + 2], buf[o + 3]]);
371    o += 4;
372    let sample_cnt = u32::from_le_bytes([buf[o], buf[o + 1], buf[o + 2], buf[o + 3]]);
373    o += 4;
374    let unit_sample_cnt = u32::from_le_bytes([buf[o], buf[o + 1], buf[o + 2], buf[o + 3]]);
375    o += 4;
376    let last_sample_cnt = u32::from_le_bytes([buf[o], buf[o + 1], buf[o + 2], buf[o + 3]]);
377    o += 4;
378    let last_sample_pack_size = u32::from_le_bytes([buf[o], buf[o + 1], buf[o + 2], buf[o + 3]]);
379
380    Ok(NwaHeader {
381        channels,
382        bits_per_sample,
383        samples_per_sec,
384        pack_mod,
385        zero_mod,
386        unit_cnt,
387        original_size,
388        pack_size,
389        sample_cnt,
390        unit_sample_cnt,
391        last_sample_cnt,
392        last_sample_pack_size,
393    })
394}
395
396struct BitReaderLE<'a> {
397    buf: &'a [u8],
398    byte_pos: usize,
399    bit_pos: u8,
400}
401
402impl<'a> BitReaderLE<'a> {
403    fn new(buf: &'a [u8]) -> Self {
404        Self {
405            buf,
406            byte_pos: 0,
407            bit_pos: 0,
408        }
409    }
410
411    fn read_bits(&mut self, n: u8) -> u32 {
412        debug_assert!(n <= 8);
413        let b0 = *self.buf.get(self.byte_pos).unwrap_or(&0);
414        let b1 = *self.buf.get(self.byte_pos + 1).unwrap_or(&0);
415        let w = u16::from_le_bytes([b0, b1]);
416        let v = ((w as u32) >> (self.bit_pos as u32)) & ((1u32 << n) - 1);
417
418        self.bit_pos = self.bit_pos.wrapping_add(n);
419        if self.bit_pos >= 8 {
420            self.byte_pos += (self.bit_pos / 8) as usize;
421            self.bit_pos &= 7;
422        }
423        v
424    }
425}
426
427fn nwa_unpack_unit(
428    src: &[u8],
429    pack_mod: i32,
430    zero_mod: bool,
431    src_smp_cnt: u32,
432    dst_le_i16: &mut [u8],
433) -> Result<()> {
434    if dst_le_i16.len() < src_smp_cnt as usize * 2 {
435        bail!("dst buffer too small");
436    }
437
438    let mut br = BitReaderLE::new(src);
439    let mut now_l: i32 = 0;
440    let mut now_r: i32 = 0;
441    let mut zero_cnt: u32 = 0;
442
443    let mut mod_map = pack_mod;
444    match mod_map {
445        0 => mod_map = 2,
446        1 => mod_map = 1,
447        2 => mod_map = 0,
448        _ => {}
449    }
450
451    match mod_map {
452        0 => unpack16(
453            &mut br,
454            zero_mod,
455            src_smp_cnt,
456            dst_le_i16,
457            3,
458            &mut now_l,
459            &mut now_r,
460            &mut zero_cnt,
461        ),
462        1 => unpack16(
463            &mut br,
464            zero_mod,
465            src_smp_cnt,
466            dst_le_i16,
467            4,
468            &mut now_l,
469            &mut now_r,
470            &mut zero_cnt,
471        ),
472        2 => unpack16(
473            &mut br,
474            zero_mod,
475            src_smp_cnt,
476            dst_le_i16,
477            5,
478            &mut now_l,
479            &mut now_r,
480            &mut zero_cnt,
481        ),
482        3 => unpack16(
483            &mut br,
484            zero_mod,
485            src_smp_cnt,
486            dst_le_i16,
487            6,
488            &mut now_l,
489            &mut now_r,
490            &mut zero_cnt,
491        ),
492        4 => unpack16(
493            &mut br,
494            zero_mod,
495            src_smp_cnt,
496            dst_le_i16,
497            7,
498            &mut now_l,
499            &mut now_r,
500            &mut zero_cnt,
501        ),
502        5 => unpack16(
503            &mut br,
504            zero_mod,
505            src_smp_cnt,
506            dst_le_i16,
507            8,
508            &mut now_l,
509            &mut now_r,
510            &mut zero_cnt,
511        ),
512        _ => bail!("invalid pack_mod: {}", pack_mod),
513    }
514}
515
516fn get_zero_count(br: &mut BitReaderLE) -> u32 {
517    let mut zero_cnt = br.read_bits(1);
518    if zero_cnt == 1 {
519        zero_cnt = br.read_bits(2);
520        if zero_cnt == 3 {
521            zero_cnt = br.read_bits(8);
522        }
523    }
524    zero_cnt as u32
525}
526
527fn apply_delta(br: &mut BitReaderLE, mod_n: u8, data_n: u8, nowsmp: &mut i32) {
528    let (bits, sign_mask, shift): (u8, u32, u8) = match (mod_n, data_n) {
529        (3, 1) => (3, 0x04, 5),
530        (3, 2) => (3, 0x04, 6),
531        (3, 3) => (3, 0x04, 7),
532        (3, 4) => (3, 0x04, 8),
533        (3, 5) => (3, 0x04, 9),
534        (3, 6) => (3, 0x04, 10),
535        (3, 7) => (6, 0x20, 11),
536
537        (4, 1) => (4, 0x08, 4),
538        (4, 2) => (4, 0x08, 5),
539        (4, 3) => (4, 0x08, 6),
540        (4, 4) => (4, 0x08, 7),
541        (4, 5) => (4, 0x08, 8),
542        (4, 6) => (4, 0x08, 9),
543        (4, 7) => (7, 0x40, 10),
544
545        (5, 1) => (5, 0x10, 3),
546        (5, 2) => (5, 0x10, 4),
547        (5, 3) => (5, 0x10, 5),
548        (5, 4) => (5, 0x10, 6),
549        (5, 5) => (5, 0x10, 7),
550        (5, 6) => (5, 0x10, 8),
551        (5, 7) => (8, 0x80, 9),
552
553        (6, 1) => (6, 0x20, 2),
554        (6, 2) => (6, 0x20, 3),
555        (6, 3) => (6, 0x20, 4),
556        (6, 4) => (6, 0x20, 5),
557        (6, 5) => (6, 0x20, 6),
558        (6, 6) => (6, 0x20, 7),
559        (6, 7) => (8, 0x80, 9),
560
561        (7, 1) => (7, 0x40, 2),
562        (7, 2) => (7, 0x40, 3),
563        (7, 3) => (7, 0x40, 4),
564        (7, 4) => (7, 0x40, 5),
565        (7, 5) => (7, 0x40, 6),
566        (7, 6) => (7, 0x40, 7),
567        (7, 7) => (8, 0x80, 9),
568
569        (8, 1) => (8, 0x80, 2),
570        (8, 2) => (8, 0x80, 3),
571        (8, 3) => (8, 0x80, 4),
572        (8, 4) => (8, 0x80, 5),
573        (8, 5) => (8, 0x80, 6),
574        (8, 6) => (8, 0x80, 7),
575        (8, 7) => (8, 0x80, 9),
576        _ => return,
577    };
578
579    let mut dat_code = br.read_bits(bits);
580    if (dat_code & sign_mask) != 0 {
581        dat_code &= !sign_mask;
582        *nowsmp -= ((dat_code as i32) << shift);
583    } else {
584        *nowsmp += ((dat_code as i32) << shift);
585    }
586}
587
588fn unpack16(
589    br: &mut BitReaderLE,
590    zero_mod: bool,
591    src_smp_cnt: u32,
592    dst_le_i16: &mut [u8],
593    mod_n: u8,
594    now_l: &mut i32,
595    now_r: &mut i32,
596    zero_cnt: &mut u32,
597) -> Result<()> {
598    for i in 0..src_smp_cnt {
599        let nowsmp_ref: &mut i32 = if (i & 1) == 0 { now_l } else { now_r };
600
601        if *zero_cnt != 0 {
602            *zero_cnt -= 1;
603        } else {
604            let mod_code = br.read_bits(3) as u8;
605            match mod_code {
606                0 => {
607                    if zero_mod {
608                        *zero_cnt = get_zero_count(br);
609                    }
610                }
611                1 => apply_delta(br, mod_n, 1, nowsmp_ref),
612                2 => apply_delta(br, mod_n, 2, nowsmp_ref),
613                3 => apply_delta(br, mod_n, 3, nowsmp_ref),
614                4 => apply_delta(br, mod_n, 4, nowsmp_ref),
615                5 => apply_delta(br, mod_n, 5, nowsmp_ref),
616                6 => apply_delta(br, mod_n, 6, nowsmp_ref),
617                7 => {
618                    let b = br.read_bits(1);
619                    if b == 0 {
620                        apply_delta(br, mod_n, 7, nowsmp_ref);
621                    } else {
622                        *nowsmp_ref = 0;
623                    }
624                }
625                _ => {}
626            }
627        }
628
629        // Write i16 little-endian.
630        let s = (*nowsmp_ref as i16).to_le_bytes();
631        let o = (i as usize) * 2;
632        dst_le_i16[o] = s[0];
633        dst_le_i16[o + 1] = s[1];
634    }
635    Ok(())
636}