← Miks.cafe / Physical & Immersive

Physical & Immersive

Lux 2 Signal

These documents are being actively worked on. Organization, designs and writings are subject to change!

Sun and their light is boundless; the original life force. They are persistent, unyielding. They come, they leave. You close your eyes, and seek them out again. They come back, the past's warmth still having left a poison blue impression on a veil, or a tidal pull that marks the sand in a familiar pattern, though they have now changed through the prism of the Earth...

Check out the collected readings using an interactive graph here.

And for a TLDR, here's the outcome in tracks:

What is the score of sunlight?

Converting sunlight in a Brooklyn apartment into MIDI, LFO, and wavetable-friendly data, to translate it into a score.

Inspiration

August 5, 2024, 6:43AM. DeKalb Ave in Bushwick, Brooklyn

I've been drawn to environmental data and atmospherics as a compositional material. My work as plygid (from "paligid") is inspired by sounds, textures and environments that are on the periphery, just on the edge of noticeable.

After a stretch of fast paced work, I've also had time to slow down and reorient my attention toward small pockets of time, which has let me notice moments that would otherwise get crowded out when everything feels urgent. Light shards touching your face at 10am ET, waking you up from the subtle heat. Or shadows streaking the wall when light passes through crooked blinds, highways for grains of dust to travel; bits of static of a lived room, seeking places of rest.

My apartment also has a railroad setup where there are windows North-West and South-East, so the sunlight enters and exits in a very distinct way, in that the light travels across the whole apartment, casting shadows of objects that hit its path. It's a quality of the apartment that I've always loved; memories of the structure, furnishings, and people moving inside are embedded into how the light moves throughout the day. The video above shows light hitting the bedroom at 6:43AM, 2 years before this project was made. Since then, I still have not bought drapes (which was obviously intentional so I can do something like this!)

Coincidentally, spring equinox has arrived! Days are officially getting longer, and the sun lingers for a few more moments each day. So this project felt like a perfect way to encapsulate the seasonal change.

I've also been trying to find a bridge between my creative practice and my technical one. Hardware felt like the right middle to extend systems past the screen and into the analog world, and into the sonic world. And a big undercurrent to this: finding something that's sort of an antidote to "AI Generated content", especially in music. Creating music out of physical data is something that is not replicatable by a prompt.

The Politics of Light (and its scarcity)

After this project, I met up with an old friend, now doing amazing, subversive things as BAKUDI SCREAM, who engaged with this work in ways that made me really take a pause. More on that below (click here to fast forward).

Use-Cases

The system is built to be modular. Light can surface through notation, timbre, or modulation, and can be translated retroactively if we have the data.

  • Export light readings over time as MIDI data with a specified scale:
    • Key data
    • Chord data (with perceptual lag offset)
  • Export a day's light arc as a wavetable (32 or 64 frames)
  • Export a day's light arc as a custom LFO shape
  • Export raw lux readings as MIDI CC automation curves
  • See raw light levels over time
  • Visualize light levels for a specified period using a plot
  • Make unique music and data that is not AI-replicatable! Backed by physical and atmospheric data
  • Create a system for future works (music, or environment-based art pieces)

Part 1 - Hardware Setup

Circuit Design

The lux sensor board sits in a simple divider network so the ADC sees a comfortable voltage swing for daylight shifts. Once I had the initial circuit connected in the breadboard, I used an iPhone flashlight to test it out and get my first readings.

Breadboard prototype: lux sensor wired for first readings

Lux 2 signal circuit board (v0) — breadboard assembly before installation and enclosure.

Light, sensed

Test using an iPhone flashlight. Values read over 2k lux causes the LED light to turn on.

Tests and fine tuning values

My apartment gets a lot of light during the morning, so I needed to ensure values aren't always peaking. The variable we could tweak in this system is the resistor. To capture light data over time, under almost the same conditions, a custom python logger was very helpful.

import serial
import csv
import datetime
import time
import sys
import os
os.makedirs('logs', exist_ok=True)
 
## Monitors Serial Port for Lux data @ Baud Rate 115200
SERIAL_PORT = '/dev/cu.usbserial-0001'
BAUD_RATE = 115200
 
## Our resistor value to experiment with
resistor_label = sys.argv[1]
## How long our experiment should run
DURATION = int(sys.argv[2])
## Where we are running the experiment
location = sys.argv[3]
 
# Specify our serial port (what we monitor)
ser = serial.Serial(SERIAL_PORT, BAUD_RATE, timeout=1)
# Flush stale buffered data before reading
ser.reset_input_buffer()  
# Wait for buffer to fully clear before reading
time.sleep(5)  
 
 
## Our experiment output
filename = f"logs/{datetime.datetime.now().strftime('%Y%m%d_%H%M%S')}_{location}_{resistor_label}.csv"
 
start_time = time.time()
 
## Receive the lux readings and add to our logs in csv format
with open(filename, 'w', newline='') as csvfile:
    writer = csv.writer(csvfile)
    writer.writerow(['timestamp', 'value'])
    
    while time.time() - start_time < DURATION:
        line = ser.readline().decode('utf-8', errors='ignore').strip()
        if line:
            timestamp = datetime.datetime.now().isoformat()
            writer.writerow([timestamp, line])
            print(f"{timestamp}: {line}")
 
ser.close()
print(f"Done! Saved to {filename}")

I tested with a few resistor values and ended up choosing 4.7k ohms as a good balance to capture the range of signals without oversaturating. Here are some samples of logs between (1k - 47k resistor values):

Resistor sweep — sample CSV logs1k–47k runs from the tuning experiments.

Parallels between lux headroom and sonic headroom

4.7k was able to reach levels near the peak during direct sunlight, which would lead to loss of fidelity at the ingestion stage (similar to clipping!).

I wanted enough headroom to make sure I capture the full range of data without oversaturating (and losing fidelity), since I could always scale the data later on.

Making the ingestor system and saving data

Overall System Design:

  • Supabase free tier to store data and serve it to other clients using Supabase HTTP Support
  • Local flask server ingests esp32 data, and writes to supabase
    • Esp32s send data every 6 seconds to an endpoint
    • Flask server writes to supabase within the same endpoint
    • Instead of having the ESP32's send data directly, I opted for a centralized controller, more secure (not exposing supabase keys in my ESP32, in case it gets lost).
    • Right now I'm using my PC, I plan to migrate this to a dedicated Raspberry Pi so my PC can rest
    • The server avoids unnecessary overnight writes by computing sunrise and sunset based on my apartment’s latitude and longitude on each calendar day using astral. Astral is a third-party library that gives solar times from coordinates and date. With this, ingestion runs only while the sun is up, so we don’t log long stretches of zeros after dark.

The first full day of data!

First full day of data

The first full day of data! A bit cloudy throughout the day, with clearer skies in the afternoon

First full day of data - SE Sensor

Sensor Placed on the SE side of the apartment

Building the dashboard

Dashboard: liwanag.miks.cafe

For the dashboard, I wanted to stay minimal and let the light data take up most of the attention. I opted to remove axis labels and other informational overhead since, at a glance, we really just care about the shape of the light and not the exact values. You can still get the lux values as you hover over the sensor data, which also changes the background of the site to match the specific light levels for that day.

I also added guidelines to show that day's dusk, dawn, sunrise, sunset and noon values so you can see how the light data changes after each segment of the day. (It's really interesting to see the SE sensor always take a big dip right after noon!)

For a visual reference, I compiled an Are.na board for design inspiration:

System In Practice (aka the bugs and pitfalls...)

I shoddily pieced together a couple of circuits that are still on a breadboard. The breadboard adhesives are stuck onto the wall (NW) and on a slanted window ledge in the bedroom (SE). So the data that comes out of it tends to be affected by these conditions.

A few weeks of data collection shows some real life events happening which affects the collected data. So it's not pure by any means.

For example, when the sensor fell off:

SE Sensor falling causing a flatline

The SE sensor fell, because it's resting on a slanted slope, despite a partial command strip reinforcing it

SE Sensor taped to window

Now taped x 10 to prevent another fall (plus the existing command strip)

Or when I was trying to find a really important document in my bedroom:

Light in bedroom during dusk, causing reading spike

I thought I misplaced an important document, so I frantically searched the bedroom for it, including turning on the big bad, clinical white, default NY apartment overhead light, causing a reading spike in the SE sensor

There's a lot to be improved in the fabrication of the sensors, for example, adding an enclosure to prevent room light from affecting the sensor, or making sure it's stable.

For now, I'm not just recording light data. . . . but life data in my apartment.

April 12-13 blackout!

UTC offset map

On April 12, dusk in New York is already the next calendar date in UTC, which caused an operational issue

On April 12, I posted the project publicly. It was also the day dusk in New York ended up past midnight in UTC (April 12 dusk in New York, 8pm EDT, was midnight on April 13, in UTC). This caused a server issue where dusk could not be found, and nothing got written to Supabase (the whole day has no readings!). A cosmic joke played me right when I publicized it.

I found out when, at night on April 12, I checked the day's readings, and there was nothing there.

s = sun(LOCATION.observer, date=datetime.now().date())

Because I wasn't passing in the timezone, the system tried to reconcile the dusk in UTC time, which already crossed over to the next day. That caused the server to error out and readings to not get posted all day. I quickly threw a fix before bed...

First attempt to fix: pass in UTC timezone

s = sun(LOCATION.observer, date=datetime.now().date(), tzinfo=timezone.utc)

This did not work because UTC dawn and dusk is not the same as New York dawn and dusk. So we lost some morning readings on April 13 as I slept through it.

Proper fix

tz = ZoneInfo(LOCATION.timezone)
s = sun(LOCATION.observer, date=datetime.now(tz).date(), tzinfo=tz)

This anchors dawn and dusk to the apartment’s timezone, which is the same frame for the date I pass in and for what astral return. That fixed it and readings came back online.

The issue is partly because I didn’t give astral an explicit tzinfo and left things ambiguous, and partly an edgecase of astral which ties the result to UTC if tzinfo isn't passed, causing an issue when dusk in a timezone crosses midnight in UTC. UTC has already rolled to the next day (April 12 dusk in NYC time is already April 13 in UTC time) which caused a server issue, so dusk for that day could not be found. Passing the apartment's ZoneInfo fixes this since it frames the operation in NY time instead of UTC time.

An alert system would be helpful to minimize future data blackouts.


Part 2 - Lux Data to Sonic Data

How do we translate the sun's light into music?

Tuning forks and written intervals — reference for harmonic ratios

Sound intervals and tuning forks: a physical reference for thinking 'in scale' and natural tunings.

The sun doesn't actually have a "score" or "piece" (if it did, it would probably be beyond our comprehension) but we do see visages of its affect when it passes matters of the earth. Like Enrique calling out to Apo Laki in Lav Diaz's Magellan, we can only translate and feel what it chooses to reveal at the time.

I wanted to approach this translation in a musical way. I had a few rules for this translation:

Translation rules:

Light (and nature) communicates, we synthesize in approximate scale

Natural processes and signals express and modulate in analog signals. To translate this into digital mediums, we can only approximate these signals. We can see this approximation occur in the first layer of our circuit, when the LDR sends readings through the ESP32's ADC bus (analog to digital converter) to translate light readings from 0-4095. (The same relationship happens when recording instruments into an audio interface when analog signals are captured into digital waveforms).

Natural design is also "in scale", following a defined pattern. This assumption derives from the ratios and patterns in nature which bloom and decay from rulesets (e.g. Fibonacci sequences or golden ratios in plant or shell growth, mycelial network optimization.) All happening in coordinated patterns that can be inferred and made approximate sense from.

So in this project, we consider translation data to be always in scale, or in between scales. We use these assumptions in the lux-to-keys and lux-to-chords scripts.

Light levels maps to musical intensity (velocity and chord density)

Periods of high light suggest a clearer, more open view, which mapped to fuller, more expansive chords and a more defined expression through higher velocity.

Low light is hazier has a more understated, and inward projection (literally, we tend to stay in during low light) which maps to something sparse, or unresolved, and lower velocity. Less externalized "events". The low light readings have less to broadcast, more desiring to be translated.

We internalize light changes with late registration

Just as light diffuses, we register its changes slowly (once it gets brighter, or darker, we start to feel this change through body or emotional changes overtime). Like letting ingredients meld over a slow heat.

To represent this, I introduced an --offset value to chord mode, that works on time: the tonic or anchor-led voice can land first, and the other chord tones follow by a controllable percentage of the clip, so the harmony registers in waves instead of all at once. This signifies the human slowness of synthesizing the new light reading (e.g. for a cat, this offset would be a lot smaller, since they would notice the sensory change immediately).

Wavetables as a landscape of moments in time

A single day of light can be divided into 32 or 64 equal bins from dawn to dusk. Each bin becomes one frame of a wavetable (a single-cycle waveform shaped by the lux readings within that window.).

When dragged into a wavetable synth, it can sweep the wavetable position and you move through the day: the tentative light of early morning, when presence blurs from the ethereal and real, the crisp peak of midday, the recession into dusk. The wavetable lets us playback how the day transpired.

Express 'momentary architectures'

Each day's translation is a score: the dance of an astral body as it cascades through atmosphere, city structures, glass, sensor, and circuit at a specific point in time. This lets us create unique archive of the light traveling into my apartment.

The composer is also a variable

All of the parts of the system to recieve light are artificial: The apartment situated in a specific corner of the block, the windows, the resistor value, the choice of scale are all human decisions that shape how the light is translated. The compositions that come from it are an interplay between the light and the conditions I've created to translate it. (E.g. the drapes I haven't bought are conveniently part of this score!).

Outputs

March 24 & 27 — exports folderMIDI, wavetable, and CC data for March 24, and March 27.

I wanted this to be modular, so I can revisit days anytime and not have to play the score "live" in the studio (though this project is already built to extend to that for exhibition purposes!) While the dashboard shows the readings, these functions are run locally.

(Later, I plan to deploy these in a Raspberry Pi Flask server, and make it accessible to known devices via endpoints).

The functions are below:

MIDI Keys lux-to-keys

Each lux value maps to midi key values in scale, using a normalized range of brightness in whatever slice of time you export so the thresholds track that window’s dim and bright.

Ableton Session view with clips generated from March 24 lux data

Lux-derived MIDI note data in D Lydian, from March 24, 2026 (clear skies).

MIDI Chords lux-to-chords

Each lux value maps to chord values in this scale, the same way: thresholds follow the dim-to-bright range in the slice you queried. Chord mode uses --anchor, defaulting to the root of the selected scale. The anchor pitch class is always preserved when thinning chords. If the generated voicing doesn't contain it, the nearest octave of that pitch is inserted. Other voices build around this anchor during moments of density or sparseness, so no matter how the chord changes, the tonal center remains audible as a thread. --offset is optional: it staggers the chord in time a little so the notes don’t have to all attack at once.

Ableton Session view with clips generated from March 24 lux data

Lux-derived MIDI chord data in D Lydian, from March 24, 2026 (clear skies).

MIDI CC lux-to-cc

The raw lux readings over time are exported as MIDI CC data continuous control curves that modulate parameters in the composition. We can also use these for automation values. In this way, the light readings can surface within the tracks beyond musical notation.

Automation lanes: MIDI CC curves mapped from NW readings for March 24

MIDI CC curves from lux readings (northwest window, March 24).

LFO lux-to-lfo

The full arc of a day (or a portion of the day) is normalized and exported as a custom LFO shape. Here, the light's movements become a modulation source which can be imported into a tool that supports custom LFO curves. Exported as CSV, and wav data.

NOTE: Ableton doesn't actually let you import custom curves! So I didn't end up using this function in my workflow.

Wavetable lux-to-wavetable

A single day is divided into 32 or 64 equal bins from dawn to dusk. Each bin becomes one frame of a wavetable. The resulting file can be loaded directly into a wavetable synth like Serum, Ableton's Wavetable or a hardware synth that supports it.

Screen capture: Wavetable frames stepped through in Serum (March 24 export).

Sample Tracks

Here's some sample tracks from 2 days, one clear day (March 24, 2026), and another cloudy day (March 27, 2026)

March 24, "Clear Skies"

March 24, 2026 (clear skies). Cellos trace the whole day (both for NW and SE Sensors). Other instruments play shorter slices of the arc.

I was kind of shut-in all day, even if it was clear-skied. I spent most of the day studying for interviews and doing some light spring cleaning.

For the track, while the cellos established the bed of the track and was mapped 1:1 with the readings, I opted to chop pieces of the data and arrange them out of time. I took some artistic liberty here (as in the cosmic dimension, moments obviously happen all at once!) The notation and chords are from the light data itself, so even if things are arranged out of time, it still reflects the true light movements.

Afternoons blend into mornings (the nostalgic "VHS" sounding synths are playing bits from the afternoon), the eager rise of the sun replays rapidly encouraging the dawn to break (flutes loop the early crest of the SE sensor).

I also opted to use CC data from the next and previous days to automate certain effects. The day carries lived memories of the previous one, and also the shadows of what's to come.

March 27, "Embraces 2x"

March 27, 2026 (cloudy). Chords and lead melody. Arrangement parsed from the day's light is played twice. CC lanes visible.

This day felt like an embrace.

I actually made this song for homework 3 of @school_of_song's workshop, taught by Arca. During the song share, someone mentioned that it felt like passing the mortal plane and wisp away into another realm like the light.

At the end of it all, we are beings without bodies that roam around and float away.


A conversation with BAKUDI SCREAM on the Politics of Light

Sunlight beckons through the sealed red door and windows of Sami's Kabab house, reaching through Dutch Kills playground. It's such an inviting wall-door that people mistake the spread as an entrance, which is around the corner (Me, Rohan, and a woman with a child all separately tried to enter this walldoor at different times)

Full disclosure - I am paraphrasing our conversation from memory

BAKUDI SCREAM: I am curious about how privilege surfaces in the accessibility of light. When I think of light, it's not always accessible. My first apartment had one window and barely got any natural light. In places that are war torn, the smog covers up the sky where light is diffuse or not even apparent.

plygid: That is really interesting... I didn't think of it that way. I always saw sunlight as the most pure form and resource, almost something readily available and something that even predates us.

BAKUDI SCREAM: Yeah I just thought of it, because before my apartment didn't get that much natural light. Luckily today I have more light and I do get a lot of sunlight. But when you don't, it really affects you. Like even in India there's a lot of smog, so light shows up differently. People can buy houses or apartments and pay more for more natural light (or floor to ceiling windows)

plygid: Yeah, it's almost like we've artificially obstructed something that is really prevalent. Like the structures and processes we've built creates this inequity of something that is so core and accessible, that literally obstructs the movement of light.

Later, on a separate but somewhat related topic...

BAKUDI SCREAM: Some people (in discussing a serious, urgent universal issue) make work that feels so pristine and meditative and clean, and I'm just like reviewing it, and it's technically impressive, but I'm like this is not it.

plygid: Like it almost emphasizes the detachment from the issue instead of being lived in... which in turn makes it worse because you're trying to bring attention to it. It's like lacking this material... thing

BAKUDI SCREAM: Yeah material condition.

plygid: Yeah, exactly.

...