Welcome to Morphogen! This guide will help you get up and running in under 30 minutes.
Morphogen is a typed, deterministic domain-specific language for creative computation. It unifies simulation, sound, visualization, and procedural design within a single, reproducible execution model.
- ✅ Deterministic by default - Bitwise-identical results across runs and platforms
- ✅ Explicit temporal model - Time evolution via
flow(dt)blocks - ✅ Declarative state -
@stateannotations make persistence clear - ✅ Multi-domain - Fields, agents, signals, and visuals in one language
- ✅ Hot-reload ready - Interactive development with live code updates
- ✅ MLIR-based - Compiles to optimized native code (v0.7.0+)
- Python 3.9 or higher
- pip package manager
# Clone the repository
git clone https://github.com/scottsen/morphogen.git
cd kairo
# Install the package
pip install -e .This will install Morphogen and its core dependencies:
- numpy - For numerical operations
- pillow - For image output
For audio I/O and video export (v0.6.0+ features):
pip install -e ".[io]"This adds:
- sounddevice - Real-time audio playback/recording
- soundfile - WAV/FLAC file I/O
- scipy - Audio processing utilities
- imageio - Video export (MP4, GIF)
# Check version
kairo --version
# You should see:
# Morphogen v0.6.0 (stable) / v0.7.0-dev (development)Let's create a simple heat diffusion simulation to understand the basics.
Create a new file called hello.kairo:
# hello.kairo - Heat diffusion simulation
use field, visual
@state temp : Field2D<f32 [K]> = random_normal(
seed=42,
shape=(128, 128),
mean=300.0,
std=50.0
)
const KAPPA : f32 [m²/s] = 0.1
flow(dt=0.01, steps=100) {
temp = diffuse(temp, rate=KAPPA, dt, iterations=20)
output colorize(temp, palette="fire", min=250.0, max=350.0)
}
Run it:
kairo run hello.kairoYou should see a visualization of heat spreading across the field, smoothing out over 100 timesteps.
Morphogen programs describe time-evolving systems through flow blocks:
flow(dt=0.01, steps=1000) {
# This block executes 1000 times with timestep 0.01
temp = diffuse(temp, rate=0.1, dt)
output colorize(temp, palette="fire")
}
Parameters:
dt- Timestep duration (in seconds or dimensionless)steps- Number of iterations to execute
Persistent variables are declared with @state:
@state vel : Field2D<Vec2<f32>> = zeros((256, 256))
@state agents : Agents<Particle> = alloc(count=1000)
flow(dt=0.01) {
vel = advect(vel, vel, dt) # Updates vel for next step
agents = integrate(agents, dt) # Updates agents for next step
}
Without @state, variables are local to each timestep.
Types can carry dimensional information:
temp : Field2D<f32 [K]> # Temperature in Kelvin
pos : Vec2<f32 [m]> # Position in meters
vel : Vec2<f32 [m/s]> # Velocity in m/s
# Unit checking (annotations, not enforced yet)
dist : f32 [m] = 10.0
time : f32 [s] = 2.0
speed = dist / time # Implicitly: f32 [m/s]
All randomness is explicit via seeded functions:
@state field : Field2D<f32> = random_normal(
seed=42, # Explicit seed
shape=(100, 100),
mean=0.0,
std=1.0
)
# Same seed → same output every time
For simulations on spatial grids (PDEs, fluid dynamics, reaction-diffusion):
use field
@state temp : Field2D<f32> = random_normal(seed=42, shape=(256, 256))
flow(dt=0.1, steps=100) {
# PDE operations
temp = diffuse(temp, rate=0.2, dt, iterations=20)
temp = advect(temp, velocity, dt)
# Stencil operations
let grad = gradient(temp)
let lap = laplacian(temp)
# Element-wise operations
temp = temp.map(|x| clamp(x, 0.0, 1.0))
}
Common operations:
diffuse()- Heat/mass diffusionadvect()- Transport along velocity fieldproject()- Incompressibility constraintgradient(),laplacian(),divergence()- Differential operators
For agent-based simulations (particles, boids, crowds):
use agent
struct Boid {
pos: Vec2<f32>
vel: Vec2<f32>
}
@state boids : Agents<Boid> = alloc(count=200, init=spawn_boid)
fn spawn_boid(id: u32, rng: RNG) -> Boid {
return Boid {
pos: rng.uniform_vec2(min=(0, 0), max=(100, 100)),
vel: rng.normal_vec2(mean=(0, 0), std=(1, 1))
}
}
flow(dt=0.01, steps=1000) {
boids = boids.map(|b| {
vel: b.vel + flocking_force(b) * dt,
pos: b.pos + b.vel * dt
})
}
Status: ✅ Production-ready as of v0.4.0
For audio synthesis and processing:
use audio
# Simple synthesis
let pluck = noise(seed=1) |> lowpass(6000)
let string = string(pluck, freq=220, t60=1.5)
let final = string |> reverb(mix=0.12)
# Real-time playback (v0.6.0+)
audio.play(final)
# Export to file (v0.6.0+)
audio.save(final, "output.wav")
Status: ✅ Production-ready as of v0.5.0 (synthesis) and v0.6.0 (I/O)
Features:
- Oscillators (sine, saw, square, triangle, noise)
- Filters (lowpass, highpass, bandpass, EQ)
- Envelopes (ADSR, AR, exponential decay)
- Effects (delay, reverb, chorus, flanger, limiter)
- Physical modeling (Karplus-Strong strings, modal synthesis)
For visualization and video export:
use visual
# Colorize fields
let field_vis = colorize(temp, palette="viridis")
# Render agents (v0.6.0+)
let agent_vis = visual.agents(
particles,
width=256,
height=256,
color_property='vel',
palette='fire',
size=3.0
)
# Layer composition (v0.6.0+)
let combined = visual.composite(
field_vis,
agent_vis,
mode="add",
opacity=[1.0, 0.7]
)
# Video export (v0.6.0+)
visual.video(frames, "animation.mp4", fps=30)
output combined
Palettes: grayscale, fire, viridis, coolwarm
Create grayscott.kairo:
use field, visual
@state u : Field2D<f32> = ones((256, 256))
@state v : Field2D<f32> = zeros((256, 256))
const Du : f32 = 0.16
const Dv : f32 = 0.08
const F : f32 = 0.060
const K : f32 = 0.062
flow(dt=1.0, steps=10000) {
# Gray-Scott reaction
let uvv = u * v * v
let du_dt = Du * laplacian(u) - uvv + F * (1.0 - u)
let dv_dt = Dv * laplacian(v) + uvv - (F + K) * v
u = u + du_dt * dt
v = v + dv_dt * dt
output colorize(v, palette="viridis")
}
Run with:
kairo run grayscott.kairoCreate particles.kairo:
use agent, visual
struct Particle {
pos: Vec2<f32 [m]>
vel: Vec2<f32 [m/s]>
age: u32
}
@state particles : Agents<Particle> = alloc(count=1000, init=spawn)
fn spawn(id: u32, rng: RNG) -> Particle {
return Particle {
pos: rng.uniform_vec2(min=(0, 0), max=(100, 100)),
vel: rng.normal_vec2(mean=(0, 0), std=(1, 1)),
age: 0
}
}
const GRAVITY : Vec2<f32 [m/s²]> = Vec2(0.0, -9.8)
flow(dt=0.01, steps=1000) {
# Apply gravity
particles = particles.map(|p| {
vel: p.vel + GRAVITY * dt,
pos: p.pos + p.vel * dt,
age: p.age + 1
})
# Bounce off floor
particles = particles.map(|p| {
vel: if p.pos.y < 0.0 { Vec2(p.vel.x, -p.vel.y * 0.8) } else { p.vel },
pos: if p.pos.y < 0.0 { Vec2(p.pos.x, 0.0) } else { p.pos }
})
output visual.agents(particles, width=512, height=512, size=2.0)
}
Create synth.kairo:
use audio
# Generate a plucked string sound
let excitation = noise(seed=7) |> lowpass(cutoff=6000) |> envexp(time=5ms)
let string_tone = string(excitation, freq=220, t60=1.5)
let final = string_tone |> reverb(mix=0.12) |> limiter(threshold=-1dB)
# Play it (requires audio I/O dependencies)
audio.play(final)
# Or save to file
audio.save(final, "pluck.wav")
A typical Morphogen project:
my-project/
├── main.kairo # Main program
├── lib/
│ ├── forces.kairo # Custom force functions
│ └── visuals.kairo # Custom visualizations
├── examples/
│ ├── 01_simple.kairo
│ └── 02_advanced.kairo
└── output/
├── frames/ # Rendered frames
└── audio/ # Exported audio
kairo run program.kairokairo run program.kairo --steps 10000 --dt 0.001kairo replCheck out the examples/ directory for:
- Beginner:
01_hello_heat.kairo,02_pulsing_circle.kairo - Intermediate:
10_heat_equation.kairo,11_gray_scott.kairo - Advanced:
v0_3_1_complete_demo.kairo, MLIR phase examples
See examples/README.md for a complete guide.
For complete language reference:
- SPECIFICATION.md - Full language specification
- LANGUAGE_REFERENCE.md - Quick reference guide
- AUDIO_SPECIFICATION.md - Audio dialect details
For implementors and advanced users:
- ARCHITECTURE.md - Morphogen Stack architecture
- docs/v0.7.0_DESIGN.md - MLIR integration roadmap
- GitHub: https://github.com/scottsen/kairo
- Issues: https://github.com/scottsen/kairo/issues
- Discussions: Share your creations and get help
-
Field Size: Start with 128×128 or 256×256 for experimentation
- Larger fields (512×512+) require more computation
- v0.7.0+ MLIR compilation significantly improves performance
-
Iteration Count: For diffusion and projection:
- Quick preview: 10 iterations
- Good quality: 20 iterations (default)
- High accuracy: 40+ iterations
-
Timestep Selection:
- Smaller
dt= more stable but slower - Larger
dt= faster but may diverge - Typical range: 0.001 to 0.1
- Smaller
-
Agent Count: Performance scales linearly
- 1,000 agents: Near-instant
- 10,000 agents: ~0.01s per frame
- 100,000+ agents: Consider spatial hashing optimizations
-
Force Calculations: Use spatial hashing for N-body forces
forces = compute_pairwise_forces( agents, radius=5.0, # Interaction radius force_func=gravity )
If you see ModuleNotFoundError: No module named 'kairo':
# Reinstall with dependencies
pip install -e .If audio.play() or audio.save() fail:
# Install I/O dependencies
pip install -e ".[io]"MLIR compilation requires additional setup:
# Install MLIR Python bindings (optional)
pip install mlir -f https://github.com/makslevental/mlir-wheels/releases/expanded_assets/latestIf MLIR is not available, Morphogen falls back to Python NumPy interpreter.
- Reduce field size:
(256, 256)→(128, 128) - Reduce iterations:
iterations=40→iterations=20 - Enable MLIR compilation for 10-100x speedup (v0.7.0+)
- ✅ Field operations (production-ready)
- ✅ Agent operations (production-ready)
- ✅ Audio synthesis (production-ready)
- ✅ Audio/visual I/O (production-ready)
- ⏳ Physical unit checking (annotations only, not enforced)
- ⏳ Hot-reload (designed, not implemented)
- ⏳ GPU acceleration (planned for v0.7.0 MLIR phases)
- ✅ MLIR integration foundation (Phase 1)
- ✅ Field operations dialect (Phase 2)
- ✅ Temporal execution (Phase 3)
- ✅ Agent operations dialect (Phase 4)
- ✅ Audio operations dialect (Phase 5)
- ✅ JIT/AOT compilation (Phase 6)
- ⏳ GPU compilation (Phase 7, in progress)
See docs/v0.7.0_DESIGN.md for the complete roadmap.
- Documentation: Check
docs/directory for detailed guides - Examples: Browse
examples/for working code - Issues: Report bugs or request features at https://github.com/scottsen/kairo/issues
- Specification: See
SPECIFICATION.mdfor language details
Congratulations! You're now ready to create your own simulations, sounds, and visualizations with Morphogen. Happy coding! 🎨🎵🔬
Version: v0.6.0 (stable) / v0.7.0-dev (development) Last Updated: 2025-11-15