1use crate::error::AudioError;
2use crate::tier::EffectKind;
3
4pub const BANK_MAGIC: [u8; 4] = *b"EAFX";
5pub const BANK_VERSION: u8 = 2;
6pub const HEADER_SIZE: usize = 10;
7pub const ENTRY_SIZE: usize = 16;
8pub const BANK_BUILD_CAP: usize = 65_536;
10
11const MAX_EFFECTS: usize = 64;
12
13#[derive(Debug, Clone, Copy)]
15pub struct SoundBank<'a> {
16 data: &'a [u8],
17 pub sample_rate_hz: u32,
18 effect_count: u16,
19}
20
21#[derive(Debug, Clone, Copy)]
23pub struct EffectEntry {
24 pub id: u16,
25 pub kind: EffectKind,
26 pub flags: u8,
27 pub default_gain_q8: u8,
28 pub param0: u16,
29 pub param1: u16,
30 pub offset: u32,
31 pub len: u32,
32}
33
34impl EffectEntry {
35 pub fn payload<'a>(&self, bank: &'a SoundBank<'_>) -> Result<&'a [u8], AudioError> {
36 bank.payload(self)
37 }
38}
39
40impl<'a> SoundBank<'a> {
41 pub fn parse(data: &'a [u8]) -> Result<Self, AudioError> {
42 if data.len() < HEADER_SIZE {
43 return Err(AudioError::TruncatedBank);
44 }
45 if data[0..4] != BANK_MAGIC {
46 return Err(AudioError::InvalidBankMagic);
47 }
48 if data[4] != BANK_VERSION {
49 return Err(AudioError::UnsupportedBankVersion);
50 }
51 let effect_count = u16::from_le_bytes([data[5], data[6]]);
52 let sample_rate_hz = u16::from_le_bytes([data[7], data[8]]) as u32;
53 let sample_rate_hz = if sample_rate_hz == 0 {
54 crate::config::DEFAULT_SAMPLE_RATE_HZ
55 } else {
56 sample_rate_hz
57 };
58 let needed = HEADER_SIZE + effect_count as usize * ENTRY_SIZE;
59 if data.len() < needed {
60 return Err(AudioError::TruncatedBank);
61 }
62 Ok(Self {
63 data,
64 sample_rate_hz,
65 effect_count,
66 })
67 }
68
69 pub const fn effect_count(&self) -> u16 {
70 self.effect_count
71 }
72
73 pub fn entry(&self, index: usize) -> Result<EffectEntry, AudioError> {
74 if index >= self.effect_count as usize {
75 return Err(AudioError::EffectNotFound);
76 }
77 let off = HEADER_SIZE + index * ENTRY_SIZE;
78 let slice = &self.data[off..off + ENTRY_SIZE];
79 let id = u16::from_le_bytes([slice[0], slice[1]]);
80 let kind = EffectKind::from_u8(slice[2]).ok_or(AudioError::InvalidEffectKind)?;
81 let flags = slice[3];
82 let default_gain_q8 = slice[4];
83 let param0 = u16::from_le_bytes([slice[6], slice[7]]);
84 let param1 = u16::from_le_bytes([slice[8], slice[9]]);
85 let offset = u32::from_le_bytes([slice[10], slice[11], slice[12], slice[13]]);
86 let len = u16::from_le_bytes([slice[14], slice[15]]) as u32;
87 Ok(EffectEntry {
88 id,
89 kind,
90 flags,
91 default_gain_q8,
92 param0,
93 param1,
94 offset,
95 len,
96 })
97 }
98
99 pub fn find_by_id(&self, id: u16) -> Result<EffectEntry, AudioError> {
100 let mut low = 0i32;
101 let mut high = self.effect_count as i32 - 1;
102
103 while low <= high {
104 let mid = (low + high) / 2;
105 let entry = self.entry(mid as usize)?;
106 if entry.id == id {
107 return Ok(entry);
108 } else if entry.id < id {
109 low = mid + 1;
110 } else {
111 high = mid - 1;
112 }
113 }
114
115 for i in 0..self.effect_count as usize {
117 let e = self.entry(i)?;
118 if e.id == id {
119 return Ok(e);
120 }
121 }
122 Err(AudioError::EffectNotFound)
123 }
124
125 pub fn payload(&self, entry: &EffectEntry) -> Result<&'a [u8], AudioError> {
126 let start = entry.offset as usize;
127 let end = start + entry.len as usize;
128 self.data.get(start..end).ok_or(AudioError::TruncatedBank)
129 }
130}
131
132pub struct BankBuilder {
134 sample_rate_hz: u32,
135 entries: heapless::Vec<EffectEntry, MAX_EFFECTS>,
136 payload: heapless::Vec<u8, { BANK_BUILD_CAP }>,
137}
138
139impl BankBuilder {
140 pub fn new(sample_rate_hz: u32) -> Self {
141 Self {
142 sample_rate_hz,
143 entries: heapless::Vec::new(),
144 payload: heapless::Vec::new(),
145 }
146 }
147
148 #[allow(clippy::too_many_arguments)]
149 pub fn add_effect(
150 &mut self,
151 id: u16,
152 kind: EffectKind,
153 flags: u8,
154 default_gain_q8: u8,
155 param0: u16,
156 param1: u16,
157 bytes: &[u8],
158 ) -> Result<(), AudioError> {
159 if self.entries.len() >= MAX_EFFECTS {
160 return Err(AudioError::BankFull);
161 }
162 let offset =
163 (HEADER_SIZE + (self.entries.len() + 1) * ENTRY_SIZE + self.payload.len()) as u32;
164 self.payload
165 .extend_from_slice(bytes)
166 .map_err(|_| AudioError::BankFull)?;
167 self.entries
168 .push(EffectEntry {
169 id,
170 kind,
171 flags,
172 default_gain_q8,
173 param0,
174 param1,
175 offset,
176 len: bytes.len() as u32,
177 })
178 .map_err(|_| AudioError::BankFull)?;
179 Ok(())
180 }
181
182 pub fn finish(
183 &self,
184 out: &mut heapless::Vec<u8, { BANK_BUILD_CAP }>,
185 ) -> Result<(), AudioError> {
186 let mut sorted_entries = self.entries.clone();
187 for i in 0..sorted_entries.len() {
188 for j in (i + 1)..sorted_entries.len() {
189 if sorted_entries[i].id > sorted_entries[j].id {
190 sorted_entries.swap(i, j);
191 }
192 }
193 }
194
195 out.clear();
196 out.extend_from_slice(&BANK_MAGIC)
197 .map_err(|_| AudioError::BankFull)?;
198 out.push(BANK_VERSION).map_err(|_| AudioError::BankFull)?;
199 let count = sorted_entries.len() as u16;
200 out.push((count & 0xFF) as u8)
201 .map_err(|_| AudioError::BankFull)?;
202 out.push((count >> 8) as u8)
203 .map_err(|_| AudioError::BankFull)?;
204 let rate = self.sample_rate_hz.min(u16::MAX as u32) as u16;
205 out.push((rate & 0xFF) as u8)
206 .map_err(|_| AudioError::BankFull)?;
207 out.push((rate >> 8) as u8)
208 .map_err(|_| AudioError::BankFull)?;
209 out.push(0).map_err(|_| AudioError::BankFull)?; for e in &sorted_entries {
211 out.push((e.id & 0xFF) as u8)
212 .map_err(|_| AudioError::BankFull)?;
213 out.push((e.id >> 8) as u8)
214 .map_err(|_| AudioError::BankFull)?;
215 out.push(e.kind as u8).map_err(|_| AudioError::BankFull)?;
216 out.push(e.flags).map_err(|_| AudioError::BankFull)?;
217 out.push(e.default_gain_q8)
218 .map_err(|_| AudioError::BankFull)?;
219 out.push(0).map_err(|_| AudioError::BankFull)?;
220 out.push((e.param0 & 0xFF) as u8)
221 .map_err(|_| AudioError::BankFull)?;
222 out.push((e.param0 >> 8) as u8)
223 .map_err(|_| AudioError::BankFull)?;
224 out.push((e.param1 & 0xFF) as u8)
225 .map_err(|_| AudioError::BankFull)?;
226 out.push((e.param1 >> 8) as u8)
227 .map_err(|_| AudioError::BankFull)?;
228 out.push((e.offset & 0xFF) as u8)
229 .map_err(|_| AudioError::BankFull)?;
230 out.push(((e.offset >> 8) & 0xFF) as u8)
231 .map_err(|_| AudioError::BankFull)?;
232 out.push(((e.offset >> 16) & 0xFF) as u8)
233 .map_err(|_| AudioError::BankFull)?;
234 out.push(((e.offset >> 24) & 0xFF) as u8)
235 .map_err(|_| AudioError::BankFull)?;
236 out.push((e.len & 0xFF) as u8)
237 .map_err(|_| AudioError::BankFull)?;
238 out.push(((e.len >> 8) & 0xFF) as u8)
239 .map_err(|_| AudioError::BankFull)?;
240 }
241 out.extend_from_slice(&self.payload)
242 .map_err(|_| AudioError::BankFull)?;
243 Ok(())
244 }
245}