Source
A source reads a value on every Latido tick and owns its signal name. Plugins register source names for you.
Signal engine for living interfaces
Connect changing data to behavior so users feel what a system is doing before they read a number.
Not a renderer.
Not a framework.
Not only audio.
A signal engine for living interfaces.
Quick Start
Use @latido/web for common browser projects. It reexports core plus DOM, audio,
targets, events, network and WAAPI plugins.
audio.energy
Signal names come from sources. The audio plugin registers names like audio.energy.
Custom sources use the name you pass to latido.source(name, reader).
import { createLatido, dom, audio, signals } from "@latido/web"
const latido = createLatido()
.use(dom())
.use(audio({ element: audioEl }))
latido.signal(signals.audio.energy)
.bindCSSVar(document.body, "--energy")
latido.start()
Concepts
Latido helps you turn values into behavior, and behavior into perception.
API / audio / events / sensors
↓
source → signal → transform → interpretation → binding → target
A source reads a value on every Latido tick and owns its signal name. Plugins register source names for you.
A signal subscribes to a source by name. If no source is registered with that name, the signal reads as 0.
Transforms shape raw values into usable ranges: normalize, clamp, smooth, decay, threshold, pulse or map.
A binding applies the transformed value to a target. DOM bindings can write CSS variables, styles, classes and attributes.
A target is what moves, breathes or reacts: DOM, Web Animations, PixiJS, Canvas, Three.js or plain objects.
Interpretation combines several signals into a state such as calm, stressed, thriving or recovering.
Each plugin exports its own catalog: @latido/audio/signals,
@latido/events/signals. @latido/web aggregates them as
signals.audio.energy. It gives editors something to autocomplete.
Use strings directly for custom sources and network names.
audio.energyregistered by audio()audio.beatregistered by audio()event.click.pulseregistered by events()event.pointer.progressXregistered by events()feed.energyregistered from a network source configsystem.healthScoreregistered by your own latido.source()Simple signal pipes
signalPipe helpers are normal functions that apply common transform chains.
Use them in binding lists, adapters or small integration layers.
signalPipe.normalized(min, max, smooth)signalPipe.clamped(smooth)signalPipe.smooth(amount)signalPipe.zero()signalPipe.constant(value)signalPipe.beat(threshold, pulse, decay)signalPipe.raw()import { createLatido, signalPipe } from "@latido/core"
bindSystemSignals(latido, view, [
{
system: "weather",
source: "weather.temperature",
target: "tone",
pipe: signalPipe.normalized(-4, 36, 0.08)
},
{
system: "weather",
source: "weather.visualBeat",
target: "beat",
pipe: signalPipe.beat(0.2, 280, 0.1)
}
])
Helper pipes are just functions. Advanced users can still write their own pipeline when they need nonlinear mapping, custom compression or domain-specific behavior.
pipe: source => source
.normalize(0, 100)
.map(value => value * value)
.smooth(0.1)
Rule utilities
@latido/core includes pure helpers for low-level rule and scoring systems.
They do not define market, weather or health behavior; your application owns those rules.
clamp(value, min, max)read(values, name)readOr(values, name, fallback)previousValues(history)scoreFromFactors(initial, factors, context)collectRisks(groups, context)createBaseHealth(system, score, risks, metrics)import {
clamp,
collectRisks,
createBaseHealth,
read,
scoreFromFactors
} from "@latido/core"
const factors = [
[
"windPenalty",
({ wind }) => -clamp((wind - 20) / 35) * 0.26
],
[
"rainPenalty",
({ rain }) => -clamp(rain / 8) * 0.2
]
]
const risks = [
{
name: "weather",
rules: [
["high wind", ({ wind }) => wind > 30]
]
}
]
const context = {
wind: read(values, "weather.wind"),
rain: read(values, "weather.rain")
}
const score = scoreFromFactors(0.98, factors, context)
return createBaseHealth(
"weather",
score,
collectRisks(risks, context),
context
)
Health interpreter
createHealthInterpreter handles history, trend detection, hysteresis,
recovering and unstable transitions. Your app still owns the base score, risks and reason text.
Use it when a latest value is not enough and you need a narrative state over time: stable, stressed, sick, recovering, healthy or custom names.
deriveBase(system, values, history)reasonFor(state, base, temporal)scoreThresholdsminStateDurationimport { createHealthInterpreter } from "@latido/core"
const interpretHealth = createHealthInterpreter({
deriveBase(system, values, history) {
return deriveWeatherBase(values, history)
},
reasonFor(state, base, temporal) {
if (state === "recovering") {
return "Recovering after sustained weather stress"
}
if (base.domainRisks.includes("high wind")) {
return "High wind and precipitation"
}
return "Stable conditions"
}
})
const health = interpretHealth("weather", values, history)
Packages
Examples
Each example focuses on behavior first. Numbers are useful, but perception is the interface.
Recipes
API Reference
Roadmap
Documentation polish, more recipes, stronger examples, clearer onboarding and stable package guidance.
Production examples for network-driven signals and more real-world interpretation layers.
More adapters, package-level guides, advanced renderer examples and higher-level perceptual components.