use super::super::nam_json::{NamMetadata, NamModelData};
use super::error::NambError;
use super::fallback::make_fallback_model_data;
use super::header::{FLAG_HAS_CRC32, NambHeader, check_crc};
use anyhow::Result;
use log::{debug, info};
pub fn parse_namb(data: &[u8]) -> Result<NamModelData> {
let header_size = std::mem::size_of::<NambHeader>();
if data.len() < header_size {
return Err(NambError::Truncated {
got: data.len(),
need: header_size,
}
.into());
}
let header = unsafe { core::ptr::read_unaligned(data.as_ptr().cast::<NambHeader>()) };
header.validate()?;
let hdr_version = header.version;
let hdr_weights_offset = header.weights_offset as usize;
let hdr_flags = header.flags;
let file_size = data.len();
info!(
"[Loader] .namb header v{} — weights_offset={}, flags=0x{:02X}, file_size={}",
hdr_version, hdr_weights_offset, hdr_flags, file_size
);
let weights_offset = header.weights_offset as usize;
if weights_offset > data.len() {
return Err(NambError::WeightsOffsetOutOfBounds {
offset: weights_offset,
file_len: data.len(),
}
.into());
}
if weights_offset < header_size {
return Err(NambError::InvalidWeightsOffset {
offset: weights_offset,
header_size,
}
.into());
}
let mut model_data = if weights_offset > header_size {
let json_bytes = &data[header_size..weights_offset];
let actual_json = if let Some(pos) = json_bytes.iter().position(|&b| b == 0) {
&json_bytes[..pos]
} else {
json_bytes
};
if !actual_json.is_empty() {
debug!(
"[Loader] .namb JSON metadata section: {} bytes",
actual_json.len()
);
crate::loader::nam_json::parse_nam_json(std::str::from_utf8(actual_json)?)?
} else {
debug!("[Loader] .namb has no JSON metadata — using fallback defaults");
make_fallback_model_data()
}
} else {
debug!("[Loader] .namb has no JSON metadata gap — using fallback defaults");
make_fallback_model_data()
};
let version = header.version;
let crc32_header = header.crc32;
if version >= 2 && hdr_flags & FLAG_HAS_CRC32 == 0 {
return Err(NambError::CrcMissing { version }.into());
}
if version == 1 && crc32_header == 0 {
return Err(NambError::CrcMismatch {
got: 0,
expected: 0,
}
.into());
}
check_crc(data, version, weights_offset, crc32_header)?;
debug!(
"[Loader] .namb CRC32 validated (v{}, crc=0x{:08X})",
version, crc32_header
);
let pesos_raw = &data[weights_offset..];
if !pesos_raw.len().is_multiple_of(4) {
let expected_len = weights_offset + pesos_raw.len() + (4 - pesos_raw.len() % 4);
return Err(NambError::Truncated {
got: data.len(),
need: expected_len,
}
.into());
}
let float_count = pesos_raw.len() / 4;
if float_count > super::MAX_FLOAT_COUNT {
return Err(NambError::WeightsTooLarge {
got: float_count,
max: super::MAX_FLOAT_COUNT,
}
.into());
}
let mut weights = Vec::with_capacity(float_count);
for (i, chunk) in pesos_raw.chunks_exact(4).enumerate() {
let val = f32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]);
if !val.is_finite() {
return Err(NambError::NonFiniteWeight {
index: i,
value: val,
}
.into());
}
weights.push(val);
}
let sample_rate_header = header.sample_rate;
let input_level_header = header.input_level_dbu;
let output_level_header = header.output_level_dbu;
debug!(
"[Loader] .namb weights read: {} floats, sample_rate={:.0}, in_level={:.1}, out_level={:.1}",
float_count, sample_rate_header, input_level_header, output_level_header
);
if !sample_rate_header.is_finite() {
return Err(NambError::InvalidHeaderField {
field: "sample_rate",
value: sample_rate_header,
reason: "must be finite",
}
.into());
}
if sample_rate_header <= 0.0 {
return Err(NambError::InvalidHeaderField {
field: "sample_rate",
value: sample_rate_header,
reason: "must be > 0.0",
}
.into());
}
if !input_level_header.is_finite() {
return Err(NambError::InvalidHeaderField {
field: "input_level_dbu",
value: input_level_header,
reason: "must be finite",
}
.into());
}
if !output_level_header.is_finite() {
return Err(NambError::InvalidHeaderField {
field: "output_level_dbu",
value: output_level_header,
reason: "must be finite",
}
.into());
}
let version_header = header.version;
model_data.weights = weights;
model_data.sample_rate = Some(sample_rate_header);
model_data.weights_layout = header.get_layout();
if let Some(ref mut metadata) = model_data.metadata {
metadata.input_level_dbu = Some(input_level_header);
metadata.output_level_dbu = Some(output_level_header);
} else {
model_data.metadata = Some(NamMetadata {
date: None,
name: None,
modeled_by: None,
gear_make: None,
gear_model: None,
gear_type: None,
tone_type: None,
training: None,
input_level_dbu: Some(input_level_header),
output_level_dbu: Some(output_level_header),
loudness: Some(-18.0),
});
}
if model_data.version.is_none() {
let end_idx = header
.version_str
.iter()
.position(|&b| b == 0)
.unwrap_or(32);
let version_str = String::from_utf8_lossy(&header.version_str[..end_idx]).into_owned();
model_data.version = Some(version_str);
}
let arch = model_data.architecture.as_str();
info!(
"[Loader] .namb v{} loaded (arch={}, {} weights, layout={:?})",
version_header, arch, float_count, model_data.weights_layout
);
Ok(model_data)
}