1use base64::{engine::general_purpose::STANDARD as B64, Engine as _};
4use qualia_audio::capture::{CapturePurpose, CaptureSession};
5use qualia_audio::cross_modal::{
6 frames_to_media_ms, propose_temporal_correlations, TimeIntervalMs,
7};
8use qualia_audio::generation::{synthesize_reference_tone, VoiceConsent};
9use qualia_audio::pipeline::{
10 run_ears_demo, run_ears_on_wav_file, run_ears_weighted, section18_smoke, sonify_demo_to_wav,
11 speech_phone_demo, EarsDemoResult,
12};
13use qualia_audio::semantic::{human_correct_quin, human_reject_quin};
14use serde::Serialize;
15
16#[derive(Debug, Clone, Serialize)]
17pub struct EarsDemoDto {
18 pub sample_rate: u32,
19 pub frames: u32,
20 pub n_events: usize,
21 pub n_quins: usize,
22 pub model_hash: String,
23 pub media_hash: String,
24 pub is_reference: bool,
25 pub mel_frames: usize,
26 pub cqt_peak: f32,
27 pub event_instance_hashes: Vec<String>,
28 pub note: String,
29}
30
31impl From<EarsDemoResult> for EarsDemoDto {
32 fn from(r: EarsDemoResult) -> Self {
33 let instances: Vec<String> = r
34 .events
35 .iter()
36 .map(|e| {
37 format!(
38 "0x{:016x}",
39 e.source_hash ^ e.start_frame ^ e.end_frame.wrapping_mul(0x9e37_79b9)
40 )
41 })
42 .collect();
43 Self {
44 sample_rate: r.sample_rate,
45 frames: r.frames,
46 n_events: r.n_events,
47 n_quins: r.n_quins,
48 model_hash: format!("0x{:016x}", r.model_hash),
49 media_hash: format!("0x{:016x}", r.media_hash),
50 is_reference: r.is_reference,
51 mel_frames: r.mel_frames,
52 cqt_peak: r.cqt_peak,
53 event_instance_hashes: instances,
54 note: r.note,
55 }
56 }
57}
58
59pub fn ears_demo(storage_root: Option<&std::path::Path>) -> Result<EarsDemoDto, String> {
60 run_ears_demo(storage_root, 440.0, 16000, 300).map(Into::into)
61}
62
63#[derive(Debug, Clone, Serialize)]
65pub struct AudioCapabilityDto {
66 pub id: String,
67 pub domain: String,
68 pub status: String,
69 pub zero_heap_hot: bool,
70 pub streaming: bool,
71 pub test_name: String,
72 pub note: String,
73}
74
75pub fn audio_capabilities() -> Vec<AudioCapabilityDto> {
77 qualia_audio::capability_registry::CAPABILITIES
78 .iter()
79 .map(|c| AudioCapabilityDto {
80 id: c.id.to_string(),
81 domain: c.domain.as_str().to_string(),
82 status: c.status.as_str().to_string(),
83 zero_heap_hot: c.zero_heap_hot,
84 streaming: c.streaming,
85 test_name: c.test_name.to_string(),
86 note: c.note.to_string(),
87 })
88 .collect()
89}
90
91#[derive(Debug, Clone, Serialize)]
92pub struct CrossModalDemoDto {
93 pub n_correlations: usize,
94 pub asserts_causality_any: bool,
95 pub note: String,
96}
97
98pub fn cross_modal_demo() -> CrossModalDemoDto {
99 let v = [TimeIntervalMs {
100 start_ms: 0,
101 end_ms: 2000,
102 instance: 0x15,
103 }];
104 let a_start = frames_to_media_ms(8000, 16000, 0);
105 let a = [TimeIntervalMs {
106 start_ms: a_start,
107 end_ms: a_start + 1000,
108 instance: 0xA0,
109 }];
110 let mut out = [qualia_audio::AvCorrelationProposal {
111 media_hash: 1,
112 visual_instance: 0,
113 auditory_instance: 0,
114 overlap_start_ms: 0,
115 overlap_end_ms: 0,
116 confidence_u16: 0,
117 asserts_causality: true,
118 }; 8];
119 let n = propose_temporal_correlations(1, &v, &a, &mut out);
120 CrossModalDemoDto {
121 n_correlations: n,
122 asserts_causality_any: out[..n].iter().any(|c| c.asserts_causality),
123 note: "Temporal overlap only — asserts_causality always false.".into(),
124 }
125}
126
127pub fn synth_with_consent(allow: bool) -> Result<u32, String> {
128 let c = if allow {
129 VoiceConsent::synthesis_only("demo-voice")
130 } else {
131 VoiceConsent::denied("demo-voice")
132 };
133 let mut o = [0.0f32; 512];
134 let r =
135 synthesize_reference_tone(c, 440.0, 16000, 512, 1, &mut o).map_err(|e| format!("{e:?}"))?;
136 Ok(r.frames)
137}
138
139pub fn ears_from_wav(
140 storage_root: Option<&std::path::Path>,
141 path: &std::path::Path,
142) -> Result<EarsDemoDto, String> {
143 run_ears_on_wav_file(storage_root, path).map(Into::into)
144}
145
146pub fn section18_smoke_dto() -> Result<String, String> {
147 section18_smoke()
148}
149
150pub fn audio_reject_instance(instance_hex: &str) -> Result<String, String> {
151 let inst = parse_hex(instance_hex)?;
152 let q = human_reject_quin(qualia_audio::q_hash("did:webizen:local-principal"), inst, 0);
153 Ok(format!(
154 "reject_quin parity=0x{:016x} instance=0x{:016x} (machine claim retained)",
155 q.parity, inst
156 ))
157}
158
159pub fn audio_correct_instance(instance_hex: &str, new_class_hex: &str) -> Result<String, String> {
160 let inst = parse_hex(instance_hex)?;
161 let cls = parse_hex(new_class_hex)?;
162 let q = human_correct_quin(
163 qualia_audio::q_hash("did:webizen:local-principal"),
164 inst,
165 cls,
166 );
167 Ok(format!(
168 "correct_quin parity=0x{:016x} new_class=0x{:016x}",
169 q.parity, cls
170 ))
171}
172
173fn parse_hex(s: &str) -> Result<u64, String> {
174 let t = s.trim().trim_start_matches("0x").trim_start_matches("0X");
175 u64::from_str_radix(t, 16).map_err(|e| e.to_string())
176}
177
178pub fn ears_weighted_demo(storage_root: Option<&std::path::Path>) -> Result<EarsDemoDto, String> {
179 run_ears_weighted(storage_root, 440.0, 16000, 300).map(Into::into)
180}
181
182pub fn sonify_ears_demo(
184 storage_root: Option<&std::path::Path>,
185) -> Result<serde_json::Value, String> {
186 let r = run_ears_demo(None, 440.0, 16000, 400)?;
187 let wav = sonify_demo_to_wav(r.sample_rate, &r.events, r.frames as usize)?;
188 let mut path_out = None;
189 if let Some(root) = storage_root {
190 let dir = root.join("audio_hear");
191 std::fs::create_dir_all(&dir).map_err(|e| e.to_string())?;
192 let p = dir.join(format!("sonify_{:016x}.wav", r.media_hash));
193 std::fs::write(&p, &wav).map_err(|e| e.to_string())?;
194 path_out = Some(p.display().to_string());
195 }
196 Ok(serde_json::json!({
197 "wav_data_url": format!("data:audio/wav;base64,{}", B64.encode(&wav)),
198 "path": path_out,
199 "n_events": r.n_events,
200 "frames": r.frames,
201 "sample_rate": r.sample_rate,
202 "note": "Parametric sonification of event intervals (U3-style navigate/hear). Not original PCM playback."
203 }))
204}
205
206pub fn speech_demo(supported: bool) -> Result<serde_json::Value, String> {
207 let (n, model) = speech_phone_demo(supported)?;
208 Ok(serde_json::json!({
209 "tokens": n,
210 "model_hash": format!("0x{:016x}", model),
211 "language_supported": supported,
212 "note": if supported {
213 "Greedy phone decode over seed speech weights (not full ASR)."
214 } else {
215 "Unknown language: empty transcript (no silent map)."
216 }
217 }))
218}
219
220pub fn capture_policy_demo() -> Result<serde_json::Value, String> {
222 let mut s = CaptureSession::new(CapturePurpose::Analysis, 16000, 1);
223 let denied = s.start().is_err();
224 s.grant_intent();
225 s.start().map_err(|e| format!("{e:?}"))?;
226 let pushed = s.push_mono(&[0.1, 0.2, 0.3, 0.0]);
227 let mut out = [0.0f32; 8];
228 let pulled = s.pull_mono(&mut out);
229 Ok(serde_json::json!({
230 "denied_without_intent": denied,
231 "pushed": pushed,
232 "pulled": pulled,
233 "note": "Shell must call grant_intent before device/file stream. Hardware mic via shell push_mono."
234 }))
235}
236
237pub fn ensure_aed_weights(storage_root: &std::path::Path) -> Result<String, String> {
239 let path = storage_root.join("models").join("aed_seed.qaed");
240 if path.is_file() {
241 let b = qualia_audio::AedWeightBundle::load_path(&path)?;
242 return Ok(format!(
243 "loaded AED weights hash=0x{:016x} path={}",
244 b.model_hash,
245 path.display()
246 ));
247 }
248 let b = qualia_audio::AedWeightBundle::from_seed(0xAED1);
249 b.save_path(&path)?;
250 Ok(format!(
251 "wrote AED seed weights hash=0x{:016x} path={}",
252 b.model_hash,
253 path.display()
254 ))
255}
256
257pub fn ensure_speech_weights(storage_root: &std::path::Path) -> Result<String, String> {
258 let path = storage_root.join("models").join("speech_seed.qspk");
259 if path.is_file() {
260 let w = qualia_audio::SpeechEncoderWeights::load_path(&path)?;
261 return Ok(format!(
262 "loaded speech weights hash=0x{:016x} path={}",
263 w.model_hash,
264 path.display()
265 ));
266 }
267 let w = qualia_audio::SpeechEncoderWeights::from_seed(7, 16);
268 w.save_path(&path)?;
269 Ok(format!(
270 "wrote speech seed weights hash=0x{:016x} path={}",
271 w.model_hash,
272 path.display()
273 ))
274}
275
276pub fn daw_history_demo() -> Result<serde_json::Value, String> {
277 use qualia_audio::{OpKind, ProcessPlan, SessionHistory, SessionOp, TrackState};
278 let mut plan = ProcessPlan::new(48000, 64);
279 plan.add_track(TrackState::default());
280 let mut hist = SessionHistory::new();
281 hist.apply_and_record(
282 &mut plan,
283 SessionOp {
284 kind: OpKind::SetGain,
285 track: 0,
286 value_f32: 0.4,
287 value_bool: false,
288 prev_f32: 1.0,
289 prev_bool: false,
290 },
291 );
292 let g1 = plan.tracks[0].gain;
293 hist.undo(&mut plan);
294 let g0 = plan.tracks[0].gain;
295 hist.redo(&mut plan);
296 let g2 = plan.tracks[0].gain;
297 let mut lane = qualia_audio::AutomationLane::new(0);
298 lane.add(0, 0.0);
299 lane.add(1000, 1.0);
300 let mid = lane.value_at(500);
301 Ok(serde_json::json!({
302 "gain_after_set": g1,
303 "gain_after_undo": g0,
304 "gain_after_redo": g2,
305 "automation_mid": mid,
306 "note": "SessionHistory undo/redo + AutomationLane interp (cold path)."
307 }))
308}
309
310pub fn analyze_mono_pcm(
312 mono: &[f32],
313 sample_rate: u32,
314 storage_root: Option<&std::path::Path>,
315) -> Result<EarsDemoDto, String> {
316 use qualia_audio::types::{AuditoryEvent, AuditoryModel, TranscriptToken};
317 use qualia_audio::wav::encode_wav_i16_mono;
318 use qualia_audio::{AedWeightBundle, WeightedAedModel};
319
320 if mono.is_empty() {
321 return Err("no PCM (arm mic or import WAV first)".into());
322 }
323 let mut i16s = vec![0i16; mono.len()];
324 for (i, &s) in mono.iter().enumerate() {
325 i16s[i] = (s.clamp(-1.0, 1.0) * 32767.0) as i16;
326 }
327 let mut wav = vec![0u8; 44 + mono.len() * 2];
328 let wn = encode_wav_i16_mono(&i16s, sample_rate.max(8000), &mut wav)
329 .map_err(|e| format!("{e:?}"))?;
330 let decoded = qualia_audio::decode_wav(&wav[..wn]).map_err(|e| format!("{e:?}"))?;
331
332 let bundle = if let Some(root) = storage_root {
333 let path = root.join("models").join("aed_seed.qaed");
334 if path.is_file() {
335 AedWeightBundle::load_path(&path)?
336 } else {
337 let b = AedWeightBundle::from_seed(0xAED1);
338 let _ = b.save_path(&path);
339 b
340 }
341 } else {
342 AedWeightBundle::from_seed(0xAED1)
343 };
344
345 let mut model = WeightedAedModel::from_bundle(bundle);
346
347 let mut events = [AuditoryEvent::empty(); 32];
348 let mut tokens = [TranscriptToken::empty(); 8];
349 let mut emb = [0.0f32; 4];
350 let mut ws = [0u8; 8];
351 let counts = model
352 .infer_chunk(decoded.view(), &mut events, &mut tokens, &mut emb, &mut ws)
353 .map_err(|e| format!("{e:?}"))?;
354
355 let media_hash = qualia_audio::media_digest(&wav[..wn]).hash;
356 let mut quins = [qualia_audio::AudioQuin::with_parity(0, 0, 0, 0, 0); 64];
357 let n_q = qualia_audio::compile_auditory_quins(
358 qualia_audio::MediaDigest {
359 hash: media_hash,
360 byte_len: wn as u64,
361 },
362 &events[..counts.events],
363 model.model_hash(),
364 &mut quins,
365 );
366
367 Ok(EarsDemoDto {
368 sample_rate: decoded.sample_rate,
369 frames: mono.len() as u32,
370 n_events: counts.events,
371 n_quins: n_q,
372 model_hash: format!("0x{:016x}", model.model_hash()),
373 media_hash: format!("0x{:016x}", media_hash),
374 is_reference: false,
375 mel_frames: 0,
376 cqt_peak: 0.0,
377 event_instance_hashes: events[..counts.events]
378 .iter()
379 .map(|e| format!("0x{:016x}", e.source_hash ^ e.start_frame))
380 .collect(),
381 note: "Live/imported mono analyzed with disk AED weights (seed-shaped, not certified foundation)."
382 .into(),
383 })
384}
385
386#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
388pub struct MixerTrackDto {
389 pub name: String,
390 pub gain: f32,
391 pub pan: f32,
392 pub mute: bool,
393 pub solo: bool,
394 pub lowpass: f32,
395 pub eq_gain_db: f32,
396 pub eq_freq_hz: f32,
397 pub comp_threshold: f32,
398 pub comp_ratio: f32,
399 pub delay_samples: u32,
400 pub delay_mix: f32,
401}
402
403impl Default for MixerTrackDto {
404 fn default() -> Self {
405 Self {
406 name: "Track".into(),
407 gain: 0.85,
408 pan: 0.0,
409 mute: false,
410 solo: false,
411 lowpass: 0.0,
412 eq_gain_db: 0.0,
413 eq_freq_hz: 1000.0,
414 comp_threshold: 1.0,
415 comp_ratio: 1.0,
416 delay_samples: 0,
417 delay_mix: 0.0,
418 }
419 }
420}
421
422pub fn mixer_default_session() -> serde_json::Value {
424 let tracks = vec![
425 MixerTrackDto {
426 name: "Tone A".into(),
427 pan: -0.4,
428 ..Default::default()
429 },
430 MixerTrackDto {
431 name: "Tone B".into(),
432 pan: 0.4,
433 gain: 0.7,
434 ..Default::default()
435 },
436 MixerTrackDto {
437 name: "Pad".into(),
438 gain: 0.5,
439 lowpass: 0.25,
440 ..Default::default()
441 },
442 ];
443 serde_json::json!({
444 "sample_rate": 48000,
445 "block_frames": 64,
446 "tracks": tracks,
447 "note": "Reference mixer — not a commercial DAW. EQ/comp/delay are deterministic primitives."
448 })
449}
450
451pub fn mixer_bounce(tracks: &[MixerTrackDto]) -> Result<serde_json::Value, String> {
453 use qualia_audio::{ProcessPlan, TrackState};
454 let n = tracks.len().min(16).max(1);
455 let frames = 2048usize;
456 let sr = 48000u32;
457 let mut plan = ProcessPlan::new(sr, 64);
458 let mut mono_bufs: Vec<Vec<f32>> = Vec::with_capacity(n);
459 for (i, t) in tracks.iter().take(n).enumerate() {
460 plan.add_track(TrackState {
461 gain: t.gain.clamp(0.0, 2.0),
462 pan: t.pan.clamp(-1.0, 1.0),
463 mute: t.mute,
464 solo: t.solo,
465 lowpass: t.lowpass.clamp(0.0, 0.99),
466 eq_gain_db: t.eq_gain_db,
467 eq_freq_hz: t.eq_freq_hz.max(20.0),
468 comp_threshold: t.comp_threshold.clamp(0.05, 1.0),
469 comp_ratio: t.comp_ratio.max(1.0),
470 delay_samples: t.delay_samples.min(512),
471 delay_mix: t.delay_mix.clamp(0.0, 1.0),
472 });
473 let freq = 220.0 * (i as f32 + 1.0);
474 let mut buf = vec![0.0f32; frames];
475 for (s, sample) in buf.iter_mut().enumerate() {
476 *sample = (2.0 * core::f32::consts::PI * freq * s as f32 / sr as f32).sin() * 0.4;
477 }
478 mono_bufs.push(buf);
479 }
480 let refs: Vec<&[f32]> = mono_bufs.iter().map(|b| b.as_slice()).collect();
481 let mut out = vec![0.0f32; frames * 2];
482 let written = plan
483 .bounce_interleaved(&refs, &mut out)
484 .map_err(|e| e.to_string())?;
485 let peak = out
486 .iter()
487 .take(written * 2)
488 .map(|x| x.abs())
489 .fold(0.0f32, f32::max);
490 let energy: f32 = out.iter().take(written * 2).map(|x| x * x).sum();
491 Ok(serde_json::json!({
492 "frames_written": written,
493 "peak": peak,
494 "energy": energy,
495 "n_tracks": n,
496 "note": "Offline bounce of synthetic tones through mixer FX — reference quality only."
497 }))
498}
499
500pub fn music_analysis_demo() -> Result<serde_json::Value, String> {
502 use qualia_audio::{
503 detect_onsets, estimate_tempo_from_onsets, propose_structure_segments, MusicAssumptions,
504 OnsetEvent, StructureSegment,
505 };
506 let sr = 16000u32;
507 let mut mono = vec![0.0f32; 8000];
508 for k in 0..8 {
510 let i = (k * 4000 / 2) as usize;
511 if i < mono.len() {
512 mono[i] = 1.0;
513 }
514 }
515 let mut onsets = [OnsetEvent {
516 frame: 0,
517 strength: 0.0,
518 }; 32];
519 let n_on = detect_onsets(&mono, 256, 128, 0.01, &mut onsets);
520 let tempo = estimate_tempo_from_onsets(
521 &onsets[..n_on],
522 sr,
523 MusicAssumptions {
524 assumes_4_4: true,
525 assumes_12tet: false,
526 tuning_a4_hz: 440.0,
527 },
528 );
529 let mut segs = [StructureSegment {
530 start_frame: 0,
531 end_frame: 0,
532 mean_energy: 0.0,
533 label_hash: 0,
534 }; 8];
535 let n_seg = propose_structure_segments(&mono, 256, 128, &mut segs);
536 Ok(serde_json::json!({
537 "n_onsets": n_on,
538 "bpm": tempo.bpm,
539 "tempo_confidence": tempo.confidence,
540 "n_segments": n_seg,
541 "note": "Reference music analysis — not a production beat tracker."
542 }))
543}
544
545pub fn daw_fx_demo() -> Result<serde_json::Value, String> {
547 use qualia_audio::{ProcessPlan, TrackState};
548 let mut tr = TrackState::default();
549 tr.eq_gain_db = 3.0;
550 tr.comp_threshold = 0.3;
551 tr.comp_ratio = 3.0;
552 tr.delay_samples = 48;
553 tr.delay_mix = 0.2;
554 let mono: Vec<f32> = (0..512)
555 .map(|i| (2.0 * core::f32::consts::PI * 440.0 * i as f32 / 48000.0).sin() * 0.5)
556 .collect();
557 let mut out = vec![0.0f32; 512];
558 let n = ProcessPlan::process_mono_fx(&tr, 48000, &mono, &mut out);
559 let energy: f32 = out.iter().map(|x| x * x).sum();
560 Ok(serde_json::json!({
561 "frames": n,
562 "energy": energy,
563 "note": "EQ+comp+delay offline path (deterministic FX primitives)."
564 }))
565}
566
567pub fn gen_audio_demo() -> Result<serde_json::Value, String> {
569 use qualia_audio::{separate_two_stems_reference, synthesize_reference_tone, VoiceConsent};
570 let mut consent = VoiceConsent::synthesis_only("demo-voice");
571 let mut tone = [0.0f32; 512];
572 let rec = synthesize_reference_tone(consent, 440.0, 16000, 512, 7, &mut tone)
573 .map_err(|e| format!("{e:?}"))?;
574 consent.revoke(1);
575 let denied = synthesize_reference_tone(consent, 440.0, 16000, 512, 7, &mut tone).is_err();
576 let mut body = [0.0f32; 512];
577 let mut detail = [0.0f32; 512];
578 let (s0, s1) = separate_two_stems_reference(&tone, 0x00E0_D1A0_u64, &mut body, &mut detail)
579 .map_err(|e| format!("{e:?}"))?;
580 Ok(serde_json::json!({
581 "synth_frames": rec.frames,
582 "is_reference_synth": rec.is_reference_synth,
583 "revoke_denies": denied,
584 "stem_body": format!("0x{:016x}", s0.stem_class),
585 "stem_detail": format!("0x{:016x}", s1.stem_class),
586 "note": "Reference synth + sep; licensed TTS/demucs COMPLETE-WITH-GATE."
587 }))
588}
589
590pub fn shared_clock_demo() -> Result<serde_json::Value, String> {
592 use qualia_audio::{events_overlapping_window, SharedMediaClock, TimeIntervalMs};
593 let clock = SharedMediaClock::new(0xC10C, 16000, 25.0);
594 let v_ms = clock.video_frame_to_ms(25);
595 let a_ms = clock.audio_frame_to_ms(16000);
596 let intervals = [
597 TimeIntervalMs {
598 start_ms: 0,
599 end_ms: 500,
600 instance: 1,
601 },
602 TimeIntervalMs {
603 start_ms: 800,
604 end_ms: 1200,
605 instance: 2,
606 },
607 ];
608 let win = TimeIntervalMs {
609 start_ms: 400,
610 end_ms: 900,
611 instance: 0,
612 };
613 let mut out = [0u64; 4];
614 let n = events_overlapping_window(&intervals, win, &mut out);
615 Ok(serde_json::json!({
616 "video_25_frames_ms": v_ms,
617 "audio_1s_ms": a_ms,
618 "drift_at_1s": clock.drift_ms(25, 16000),
619 "window_hits": n,
620 "asserts_causality": false,
621 "note": "Shared clock + joint interval query; overlap ≠ causality."
622 }))
623}
624
625pub fn speech_from_disk(
627 storage_root: &std::path::Path,
628 supported: bool,
629) -> Result<serde_json::Value, String> {
630 use qualia_audio::{decode_for_language, SpeechEncoderWeights, TranscriptToken};
631 let path = storage_root.join("models").join("speech_seed.qspk");
632 let w = if path.is_file() {
633 SpeechEncoderWeights::load_path(&path)?
634 } else {
635 let w = SpeechEncoderWeights::from_seed(7, 16);
636 w.save_path(&path)?;
637 w
638 };
639 let mut mono = vec![0.0f32; 4096];
640 for i in 0..mono.len() {
641 mono[i] = (2.0 * core::f32::consts::PI * 180.0 * i as f32 / 16000.0).sin() * 0.25;
642 }
643 let mut tok = [TranscriptToken::empty(); 32];
644 let n =
645 decode_for_language(&w, &mono, 16000, supported, &mut tok).map_err(|e| format!("{e:?}"))?;
646 Ok(serde_json::json!({
647 "tokens": n,
648 "model_hash": format!("0x{:016x}", w.model_hash),
649 "weights_path": path.display().to_string(),
650 "language_supported": supported,
651 "note": "Speech weights loaded from disk when present."
652 }))
653}
654
655#[cfg(test)]
656mod tests {
657 use super::*;
658
659 #[test]
660 fn ears_dto() {
661 let d = ears_demo(None).unwrap();
662 assert!(d.n_events >= 1);
663 assert!(d.is_reference);
664 assert!(d.cqt_peak > 0.0);
665 }
666
667 #[test]
668 fn cross_modal_not_causal() {
669 let d = cross_modal_demo();
670 assert!(!d.asserts_causality_any);
671 }
672
673 #[test]
674 fn section18_ok() {
675 assert!(section18_smoke_dto().unwrap().contains("OK"));
676 }
677
678 #[test]
679 fn analyze_mono_pcm_seed() {
680 let mut mono = vec![0.0f32; 4096];
681 for i in 0..mono.len() {
682 mono[i] = (2.0 * core::f32::consts::PI * 440.0 * i as f32 / 16000.0).sin() * 0.3;
683 }
684 let d = analyze_mono_pcm(&mono, 16000, None).unwrap();
685 assert_eq!(d.sample_rate, 16000);
686 assert!(d.frames > 0);
687 assert!(!d.model_hash.is_empty());
688 }
689}