Pebble Time 2 手表 64k 演示 demo 发布
64k demo for the Pebble Time 2
开发者发布了一款运行在 Pebble Time 2 手表上的 64k demo,使用 Mad Tea Synth 插件在 DAW 中制作,代码语言为 Tea++。
A 64k demo for the Pebble Time 2 Watch and MicroW8
If you have the watch, you can install it from the official pebble site
Mad Tea Synth
The demo is made in Mad Tea Synth, which is a plug-in used inside in a music software/DAW such as Reaper or Bitwig. It’s basically a list of events, like a kick drum, a lead synth or a visual effect.
Each event is mapped to a midi note-range, and then it is just a code box (or two code boxes for events with both sound and visuals)

In each codebox you have access to things like midi velocity vel, song position in beats beat, seconds since note-on t and relative sample rate rate etc
Then there are some basic functions to draw circles, lines, rects and text. On the audio side there’s support for a sample slot per event which is streamed using the function smp.
The codeboxes use a custom language (Tea++). It’s imperative with inferred types. I put effort into making it feel like casual scripting (but with support to compile to C and output fixed point for maths)
Music / Concept
There is a famous voice sample from the intro of Howdy Doody, a 1950s kids television show, which was later sampled in 80s/90s hiphop/dance scenes. The whole thing goes “Say kids, what time is it”, and that last part seemed useful…
I could hear the melody in my head, and I cut and tuned the words accordingly. For other samples, I reused a stab from Daydreamer and the dirty electric bass from RE-FORM
Then there’s just a lot of time-related concepts to draw from, not sure I managed to pick well though…
No synth in the synth?
The plan was to have an api for sound design, partly derived from the RE-FORM Synth. That never happened so there was nothing included except playing mono samples.
I reached for an experiment from 2013. The idea was:
- A pulse-width waveform is just animating a single bit-flip linearly from 0 to 1, -what if we combine multiple animated sign-flips using xor?
I tried many alternatives back then, and I really liked one which sounded like a bass with a lot of phaser fx:
pw = (x, pw) => { // normal pw, for reference
x -= floor(x)
return x < pw ? -1 : 1
}
phaserpw = (x, pw) => {
x -= floor(x)
return ( (x < 1/8 - pw * 1/8)
^ (x < 2/8 - pw * 2/8)
^ (x < 5/8 - pw * 2/8)
^ (x < 6/8 + pw * 2/8)
^ (x < 7/8 + pw * 1/8)
) ? -1 : 1
} So for this demo I rewrote that with a boxcar-filter, which was then used for almost all of the synth sounds in the demo. Play around / listen in Mad Tea Lab.
Ascii Rendering
Similar to some of the scenes in PRINT, but on a much smaller scale…

The implementation first render to a small grayscale buffer. It samples 3 grayscale pixels vertically, and then the difference of the 2 outer samples determines which of 2 lookup tables should be used for middle point. (The dual-lut makes edges smoother)
upper_heavy_lut = " '`:""--**!^+)<?vcYCFos35EH90BNW"
lower_heavy_lut = " ..,:;-__*!!+(>=Jc7lnIaedK$6Q&MW" Note: actual luts in demo are 255 chars long
The greetings text changes the stroke weight of each character based on the (interpolated) grayscale pixel value instead of swapping the glyph.
The grayscale source image is mostly single color with antialiased edges, so to make it more interesting, I apply vinjetting, this also gives some nice stability of filled areas when animating. Most ascii-render scenes also use 2 size levels, so larger glyphs alternate with 2x2 of half the size.
Hourglass scene
This scene was made to contrast the overall monochrome style of the demo and showcase the color display. The display supports 64 colors (4 shades per RGB channel), and I felt like I evaluated all relevant 7-color warm-to-cold palettes…

The e-paper display looks best in sunlight and warm lighting so that’s what I used for the video recording.
Outline prototyped in Mad Tea Labs func-plot, then just combined into a single function
Animating with code
I prefer stateless implementations for non-interactive/fixed animation, and utility functions that output normalized ranges.
Movement / Transition / Hits
The renderman/glsl smoothstep api is great for animating, I use these functions:
linearstep (start_time, end_time, time)
smoothstep (start_time, end_time, time)
smootherstep (start_time, end_time, time)Integrated versions (a == area under the curve)
linearstepa (start_time, end_time, time)
smoothstepa (start_time, end_time, time)
smootherstepa(start_time, end_time, time)The last ones are handy for instance when doing interpolation/glide between two notes, or if you want graphics to transition between stationary and spinning; instead of doing:
cur_spin_speed = smoothstep(0, 1, t) * max_speed
rotation += cur_spin_speedSkip the state and do:
rotation = smoothstepa(0, 1, t) * max_speedFor visually syncing “hits” like kicks, snares and stabs, I again use linearstep, but with an exponent to control the curve.
Rhythmic Time Warp
Time warping is a nice way to apply rhytmic sync to something that is already animated, like the dot-tunnel scene. I usually just pass time though a function like this one:
rhythmic_time_warp = (x, amount, len = 0.18) => {
var steps = fract(x + 0.5)
return x + amount * (smootherstep(-len, len, steps - 0.5) - steps)
}The function is designed so that time is always running the fastest exactly at integers, however, I tend to have to offset the input time slightly depending on the context to get the right feel.
Rhythmic Bounce
Simply 4 * frac(x) * frac(-x)
Tooling failure
As I was working on the visuals for the demo, I felt it was generally nicer to just use beats(global time) with a manual offset, rather than t, becuase when using t you cannot start previewing in the middle of an event. But that means having to update the offset if I move the scene around, so a key promise of visual editing in the DAW fell flat, which was disappointing…
Going 64k
Actual hardware seem capable, but for better battery life the Pebble SDK sets limits: 128kb RAM, 256kb flash storage, and no floating point support. There was also a bug in the pebble SDK limiting the binary to 64k. I felt I would not have the time to make good use of the flash storage anyway so I went for 64k for everything.
To really take advantage of the hardware, you’d have to change the firmware/OS. But this would make it much more cumbersome for people to actually run the demo…
Improving watch spreaker quality
The watch support audio playback at 16-bit 16 khz, but the challange is the tiny speaker. I suspected I could improve audio quality by just applying a few band/notch filters (reducing the annoying resonant high frequencies). This turned out to be harder than expected, as distortion quickly appeared when trying to equalize the frequency profile. After a few iterations I settled on 3 boosted peak filters + gain reduction. Just tweaked by ear, and already sacrificing a lot of volume for better quality. You can toggle this on/off using the down button on the watch.

Show-time!
I worked hard to finish this for Evoke 2026, however I’d failed to notice that the party had adopted a hard anti-ai stance. So the demo was disqualified as LLMs were used during tools development. Instead I submitted it to Xenium a week later, but as a remote entry unfortunately as I did not have energy for more travel. Disqualification had some good outcomes, as the delay gave time to do a MicroW8 port and improve the video edit.
Demo scope vs tool scope
Making this demo helped drive the development of the tool, however the demo scope is small compared to the tooling. Feel free to continue reading about the Mad Tea Synth Project
来源:Hacker News · teadrinker.net