All scientifically accurate data is computed here, then baked into textures for real-time use.
Python Script DAT calls PSG API with MCD atmospheric profiles. Runs at project load, not per-frame.
# PSG config for each (solar_elev, dust_tau, Ls) combo curl -d type=rad --data-urlencode file@config.txt \ https://psg.gsfc.nasa.gov/api.php
Converts PSG spectral output into 2D lookup textures. X-axis = angle from sun (0°–180°), Y-axis = elevation (-10°–90°). RGB = radiance at 630/530/440 nm.
# Grid: 14 solar elevations × 4 dust states × 12 seasons # = 672 PSG batch runs → 672 sky LUT textures # Packed into atlas: 4×3 grid per dust state
Crops MOLA elevation data and MGS/TES albedo maps to viewport region. Outputs 16-bit heightmap and RGB albedo texture.
Pre-computes Mie scattering properties from PSG's models. For Mars dust (reff ~1.5μm, σ=1.5): extinction coefficient, single-scattering albedo (ω), and Henyey-Greenstein asymmetry factor (g) at each wavelength.
# Henyey-Greenstein phase function: # P(θ) = (1 - g²) / (1 - 2g·cosθ + g²)^(3/2) # Mars dust at 440nm: g ≈ 0.63 (strong forward) # Mars dust at 630nm: g ≈ 0.68
Receives USB-MIDI from SOMI-1 hub. Each sensor sends 7 CC channels: 3 tilt (X/Y/Z), 3 acceleration (X/Y/Z), 1 activity. Two sensors = 14 CC channels.
Smooths raw sensor data. SOMI-1 accelerometers can be jittery — apply low-pass filter. Different time constants for each mapping: solar position needs smooth/slow, dust storm trigger needs fast response.
# Filter settings per channel: # sun_elevation: filter=0.3s (smooth, deliberate) # sun_azimuth: filter=0.3s # dust_opacity: filter=0.8s (slow, geological) # storm_trigger: filter=0.05s (fast, percussive)
Remaps MIDI 0–127 to physical parameter ranges. This is where instrument design happens — the feel of the mapping.
# SENSOR 1 (Right wrist) — Solar control # Tilt Y → sun_elevation: 0–127 → -5° to 90° # Tilt X → sun_azimuth: 0–127 → 0° to 360° # Activity → dust_lift: 0–127 → 0.0 to 0.3 (additive) # SENSOR 2 (Left wrist) — Atmosphere control # Tilt Y → dust_base_tau: 0–127 → 0.2 to 8.0 (log) # Tilt X → season_Ls: 0–127 → 0° to 360° # Accel Z → storm_trigger: threshold > 100 → bool
Combines dust_base_tau + dust_lift into final dust_tau. Detects storm trigger (sharp wrist snap). Clamps all values to physical ranges.
Captures live audio input or playback. Feed it music, ambient sound, or Martian wind recordings.
FFT analysis split into configurable bands. Three-band split mirrors PSG's approach: low (dust resonance), mid (wind), high (ice crystal scintillation).
# Band splits: # Bass: 20–200 Hz → dust_opacity_mod (±0.5 tau) # Mid: 200–2kHz → wind_displacement (terrain shimmer) # High: 2k–16kHz → ice_scintillation (aureole sparkle)
Loads pre-baked sky LUT atlas textures. One atlas per dust state, containing multiple solar elevations tiled in a grid. 32-bit float EXR format preserves HDR radiance values.
Takes dust_tau from CHOP, determines which two atlas textures to interpolate between. Also interpolates within atlas based on sun_elevation. Outputs a single blended sky LUT for current conditions.
// Trilinear interpolation in parameter space: // 1. dust_tau → blend between two nearest dust atlases // 2. sun_elevation → sample correct tile within atlas // 3. season_Ls → blend between seasonal variants vec3 sky = mix( sampleAtlas(atlas_lo, sun_elev, angle_from_sun), sampleAtlas(atlas_hi, sun_elev, angle_from_sun), dust_frac );
MOLA heightmap and TES albedo map, loaded as 32-bit float and 8-bit RGB respectively.
Mie scattering parameters texture. The GLSL shader reads extinction, albedo, and HG asymmetry factor per wavelength for the current dust particle size.
Bridges all control channels to GLSL TOP uniforms. This is the handoff from the instrument layer to the render layer. Each named CHOP channel becomes a named uniform in the shader.
// In GLSL TOP → Vectors 1 page: // u_sun_elev ← sun_elev channel // u_sun_az ← sun_az channel // u_dust_tau ← dust_tau channel // u_Ls ← Ls channel // u_audio_bass ← bass_power channel // u_audio_mid ← mid_power channel // u_audio_high ← high_power channel // u_storm ← storm_active channel
For each pixel, compute viewing direction (elevation, azimuth relative to sun). Sample the blended sky LUT. Add audio-reactive modulation: bass thickens the atmosphere, high frequencies create scintillation near the sun.
uniform float u_sun_elev, u_sun_az, u_dust_tau;
uniform float u_audio_bass, u_audio_high;
uniform sampler2D u_sky_lut;
void main() {
vec2 uv = gl_FragCoord.xy / uTD2DInfos[0].res.zw;
float view_elev = uv.y * 90.0;
float view_az_from_sun = abs(uv.x * 360.0 - u_sun_az);
vec2 lut_coord = vec2(
view_az_from_sun / 180.0,
(view_elev + 10.0) / 100.0
);
vec3 sky = texture(u_sky_lut, lut_coord).rgb;
sky *= 1.0 + u_audio_bass * 0.15;
float sun_prox = 1.0 - smoothstep(0.0, 15.0, view_az_from_sun);
sky += sun_prox * u_audio_high * vec3(0.02, 0.03, 0.08);
fragColor = TDOutputSwizzle(vec4(sky, 1.0));
}Ray-marches into MOLA heightmap to render terrain. Applies surface albedo from TES. Lighting uses sun direction from uniforms. Atmospheric fog based on dust_tau dims distant terrain.
// Terrain ray-march with real MOLA data // Atmospheric extinction: exp(-dust_tau * distance / H) // H = scale height ≈ 11.1 km for Mars // Surface illumination: Lambert * solar_irradiance // Fog color: sample sky LUT at horizon elevation
Renders the solar disk with physically correct angular size (0.35° from Mars), limb darkening, and wavelength-dependent extinction. At low sun elevations with high dust, the blue aureole emerges from Mie forward scattering.
// Sun angular radius: 0.176° from Mars (0.35° diameter) // Limb darkening: I(θ) = I₀(1 - u(1 - cosθ)) // Extinction: exp(-tau_total(λ) / cos(zenith)) // Blue aureole: HG phase function at small angles // P(θ) = (1-g²)/(1-2g·cosθ+g²)^1.5 // At 440nm, g≈0.63 → strong forward peak → blue halo
Combines sky + terrain + sun. Applies physically-based tonemapping (ACES or Reinhard) to compress HDR to display range. Adds subtle dust-in-air scattering. Optional color grading toward Curiosity/Spirit camera response curves.
// Composite order: sky → terrain (with depth) → sun (additive) // Tonemapping: ACES filmic for natural roll-off // Optional: apply Mars camera white balance // (Spirit/Curiosity approximate sensor response) // Storm overlay: when storm_active, add animated // dust particle noise layer (u_audio_bass drives density)
Fullscreen output to projector or display. For live performance, this IS the Martian sky filling the room.
Shares the rendered frame with other applications in real-time on GPU. Feed into Resolume for VJ mixing, or into Ableton Live for synchronized audio-visual performance.
Records the performance to video. HAP codec for real-time playback, or ProRes for editing. Capture the entire sunset performance.
Network video output for streaming or multi-machine setups. Send the Mars visualization over the network to other displays or streaming software.
| Sensor | Motion | Controls | Range | Gesture Feel |
|---|---|---|---|---|
| 1 (R wrist) | Tilt Y | Solar Elevation | -5° → 90° | Raise arm = raise sun |
| 1 (R wrist) | Tilt X | Solar Azimuth | 0° → 360° | Sweep arm = sweep sun across horizon |
| 1 (R wrist) | Activity | Dust Lift (additive) | 0.0 → +0.3 τ | Move fast = kick up dust |
| 2 (L wrist) | Tilt Y | Base Dust Opacity | 0.2 → 8.0 τ (log) | Raise arm = thicken atmosphere |
| 2 (L wrist) | Tilt X | Season (Ls) | 0° → 360° | Rotate wrist = change season |
| 2 (L wrist) | Accel Z (snap) | Storm Trigger | bool | Sharp wrist snap = instant storm |
| Frequency Band | Controls | Range | Visual Effect |
|---|---|---|---|
| Bass (20–200 Hz) | Dust Opacity Modulation | ±0.5 τ | Kick drum thickens the air, sky reddens |
| Mid (200 Hz–2 kHz) | Terrain Wind Shimmer | 0–3 px displacement | Harmonic content makes terrain haze ripple |
| High (2–16 kHz) | Ice Crystal Scintillation | 0–0.15 brightness | Hi-hats and cymbals sparkle the solar aureole |
| Asset | Specification | Memory / Time |
|---|---|---|
| Sky LUT atlases (4 dust states × 12 seasons) | ~48 textures @ 1024×512 EXR | ~96 MB |
| MOLA heightmap (cropped region) | 2048×2048 × 32-bit float | ~16 MB |
| TES albedo map (cropped) | 2048×2048 × RGB 8-bit | ~12 MB |
| Scattering LUT (dust + ice) | 256×1 × RGBA float × 2 | ~4 KB |
| PSG API calls (foundry phase) | ~672 batch calls | ~2 hrs compute |
| Total GPU texture memory | ~125 MB |