Color · Audio · Haptics — compiled to WebAssembly. Every algorithm grounded in a published standard. Deterministic, energy-conserving, zero unsafe Rust.
output + absorbed + scattered = input · ±1e-4 across all domains
Sensory Physics
Momoto unifies color science, acoustic loudness, and haptic force under a single energy-conserving model. No magic numbers. No platform hacks.
Photon wavelength 380–780 nm. OKLCH perceptual polar space, HCT Material Design 3, WCAG 2.1 and APCA-W3 accessibility contrast, CVD simulation, harmony engine with 7 types, and glass physics with GGX PBR.
Sound pressure 20 Hz–20 kHz. K-weighting IIR biquad filters, integrated/momentary/short-term LUFS gating per ITU-R BS.1770-4, Cooley-Tukey FFT radix-2, Mel filterbank with HTK+Slaney scales, and EBU R128 broadcast compliance.
Vibrotactile force in Hz, N, and Joules. Weber–Fechner perception model, energy budget with capacity and recharge tracking, LRA/ERM/Piezo actuator mapping, and waveform synthesis (Sine, Pulse, Ramp, Buzz).
Cross-domain orchestrator with zero vtable overhead. DomainVariant enum dispatch — LLVM inlines all match arms. Perceptual alignment between any two domain values, system energy validation, and 4096-f32 preallocated scratch buffer.
Code
Three sensory domains. One Cargo dependency. One WASM init. All algorithms deterministic across native, browser, and Node.js.
use momoto_core::color::Color; use momoto_metrics::{APCAMetric, WCAGMetric}; let fg = Color::from_hex("#FFFFFF").unwrap(); let bg = Color::from_hex("#3B82F6").unwrap(); let wcag = WCAGMetric::new().evaluate(fg, bg).contrast; let apca = APCAMetric::new().evaluate(fg, bg).contrast; println!("WCAG: {:.2}:1 APCA Lc: {:.1}", wcag, apca); // WCAG: 3.06:1 APCA Lc: 55.2
use momoto_core::space::oklch::OKLCH; use momoto_intelligence::{ generate_palette, harmony_score, HarmonyType }; let seed = OKLCH::new(0.65, 0.18, 210.0); let palette = generate_palette( seed, HarmonyType::Analogous { spread: 45.0 } ); let score = harmony_score(&palette.colors); println!("Harmony: {:.3}", score); // 0.0–1.0
use momoto_audio::AudioDomain; let domain = AudioDomain::at_48khz(); let mut analyzer = domain.lufs_analyzer(1).unwrap(); // Process audio blocks (K-weighted ITU-R BS.1770-4) let samples: Vec<f32> = /* your audio blocks */; analyzer.add_mono_block(&samples); println!("Integrated: {:.1} LUFS", analyzer.integrated()); let check = domain.validate_broadcast(analyzer.integrated()); println!("EBU R128 passes: {}", check.passes); // target: -23.0 LUFS ±1 LU
use momoto_audio::{AudioDomain, MelScale}; let domain = AudioDomain::at_48khz(); // Radix-2 Cooley-Tukey FFT (power-of-2 sizes only) let frame: [f32; 1024] = /* one FFT frame */; let power = domain.fft_power_spectrum(&frame); // power: Box<[f32]> len=513, units: dB FS // Mel filterbank (40 bands, HTK scale) let mel = MelScale::htk(40, 48000); let energies = mel.apply(&power); // energies: Box<[f32]> len=40
use momoto_haptics::{ ActuatorModel, EnergyBudget, mapping::FrequencyForceMapper, }; // 50 mJ capacity, 10 mJ/s recharge let mut budget = EnergyBudget::with_recharge( 0.050, 0.010 ); budget.try_consume(0.005).expect("tap ok"); budget.tick(0.1); // 100 ms elapsed let mapper = FrequencyForceMapper::new( ActuatorModel::Lra ); let spec = mapper.map(0.7, 100.0); println!("{:.0} Hz {:.4} N {:.4} J", spec.freq_hz, spec.force_n, spec.energy_j()); // 175 Hz 0.1400 N 0.0140 J
use momoto_haptics::{HapticWaveform, WaveformKind}; // Generate waveforms at 8 kHz let tap = HapticWaveform::generate( WaveformKind::Pulse, 200.0, 0.8, 8000, 50 ); let buzz = HapticWaveform::generate( WaveformKind::Buzz, 180.0, 0.6, 8000, 100 ); // WaveformKind: Sine | Pulse | Ramp | Buzz // All Box<[f32]> — zero-copy, WASM-safe
use momoto_engine::MomotoEngine; use momoto_core::traits::domain::DomainId; let engine = MomotoEngine::new(); // Normalize raw perceptual energy to [0, 1] // Audio: (lufs + 70) / 70 let color_norm = engine.normalize_perceptual_energy( DomainId::Color, 0.72 ); let audio_norm = engine.normalize_perceptual_energy( DomainId::Audio, -23.0 ); // Alignment between two domain values (0.0–1.0) let alignment = engine.perceptual_alignment( DomainId::Color, DomainId::Color, 0.72, 0.68 ); println!("Alignment: {:.3}", alignment);
use momoto_engine::MomotoEngine; let engine = MomotoEngine::new(); // Validate energy invariant across all domains // output + absorbed + scattered = input (±1e-4) let report = engine.validate_system_energy(); println!( "Conserved: {} Max error: {:.2e}", report.system_conserved, report.max_error ); // Conserved: true Max error: 1.00e-15 // DomainVariant enum dispatch — no vtable // LLVM inlines all match arms in hot paths
import init, { wcagContrastRatio, wcagPasses, audioLufs, audioValidateEbuR128, audioFftPowerSpectrum, } from 'momoto-wasm'; await init(); // load .wasm binary // Color — WCAG contrast const ratio = wcagContrastRatio('#FFFFFF', '#3B82F6'); console.log(`WCAG: ${ratio.toFixed(2)}:1`); // Audio — 1 kHz sine, 1 second at 48 kHz const samples = new Float32Array(48000); for (let i = 0; i < 48000; i++) samples[i] = Math.sin(2 * Math.PI * 1000 * i / 48000); const lufs = audioLufs(samples, 48000, 1); const ok = audioValidateEbuR128(lufs); console.log(`Integrated: ${lufs.toFixed(2)} LUFS — EBU R128: ${ok}`);
# Color only (<350 KB gzipped) wasm-pack build --target web -- \ --no-default-features --features color # Color + Audio (<500 KB) wasm-pack build --target web -- \ --features audio # Full multimodal (<550 KB) wasm-pack build --target web -- \ --features multimodal # With browser panic messages wasm-pack build --target web -- \ --features multimodal,panic_hook
Design
Enum dispatch instead of vtable — LLVM inlines every match arm. No heap in hot loops. No unsafe Rust. Zero dynamic dispatch in the critical path.
Ecosystem
Each crate is independently usable. Zero unsafe. Zero heap allocation in hot loops. All publish to crates.io at v7.1.
Fresnel equations, K-weighting, Weber's law. No heuristics or magic numbers.
Same input → same output on every platform: native, browser, Node.js.
DomainVariant enum instead of Box<dyn Domain> — LLVM inlines all match arms.
Box<[f32]> allocated once per call. Inner loops are zero-alloc.
Reference
Visual reference for the color intelligence pipeline and agent workflow.