1#![allow(clippy::needless_range_loop)]
2
3use wasm_bindgen::prelude::*;
4
5pub mod core;
6pub mod lossless;
7pub mod lossy;
8pub mod streaming;
9
10mod reader;
11mod writer;
12
13pub use core::{
14 compute_crc32, metadata::*, rice, ChannelData, FloFile, FloResult, FrameType, ResidualEncoding,
15 HEADER_SIZE, MAGIC, VERSION_MAJOR, VERSION_MINOR,
16};
17pub use lossless::{lpc, Decoder, Encoder};
18pub use lossy::{
19 deserialize_frame, serialize_frame, BlockSize, Mdct, PsychoacousticModel, QualityPreset,
20 TransformDecoder as LossyDecoder, TransformEncoder as LossyEncoder, TransformFrame, WindowType,
21};
22pub use reader::Reader;
23pub use streaming::{
24 DecoderState, EncodedFrame, StreamingAudioInfo, StreamingDecoder, StreamingEncoder,
25};
26pub use writer::Writer;
27
28#[wasm_bindgen]
32#[derive(Debug, Clone)]
33pub struct AudioInfo {
34 #[wasm_bindgen(skip)]
36 pub version: String,
37 pub sample_rate: u32,
39 pub channels: u8,
41 pub bit_depth: u8,
43 pub total_frames: u64,
45 pub duration_secs: f64,
47 pub file_size: usize,
49 pub compression_ratio: f64,
51 pub crc_valid: bool,
53 pub is_lossy: bool,
55 pub lossy_quality: u8,
57}
58
59#[wasm_bindgen]
60impl AudioInfo {
61 #[wasm_bindgen(getter)]
62 pub fn version(&self) -> String {
63 self.version.clone()
64 }
65}
66
67fn to_js_err(e: String) -> JsValue {
71 JsValue::from_str(&e)
72}
73
74#[wasm_bindgen]
92pub fn encode(
93 samples: &[f32],
94 sample_rate: u32,
95 channels: u8,
96 bit_depth: u8,
97 metadata: Option<Vec<u8>>,
98) -> Result<Vec<u8>, JsValue> {
99 let encoder = Encoder::new(sample_rate, channels, bit_depth);
100 encoder
101 .encode(samples, &metadata.unwrap_or_default())
102 .map_err(to_js_err)
103}
104
105#[wasm_bindgen]
122pub fn encode_lossy(
123 samples: &[f32],
124 sample_rate: u32,
125 channels: u8,
126 _bit_depth: u8,
127 quality: u8,
128 metadata: Option<Vec<u8>>,
129) -> Result<Vec<u8>, JsValue> {
130 let quality_f32 = match quality {
132 0 => 0.0,
133 1 => 0.35,
134 2 => 0.55,
135 3 => 0.75,
136 _ => 1.0,
137 };
138
139 let mut encoder = lossy::TransformEncoder::new(sample_rate, channels, quality_f32);
140 encoder
141 .encode_to_flo(samples, &metadata.unwrap_or_default())
142 .map_err(to_js_err)
143}
144
145#[wasm_bindgen]
158pub fn encode_with_bitrate(
159 samples: &[f32],
160 sample_rate: u32,
161 channels: u8,
162 _bit_depth: u8,
163 target_bitrate_kbps: u32,
164 metadata: Option<Vec<u8>>,
165) -> Result<Vec<u8>, JsValue> {
166 let quality =
168 lossy::QualityPreset::from_bitrate(target_bitrate_kbps, sample_rate, channels).as_f32();
169 let mut encoder = lossy::TransformEncoder::new(sample_rate, channels, quality);
170 encoder
171 .encode_to_flo(samples, &metadata.unwrap_or_default())
172 .map_err(to_js_err)
173}
174
175#[wasm_bindgen]
186pub fn decode(data: &[u8]) -> Result<Vec<f32>, JsValue> {
187 let reader = Reader::new();
189 let file = reader.read(data).map_err(to_js_err)?;
190
191 let is_transform = file
193 .frames
194 .iter()
195 .any(|f| f.frame_type == (FrameType::Transform as u8));
196
197 if is_transform {
198 decode_transform_file(&file).map_err(to_js_err)
200 } else {
201 let decoder = Decoder::new();
203 decoder.decode_file(&file).map_err(to_js_err)
204 }
205}
206
207fn decode_transform_file(file: &FloFile) -> FloResult<Vec<f32>> {
216 let mut decoder = lossy::TransformDecoder::new(file.header.sample_rate, file.header.channels);
217 let mut all_samples = Vec::new();
218 let mut frame_count = 0;
219
220 for frame in &file.frames {
221 if frame.channels.is_empty() {
222 continue;
223 }
224
225 let frame_data = &frame.channels[0].residuals;
227
228 if let Some(transform_frame) = lossy::deserialize_frame(frame_data) {
229 let samples = decoder.decode_frame(&transform_frame);
230
231 if frame_count > 0 {
233 all_samples.extend(samples);
234 }
235 frame_count += 1;
236 } else {
237 return Err("Failed to deserialize transform frame".to_string());
238 }
239 }
240
241 Ok(all_samples)
242}
243
244#[wasm_bindgen]
252pub fn validate(data: &[u8]) -> Result<bool, JsValue> {
253 let reader = Reader::new();
254 match reader.read(data) {
255 Ok(file) => {
256 let start = (4 + file.header.header_size + file.header.toc_size) as usize;
257 let end = start + (file.header.data_size as usize);
258 if end <= data.len() {
259 let computed = core::crc32::compute(&data[start..end]);
260 Ok(computed == file.header.data_crc32)
261 } else {
262 Ok(false)
263 }
264 }
265 Err(_) => Ok(false),
266 }
267}
268
269#[wasm_bindgen]
277pub fn info(data: &[u8]) -> Result<AudioInfo, JsValue> {
278 let reader = Reader::new();
279 let file = reader.read(data).map_err(to_js_err)?;
280
281 let duration_secs = file.header.total_frames as f64;
282 let original_size = ((file.header.total_frames as f64)
283 * (file.header.sample_rate as f64)
284 * (file.header.channels as f64)
285 * ((file.header.bit_depth as f64) / 8.0)) as usize;
286 let compression_ratio = if !data.is_empty() {
287 (original_size as f64) / (data.len() as f64)
288 } else {
289 0.0
290 };
291
292 let start = (4 + file.header.header_size + file.header.toc_size) as usize;
294 let end = start + (file.header.data_size as usize);
295 let crc_valid = if end <= data.len() {
296 core::crc32::compute(&data[start..end]) == file.header.data_crc32
297 } else {
298 false
299 };
300
301 let is_lossy = (file.header.flags & 0x01) != 0;
303 let lossy_quality = ((file.header.flags >> 8) & 0x0f) as u8;
304
305 Ok(AudioInfo {
306 version: format!(
307 "{}.{}",
308 file.header.version_major, file.header.version_minor
309 ),
310 sample_rate: file.header.sample_rate,
311 channels: file.header.channels,
312 bit_depth: file.header.bit_depth,
313 total_frames: file.header.total_frames,
314 duration_secs,
315 file_size: data.len(),
316 compression_ratio,
317 crc_valid,
318 is_lossy,
319 lossy_quality,
320 })
321}
322
323#[wasm_bindgen]
325pub fn version() -> String {
326 format!("{}.{}", VERSION_MAJOR, VERSION_MINOR)
327}
328
329#[wasm_bindgen]
332pub struct WasmStreamingDecoder {
333 inner: StreamingDecoder,
334}
335
336#[wasm_bindgen]
337impl WasmStreamingDecoder {
338 #[wasm_bindgen(constructor)]
340 pub fn new() -> Self {
341 Self {
342 inner: StreamingDecoder::new(),
343 }
344 }
345
346 #[wasm_bindgen]
348 pub fn feed(&mut self, data: &[u8]) -> Result<bool, JsValue> {
349 self.inner.feed(data).map_err(to_js_err)
350 }
351
352 #[wasm_bindgen]
354 pub fn is_ready(&self) -> bool {
355 self.inner.state() == DecoderState::Ready
356 }
357
358 #[wasm_bindgen]
360 pub fn is_finished(&self) -> bool {
361 self.inner.state() == DecoderState::Finished
362 }
363
364 #[wasm_bindgen]
366 pub fn has_error(&self) -> bool {
367 self.inner.state() == DecoderState::Error
368 }
369
370 #[wasm_bindgen]
372 pub fn state(&self) -> String {
373 match self.inner.state() {
374 DecoderState::WaitingForHeader => "waiting_for_header".into(),
375 DecoderState::WaitingForToc => "waiting_for_toc".into(),
376 DecoderState::Ready => "ready".into(),
377 DecoderState::Finished => "finished".into(),
378 DecoderState::Error => "error".into(),
379 }
380 }
381
382 #[wasm_bindgen]
386 pub fn get_info(&self) -> Result<JsValue, JsValue> {
387 match self.inner.info() {
388 Some(info) => {
389 let obj = js_sys::Object::new();
390 js_sys::Reflect::set(&obj, &"sample_rate".into(), &info.sample_rate.into())?;
391 js_sys::Reflect::set(&obj, &"channels".into(), &info.channels.into())?;
392 js_sys::Reflect::set(&obj, &"bit_depth".into(), &info.bit_depth.into())?;
393 js_sys::Reflect::set(
394 &obj,
395 &"total_frames".into(),
396 &(info.total_frames as f64).into(),
397 )?;
398 js_sys::Reflect::set(&obj, &"is_lossy".into(), &info.is_lossy.into())?;
399 Ok(obj.into())
400 }
401 None => Ok(JsValue::NULL),
402 }
403 }
404
405 #[wasm_bindgen]
407 pub fn decode_available(&mut self) -> Result<Vec<f32>, JsValue> {
408 self.inner.decode_available().map_err(to_js_err)
409 }
410
411 #[wasm_bindgen]
425 pub fn next_frame(&mut self) -> Result<JsValue, JsValue> {
426 match self.inner.next_frame() {
427 Ok(Some(samples)) => {
428 let array = js_sys::Float32Array::new_with_length(samples.len() as u32);
429 array.copy_from(&samples);
430 Ok(array.into())
431 }
432 Ok(None) => Ok(JsValue::NULL),
433 Err(e) => Err(to_js_err(e)),
434 }
435 }
436
437 #[wasm_bindgen]
439 pub fn available_frames(&self) -> usize {
440 self.inner.available_frames()
441 }
442
443 #[wasm_bindgen]
445 pub fn current_frame_index(&self) -> usize {
446 self.inner.current_frame_index()
447 }
448
449 #[wasm_bindgen]
453 pub fn reset(&mut self) {
454 self.inner.reset();
455 }
456
457 #[wasm_bindgen]
459 pub fn buffered_bytes(&self) -> usize {
460 self.inner.buffered_bytes()
461 }
462}
463
464impl Default for WasmStreamingDecoder {
465 fn default() -> Self {
466 Self::new()
467 }
468}
469
470#[wasm_bindgen]
480pub fn create_metadata(
481 title: Option<String>,
482 artist: Option<String>,
483 album: Option<String>,
484) -> Result<Vec<u8>, JsValue> {
485 let meta = FloMetadata::with_basic(title, artist, album);
486 meta.to_msgpack()
487 .map_err(|e| JsValue::from_str(&e.to_string()))
488}
489
490#[wasm_bindgen]
498pub fn create_metadata_from_object(obj: JsValue) -> Result<Vec<u8>, JsValue> {
499 let meta: FloMetadata = serde_wasm_bindgen::from_value(obj)
500 .map_err(|e| JsValue::from_str(&format!("Invalid metadata: {}", e)))?;
501 meta.to_msgpack()
502 .map_err(|e| JsValue::from_str(&e.to_string()))
503}
504
505#[wasm_bindgen]
513pub fn get_metadata(data: &[u8]) -> Result<JsValue, JsValue> {
514 let reader = Reader::new();
515 let file = reader.read(data).map_err(to_js_err)?;
516
517 if file.metadata.is_empty() {
518 return Ok(JsValue::NULL);
519 }
520
521 let meta = FloMetadata::from_msgpack(&file.metadata)
522 .map_err(|e| JsValue::from_str(&format!("Invalid metadata: {}", e)))?;
523
524 serde_wasm_bindgen::to_value(&meta)
525 .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))
526}
527
528#[wasm_bindgen]
536pub fn get_cover_art(data: &[u8]) -> Result<JsValue, JsValue> {
537 let reader = Reader::new();
538 let file = reader.read(data).map_err(to_js_err)?;
539
540 if file.metadata.is_empty() {
541 return Ok(JsValue::NULL);
542 }
543
544 let meta = FloMetadata::from_msgpack(&file.metadata)
545 .map_err(|e| JsValue::from_str(&format!("Invalid metadata: {}", e)))?;
546
547 if let Some(pic) = meta.front_cover().or_else(|| meta.any_picture()) {
549 let obj = js_sys::Object::new();
550 js_sys::Reflect::set(&obj, &"mime_type".into(), &pic.mime_type.clone().into())?;
551 js_sys::Reflect::set(
552 &obj,
553 &"data".into(),
554 &js_sys::Uint8Array::from(&pic.data[..]).into(),
555 )?;
556 if let Some(ref desc) = pic.description {
557 js_sys::Reflect::set(&obj, &"description".into(), &desc.clone().into())?;
558 }
559 Ok(obj.into())
560 } else {
561 Ok(JsValue::NULL)
562 }
563}
564
565#[wasm_bindgen]
578pub fn set_metadata_field(
579 metadata: Option<Vec<u8>>,
580 field: &str,
581 value: JsValue,
582) -> Result<Vec<u8>, JsValue> {
583 let meta = match &metadata {
585 Some(data) if !data.is_empty() => FloMetadata::from_msgpack(data)
586 .map_err(|e| JsValue::from_str(&format!("Invalid metadata: {}", e)))?,
587 _ => FloMetadata::new(),
588 };
589
590 let mut obj: serde_json::Value = serde_json::to_value(&meta)
592 .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))?;
593
594 let json_value = if value.is_null() || value.is_undefined() {
596 serde_json::Value::Null
597 } else if let Some(s) = value.as_string() {
598 serde_json::Value::String(s)
599 } else if let Some(n) = value.as_f64() {
600 serde_json::json!(n)
601 } else if let Some(b) = value.as_bool() {
602 serde_json::Value::Bool(b)
603 } else {
604 let js_json = js_sys::JSON::stringify(&value)
606 .map_err(|_| JsValue::from_str("Cannot serialize value"))?;
607 serde_json::from_str(&js_json.as_string().unwrap_or_default())
608 .map_err(|e| JsValue::from_str(&format!("Invalid JSON: {}", e)))?
609 };
610
611 if let serde_json::Value::Object(ref mut map) = obj {
613 map.insert(field.to_string(), json_value);
614 } else {
615 return Err(JsValue::from_str("Internal error: metadata not an object"));
616 }
617
618 let updated: FloMetadata = serde_json::from_value(obj)
620 .map_err(|e| JsValue::from_str(&format!("Invalid field '{}': {}", field, e)))?;
621
622 updated
623 .to_msgpack()
624 .map_err(|e| JsValue::from_str(&e.to_string()))
625}
626
627#[wasm_bindgen]
632pub fn get_synced_lyrics(data: &[u8]) -> Result<JsValue, JsValue> {
633 let reader = Reader::new();
634 let file = reader.read(data).map_err(to_js_err)?;
635
636 if file.metadata.is_empty() {
637 return Ok(JsValue::NULL);
638 }
639
640 let meta = FloMetadata::from_msgpack(&file.metadata)
641 .map_err(|e| JsValue::from_str(&format!("Invalid metadata: {}", e)))?;
642
643 if meta.synced_lyrics.is_empty() {
644 return Ok(JsValue::NULL);
645 }
646
647 serde_wasm_bindgen::to_value(&meta.synced_lyrics)
648 .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))
649}
650
651#[wasm_bindgen]
656pub fn get_waveform_data(data: &[u8]) -> Result<JsValue, JsValue> {
657 let reader = Reader::new();
658 let file = reader.read(data).map_err(to_js_err)?;
659
660 if file.metadata.is_empty() {
661 return Ok(JsValue::NULL);
662 }
663
664 let meta = FloMetadata::from_msgpack(&file.metadata)
665 .map_err(|e| JsValue::from_str(&format!("Invalid metadata: {}", e)))?;
666
667 match meta.waveform_data {
668 Some(ref waveform) => serde_wasm_bindgen::to_value(waveform)
669 .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e))),
670 None => Ok(JsValue::NULL),
671 }
672}
673
674#[wasm_bindgen]
679pub fn get_section_markers(data: &[u8]) -> Result<JsValue, JsValue> {
680 let reader = Reader::new();
681 let file = reader.read(data).map_err(to_js_err)?;
682
683 if file.metadata.is_empty() {
684 return Ok(JsValue::NULL);
685 }
686
687 let meta = FloMetadata::from_msgpack(&file.metadata)
688 .map_err(|e| JsValue::from_str(&format!("Invalid metadata: {}", e)))?;
689
690 if meta.section_markers.is_empty() {
691 return Ok(JsValue::NULL);
692 }
693
694 serde_wasm_bindgen::to_value(&meta.section_markers)
695 .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))
696}
697
698#[wasm_bindgen]
709pub fn update_metadata(flo_data: &[u8], new_metadata: &[u8]) -> Result<Vec<u8>, JsValue> {
710 update_metadata_bytes(flo_data, new_metadata).map_err(to_js_err)
711}
712
713pub fn update_metadata_bytes(flo_data: &[u8], new_metadata: &[u8]) -> FloResult<Vec<u8>> {
715 if flo_data.len() < HEADER_SIZE as usize {
717 return Err("File too small to be valid flo".to_string());
718 }
719
720 if flo_data[0..4] != MAGIC {
722 return Err("Invalid flo file: bad magic".to_string());
723 }
724
725 let reader = Reader::new();
727 let file = reader.read(flo_data)?;
728
729 let meta_offset = 4
732 + file.header.header_size as usize
733 + file.header.toc_size as usize
734 + file.header.data_size as usize
735 + file.header.extra_size as usize;
736
737 let mut result = Vec::with_capacity(meta_offset + new_metadata.len());
739 result.extend_from_slice(&flo_data[..meta_offset]);
740
741 result.extend_from_slice(new_metadata);
743
744 let meta_size_offset = 4 + 2 + 2 + 4 + 1 + 1 + 8 + 1 + 3 + 4 + 8 + 8 + 8 + 8;
748 let new_meta_size = new_metadata.len() as u64;
749 result[meta_size_offset..meta_size_offset + 8].copy_from_slice(&new_meta_size.to_le_bytes());
750
751 Ok(result)
752}
753
754#[wasm_bindgen]
765pub fn set_metadata(flo_data: &[u8], metadata: JsValue) -> Result<Vec<u8>, JsValue> {
766 let new_meta_bytes = create_metadata_from_object(metadata)?;
767 update_metadata(flo_data, &new_meta_bytes)
768}
769
770pub fn strip_metadata_bytes(flo_data: &[u8]) -> FloResult<Vec<u8>> {
778 update_metadata_bytes(flo_data, &[])
779}
780
781#[wasm_bindgen]
789pub fn strip_metadata(flo_data: &[u8]) -> Result<Vec<u8>, JsValue> {
790 strip_metadata_bytes(flo_data).map_err(to_js_err)
791}
792
793#[wasm_bindgen]
801pub fn get_metadata_bytes(flo_data: &[u8]) -> Result<Vec<u8>, JsValue> {
802 get_metadata_bytes_native(flo_data).map_err(to_js_err)
803}
804
805pub fn get_metadata_bytes_native(flo_data: &[u8]) -> FloResult<Vec<u8>> {
813 if flo_data.len() < HEADER_SIZE as usize {
814 return Err("File too small".to_string());
815 }
816
817 let reader = Reader::new();
819 let file = reader.read(flo_data)?;
820
821 Ok(file.metadata)
822}
823
824#[wasm_bindgen]
826pub fn has_metadata(flo_data: &[u8]) -> bool {
827 if flo_data.len() < HEADER_SIZE as usize {
828 return false;
829 }
830
831 let meta_size_offset = 4 + 2 + 2 + 4 + 1 + 1 + 8 + 1 + 3 + 4 + 8 + 8 + 8 + 8;
833 if flo_data.len() < meta_size_offset + 8 {
834 return false;
835 }
836
837 let meta_size = u64::from_le_bytes(
838 flo_data[meta_size_offset..meta_size_offset + 8]
839 .try_into()
840 .unwrap_or([0; 8]),
841 );
842
843 meta_size > 0
844}
845
846#[cfg(test)]
849mod tests {
850 use super::*;
851
852 #[test]
853 fn test_frame_type_conversion() {
854 assert_eq!(FrameType::from(0), FrameType::Silence);
855 assert_eq!(FrameType::from(8), FrameType::Alpc8);
856 assert_eq!(FrameType::from(254), FrameType::Raw);
857 assert!(FrameType::Alpc8.is_alpc());
858 assert!(!FrameType::Silence.is_alpc());
859 assert_eq!(FrameType::Alpc8.lpc_order(), Some(8));
860 }
861
862 #[test]
863 fn test_version() {
864 assert_eq!(version(), "1.2");
865 }
866
867 #[test]
868 fn test_update_metadata_preserves_audio() {
869 let samples: Vec<f32> = (0..4410).map(|i| (i as f32 * 0.01).sin() * 0.5).collect();
871 let encoder = Encoder::new(44100, 1, 16);
872 let original_meta = b"original metadata";
873 let flo_data = encoder.encode(&samples, original_meta).unwrap();
874
875 let new_meta = b"new metadata that is longer!";
877 let updated = update_metadata_bytes(&flo_data, new_meta).unwrap();
878
879 let reader = Reader::new();
881 let file = reader.read(&updated).unwrap();
882
883 assert_eq!(file.metadata, new_meta);
885 assert_eq!(file.header.meta_size, new_meta.len() as u64);
886
887 let original_file = reader.read(&flo_data).unwrap();
889 assert_eq!(file.header.data_crc32, original_file.header.data_crc32);
890
891 let decoder = Decoder::new();
893 let original_samples = decoder.decode(&flo_data).unwrap();
894 let updated_samples = decoder.decode(&updated).unwrap();
895 assert_eq!(original_samples, updated_samples);
896 }
897
898 #[test]
899 fn test_strip_metadata() {
900 let samples: Vec<f32> = vec![0.5; 1000];
901 let encoder = Encoder::new(44100, 1, 16);
902 let flo_with_meta = encoder.encode(&samples, b"some metadata here").unwrap();
903
904 let stripped = update_metadata_bytes(&flo_with_meta, &[]).unwrap();
906
907 let reader = Reader::new();
909 let file = reader.read(&stripped).unwrap();
910 assert!(file.metadata.is_empty());
911 assert_eq!(file.header.meta_size, 0);
912
913 assert!(stripped.len() < flo_with_meta.len());
915 }
916
917 #[test]
918 fn test_has_metadata() {
919 let samples: Vec<f32> = vec![0.5; 1000];
920 let encoder = Encoder::new(44100, 1, 16);
921
922 let with_meta = encoder.encode(&samples, b"metadata").unwrap();
923 let without_meta = encoder.encode(&samples, &[]).unwrap();
924
925 assert!(has_metadata(&with_meta));
926 assert!(!has_metadata(&without_meta));
927 }
928}