cridecoder 0.2.0__tar.gz → 0.2.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {cridecoder-0.2.0 → cridecoder-0.2.2}/.github/workflows/release-python.yml +2 -2
- {cridecoder-0.2.0 → cridecoder-0.2.2}/Cargo.lock +13 -13
- {cridecoder-0.2.0 → cridecoder-0.2.2}/Cargo.toml +13 -2
- {cridecoder-0.2.0 → cridecoder-0.2.2}/PKG-INFO +1 -1
- {cridecoder-0.2.0 → cridecoder-0.2.2}/pyproject.toml +1 -1
- {cridecoder-0.2.0 → cridecoder-0.2.2}/src/acb/afs.rs +22 -6
- {cridecoder-0.2.0 → cridecoder-0.2.2}/src/hca/decoder.rs +77 -35
- {cridecoder-0.2.0 → cridecoder-0.2.2}/src/hca/hca_file.rs +98 -36
- cridecoder-0.2.2/src/hca/imdct.rs +350 -0
- {cridecoder-0.2.0 → cridecoder-0.2.2}/src/reader.rs +15 -1
- {cridecoder-0.2.0 → cridecoder-0.2.2}/src/usm/extractor.rs +33 -29
- {cridecoder-0.2.0 → cridecoder-0.2.2}/tests/integration_tests.rs +37 -0
- cridecoder-0.2.0/src/hca/imdct.rs +0 -328
- {cridecoder-0.2.0 → cridecoder-0.2.2}/.github/copilot-instructions.md +0 -0
- {cridecoder-0.2.0 → cridecoder-0.2.2}/.github/dependabot.yml +0 -0
- {cridecoder-0.2.0 → cridecoder-0.2.2}/.github/workflows/ci.yml +0 -0
- {cridecoder-0.2.0 → cridecoder-0.2.2}/.github/workflows/release-crate.yml +0 -0
- {cridecoder-0.2.0 → cridecoder-0.2.2}/.gitignore +0 -0
- {cridecoder-0.2.0 → cridecoder-0.2.2}/AGENTS.md +0 -0
- {cridecoder-0.2.0 → cridecoder-0.2.2}/CLAUDE.md +0 -0
- {cridecoder-0.2.0 → cridecoder-0.2.2}/LICENSE +0 -0
- {cridecoder-0.2.0 → cridecoder-0.2.2}/README.md +0 -0
- {cridecoder-0.2.0 → cridecoder-0.2.2}/examples/debug_acb.rs +0 -0
- {cridecoder-0.2.0 → cridecoder-0.2.2}/examples/test_acb.rs +0 -0
- {cridecoder-0.2.0 → cridecoder-0.2.2}/examples/test_hca.rs +0 -0
- {cridecoder-0.2.0 → cridecoder-0.2.2}/examples/test_usm.rs +0 -0
- {cridecoder-0.2.0 → cridecoder-0.2.2}/src/acb/builder.rs +0 -0
- {cridecoder-0.2.0 → cridecoder-0.2.2}/src/acb/consts.rs +0 -0
- {cridecoder-0.2.0 → cridecoder-0.2.2}/src/acb/extractor.rs +0 -0
- {cridecoder-0.2.0 → cridecoder-0.2.2}/src/acb/track.rs +0 -0
- {cridecoder-0.2.0 → cridecoder-0.2.2}/src/acb/utf.rs +0 -0
- {cridecoder-0.2.0 → cridecoder-0.2.2}/src/acb.rs +0 -0
- {cridecoder-0.2.0 → cridecoder-0.2.2}/src/hca/ath.rs +0 -0
- {cridecoder-0.2.0 → cridecoder-0.2.2}/src/hca/bitreader.rs +0 -0
- {cridecoder-0.2.0 → cridecoder-0.2.2}/src/hca/cipher.rs +0 -0
- {cridecoder-0.2.0 → cridecoder-0.2.2}/src/hca/encoder.rs +0 -0
- {cridecoder-0.2.0 → cridecoder-0.2.2}/src/hca/tables.rs +0 -0
- {cridecoder-0.2.0 → cridecoder-0.2.2}/src/hca.rs +0 -0
- {cridecoder-0.2.0 → cridecoder-0.2.2}/src/lib.rs +0 -0
- {cridecoder-0.2.0 → cridecoder-0.2.2}/src/python.rs +0 -0
- {cridecoder-0.2.0 → cridecoder-0.2.2}/src/usm/builder.rs +0 -0
- {cridecoder-0.2.0 → cridecoder-0.2.2}/src/usm/metadata.rs +0 -0
- {cridecoder-0.2.0 → cridecoder-0.2.2}/src/usm.rs +0 -0
|
@@ -58,7 +58,7 @@ jobs:
|
|
|
58
58
|
VERSION="${GITHUB_REF_NAME#v}"
|
|
59
59
|
sed -i '' "1,/^version = .*/{s/^version = .*/version = \"${VERSION}\"/;}" Cargo.toml
|
|
60
60
|
sed -i '' "s/^version = .*/version = \"${VERSION}\"/" pyproject.toml
|
|
61
|
-
- uses: actions/setup-python@
|
|
61
|
+
- uses: actions/setup-python@v6
|
|
62
62
|
with:
|
|
63
63
|
python-version: |
|
|
64
64
|
3.9
|
|
@@ -95,7 +95,7 @@ jobs:
|
|
|
95
95
|
VERSION="${GITHUB_REF_NAME#v}"
|
|
96
96
|
sed -i "0,/^version = .*/s//version = \"${VERSION}\"/" Cargo.toml
|
|
97
97
|
sed -i "s/^version = .*/version = \"${VERSION}\"/" pyproject.toml
|
|
98
|
-
- uses: actions/setup-python@
|
|
98
|
+
- uses: actions/setup-python@v6
|
|
99
99
|
with:
|
|
100
100
|
python-version: |
|
|
101
101
|
3.9
|
|
@@ -28,7 +28,7 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
|
|
28
28
|
|
|
29
29
|
[[package]]
|
|
30
30
|
name = "cridecoder"
|
|
31
|
-
version = "0.2.
|
|
31
|
+
version = "0.2.2"
|
|
32
32
|
dependencies = [
|
|
33
33
|
"byteorder",
|
|
34
34
|
"encoding_rs",
|
|
@@ -204,9 +204,9 @@ dependencies = [
|
|
|
204
204
|
|
|
205
205
|
[[package]]
|
|
206
206
|
name = "pyo3"
|
|
207
|
-
version = "0.28.
|
|
207
|
+
version = "0.28.3"
|
|
208
208
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
209
|
-
checksum = "
|
|
209
|
+
checksum = "91fd8e38a3b50ed1167fb981cd6fd60147e091784c427b8f7183a7ee32c31c12"
|
|
210
210
|
dependencies = [
|
|
211
211
|
"libc",
|
|
212
212
|
"once_cell",
|
|
@@ -218,18 +218,18 @@ dependencies = [
|
|
|
218
218
|
|
|
219
219
|
[[package]]
|
|
220
220
|
name = "pyo3-build-config"
|
|
221
|
-
version = "0.28.
|
|
221
|
+
version = "0.28.3"
|
|
222
222
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
223
|
-
checksum = "
|
|
223
|
+
checksum = "e368e7ddfdeb98c9bca7f8383be1648fd84ab466bf2bc015e94008db6d35611e"
|
|
224
224
|
dependencies = [
|
|
225
225
|
"target-lexicon",
|
|
226
226
|
]
|
|
227
227
|
|
|
228
228
|
[[package]]
|
|
229
229
|
name = "pyo3-ffi"
|
|
230
|
-
version = "0.28.
|
|
230
|
+
version = "0.28.3"
|
|
231
231
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
232
|
-
checksum = "
|
|
232
|
+
checksum = "7f29e10af80b1f7ccaf7f69eace800a03ecd13e883acfacc1e5d0988605f651e"
|
|
233
233
|
dependencies = [
|
|
234
234
|
"libc",
|
|
235
235
|
"pyo3-build-config",
|
|
@@ -237,9 +237,9 @@ dependencies = [
|
|
|
237
237
|
|
|
238
238
|
[[package]]
|
|
239
239
|
name = "pyo3-macros"
|
|
240
|
-
version = "0.28.
|
|
240
|
+
version = "0.28.3"
|
|
241
241
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
242
|
-
checksum = "
|
|
242
|
+
checksum = "df6e520eff47c45997d2fc7dd8214b25dd1310918bbb2642156ef66a67f29813"
|
|
243
243
|
dependencies = [
|
|
244
244
|
"proc-macro2",
|
|
245
245
|
"pyo3-macros-backend",
|
|
@@ -249,9 +249,9 @@ dependencies = [
|
|
|
249
249
|
|
|
250
250
|
[[package]]
|
|
251
251
|
name = "pyo3-macros-backend"
|
|
252
|
-
version = "0.28.
|
|
252
|
+
version = "0.28.3"
|
|
253
253
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
254
|
-
checksum = "
|
|
254
|
+
checksum = "c4cdc218d835738f81c2338f822078af45b4afdf8b2e33cbb5916f108b813acb"
|
|
255
255
|
dependencies = [
|
|
256
256
|
"heck",
|
|
257
257
|
"proc-macro2",
|
|
@@ -326,9 +326,9 @@ dependencies = [
|
|
|
326
326
|
|
|
327
327
|
[[package]]
|
|
328
328
|
name = "serde_json"
|
|
329
|
-
version = "1.0.
|
|
329
|
+
version = "1.0.150"
|
|
330
330
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
331
|
-
checksum = "
|
|
331
|
+
checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9"
|
|
332
332
|
dependencies = [
|
|
333
333
|
"itoa",
|
|
334
334
|
"memchr",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "cridecoder"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.2"
|
|
4
4
|
edition = "2021"
|
|
5
5
|
description = "CRI codec library for ACB/AWB, HCA audio, and USM video extraction"
|
|
6
6
|
license = "MIT"
|
|
@@ -10,7 +10,18 @@ homepage = "https://github.com/Team-Haruki/cridecoder"
|
|
|
10
10
|
readme = "README.md"
|
|
11
11
|
keywords = ["cri", "hca", "acb", "usm", "audio"]
|
|
12
12
|
categories = ["multimedia::audio", "multimedia::video", "parser-implementations"]
|
|
13
|
-
exclude = [
|
|
13
|
+
exclude = [
|
|
14
|
+
"*.acb",
|
|
15
|
+
"*.usm",
|
|
16
|
+
"*.hca",
|
|
17
|
+
"*.wav",
|
|
18
|
+
"*.m2v",
|
|
19
|
+
"test_output_*",
|
|
20
|
+
".DS_Store",
|
|
21
|
+
".idea/*",
|
|
22
|
+
"Cargo.toml.orig",
|
|
23
|
+
"tests/__pycache__/*",
|
|
24
|
+
]
|
|
14
25
|
|
|
15
26
|
[lib]
|
|
16
27
|
crate-type = ["cdylib", "rlib"]
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
//! AFS2 archive parser
|
|
2
2
|
|
|
3
3
|
use crate::reader::{align, Reader};
|
|
4
|
+
use std::collections::HashMap;
|
|
4
5
|
use std::io::{Read, Seek, SeekFrom};
|
|
5
6
|
use thiserror::Error;
|
|
6
7
|
|
|
@@ -15,7 +16,7 @@ pub enum AfsError {
|
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
/// A file entry in an AFS2 archive
|
|
18
|
-
#[derive(Debug, Clone)]
|
|
19
|
+
#[derive(Debug, Clone, Copy)]
|
|
19
20
|
pub struct AfsFileEntry {
|
|
20
21
|
pub cue_id: i32,
|
|
21
22
|
pub offset: u32,
|
|
@@ -27,6 +28,7 @@ pub struct AfsArchive<R: Read + Seek> {
|
|
|
27
28
|
pub alignment: u32,
|
|
28
29
|
pub subkey: u16,
|
|
29
30
|
pub files: Vec<AfsFileEntry>,
|
|
31
|
+
cue_index: HashMap<i32, usize>,
|
|
30
32
|
reader: Reader<R>,
|
|
31
33
|
}
|
|
32
34
|
|
|
@@ -94,25 +96,30 @@ impl<R: Read + Seek> AfsArchive<R> {
|
|
|
94
96
|
});
|
|
95
97
|
}
|
|
96
98
|
|
|
99
|
+
let cue_index = files
|
|
100
|
+
.iter()
|
|
101
|
+
.enumerate()
|
|
102
|
+
.map(|(idx, file)| (file.cue_id, idx))
|
|
103
|
+
.collect();
|
|
104
|
+
|
|
97
105
|
Ok(Self {
|
|
98
106
|
alignment,
|
|
99
107
|
subkey,
|
|
100
108
|
files,
|
|
109
|
+
cue_index,
|
|
101
110
|
reader: buf,
|
|
102
111
|
})
|
|
103
112
|
}
|
|
104
113
|
|
|
105
114
|
/// Get file data for a specific cue ID
|
|
106
115
|
pub fn file_data_for_cue_id(&mut self, cue_id: i32) -> Result<Vec<u8>, AfsError> {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
return self.file_data(f.clone());
|
|
110
|
-
}
|
|
116
|
+
if let Some(&idx) = self.cue_index.get(&cue_id) {
|
|
117
|
+
return self.file_data_at_index(idx);
|
|
111
118
|
}
|
|
112
119
|
|
|
113
120
|
// Fallback to first file if cue IDs start at 0
|
|
114
121
|
if !self.files.is_empty() && self.files[0].cue_id == 0 {
|
|
115
|
-
return self.
|
|
122
|
+
return self.file_data_at_index(0);
|
|
116
123
|
}
|
|
117
124
|
|
|
118
125
|
Err(AfsError::CueNotFound(cue_id))
|
|
@@ -120,6 +127,15 @@ impl<R: Read + Seek> AfsArchive<R> {
|
|
|
120
127
|
|
|
121
128
|
/// Get file data for an entry
|
|
122
129
|
pub fn file_data(&mut self, entry: AfsFileEntry) -> Result<Vec<u8>, AfsError> {
|
|
130
|
+
self.file_data_by_entry(&entry)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
fn file_data_at_index(&mut self, index: usize) -> Result<Vec<u8>, AfsError> {
|
|
134
|
+
let entry = self.files[index];
|
|
135
|
+
self.file_data_by_entry(&entry)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
fn file_data_by_entry(&mut self, entry: &AfsFileEntry) -> Result<Vec<u8>, AfsError> {
|
|
123
139
|
Ok(self
|
|
124
140
|
.reader
|
|
125
141
|
.read_bytes_at(entry.size as usize, entry.offset as u64)?)
|
|
@@ -24,7 +24,6 @@ pub const HCA_MASK: u32 = 0x7F7F7F7F;
|
|
|
24
24
|
pub const HCA_SUBFRAMES: usize = 8;
|
|
25
25
|
pub const HCA_SAMPLES_PER_SUBFRAME: usize = 128;
|
|
26
26
|
pub const HCA_SAMPLES_PER_FRAME: usize = HCA_SUBFRAMES * HCA_SAMPLES_PER_SUBFRAME;
|
|
27
|
-
pub const HCA_MDCT_BITS: usize = 7;
|
|
28
27
|
pub const HCA_MIN_CHANNELS: u32 = 1;
|
|
29
28
|
pub const HCA_MAX_CHANNELS: usize = 16;
|
|
30
29
|
pub const HCA_MIN_SAMPLE_RATE: u32 = 1;
|
|
@@ -726,16 +725,36 @@ impl ClHca {
|
|
|
726
725
|
|
|
727
726
|
/// Read decoded samples as 16-bit PCM
|
|
728
727
|
pub fn read_samples_16(&self, samples: &mut [i16]) {
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
728
|
+
let channels = self.channels as usize;
|
|
729
|
+
|
|
730
|
+
match channels {
|
|
731
|
+
1 => {
|
|
732
|
+
for i in 0..HCA_SUBFRAMES {
|
|
733
|
+
let base = i * HCA_SAMPLES_PER_SUBFRAME;
|
|
734
|
+
for j in 0..HCA_SAMPLES_PER_SUBFRAME {
|
|
735
|
+
samples[base + j] = pcm_f32_to_i16(self.channel[0].wave[i][j]);
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
2 => {
|
|
740
|
+
for i in 0..HCA_SUBFRAMES {
|
|
741
|
+
let base = i * HCA_SAMPLES_PER_SUBFRAME * 2;
|
|
742
|
+
for j in 0..HCA_SAMPLES_PER_SUBFRAME {
|
|
743
|
+
let idx = base + j * 2;
|
|
744
|
+
samples[idx] = pcm_f32_to_i16(self.channel[0].wave[i][j]);
|
|
745
|
+
samples[idx + 1] = pcm_f32_to_i16(self.channel[1].wave[i][j]);
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
_ => {
|
|
750
|
+
let mut idx = 0;
|
|
751
|
+
for i in 0..HCA_SUBFRAMES {
|
|
752
|
+
for j in 0..HCA_SAMPLES_PER_SUBFRAME {
|
|
753
|
+
for k in 0..channels {
|
|
754
|
+
samples[idx] = pcm_f32_to_i16(self.channel[k].wave[i][j]);
|
|
755
|
+
idx += 1;
|
|
756
|
+
}
|
|
757
|
+
}
|
|
739
758
|
}
|
|
740
759
|
}
|
|
741
760
|
}
|
|
@@ -899,8 +918,10 @@ impl ClHca {
|
|
|
899
918
|
return Err(HcaError::ChecksumFailed);
|
|
900
919
|
}
|
|
901
920
|
|
|
902
|
-
// Decrypt
|
|
903
|
-
|
|
921
|
+
// Decrypt only when the file uses a non-identity cipher.
|
|
922
|
+
if self.ciph_type != 0 {
|
|
923
|
+
cipher_decrypt(&self.cipher_table, &mut data[..self.frame_size as usize]);
|
|
924
|
+
}
|
|
904
925
|
|
|
905
926
|
// Re-initialize bitreader after decryption
|
|
906
927
|
let mut br = BitReader::with_offset(data, 2); // Skip sync word
|
|
@@ -1113,40 +1134,54 @@ impl ClHca {
|
|
|
1113
1134
|
fn dequantize_coefficients(&mut self, ch: usize, br: &mut BitReader, subframe: usize) {
|
|
1114
1135
|
let channel = &mut self.channel[ch];
|
|
1115
1136
|
let cc_count = channel.coded_count;
|
|
1137
|
+
let spectra = &mut channel.spectra[subframe];
|
|
1116
1138
|
|
|
1117
|
-
for i in
|
|
1139
|
+
for (i, sample) in spectra.iter_mut().enumerate().take(cc_count) {
|
|
1118
1140
|
let resolution = channel.resolution[i];
|
|
1119
|
-
let
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1141
|
+
let qc = match resolution {
|
|
1142
|
+
0 => 0.0,
|
|
1143
|
+
1..=7 => {
|
|
1144
|
+
let bits = MAX_BIT_TABLE[resolution as usize];
|
|
1145
|
+
let code = br.read(bits as usize);
|
|
1146
|
+
let index = ((resolution as usize) << 4) + code as usize;
|
|
1147
|
+
let skip = READ_BIT_TABLE[index] as i32 - bits as i32;
|
|
1148
|
+
if skip > 0 {
|
|
1149
|
+
br.skip(skip as usize);
|
|
1150
|
+
} else if skip < 0 {
|
|
1151
|
+
br.set_position(br.position().saturating_sub((-skip) as usize));
|
|
1152
|
+
}
|
|
1153
|
+
READ_VAL_TABLE[index]
|
|
1128
1154
|
}
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1155
|
+
_ => {
|
|
1156
|
+
let bits = MAX_BIT_TABLE[resolution as usize];
|
|
1157
|
+
let code = br.read(bits as usize);
|
|
1158
|
+
// Sign-magnitude form: sign is bit 0, magnitude is bits 1+
|
|
1159
|
+
let sign = if (code & 1) != 0 { -1i32 } else { 1i32 };
|
|
1160
|
+
let signed_code = sign * (code >> 1) as i32;
|
|
1161
|
+
if signed_code == 0 {
|
|
1162
|
+
br.set_position(br.position() - 1);
|
|
1163
|
+
}
|
|
1164
|
+
signed_code as f32
|
|
1138
1165
|
}
|
|
1139
|
-
READ_VAL_TABLE[index]
|
|
1140
1166
|
};
|
|
1141
1167
|
|
|
1142
|
-
|
|
1168
|
+
*sample = channel.gain[i] * qc;
|
|
1143
1169
|
}
|
|
1144
1170
|
|
|
1145
1171
|
// Clean rest of spectra
|
|
1146
|
-
|
|
1172
|
+
spectra[cc_count..HCA_SAMPLES_PER_SUBFRAME].fill(0.0);
|
|
1147
1173
|
}
|
|
1148
1174
|
|
|
1149
1175
|
fn decode_block_transform(&mut self) {
|
|
1176
|
+
if self.min_resolution > 0 && self.bands_per_hfr_group == 0 && self.stereo_band_count == 0 {
|
|
1177
|
+
for subframe in 0..HCA_SUBFRAMES {
|
|
1178
|
+
for ch in 0..self.channels as usize {
|
|
1179
|
+
imdct_transform(&mut self.channel[ch], subframe);
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
return;
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1150
1185
|
for subframe in 0..HCA_SUBFRAMES {
|
|
1151
1186
|
// Restore missing bands
|
|
1152
1187
|
for ch in 0..self.channels as usize {
|
|
@@ -1286,6 +1321,13 @@ impl ClHca {
|
|
|
1286
1321
|
}
|
|
1287
1322
|
}
|
|
1288
1323
|
|
|
1324
|
+
#[inline]
|
|
1325
|
+
fn pcm_f32_to_i16(sample: f32) -> i16 {
|
|
1326
|
+
const SCALE_F: f32 = 32768.0;
|
|
1327
|
+
let scaled = (sample * SCALE_F) as i32;
|
|
1328
|
+
scaled.clamp(-32768, 32767) as i16
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1289
1331
|
#[cfg(test)]
|
|
1290
1332
|
mod tests {
|
|
1291
1333
|
use super::*;
|
|
@@ -155,6 +155,40 @@ impl<R: Read + Seek> HcaDecoder<R> {
|
|
|
155
155
|
Ok((&self.fbuf[start_idx..], num_samples))
|
|
156
156
|
}
|
|
157
157
|
|
|
158
|
+
/// Decode a single frame into interleaved 16-bit PCM samples.
|
|
159
|
+
///
|
|
160
|
+
/// Returns the number of sample frames written, not the number of i16
|
|
161
|
+
/// values. The caller-provided buffer must fit one full decoded HCA frame.
|
|
162
|
+
pub fn decode_frame_i16(&mut self, pcm: &mut [i16]) -> Result<usize, HcaDecoderError> {
|
|
163
|
+
let frame_len = self.info.samples_per_block * self.info.channel_count as usize;
|
|
164
|
+
if pcm.len() < frame_len {
|
|
165
|
+
return Err(HcaDecoderError::InvalidSampleRange);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
self.read_packet()?;
|
|
169
|
+
self.handle.decode_block(&mut self.buf)?;
|
|
170
|
+
self.handle.read_samples_16(&mut pcm[..frame_len]);
|
|
171
|
+
|
|
172
|
+
let samples = self.info.samples_per_block as i32;
|
|
173
|
+
let mut discard = 0;
|
|
174
|
+
|
|
175
|
+
if self.current_delay > 0 {
|
|
176
|
+
if self.current_delay >= samples {
|
|
177
|
+
self.current_delay -= samples;
|
|
178
|
+
return Ok(0);
|
|
179
|
+
}
|
|
180
|
+
discard = self.current_delay;
|
|
181
|
+
self.current_delay = 0;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
let start = discard as usize * self.info.channel_count as usize;
|
|
185
|
+
if start > 0 {
|
|
186
|
+
pcm.copy_within(start..frame_len, 0);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
Ok((samples - discard) as usize)
|
|
190
|
+
}
|
|
191
|
+
|
|
158
192
|
/// Decode the entire HCA file and return all samples
|
|
159
193
|
pub fn decode_all(&mut self) -> Result<Vec<f32>, HcaDecoderError> {
|
|
160
194
|
self.reset();
|
|
@@ -177,6 +211,35 @@ impl<R: Read + Seek> HcaDecoder<R> {
|
|
|
177
211
|
Ok(all_samples)
|
|
178
212
|
}
|
|
179
213
|
|
|
214
|
+
/// Decode the entire HCA file as interleaved 16-bit PCM chunks.
|
|
215
|
+
///
|
|
216
|
+
/// The callback receives only valid samples after encoder-delay trimming.
|
|
217
|
+
/// This is useful when piping HCA directly into an audio encoder without
|
|
218
|
+
/// materializing an intermediate WAV file.
|
|
219
|
+
pub fn decode_to_pcm16_chunks<F>(&mut self, mut on_chunk: F) -> Result<(), HcaDecoderError>
|
|
220
|
+
where
|
|
221
|
+
F: FnMut(&[i16]) -> Result<(), HcaDecoderError>,
|
|
222
|
+
{
|
|
223
|
+
self.reset();
|
|
224
|
+
|
|
225
|
+
let channels = self.info.channel_count as usize;
|
|
226
|
+
let mut pcm_buf = vec![0i16; self.info.samples_per_block * channels];
|
|
227
|
+
|
|
228
|
+
loop {
|
|
229
|
+
match self.decode_frame_i16(&mut pcm_buf) {
|
|
230
|
+
Ok(0) => {}
|
|
231
|
+
Ok(sample_frames) => {
|
|
232
|
+
let sample_count = sample_frames * channels;
|
|
233
|
+
on_chunk(&pcm_buf[..sample_count])?;
|
|
234
|
+
}
|
|
235
|
+
Err(HcaDecoderError::Eof) => break,
|
|
236
|
+
Err(e) => return Err(e),
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
Ok(())
|
|
241
|
+
}
|
|
242
|
+
|
|
180
243
|
/// Seek to a specific sample position
|
|
181
244
|
pub fn seek(&mut self, sample_num: u32) {
|
|
182
245
|
let target_sample = sample_num + self.info.encoder_delay;
|
|
@@ -309,48 +372,47 @@ impl<R: Read + Seek> HcaDecoder<R> {
|
|
|
309
372
|
|
|
310
373
|
w.write_all(&header)?;
|
|
311
374
|
|
|
312
|
-
let
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
loop {
|
|
316
|
-
match self.read_packet() {
|
|
317
|
-
Ok(()) => {}
|
|
318
|
-
Err(HcaDecoderError::Eof) => break,
|
|
319
|
-
Err(e) => return Err(e),
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
self.handle.decode_block(&mut self.buf)?;
|
|
323
|
-
self.handle.read_samples_16(&mut pcm_buf);
|
|
375
|
+
let frame_len = self.info.samples_per_block * self.info.channel_count as usize;
|
|
376
|
+
let mut data_buf = vec![0u8; frame_len * 2];
|
|
324
377
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
if self.current_delay > 0 {
|
|
329
|
-
if self.current_delay >= samples {
|
|
330
|
-
self.current_delay -= samples;
|
|
331
|
-
continue;
|
|
332
|
-
}
|
|
333
|
-
discard = self.current_delay;
|
|
334
|
-
self.current_delay = 0;
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
let start = discard as usize * self.info.channel_count as usize;
|
|
338
|
-
let end = samples as usize * self.info.channel_count as usize;
|
|
339
|
-
|
|
340
|
-
if start >= end || end > pcm_buf.len() {
|
|
378
|
+
self.decode_to_pcm16_chunks(|pcm| {
|
|
379
|
+
if pcm.len() > frame_len {
|
|
341
380
|
return Err(HcaDecoderError::InvalidSampleRange);
|
|
342
381
|
}
|
|
382
|
+
write_pcm_i16_le(w, pcm, &mut data_buf)?;
|
|
383
|
+
Ok(())
|
|
384
|
+
})
|
|
385
|
+
}
|
|
386
|
+
}
|
|
343
387
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
388
|
+
#[cfg(target_endian = "little")]
|
|
389
|
+
fn write_pcm_i16_le<W: Write>(
|
|
390
|
+
writer: &mut W,
|
|
391
|
+
samples: &[i16],
|
|
392
|
+
_scratch: &mut [u8],
|
|
393
|
+
) -> io::Result<()> {
|
|
394
|
+
let bytes = unsafe {
|
|
395
|
+
// SAFETY: i16 is a plain integer type, and on little-endian targets its
|
|
396
|
+
// in-memory representation is exactly the little-endian PCM byte order.
|
|
397
|
+
std::slice::from_raw_parts(
|
|
398
|
+
samples.as_ptr().cast::<u8>(),
|
|
399
|
+
std::mem::size_of_val(samples),
|
|
400
|
+
)
|
|
401
|
+
};
|
|
402
|
+
writer.write_all(bytes)
|
|
403
|
+
}
|
|
351
404
|
|
|
352
|
-
|
|
405
|
+
#[cfg(not(target_endian = "little"))]
|
|
406
|
+
fn write_pcm_i16_le<W: Write>(
|
|
407
|
+
writer: &mut W,
|
|
408
|
+
samples: &[i16],
|
|
409
|
+
scratch: &mut [u8],
|
|
410
|
+
) -> io::Result<()> {
|
|
411
|
+
let byte_len = samples.len() * 2;
|
|
412
|
+
for (chunk, &sample) in scratch[..byte_len].chunks_exact_mut(2).zip(samples) {
|
|
413
|
+
chunk.copy_from_slice(&sample.to_le_bytes());
|
|
353
414
|
}
|
|
415
|
+
writer.write_all(&scratch[..byte_len])
|
|
354
416
|
}
|
|
355
417
|
|
|
356
418
|
fn scale_frame_score(score: i32) -> i32 {
|